@fieldwangai/agentflow 0.1.32 → 0.1.34

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.
Files changed (45) hide show
  1. package/bin/lib/catalog-flows.mjs +7 -2
  2. package/bin/lib/composer-node-schema.mjs +7 -7
  3. package/bin/lib/composer-planner.mjs +5 -5
  4. package/bin/lib/git-worktree.mjs +263 -0
  5. package/bin/lib/gitlab-mr.mjs +174 -0
  6. package/bin/lib/locales/en.json +27 -7
  7. package/bin/lib/locales/zh.json +26 -6
  8. package/bin/lib/marketplace.mjs +106 -2
  9. package/bin/lib/paths.mjs +5 -1
  10. package/bin/lib/scheduler.mjs +8 -3
  11. package/bin/lib/ui-server.mjs +284 -13
  12. package/bin/pipeline/build-node-prompt.mjs +27 -1
  13. package/bin/pipeline/pre-process-node.mjs +216 -39
  14. package/bin/pipeline/validate-flow.mjs +7 -17
  15. package/builtin/nodes/agent_subAgent.md +2 -0
  16. package/builtin/nodes/control_agent_toBool.md +4 -0
  17. package/builtin/nodes/control_cancelled.md +8 -4
  18. package/builtin/nodes/control_cd_workspace.md +2 -0
  19. package/builtin/nodes/control_delay.md +9 -0
  20. package/builtin/nodes/control_toBool.md +6 -2
  21. package/builtin/nodes/control_user_workspace.md +2 -0
  22. package/builtin/nodes/control_wait_until.md +9 -0
  23. package/builtin/nodes/display_html.md +31 -0
  24. package/builtin/nodes/display_image.md +35 -0
  25. package/builtin/nodes/provide_bool.md +13 -0
  26. package/builtin/nodes/provide_file.md +2 -0
  27. package/builtin/nodes/provide_str.md +2 -0
  28. package/builtin/nodes/tool_get_env.md +4 -0
  29. package/builtin/nodes/tool_git_checkout.md +14 -2
  30. package/builtin/nodes/tool_git_worktree_load.md +59 -0
  31. package/builtin/nodes/tool_git_worktree_unload.md +51 -0
  32. package/builtin/nodes/tool_gitlab_create_mr.md +113 -0
  33. package/builtin/nodes/tool_load_key.md +4 -0
  34. package/builtin/nodes/tool_nodejs.md +4 -0
  35. package/builtin/nodes/tool_print.md +2 -0
  36. package/builtin/nodes/tool_save_key.md +4 -0
  37. package/builtin/nodes/tool_user_ask.md +3 -1
  38. package/builtin/nodes/tool_user_check.md +3 -1
  39. package/builtin/web-ui/dist/assets/index-B1j_UaHw.js +202 -0
  40. package/builtin/web-ui/dist/assets/index-ChiTnW0H.css +1 -0
  41. package/builtin/web-ui/dist/index.html +2 -2
  42. package/package.json +1 -1
  43. package/builtin/nodes/control_deadline.md +0 -32
  44. package/builtin/web-ui/dist/assets/index-D0Tkhqr6.css +0 -1
  45. package/builtin/web-ui/dist/assets/index-DyhW5chp.js +0 -197
