@fieldwangai/agentflow 0.1.159 → 0.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/auth.mjs +231 -14
- package/bin/lib/catalog-flows.mjs +12 -1
- package/bin/lib/cli-auth-page.mjs +103 -0
- package/bin/lib/control-while.mjs +336 -0
- package/bin/lib/flow-dsl/codegen.mjs +74 -11
- package/bin/lib/flow-dsl/ir.mjs +33 -2
- package/bin/lib/flow-dsl/lint.mjs +128 -3
- package/bin/lib/flow-dsl/parser.mjs +168 -4
- package/bin/lib/marketplace.mjs +10 -2
- package/bin/lib/node-package-manifest.mjs +7 -2
- package/bin/lib/node-ui-kit.mjs +156 -0
- package/bin/lib/paths.mjs +2 -0
- package/bin/lib/skill-runtime.mjs +6 -0
- package/bin/lib/table.mjs +2 -3
- package/bin/lib/ui-server.mjs +220 -17
- package/bin/lib/workspace-draft.mjs +74 -0
- package/bin/lib/workspace-flow-store.mjs +11 -2
- package/bin/lib/workspace-graph-merge.mjs +3 -0
- package/bin/lib/workspace-routes.mjs +307 -5
- package/bin/lib/workspace-server.mjs +598 -11
- package/bin/pipeline/validate-flow.mjs +3 -3
- package/builtin/nodes/control_parse_json.md +25 -0
- package/builtin/nodes/control_subflow_call.md +27 -0
- package/builtin/nodes/control_while.md +124 -0
- package/builtin/nodes/provide_json.md +14 -0
- package/builtin/nodes/workspace_subflow_input.md +14 -0
- package/builtin/pipelines/new/workspace.flow.js +1 -1
- package/builtin/pipelines/subflow-preview/workspace.flow.js +29 -0
- package/builtin/pipelines/subflow-preview/workspace.layout.json +38 -0
- package/builtin/pipelines/subflow-preview/workspace.nodes.json +19 -0
- package/builtin/pipelines/subflow-preview/workspace.state.json +93 -0
- package/builtin/pipelines/while-subflow-preview/workspace.flow.js +55 -0
- package/builtin/pipelines/while-subflow-preview/workspace.layout.json +66 -0
- package/builtin/pipelines/while-subflow-preview/workspace.nodes.json +49 -0
- package/builtin/pipelines/while-subflow-preview/workspace.state.json +194 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CY19DsYq.js → WorkflowAssistantThread-CTrXOZ00.js} +1 -1
- package/builtin/web-ui/dist/assets/index-5uJFccdX.css +1 -0
- package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +873 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +6 -1
- package/reference/flow-control-capabilities.md +41 -16
- package/shared/slot-types.js +58 -0
- package/skills/agentflow-author-flow/SKILL.md +81 -8
- package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
- package/skills/agentflow-cli/SKILL.md +130 -25
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14765 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_agent_toBool.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +124 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +73 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
- package/skills/agentflow-cli/runtime/package.json +6 -0
- package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +393 -28
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
- package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
- package/skills/agentflow-flow-dsl/SKILL.md +83 -10
- package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +3 -0
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +230 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +3 -3
- package/skills/agentflow-flow-recipes/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +44 -13
- package/skills/agentflow-node-reference/references/builtin-nodes.md +25 -0
- package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
- package/builtin/web-ui/dist/assets/index-B9ppXv7e.css +0 -1
- package/builtin/web-ui/dist/assets/index-CdEQWRrp.js +0 -872
- package/skills/agentflow-workspace-graph/SKILL.md +0 -85
|
@@ -13,6 +13,18 @@ import { readAuthUsers } from "./auth.mjs";
|
|
|
13
13
|
import { listFlowsJson, listNodesJson } from "./catalog-flows.mjs";
|
|
14
14
|
import { startComposerAgent } from "./composer-agent.mjs";
|
|
15
15
|
import { loadResourcesForSkillKeys } from "./composer-skill-router.mjs";
|
|
16
|
+
import {
|
|
17
|
+
MAX_CONTROL_WHILE_STEP_STDERR_BYTES,
|
|
18
|
+
MAX_CONTROL_WHILE_STEP_STDOUT_BYTES,
|
|
19
|
+
controlWhileCheckpointFingerprint,
|
|
20
|
+
controlWhileIdempotencyKey,
|
|
21
|
+
normalizeControlWhileConfig,
|
|
22
|
+
normalizeControlWhileInitialState,
|
|
23
|
+
parseControlWhileStepResult,
|
|
24
|
+
resolveControlWhileCheckpoint,
|
|
25
|
+
runControlWhile,
|
|
26
|
+
serializeControlWhileState,
|
|
27
|
+
} from "./control-while.mjs";
|
|
16
28
|
import { graphToFlowFiles } from "./flow-dsl/index.mjs";
|
|
17
29
|
import { buildGitContext, inferGitRepoRootFromWorktree, loadGitWorktree, normalizeGitContext, runGit, sanitizeWorktreeName, unloadGitWorktree } from "./git-worktree.mjs";
|
|
18
30
|
import { createGitLabMergeRequest } from "./gitlab-mr.mjs";
|
|
@@ -29,9 +41,10 @@ import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
|
29
41
|
import { sendWecomAppMarkdown, sendWecomGroupMarkdown } from "./wecom.mjs";
|
|
30
42
|
import { getWorkspaceCollaborationByFlow, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, workspaceCollaborationAccess, workspaceCollaborationSummary } from "./workspace-collaboration.mjs";
|
|
31
43
|
import { FLOW_SOURCE_FILENAME, WORKSPACE_GRAPH_FILENAME, WorkspaceFlowParseError, readWorkspaceGraphFiles, readWorkspaceRunFingerprints, writeWorkspaceGraphFiles } from "./workspace-flow-store.mjs";
|
|
32
|
-
import { createWorkspaceRunController } from "./workspace-run-controller.mjs";
|
|
44
|
+
import { createWorkspaceRunController, terminateWorkspaceChild } from "./workspace-run-controller.mjs";
|
|
33
45
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession } from "./workspace-run-logs.mjs";
|
|
34
46
|
import { splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
47
|
+
import { isWorkspaceDraftDir } from "./workspace-draft.mjs";
|
|
35
48
|
import { getPipelineFiles } from "./workspace-tree.mjs";
|
|
36
49
|
import { spawn } from "child_process";
|
|
37
50
|
import crypto from "crypto";
|
|
@@ -1122,6 +1135,7 @@ function normalizeWorkspaceGraphPayload(payload) {
|
|
|
1122
1135
|
instances: graph?.instances && typeof graph.instances === "object" && !Array.isArray(graph.instances) ? graph.instances : {},
|
|
1123
1136
|
edges: Array.isArray(graph?.edges) ? graph.edges : [],
|
|
1124
1137
|
ui: graph?.ui && typeof graph.ui === "object" ? graph.ui : { nodePositions: {} },
|
|
1138
|
+
subflows: graph?.subflows && typeof graph.subflows === "object" && !Array.isArray(graph.subflows) ? graph.subflows : {},
|
|
1125
1139
|
updatedAt: new Date().toISOString(),
|
|
1126
1140
|
};
|
|
1127
1141
|
}
|
|
@@ -1130,7 +1144,7 @@ export function workspaceRunTouchedNodeIds(result) {
|
|
|
1130
1144
|
const ids = new Set();
|
|
1131
1145
|
// A deferred run has only executed the prefix ending at the waiting node. Merging the
|
|
1132
1146
|
// complete plan here would overwrite unrelated edits made while Jenkins is running.
|
|
1133
|
-
if (!result?.deferred) {
|
|
1147
|
+
if (!result?.deferred && !(Array.isArray(result?.pauseNodeIds) && result.pauseNodeIds.length)) {
|
|
1134
1148
|
for (const id of Array.isArray(result?.order) ? result.order : []) {
|
|
1135
1149
|
const text = String(id || "").trim();
|
|
1136
1150
|
if (text) ids.add(text);
|
|
@@ -1478,6 +1492,7 @@ export function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {})
|
|
|
1478
1492
|
requestedFlowSource: flowSource,
|
|
1479
1493
|
workspaceId: collaboration?.id || workspaceId,
|
|
1480
1494
|
archived,
|
|
1495
|
+
draft: isWorkspaceDraftDir(result.path),
|
|
1481
1496
|
collaboration,
|
|
1482
1497
|
collaborationAccess: workspaceCollaborationAccess(collaboration, opts.userId),
|
|
1483
1498
|
};
|
|
@@ -2490,6 +2505,46 @@ export function workspacePublishAgentOutputFiles(structured, runPackage) {
|
|
|
2490
2505
|
: base;
|
|
2491
2506
|
}
|
|
2492
2507
|
|
|
2508
|
+
/**
|
|
2509
|
+
* Node package outputs use files as a transport contract, not as the semantic
|
|
2510
|
+
* value of every slot. Text/json/bool slots receive the file contents; only
|
|
2511
|
+
* file/image slots intentionally expose a published artifact path.
|
|
2512
|
+
*/
|
|
2513
|
+
export function workspaceMaterializeNodePackageOutputValues(structured, runPackage, instance) {
|
|
2514
|
+
if (!structured || !runPackage || !instance) return structured;
|
|
2515
|
+
const slots = (Array.isArray(instance.output) ? instance.output : [])
|
|
2516
|
+
.filter((slot) => !isWorkspaceSemanticOutputSlot(slot));
|
|
2517
|
+
if (!slots.length) return structured;
|
|
2518
|
+
const outParams = { ...(structured.outParams || {}) };
|
|
2519
|
+
let result = structured.result;
|
|
2520
|
+
let resultFile = structured.resultFile;
|
|
2521
|
+
let changed = false;
|
|
2522
|
+
for (const slot of slots) {
|
|
2523
|
+
const name = String(slot?.name || "").trim();
|
|
2524
|
+
if (!name) continue;
|
|
2525
|
+
const type = String(slot?.type || "text").trim().toLowerCase();
|
|
2526
|
+
if (type === "file" || type === "image") continue;
|
|
2527
|
+
const primary = workspaceIsPrimaryOutputSlot(slot, instance.output);
|
|
2528
|
+
const fileKey = `${name}File`;
|
|
2529
|
+
const relPath = primary ? resultFile : outParams[fileKey];
|
|
2530
|
+
const clean = workspaceSafeNodeOutputRelPath(relPath);
|
|
2531
|
+
if (!clean) continue;
|
|
2532
|
+
const source = workspaceNodeOutputCandidates(runPackage, clean)
|
|
2533
|
+
.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
2534
|
+
if (!source) continue;
|
|
2535
|
+
const value = fs.readFileSync(source, "utf8");
|
|
2536
|
+
if (primary) {
|
|
2537
|
+
result = value;
|
|
2538
|
+
resultFile = "";
|
|
2539
|
+
} else {
|
|
2540
|
+
outParams[name] = value;
|
|
2541
|
+
delete outParams[fileKey];
|
|
2542
|
+
}
|
|
2543
|
+
changed = true;
|
|
2544
|
+
}
|
|
2545
|
+
return changed ? { ...structured, result, resultFile, outParams } : structured;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2493
2548
|
function workspaceOutputFieldForSlot(slot, slots = null) {
|
|
2494
2549
|
const name = String(slot?.name || "").trim();
|
|
2495
2550
|
if (!name || workspaceIsPrimaryOutputSlot(slot, slots)) return "result";
|
|
@@ -2939,6 +2994,50 @@ function workspaceControlIfBranchToSourceHandle(branch) {
|
|
|
2939
2994
|
// 从此对它的搜索全部静默返回空。
|
|
2940
2995
|
const FINGERPRINT_SEP = "\u0000";
|
|
2941
2996
|
|
|
2997
|
+
function workspaceSubflowFingerprintDescriptor(graph, subflowId, stack = new Set()) {
|
|
2998
|
+
const id = String(subflowId || "").trim();
|
|
2999
|
+
const subflow = graph?.subflows?.[id];
|
|
3000
|
+
if (!id || !subflow) return { id, missing: true };
|
|
3001
|
+
if (stack.has(id)) return { id, recursive: true };
|
|
3002
|
+
|
|
3003
|
+
const nextStack = new Set(stack);
|
|
3004
|
+
nextStack.add(id);
|
|
3005
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
3006
|
+
const memberSet = new Set(Array.isArray(subflow.nodeIds) ? subflow.nodeIds.map(String) : []);
|
|
3007
|
+
const memberShape = [...memberSet].sort().map((memberId) => {
|
|
3008
|
+
const member = instances[memberId] || {};
|
|
3009
|
+
return [
|
|
3010
|
+
memberId,
|
|
3011
|
+
String(member.definitionId || ""),
|
|
3012
|
+
String(member.body || ""),
|
|
3013
|
+
String(member.script || ""),
|
|
3014
|
+
String(member.scriptRef || ""),
|
|
3015
|
+
String(member.subflowId || ""),
|
|
3016
|
+
String(member.conditionSubflowId || ""),
|
|
3017
|
+
String(member.bodySubflowId || ""),
|
|
3018
|
+
(member.input || []).map((slot) => [String(slot?.name || ""), String(slot?.type || ""), workspaceSlotValue(slot)]),
|
|
3019
|
+
];
|
|
3020
|
+
});
|
|
3021
|
+
const internalEdges = (graph?.edges || []).filter((edge) => (
|
|
3022
|
+
memberSet.has(String(edge?.source || "")) && memberSet.has(String(edge?.target || ""))
|
|
3023
|
+
));
|
|
3024
|
+
const nestedIds = new Set();
|
|
3025
|
+
for (const memberId of memberSet) {
|
|
3026
|
+
const member = instances[memberId] || {};
|
|
3027
|
+
if (String(member.definitionId || "") === "control_subflow_call" && member.subflowId) {
|
|
3028
|
+
nestedIds.add(String(member.subflowId));
|
|
3029
|
+
}
|
|
3030
|
+
if (String(member.definitionId || "") === "control_while") {
|
|
3031
|
+
if (member.conditionSubflowId) nestedIds.add(String(member.conditionSubflowId));
|
|
3032
|
+
if (member.bodySubflowId) nestedIds.add(String(member.bodySubflowId));
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
const nested = [...nestedIds].sort().map((nestedId) => (
|
|
3036
|
+
workspaceSubflowFingerprintDescriptor(graph, nestedId, nextStack)
|
|
3037
|
+
));
|
|
3038
|
+
return { id, subflow, memberShape, internalEdges, nested };
|
|
3039
|
+
}
|
|
3040
|
+
|
|
2942
3041
|
export function workspaceNodeInputFingerprint(graph, nodeId, memo = new Map(), stack = new Set()) {
|
|
2943
3042
|
const id = String(nodeId || "");
|
|
2944
3043
|
if (memo.has(id)) return memo.get(id);
|
|
@@ -2957,6 +3056,20 @@ export function workspaceNodeInputFingerprint(graph, nodeId, memo = new Map(), s
|
|
|
2957
3056
|
marketplaceRef ? "" : String(instance.script || ""),
|
|
2958
3057
|
String(instance.model || ""),
|
|
2959
3058
|
];
|
|
3059
|
+
if (String(instance.definitionId || "") === "control_subflow_call") {
|
|
3060
|
+
const subflowId = String(instance.subflowId || "");
|
|
3061
|
+
parts.push(`subflow=${JSON.stringify(workspaceSubflowFingerprintDescriptor(graph, subflowId))}`);
|
|
3062
|
+
}
|
|
3063
|
+
if (String(instance.definitionId || "") === "control_while") {
|
|
3064
|
+
const conditionSubflowId = String(instance.conditionSubflowId || "");
|
|
3065
|
+
const bodySubflowId = String(instance.bodySubflowId || "");
|
|
3066
|
+
if (conditionSubflowId || bodySubflowId) {
|
|
3067
|
+
parts.push(`whileSubflows=${JSON.stringify({
|
|
3068
|
+
condition: workspaceSubflowFingerprintDescriptor(graph, conditionSubflowId),
|
|
3069
|
+
body: workspaceSubflowFingerprintDescriptor(graph, bodySubflowId),
|
|
3070
|
+
})}`);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
2960
3073
|
|
|
2961
3074
|
const incoming = new Map();
|
|
2962
3075
|
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
@@ -3005,7 +3118,7 @@ function workspaceEdgeHasCachedOutput(graph, edge, scopedRoot = "", opts = {}) {
|
|
|
3005
3118
|
if (!source) return false;
|
|
3006
3119
|
const defId = String(source.definitionId || "");
|
|
3007
3120
|
// provide.* 不执行,它的值就是作者填的;没有「上次跑出来的」这回事,也就无从失效
|
|
3008
|
-
if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file" || defId === "provide_password") {
|
|
3121
|
+
if (defId === "provide_str" || defId === "provide_json" || defId === "provide_bool" || defId === "provide_file" || defId === "provide_password") {
|
|
3009
3122
|
return Boolean(String(workspaceInstanceText(source) || "").trim());
|
|
3010
3123
|
}
|
|
3011
3124
|
// 有值还不够,得是**这套输入**跑出来的值。指纹对不上说明上游或节点自身改过,重跑
|
|
@@ -4634,6 +4747,9 @@ async function workspaceRunToolNodejsScript({
|
|
|
4634
4747
|
emit,
|
|
4635
4748
|
signal,
|
|
4636
4749
|
onActiveChild,
|
|
4750
|
+
stderrAsStatus = false,
|
|
4751
|
+
maxStdoutBytes = Infinity,
|
|
4752
|
+
maxStderrBytes = Infinity,
|
|
4637
4753
|
}) {
|
|
4638
4754
|
const scriptRef = String(instance?.scriptRef || "").trim();
|
|
4639
4755
|
const scriptAbs = scriptRef ? workspaceResolveFlowFile(scopedRoot, scriptRef, "scriptRef") : "";
|
|
@@ -4692,6 +4808,9 @@ async function workspaceRunToolNodejsScript({
|
|
|
4692
4808
|
if (typeof onActiveChild === "function") onActiveChild(child, { processGroup });
|
|
4693
4809
|
let stdout = "";
|
|
4694
4810
|
let stderr = "";
|
|
4811
|
+
let stdoutBytes = 0;
|
|
4812
|
+
let stderrBytes = 0;
|
|
4813
|
+
let outputLimitError = null;
|
|
4695
4814
|
let settled = false;
|
|
4696
4815
|
const finish = (callback) => {
|
|
4697
4816
|
if (settled) return;
|
|
@@ -4702,13 +4821,33 @@ async function workspaceRunToolNodejsScript({
|
|
|
4702
4821
|
child.stdout.setEncoding("utf-8");
|
|
4703
4822
|
child.stderr.setEncoding("utf-8");
|
|
4704
4823
|
child.stdout.on("data", (chunk) => {
|
|
4705
|
-
|
|
4824
|
+
if (outputLimitError) return;
|
|
4825
|
+
const text = String(chunk);
|
|
4826
|
+
stdoutBytes += Buffer.byteLength(text, "utf-8");
|
|
4827
|
+
if (stdoutBytes > maxStdoutBytes) {
|
|
4828
|
+
outputLimitError = new Error(`script stdout exceeded ${maxStdoutBytes} bytes`);
|
|
4829
|
+
terminateWorkspaceChild(child, { processGroup });
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
stdout += text;
|
|
4706
4833
|
});
|
|
4707
4834
|
child.stderr.on("data", (chunk) => {
|
|
4708
|
-
|
|
4835
|
+
if (outputLimitError) return;
|
|
4836
|
+
const text = String(chunk);
|
|
4837
|
+
stderrBytes += Buffer.byteLength(text, "utf-8");
|
|
4838
|
+
if (stderrBytes > maxStderrBytes) {
|
|
4839
|
+
outputLimitError = new Error(`script stderr exceeded ${maxStderrBytes} bytes`);
|
|
4840
|
+
terminateWorkspaceChild(child, { processGroup });
|
|
4841
|
+
return;
|
|
4842
|
+
}
|
|
4843
|
+
stderr += text;
|
|
4709
4844
|
});
|
|
4710
4845
|
child.on("error", (error) => finish(() => reject(error)));
|
|
4711
4846
|
child.on("close", (code) => {
|
|
4847
|
+
if (outputLimitError) {
|
|
4848
|
+
finish(() => reject(outputLimitError));
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4712
4851
|
if (signal?.aborted) {
|
|
4713
4852
|
finish(() => {
|
|
4714
4853
|
const error = new Error("Workspace run stopped");
|
|
@@ -4718,7 +4857,8 @@ async function workspaceRunToolNodejsScript({
|
|
|
4718
4857
|
return;
|
|
4719
4858
|
}
|
|
4720
4859
|
if (stderr.trim()) {
|
|
4721
|
-
emit?.({ type: "
|
|
4860
|
+
if (stderrAsStatus) emit?.({ type: "status", line: `[step stderr] ${stderr.trim().slice(-4000)}` });
|
|
4861
|
+
else emit?.({ type: "natural", kind: "warning", text: `[script stderr]\n${stderr.trim().slice(-4000)}` });
|
|
4722
4862
|
}
|
|
4723
4863
|
if (code !== 0) {
|
|
4724
4864
|
finish(() => reject(new Error(`tool_nodejs script exited ${code}${stderr.trim() ? `: ${stderr.trim().slice(-800)}` : ""}`)));
|
|
@@ -4744,6 +4884,140 @@ function workspaceNodeModelKey(instance, fallback = "") {
|
|
|
4744
4884
|
return String(fallback || "").trim();
|
|
4745
4885
|
}
|
|
4746
4886
|
|
|
4887
|
+
function workspaceAssertWhileSubflowContract(graph, nodeId, conditionSubflowId, bodySubflowId) {
|
|
4888
|
+
const conditionId = String(conditionSubflowId || "").trim();
|
|
4889
|
+
const bodyId = String(bodySubflowId || "").trim();
|
|
4890
|
+
if (!conditionId || !bodyId) {
|
|
4891
|
+
throw new Error(`control.while ${nodeId} requires both conditionSubflowId and bodySubflowId`);
|
|
4892
|
+
}
|
|
4893
|
+
if (conditionId === bodyId) {
|
|
4894
|
+
throw new Error(`control.while ${nodeId} condition and body must be different subflows`);
|
|
4895
|
+
}
|
|
4896
|
+
const condition = graph?.subflows?.[conditionId];
|
|
4897
|
+
const body = graph?.subflows?.[bodyId];
|
|
4898
|
+
if (!condition) throw new Error(`control.while ${nodeId} references missing condition subflow ${conditionId}`);
|
|
4899
|
+
if (!body) throw new Error(`control.while ${nodeId} references missing body subflow ${bodyId}`);
|
|
4900
|
+
for (const name of ["state", "iteration"]) {
|
|
4901
|
+
if (!condition.inputs?.[name]) throw new Error(`control.while ${nodeId} condition subflow ${conditionId} requires input ${name}`);
|
|
4902
|
+
}
|
|
4903
|
+
if (!condition.outputs?.decision) {
|
|
4904
|
+
throw new Error(`control.while ${nodeId} condition subflow ${conditionId} requires output decision`);
|
|
4905
|
+
}
|
|
4906
|
+
for (const name of ["state", "iteration", "idempotencyKey"]) {
|
|
4907
|
+
if (!body.inputs?.[name]) throw new Error(`control.while ${nodeId} body subflow ${bodyId} requires input ${name}`);
|
|
4908
|
+
}
|
|
4909
|
+
if (!body.outputs?.state) {
|
|
4910
|
+
throw new Error(`control.while ${nodeId} body subflow ${bodyId} requires output state`);
|
|
4911
|
+
}
|
|
4912
|
+
return { conditionId, bodyId, condition, body };
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
async function workspaceRunSubflowFrame({
|
|
4916
|
+
root,
|
|
4917
|
+
scopedRoot,
|
|
4918
|
+
payload,
|
|
4919
|
+
userCtx,
|
|
4920
|
+
opts,
|
|
4921
|
+
graph,
|
|
4922
|
+
parentNodeId,
|
|
4923
|
+
parentDefinitionId,
|
|
4924
|
+
subflowId,
|
|
4925
|
+
inputValues = {},
|
|
4926
|
+
signal = null,
|
|
4927
|
+
emit,
|
|
4928
|
+
eventContext = {},
|
|
4929
|
+
}) {
|
|
4930
|
+
const id = String(subflowId || "").trim();
|
|
4931
|
+
const subflow = graph?.subflows?.[id];
|
|
4932
|
+
if (!subflow) throw new Error(`Subflow ${id || "(empty)"} does not exist`);
|
|
4933
|
+
const callStack = Array.isArray(opts?.subflowCallStack) ? opts.subflowCallStack : [];
|
|
4934
|
+
if (callStack.includes(id)) {
|
|
4935
|
+
throw new Error(`Recursive subflow call is not allowed: ${[...callStack, id].join(" -> ")}`);
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
const memberSet = new Set((subflow.nodeIds || []).map(String));
|
|
4939
|
+
if (!memberSet.size) throw new Error(`Subflow ${id} has no member nodes`);
|
|
4940
|
+
const callFrameId = `${parentNodeId}:${String(eventContext?.whileRole || "call")}:${String(eventContext?.iteration || "0")}:${crypto.randomBytes(6).toString("hex")}`;
|
|
4941
|
+
const childGraph = typeof structuredClone === "function"
|
|
4942
|
+
? structuredClone(graph)
|
|
4943
|
+
: JSON.parse(JSON.stringify(graph));
|
|
4944
|
+
const syntheticRunId = `__subflow_run_${crypto.randomBytes(6).toString("hex")}`;
|
|
4945
|
+
childGraph.instances[syntheticRunId] = {
|
|
4946
|
+
definitionId: "workspace_run",
|
|
4947
|
+
label: `${subflow.label || id} · call frame`,
|
|
4948
|
+
input: [{ type: "node", name: "prev", value: "" }],
|
|
4949
|
+
output: [{ type: "node", name: "next", value: "" }],
|
|
4950
|
+
};
|
|
4951
|
+
childGraph.edges = (childGraph.edges || []).filter((edge) => (
|
|
4952
|
+
memberSet.has(String(edge?.source || "")) && memberSet.has(String(edge?.target || ""))
|
|
4953
|
+
));
|
|
4954
|
+
for (const rootId of subflow.roots || []) {
|
|
4955
|
+
const rootInstance = childGraph.instances?.[rootId];
|
|
4956
|
+
const prevIndex = (rootInstance?.input || []).findIndex((slot) => String(slot?.name || "") === "prev");
|
|
4957
|
+
if (prevIndex < 0) throw new Error(`Subflow ${id} root ${rootId} has no prev input`);
|
|
4958
|
+
childGraph.edges.push({
|
|
4959
|
+
source: syntheticRunId,
|
|
4960
|
+
target: rootId,
|
|
4961
|
+
sourceHandle: "output-0",
|
|
4962
|
+
targetHandle: `input-${prevIndex}`,
|
|
4963
|
+
});
|
|
4964
|
+
}
|
|
4965
|
+
for (const [name, binding] of Object.entries(subflow.inputs || {})) {
|
|
4966
|
+
const proxy = childGraph.instances?.[binding.nodeId];
|
|
4967
|
+
if (!proxy) throw new Error(`Subflow ${id} input ${name} references missing proxy ${binding.nodeId}`);
|
|
4968
|
+
childGraph.instances[binding.nodeId] = workspaceSetOutputSlot(proxy, binding.slot || "value", inputValues[name] ?? "");
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
const frameContext = {
|
|
4972
|
+
...eventContext,
|
|
4973
|
+
parentNodeId,
|
|
4974
|
+
subflowId: id,
|
|
4975
|
+
callFrameId,
|
|
4976
|
+
};
|
|
4977
|
+
emit({
|
|
4978
|
+
type: "subflow-start",
|
|
4979
|
+
nodeId: parentNodeId,
|
|
4980
|
+
definitionId: parentDefinitionId,
|
|
4981
|
+
inputs: Object.keys(subflow.inputs || {}),
|
|
4982
|
+
...frameContext,
|
|
4983
|
+
});
|
|
4984
|
+
const childResult = await runWorkspaceGraph(root, scopedRoot, {
|
|
4985
|
+
...payload,
|
|
4986
|
+
graph: childGraph,
|
|
4987
|
+
runNodeId: syntheticRunId,
|
|
4988
|
+
ignoreCache: true,
|
|
4989
|
+
forceNodeIds: [],
|
|
4990
|
+
}, userCtx, {
|
|
4991
|
+
...opts,
|
|
4992
|
+
signal: signal || opts?.signal || null,
|
|
4993
|
+
subflowCallStack: [...callStack, id],
|
|
4994
|
+
onEvent: (event) => {
|
|
4995
|
+
if (event?.type === "graph") return;
|
|
4996
|
+
emit({ ...event, ...frameContext });
|
|
4997
|
+
},
|
|
4998
|
+
});
|
|
4999
|
+
if (childResult.deferred || childResult.pauseNodeIds?.length) {
|
|
5000
|
+
throw new Error(`Subflow ${id} paused or deferred; resumable subflow call frames are not supported yet`);
|
|
5001
|
+
}
|
|
5002
|
+
for (const memberId of memberSet) {
|
|
5003
|
+
if (childResult.graph.instances?.[memberId]) graph.instances[memberId] = childResult.graph.instances[memberId];
|
|
5004
|
+
}
|
|
5005
|
+
const resultValues = {};
|
|
5006
|
+
for (const [name, binding] of Object.entries(subflow.outputs || {})) {
|
|
5007
|
+
const source = childResult.graph.instances?.[binding.nodeId];
|
|
5008
|
+
const slot = (source?.output || []).find((item) => String(item?.name || "") === String(binding.slot || ""));
|
|
5009
|
+
resultValues[name] = workspaceSlotValue(slot);
|
|
5010
|
+
}
|
|
5011
|
+
emit({
|
|
5012
|
+
type: "subflow-done",
|
|
5013
|
+
nodeId: parentNodeId,
|
|
5014
|
+
definitionId: parentDefinitionId,
|
|
5015
|
+
outputs: Object.keys(resultValues),
|
|
5016
|
+
...frameContext,
|
|
5017
|
+
});
|
|
5018
|
+
return { resultValues, callFrameId };
|
|
5019
|
+
}
|
|
5020
|
+
|
|
4747
5021
|
export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts = {}) {
|
|
4748
5022
|
const graph = hydrateWorkspaceGraphForRuntime(root, {
|
|
4749
5023
|
root: scopedRoot,
|
|
@@ -4752,8 +5026,11 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
4752
5026
|
archived: payload.archived === true || payload.flowArchived === true,
|
|
4753
5027
|
}, payload.graph || {}, userCtx);
|
|
4754
5028
|
const runNodeId = String(payload?.runNodeId || "").trim();
|
|
5029
|
+
const forceNodeIds = new Set((Array.isArray(payload?.forceNodeIds) ? payload.forceNodeIds : [])
|
|
5030
|
+
.map((nodeId) => String(nodeId || "").trim())
|
|
5031
|
+
.filter(Boolean));
|
|
4755
5032
|
const { order, pauseNodeIds } = workspaceRunPlan(graph, runNodeId, scopedRoot, {
|
|
4756
|
-
forceNodeIds
|
|
5033
|
+
forceNodeIds,
|
|
4757
5034
|
ignoreCache: payload?.ignoreCache === true,
|
|
4758
5035
|
});
|
|
4759
5036
|
// 指纹只由「节点定义 + 上游指纹 + 没接线的槽位值」决定,运行过程中这些都不变,所以
|
|
@@ -4835,6 +5112,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
4835
5112
|
const runTmpRoot = workspaceCreateRunTmpRoot(scopedRoot, runNodeId);
|
|
4836
5113
|
const controlBranches = new Map();
|
|
4837
5114
|
const skippedNodes = new Set();
|
|
5115
|
+
const runtimePauseNodeIds = [];
|
|
4838
5116
|
let deferred = null;
|
|
4839
5117
|
const incomingControlEdgesByTarget = new Map();
|
|
4840
5118
|
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
@@ -4892,6 +5170,45 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
4892
5170
|
continue;
|
|
4893
5171
|
}
|
|
4894
5172
|
|
|
5173
|
+
if (defId === "workspace_subflow_input") {
|
|
5174
|
+
const value = workspaceSlotValue((instance.output || []).find((slot) => String(slot?.name || "") === "value"));
|
|
5175
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5176
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5177
|
+
continue;
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
if (defId === "control_subflow_call") {
|
|
5181
|
+
const subflowId = String(instance.subflowId || "").trim();
|
|
5182
|
+
const subflow = graph?.subflows?.[subflowId];
|
|
5183
|
+
if (!subflow) throw new Error(`flow.call ${nodeId} references missing subflow ${subflowId || "(empty)"}`);
|
|
5184
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5185
|
+
const { resultValues, callFrameId } = await workspaceRunSubflowFrame({
|
|
5186
|
+
root,
|
|
5187
|
+
scopedRoot,
|
|
5188
|
+
payload,
|
|
5189
|
+
userCtx,
|
|
5190
|
+
opts,
|
|
5191
|
+
graph,
|
|
5192
|
+
parentNodeId: nodeId,
|
|
5193
|
+
parentDefinitionId: defId,
|
|
5194
|
+
subflowId,
|
|
5195
|
+
inputValues,
|
|
5196
|
+
signal,
|
|
5197
|
+
emit,
|
|
5198
|
+
});
|
|
5199
|
+
let nextInstance = instance;
|
|
5200
|
+
for (const [name, value] of Object.entries(resultValues)) {
|
|
5201
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, name, value);
|
|
5202
|
+
}
|
|
5203
|
+
graph.instances[nodeId] = nextInstance;
|
|
5204
|
+
const primaryName = Object.keys(subflow.outputs || {})[0] || "";
|
|
5205
|
+
const primaryValue = primaryName ? resultValues[primaryName] : "";
|
|
5206
|
+
const updatedDisplays = publishNodeOutput(nodeId, primaryValue);
|
|
5207
|
+
emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
5208
|
+
emit({ type: "node-done", nodeId, definitionId: defId, subflowId, callFrameId });
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
|
|
4895
5212
|
if (defId === "control_load_skills") {
|
|
4896
5213
|
const skillStartedAt = Date.now();
|
|
4897
5214
|
const nodeSkillKeys = selectedSkillKeysFromInstance(instance);
|
|
@@ -4961,6 +5278,252 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
4961
5278
|
continue;
|
|
4962
5279
|
}
|
|
4963
5280
|
|
|
5281
|
+
if (defId === "control_parse_json") {
|
|
5282
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5283
|
+
const raw = String(inputValues.value ?? "").trim();
|
|
5284
|
+
if (!raw) throw new Error(`control.parseJson ${nodeId} requires a non-empty value`);
|
|
5285
|
+
let parsed;
|
|
5286
|
+
try {
|
|
5287
|
+
parsed = JSON.parse(raw);
|
|
5288
|
+
} catch (error) {
|
|
5289
|
+
throw new Error(`control.parseJson ${nodeId} received invalid JSON: ${error.message}`);
|
|
5290
|
+
}
|
|
5291
|
+
const content = JSON.stringify(parsed);
|
|
5292
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "result", content);
|
|
5293
|
+
publishNodeOutput(nodeId, content, { emitGraph: true });
|
|
5294
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5295
|
+
continue;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5298
|
+
if (defId === "control_while") {
|
|
5299
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5300
|
+
const config = normalizeControlWhileConfig(inputValues);
|
|
5301
|
+
const stepScript = String(instance.script || instance.body || "").trim();
|
|
5302
|
+
const stepScriptRef = String(instance.scriptRef || "").trim();
|
|
5303
|
+
const conditionSubflowId = String(instance.conditionSubflowId || "").trim();
|
|
5304
|
+
const bodySubflowId = String(instance.bodySubflowId || "").trim();
|
|
5305
|
+
const usesSubflows = Boolean(conditionSubflowId || bodySubflowId);
|
|
5306
|
+
const whileSubflows = usesSubflows
|
|
5307
|
+
? workspaceAssertWhileSubflowContract(graph, nodeId, conditionSubflowId, bodySubflowId)
|
|
5308
|
+
: null;
|
|
5309
|
+
if (!usesSubflows && !stepScript && !stepScriptRef) {
|
|
5310
|
+
throw new Error(`control.while ${nodeId} requires condition/body subflows or a step command/scriptRef`);
|
|
5311
|
+
}
|
|
5312
|
+
const previousDecision = workspaceSlotValue(workspaceSlotByName(instance, "decision")).trim().toLowerCase();
|
|
5313
|
+
const checkpointState = workspaceSlotValue(
|
|
5314
|
+
(Array.isArray(instance.output) ? instance.output : []).find((slot) => String(slot?.name || "") === "state"),
|
|
5315
|
+
);
|
|
5316
|
+
const checkpointFingerprint = workspaceSlotValue(
|
|
5317
|
+
(Array.isArray(instance.output) ? instance.output : []).find((slot) => String(slot?.name || "") === "checkpointFingerprint"),
|
|
5318
|
+
);
|
|
5319
|
+
const inputState = normalizeControlWhileInitialState(inputValues.state);
|
|
5320
|
+
const expectedCheckpointFingerprint = controlWhileCheckpointFingerprint({
|
|
5321
|
+
inputFingerprint: plannedFingerprints.get(nodeId) || "",
|
|
5322
|
+
initialState: inputState,
|
|
5323
|
+
});
|
|
5324
|
+
const checkpoint = resolveControlWhileCheckpoint({
|
|
5325
|
+
previousDecision,
|
|
5326
|
+
state: checkpointState,
|
|
5327
|
+
history: workspaceSlotValue(workspaceSlotByName(instance, "history")),
|
|
5328
|
+
iterations: workspaceSlotValue(workspaceSlotByName(instance, "iterations")),
|
|
5329
|
+
fingerprint: checkpointFingerprint,
|
|
5330
|
+
expectedFingerprint: expectedCheckpointFingerprint,
|
|
5331
|
+
allowResume: payload?.ignoreCache !== true && !forceNodeIds.has(nodeId),
|
|
5332
|
+
});
|
|
5333
|
+
const initialState = checkpoint.resumable ? checkpoint.state : inputState;
|
|
5334
|
+
if (checkpoint.resumable) {
|
|
5335
|
+
emit({ type: "status", nodeId, line: "control.while resume from waiting checkpoint" });
|
|
5336
|
+
} else if (previousDecision === "wait") {
|
|
5337
|
+
emit({ type: "status", nodeId, line: `control.while reset waiting checkpoint: ${checkpoint.reason}` });
|
|
5338
|
+
}
|
|
5339
|
+
const loop = await runControlWhile({
|
|
5340
|
+
initialState,
|
|
5341
|
+
initialHistory: checkpoint.resumable ? checkpoint.history : [],
|
|
5342
|
+
initialElapsedMs: checkpoint.resumable ? checkpoint.elapsedMs : 0,
|
|
5343
|
+
startIteration: checkpoint.resumable ? checkpoint.nextIteration : 1,
|
|
5344
|
+
...config,
|
|
5345
|
+
signal,
|
|
5346
|
+
idempotencyKeyForIteration: ({ iteration }) => controlWhileIdempotencyKey({
|
|
5347
|
+
checkpointFingerprint: expectedCheckpointFingerprint,
|
|
5348
|
+
nodeId,
|
|
5349
|
+
iteration,
|
|
5350
|
+
}),
|
|
5351
|
+
onIterationStart: ({ iteration, remainingMs, idempotencyKey }) => {
|
|
5352
|
+
emit({
|
|
5353
|
+
type: "while-iteration-start",
|
|
5354
|
+
nodeId,
|
|
5355
|
+
definitionId: defId,
|
|
5356
|
+
iteration,
|
|
5357
|
+
maxIterations: config.maxIterations,
|
|
5358
|
+
remainingMs,
|
|
5359
|
+
idempotencyKey,
|
|
5360
|
+
});
|
|
5361
|
+
emit({ type: "status", nodeId, line: `control.while iteration ${iteration}/${config.maxIterations}` });
|
|
5362
|
+
},
|
|
5363
|
+
onIterationDone: ({ iteration, decision, summary, elapsedMs, idempotencyKey }) => {
|
|
5364
|
+
emit({
|
|
5365
|
+
type: "while-iteration-done",
|
|
5366
|
+
nodeId,
|
|
5367
|
+
definitionId: defId,
|
|
5368
|
+
iteration,
|
|
5369
|
+
decision,
|
|
5370
|
+
summary,
|
|
5371
|
+
elapsedMs,
|
|
5372
|
+
idempotencyKey,
|
|
5373
|
+
});
|
|
5374
|
+
emit({ type: "status", nodeId, line: `control.while iteration ${iteration}: ${decision}${summary ? ` · ${summary}` : ""}` });
|
|
5375
|
+
},
|
|
5376
|
+
executeStep: async ({ iteration, state, signal: stepSignal, idempotencyKey }) => {
|
|
5377
|
+
const stateText = serializeControlWhileState(state);
|
|
5378
|
+
const iterationInputs = {
|
|
5379
|
+
...inputValues,
|
|
5380
|
+
state: stateText,
|
|
5381
|
+
iteration: String(iteration),
|
|
5382
|
+
};
|
|
5383
|
+
if (whileSubflows) {
|
|
5384
|
+
const conditionFrame = await workspaceRunSubflowFrame({
|
|
5385
|
+
root,
|
|
5386
|
+
scopedRoot,
|
|
5387
|
+
payload,
|
|
5388
|
+
userCtx,
|
|
5389
|
+
opts,
|
|
5390
|
+
graph,
|
|
5391
|
+
parentNodeId: nodeId,
|
|
5392
|
+
parentDefinitionId: defId,
|
|
5393
|
+
subflowId: whileSubflows.conditionId,
|
|
5394
|
+
inputValues: {
|
|
5395
|
+
state: stateText,
|
|
5396
|
+
iteration: String(iteration),
|
|
5397
|
+
idempotencyKey,
|
|
5398
|
+
},
|
|
5399
|
+
signal: stepSignal,
|
|
5400
|
+
emit,
|
|
5401
|
+
eventContext: { iteration, whileRole: "condition" },
|
|
5402
|
+
});
|
|
5403
|
+
const conditionStep = parseControlWhileStepResult(JSON.stringify({
|
|
5404
|
+
decision: String(conditionFrame.resultValues.decision || "").trim().toLowerCase(),
|
|
5405
|
+
summary: String(conditionFrame.resultValues.summary || ""),
|
|
5406
|
+
}), state);
|
|
5407
|
+
if (conditionStep.decision !== "continue") return JSON.stringify(conditionStep);
|
|
5408
|
+
|
|
5409
|
+
const bodyFrame = await workspaceRunSubflowFrame({
|
|
5410
|
+
root,
|
|
5411
|
+
scopedRoot,
|
|
5412
|
+
payload,
|
|
5413
|
+
userCtx,
|
|
5414
|
+
opts,
|
|
5415
|
+
graph,
|
|
5416
|
+
parentNodeId: nodeId,
|
|
5417
|
+
parentDefinitionId: defId,
|
|
5418
|
+
subflowId: whileSubflows.bodyId,
|
|
5419
|
+
inputValues: {
|
|
5420
|
+
state: stateText,
|
|
5421
|
+
iteration: String(iteration),
|
|
5422
|
+
idempotencyKey,
|
|
5423
|
+
},
|
|
5424
|
+
signal: stepSignal,
|
|
5425
|
+
emit,
|
|
5426
|
+
eventContext: { iteration, whileRole: "body" },
|
|
5427
|
+
});
|
|
5428
|
+
const nextStateText = String(bodyFrame.resultValues.state || "").trim();
|
|
5429
|
+
if (!nextStateText) {
|
|
5430
|
+
throw new Error(`control.while ${nodeId} body subflow ${whileSubflows.bodyId} returned an empty state`);
|
|
5431
|
+
}
|
|
5432
|
+
const nextState = normalizeControlWhileInitialState(nextStateText);
|
|
5433
|
+
return JSON.stringify({
|
|
5434
|
+
decision: "continue",
|
|
5435
|
+
state: nextState,
|
|
5436
|
+
summary: String(bodyFrame.resultValues.summary || conditionStep.summary || ""),
|
|
5437
|
+
});
|
|
5438
|
+
}
|
|
5439
|
+
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, `${nodeId}-iteration-${iteration}`, {
|
|
5440
|
+
scopedRoot,
|
|
5441
|
+
cwd,
|
|
5442
|
+
task: stepScript || stepScriptRef,
|
|
5443
|
+
inputValues: iterationInputs,
|
|
5444
|
+
});
|
|
5445
|
+
const runtimeInputValues = { ...iterationInputs, ...(runPackage.inputValues || {}) };
|
|
5446
|
+
let activeStepChild = null;
|
|
5447
|
+
let activeStepChildOptions = {};
|
|
5448
|
+
const terminateStep = () => terminateWorkspaceChild(activeStepChild, activeStepChildOptions);
|
|
5449
|
+
stepSignal?.addEventListener("abort", terminateStep, { once: true });
|
|
5450
|
+
try {
|
|
5451
|
+
const content = await workspaceRunToolNodejsScript({
|
|
5452
|
+
scopedRoot,
|
|
5453
|
+
cwd,
|
|
5454
|
+
instance: { ...instance, script: stepScript, scriptRef: stepScript ? "" : stepScriptRef },
|
|
5455
|
+
inputValues: runtimeInputValues,
|
|
5456
|
+
runPackage,
|
|
5457
|
+
userCtx,
|
|
5458
|
+
envOverlay: {
|
|
5459
|
+
...runEnv,
|
|
5460
|
+
AGENTFLOW_WHILE_STATE: stateText,
|
|
5461
|
+
AGENTFLOW_WHILE_ITERATION: String(iteration),
|
|
5462
|
+
AGENTFLOW_WHILE_MAX_ITERATIONS: String(config.maxIterations),
|
|
5463
|
+
AGENTFLOW_WHILE_TIMEOUT_MS: String(config.timeoutMs),
|
|
5464
|
+
AGENTFLOW_WHILE_IDEMPOTENCY_KEY: idempotencyKey,
|
|
5465
|
+
},
|
|
5466
|
+
emit: (event) => emit({ ...event, nodeId, iteration }),
|
|
5467
|
+
signal: stepSignal,
|
|
5468
|
+
stderrAsStatus: true,
|
|
5469
|
+
maxStdoutBytes: MAX_CONTROL_WHILE_STEP_STDOUT_BYTES,
|
|
5470
|
+
maxStderrBytes: MAX_CONTROL_WHILE_STEP_STDERR_BYTES,
|
|
5471
|
+
onActiveChild: (child, childOptions = {}) => {
|
|
5472
|
+
activeStepChild = child || null;
|
|
5473
|
+
activeStepChildOptions = childOptions;
|
|
5474
|
+
if (stepSignal?.aborted && child) terminateStep();
|
|
5475
|
+
if (typeof opts.onActiveChild === "function") opts.onActiveChild(child, childOptions);
|
|
5476
|
+
},
|
|
5477
|
+
});
|
|
5478
|
+
return workspaceStructuredAgentOutput(content).result || content;
|
|
5479
|
+
} finally {
|
|
5480
|
+
stepSignal?.removeEventListener("abort", terminateStep);
|
|
5481
|
+
}
|
|
5482
|
+
},
|
|
5483
|
+
});
|
|
5484
|
+
const resultContent = JSON.stringify({
|
|
5485
|
+
decision: loop.decision,
|
|
5486
|
+
iterations: loop.iterations,
|
|
5487
|
+
state: loop.state,
|
|
5488
|
+
summary: loop.summary,
|
|
5489
|
+
history: loop.history,
|
|
5490
|
+
});
|
|
5491
|
+
let nextInstance = workspaceSetOutputSlot(instance, "result", resultContent);
|
|
5492
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "state", serializeControlWhileState(loop.state));
|
|
5493
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "decision", loop.decision);
|
|
5494
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "iterations", String(loop.iterations));
|
|
5495
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "summary", loop.summary);
|
|
5496
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "history", JSON.stringify(loop.history));
|
|
5497
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "checkpointFingerprint", expectedCheckpointFingerprint);
|
|
5498
|
+
graph.instances[nodeId] = nextInstance;
|
|
5499
|
+
const updatedDisplays = publishNodeOutput(nodeId, resultContent);
|
|
5500
|
+
emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
5501
|
+
if (loop.decision === "fail") {
|
|
5502
|
+
emit({
|
|
5503
|
+
type: "node-failed",
|
|
5504
|
+
nodeId,
|
|
5505
|
+
definitionId: defId,
|
|
5506
|
+
decision: loop.decision,
|
|
5507
|
+
iterations: loop.iterations,
|
|
5508
|
+
summary: loop.summary,
|
|
5509
|
+
});
|
|
5510
|
+
throw new Error(loop.summary || "control.while step failed");
|
|
5511
|
+
}
|
|
5512
|
+
emit({
|
|
5513
|
+
type: "node-done",
|
|
5514
|
+
nodeId,
|
|
5515
|
+
definitionId: defId,
|
|
5516
|
+
decision: loop.decision,
|
|
5517
|
+
iterations: loop.iterations,
|
|
5518
|
+
summary: loop.summary,
|
|
5519
|
+
});
|
|
5520
|
+
if (loop.decision === "wait") {
|
|
5521
|
+
runtimePauseNodeIds.push(nodeId);
|
|
5522
|
+
break;
|
|
5523
|
+
}
|
|
5524
|
+
continue;
|
|
5525
|
+
}
|
|
5526
|
+
|
|
4964
5527
|
if (defId === "provide_str" || defId === "provide_password") {
|
|
4965
5528
|
const content = workspaceInstanceText(instance);
|
|
4966
5529
|
publishNodeOutput(nodeId, content, { emitGraph: true });
|
|
@@ -4968,6 +5531,21 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
4968
5531
|
continue;
|
|
4969
5532
|
}
|
|
4970
5533
|
|
|
5534
|
+
if (defId === "provide_json") {
|
|
5535
|
+
const raw = workspaceSlotValue(Array.isArray(instance.output) ? instance.output[0] : null) || workspaceInstanceText(instance);
|
|
5536
|
+
let parsed;
|
|
5537
|
+
try {
|
|
5538
|
+
parsed = JSON.parse(String(raw || "").trim());
|
|
5539
|
+
} catch (error) {
|
|
5540
|
+
throw new Error(`provide.json ${nodeId} received invalid JSON: ${error.message}`);
|
|
5541
|
+
}
|
|
5542
|
+
const content = JSON.stringify(parsed);
|
|
5543
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "value", content);
|
|
5544
|
+
publishNodeOutput(nodeId, content, { emitGraph: true });
|
|
5545
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5546
|
+
continue;
|
|
5547
|
+
}
|
|
5548
|
+
|
|
4971
5549
|
if (defId === "provide_bool") {
|
|
4972
5550
|
const raw = workspaceSlotValue(Array.isArray(instance.output) ? instance.output[0] : null) || workspaceInstanceText(instance);
|
|
4973
5551
|
const content = ["true", "1", "yes", "on"].includes(String(raw || "").trim().toLowerCase()) ? "true" : "false";
|
|
@@ -5451,7 +6029,11 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5451
6029
|
signal,
|
|
5452
6030
|
onActiveChild: opts.onActiveChild,
|
|
5453
6031
|
});
|
|
5454
|
-
const
|
|
6032
|
+
const structuredAgentOutput = workspaceStructuredAgentOutput(content);
|
|
6033
|
+
const nodeOutput = String(instance.marketplaceRef || "").trim()
|
|
6034
|
+
? workspaceMaterializeNodePackageOutputValues(structuredAgentOutput, runPackage, instance)
|
|
6035
|
+
: structuredAgentOutput;
|
|
6036
|
+
const normalizedAgentOutput = workspacePublishAgentOutputFiles(nodeOutput, runPackage);
|
|
5455
6037
|
const resultContent = normalizedAgentOutput.result || content;
|
|
5456
6038
|
recordNodeOutput(nodeId, resultContent);
|
|
5457
6039
|
const slotUpdate = workspaceApplyAgentOutputSlots(instance, normalizedAgentOutput);
|
|
@@ -5651,11 +6233,12 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5651
6233
|
workspaceCleanupAutoWorktrees(autoCleanupWorktrees, graph, emit);
|
|
5652
6234
|
workspaceCleanupTmpRoot(runTmpRoot, userCtx, emit);
|
|
5653
6235
|
}
|
|
5654
|
-
|
|
5655
|
-
|
|
6236
|
+
const finalPauseNodeIds = Array.from(new Set([...pauseNodeIds, ...runtimePauseNodeIds]));
|
|
6237
|
+
if (!deferred && finalPauseNodeIds.length > 0) {
|
|
6238
|
+
emit({ type: "paused", nodeIds: finalPauseNodeIds, message: `Workspace run paused at ${finalPauseNodeIds.join(", ")}` });
|
|
5656
6239
|
}
|
|
5657
6240
|
graph.updatedAt = new Date().toISOString();
|
|
5658
|
-
return { graph, events, order, pauseNodeIds, deferred };
|
|
6241
|
+
return { graph, events, order, pauseNodeIds: finalPauseNodeIds, deferred };
|
|
5659
6242
|
}
|
|
5660
6243
|
|
|
5661
6244
|
export function isWorkspaceRunAbortError(err) {
|
|
@@ -6145,6 +6728,10 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
|
|
|
6145
6728
|
);
|
|
6146
6729
|
if (sameSharedFlow || key.startsWith(prefix)) delete schedules[key];
|
|
6147
6730
|
}
|
|
6731
|
+
if (isWorkspaceDraftDir(scoped?.root || "")) {
|
|
6732
|
+
writeWorkspaceScheduleRegistry({ version: 1, schedules });
|
|
6733
|
+
return [];
|
|
6734
|
+
}
|
|
6148
6735
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
6149
6736
|
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
6150
6737
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|