@fieldwangai/agentflow 0.1.149 → 0.1.154
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/README.md +20 -33
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +167 -88
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +276 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +268 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +10 -30
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +43 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +153 -283
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1106 -18013
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DPbR2UaN.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +8 -47
- package/skills/agentflow-cli/SKILL.md +158 -25
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +668 -18
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +7 -248
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +4 -73
- package/skills/agentflow-flow-recipes/SKILL.md +7 -18
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +3 -54
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +5 -4
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-placeholder-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/SKILL.md +2 -2
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-BSCpd5la.js +0 -889
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
|
@@ -1,1430 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* 统一预处理入口:对当前节点做 ${} 替换并生成 prompt.md;读取 role / model 并计算 subagent。
|
|
4
|
-
* 用法:node pre-process-node.mjs <workspaceRoot> <flowName> <uuid> <instanceId>
|
|
5
|
-
* 输出(stdout JSON):{ "ok": true, "promptPath": "...", "optionalPromptPath"?: "...", "directCommand"?: "...", "subagent": "...", "definitionId": "...", "role"?: "...", "model"?: "..." }
|
|
6
|
-
* definitionId 供 CLI 做 LOCAL_ONLY 判断;directCommand 供 CLI 直接执行并跳过 agent(与 optionalPromptPath 语义一致,仅 CLI 使用)。
|
|
7
|
-
*
|
|
8
|
-
* 当前 pre-process 流程与 cache 的关系:
|
|
9
|
-
*
|
|
10
|
-
* 1) 公共开头:从 memory 加载 execId(+1 作为本轮),得到 runDir、resultPathRel;读 flow.json 得 definitionId。
|
|
11
|
-
*
|
|
12
|
-
* 2) 分支 A(definitionId === "control_if"):
|
|
13
|
-
* - 用 getResolvedValues + 第一个 bool 槽取值 → parseBool → branch;
|
|
14
|
-
* - writeResult(success, branch);
|
|
15
|
-
* - buildNodePrompt → writeCacheJsonForNode(统一写 .cache.json)→ 写 noop prompt,设 optionalPromptPath,return。
|
|
16
|
-
*
|
|
17
|
-
* 3) 分支 B(普通节点,含 control_toBool 走 tool_nodejs 同路径):
|
|
18
|
-
* - buildNodePrompt → writeResult("running") → writeCacheJsonForNode(统一写 .cache.json);
|
|
19
|
-
* - 若有 tool_load_key/tool_save_key/tool_get_env/control_anyOne 再设 optionalPromptPath,并视情况输出 directCommand 供 CLI 执行;
|
|
20
|
-
* - 返回 promptPath、resultPath、execId、subagent 等。
|
|
21
|
-
*
|
|
22
|
-
* cache.json 流程已统一:control_if 与普通节点均通过 writeCacheJsonForNode 在「prompt 已存在」的前提下执行 computeCacheMd5 并写入 intermediate/<instanceId>/<instanceId>.cache.json,结构一致(含 cacheMd5、cacheInputInfo、execId、inputHandlerExecIds、payload)。
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { spawnSync } from "child_process";
|
|
26
|
-
import fs from "fs";
|
|
27
|
-
import os from "os";
|
|
28
|
-
import path from "path";
|
|
29
|
-
import { fileURLToPath } from "url";
|
|
30
|
-
|
|
31
|
-
import { loadFlowDefinition } from "./parse-flow.mjs";
|
|
32
|
-
import { buildNodePrompt } from "./build-node-prompt.mjs";
|
|
33
|
-
import { snapshotPriorRoundIfNeeded } from "./snapshot-prior-round.mjs";
|
|
34
|
-
import { computeCacheMd5 } from "./compute-cache-md5.mjs";
|
|
35
|
-
import { getResolvedValues } from "./get-resolved-values.mjs";
|
|
36
|
-
import { parseBool, getFirstBoolInputValue } from "./parse-bool.mjs";
|
|
37
|
-
import { writeResult } from "./write-result.mjs";
|
|
38
|
-
import { intermediateResultBasename, intermediateCacheBasename, intermediateDirForNode, outputNodeBasename, outputDirForNode } from "./get-exec-id.mjs";
|
|
39
|
-
import { logToRunTag } from "./run-log.mjs";
|
|
40
|
-
import { getRunDir, sanitizeAgentflowUserId } from "../lib/paths.mjs";
|
|
41
|
-
import {
|
|
42
|
-
buildSkillsContext,
|
|
43
|
-
buildSkillsContextFromRegistry,
|
|
44
|
-
buildDefaultWorkspaceContext,
|
|
45
|
-
expandRuntimePlaceholders,
|
|
46
|
-
normalizeSkillsContext,
|
|
47
|
-
normalizeWorkspaceContext,
|
|
48
|
-
parseSkillKeyList,
|
|
49
|
-
resolveWorkspaceTarget,
|
|
50
|
-
} from "../lib/runtime-context.mjs";
|
|
51
|
-
import { buildGitContext, inferGitRepoRootFromWorktree, loadGitWorktree, normalizeGitContext, unloadGitWorktree } from "../lib/git-worktree.mjs";
|
|
52
|
-
import { createGitLabMergeRequest } from "../lib/gitlab-mr.mjs";
|
|
53
|
-
import { sendWecomAppMarkdown, sendWecomGroupMarkdown } from "../lib/wecom.mjs";
|
|
54
|
-
import {
|
|
55
|
-
advanceJenkinsBuild,
|
|
56
|
-
createJenkinsSkillInvoker,
|
|
57
|
-
normalizeJenkinsBuildConfig,
|
|
58
|
-
readJenkinsBuildState,
|
|
59
|
-
writeJenkinsBuildState,
|
|
60
|
-
} from "../lib/jenkins.mjs";
|
|
61
|
-
|
|
62
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
63
|
-
|
|
64
|
-
const ROLE_TO_SUBAGENT = {
|
|
65
|
-
requirement: "agentflow-node-executor-requirement",
|
|
66
|
-
planning: "agentflow-node-executor-planning",
|
|
67
|
-
code: "agentflow-node-executor-code",
|
|
68
|
-
test: "agentflow-node-executor-test",
|
|
69
|
-
normal: "agentflow-node-executor",
|
|
70
|
-
求拆解: "agentflow-node-executor-requirement",
|
|
71
|
-
技术规划: "agentflow-node-executor-planning",
|
|
72
|
-
代码执行: "agentflow-node-executor-code",
|
|
73
|
-
测试回归: "agentflow-node-executor-test",
|
|
74
|
-
普通: "agentflow-node-executor",
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
function readFlowJson(workspaceRoot, flowName, uuid) {
|
|
78
|
-
const flowJsonPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "intermediate", "flow.json");
|
|
79
|
-
if (!fs.existsSync(flowJsonPath)) return null;
|
|
80
|
-
try {
|
|
81
|
-
const flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
82
|
-
return flow?.ok && Array.isArray(flow.nodes) ? flow : null;
|
|
83
|
-
} catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function getRoleAndModelFromFlowJson(workspaceRoot, flowName, uuid, instanceId) {
|
|
89
|
-
const flow = readFlowJson(workspaceRoot, flowName, uuid);
|
|
90
|
-
if (!flow || !Array.isArray(flow.nodes)) {
|
|
91
|
-
return { role: "普通", model: null };
|
|
92
|
-
}
|
|
93
|
-
const node = flow.nodes.find((n) => n.id === instanceId) || null;
|
|
94
|
-
const roleRaw = node && node.role != null ? String(node.role).trim() : "";
|
|
95
|
-
const modelRaw = node && node.model != null ? String(node.model).trim() : "";
|
|
96
|
-
const role = roleRaw || "普通";
|
|
97
|
-
const model = modelRaw || null;
|
|
98
|
-
return { role, model };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** 从 flow.json 读取节点 definitionId,优先 node.definitionId,回退 nodeDefinitions[instanceId](start/end 等可据此做本地跳过) */
|
|
102
|
-
function getDefinitionIdFromFlowJson(workspaceRoot, flowName, uuid, instanceId) {
|
|
103
|
-
const flow = readFlowJson(workspaceRoot, flowName, uuid);
|
|
104
|
-
if (!flow) return null;
|
|
105
|
-
const node = flow.nodes.find((n) => n.id === instanceId);
|
|
106
|
-
return node?.definitionId ?? flow.nodeDefinitions?.[instanceId] ?? null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 统一:根据当前 prompt 文件 + resolvedInputs + 上游 cache 算 MD5 并写 intermediate/<instanceId>/<instanceId>.cache.json。
|
|
111
|
-
* 调用前需保证 buildNodePrompt 已执行(prompt 文件已存在)。control_if 与普通节点共用此流程。
|
|
112
|
-
*/
|
|
113
|
-
function writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
114
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
115
|
-
const cache = computeCacheMd5(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
116
|
-
if (!cache.ok || (!cache.cacheMd5 && !cache.cacheInputInfo)) return;
|
|
117
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
118
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
119
|
-
const cachePath = path.join(nodeIntermediateDir, intermediateCacheBasename(instanceId, execId));
|
|
120
|
-
const cacheObj = {
|
|
121
|
-
cacheMd5: cache.cacheMd5,
|
|
122
|
-
cacheInputInfo: cache.cacheInputInfo,
|
|
123
|
-
execId,
|
|
124
|
-
};
|
|
125
|
-
if (cache.inputHandlerExecIds != null && Object.keys(cache.inputHandlerExecIds).length > 0) {
|
|
126
|
-
cacheObj.inputHandlerExecIds = cache.inputHandlerExecIds;
|
|
127
|
-
}
|
|
128
|
-
if (cache.payload !== undefined) cacheObj.payload = cache.payload;
|
|
129
|
-
// 备份由 snapshotPriorRoundIfNeeded 统一在 pre-process 入口完成;此处只管写新 cache。
|
|
130
|
-
fs.writeFileSync(cachePath, JSON.stringify(cacheObj, null, 0), "utf-8");
|
|
131
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", {
|
|
132
|
-
event: "cache-written",
|
|
133
|
-
instanceId,
|
|
134
|
-
cacheMd5: cache.cacheMd5,
|
|
135
|
-
cachePath: path.join(intermediateDirForNode(instanceId), intermediateCacheBasename(instanceId, execId)),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Bash 单引号包裹任意参数:单引号内无命令替换/变量展开,避免 save_key 的 value 含反引号、`$()`、换行时把整段当 shell 执行。
|
|
141
|
-
* 用法:'it'\''s' 表示 it's
|
|
142
|
-
*/
|
|
143
|
-
function bashSingleQuote(s) {
|
|
144
|
-
if (s == null) return "''";
|
|
145
|
-
return "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function agentflowCommand() {
|
|
149
|
-
const userId = sanitizeAgentflowUserId(process.env.AGENTFLOW_USER_ID);
|
|
150
|
-
if (!userId) return "agentflow";
|
|
151
|
-
return `AGENTFLOW_USER_ID=${bashSingleQuote(userId)} agentflow`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function parseDurationMs(raw) {
|
|
155
|
-
const text = String(raw || "").trim();
|
|
156
|
-
if (!text) throw new Error("duration is required");
|
|
157
|
-
const m = text.match(/^(\d+(?:\.\d+)?)\s*(ms|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hour|hours|d|day|days)$/i);
|
|
158
|
-
if (!m) throw new Error(`Invalid duration: ${text}`);
|
|
159
|
-
const n = Number(m[1]);
|
|
160
|
-
if (!Number.isFinite(n) || n < 0) throw new Error(`Invalid duration: ${text}`);
|
|
161
|
-
const unit = m[2].toLowerCase();
|
|
162
|
-
const mult =
|
|
163
|
-
unit === "ms" || unit.startsWith("millisecond") ? 1 :
|
|
164
|
-
unit === "s" || unit === "sec" || unit === "secs" || unit.startsWith("second") ? 1000 :
|
|
165
|
-
unit === "m" || unit === "min" || unit === "mins" || unit.startsWith("minute") ? 60_000 :
|
|
166
|
-
unit === "h" || unit === "hr" || unit.startsWith("hour") ? 3_600_000 :
|
|
167
|
-
86_400_000;
|
|
168
|
-
return Math.round(n * mult);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function zonedDateTimeToUtc(year, month, day, hour, minute, second, timezone) {
|
|
172
|
-
const guess = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
173
|
-
const parts = new Intl.DateTimeFormat("en-US", {
|
|
174
|
-
timeZone: timezone,
|
|
175
|
-
year: "numeric",
|
|
176
|
-
month: "2-digit",
|
|
177
|
-
day: "2-digit",
|
|
178
|
-
hour: "2-digit",
|
|
179
|
-
minute: "2-digit",
|
|
180
|
-
second: "2-digit",
|
|
181
|
-
hour12: false,
|
|
182
|
-
}).formatToParts(new Date(guess)).reduce((acc, p) => {
|
|
183
|
-
if (p.type !== "literal") acc[p.type] = Number(p.value);
|
|
184
|
-
return acc;
|
|
185
|
-
}, {});
|
|
186
|
-
if (parts.hour === 24) parts.hour = 0;
|
|
187
|
-
const asIfUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
188
|
-
return new Date(guess - (asIfUtc - guess));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function parseDateTime(raw, timezone = "Asia/Shanghai") {
|
|
192
|
-
const text = String(raw || "").trim();
|
|
193
|
-
if (!text) throw new Error("until/deadlineAt is required");
|
|
194
|
-
const ymd = text.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/);
|
|
195
|
-
if (ymd) {
|
|
196
|
-
return zonedDateTimeToUtc(
|
|
197
|
-
Number(ymd[1]),
|
|
198
|
-
Number(ymd[2]),
|
|
199
|
-
Number(ymd[3]),
|
|
200
|
-
Number(ymd[4] || 0),
|
|
201
|
-
Number(ymd[5] || 0),
|
|
202
|
-
Number(ymd[6] || 0),
|
|
203
|
-
timezone,
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
const direct = Date.parse(text);
|
|
207
|
-
if (Number.isFinite(direct)) return new Date(direct);
|
|
208
|
-
|
|
209
|
-
const hm = text.match(/^(tomorrow\s+)?(\d{1,2}):(\d{2})(?::(\d{2}))?$/i);
|
|
210
|
-
if (hm) {
|
|
211
|
-
const now = new Date();
|
|
212
|
-
const parts = new Intl.DateTimeFormat("en-US", {
|
|
213
|
-
timeZone: timezone,
|
|
214
|
-
year: "numeric",
|
|
215
|
-
month: "2-digit",
|
|
216
|
-
day: "2-digit",
|
|
217
|
-
}).formatToParts(now).reduce((acc, p) => {
|
|
218
|
-
if (p.type !== "literal") acc[p.type] = Number(p.value);
|
|
219
|
-
return acc;
|
|
220
|
-
}, {});
|
|
221
|
-
const date = zonedDateTimeToUtc(parts.year, parts.month, parts.day, Number(hm[2]), Number(hm[3]), Number(hm[4] || 0), timezone);
|
|
222
|
-
if (hm[1]) date.setUTCDate(date.getUTCDate() + 1);
|
|
223
|
-
return date;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
throw new Error(`Invalid datetime: ${text}`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function outputPathAbs(runDir, instanceId, execId, slotName) {
|
|
230
|
-
return path.join(runDir, outputDirForNode(instanceId), outputNodeBasename(instanceId, execId, slotName));
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function writeOutputSlot(runDir, instanceId, execId, slotName, value) {
|
|
234
|
-
const p = outputPathAbs(runDir, instanceId, execId, slotName);
|
|
235
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
236
|
-
fs.writeFileSync(p, String(value ?? "") + "\n", "utf-8");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function readFlowJsonObject(workspaceRoot, flowName, uuid) {
|
|
240
|
-
const flowJsonPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "intermediate", "flow.json");
|
|
241
|
-
if (!fs.existsSync(flowJsonPath)) return null;
|
|
242
|
-
try {
|
|
243
|
-
const flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
244
|
-
return flow && typeof flow === "object" ? flow : null;
|
|
245
|
-
} catch {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId) {
|
|
251
|
-
const flowJson = readFlowJsonObject(workspaceRoot, flowName, uuid);
|
|
252
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
253
|
-
const inputs = data.ok ? (data.resolvedInputs || {}) : {};
|
|
254
|
-
const workspaceContext = normalizeWorkspaceContext(inputs.workspaceContext, workspaceRoot, flowName, flowJson);
|
|
255
|
-
const skillsContext = normalizeSkillsContext(inputs.skillsContext);
|
|
256
|
-
return { inputs, workspaceContext, skillsContext, flowJson };
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function sanitizeRepoDirName(repoUrl) {
|
|
260
|
-
const raw = String(repoUrl || "").trim().replace(/\.git$/i, "");
|
|
261
|
-
const last = raw.split(/[/:]/).filter(Boolean).pop() || "repo";
|
|
262
|
-
return last.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function runGit(args, cwd) {
|
|
266
|
-
return spawnSync("git", args, {
|
|
267
|
-
cwd,
|
|
268
|
-
encoding: "utf-8",
|
|
269
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function isTruthyInput(value) {
|
|
274
|
-
const text = String(value ?? "").trim().toLowerCase();
|
|
275
|
-
return text === "true" || text === "1" || text === "yes" || text === "y" || text === "on";
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function resolveMaybeWorkspacePath(raw, workspaceContext, extra = {}) {
|
|
279
|
-
const text = String(raw || "").trim();
|
|
280
|
-
if (!text) return "";
|
|
281
|
-
return resolveWorkspaceTarget(text, workspaceContext, extra);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel) {
|
|
285
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
286
|
-
const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
287
|
-
const repoUrl = String(inputs.repoUrl || inputs.url || "").trim();
|
|
288
|
-
if (!repoUrl) throw new Error("tool_git_checkout: repoUrl is required");
|
|
289
|
-
const branch = String(inputs.branch || "").trim();
|
|
290
|
-
const pullIfExists = String(inputs.pullIfExists ?? "true").trim().toLowerCase() !== "false";
|
|
291
|
-
const includeSubmodules = isTruthyInput(inputs.includeSubmodules ?? inputs.submodules ?? inputs.pullSubmodules);
|
|
292
|
-
const defaultDir = path.join(workspaceContext.pipelineWorkspace || workspaceRoot, ".workspace", "agentflow", "git-repos", sanitizeRepoDirName(repoUrl));
|
|
293
|
-
const targetRaw = String(inputs.targetDir || "").trim();
|
|
294
|
-
const targetDir = targetRaw
|
|
295
|
-
? resolveWorkspaceTarget(targetRaw, workspaceContext, { repoName: sanitizeRepoDirName(repoUrl) })
|
|
296
|
-
: defaultDir;
|
|
297
|
-
|
|
298
|
-
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
|
299
|
-
let changed = false;
|
|
300
|
-
let action = "clone";
|
|
301
|
-
if (fs.existsSync(path.join(targetDir, ".git"))) {
|
|
302
|
-
action = pullIfExists ? "pull" : "exists";
|
|
303
|
-
if (pullIfExists) {
|
|
304
|
-
const fetch = runGit(["fetch", "--all", "--prune"], targetDir);
|
|
305
|
-
if (fetch.status !== 0) throw new Error(`git fetch failed: ${fetch.stderr || fetch.stdout}`);
|
|
306
|
-
if (branch) {
|
|
307
|
-
const checkout = runGit(["checkout", branch], targetDir);
|
|
308
|
-
if (checkout.status !== 0) throw new Error(`git checkout failed: ${checkout.stderr || checkout.stdout}`);
|
|
309
|
-
}
|
|
310
|
-
const before = runGit(["rev-parse", "HEAD"], targetDir).stdout.trim();
|
|
311
|
-
const pull = runGit(["pull", "--ff-only"], targetDir);
|
|
312
|
-
if (pull.status !== 0) throw new Error(`git pull failed: ${pull.stderr || pull.stdout}`);
|
|
313
|
-
const after = runGit(["rev-parse", "HEAD"], targetDir).stdout.trim();
|
|
314
|
-
changed = before !== after;
|
|
315
|
-
}
|
|
316
|
-
} else {
|
|
317
|
-
const args = ["clone"];
|
|
318
|
-
if (includeSubmodules) args.push("--recurse-submodules");
|
|
319
|
-
if (branch) args.push("--branch", branch);
|
|
320
|
-
args.push(repoUrl, targetDir);
|
|
321
|
-
const clone = runGit(args, workspaceContext.cwd || workspaceRoot);
|
|
322
|
-
if (clone.status !== 0) throw new Error(`git clone failed: ${clone.stderr || clone.stdout}`);
|
|
323
|
-
changed = true;
|
|
324
|
-
}
|
|
325
|
-
if (includeSubmodules) {
|
|
326
|
-
const submodule = runGit(["submodule", "update", "--init", "--recursive"], targetDir);
|
|
327
|
-
if (submodule.status !== 0) throw new Error(`git submodule update failed: ${submodule.stderr || submodule.stdout}`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const currentBranch = runGit(["rev-parse", "--abbrev-ref", "HEAD"], targetDir).stdout.trim();
|
|
331
|
-
const commit = runGit(["rev-parse", "HEAD"], targetDir).stdout.trim();
|
|
332
|
-
const gitContext = buildGitContext({
|
|
333
|
-
repoPath: targetDir,
|
|
334
|
-
branch: currentBranch === "HEAD" ? "DETACHED" : currentBranch,
|
|
335
|
-
commit,
|
|
336
|
-
remote: String(inputs.remote || "origin").trim() || "origin",
|
|
337
|
-
});
|
|
338
|
-
const outWorkspaceContext = {
|
|
339
|
-
version: 1,
|
|
340
|
-
label: inputs.label || sanitizeRepoDirName(repoUrl),
|
|
341
|
-
cwd: path.resolve(targetDir),
|
|
342
|
-
workspaceRoot: path.resolve(targetDir),
|
|
343
|
-
pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
|
|
344
|
-
flowDir: workspaceContext.flowDir,
|
|
345
|
-
previous: workspaceContext,
|
|
346
|
-
};
|
|
347
|
-
writeOutputSlot(runDir, instanceId, execId, "repoPath", targetDir);
|
|
348
|
-
writeOutputSlot(runDir, instanceId, execId, "branch", currentBranch);
|
|
349
|
-
writeOutputSlot(runDir, instanceId, execId, "commit", commit);
|
|
350
|
-
writeOutputSlot(runDir, instanceId, execId, "changed", changed ? "true" : "false");
|
|
351
|
-
writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(outWorkspaceContext));
|
|
352
|
-
writeOutputSlot(runDir, instanceId, execId, "gitContext", JSON.stringify(gitContext));
|
|
353
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `git ${action}: ${currentBranch}@${commit.slice(0, 8)}` }, { execId });
|
|
354
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-checkout", `Git checkout completed: ${targetDir}\n`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function requireWorkspaceContextInput(inputs, definitionId) {
|
|
358
|
-
if (!String(inputs?.workspaceContext || "").trim()) {
|
|
359
|
-
throw new Error(`${definitionId}: workspaceContext is required`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
364
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
365
|
-
const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
366
|
-
requireWorkspaceContextInput(inputs, "tool_git_worktree_load");
|
|
367
|
-
const gitContext = normalizeGitContext(inputs.gitContext);
|
|
368
|
-
const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext) ||
|
|
369
|
-
(gitContext?.repoPath ? path.resolve(gitContext.repoPath) : "");
|
|
370
|
-
if (!repoPath) throw new Error("tool_git_worktree_load: repoPath or gitContext.repoPath is required");
|
|
371
|
-
const branch = String(inputs.branch || "").trim();
|
|
372
|
-
const worktreePath = resolveMaybeWorkspacePath(inputs.worktreePath, workspaceContext, { branch }) ||
|
|
373
|
-
(gitContext?.worktreePath ? path.resolve(gitContext.worktreePath) : "");
|
|
374
|
-
const force = isTruthyInput(inputs.force);
|
|
375
|
-
const pruneMissing = String(inputs.pruneMissing ?? "true").trim().toLowerCase() !== "false";
|
|
376
|
-
const result = loadGitWorktree({
|
|
377
|
-
repoPath,
|
|
378
|
-
branch,
|
|
379
|
-
worktreePath,
|
|
380
|
-
pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
|
|
381
|
-
force,
|
|
382
|
-
pruneMissing,
|
|
383
|
-
});
|
|
384
|
-
const outWorkspaceContext = {
|
|
385
|
-
version: 1,
|
|
386
|
-
label: result.branch === "DETACHED" ? `worktree:${result.commit.slice(0, 8)}` : `worktree:${result.branch}`,
|
|
387
|
-
cwd: result.worktreePath,
|
|
388
|
-
workspaceRoot: result.worktreePath,
|
|
389
|
-
pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
|
|
390
|
-
flowDir: workspaceContext.flowDir,
|
|
391
|
-
previous: workspaceContext,
|
|
392
|
-
};
|
|
393
|
-
const outGitContext = buildGitContext({
|
|
394
|
-
repoPath: result.repoRoot,
|
|
395
|
-
worktreePath: result.worktreePath,
|
|
396
|
-
branch: result.branch,
|
|
397
|
-
commit: result.commit,
|
|
398
|
-
remote: gitContext?.remote || "origin",
|
|
399
|
-
remoteUrl: gitContext?.remoteUrl || "",
|
|
400
|
-
});
|
|
401
|
-
writeOutputSlot(runDir, instanceId, execId, "worktreePath", result.worktreePath);
|
|
402
|
-
writeOutputSlot(runDir, instanceId, execId, "branch", result.branch);
|
|
403
|
-
writeOutputSlot(runDir, instanceId, execId, "commit", result.commit);
|
|
404
|
-
writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(outWorkspaceContext));
|
|
405
|
-
writeOutputSlot(runDir, instanceId, execId, "gitContext", JSON.stringify(outGitContext));
|
|
406
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, {
|
|
407
|
-
status: "success",
|
|
408
|
-
message: `worktree loaded: ${result.worktreePath} (${result.branch}@${result.commit.slice(0, 8)})`,
|
|
409
|
-
}, { execId });
|
|
410
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-worktree-load", `Git worktree loaded: ${result.worktreePath}\n`);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
414
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
415
|
-
const { inputs, workspaceContext, flowJson } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
416
|
-
requireWorkspaceContextInput(inputs, "tool_git_worktree_unload");
|
|
417
|
-
const gitContext = normalizeGitContext(inputs.gitContext);
|
|
418
|
-
const worktreePath = resolveMaybeWorkspacePath(inputs.worktreePath, workspaceContext) ||
|
|
419
|
-
(gitContext?.worktreePath ? path.resolve(gitContext.worktreePath) : "") ||
|
|
420
|
-
(workspaceContext.cwd ? path.resolve(workspaceContext.cwd) : "") ||
|
|
421
|
-
(workspaceContext.workspaceRoot ? path.resolve(workspaceContext.workspaceRoot) : "");
|
|
422
|
-
if (!worktreePath) throw new Error("tool_git_worktree_unload: workspaceContext.cwd or worktreePath is required");
|
|
423
|
-
const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext) ||
|
|
424
|
-
(gitContext?.repoPath ? path.resolve(gitContext.repoPath) : "") ||
|
|
425
|
-
inferGitRepoRootFromWorktree(worktreePath) ||
|
|
426
|
-
(workspaceContext.previous?.cwd ? path.resolve(workspaceContext.previous.cwd) : "") ||
|
|
427
|
-
(workspaceContext.previous?.workspaceRoot ? path.resolve(workspaceContext.previous.workspaceRoot) : "");
|
|
428
|
-
const force = isTruthyInput(inputs.force);
|
|
429
|
-
const prune = String(inputs.prune ?? "true").trim().toLowerCase() !== "false";
|
|
430
|
-
const result = unloadGitWorktree({ repoPath, worktreePath, force, prune });
|
|
431
|
-
const nextWorkspaceContext = workspaceContext.previous
|
|
432
|
-
? normalizeWorkspaceContext(workspaceContext.previous, workspaceRoot, flowName, flowJson)
|
|
433
|
-
: buildDefaultWorkspaceContext(workspaceRoot, flowName, flowJson);
|
|
434
|
-
writeOutputSlot(runDir, instanceId, execId, "removed", "true");
|
|
435
|
-
writeOutputSlot(runDir, instanceId, execId, "message", result.message);
|
|
436
|
-
writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(nextWorkspaceContext));
|
|
437
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, {
|
|
438
|
-
status: "success",
|
|
439
|
-
message: result.message,
|
|
440
|
-
}, { execId });
|
|
441
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-worktree-unload", `${result.message}\n`);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
445
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
446
|
-
const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
447
|
-
const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext);
|
|
448
|
-
const result = await createGitLabMergeRequest({
|
|
449
|
-
gitContext: inputs.gitContext,
|
|
450
|
-
workspaceCwd: workspaceContext.cwd,
|
|
451
|
-
repoPath,
|
|
452
|
-
sourceBranch: inputs.sourceBranch,
|
|
453
|
-
targetBranch: inputs.targetBranch,
|
|
454
|
-
title: inputs.title,
|
|
455
|
-
description: inputs.description,
|
|
456
|
-
draft: inputs.draft,
|
|
457
|
-
labels: inputs.labels,
|
|
458
|
-
push: inputs.push,
|
|
459
|
-
remote: inputs.remote,
|
|
460
|
-
tokenEnv: inputs.tokenEnv,
|
|
461
|
-
gitlabApiBase: inputs.gitlabApiBase,
|
|
462
|
-
removeSourceBranch: inputs.removeSourceBranch,
|
|
463
|
-
squash: inputs.squash,
|
|
464
|
-
}, process.env);
|
|
465
|
-
writeOutputSlot(runDir, instanceId, execId, "mrUrl", result.mrUrl);
|
|
466
|
-
writeOutputSlot(runDir, instanceId, execId, "created", result.created ? "true" : "false");
|
|
467
|
-
writeOutputSlot(runDir, instanceId, execId, "mrIid", result.mrIid ?? "");
|
|
468
|
-
writeOutputSlot(runDir, instanceId, execId, "projectId", result.projectId ?? "");
|
|
469
|
-
writeOutputSlot(runDir, instanceId, execId, "sourceBranch", result.sourceBranch ?? "");
|
|
470
|
-
writeOutputSlot(runDir, instanceId, execId, "targetBranch", result.targetBranch ?? "");
|
|
471
|
-
writeOutputSlot(runDir, instanceId, execId, "title", result.title ?? "");
|
|
472
|
-
writeOutputSlot(runDir, instanceId, execId, "message", result.message ?? "");
|
|
473
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, {
|
|
474
|
-
status: "success",
|
|
475
|
-
message: result.message || result.mrUrl,
|
|
476
|
-
body: result.mrUrl,
|
|
477
|
-
}, { execId });
|
|
478
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "gitlab-create-mr", `${result.message || "GitLab MR ready"}\n${result.mrUrl}\n`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
async function emitWecomMarkdownNode(workspaceRoot, flowName, uuid, instanceId, execId, definitionId) {
|
|
482
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
483
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
484
|
-
const inputs = data.ok ? (data.resolvedInputs || {}) : {};
|
|
485
|
-
const result = definitionId === "tool_wecom_send_app_markdown"
|
|
486
|
-
? await sendWecomAppMarkdown({
|
|
487
|
-
markdown: inputs.markdown || inputs.content,
|
|
488
|
-
toUser: inputs.toUser,
|
|
489
|
-
corpId: inputs.corpId,
|
|
490
|
-
corpSecret: inputs.corpSecret,
|
|
491
|
-
agentId: inputs.agentId,
|
|
492
|
-
accessToken: inputs.accessToken,
|
|
493
|
-
}, process.env)
|
|
494
|
-
: await sendWecomGroupMarkdown({
|
|
495
|
-
markdown: inputs.markdown || inputs.content,
|
|
496
|
-
webhookUrl: inputs.webhookUrl,
|
|
497
|
-
webhookKey: inputs.webhookKey,
|
|
498
|
-
}, process.env);
|
|
499
|
-
writeOutputSlot(runDir, instanceId, execId, "sent", "true");
|
|
500
|
-
writeOutputSlot(runDir, instanceId, execId, "message", result.message);
|
|
501
|
-
writeOutputSlot(runDir, instanceId, execId, "response", JSON.stringify(result.response || {}));
|
|
502
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, {
|
|
503
|
-
status: "success",
|
|
504
|
-
message: result.message,
|
|
505
|
-
body: result.message,
|
|
506
|
-
}, { execId });
|
|
507
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "wecom-markdown", `${result.message}\n`);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function emitJenkinsBuildNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
511
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
512
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
513
|
-
if (!data.ok) throw new Error("getResolvedValues failed");
|
|
514
|
-
const inputs = data.resolvedInputs || {};
|
|
515
|
-
const config = normalizeJenkinsBuildConfig(inputs);
|
|
516
|
-
const statePath = path.join(runDir, "state", `${instanceId}.jenkins.json`);
|
|
517
|
-
const priorState = readJenkinsBuildState(statePath);
|
|
518
|
-
if (priorState?.job && priorState.job !== config.job) {
|
|
519
|
-
throw new Error(`checkpoint job mismatch: ${priorState.job} != ${config.job}`);
|
|
520
|
-
}
|
|
521
|
-
let invoker = null;
|
|
522
|
-
const invoke = (operation, args) => {
|
|
523
|
-
if (!invoker) {
|
|
524
|
-
invoker = createJenkinsSkillInvoker({
|
|
525
|
-
workspaceRoot,
|
|
526
|
-
credentialRef: config.credentialRef,
|
|
527
|
-
env: process.env,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
return invoker(operation, args);
|
|
531
|
-
};
|
|
532
|
-
const result = advanceJenkinsBuild({
|
|
533
|
-
state: priorState,
|
|
534
|
-
config,
|
|
535
|
-
invoke,
|
|
536
|
-
persistState: (state) => writeJenkinsBuildState(statePath, state),
|
|
537
|
-
nowMs: Date.now(),
|
|
538
|
-
cancelled: readCancelFlag(runDir),
|
|
539
|
-
});
|
|
540
|
-
writeJenkinsBuildState(statePath, result.state);
|
|
541
|
-
writeOutputSlot(runDir, instanceId, execId, "status", result.outputs?.status || result.state?.status || "");
|
|
542
|
-
writeOutputSlot(runDir, instanceId, execId, "url", result.outputs?.url || result.state?.url || result.state?.buildUrl || "");
|
|
543
|
-
writeOutputSlot(runDir, instanceId, execId, "qrUrl", result.outputs?.qrUrl || result.state?.qrUrl || "");
|
|
544
|
-
|
|
545
|
-
const message = result.message || result.state?.message || "Jenkins Build";
|
|
546
|
-
if (result.kind === "failed") {
|
|
547
|
-
writeResult(
|
|
548
|
-
workspaceRoot,
|
|
549
|
-
flowName,
|
|
550
|
-
uuid,
|
|
551
|
-
instanceId,
|
|
552
|
-
{ status: "failed", message },
|
|
553
|
-
{ execId, preserveBody: false, body: JSON.stringify({ status: "ERROR", message }, null, 2) },
|
|
554
|
-
);
|
|
555
|
-
throw new Error(message);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const promptPath = emitLocalNoopPrompt(
|
|
559
|
-
workspaceRoot,
|
|
560
|
-
runDir,
|
|
561
|
-
instanceId,
|
|
562
|
-
result.kind === "waiting" ? "jenkins-waiting" : "jenkins-complete",
|
|
563
|
-
`${message}\n`,
|
|
564
|
-
);
|
|
565
|
-
if (result.kind === "waiting") {
|
|
566
|
-
const waitId = `${uuid}:${instanceId}:jenkins-build`;
|
|
567
|
-
writeWaitState(runDir, {
|
|
568
|
-
waitId,
|
|
569
|
-
status: "waiting",
|
|
570
|
-
reason: "tool_jenkins_build",
|
|
571
|
-
resumeMode: "rerun",
|
|
572
|
-
flowName,
|
|
573
|
-
uuid,
|
|
574
|
-
instanceId,
|
|
575
|
-
execId,
|
|
576
|
-
phase: result.state?.phase || "queued",
|
|
577
|
-
buildNumber: result.state?.buildNumber || "",
|
|
578
|
-
wakeAt: result.wakeAt,
|
|
579
|
-
createdAt: priorState?.createdAt || new Date().toISOString(),
|
|
580
|
-
});
|
|
581
|
-
writeResult(
|
|
582
|
-
workspaceRoot,
|
|
583
|
-
flowName,
|
|
584
|
-
uuid,
|
|
585
|
-
instanceId,
|
|
586
|
-
{ status: "pending", message },
|
|
587
|
-
{ execId, preserveBody: false, body: JSON.stringify({ phase: result.state?.phase, wakeAt: result.wakeAt }, null, 2) },
|
|
588
|
-
);
|
|
589
|
-
} else {
|
|
590
|
-
writeResult(
|
|
591
|
-
workspaceRoot,
|
|
592
|
-
flowName,
|
|
593
|
-
uuid,
|
|
594
|
-
instanceId,
|
|
595
|
-
{ status: "success", message },
|
|
596
|
-
{
|
|
597
|
-
execId,
|
|
598
|
-
preserveBody: false,
|
|
599
|
-
body: JSON.stringify({
|
|
600
|
-
status: result.outputs?.status || result.state?.status || "",
|
|
601
|
-
url: result.outputs?.url || result.state?.url || "",
|
|
602
|
-
qrUrl: result.outputs?.qrUrl || result.state?.qrUrl || "",
|
|
603
|
-
}, null, 2),
|
|
604
|
-
},
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
return { promptPath, lifecycle: result.kind };
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
611
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
612
|
-
const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
613
|
-
const mode = String(inputs.mode || "set").trim().toLowerCase();
|
|
614
|
-
let next;
|
|
615
|
-
if (mode === "pop") {
|
|
616
|
-
next = normalizeWorkspaceContext(workspaceContext.previous, workspaceRoot, flowName);
|
|
617
|
-
} else {
|
|
618
|
-
const target = resolveWorkspaceTarget(inputs.path || inputs.target || inputs.repoPath || "", workspaceContext);
|
|
619
|
-
if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
|
|
620
|
-
throw new Error(`control_cd_workspace: path directory not found: ${target}`);
|
|
621
|
-
}
|
|
622
|
-
next = {
|
|
623
|
-
version: 1,
|
|
624
|
-
label: String(inputs.label || path.basename(target) || "workspace").trim(),
|
|
625
|
-
cwd: path.resolve(target),
|
|
626
|
-
workspaceRoot: path.resolve(target),
|
|
627
|
-
pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
|
|
628
|
-
flowDir: workspaceContext.flowDir,
|
|
629
|
-
previous: mode === "push" ? workspaceContext : workspaceContext.previous || null,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(next));
|
|
633
|
-
writeOutputSlot(runDir, instanceId, execId, "cwd", next.cwd);
|
|
634
|
-
writeOutputSlot(runDir, instanceId, execId, "previous", next.previous ? JSON.stringify(next.previous) : "");
|
|
635
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `cwd=${next.cwd}` }, { execId });
|
|
636
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "cd-workspace", `Workspace context switched to: ${next.cwd}\n`);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
640
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
641
|
-
const { workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
642
|
-
const homeDir = path.resolve(os.homedir());
|
|
643
|
-
const next = {
|
|
644
|
-
version: 1,
|
|
645
|
-
label: "home",
|
|
646
|
-
cwd: homeDir,
|
|
647
|
-
workspaceRoot: homeDir,
|
|
648
|
-
pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
|
|
649
|
-
flowDir: workspaceContext.flowDir,
|
|
650
|
-
previous: workspaceContext,
|
|
651
|
-
};
|
|
652
|
-
writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(next));
|
|
653
|
-
writeOutputSlot(runDir, instanceId, execId, "cwd", next.cwd);
|
|
654
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `user workspace: ${homeDir}` }, { execId });
|
|
655
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "user-workspace", `Workspace context switched to user home: ${homeDir}\n`);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
function emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
659
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
660
|
-
const { inputs, workspaceContext, skillsContext: existingSkills } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
661
|
-
const mergeMode = String(inputs.mergeMode || "replace").trim();
|
|
662
|
-
const skillKeys = parseSkillKeyList(inputs.skillKeys || inputs.skills || inputs.keys || "");
|
|
663
|
-
let source = "public-registry";
|
|
664
|
-
let loaded;
|
|
665
|
-
if (skillKeys.length > 0) {
|
|
666
|
-
loaded = buildSkillsContextFromRegistry({ workspaceContext, skillKeys, mergeMode });
|
|
667
|
-
} else {
|
|
668
|
-
source = String(inputs.source || "current-workspace").trim();
|
|
669
|
-
const paths = String(inputs.paths || "")
|
|
670
|
-
.split(/\r?\n|,/)
|
|
671
|
-
.map((x) => expandRuntimePlaceholders(x, workspaceContext).trim())
|
|
672
|
-
.filter(Boolean);
|
|
673
|
-
const include = String(inputs.include || "").split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
|
|
674
|
-
const exclude = String(inputs.exclude || "").split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
|
|
675
|
-
loaded = buildSkillsContext({ workspaceContext, source, paths, include, exclude, mergeMode });
|
|
676
|
-
}
|
|
677
|
-
let next = loaded;
|
|
678
|
-
if (existingSkills && mergeMode !== "replace") {
|
|
679
|
-
const existingBodies = Array.isArray(existingSkills.skillBodies) ? existingSkills.skillBodies : [];
|
|
680
|
-
const loadedBodies = Array.isArray(loaded.skillBodies) ? loaded.skillBodies : [];
|
|
681
|
-
const skillBodies = mergeMode === "prepend" ? [...loadedBodies, ...existingBodies] : [...existingBodies, ...loadedBodies];
|
|
682
|
-
const seen = new Set();
|
|
683
|
-
next = {
|
|
684
|
-
...loaded,
|
|
685
|
-
skillBodies: skillBodies.filter((s) => {
|
|
686
|
-
const key = s.key || s.name;
|
|
687
|
-
if (seen.has(key)) return false;
|
|
688
|
-
seen.add(key);
|
|
689
|
-
return true;
|
|
690
|
-
}),
|
|
691
|
-
};
|
|
692
|
-
next.skills = next.skillBodies.map(({ body, ...meta }) => meta);
|
|
693
|
-
next.skillKeys = next.skills.map((s) => s.key);
|
|
694
|
-
next.loadedCount = next.skills.length;
|
|
695
|
-
}
|
|
696
|
-
writeOutputSlot(runDir, instanceId, execId, "skillsContext", JSON.stringify(next));
|
|
697
|
-
writeOutputSlot(runDir, instanceId, execId, "loadedCount", String(next.loadedCount || 0));
|
|
698
|
-
writeOutputSlot(runDir, instanceId, execId, "summary", `${next.loadedCount || 0} skills loaded from ${source}`);
|
|
699
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `加载 ${next.loadedCount || 0} 个 skills` }, { execId });
|
|
700
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "load-skills", `Loaded ${next.loadedCount || 0} skills.\n`);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function readPrintableValue(value, runDir) {
|
|
704
|
-
const text = String(value ?? "").trim();
|
|
705
|
-
if (!text) return "";
|
|
706
|
-
const candidates = [];
|
|
707
|
-
if (path.isAbsolute(text)) candidates.push(text);
|
|
708
|
-
candidates.push(path.join(runDir, text));
|
|
709
|
-
for (const candidate of candidates) {
|
|
710
|
-
try {
|
|
711
|
-
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) continue;
|
|
712
|
-
const stat = fs.statSync(candidate);
|
|
713
|
-
if (stat.size > 1024 * 1024) {
|
|
714
|
-
return fs.readFileSync(candidate, "utf-8").slice(0, 1024 * 1024) + "\n\n[内容超过 1MB,已截断]";
|
|
715
|
-
}
|
|
716
|
-
return fs.readFileSync(candidate, "utf-8").trim();
|
|
717
|
-
} catch (_) {}
|
|
718
|
-
}
|
|
719
|
-
return text;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function readResultBody(runDir, sourceInstanceId) {
|
|
723
|
-
const id = String(sourceInstanceId || "").trim();
|
|
724
|
-
if (!id) return "";
|
|
725
|
-
const resultPath = path.join(runDir, intermediateDirForNode(id), intermediateResultBasename(id, 1));
|
|
726
|
-
try {
|
|
727
|
-
if (!fs.existsSync(resultPath)) return "";
|
|
728
|
-
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
729
|
-
const match = raw.match(/---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n([\s\S]*)$/);
|
|
730
|
-
return (match ? match[1] : raw).trim();
|
|
731
|
-
} catch (_) {
|
|
732
|
-
return "";
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
737
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
738
|
-
const flowJson = readFlowJsonObject(workspaceRoot, flowName, uuid);
|
|
739
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
740
|
-
const inputs = data.ok ? (data.resolvedInputs || {}) : {};
|
|
741
|
-
const skipNames = new Set(["prev", "next", "workspaceContext", "gitContext", "skillsContext", "workspaceRoot", "pipelineWorkspace", "flowName", "runDir", "flowDir", "cwd"]);
|
|
742
|
-
|
|
743
|
-
let content = readPrintableValue(inputs.content, runDir);
|
|
744
|
-
if (!content) {
|
|
745
|
-
const parts = [];
|
|
746
|
-
for (const [name, value] of Object.entries(inputs)) {
|
|
747
|
-
if (skipNames.has(name)) continue;
|
|
748
|
-
const v = readPrintableValue(value, runDir);
|
|
749
|
-
if (!v) continue;
|
|
750
|
-
parts.push(name === "content" ? v : `## ${name}\n\n${v}`);
|
|
751
|
-
}
|
|
752
|
-
content = parts.join("\n\n").trim();
|
|
753
|
-
}
|
|
754
|
-
if (!content && inputs.prev) {
|
|
755
|
-
content = readResultBody(runDir, inputs.prev);
|
|
756
|
-
}
|
|
757
|
-
if (!content && flowJson?.nodes) {
|
|
758
|
-
const node = flowJson.nodes.find((n) => n.id === instanceId);
|
|
759
|
-
content = String(node?.body || "").trim();
|
|
760
|
-
}
|
|
761
|
-
if (!content) content = "(tool_print 没有可展示内容:请填写 content 输入,或连接上游输出到 content。)";
|
|
762
|
-
|
|
763
|
-
writeResult(
|
|
764
|
-
workspaceRoot,
|
|
765
|
-
flowName,
|
|
766
|
-
uuid,
|
|
767
|
-
instanceId,
|
|
768
|
-
{ status: "success", message: "Print 输出" },
|
|
769
|
-
{ execId, preserveBody: false, body: content },
|
|
770
|
-
);
|
|
771
|
-
return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "tool-print", `Printed content for ${instanceId}.\n`);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
function writeWaitState(runDir, state) {
|
|
775
|
-
const legacyPath = path.join(runDir, "wait-state.json");
|
|
776
|
-
const registryPath = path.join(runDir, "wait-states.json");
|
|
777
|
-
let registry = { version: 1, flowName: state.flowName, uuid: state.uuid, waits: [] };
|
|
778
|
-
if (fs.existsSync(registryPath)) {
|
|
779
|
-
try {
|
|
780
|
-
const parsed = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
|
|
781
|
-
if (parsed && typeof parsed === "object" && Array.isArray(parsed.waits)) registry = parsed;
|
|
782
|
-
} catch (_) {}
|
|
783
|
-
}
|
|
784
|
-
const waitId = String(state.waitId || state.id || `${state.instanceId}:${state.execId || 1}`).trim();
|
|
785
|
-
const waits = registry.waits.filter((w) => {
|
|
786
|
-
if (!w) return false;
|
|
787
|
-
const key = String(w.waitId || w.id || "").trim();
|
|
788
|
-
if (key && key === waitId) return false;
|
|
789
|
-
return !(w.instanceId === state.instanceId && String(w.execId || 1) === String(state.execId || 1));
|
|
790
|
-
});
|
|
791
|
-
const wait = { ...state, waitId, id: state.id || waitId };
|
|
792
|
-
waits.push(wait);
|
|
793
|
-
registry = {
|
|
794
|
-
...registry,
|
|
795
|
-
version: 1,
|
|
796
|
-
flowName: state.flowName,
|
|
797
|
-
uuid: state.uuid,
|
|
798
|
-
updatedAt: new Date().toISOString(),
|
|
799
|
-
waits,
|
|
800
|
-
};
|
|
801
|
-
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
802
|
-
fs.writeFileSync(legacyPath, JSON.stringify(wait, null, 2) + "\n", "utf-8");
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function buildWaitId(uuid, instanceId, execId, explicit = "") {
|
|
806
|
-
const text = String(explicit || "").trim();
|
|
807
|
-
if (text) return text;
|
|
808
|
-
return `${uuid}:${instanceId}:${execId || 1}`;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
function readWaitStateById(runDir, waitId) {
|
|
812
|
-
const text = String(waitId || "").trim();
|
|
813
|
-
if (!text) return null;
|
|
814
|
-
const registryPath = path.join(runDir, "wait-states.json");
|
|
815
|
-
if (fs.existsSync(registryPath)) {
|
|
816
|
-
try {
|
|
817
|
-
const registry = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
|
|
818
|
-
const waits = Array.isArray(registry?.waits) ? registry.waits : [];
|
|
819
|
-
const found = waits.find((w) => {
|
|
820
|
-
const key = String(w?.waitId || w?.id || "").trim();
|
|
821
|
-
return key === text;
|
|
822
|
-
});
|
|
823
|
-
if (found) return found;
|
|
824
|
-
} catch (_) {}
|
|
825
|
-
}
|
|
826
|
-
const legacyPath = path.join(runDir, "wait-state.json");
|
|
827
|
-
if (fs.existsSync(legacyPath)) {
|
|
828
|
-
try {
|
|
829
|
-
const state = JSON.parse(fs.readFileSync(legacyPath, "utf-8"));
|
|
830
|
-
const key = String(state?.waitId || state?.id || "").trim();
|
|
831
|
-
if (key === text) return state;
|
|
832
|
-
} catch (_) {}
|
|
833
|
-
}
|
|
834
|
-
return null;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
function readCancelFlagForWait(runDir, waitId) {
|
|
838
|
-
const state = readWaitStateById(runDir, waitId);
|
|
839
|
-
if (state && (state.cancelled === true || state.status === "cancelled" || state.branch === "cancelled")) return true;
|
|
840
|
-
return readCancelFlag(runDir);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
function readCancelFlag(runDir) {
|
|
844
|
-
for (const name of ["cancelled", "cancelled.json", "wait-cancelled.json"]) {
|
|
845
|
-
const p = path.join(runDir, name);
|
|
846
|
-
if (!fs.existsSync(p)) continue;
|
|
847
|
-
if (name.endsWith(".json")) {
|
|
848
|
-
try {
|
|
849
|
-
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
850
|
-
if (data && (data.cancelled === true || data.status === "cancelled")) return true;
|
|
851
|
-
} catch (_) {}
|
|
852
|
-
} else {
|
|
853
|
-
return true;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
function boolish(v) {
|
|
860
|
-
if (v == null || String(v).trim() === "") return false;
|
|
861
|
-
const s = String(v).trim();
|
|
862
|
-
if (fs.existsSync(s)) {
|
|
863
|
-
try {
|
|
864
|
-
return parseBool(fs.readFileSync(s, "utf-8").trim());
|
|
865
|
-
} catch (_) {
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
return parseBool(s);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
function emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, suffix, content) {
|
|
873
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
874
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
875
|
-
const promptPath = path.join(nodeIntermediateDir, `${instanceId}.${suffix}.prompt.md`);
|
|
876
|
-
fs.writeFileSync(promptPath, content, "utf-8");
|
|
877
|
-
return path.relative(workspaceRoot, promptPath).replace(/\\/g, "/");
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* 若为 tool_load_key / tool_save_key / tool_get_env,写入「直接执行 agentflow apply -ai run-tool-nodejs + 对应脚本」的 prompt,
|
|
882
|
-
* key/value 从 getResolvedValues 的 resolvedInputs 读取并拼入命令。
|
|
883
|
-
* 返回 { optionalPromptPath, directCommand },供 AI 用 optionalPromptPath、CLI 用 directCommand 执行。
|
|
884
|
-
* @param {number} execId - 本轮 execId,传入 run-tool-nodejs 以写对 result 文件(第二轮起必须)
|
|
885
|
-
*/
|
|
886
|
-
function emitLoadSaveKeyOptionalPrompt(workspaceRoot, flowName, uuid, instanceId, definitionId, execId) {
|
|
887
|
-
if (definitionId !== "tool_load_key" && definitionId !== "tool_save_key" && definitionId !== "tool_get_env") return null;
|
|
888
|
-
const scriptName =
|
|
889
|
-
definitionId === "tool_load_key" ? "load-key.mjs"
|
|
890
|
-
: definitionId === "tool_save_key" ? "save-key.mjs"
|
|
891
|
-
: "get-env.mjs";
|
|
892
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
893
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
894
|
-
const promptFileName = `${instanceId}.run-key.prompt.md`;
|
|
895
|
-
const promptPath = path.join(nodeIntermediateDir, promptFileName);
|
|
896
|
-
|
|
897
|
-
let key = "";
|
|
898
|
-
let value = "";
|
|
899
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
900
|
-
if (data.ok && data.resolvedInputs) {
|
|
901
|
-
const inputs = data.resolvedInputs;
|
|
902
|
-
key = inputs.key != null ? String(inputs.key).trim() : "";
|
|
903
|
-
value = inputs.value != null ? String(inputs.value).trim() : "";
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
const rootArg = workspaceRoot;
|
|
907
|
-
const q = bashSingleQuote;
|
|
908
|
-
const keyQ = q(key);
|
|
909
|
-
const af = agentflowCommand();
|
|
910
|
-
const directCommand =
|
|
911
|
-
definitionId === "tool_get_env"
|
|
912
|
-
? `${af} apply -ai get-env ${q(rootArg)} ${q(flowName)} ${q(uuid)} ${q(instanceId)} ${q(String(execId))} ${keyQ}`
|
|
913
|
-
: (() => {
|
|
914
|
-
const scriptArgs =
|
|
915
|
-
definitionId === "tool_load_key"
|
|
916
|
-
? `${q(rootArg)} ${q(flowName)} ${q(uuid)} ${keyQ}`
|
|
917
|
-
: `${q(rootArg)} ${q(flowName)} ${q(uuid)} ${keyQ} ${q(value)}`;
|
|
918
|
-
const scriptPath = path.join(__dirname, definitionId === "tool_load_key" ? "load-key.mjs" : "save-key.mjs");
|
|
919
|
-
return `${af} apply -ai run-tool-nodejs ${q(rootArg)} ${q(flowName)} ${q(uuid)} ${q(instanceId)} ${q(String(execId))} -- node ${q(scriptPath)} ${scriptArgs}`;
|
|
920
|
-
})();
|
|
921
|
-
const content = `此节点不调用 subagent,请主 agent 在工作区根目录直接执行以下命令完成该节点。
|
|
922
|
-
|
|
923
|
-
\`\`\`bash
|
|
924
|
-
${directCommand}
|
|
925
|
-
\`\`\`
|
|
926
|
-
`;
|
|
927
|
-
|
|
928
|
-
try {
|
|
929
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
930
|
-
// 备份由 snapshotPriorRoundIfNeeded 统一处理
|
|
931
|
-
fs.writeFileSync(promptPath, content, "utf-8");
|
|
932
|
-
} catch (e) {
|
|
933
|
-
return null;
|
|
934
|
-
}
|
|
935
|
-
const relativePath = path.relative(workspaceRoot, promptPath);
|
|
936
|
-
return { optionalPromptPath: relativePath.replace(/\\/g, "/"), directCommand };
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* 若为 tool_nodejs 且 buildNodePrompt 返回了非空 script(来自 flow.yaml instance.script 字段),
|
|
941
|
-
* 生成 directCommand 直接通过 run-tool-nodejs 执行脚本,不调用 subagent。
|
|
942
|
-
* @param {string} resolvedScript - 已解析占位符且各参数已 shell-quote 的命令(run-tool-nodejs -- 之后的部分)
|
|
943
|
-
* @returns {{ optionalPromptPath: string, directCommand: string } | null}
|
|
944
|
-
*/
|
|
945
|
-
function emitToolNodejsDirectCommand(workspaceRoot, flowName, uuid, instanceId, resolvedScript, execId) {
|
|
946
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
947
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
948
|
-
const promptFileName = `${instanceId}.tool-nodejs-direct.prompt.md`;
|
|
949
|
-
const promptPath = path.join(nodeIntermediateDir, promptFileName);
|
|
950
|
-
|
|
951
|
-
const q = bashSingleQuote;
|
|
952
|
-
const directCommand = `${agentflowCommand()} apply -ai run-tool-nodejs ${q(workspaceRoot)} ${q(flowName)} ${q(uuid)} ${q(instanceId)} ${q(String(execId))} -- ${resolvedScript}`;
|
|
953
|
-
const content = `此节点为 tool_nodejs(直接执行模式),不调用 subagent,由流水线直接执行以下命令。
|
|
954
|
-
|
|
955
|
-
\`\`\`bash
|
|
956
|
-
${directCommand}
|
|
957
|
-
\`\`\`
|
|
958
|
-
`;
|
|
959
|
-
|
|
960
|
-
try {
|
|
961
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
962
|
-
// 备份由 snapshotPriorRoundIfNeeded 统一处理
|
|
963
|
-
fs.writeFileSync(promptPath, content, "utf-8");
|
|
964
|
-
} catch (e) {
|
|
965
|
-
return null;
|
|
966
|
-
}
|
|
967
|
-
const relativePath = path.relative(workspaceRoot, promptPath);
|
|
968
|
-
return { optionalPromptPath: relativePath.replace(/\\/g, "/"), directCommand };
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* 若为 control_anyOne,写入「直接执行 write-result 将该节点标为 success」的 prompt,不调用 subagent。
|
|
973
|
-
* 返回 { optionalPromptPath, directCommand },供 AI 用 optionalPromptPath、CLI 用 directCommand 执行。
|
|
974
|
-
*/
|
|
975
|
-
function emitAnyOneOptionalPrompt(workspaceRoot, flowName, uuid, instanceId, execId) {
|
|
976
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
977
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
978
|
-
const promptFileName = `${instanceId}.anyOne.prompt.md`;
|
|
979
|
-
const promptPath = path.join(nodeIntermediateDir, promptFileName);
|
|
980
|
-
|
|
981
|
-
const jsonPayload = JSON.stringify({
|
|
982
|
-
status: "success",
|
|
983
|
-
message: "任一前驱已就绪,直接通过",
|
|
984
|
-
execId,
|
|
985
|
-
});
|
|
986
|
-
const directCommand = `${agentflowCommand()} apply -ai write-result ${bashSingleQuote(workspaceRoot)} ${bashSingleQuote(flowName)} ${bashSingleQuote(uuid)} ${bashSingleQuote(instanceId)} --json ${bashSingleQuote(jsonPayload)}`;
|
|
987
|
-
const content = `此节点为 control_anyOne,不调用 subagent。请主 agent 在工作区根目录直接执行以下命令将该节点标记为 success。
|
|
988
|
-
|
|
989
|
-
\`\`\`bash
|
|
990
|
-
${directCommand}
|
|
991
|
-
\`\`\`
|
|
992
|
-
`;
|
|
993
|
-
|
|
994
|
-
try {
|
|
995
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
996
|
-
// 备份由 snapshotPriorRoundIfNeeded 统一处理
|
|
997
|
-
fs.writeFileSync(promptPath, content, "utf-8");
|
|
998
|
-
} catch (e) {
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
|
-
const relativePath = path.relative(workspaceRoot, promptPath);
|
|
1002
|
-
return { optionalPromptPath: relativePath.replace(/\\/g, "/"), directCommand };
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
async function main() {
|
|
1006
|
-
const args = process.argv.slice(2);
|
|
1007
|
-
if (args.length < 4) {
|
|
1008
|
-
console.error(
|
|
1009
|
-
JSON.stringify({
|
|
1010
|
-
ok: false,
|
|
1011
|
-
error:
|
|
1012
|
-
"Usage: node pre-process-node.mjs <workspaceRoot> <flowName> <uuid> <instanceId>",
|
|
1013
|
-
}),
|
|
1014
|
-
);
|
|
1015
|
-
process.exit(1);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
const [root, flowName, uuid, instanceId] = args;
|
|
1019
|
-
const workspaceRoot = path.resolve(root);
|
|
1020
|
-
|
|
1021
|
-
let execId = 1;
|
|
1022
|
-
let priorExecId = 0;
|
|
1023
|
-
const loadKeyPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "load-key.mjs");
|
|
1024
|
-
const execIdKey = "execId_" + instanceId;
|
|
1025
|
-
// load-key.mjs 参数约定:<workspaceRoot> <flowName> <uuid> <key>。
|
|
1026
|
-
// 缺 flowName 会导致 getRunDir 拼成错误路径,memory 永远读不到 → execId 每轮都回到 1,
|
|
1027
|
-
// snapshotPriorRoundIfNeeded 也永远 no-op,loop 跑多少轮 sidebar 都只有 #1/#2。
|
|
1028
|
-
const loadResult = spawnSync(process.execPath, [loadKeyPath, workspaceRoot, flowName, uuid, execIdKey], {
|
|
1029
|
-
cwd: workspaceRoot,
|
|
1030
|
-
encoding: "utf-8",
|
|
1031
|
-
});
|
|
1032
|
-
if (loadResult.stdout) {
|
|
1033
|
-
try {
|
|
1034
|
-
const out = JSON.parse(loadResult.stdout.trim());
|
|
1035
|
-
const result = out?.message?.result;
|
|
1036
|
-
if (result !== undefined && result !== "") {
|
|
1037
|
-
const current = parseInt(String(result), 10) || 0;
|
|
1038
|
-
priorExecId = current;
|
|
1039
|
-
execId = current + 1;
|
|
1040
|
-
}
|
|
1041
|
-
} catch (_) {}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
1045
|
-
|
|
1046
|
-
// 唯一备份入口:把上一轮的 intermediate/output 文件统一 rename 为 _<priorExecId>。
|
|
1047
|
-
// 之后任何 writer(write-result / build-node-prompt / run-tool-nodejs / get-env 等)
|
|
1048
|
-
// 都不再负责备份,只管对 current 路径写入新内容。
|
|
1049
|
-
snapshotPriorRoundIfNeeded(runDir, instanceId, priorExecId);
|
|
1050
|
-
|
|
1051
|
-
const resultPathRel = `${intermediateDirForNode(instanceId)}/${intermediateResultBasename(instanceId, execId)}`;
|
|
1052
|
-
|
|
1053
|
-
/** control_if:不执行 subagent,根据第一个 bool 类型输入直接写 result 并返回 optionalPromptPath */
|
|
1054
|
-
const definitionId = getDefinitionIdFromFlowJson(workspaceRoot, flowName, uuid, instanceId);
|
|
1055
|
-
if (definitionId === "control_if") {
|
|
1056
|
-
const flow = readFlowJson(workspaceRoot, flowName, uuid);
|
|
1057
|
-
const inputSlotTypes = (flow?.inputSlotTypes && flow.inputSlotTypes[instanceId]) || null;
|
|
1058
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
1059
|
-
if (!data.ok || !data.resolvedInputs) {
|
|
1060
|
-
console.error(JSON.stringify({ ok: false, error: "control_if: getResolvedValues failed or no resolvedInputs" }));
|
|
1061
|
-
process.exit(1);
|
|
1062
|
-
}
|
|
1063
|
-
const rawVal = getFirstBoolInputValue(data.resolvedInputs, inputSlotTypes);
|
|
1064
|
-
if (rawVal == null) {
|
|
1065
|
-
console.error(JSON.stringify({ ok: false, error: "control_if: no bool-type input slot found" }));
|
|
1066
|
-
process.exit(1);
|
|
1067
|
-
}
|
|
1068
|
-
let boolValue;
|
|
1069
|
-
let filePath = null;
|
|
1070
|
-
if (rawVal.startsWith("output/") || rawVal.startsWith("intermediate/")) {
|
|
1071
|
-
filePath = path.join(runDir, rawVal);
|
|
1072
|
-
} else if (path.isAbsolute(rawVal)) {
|
|
1073
|
-
filePath = rawVal;
|
|
1074
|
-
}
|
|
1075
|
-
if (filePath) {
|
|
1076
|
-
if (fs.existsSync(filePath)) {
|
|
1077
|
-
boolValue = parseBool(fs.readFileSync(filePath, "utf-8").trim());
|
|
1078
|
-
} else {
|
|
1079
|
-
// 查找 snapshotPriorRoundIfNeeded 创建的 _N 备份文件
|
|
1080
|
-
const dir = path.dirname(filePath);
|
|
1081
|
-
const ext = path.extname(filePath);
|
|
1082
|
-
const base = path.basename(filePath, ext);
|
|
1083
|
-
let found = false;
|
|
1084
|
-
try {
|
|
1085
|
-
if (fs.existsSync(dir)) {
|
|
1086
|
-
const candidates = fs.readdirSync(dir).filter(f =>
|
|
1087
|
-
f.startsWith(base + "_") && f.endsWith(ext) &&
|
|
1088
|
-
/^\d+$/.test(f.slice(base.length + 1, -ext.length))
|
|
1089
|
-
);
|
|
1090
|
-
if (candidates.length > 0) {
|
|
1091
|
-
candidates.sort((a, b) => {
|
|
1092
|
-
const na = parseInt(a.slice(base.length + 1, -ext.length), 10);
|
|
1093
|
-
const nb = parseInt(b.slice(base.length + 1, -ext.length), 10);
|
|
1094
|
-
return nb - na;
|
|
1095
|
-
});
|
|
1096
|
-
boolValue = parseBool(fs.readFileSync(path.join(dir, candidates[0]), "utf-8").trim());
|
|
1097
|
-
found = true;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
} catch (_) {}
|
|
1101
|
-
if (!found) {
|
|
1102
|
-
console.error(JSON.stringify({ ok: false, error: `control_if: bool input file not found: ${rawVal}` }));
|
|
1103
|
-
process.exit(1);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
} else {
|
|
1107
|
-
boolValue = parseBool(rawVal);
|
|
1108
|
-
}
|
|
1109
|
-
const branch = boolValue ? "true" : "false";
|
|
1110
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `分支 ${branch}`, branch }, { execId });
|
|
1111
|
-
const nodeIntermediateDir = path.join(runDir, intermediateDirForNode(instanceId));
|
|
1112
|
-
fs.mkdirSync(nodeIntermediateDir, { recursive: true });
|
|
1113
|
-
const build = buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1114
|
-
if (build.ok) writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1115
|
-
const noopPromptPath = path.join(nodeIntermediateDir, `${instanceId}.control_if_noop.prompt.md`);
|
|
1116
|
-
// 备份由 snapshotPriorRoundIfNeeded 统一处理
|
|
1117
|
-
fs.writeFileSync(
|
|
1118
|
-
noopPromptPath,
|
|
1119
|
-
"此节点为 **control_if**,已由预处理根据 bool 输入直接写入 result,无需执行任何操作。",
|
|
1120
|
-
"utf-8",
|
|
1121
|
-
);
|
|
1122
|
-
const optionalPromptPath = path.relative(workspaceRoot, noopPromptPath).replace(/\\/g, "/");
|
|
1123
|
-
const output = {
|
|
1124
|
-
ok: true,
|
|
1125
|
-
promptPath: optionalPromptPath,
|
|
1126
|
-
resultPath: resultPathRel,
|
|
1127
|
-
execId,
|
|
1128
|
-
subagent: "agentflow-node-executor",
|
|
1129
|
-
optionalPromptPath,
|
|
1130
|
-
definitionId,
|
|
1131
|
-
};
|
|
1132
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "control_if-direct-write", instanceId, branch });
|
|
1133
|
-
console.log(JSON.stringify(output));
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
if (definitionId === "control_delay" || definitionId === "control_wait_until") {
|
|
1138
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
1139
|
-
if (!data.ok) {
|
|
1140
|
-
console.error(JSON.stringify({ ok: false, error: `${definitionId}: getResolvedValues failed` }));
|
|
1141
|
-
process.exit(1);
|
|
1142
|
-
}
|
|
1143
|
-
const inputs = data.resolvedInputs || {};
|
|
1144
|
-
let wakeAt;
|
|
1145
|
-
try {
|
|
1146
|
-
if (definitionId === "control_delay") {
|
|
1147
|
-
const duration = inputs.duration ?? inputs.value ?? "";
|
|
1148
|
-
wakeAt = new Date(Date.now() + parseDurationMs(duration)).toISOString();
|
|
1149
|
-
} else {
|
|
1150
|
-
const timezone = inputs.timezone || "Asia/Shanghai";
|
|
1151
|
-
wakeAt = parseDateTime(inputs.until ?? inputs.wakeAt ?? "", timezone).toISOString();
|
|
1152
|
-
}
|
|
1153
|
-
} catch (e) {
|
|
1154
|
-
console.error(JSON.stringify({ ok: false, error: `${definitionId}: ${e.message}` }));
|
|
1155
|
-
process.exit(1);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
const waitId = buildWaitId(uuid, instanceId, execId, inputs.waitId || inputs.watchId);
|
|
1159
|
-
writeOutputSlot(runDir, instanceId, execId, "waitId", waitId);
|
|
1160
|
-
writeOutputSlot(runDir, instanceId, execId, "wakeAt", wakeAt);
|
|
1161
|
-
writeWaitState(runDir, {
|
|
1162
|
-
waitId,
|
|
1163
|
-
status: "waiting",
|
|
1164
|
-
reason: definitionId,
|
|
1165
|
-
flowName,
|
|
1166
|
-
uuid,
|
|
1167
|
-
instanceId,
|
|
1168
|
-
execId,
|
|
1169
|
-
wakeAt,
|
|
1170
|
-
createdAt: new Date().toISOString(),
|
|
1171
|
-
});
|
|
1172
|
-
writeResult(
|
|
1173
|
-
workspaceRoot,
|
|
1174
|
-
flowName,
|
|
1175
|
-
uuid,
|
|
1176
|
-
instanceId,
|
|
1177
|
-
{ status: "pending", message: `等待至 ${wakeAt}` },
|
|
1178
|
-
{ execId, preserveBody: false },
|
|
1179
|
-
);
|
|
1180
|
-
const promptPath = emitLocalNoopPrompt(
|
|
1181
|
-
workspaceRoot,
|
|
1182
|
-
runDir,
|
|
1183
|
-
instanceId,
|
|
1184
|
-
"waiting",
|
|
1185
|
-
`此节点为 ${definitionId},已写入 wait-state.json,等待 scheduler 在 ${wakeAt} 唤醒。\n`,
|
|
1186
|
-
);
|
|
1187
|
-
writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1188
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "waiting", instanceId, waitId, wakeAt, definitionId });
|
|
1189
|
-
console.log(JSON.stringify({
|
|
1190
|
-
ok: true,
|
|
1191
|
-
promptPath,
|
|
1192
|
-
resultPath: resultPathRel,
|
|
1193
|
-
execId,
|
|
1194
|
-
subagent: "agentflow-node-executor",
|
|
1195
|
-
optionalPromptPath: promptPath,
|
|
1196
|
-
definitionId,
|
|
1197
|
-
}));
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
if (definitionId === "control_interval_loop") {
|
|
1202
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
1203
|
-
if (!data.ok) {
|
|
1204
|
-
console.error(JSON.stringify({ ok: false, error: "control_interval_loop: getResolvedValues failed" }));
|
|
1205
|
-
process.exit(1);
|
|
1206
|
-
}
|
|
1207
|
-
const inputs = data.resolvedInputs || {};
|
|
1208
|
-
let branch = "continue";
|
|
1209
|
-
let message = "";
|
|
1210
|
-
let wakeAt = "";
|
|
1211
|
-
let expired = false;
|
|
1212
|
-
try {
|
|
1213
|
-
const timezone = inputs.timezone || "Asia/Shanghai";
|
|
1214
|
-
const cancelled = boolish(inputs.cancelled) || readCancelFlag(runDir);
|
|
1215
|
-
const done = boolish(inputs.done);
|
|
1216
|
-
if (cancelled) {
|
|
1217
|
-
branch = "cancelled";
|
|
1218
|
-
message = "已取消";
|
|
1219
|
-
} else if (done) {
|
|
1220
|
-
branch = "done";
|
|
1221
|
-
message = "已完成";
|
|
1222
|
-
} else {
|
|
1223
|
-
let deadline = null;
|
|
1224
|
-
if (inputs.deadlineAt) {
|
|
1225
|
-
deadline = parseDateTime(inputs.deadlineAt, timezone);
|
|
1226
|
-
} else if (inputs.duration) {
|
|
1227
|
-
const start = inputs.startAt ? parseDateTime(inputs.startAt, timezone) : new Date();
|
|
1228
|
-
deadline = new Date(start.getTime() + parseDurationMs(inputs.duration));
|
|
1229
|
-
}
|
|
1230
|
-
expired = deadline ? Date.now() >= deadline.getTime() : false;
|
|
1231
|
-
if (deadline) writeOutputSlot(runDir, instanceId, execId, "deadlineAt", deadline.toISOString());
|
|
1232
|
-
if (expired) {
|
|
1233
|
-
branch = "timeout";
|
|
1234
|
-
message = `已超过截止时间 ${deadline.toISOString()}`;
|
|
1235
|
-
} else {
|
|
1236
|
-
const interval = inputs.interval || "10m";
|
|
1237
|
-
wakeAt = new Date(Date.now() + parseDurationMs(interval)).toISOString();
|
|
1238
|
-
branch = "continue";
|
|
1239
|
-
message = `等待至 ${wakeAt}`;
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
} catch (e) {
|
|
1243
|
-
console.error(JSON.stringify({ ok: false, error: `control_interval_loop: ${e.message}` }));
|
|
1244
|
-
process.exit(1);
|
|
1245
|
-
}
|
|
1246
|
-
writeOutputSlot(runDir, instanceId, execId, "expired", expired ? "true" : "false");
|
|
1247
|
-
if (wakeAt) writeOutputSlot(runDir, instanceId, execId, "wakeAt", wakeAt);
|
|
1248
|
-
const promptPath = emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "interval-loop", `此节点为 control_interval_loop,分支:${branch}。\n`);
|
|
1249
|
-
writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1250
|
-
if (wakeAt) {
|
|
1251
|
-
writeWaitState(runDir, {
|
|
1252
|
-
status: "waiting",
|
|
1253
|
-
reason: definitionId,
|
|
1254
|
-
flowName,
|
|
1255
|
-
uuid,
|
|
1256
|
-
instanceId,
|
|
1257
|
-
execId,
|
|
1258
|
-
branch,
|
|
1259
|
-
wakeAt,
|
|
1260
|
-
createdAt: new Date().toISOString(),
|
|
1261
|
-
});
|
|
1262
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "pending", message, branch }, { execId, preserveBody: false });
|
|
1263
|
-
} else {
|
|
1264
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message, branch }, { execId, preserveBody: false });
|
|
1265
|
-
}
|
|
1266
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "interval-loop", instanceId, branch, wakeAt: wakeAt || undefined });
|
|
1267
|
-
console.log(JSON.stringify({
|
|
1268
|
-
ok: true,
|
|
1269
|
-
promptPath,
|
|
1270
|
-
resultPath: resultPathRel,
|
|
1271
|
-
execId,
|
|
1272
|
-
subagent: "agentflow-node-executor",
|
|
1273
|
-
optionalPromptPath: promptPath,
|
|
1274
|
-
definitionId,
|
|
1275
|
-
}));
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
if (definitionId === "control_cancelled") {
|
|
1280
|
-
const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
|
|
1281
|
-
if (!data.ok) {
|
|
1282
|
-
console.error(JSON.stringify({ ok: false, error: "control_cancelled: getResolvedValues failed" }));
|
|
1283
|
-
process.exit(1);
|
|
1284
|
-
}
|
|
1285
|
-
let boolValue;
|
|
1286
|
-
let message;
|
|
1287
|
-
try {
|
|
1288
|
-
const inputs = data.resolvedInputs || {};
|
|
1289
|
-
const waitId = String(inputs.waitId || inputs.watchId || "").trim();
|
|
1290
|
-
boolValue = waitId ? readCancelFlagForWait(runDir, waitId) : readCancelFlag(runDir);
|
|
1291
|
-
writeOutputSlot(runDir, instanceId, execId, "cancelled", boolValue ? "true" : "false");
|
|
1292
|
-
message = boolValue ? "已取消" : "未取消";
|
|
1293
|
-
} catch (e) {
|
|
1294
|
-
console.error(JSON.stringify({ ok: false, error: `control_cancelled: ${e.message}` }));
|
|
1295
|
-
process.exit(1);
|
|
1296
|
-
}
|
|
1297
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message }, { execId, preserveBody: false });
|
|
1298
|
-
const promptPath = emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "local", `此节点为 ${definitionId},已本地计算完成。\n`);
|
|
1299
|
-
writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1300
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "local-control", instanceId, definitionId, value: boolValue });
|
|
1301
|
-
console.log(JSON.stringify({
|
|
1302
|
-
ok: true,
|
|
1303
|
-
promptPath,
|
|
1304
|
-
resultPath: resultPathRel,
|
|
1305
|
-
execId,
|
|
1306
|
-
subagent: "agentflow-node-executor",
|
|
1307
|
-
optionalPromptPath: promptPath,
|
|
1308
|
-
definitionId,
|
|
1309
|
-
}));
|
|
1310
|
-
return;
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
if (definitionId === "tool_git_checkout" || definitionId === "tool_git_worktree_load" || definitionId === "tool_git_worktree_unload" || definitionId === "tool_gitlab_create_mr" || definitionId === "tool_jenkins_build" || definitionId === "tool_wecom_send_group_markdown" || definitionId === "tool_wecom_send_app_markdown" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
|
|
1314
|
-
try {
|
|
1315
|
-
const localResult =
|
|
1316
|
-
definitionId === "tool_git_checkout"
|
|
1317
|
-
? { promptPath: emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel) }
|
|
1318
|
-
: definitionId === "tool_git_worktree_load"
|
|
1319
|
-
? { promptPath: emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1320
|
-
: definitionId === "tool_git_worktree_unload"
|
|
1321
|
-
? { promptPath: emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1322
|
-
: definitionId === "tool_gitlab_create_mr"
|
|
1323
|
-
? { promptPath: await emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1324
|
-
: definitionId === "tool_jenkins_build"
|
|
1325
|
-
? emitJenkinsBuildNode(workspaceRoot, flowName, uuid, instanceId, execId)
|
|
1326
|
-
: definitionId === "tool_wecom_send_group_markdown" || definitionId === "tool_wecom_send_app_markdown"
|
|
1327
|
-
? { promptPath: await emitWecomMarkdownNode(workspaceRoot, flowName, uuid, instanceId, execId, definitionId) }
|
|
1328
|
-
: definitionId === "control_cd_workspace"
|
|
1329
|
-
? { promptPath: emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1330
|
-
: definitionId === "control_user_workspace"
|
|
1331
|
-
? { promptPath: emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1332
|
-
: definitionId === "control_load_skills"
|
|
1333
|
-
? { promptPath: emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId) }
|
|
1334
|
-
: { promptPath: emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId) };
|
|
1335
|
-
const promptPath = localResult.promptPath;
|
|
1336
|
-
writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1337
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "runtime-context-node", instanceId, definitionId });
|
|
1338
|
-
console.log(JSON.stringify({
|
|
1339
|
-
ok: true,
|
|
1340
|
-
promptPath,
|
|
1341
|
-
resultPath: resultPathRel,
|
|
1342
|
-
execId,
|
|
1343
|
-
subagent: "agentflow-node-executor",
|
|
1344
|
-
optionalPromptPath: promptPath,
|
|
1345
|
-
definitionId,
|
|
1346
|
-
...(localResult.lifecycle ? { nodeLifecycle: localResult.lifecycle } : {}),
|
|
1347
|
-
}));
|
|
1348
|
-
return;
|
|
1349
|
-
} catch (e) {
|
|
1350
|
-
console.error(JSON.stringify({ ok: false, error: `${definitionId}: ${e.message || e}` }));
|
|
1351
|
-
process.exit(1);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
const runtimeContexts = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
|
|
1356
|
-
const data = buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId, {
|
|
1357
|
-
workspaceContext: runtimeContexts.workspaceContext,
|
|
1358
|
-
skillsContext: runtimeContexts.skillsContext,
|
|
1359
|
-
});
|
|
1360
|
-
if (!data.ok) {
|
|
1361
|
-
console.error(JSON.stringify({ ok: false, error: data.error || "build-node-prompt failed" }));
|
|
1362
|
-
process.exit(1);
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
const { role, model } = getRoleAndModelFromFlowJson(workspaceRoot, flowName, uuid, instanceId);
|
|
1366
|
-
const subagent = ROLE_TO_SUBAGENT[role] ?? (role && String(role).trim() ? String(role).trim() : ROLE_TO_SUBAGENT.普通);
|
|
1367
|
-
|
|
1368
|
-
const intermediateDir = path.join(runDir, "intermediate");
|
|
1369
|
-
|
|
1370
|
-
writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "running", message: "执行中" }, { preserveBody: false, execId });
|
|
1371
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", {
|
|
1372
|
-
event: "result-running",
|
|
1373
|
-
instanceId,
|
|
1374
|
-
resultPath: resultPathRel,
|
|
1375
|
-
});
|
|
1376
|
-
|
|
1377
|
-
writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1378
|
-
|
|
1379
|
-
const output = {
|
|
1380
|
-
ok: true,
|
|
1381
|
-
promptPath: data.promptPath,
|
|
1382
|
-
nodeContext: data.nodeContext ?? "",
|
|
1383
|
-
taskBody: data.taskBody ?? "",
|
|
1384
|
-
resultPath: resultPathRel,
|
|
1385
|
-
execId,
|
|
1386
|
-
subagent,
|
|
1387
|
-
definitionId,
|
|
1388
|
-
role,
|
|
1389
|
-
};
|
|
1390
|
-
if (data.workspaceContext) output.workspaceContext = data.workspaceContext;
|
|
1391
|
-
if (data.skillsContext) output.skillsContext = data.skillsContext;
|
|
1392
|
-
if (model) output.model = model;
|
|
1393
|
-
if (data.optionalPromptPath) {
|
|
1394
|
-
output.optionalPromptPath = data.optionalPromptPath;
|
|
1395
|
-
}
|
|
1396
|
-
const runKeyResult = emitLoadSaveKeyOptionalPrompt(workspaceRoot, flowName, uuid, instanceId, definitionId, execId);
|
|
1397
|
-
if (runKeyResult) {
|
|
1398
|
-
output.optionalPromptPath = runKeyResult.optionalPromptPath;
|
|
1399
|
-
output.directCommand = runKeyResult.directCommand;
|
|
1400
|
-
} else if (definitionId === "control_anyOne") {
|
|
1401
|
-
const anyOneResult = emitAnyOneOptionalPrompt(workspaceRoot, flowName, uuid, instanceId, execId);
|
|
1402
|
-
if (anyOneResult) {
|
|
1403
|
-
output.optionalPromptPath = anyOneResult.optionalPromptPath;
|
|
1404
|
-
output.directCommand = anyOneResult.directCommand;
|
|
1405
|
-
}
|
|
1406
|
-
} else if ((definitionId === "tool_nodejs" || definitionId === "control_toBool" || String(definitionId || "").startsWith("marketplace:")) && data.script) {
|
|
1407
|
-
const toolNodejsResult = emitToolNodejsDirectCommand(workspaceRoot, flowName, uuid, instanceId, data.script, execId);
|
|
1408
|
-
if (toolNodejsResult) {
|
|
1409
|
-
output.optionalPromptPath = toolNodejsResult.optionalPromptPath;
|
|
1410
|
-
output.directCommand = toolNodejsResult.directCommand;
|
|
1411
|
-
output.resolvedScript = data.script;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
logToRunTag(workspaceRoot, flowName, uuid, "pre-process", {
|
|
1415
|
-
event: "done",
|
|
1416
|
-
instanceId,
|
|
1417
|
-
promptPath: data.promptPath,
|
|
1418
|
-
resultPath: resultPathRel,
|
|
1419
|
-
subagent,
|
|
1420
|
-
definitionId,
|
|
1421
|
-
hasOptionalPrompt: !!output.optionalPromptPath,
|
|
1422
|
-
hasDirectCommand: !!output.directCommand,
|
|
1423
|
-
});
|
|
1424
|
-
console.log(JSON.stringify(output));
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
main().catch((e) => {
|
|
1428
|
-
console.error(JSON.stringify({ ok: false, error: e.message || String(e) }));
|
|
1429
|
-
process.exit(1);
|
|
1430
|
-
});
|