@@ -41,12 +41,15 @@ import { getRunDir, sanitizeAgentflowUserId } from "../lib/paths.mjs";
41
41
  import {
42
42
  buildSkillsContext,
43
43
  buildSkillsContextFromRegistry,
44
+ buildDefaultWorkspaceContext,
44
45
  expandRuntimePlaceholders,
45
46
  normalizeSkillsContext,
46
47
  normalizeWorkspaceContext,
47
48
  parseSkillKeyList,
48
49
  resolveWorkspaceTarget,
49
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";
50
53
 
51
54
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
52
55
 
@@ -264,6 +267,12 @@ function isTruthyInput(value) {
264
267
  return text === "true" || text === "1" || text === "yes" || text === "y" || text === "on";
265
268
  }
266
269
 
270
+ function resolveMaybeWorkspacePath(raw, workspaceContext, extra = {}) {
271
+ const text = String(raw || "").trim();
272
+ if (!text) return "";
273
+ return resolveWorkspaceTarget(text, workspaceContext, extra);
274
+ }
275
+
267
276
  function emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel) {
268
277
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
269
278
  const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
@@ -312,6 +321,12 @@ function emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId,
312
321
 
313
322
  const currentBranch = runGit(["rev-parse", "--abbrev-ref", "HEAD"], targetDir).stdout.trim();
314
323
  const commit = runGit(["rev-parse", "HEAD"], targetDir).stdout.trim();
324
+ const gitContext = buildGitContext({
325
+ repoPath: targetDir,
326
+ branch: currentBranch === "HEAD" ? "DETACHED" : currentBranch,
327
+ commit,
328
+ remote: String(inputs.remote || "origin").trim() || "origin",
329
+ });
315
330
  const outWorkspaceContext = {
316
331
  version: 1,
317
332
  label: inputs.label || sanitizeRepoDirName(repoUrl),
@@ -326,10 +341,131 @@ function emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId,
326
341
  writeOutputSlot(runDir, instanceId, execId, "commit", commit);
327
342
  writeOutputSlot(runDir, instanceId, execId, "changed", changed ? "true" : "false");
328
343
  writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(outWorkspaceContext));
344
+ writeOutputSlot(runDir, instanceId, execId, "gitContext", JSON.stringify(gitContext));
329
345
  writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `git ${action}: ${currentBranch}@${commit.slice(0, 8)}` }, { execId });
330
346
  return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-checkout", `Git checkout completed: ${targetDir}\n`);
331
347
  }
332
348
 
