feat(server): enhance script execution and workflow management

- Introduced compileWorkflowScripts function to parse and validate workflow scripts, supporting both linear and DAG execution modes.
- Added runLinearSteps and runDagSteps functions for executing compiled workflows based on their structure.
- Updated createRegistry function to utilize the new workflow compilation and execution logic.
- Enhanced script sandbox with meta extraction and instantiation capabilities for better script management.
- Improved API endpoints to return script metadata and handle errors more effectively.
- Removed deprecated scripts and updated workflows to reflect new structure and functionality.
This commit is contained in:
2026-08-14 19:14:44 +07:00
parent ddff33cf49
commit 049f5c56e3
21 changed files with 809 additions and 87 deletions
+24 -2
View File
@@ -9,11 +9,12 @@ import {
} from "../api/hooks.js";
import { CodeEditor } from "../components/CodeEditor.jsx";
import { LogViewer } from "../components/LogViewer.jsx";
import { ScriptMetaPanel } from "../components/ScriptMetaPanel.jsx";
import { StatusBadge } from "../lib/format.jsx";
import {
DEFAULT_INPUT_CONTEXT,
NEW_SCRIPT_TEMPLATE,
normalizeScriptName,
contextFromMeta,
prettyJson,
} from "../lib/script.js";
@@ -29,6 +30,7 @@ export function ScriptDryRunPage() {
const [content, setContent] = useState("");
const [inputJson, setInputJson] = useState(DEFAULT_INPUT_CONTEXT);
const [inputTouched, setInputTouched] = useState(false);
const [outputJson, setOutputJson] = useState("");
const [logs, setLogs] = useState([]);
const [runStatus, setRunStatus] = useState(null);
@@ -56,6 +58,14 @@ export function ScriptDryRunPage() {
const backHref = `/scripts/${encodeURIComponent(name)}/edit`;
const lastRun = dryRun.data;
const meta = lastRun?.meta ?? existing.data?.meta ?? null;
const metaError = lastRun?.metaError ?? existing.data?.metaError ?? null;
useEffect(() => {
if (inputTouched) return;
if (!existing.data?.meta) return;
setInputJson(prettyJson(contextFromMeta(existing.data.meta)));
}, [existing.data?.meta, inputTouched]);
useEffect(() => {
if (!lastRun) return;
@@ -170,7 +180,10 @@ export function ScriptDryRunPage() {
<CodeEditor
language="json"
value={inputJson}
onChange={setInputJson}
onChange={(value) => {
setInputTouched(true);
setInputJson(value);
}}
height="100%"
/>
</div>
@@ -208,6 +221,15 @@ export function ScriptDryRunPage() {
</section>
</div>
<details className="collapse collapse-arrow shrink-0 border border-base-300 bg-base-100">
<summary className="collapse-title min-h-0 py-2 text-sm font-semibold">
Input / output
</summary>
<div className="collapse-content">
<ScriptMetaPanel meta={meta} metaError={metaError} />
</div>
</details>
<LogViewer logs={logs} className="h-48 shrink-0 lg:h-56" />
</div>
);
+10
View File
@@ -4,6 +4,7 @@ import { LuArrowLeft, LuPlay, LuSave } from "react-icons/lu";
import { errorMessage } from "../api/client.js";
import { useSaveScript, useScript } from "../api/hooks.js";
import { CodeEditor } from "../components/CodeEditor.jsx";
import { ScriptMetaPanel } from "../components/ScriptMetaPanel.jsx";
import { NEW_SCRIPT_TEMPLATE, normalizeScriptName } from "../lib/script.js";
export function ScriptNewPage() {
@@ -157,6 +158,15 @@ export function ScriptEditPage() {
<CodeEditor language="javascript" value={content} onChange={setContent} height="100%" />
</form>
<details className="collapse collapse-arrow shrink-0 border border-base-300 bg-base-100">
<summary className="collapse-title min-h-0 py-2 text-sm font-semibold">
Input / output
</summary>
<div className="collapse-content">
<ScriptMetaPanel meta={existing.data?.meta} metaError={existing.data?.metaError} />
</div>
</details>
{save.isError ? (
<p className="text-error text-sm shrink-0">{errorMessage(save.error)}</p>
) : null}
+21 -8
View File
@@ -34,30 +34,42 @@ export function ScriptsPage() {
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th className="w-28" />
</tr>
</thead>
<tbody>
{scripts.map((s) => (
<tr key={s} className="hover">
{scripts.map((s) => {
const name = typeof s === "string" ? s : s.name;
const description = typeof s === "string" ? "" : s.meta?.description;
const metaError = typeof s === "string" ? null : s.metaError;
return (
<tr key={name} className="hover">
<td className="font-mono">
<Link className="link" to={`/scripts/${encodeURIComponent(s)}/edit`}>
{s}
<Link className="link" to={`/scripts/${encodeURIComponent(name)}/edit`}>
{name}
</Link>
</td>
<td className="max-w-xl truncate text-sm opacity-80">
{metaError ? (
<span className="text-error">{metaError}</span>
) : (
description || "—"
)}
</td>
<td className="text-right whitespace-nowrap">
<button
type="button"
className="btn btn-ghost btn-xs"
title="Dry run"
onClick={() =>
navigate(`/scripts/${encodeURIComponent(s)}/dry-run`)
navigate(`/scripts/${encodeURIComponent(name)}/dry-run`)
}
>
<LuPlay className="size-4" />
</button>
<Link
to={`/scripts/${encodeURIComponent(s)}/edit`}
to={`/scripts/${encodeURIComponent(name)}/edit`}
className="btn btn-ghost btn-xs"
title="Edit"
>
@@ -67,13 +79,14 @@ export function ScriptsPage() {
type="button"
className="btn btn-ghost btn-xs text-error"
title="Delete"
onClick={() => setConfirmDelete(s)}
onClick={() => setConfirmDelete(name)}
>
<LuTrash2 className="size-4" />
</button>
</td>
</tr>
))}
);
})}
</tbody>
</table>
</div>