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
+39 -1
View File
@@ -1,6 +1,19 @@
export const NEW_SCRIPT_TEMPLATE = `export default async function main(ctx) {
export const NEW_SCRIPT_TEMPLATE = `async function main(ctx) {
return ctx;
}
main.meta = {
description: "",
config: {},
input: {},
output: {},
example: {
data: {},
config: {},
},
};
export default main;
`;
export const DEFAULT_INPUT_CONTEXT = `{
@@ -23,3 +36,28 @@ export function prettyJson(value) {
return String(value);
}
}
function defaultsFromFields(fields) {
if (fields == null || typeof fields !== "object" || Array.isArray(fields)) return {};
/** @type {Record<string, unknown>} */
const out = {};
for (const [key, spec] of Object.entries(fields)) {
if (spec && typeof spec === "object" && "default" in spec) {
out[key] = spec.default;
}
}
return out;
}
export function contextFromMeta(meta) {
if (meta?.example && typeof meta.example === "object" && !Array.isArray(meta.example)) {
return {
data: "data" in meta.example ? meta.example.data : {},
config: "config" in meta.example ? meta.example.config ?? {} : {},
};
}
return {
data: defaultsFromFields(meta?.input),
config: defaultsFromFields(meta?.config),
};
}
+69 -16
View File
@@ -8,11 +8,30 @@ function sanitize(label) {
}
function parseStep(step) {
if (typeof step === "string") return step;
if (step?.script) return step.script;
if (typeof step === "string") {
return { script: step, id: null, needs: null };
}
if (step?.script) {
return {
script: step.script,
id: typeof step.id === "string" && step.id ? step.id : null,
needs: step.needs ?? null,
};
}
return null;
}
function parentIdsFromNeeds(needs) {
if (needs == null) return null;
if (Array.isArray(needs)) {
return needs.filter((x) => typeof x === "string" && x.length > 0);
}
if (typeof needs === "object") {
return Object.values(needs).filter((x) => typeof x === "string" && x.length > 0);
}
return [];
}
export function workflowToArchitecture(parsed) {
/** @type {Record<string, string>} */
const scriptIds = {};
@@ -38,27 +57,61 @@ export function workflowToArchitecture(parsed) {
});
const scripts = Array.isArray(parsed.scripts) ? parsed.scripts : [];
let prev = null;
let firstScript = null;
const parsedSteps = [];
scripts.forEach((step, i) => {
const script = parseStep(step);
if (!script) return;
const id = `s${i}`;
scriptIds[id] = script;
lines.push(` service ${id}(server)[${sanitize(script)}] in ${groupId}`);
if (!firstScript) firstScript = id;
if (prev) lines.push(` ${prev}:R -- L:${id}`);
prev = id;
const parsedStep = parseStep(step);
if (!parsedStep) return;
parsedSteps.push({ ...parsedStep, mermaidId: `s${i}` });
});
// All triggers point at the first script (not chained to each other).
if (firstScript) {
const dagMode = parsedSteps.some((s) => s.needs != null);
/** @type {Record<string, string>} */
const idToMermaid = {};
for (const s of parsedSteps) {
if (s.id) idToMermaid[s.id] = s.mermaidId;
}
for (const s of parsedSteps) {
scriptIds[s.mermaidId] = s.script;
lines.push(
` service ${s.mermaidId}(server)[${sanitize(s.id || s.script)}] in ${groupId}`,
);
}
if (dagMode) {
const roots = [];
for (const s of parsedSteps) {
const parents = parentIdsFromNeeds(s.needs);
if (!parents || parents.length === 0) {
roots.push(s.mermaidId);
continue;
}
for (const pid of parents) {
const from = idToMermaid[pid];
if (from) lines.push(` ${from}:R -- L:${s.mermaidId}`);
}
}
for (const tid of triggerIds) {
lines.push(` ${tid}:R -- L:${firstScript}`);
for (const rid of roots) {
lines.push(` ${tid}:R -- L:${rid}`);
}
}
} else {
let prev = null;
let firstScript = null;
for (const s of parsedSteps) {
if (!firstScript) firstScript = s.mermaidId;
if (prev) lines.push(` ${prev}:R -- L:${s.mermaidId}`);
prev = s.mermaidId;
}
if (firstScript) {
for (const tid of triggerIds) {
lines.push(` ${tid}:R -- L:${firstScript}`);
}
}
}
// Keep multiple triggers in one column so they don't overlap.
if (triggerIds.length >= 2) {
lines.push(` align column ${triggerIds.join(" ")}`);
}