349
+ function requireWorkspaceContextInput(inputs, definitionId) {
350
+ if (!String(inputs?.workspaceContext || "").trim()) {
351
+ throw new Error(`${definitionId}: workspaceContext is required`);
352
+ }
353
+ }
354
+
355
+ function emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId) {
356
+ const runDir = getRunDir(workspaceRoot, flowName, uuid);
357
+ const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
358
+ requireWorkspaceContextInput(inputs, "tool_git_worktree_load");
359
+ const gitContext = normalizeGitContext(inputs.gitContext);
360
+ const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext) ||
361
+ (gitContext?.repoPath ? path.resolve(gitContext.repoPath) : "");
362
+ if (!repoPath) throw new Error("tool_git_worktree_load: repoPath or gitContext.repoPath is required");
363
+ const branch = String(inputs.branch || "").trim();
364
+ const worktreePath = resolveMaybeWorkspacePath(inputs.worktreePath, workspaceContext, { branch }) ||
365
+ (gitContext?.worktreePath ? path.resolve(gitContext.worktreePath) : "");
366
+ const result = loadGitWorktree({
367
+ repoPath,
368
+ branch,
369
+ worktreePath,
370
+ pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
371
+ });
372
+ const outWorkspaceContext = {
373
+ version: 1,
374
+ label: result.branch === "DETACHED" ? `worktree:${result.commit.slice(0, 8)}` : `worktree:${result.branch}`,
375
+ cwd: result.worktreePath,
376
+ workspaceRoot: result.worktreePath,
377
+ pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
378
+ flowDir: workspaceContext.flowDir,
379
+ previous: workspaceContext,
380
+ };
381
+ const outGitContext = buildGitContext({
382
+ repoPath: result.repoRoot,
383
+ worktreePath: result.worktreePath,
384
+ branch: result.branch,
385
+ commit: result.commit,
386
+ remote: gitContext?.remote || "origin",
387
+ remoteUrl: gitContext?.remoteUrl || "",
388
+ });
389
+ writeOutputSlot(runDir, instanceId, execId, "worktreePath", result.worktreePath);
390
+ writeOutputSlot(runDir, instanceId, execId, "branch", result.branch);
391
+ writeOutputSlot(runDir, instanceId, execId, "commit", result.commit);
392
+ writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(outWorkspaceContext));
393
+ writeOutputSlot(runDir, instanceId, execId, "gitContext", JSON.stringify(outGitContext));
394
+ writeResult(workspaceRoot, flowName, uuid, instanceId, {
395
+ status: "success",
396
+ message: `worktree loaded: ${result.worktreePath} (${result.branch}@${result.commit.slice(0, 8)})`,
397
+ }, { execId });
398
+ return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-worktree-load", `Git worktree loaded: ${result.worktreePath}\n`);
399
+ }
400
+
401
+ function emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId) {
402
+ const runDir = getRunDir(workspaceRoot, flowName, uuid);
403
+ const { inputs, workspaceContext, flowJson } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
404
+ requireWorkspaceContextInput(inputs, "tool_git_worktree_unload");
405
+ const gitContext = normalizeGitContext(inputs.gitContext);
406
+ const worktreePath = resolveMaybeWorkspacePath(inputs.worktreePath, workspaceContext) ||
407
+ (gitContext?.worktreePath ? path.resolve(gitContext.worktreePath) : "") ||
408
+ (workspaceContext.cwd ? path.resolve(workspaceContext.cwd) : "") ||
409
+ (workspaceContext.workspaceRoot ? path.resolve(workspaceContext.workspaceRoot) : "");
410
+ if (!worktreePath) throw new Error("tool_git_worktree_unload: workspaceContext.cwd or worktreePath is required");
411
+ const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext) ||
412
+ (gitContext?.repoPath ? path.resolve(gitContext.repoPath) : "") ||
413
+ inferGitRepoRootFromWorktree(worktreePath) ||
414
+ (workspaceContext.previous?.cwd ? path.resolve(workspaceContext.previous.cwd) : "") ||
415
+ (workspaceContext.previous?.workspaceRoot ? path.resolve(workspaceContext.previous.workspaceRoot) : "");
416
+ const force = isTruthyInput(inputs.force);
417
+ const prune = String(inputs.prune ?? "true").trim().toLowerCase() !== "false";
418
+ const result = unloadGitWorktree({ repoPath, worktreePath, force, prune });
419
+ const nextWorkspaceContext = workspaceContext.previous
420
+ ? normalizeWorkspaceContext(workspaceContext.previous, workspaceRoot, flowName, flowJson)
421
+ : buildDefaultWorkspaceContext(workspaceRoot, flowName, flowJson);
422
+ writeOutputSlot(runDir, instanceId, execId, "removed", "true");
423
+ writeOutputSlot(runDir, instanceId, execId, "message", result.message);
424
+ writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(nextWorkspaceContext));
425
+ writeResult(workspaceRoot, flowName, uuid, instanceId, {
426
+ status: "success",
427
+ message: result.message,
428
+ }, { execId });
429
+ return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "git-worktree-unload", `${result.message}\n`);
430
+ }
431
+
432
+ async function emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId) {
433
+ const runDir = getRunDir(workspaceRoot, flowName, uuid);
434
+ const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
435
+ const repoPath = resolveMaybeWorkspacePath(inputs.repoPath, workspaceContext);
436
+ const result = await createGitLabMergeRequest({
437
+ gitContext: inputs.gitContext,
438
+ workspaceCwd: workspaceContext.cwd,
439
+ repoPath,
440
+ sourceBranch: inputs.sourceBranch,
441
+ targetBranch: inputs.targetBranch,
442
+ title: inputs.title,
443
+ description: inputs.description,
444
+ draft: inputs.draft,
445
+ labels: inputs.labels,
446
+ push: inputs.push,
447
+ remote: inputs.remote,
448
+ tokenEnv: inputs.tokenEnv,
449
+ gitlabApiBase: inputs.gitlabApiBase,
450
+ removeSourceBranch: inputs.removeSourceBranch,
451
+ squash: inputs.squash,
452
+ }, process.env);
453
+ writeOutputSlot(runDir, instanceId, execId, "mrUrl", result.mrUrl);
454
+ writeOutputSlot(runDir, instanceId, execId, "created", result.created ? "true" : "false");
455
+ writeOutputSlot(runDir, instanceId, execId, "mrIid", result.mrIid ?? "");
456
+ writeOutputSlot(runDir, instanceId, execId, "projectId", result.projectId ?? "");
457
+ writeOutputSlot(runDir, instanceId, execId, "sourceBranch", result.sourceBranch ?? "");
458
+ writeOutputSlot(runDir, instanceId, execId, "targetBranch", result.targetBranch ?? "");
459
+ writeOutputSlot(runDir, instanceId, execId, "title", result.title ?? "");
460
+ writeOutputSlot(runDir, instanceId, execId, "message", result.message ?? "");
461
+ writeResult(workspaceRoot, flowName, uuid, instanceId, {
462
+ status: "success",
463
+ message: result.message || result.mrUrl,
464
+ body: result.mrUrl,
465
+ }, { execId });
466
+ return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "gitlab-create-mr", `${result.message || "GitLab MR ready"}\n${result.mrUrl}\n`);
467
+ }
468
+
333
469
  function emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) {
334
470
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
335
471
  const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
@@ -461,7 +597,7 @@ function emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId) {
461
597
  const flowJson = readFlowJsonObject(workspaceRoot, flowName, uuid);
462
598
  const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
463
599
  const inputs = data.ok ? (data.resolvedInputs || {}) : {};
464
- const skipNames = new Set(["prev", "next", "workspaceContext", "skillsContext", "workspaceRoot", "pipelineWorkspace", "flowName", "runDir", "flowDir", "cwd"]);
600
+ const skipNames = new Set(["prev", "next", "workspaceContext", "gitContext", "skillsContext", "workspaceRoot", "pipelineWorkspace", "flowName", "runDir", "flowDir", "cwd"]);
465
601
 
466
602
  let content = readPrintableValue(inputs.content, runDir);
467
603
  if (!content) {
@@ -504,8 +640,14 @@ function writeWaitState(runDir, state) {
504
640
  if (parsed && typeof parsed === "object" && Array.isArray(parsed.waits)) registry = parsed;
505
641
  } catch (_) {}
506
642
  }
507
- const waits = registry.waits.filter((w) => w && w.instanceId !== state.instanceId);
508
- const wait = { ...state, id: state.id || state.instanceId };
643
+ const waitId = String(state.waitId || state.id || `${state.instanceId}:${state.execId || 1}`).trim();
644
+ const waits = registry.waits.filter((w) => {
645
+ if (!w) return false;
646
+ const key = String(w.waitId || w.id || "").trim();
647
+ if (key && key === waitId) return false;
648
+ return !(w.instanceId === state.instanceId && String(w.execId || 1) === String(state.execId || 1));
649
+ });
650
+ const wait = { ...state, waitId, id: state.id || waitId };
509
651
  waits.push(wait);
510
652
  registry = {
511
653
  ...registry,
@@ -519,6 +661,44 @@ function writeWaitState(runDir, state) {
519
661
  fs.writeFileSync(legacyPath, JSON.stringify(wait, null, 2) + "\n", "utf-8");
520
662
  }
521
663
 
664
+ function buildWaitId(uuid, instanceId, execId, explicit = "") {
665
+ const text = String(explicit || "").trim();
666
+ if (text) return text;
667
+ return `${uuid}:${instanceId}:${execId || 1}`;
668
+ }
669
+
670
+ function readWaitStateById(runDir, waitId) {
671
+ const text = String(waitId || "").trim();
672
+ if (!text) return null;
673
+ const registryPath = path.join(runDir, "wait-states.json");
674
+ if (fs.existsSync(registryPath)) {
675
+ try {
676
+ const registry = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
677
+ const waits = Array.isArray(registry?.waits) ? registry.waits : [];
678
+ const found = waits.find((w) => {
679
+ const key = String(w?.waitId || w?.id || "").trim();
680
+ return key === text;
681
+ });
682
+ if (found) return found;
683
+ } catch (_) {}
684
+ }
685
+ const legacyPath = path.join(runDir, "wait-state.json");
686
+ if (fs.existsSync(legacyPath)) {
687
+ try {
688
+ const state = JSON.parse(fs.readFileSync(legacyPath, "utf-8"));
689
+ const key = String(state?.waitId || state?.id || "").trim();
690
+ if (key === text) return state;
691
+ } catch (_) {}
692
+ }
693
+ return null;
694
+ }
695
+
696
+ function readCancelFlagForWait(runDir, waitId) {
697
+ const state = readWaitStateById(runDir, waitId);
698
+ if (state && (state.cancelled === true || state.status === "cancelled" || state.branch === "cancelled")) return true;
699
+ return readCancelFlag(runDir);
700
+ }
701
+
522
702
  function readCancelFlag(runDir) {
523
703
  for (const name of ["cancelled", "cancelled.json", "wait-cancelled.json"]) {
524
704
  const p = path.join(runDir, name);
@@ -681,7 +861,7 @@ ${directCommand}
681
861
  return { optionalPromptPath: relativePath.replace(/\\/g, "/"), directCommand };
682
862
  }
683
863
 
684
- function main() {
864
+ async function main() {
685
865
  const args = process.argv.slice(2);
686
866
  if (args.length < 4) {
687
867
  console.error(
@@ -834,8 +1014,11 @@ function main() {
834
1014
  process.exit(1);
835
1015
  }
836
1016
 
1017
+ const waitId = buildWaitId(uuid, instanceId, execId, inputs.waitId || inputs.watchId);
1018
+ writeOutputSlot(runDir, instanceId, execId, "waitId", waitId);
837
1019
  writeOutputSlot(runDir, instanceId, execId, "wakeAt", wakeAt);
838
1020
  writeWaitState(runDir, {
1021
+ waitId,
839
1022
  status: "waiting",
840
1023
  reason: definitionId,
841
1024
  flowName,
@@ -861,7 +1044,7 @@ function main() {
861
1044
  `此节点为 ${definitionId},已写入 wait-state.json,等待 scheduler 在 ${wakeAt} 唤醒。\n`,
862
1045
  );
863
1046
  writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
864
- logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "waiting", instanceId, wakeAt, definitionId });
1047
+ logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "waiting", instanceId, waitId, wakeAt, definitionId });
865
1048
  console.log(JSON.stringify({
866
1049
  ok: true,
867
1050
  promptPath,
@@ -952,37 +1135,22 @@ function main() {
952
1135
  return;
953
1136
  }
954
1137
 
955
- if (definitionId === "control_deadline" || definitionId === "control_cancelled") {
1138
+ if (definitionId === "control_cancelled") {
956
1139
  const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
957
1140
  if (!data.ok) {
958
- console.error(JSON.stringify({ ok: false, error: `${definitionId}: getResolvedValues failed` }));
1141
+ console.error(JSON.stringify({ ok: false, error: "control_cancelled: getResolvedValues failed" }));
959
1142
  process.exit(1);
960
1143
  }
961
- const inputs = data.resolvedInputs || {};
962
- let boolValue = false;
963
- let message = "";
1144
+ let boolValue;
1145
+ let message;
964
1146
  try {
965
- if (definitionId === "control_deadline") {
966
- const timezone = inputs.timezone || "Asia/Shanghai";
967
- let deadline;
968
- if (inputs.deadlineAt) {
969
- deadline = parseDateTime(inputs.deadlineAt, timezone);
970
- } else {
971
- const start = inputs.startAt ? parseDateTime(inputs.startAt, timezone) : new Date();
972
- deadline = new Date(start.getTime() + parseDurationMs(inputs.duration || ""));
973
- }
974
- const deadlineAt = deadline.toISOString();
975
- boolValue = Date.now() >= deadline.getTime();
976
- writeOutputSlot(runDir, instanceId, execId, "deadlineAt", deadlineAt);
977
- writeOutputSlot(runDir, instanceId, execId, "expired", boolValue ? "true" : "false");
978
- message = boolValue ? `已超过截止时间 ${deadlineAt}` : `未超过截止时间 ${deadlineAt}`;
979
- } else {
980
- boolValue = readCancelFlag(runDir);
981
- writeOutputSlot(runDir, instanceId, execId, "cancelled", boolValue ? "true" : "false");
982
- message = boolValue ? "已取消" : "未取消";
983
- }
1147
+ const inputs = data.resolvedInputs || {};
1148
+ const waitId = String(inputs.waitId || inputs.watchId || "").trim();
1149
+ boolValue = waitId ? readCancelFlagForWait(runDir, waitId) : readCancelFlag(runDir);
1150
+ writeOutputSlot(runDir, instanceId, execId, "cancelled", boolValue ? "true" : "false");
1151
+ message = boolValue ? "已取消" : "未取消";
984
1152
  } catch (e) {
985
- console.error(JSON.stringify({ ok: false, error: `${definitionId}: ${e.message}` }));
1153
+ console.error(JSON.stringify({ ok: false, error: `control_cancelled: ${e.message}` }));
986
1154
  process.exit(1);
987
1155
  }
988
1156
  writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message }, { execId, preserveBody: false });
@@ -1001,18 +1169,24 @@ function main() {
1001
1169
  return;
1002
1170
  }
1003
1171
 
1004
- if (definitionId === "tool_git_checkout" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
1172
+ if (definitionId === "tool_git_checkout" || definitionId === "tool_git_worktree_load" || definitionId === "tool_git_worktree_unload" || definitionId === "tool_gitlab_create_mr" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
1005
1173
  try {
1006
1174
  const promptPath =
1007
1175
  definitionId === "tool_git_checkout"
1008
1176
  ? emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel)
1009
- : definitionId === "control_cd_workspace"
1010
- ? emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1011
- : definitionId === "control_user_workspace"
1012
- ? emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1013
- : definitionId === "control_load_skills"
1014
- ? emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId)
1015
- : emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId);
1177
+ : definitionId === "tool_git_worktree_load"
1178
+ ? emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId)
1179
+ : definitionId === "tool_git_worktree_unload"
1180
+ ? emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId)
1181
+ : definitionId === "tool_gitlab_create_mr"
1182
+ ? await emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId)
1183
+ : definitionId === "control_cd_workspace"
1184
+ ? emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1185
+ : definitionId === "control_user_workspace"
1186
+ ? emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1187
+ : definitionId === "control_load_skills"
1188
+ ? emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId)
1189
+ : emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId);
1016
1190
  writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
1017
1191
  logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "runtime-context-node", instanceId, definitionId });
1018
1192
  console.log(JSON.stringify({
@@ -1103,4 +1277,7 @@ function main() {
1103
1277
  console.log(JSON.stringify(output));
1104
1278
  }
1105
1279
 
1106
- main();
1280
+ main().catch((e) => {
1281
+ console.error(JSON.stringify({ ok: false, error: e.message || String(e) }));
1282
+ process.exit(1);
1283
+ });
@@ -493,26 +493,16 @@ function checkFlowCore(nodes, edges, flowDir, nodeIdToSlots, getNodeBody, instan
493
493
  }
494
494
  }
495
495
 
496
- // provide_str / provide_file output 类型校验
497
- if (defId === "provide_str") {
496
+ // provide_str / provide_file / provide_bool output 类型校验
497
+ if (defId === "provide_str" || defId === "provide_file" || defId === "provide_bool") {
498
498
  const out = Array.isArray(inst.output) ? inst.output : [];
499
+ const expectedType = defId === "provide_file" ? "file" : defId === "provide_bool" ? "bool" : "text";
499
500
  if (out.length !== 1) {
500
- errors.push(`节点 "${n.id}"(provide_str)output 必须仅有 1 个槽位(value:text),当前 ${out.length} 个`);
501
+ errors.push(`节点 "${n.id}"(${defId})output 必须仅有 1 个槽位(value:${expectedType}),当前 ${out.length} 个`);
501
502
  } else {
502
503
  const t0 = normType(out[0] && out[0].type);
503
- if (t0 !== "text") {
504
- errors.push(`节点 "${n.id}"(provide_str)output[0].type 必须为 \`text\`,当前为 \`${t0 || "(空)"}\``);
505
- }
506
- }
507
- }
508
- if (defId === "provide_file") {
509
- const out = Array.isArray(inst.output) ? inst.output : [];
510
- if (out.length !== 1) {
511
- errors.push(`节点 "${n.id}"(provide_file)output 必须仅有 1 个槽位(value:file),当前 ${out.length} 个`);
512
- } else {
513
- const t0 = normType(out[0] && out[0].type);
514
- if (t0 !== "file") {
515
- errors.push(`节点 "${n.id}"(provide_file)output[0].type 必须为 \`file\`,当前为 \`${t0 || "(空)"}\``);
504
+ if (t0 !== expectedType) {
505
+ errors.push(`节点 "${n.id}"(${defId})output[0].type 必须为 \`${expectedType}\`,当前为 \`${t0 || "(空)"}\``);
516
506
  }
517
507
  }
518
508
  }
@@ -675,7 +665,7 @@ function computeValidation(loaded, workspaceRoot) {
675
665
  if (!isNodeEdge) continue;
676
666
  if (srcDef.startsWith("provide_") || tgtDef.startsWith("provide_")) {
677
667
  validationErrors.push(
678
- `provide_* 节点不得出现在控制链上(node→node 边):${e.source} ${sh} -> ${e.target} ${th};provide 仅作数据源,请改连下游 text/file 数据槽`
668
+ `provide_* 节点不得出现在控制链上(node→node 边):${e.source} ${sh} -> ${e.target} ${th};provide 仅作数据源,请改连下游 text/file/bool 数据槽`
679
669
  );
680
670
  }
681
671
  }
@@ -19,5 +19,7 @@ output:
19
19
  - type: text
20
20
  name: result
21
21
  default: ""
22
+ required: true
23
+ showOnNode: true
22
24
  ---
23
25
  ${USER_PROMPT}
@@ -9,6 +9,8 @@ input:
9
9
  - type: text
10
10
  name: value
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: node
14
16
  name: next
@@ -16,5 +18,7 @@ output:
16
18
  - type: bool
17
19
  name: prediction
18
20
  default: ""
21
+ required: true
22
+ showOnNode: true
19
23
  ---
20
24
  ${USER_PROMPT}
@@ -1,14 +1,16 @@
1
1
  ---
2
- # 内置节点:取消状态判断
3
- description: Check whether the current run/watch has been cancelled. Use cancelled output with control_if.
4
- displayName: Cancelled
2
+ # 内置节点:取消判断
3
+ description: Check whether the current wait/run has been cancelled. Use cancelled output with control_if.
4
+ displayName: Cancel Check
5
5
  input:
6
6
  - type: node
7
7
  name: prev
8
8
  default: ""
9
9
  - type: text
10
- name: watchId
10
+ name: waitId
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: node
14
16
  name: next
@@ -16,5 +18,7 @@ output:
16
18
  - type: bool
17
19
  name: cancelled
18
20
  default: ""
21
+ required: true
22
+ showOnNode: true
19
23
  ---
20
24
  ${USER_PROMPT}
@@ -35,6 +35,8 @@ output:
35
35
  - type: text
36
36
  name: workspaceContext
37
37
  default: ""
38
+ required: true
39
+ showOnNode: true
38
40
  - type: file
39
41
  name: cwd
40
42
  default: ""
@@ -9,12 +9,21 @@ input:
9
9
  - type: text
10
10
  name: duration
11
11
  default: "10m"
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: node
14
16
  name: next
15
17
  default: ""
18
+ - type: text
19
+ name: waitId
20
+ default: ""
21
+ required: true
22
+ showOnNode: true
16
23
  - type: text
17
24
  name: wakeAt
18
25
  default: ""
26
+ required: true
27
+ showOnNode: true
19
28
  ---
20
29
  ${USER_PROMPT}
@@ -1,7 +1,7 @@
1
1
  ---
2
- # 内置节点:转布尔(本地脚本执行,★ 可扩展输入)
2
+ # 内置节点:代码转布尔(本地脚本执行,★ 可扩展输入)
3
3
  description: "Script-based boolean conversion: executes script to produce true/false prediction. Like tool_nodejs but enforces bool output. Must have script field."
4
- displayName: ToBool
4
+ displayName: Code ToBool
5
5
  input:
6
6
  - type: node
7
7
  name: prev
@@ -9,6 +9,8 @@ input:
9
9
  - type: text
10
10
  name: value
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: node
14
16
  name: next
@@ -16,6 +18,8 @@ output:
16
18
  - type: bool
17
19
  name: prediction
18
20
  default: ""
21
+ required: true
22
+ showOnNode: true
19
23
  extensible: true
20
24
  ---
21
25
  ${USER_PROMPT}
@@ -13,6 +13,8 @@ output:
13
13
  - type: text
14
14
  name: workspaceContext
15
15
  default: ""
16
+ required: true
17
+ showOnNode: true
16
18
  - type: file
17
19
  name: cwd
18
20
  default: ""
@@ -9,6 +9,8 @@ input:
9
9
  - type: text
10
10
  name: until
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  - type: text
13
15
  name: timezone
14
16
  default: "Asia/Shanghai"
@@ -16,8 +18,15 @@ output:
16
18
  - type: node
17
19
  name: next
18
20
  default: ""
21
+ - type: text
22
+ name: waitId
23
+ default: ""
24
+ required: true
25
+ showOnNode: true
19
26
  - type: text
20
27
  name: wakeAt
21
28
  default: ""
29
+ required: true
30
+ showOnNode: true
22
31
  ---
23
32
  ${USER_PROMPT}
@@ -0,0 +1,31 @@
1
+ ---
2
+ # Built-in node: HTML Display
3
+ description: Display HTML content in workspace canvas; passes HTML downstream as text
4
+ displayName: HTML Display
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: content
11
+ default: ""
12
+ required: true
13
+ showOnNode: true
14
+ - type: file
15
+ name: filePath
16
+ default: ""
17
+ showOnNode: false
18
+ - type: text
19
+ name: workspaceContext
20
+ default: ""
21
+ showOnNode: false
22
+ output:
23
+ - type: text
24
+ name: content
25
+ default: ""
26
+ showOnNode: false
27
+ - type: node
28
+ name: next
29
+ default: ""
30
+ ---
31
+ ${content}
@@ -0,0 +1,35 @@
1
+ ---
2
+ # Built-in node: Image Display
3
+ description: Display an image URL, data URL, or image path in workspace canvas; passes source downstream as text
4
+ displayName: Image Display
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: src
11
+ default: ""
12
+ required: true
13
+ showOnNode: true
14
+ - type: file
15
+ name: filePath
16
+ default: ""
17
+ showOnNode: false
18
+ - type: text
19
+ name: alt
20
+ default: ""
21
+ showOnNode: false
22
+ - type: text
23
+ name: workspaceContext
24
+ default: ""
25
+ showOnNode: false
26
+ output:
27
+ - type: text
28
+ name: src
29
+ default: ""
30
+ showOnNode: false
31
+ - type: node
32
+ name: next
33
+ default: ""
34
+ ---
35
+ ${src}
@@ -0,0 +1,13 @@
1
+ ---
2
+ # 内置节点:直接提供布尔值
3
+ description: Provide a boolean value directly, value will be passed to downstream as true or false
4
+ displayName: Boolean
5
+ input: []
6
+ output:
7
+ - type: bool
8
+ name: value
9
+ default: "false"
10
+ required: true
11
+ showOnNode: true
12
+ ---
13
+ ${USER_PROMPT}
@@ -7,5 +7,7 @@ output:
7
7
  - type: file
8
8
  name: value
9
9
  default: ""
10
+ required: true
11
+ showOnNode: true
10
12
  ---
11
13
  ${USER_PROMPT}
@@ -7,5 +7,7 @@ output:
7
7
  - type: text
8
8
  name: value
9
9
  default: ""
10
+ required: true
11
+ showOnNode: true
10
12
  ---
11
13
  ${USER_PROMPT}
@@ -6,9 +6,13 @@ input:
6
6
  - type: text
7
7
  name: key
8
8
  default: ""
9
+ required: true
10
+ showOnNode: true
9
11
  output:
10
12
  - type: text
11
13
  name: value
12
14
  default: ""
15
+ required: true
16
+ showOnNode: true
13
17
  ---
14
18
  ${USER_PROMPT}