@zhushanwen/subagent-core 0.2.0 → 0.4.0

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 (195) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-4KN4TTG7.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-X7SZ5HLQ.js +66 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +65 -0
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +93 -1
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +93 -1
  18. package/dist/execution/engine/engines/zcode/constants.js +43 -1
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +1 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +14709 -100
  24. package/dist/index.d.cts +4053 -200
  25. package/dist/index.d.ts +4053 -200
  26. package/dist/index.js +14358 -102
  27. package/dist/{types-BxyAidGf.d.cts → types-DpUO16pj.d.cts} +464 -1
  28. package/dist/{types-BxyAidGf.d.ts → types-DpUO16pj.d.ts} +464 -1
  29. package/package.json +6 -3
  30. package/src/__tests__/review-fix-loop-script.test.ts +90 -2
  31. package/src/__tests__/review-fix-loop-utils.test.ts +21 -0
  32. package/src/__tests__/smoke.test.ts +9 -2
  33. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  34. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  35. package/src/execution/__tests__/chat-engine-routing.test.ts +71 -0
  36. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +1 -0
  37. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  38. package/src/execution/__tests__/delivery-methods.test.ts +2 -0
  39. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  40. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  41. package/src/execution/__tests__/epipe-fallback.test.ts +2 -0
  42. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  43. package/src/execution/__tests__/finalize-record.test.ts +106 -0
  44. package/src/execution/__tests__/gc-timer.test.ts +2 -0
  45. package/src/execution/__tests__/get-record-for-action-restart.test.ts +2 -0
  46. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  47. package/src/execution/__tests__/keep-alive-no-progress.test.ts +378 -0
  48. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  49. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  50. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  51. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  52. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  53. package/src/execution/__tests__/notify-ledger.test.ts +294 -0
  54. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  55. package/src/execution/__tests__/record-store.test.ts +83 -0
  56. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +4 -0
  57. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +1 -0
  58. package/src/execution/__tests__/run-spawn-edges.test.ts +407 -17
  59. package/src/execution/__tests__/run-spawn-integration.test.ts +1 -1
  60. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +12 -4
  61. package/src/execution/__tests__/service-kill-escalation.test.ts +89 -0
  62. package/src/execution/__tests__/session-pending.test.ts +228 -0
  63. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  64. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  65. package/src/execution/__tests__/session-runner-epipe.test.ts +1 -0
  66. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +226 -0
  67. package/src/execution/__tests__/settled-watchdog.test.ts +289 -0
  68. package/src/execution/__tests__/spawned-children.test.ts +153 -10
  69. package/src/execution/__tests__/subagent-actions-core.test.ts +997 -0
  70. package/src/execution/__tests__/subagent-service-message-close.test.ts +71 -10
  71. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  72. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +266 -0
  73. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +3 -0
  74. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +316 -0
  75. package/src/execution/__tests__/timeout-integration.test.ts +7 -7
  76. package/src/execution/__tests__/ui-request-handler-factory.test.ts +95 -1
  77. package/src/execution/__tests__/ui-request-queue.test.ts +25 -0
  78. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  79. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  80. package/src/execution/agent-registry.ts +225 -1
  81. package/src/execution/agents-assembly.ts +88 -0
  82. package/src/execution/concurrency-pool.ts +71 -9
  83. package/src/execution/dialog-queue.ts +101 -3
  84. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +49 -2
  85. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +87 -1
  86. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +26 -0
  87. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +83 -1
  88. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +131 -0
  89. package/src/execution/engine/__tests__/registry.test.ts +90 -1
  90. package/src/execution/engine/engine-discovery.ts +12 -16
  91. package/src/execution/engine/engines/pi/task-spec-mapper.ts +3 -3
  92. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +274 -0
  93. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  94. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +472 -0
  95. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +387 -0
  96. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +780 -0
  97. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +815 -0
  98. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +462 -0
  99. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +119 -1
  100. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +136 -6
  101. package/src/execution/engine/engines/zcode/appserver-home.ts +442 -0
  102. package/src/execution/engine/engines/zcode/appserver-probe.ts +141 -0
  103. package/src/execution/engine/engines/zcode/connection.ts +585 -0
  104. package/src/execution/engine/engines/zcode/constants.ts +129 -0
  105. package/src/execution/engine/engines/zcode/golden-sample.ts +54 -8
  106. package/src/execution/engine/engines/zcode/preparer.ts +25 -22
  107. package/src/execution/engine/engines/zcode/session-channel.ts +656 -0
  108. package/src/execution/engine/engines/zcode/zcode-engine.ts +909 -41
  109. package/src/execution/engine/host-task-spec.ts +3 -3
  110. package/src/execution/engine/port.ts +41 -1
  111. package/src/execution/engine/registry.ts +53 -0
  112. package/src/execution/finalize-record.ts +63 -5
  113. package/src/execution/get-state-handshake.ts +85 -10
  114. package/src/execution/lifecycle-manager.ts +27 -3
  115. package/src/execution/manifest-store.ts +53 -62
  116. package/src/execution/notifier.ts +10 -10
  117. package/src/execution/notify-ledger.ts +117 -16
  118. package/src/execution/record-entry.ts +8 -2
  119. package/src/execution/record-store.ts +29 -15
  120. package/src/execution/session-pending.ts +213 -62
  121. package/src/execution/session-runner.ts +974 -128
  122. package/src/execution/sessions-index.ts +10 -55
  123. package/src/execution/settled-watchdog.ts +99 -0
  124. package/src/execution/subagent-actions-core.ts +686 -0
  125. package/src/execution/subagent-service.ts +396 -49
  126. package/src/execution/ui-request-handler-factory.ts +27 -4
  127. package/src/execution/worktree-git-ops.ts +397 -0
  128. package/src/execution/worktree-manager.ts +72 -10
  129. package/src/execution/worktree-registry.ts +6 -12
  130. package/src/index.ts +413 -6
  131. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  132. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -4
  133. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  134. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +1 -8
  135. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  136. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +382 -0
  137. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  138. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  139. package/src/orchestration/__tests__/file-run-store.test.ts +389 -0
  140. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  141. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  142. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  143. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  144. package/src/orchestration/__tests__/lifecycle.test.ts +77 -0
  145. package/src/orchestration/__tests__/run-snapshot.test.ts +353 -0
  146. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  147. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  148. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  149. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  150. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  151. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  152. package/src/orchestration/agent-opts-resolver.ts +7 -7
  153. package/src/orchestration/args-meta.ts +198 -0
  154. package/src/orchestration/error-recovery.ts +416 -146
  155. package/src/orchestration/execute-agent-call.ts +9 -9
  156. package/src/orchestration/file-run-store.ts +327 -0
  157. package/src/orchestration/launcher.ts +35 -26
  158. package/src/orchestration/lifecycle.ts +355 -79
  159. package/src/orchestration/models/agent-call.ts +7 -7
  160. package/src/orchestration/models/budget.ts +5 -5
  161. package/src/orchestration/models/run-runtime.ts +13 -13
  162. package/src/orchestration/models/trace.ts +8 -8
  163. package/src/orchestration/models/workflow-run.ts +27 -27
  164. package/src/orchestration/models/workflow-script.ts +4 -4
  165. package/src/orchestration/run-snapshot.ts +266 -0
  166. package/src/orchestration/script-generate.ts +154 -0
  167. package/src/orchestration/script-lint.ts +33 -33
  168. package/src/orchestration/worker-handle.ts +10 -10
  169. package/src/orchestration/worker-host.ts +10 -10
  170. package/src/orchestration/worker-script-builder.ts +401 -346
  171. package/src/orchestration/workflow-files.ts +37 -11
  172. package/src/orchestration/workflow-run-summary.ts +69 -0
  173. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  174. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  175. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  176. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  177. package/src/shared/__tests__/injection-render.test.ts +518 -0
  178. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  179. package/src/shared/__tests__/resource-discovery.test.ts +3 -2
  180. package/src/shared/agent-ref.ts +143 -2
  181. package/src/shared/atomic-write.ts +320 -0
  182. package/src/shared/bounded-serialize.ts +154 -0
  183. package/src/shared/injection-render.ts +279 -0
  184. package/src/shared/meta-parser.ts +41 -1
  185. package/src/shared/resource-discovery.ts +104 -37
  186. package/src/shared/resource-meta.ts +14 -0
  187. package/src/shared/xml-injection.ts +9 -9
  188. package/workflows/README.md +9 -9
  189. package/workflows/chain.js +4 -2
  190. package/workflows/map-reduce.js +5 -3
  191. package/workflows/parallel.js +5 -3
  192. package/workflows/review-fix-loop-utils.cjs +14 -3
  193. package/workflows/review-fix-loop.js +18 -7
  194. package/workflows/scatter-gather.js +4 -2
  195. package/dist/chunk-3VOERJPJ.js +0 -22
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Workflow 脚本创作管线(generate)——pi-sw tool-workflow-script.ts actionGenerate
3
+ * 的校验管线下沉(convergence D-6 / W4)。
4
+ *
5
+ * 五道闸 + round-trip + tmp 写盘,闸序与 pi 现版一致:
6
+ * 1. ESM import 拒绝(Worker 跑 CJS);'export const meta' 例外
7
+ * 2. 非 meta 的 export 拒绝
8
+ * 3. meta 声明必需(@pi-meta YAML 块注释或 legacy const meta,过渡期 m0)
9
+ * 4. agent() 调用必需
10
+ * 5. 语法检查(包 async IIFE,与 runtime 包裹形态一致)
11
+ * 6. round-trip:@pi-meta 存在时 parseResourceMetaDetailed 校验 YAML(报行列)
12
+ * 7. 通过全部校验 → tmp 目录写盘
13
+ *
14
+ * 与 pi 版的差异(均为宿主职责,不属纯函数边界):
15
+ * - 不含 signal aborted 检查(AbortSignal 是 pi tool 契约层关注,宿主改接时自留);
16
+ * - 返回结构化结果而非 throw——pi 只对 execute throw 置 isError:true 的契约
17
+ * 转换由宿主(C5 改接)负责,core 保持纯函数。
18
+ *
19
+ * 报错文案逐字平移自 pi 现版(含 round-trip 的 line/col 信息)——pi 侧行为
20
+ * 不变是 CA2 验收前提,任何文案改动必须同步两处。
21
+ *
22
+ * tmp 目录经 options.tmpDir 宿主注入(缺省 pi 布局,与 workflow-files.ts
23
+ * 同源常量——save/delete/generate 三入口共享同一目录参数化口径)。
24
+ *
25
+ * 层归属:orchestration(创作闭环,与 lintScript / workflow-files 同域)。
26
+ */
27
+
28
+ import { mkdirSync, writeFileSync } from "node:fs";
29
+ import { resolve } from "node:path";
30
+
31
+ import { parseResourceMetaDetailed } from "../shared/meta-parser.ts";
32
+ import { DEFAULT_WORKFLOW_TMP_DIR } from "./workflow-files.ts";
33
+
34
+ /** generate 目录注入参数:tmp 落盘目录宿主注入(缺省 DEFAULT_WORKFLOW_TMP_DIR)。 */
35
+ export interface GenerateWorkflowScriptOptions {
36
+ tmpDir?: string;
37
+ }
38
+
39
+ /**
40
+ * 管线结果:成功 = 落盘绝对路径;失败 = 报错文案(逐字对齐 pi 现版,
41
+ * 含 round-trip 的行列信息——供宿主转 throw 后 LLM 自纠正)。
42
+ */
43
+ export type GenerateWorkflowScriptResult =
44
+ | { ok: true; path: string }
45
+ | { ok: false; error: string };
46
+
47
+ // ── 校验闸(每闸返回 error 文案;undefined = 通过。文案逐字对齐 pi 现版) ──
48
+
49
+ /** 闸 0:参数完备(缺 name/script 即拒)。 */
50
+ function checkRequiredParams(name: string, script: string): string | undefined {
51
+ if (!name || !script) {
52
+ return "generate requires 'name' and 'script' parameters";
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ /** 闸 1:ESM 语法拒绝(Worker 跑 CJS);'export const meta' 例外。stripped = 注释剥离后源码。 */
58
+ function checkEsamSyntax(stripped: string): string | undefined {
59
+ if (/\bimport\s+(?:type\s+)?[\w{*]/.test(stripped)) {
60
+ return "Script uses ESM 'import' syntax. Workflow scripts run in a CJS Worker — use require() instead.";
61
+ }
62
+ const hasExportMeta = /\bexport\s+const\s+meta\s*=/.test(stripped);
63
+ const otherExports = stripped.match(/\bexport\s+(?:const|let|var|function|default|\{)/g);
64
+ if (otherExports && !hasExportMeta) {
65
+ return "Script uses ESM 'export' (non-meta). Use 'const meta = {...}' at top level instead.";
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ // 闸 2:meta 声明必需(/* @pi-meta */ YAML 块注释或 legacy const meta,过渡期 m0)
71
+ function checkMetaDeclaration(script: string, hasPiMeta: boolean): string | undefined {
72
+ const hasLegacyMeta = script.includes("const meta") || script.includes("export const meta");
73
+ if (!hasPiMeta && !hasLegacyMeta) {
74
+ return "Script must contain a meta declaration: a /* @pi-meta */ YAML block comment (preferred) or legacy const meta = { ... }. The block has the form: a block comment starting with /* @pi-meta followed by YAML (name/description/phases/parameters?/usage?), closed by */ on its own line.";
75
+ }
76
+ return undefined;
77
+ }
78
+
79
+ /** 闸 3:agent() 调用必需。 */
80
+ function checkAgentUsage(stripped: string): string | undefined {
81
+ if (!/\bagent\s*\(/.test(stripped)) {
82
+ return "Script does not contain any agent() calls. A workflow must call agent() at least once.";
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ /** 闸 4:语法检查(包 async IIFE,与 runtime 包裹形态一致)。 */
88
+ function checkSyntax(script: string): string | undefined {
89
+ const cjsScript = script.replace(/\bexport\s+const\s+meta\b/, "const meta");
90
+ try {
91
+ new Function(`(async () => { ${cjsScript} })();`);
92
+ return undefined;
93
+ } catch (err: unknown) {
94
+ const msg = err instanceof Error ? err.message : String(err);
95
+ return `Syntax error in script: ${msg}`;
96
+ }
97
+ }
98
+
99
+ /** 闸 4b:round-trip——@pi-meta 存在时 parseResourceMetaDetailed 校验 YAML(报行列)。 */
100
+ function checkMetaRoundTrip(script: string, hasPiMeta: boolean): string | undefined {
101
+ if (!hasPiMeta) return undefined;
102
+ const detailed = parseResourceMetaDetailed(script, "workflow");
103
+ if (detailed.ok) return undefined;
104
+ const loc = "linePos" in detailed && detailed.linePos
105
+ ? ` (line ${detailed.linePos.line}, col ${detailed.linePos.col})`
106
+ : "";
107
+ return `Generated /* @pi-meta */ YAML cannot be parsed${loc}: ${detailed.error}. Common causes: YAML indent errors, patternProperties regex must use double backslash (\\d not \d), or a stray star-slash inside the YAML body. Fix the meta block and retry.`;
108
+ }
109
+
110
+ /**
111
+ * 校验并落盘一个 AI 生成的 workflow 临时脚本。
112
+ *
113
+ * @param name 脚本名(落盘 <tmpDir>/{name}.js)
114
+ * @param script 完整脚本源码
115
+ * @param options 目录注入(tmpDir 缺省 pi 布局,相对 cwd resolve)
116
+ */
117
+ export function generateWorkflowScript(
118
+ name: string,
119
+ script: string,
120
+ options?: GenerateWorkflowScriptOptions,
121
+ ): GenerateWorkflowScriptResult {
122
+ const paramError = checkRequiredParams(name, script);
123
+ if (paramError) return { ok: false, error: paramError };
124
+
125
+ // 1. Reject ESM syntax (Worker runs CJS); 'export const meta' 例外
126
+ const stripped = script.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
127
+ const esamError = checkEsamSyntax(stripped);
128
+ if (esamError) return { ok: false, error: esamError };
129
+
130
+ // 2. Validate meta declaration (/* @pi-meta */ new format preferred; legacy const meta accepted during transition — m0)
131
+ const hasPiMeta = /\/\*\s*@pi-meta\s*\n/.test(script);
132
+ const metaError = checkMetaDeclaration(script, hasPiMeta);
133
+ if (metaError) return { ok: false, error: metaError };
134
+
135
+ // 3. Check agent usage
136
+ const agentError = checkAgentUsage(stripped);
137
+ if (agentError) return { ok: false, error: agentError };
138
+
139
+ // 4. Syntax check (wrap in async IIFE like runtime)
140
+ const syntaxError = checkSyntax(script);
141
+ if (syntaxError) return { ok: false, error: syntaxError };
142
+
143
+ // 4b. Round-trip: validate /* @pi-meta */ YAML before writing (v5 §4.7 / ERR4 — report linePos, don't write bad files)
144
+ const roundTripError = checkMetaRoundTrip(script, hasPiMeta);
145
+ if (roundTripError) return { ok: false, error: roundTripError };
146
+
147
+ // 5. Write to tmp directory
148
+ const tmpDir = resolve(options?.tmpDir ?? DEFAULT_WORKFLOW_TMP_DIR);
149
+ mkdirSync(tmpDir, { recursive: true });
150
+ const filePath = resolve(tmpDir, `${name}.js`);
151
+ writeFileSync(filePath, script, "utf-8");
152
+
153
+ return { ok: true, path: filePath };
154
+ }
@@ -76,13 +76,13 @@ function checkEntryPoint(source: string): LintFinding[] {
76
76
  function checkLine(lineText: string, lineNum: number): LintFinding[] {
77
77
  const results: LintFinding[] = [];
78
78
 
79
- // 跳过注释行
79
+ // 跳过注释行
80
80
  const trimmed = lineText.trim();
81
81
  if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
82
82
  return results;
83
83
  }
84
84
 
85
- // result.output / result.parsedOutput / result.content
85
+ // result.output / result.parsedOutput / result.content
86
86
  const resultAccessPatterns: Array<{ regex: RegExp; field: string }> = [
87
87
  { regex: /\bresult\s*\.\s*output\b/, field: "output" },
88
88
  { regex: /\bresult\s*\.\s*parsedOutput\b/, field: "parsedOutput" },
@@ -99,7 +99,7 @@ function checkLine(lineText: string, lineNum: number): LintFinding[] {
99
99
  }
100
100
  }
101
101
 
102
- // 文件传状态(readFileSync of STATE)
102
+ // 文件传状态(readFileSync of STATE)
103
103
  if (/readFileSync\(.*STATE.*\)|readFileSync\(.*state.*\.json/i.test(lineText)) {
104
104
  results.push({
105
105
  severity: "warning",
@@ -109,7 +109,7 @@ function checkLine(lineText: string, lineNum: number): LintFinding[] {
109
109
  });
110
110
  }
111
111
 
112
- // unlinkSync 清理状态
112
+ // unlinkSync 清理状态
113
113
  if (/unlinkSync.*state/i.test(lineText)) {
114
114
  results.push({
115
115
  severity: "warning",
@@ -169,7 +169,7 @@ function forEachAgentCallRange(
169
169
  const line = lines[i];
170
170
  const trimmed = line.trim();
171
171
 
172
- // 跳过注释
172
+ // 跳过注释
173
173
  if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
174
174
  continue;
175
175
  }
@@ -177,12 +177,12 @@ function forEachAgentCallRange(
177
177
  // 匹配/计括号都用剔除字符串与注释后的行,避免字面量内容干扰
178
178
  const codeLine = stripStringsAndComments(line);
179
179
 
180
- // 检测 agent 调用开始
180
+ // 检测 agent 调用开始
181
181
  if (!inAgentCall && /\bagent\s*\(/.test(codeLine)) {
182
182
  inAgentCall = true;
183
183
  depth = 0;
184
184
  agentStartLine = i;
185
- // 从 agent( 开始计括号
185
+ // 从 agent( 开始计括号
186
186
  const afterAgent = codeLine.replace(/^.*?\bagent\s*\(/, "(");
187
187
  // 非字面量实参(agent(callVar) / agent(expr))→ 跳过(见函数头注释)。
188
188
  // 形如 `agent(` 换行 `{` 的多行字面量调用(argTail 为空)保留原追踪行为。
@@ -196,7 +196,7 @@ function forEachAgentCallRange(
196
196
  if (ch === ")" || ch === "}" || ch === "]") depth--;
197
197
  }
198
198
  if (depth <= 0) {
199
- // 单行 agent 调用
199
+ // 单行 agent 调用
200
200
  callback(agentStartLine, i);
201
201
  inAgentCall = false;
202
202
  }
@@ -247,15 +247,15 @@ function checkAgentCallOptions(
247
247
  for (let i = startLine; i <= endLine; i++) {
248
248
  const line = lines[i];
249
249
 
250
- // 跳过变量声明(const/let/var outputSchema = ...)
250
+ // 跳过变量声明(const/let/var outputSchema = ...)
251
251
  if (/\b(?:const|let|var)\s+outputSchema\b/.test(line)) {
252
252
  continue;
253
253
  }
254
254
 
255
- // 匹配:outputSchema 作为对象 key(简写或显式)
255
+ // 匹配:outputSchema 作为对象 key(简写或显式)
256
256
  if (/\boutputSchema\s*[,\}]/.test(line) || /\boutputSchema\s*:/.test(line)) {
257
- // 排除:outputSchema 作为 value(在另一个 key 的冒号后)
258
- // e.g. "schema: outputSchema," — outputSchema 前是冒号
257
+ // 排除:outputSchema 作为 value(在另一个 key 的冒号后)
258
+ // e.g. "schema: outputSchema," — outputSchema 前是冒号
259
259
  const beforeOutput = line.substring(0, line.indexOf("outputSchema"));
260
260
  if (/:\s*$/.test(beforeOutput)) {
261
261
  continue; // outputSchema 是 value,不是 key
@@ -329,7 +329,7 @@ function checkAgentDescription(source: string): LintFinding[] {
329
329
  // 对象以展开开头(agent({ ...call, ... })):description 来自运行时对象、调用点静态
330
330
  // 不可见——无法验证即不报(review-fix-loop 的 agent({ ...call, agent: ... }) 即此形态)。
331
331
  if (/\{\s*\.\.\./.test(range)) return;
332
- // 匹配 description 或 label 作为对象 key(后跟冒号)
332
+ // 匹配 description 或 label 作为对象 key(后跟冒号)
333
333
  if (!/\b(description|label)\s*:/.test(range)) {
334
334
  findings.push({
335
335
  severity: "warning",
@@ -416,7 +416,7 @@ function isIIFEAwaited(source: string, iifeStart: number): boolean {
416
416
  function checkBareAsyncIIFE(source: string): LintFinding[] {
417
417
  if (!ENTRY_POINT_PATTERNS.some((p) => p.test(source))) return [];
418
418
 
419
- // 用 matchAll 检查所有 IIFE(脚本可能有多个,每个都需独立判断)
419
+ // 用 matchAll 检查所有 IIFE(脚本可能有多个,每个都需独立判断)
420
420
  const findings: LintFinding[] = [];
421
421
  for (const match of source.matchAll(BARE_ASYNC_IIFE_PATTERN)) {
422
422
  const iifeStart = match.index ?? 0;
@@ -519,13 +519,13 @@ function checkMetaPhases(source: string): LintFinding[] {
519
519
  function checkPhaseConsistency(source: string): LintFinding[] {
520
520
  const findings: LintFinding[] = [];
521
521
 
522
- // 提取 meta.phases 声明的字符串 + 声明所在行号
522
+ // 提取 meta.phases 声明的字符串 + 声明所在行号
523
523
  const declared = new Map<string, number>();
524
524
  const phasesArrayMatch = source.match(/phases\s*:\s*\[[^\]]*\]/);
525
525
  if (phasesArrayMatch && phasesArrayMatch.index !== undefined) {
526
526
  const inner = phasesArrayMatch[0];
527
- // 对象数组(如 [{title,detail}])由 checkMetaPhases 单独报,这里跳过提取,
528
- // 避免从对象字段里误抽出字符串作 declared。
527
+ // 对象数组(如 [{title,detail}])由 checkMetaPhases 单独报,这里跳过提取,
528
+ // 避免从对象字段里误抽出字符串作 declared。
529
529
  if (!/\[\s*\{/.test(inner)) {
530
530
  const phasesLine = source.slice(0, phasesArrayMatch.index).split("\n").length;
531
531
  for (const m of inner.matchAll(/['"]([^'"]+)['"]/g)) {
@@ -534,7 +534,7 @@ function checkPhaseConsistency(source: string): LintFinding[] {
534
534
  }
535
535
  }
536
536
 
537
- // 提取所有 phase() 调用实参 + 首次出现的行号
537
+ // 提取所有 phase() 调用实参 + 首次出现的行号
538
538
  const called = new Map<string, number>();
539
539
  for (const m of source.matchAll(/\bphase\s*\(\s*['"]([^'"]+)['"]/g)) {
540
540
  if (m.index === undefined) continue;
@@ -542,10 +542,10 @@ function checkPhaseConsistency(source: string): LintFinding[] {
542
542
  if (!called.has(m[1])) called.set(m[1], lineNum);
543
543
  }
544
544
 
545
- // 两者都为空 → 跳过(脚本不使用 phase 机制)
545
+ // 两者都为空 → 跳过(脚本不使用 phase 机制)
546
546
  if (declared.size === 0 && called.size === 0) return [];
547
547
 
548
- // 声明了但从未 phase() 调用
548
+ // 声明了但从未 phase() 调用
549
549
  for (const [name, line] of declared) {
550
550
  if (!called.has(name)) {
551
551
  findings.push({
@@ -557,7 +557,7 @@ function checkPhaseConsistency(source: string): LintFinding[] {
557
557
  }
558
558
  }
559
559
 
560
- // 调用了但未声明
560
+ // 调用了但未声明
561
561
  for (const [name, line] of called) {
562
562
  if (!declared.has(name)) {
563
563
  findings.push({
@@ -724,10 +724,10 @@ export function lintScript(source: string): LintResult {
724
724
  const lines = source.split("\n");
725
725
  const findings: LintFinding[] = [];
726
726
 
727
- // 入口检查(必须有 agent/parallel/pipeline 之一)
727
+ // 入口检查(必须有 agent/parallel/pipeline 之一)
728
728
  findings.push(...checkEntryPoint(source));
729
729
 
730
- // 逐行检查(result.output、文件传状态等)
730
+ // 逐行检查(result.output、文件传状态等)
731
731
  for (let i = 0; i < lines.length; i++) {
732
732
  findings.push(...checkLine(lines[i], i + 1));
733
733
  }
@@ -735,23 +735,23 @@ export function lintScript(source: string): LintResult {
735
735
  // agent 调用上下文检查(outputSchema 作为 key)
736
736
  findings.push(...checkAgentCalls(source));
737
737
 
738
- // [HISTORICAL] 顶层未 await 的异步 IIFE + 内部调 agent——子进程被提前 kill。
739
- // 教训来源:daily-news-impact.js 用 (async function main(){...})();() 包裹整个脚本,
740
- // worker 外层 IIFE 不等内层 IIFE 就 postMessage("return"),主线程 transition done
741
- // → release runtime → controller.abort() → spawn 后 2ms SIGKILL 子进程。
742
- // 诊断耗时 4 轮:先后误判为 model 故障 / 工具缺失 / turn-signal abort / 并发门闩 gate 异常,
743
- // 最终靠 worker-host → handleReturn → release → abort 的调用栈定位。
738
+ // [HISTORICAL] 顶层未 await 的异步 IIFE + 内部调 agent——子进程被提前 kill。
739
+ // 教训来源:daily-news-impact.js 用 (async function main(){...})();() 包裹整个脚本,
740
+ // worker 外层 IIFE 不等内层 IIFE 就 postMessage("return"),主线程 transition done
741
+ // → release runtime → controller.abort() → spawn 后 2ms SIGKILL 子进程。
742
+ // 诊断耗时 4 轮:先后误判为 model 故障 / 工具缺失 / turn-signal abort / 并发门闩 gate 异常,
743
+ // 最终靠 worker-host → handleReturn → release → abort 的调用栈定位。
744
744
  findings.push(...checkBareAsyncIIFE(source));
745
745
 
746
- // m4 W1-W3:meta 质量(description/when/notFor 短单句 + 不含已声明参数名)。
747
- // 仅对 IF1 解析成功的 @pi-meta 执行——旧 const meta 格式(D1 无 adapter)不检查。
746
+ // m4 W1-W3:meta 质量(description/when/notFor 短单句 + 不含已声明参数名)。
747
+ // 仅对 IF1 解析成功的 @pi-meta 执行——旧 const meta 格式(D1 无 adapter)不检查。
748
748
  const meta = parseResourceMeta(source, "workflow");
749
749
  if (meta && meta.kind === "workflow") {
750
750
  findings.push(...checkMetaQuality(meta));
751
751
  }
752
752
 
753
- // 显示性检查(warning):agent 缺 description / meta.phases 形式 / phase 一致性。
754
- // 目的:让 TUI /workflows 视图避免 unnamed agent 与 (unnamed) phase 分组。
753
+ // 显示性检查(warning):agent 缺 description / meta.phases 形式 / phase 一致性。
754
+ // 目的:让 TUI /workflows 视图避免 unnamed agent 与 (unnamed) phase 分组。
755
755
  findings.push(...checkAgentDescription(source));
756
756
  findings.push(...checkMetaPhases(source));
757
757
  findings.push(...checkPhaseConsistency(source));
@@ -29,7 +29,7 @@ export type WorkerExitHandler = (code: number) => void;
29
29
 
30
30
  export class WorkerHandle {
31
31
  private readonly worker: Worker;
32
- /**
32
+ /**
33
33
  * 竞态守卫。true = 此 handle 仍是当前活动 handle;false = 已 terminate,
34
34
  * 后续事件(已终止 worker 延迟触发的 message/error/exit)必须忽略。
35
35
  *
@@ -42,17 +42,17 @@ export class WorkerHandle {
42
42
  this.worker = worker;
43
43
  }
44
44
 
45
- /** 此 handle 是否仍是当前活动 handle(terminate 后 false,G-025)。 */
45
+ /** 此 handle 是否仍是当前活动 handle(terminate 后 false,G-025)。 */
46
46
  get isCurrent(): boolean {
47
47
  return this.current;
48
48
  }
49
49
 
50
- /** 底层 Worker(WorkerHost/RunRuntime 偶尔需要直接访问,如 ref/href)。 */
50
+ /** 底层 Worker(WorkerHost/RunRuntime 偶尔需要直接访问,如 ref/href)。 */
51
51
  get raw(): Worker {
52
52
  return this.worker;
53
53
  }
54
54
 
55
- /**
55
+ /**
56
56
  * 向 worker 发送消息。terminate 后 no-op(已终止 handle 的 postMessage 无意义)。
57
57
  */
58
58
  postMessage(msg: unknown): void {
@@ -60,7 +60,7 @@ export class WorkerHandle {
60
60
  this.worker.postMessage(msg);
61
61
  }
62
62
 
63
- /**
63
+ /**
64
64
  * 终止 worker 线程。幂等——重复调用安全,第二次起 no-op。
65
65
  * 置 isCurrent=false 后再 await worker.terminate,确保并发 exit 事件
66
66
  * 在 terminate resolve 之前到达时也被守卫拦下。
@@ -71,13 +71,13 @@ export class WorkerHandle {
71
71
  try {
72
72
  await this.worker.terminate();
73
73
  } catch (err) {
74
- // terminate 失败不阻断(worker 可能已退出)。current 已 false,安全。
75
- // 不向上抛——调用方(RunRuntime.release)不应被底层线程错误打断。
74
+ // terminate 失败不阻断(worker 可能已退出)。current 已 false,安全。
75
+ // 不向上抛——调用方(RunRuntime.release)不应被底层线程错误打断。
76
76
  void err;
77
77
  }
78
78
  }
79
79
 
80
- /**
80
+ /**
81
81
  * 绑定 message 回调。仅当 isCurrent 时触发——已终止 handle 的事件被吞掉。
82
82
  * 返回 this 便于链式 onMessage(...).onError(...).onExit(...)。
83
83
  */
@@ -89,7 +89,7 @@ export class WorkerHandle {
89
89
  return this;
90
90
  }
91
91
 
92
- /**
92
+ /**
93
93
  * 绑定 error 回调。仅当 isCurrent 时触发。
94
94
  */
95
95
  onError(handler: WorkerErrorHandler): this {
@@ -100,7 +100,7 @@ export class WorkerHandle {
100
100
  return this;
101
101
  }
102
102
 
103
- /**
103
+ /**
104
104
  * 绑定 exit 回调。仅当 isCurrent 时触发——这是 G-025 的关键守卫:
105
105
  * terminate(old) → startWorker(new) → old exit 触发时,old handle.current
106
106
  * 已为 false,回调 no-op,不会误删 new worker。
@@ -28,7 +28,7 @@ import { buildWorkerScript } from "./worker-script-builder.ts";
28
28
  // ── WorkerHostImpl ───────────────────────────────────────────
29
29
 
30
30
  export class WorkerHostImpl implements WorkerHost {
31
- /**
31
+ /**
32
32
  * 启动一个 Worker thread 运行 workflow 脚本。
33
33
  *
34
34
  * 1. 用 buildWorkerScript(spec.scriptSource) 包装用户脚本(注入 agent/parallel/
@@ -59,9 +59,9 @@ export class WorkerHostImpl implements WorkerHost {
59
59
  name: spec.scriptName,
60
60
  description: spec.description,
61
61
  },
62
- // D-12 regression fix (round-2 #1):注入 budget,否则 worker 内 $BUDGET.total 恒为 0。
63
- // 旧 agent-call-handler.ts 删除时同时丢失了 budget 注入和 budget-update 发送方,
64
- // 导致依赖 $BUDGET 做动态预算分支的脚本静默得到全 0。
62
+ // D-12 regression fix (round-2 #1):注入 budget,否则 worker 内 $BUDGET.total 恒为 0。
63
+ // 旧 agent-call-handler.ts 删除时同时丢失了 budget 注入和 budget-update 发送方,
64
+ // 导致依赖 $BUDGET 做动态预算分支的脚本静默得到全 0。
65
65
  budget: {
66
66
  maxTokens: spec.budgetTokens,
67
67
  usedTokens: 0,
@@ -77,9 +77,9 @@ export class WorkerHostImpl implements WorkerHost {
77
77
 
78
78
  const handle = new WorkerHandle(worker);
79
79
 
80
- // 绑定事件——WorkerHandle 内部用 isCurrent 守卫,terminate 后回调 no-op(G-025)。
81
- // handlers 的 onMessage/onError/onExit 都是 async,这里 void 掉 promise(worker 事件
82
- // 不能 await,且 handler 内部错误由 lifecycle 统一捕获)。
80
+ // 绑定事件——WorkerHandle 内部用 isCurrent 守卫,terminate 后回调 no-op(G-025)。
81
+ // handlers 的 onMessage/onError/onExit 都是 async,这里 void 掉 promise(worker 事件
82
+ // 不能 await,且 handler 内部错误由 lifecycle 统一捕获)。
83
83
  handle.onMessage((raw) => {
84
84
  void handlers.onMessage(raw);
85
85
  });
@@ -87,9 +87,9 @@ export class WorkerHostImpl implements WorkerHost {
87
87
  void handlers.onError(err);
88
88
  });
89
89
  handle.onExit((code) => {
90
- // C.3 修复:传 handle 给 onExit,调用方用 handle.isCurrent 做竞态防护。
91
- // 注意:此时 handle.isCurrent 仍为 true(onExit 回调仅在 isCurrent 时触发,
92
- // WorkerHandle 的内部守卫已过滤掉 terminate 后的 stale exit)。
90
+ // C.3 修复:传 handle 给 onExit,调用方用 handle.isCurrent 做竞态防护。
91
+ // 注意:此时 handle.isCurrent 仍为 true(onExit 回调仅在 isCurrent 时触发,
92
+ // WorkerHandle 的内部守卫已过滤掉 terminate 后的 stale exit)。
93
93
  void handlers.onExit(code, handle);
94
94
  });
95
95