@zhushanwen/pi-subagent-workflow 0.1.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.
- package/agents/context-builder.md +17 -0
- package/agents/general-purpose.md +16 -0
- package/agents/oracle.md +17 -0
- package/agents/planner.md +17 -0
- package/agents/researcher.md +17 -0
- package/agents/reviewer.md +17 -0
- package/agents/scout.md +17 -0
- package/agents/worker.md +16 -0
- package/examples/README.md +43 -0
- package/examples/chain.example.js +92 -0
- package/examples/map-reduce.example.js +99 -0
- package/examples/parallel.example.js +82 -0
- package/examples/scatter-gather.example.js +106 -0
- package/index.ts +1 -0
- package/package.json +66 -0
- package/skills/workflow-script-format/SKILL.md +328 -0
- package/src/execution/__tests__/agent-registry.test.ts +164 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +128 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/bg-notify-render.test.ts +256 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +217 -0
- package/src/execution/__tests__/config.test.ts +110 -0
- package/src/execution/__tests__/crash-recovery.test.ts +311 -0
- package/src/execution/__tests__/execute-nesting.test.ts +359 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +138 -0
- package/src/execution/__tests__/execution-record.test.ts +959 -0
- package/src/execution/__tests__/finalized-marker.test.ts +82 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +135 -0
- package/src/execution/__tests__/format.test.ts +320 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/list-component.test.ts +347 -0
- package/src/execution/__tests__/model-resolver.test.ts +356 -0
- package/src/execution/__tests__/output-collector.test.ts +61 -0
- package/src/execution/__tests__/path-encoding.test.ts +75 -0
- package/src/execution/__tests__/pi-invocation.test.ts +73 -0
- package/src/execution/__tests__/record-store.test.ts +545 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +439 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +897 -0
- package/src/execution/__tests__/sdk-contract.test.ts +272 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +247 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +359 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +314 -0
- package/src/execution/__tests__/session-start-reaper.test.ts +227 -0
- package/src/execution/__tests__/spawn-args.test.ts +244 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/subagent-service.test.ts +678 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +389 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +381 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/tool-action.test.ts +330 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/worktree-manager.test.ts +423 -0
- package/src/execution/__tests__/worktree-registry.test.ts +161 -0
- package/src/execution/agent-registry.ts +252 -0
- package/src/execution/agent-result-mapper.ts +84 -0
- package/src/execution/alive-store.ts +92 -0
- package/src/execution/best-effort.ts +30 -0
- package/src/execution/concurrency-pool.ts +84 -0
- package/src/execution/config.ts +73 -0
- package/src/execution/execute-options-mapper.ts +86 -0
- package/src/execution/execution-record.ts +778 -0
- package/src/execution/finalized-marker.ts +51 -0
- package/src/execution/model-config-service.ts +225 -0
- package/src/execution/model-resolver.ts +247 -0
- package/src/execution/notifier.ts +168 -0
- package/src/execution/output-collector.ts +88 -0
- package/src/execution/path-encoding.ts +34 -0
- package/src/execution/pi-invocation.ts +70 -0
- package/src/execution/record-store.ts +350 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +98 -0
- package/src/execution/session-reconstructor.ts +450 -0
- package/src/execution/session-runner.ts +725 -0
- package/src/execution/spawn-event-adapter.ts +150 -0
- package/src/execution/subagent-service.ts +973 -0
- package/src/execution/subprocess-agent-runner.ts +108 -0
- package/src/execution/temp-prompt.ts +57 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +88 -0
- package/src/execution/types.ts +634 -0
- package/src/execution/worktree-manager.ts +285 -0
- package/src/execution/worktree-registry.ts +144 -0
- package/src/index.ts +454 -0
- package/src/interface/bg-notify-render.ts +286 -0
- package/src/interface/commands.ts +157 -0
- package/src/interface/format.ts +501 -0
- package/src/interface/gui-adapter.ts +136 -0
- package/src/interface/helpers.ts +110 -0
- package/src/interface/list-component.ts +643 -0
- package/src/interface/list-shared.ts +84 -0
- package/src/interface/list-view.ts +373 -0
- package/src/interface/reentry-guard.ts +30 -0
- package/src/interface/subagent-actions.ts +294 -0
- package/src/interface/subagent-tool.ts +294 -0
- package/src/interface/subagents.ts +30 -0
- package/src/interface/tool-render.ts +333 -0
- package/src/interface/tool-workflow-script.ts +351 -0
- package/src/interface/tool-workflow.ts +485 -0
- package/src/interface/views/WorkflowsView.ts +944 -0
- package/src/interface/views/detail-content.ts +298 -0
- package/src/interface/views/format.ts +320 -0
- package/src/orchestration/__tests__/concurrency-gate.test.ts +125 -0
- package/src/orchestration/__tests__/config-loader.test.ts +381 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +332 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +248 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +385 -0
- package/src/orchestration/__tests__/script-lint.test.ts +347 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +42 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +319 -0
- package/src/orchestration/agent-opts-resolver.ts +128 -0
- package/src/orchestration/concurrency-gate.ts +69 -0
- package/src/orchestration/config-loader.ts +313 -0
- package/src/orchestration/error-recovery.ts +578 -0
- package/src/orchestration/execute-agent-call.ts +174 -0
- package/src/orchestration/jsonl-run-store.ts +292 -0
- package/src/orchestration/launcher.ts +368 -0
- package/src/orchestration/lifecycle.ts +373 -0
- package/src/orchestration/models/__tests__/budget.test.ts +367 -0
- package/src/orchestration/models/agent-call.ts +76 -0
- package/src/orchestration/models/budget.ts +148 -0
- package/src/orchestration/models/ports.ts +165 -0
- package/src/orchestration/models/run-runtime.ts +91 -0
- package/src/orchestration/models/run-spec.ts +54 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +102 -0
- package/src/orchestration/models/types.ts +242 -0
- package/src/orchestration/models/workflow-run.ts +275 -0
- package/src/orchestration/models/workflow-script-registry.ts +32 -0
- package/src/orchestration/models/workflow-script.ts +90 -0
- package/src/orchestration/node-ops.ts +192 -0
- package/src/orchestration/script-lint.ts +387 -0
- package/src/orchestration/skill-discovery.ts +60 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +93 -0
- package/src/orchestration/worker-script-builder.ts +281 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +128 -0
- package/src/shared/__tests__/resource-discovery.test.ts +226 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/resource-discovery.ts +535 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* script-lint — 单元测试。
|
|
3
|
+
*
|
|
4
|
+
* lintScript 是纯函数、零副作用、零 IO,直接传入源码字符串即可。
|
|
5
|
+
*
|
|
6
|
+
* 覆盖:入口检查、result.output/outputSchema/文件状态、bare async IIFE、
|
|
7
|
+
* 多错误同时存在、注释行跳过。
|
|
8
|
+
*
|
|
9
|
+
* 【差异说明】源码中实际不存在「未闭合括号」/「嵌套括号深度」检查——
|
|
10
|
+
* lintScript 是 API 误用 lint(非语法 lint),不做括号配对。
|
|
11
|
+
* 因此原任务清单的对应条目已替换为实际存在的检查项(IIFE / outputSchema 等)。
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
|
|
15
|
+
import { lintScript, type LintFinding } from "../script-lint.ts";
|
|
16
|
+
|
|
17
|
+
/** 取所有 error 级 finding。 */
|
|
18
|
+
function errors(findings: LintFinding[]): LintFinding[] {
|
|
19
|
+
return findings.filter((f) => f.severity === "error");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 取所有 warning 级 finding。 */
|
|
23
|
+
function warnings(findings: LintFinding[]): LintFinding[] {
|
|
24
|
+
return findings.filter((f) => f.severity === "warning");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── 基础验证 ──────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
describe("合法脚本通过", () => {
|
|
30
|
+
it("包含 agent() 调用且无其他问题 → valid=true", () => {
|
|
31
|
+
const src = `const meta = { name: "x", description: "d" };\nawait agent({ prompt: "do something" });\n`;
|
|
32
|
+
const result = lintScript(src);
|
|
33
|
+
|
|
34
|
+
expect(result.valid).toBe(true);
|
|
35
|
+
expect(errors(result.findings)).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("parallel() 也是合法入口", () => {
|
|
39
|
+
const src = `await parallel([\n { prompt: "a" },\n { prompt: "b" }\n]);\n`;
|
|
40
|
+
const result = lintScript(src);
|
|
41
|
+
|
|
42
|
+
expect(result.valid).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("pipeline() 也是合法入口", () => {
|
|
46
|
+
const src = `await pipeline({ prompt: "x" });\n`;
|
|
47
|
+
const result = lintScript(src);
|
|
48
|
+
|
|
49
|
+
expect(result.valid).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── 入口检查 ──────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe("缺入口 — 必须含 agent/parallel/pipeline 之一", () => {
|
|
56
|
+
it("不含任何入口 → 报 error", () => {
|
|
57
|
+
const src = `const x = 1;\nconsole.log("no orchestration here");\n`;
|
|
58
|
+
const result = lintScript(src);
|
|
59
|
+
|
|
60
|
+
expect(result.valid).toBe(false);
|
|
61
|
+
const errs = errors(result.findings);
|
|
62
|
+
expect(errs).toHaveLength(1);
|
|
63
|
+
expect(errs[0].message).toMatch(/must call agent.*parallel.*pipeline/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("错误信息建议添加 agent/parallel/pipeline", () => {
|
|
67
|
+
const src = `const x = 1;\n`;
|
|
68
|
+
const result = lintScript(src);
|
|
69
|
+
|
|
70
|
+
const finding = errors(result.findings)[0];
|
|
71
|
+
expect(finding.suggestion).toMatch(/agent\(\).*parallel\(\).*pipeline\(\)/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("类似 agent 字符串但不构成调用不满足入口(agentX( ))", () => {
|
|
75
|
+
const src = `const meta = {};\nagentX("not a real agent call");\n`;
|
|
76
|
+
const result = lintScript(src);
|
|
77
|
+
|
|
78
|
+
expect(result.valid).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── result.output 等错误字段 ───────────────────────────────────
|
|
83
|
+
|
|
84
|
+
describe("result.output / parsedOutput / content 错误访问", () => {
|
|
85
|
+
it("result.output → error", () => {
|
|
86
|
+
const src = `const out = await agent({ prompt: "x" });\nconst v = result.output;\n`;
|
|
87
|
+
const result = lintScript(src);
|
|
88
|
+
|
|
89
|
+
const errs = errors(result.findings);
|
|
90
|
+
const r = errs.find((f) => f.message.includes("result.output"));
|
|
91
|
+
expect(r).toBeDefined();
|
|
92
|
+
expect(r!.line).toBe(2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("result.parsedOutput → error", () => {
|
|
96
|
+
const src = `await agent({ prompt: "x" });\nreturn result.parsedOutput;\n`;
|
|
97
|
+
const result = lintScript(src);
|
|
98
|
+
|
|
99
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.parsedOutput")))
|
|
100
|
+
.toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("result.content → error", () => {
|
|
104
|
+
const src = `await agent({ prompt: "x" });\nconsole.log(result.content);\n`;
|
|
105
|
+
const result = lintScript(src);
|
|
106
|
+
|
|
107
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.content")))
|
|
108
|
+
.toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("result 的三种错误字段都建议用 await agent() 直接取值", () => {
|
|
112
|
+
const src = `await agent({ prompt: "x" });\nresult.output;\nresult.parsedOutput;\nresult.content;\n`;
|
|
113
|
+
const result = lintScript(src);
|
|
114
|
+
|
|
115
|
+
for (const f of errors(result.findings)) {
|
|
116
|
+
expect(f.suggestion).toMatch(/await agent/);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── outputSchema 作为 key(agent 选项误用)─────────────────────
|
|
122
|
+
|
|
123
|
+
describe("outputSchema 作为 agent 选项 key", () => {
|
|
124
|
+
it("简写 outputSchema 作为 key → error", () => {
|
|
125
|
+
const src = `await agent({\n prompt: "x",\n outputSchema,\n});\n`;
|
|
126
|
+
const result = lintScript(src);
|
|
127
|
+
|
|
128
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema"))).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("显式 outputSchema: ... 作为 key → error", () => {
|
|
132
|
+
const src = `await agent({\n prompt: "x",\n outputSchema: foo,\n});\n`;
|
|
133
|
+
const result = lintScript(src);
|
|
134
|
+
|
|
135
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema"))).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("schema: outputSchema 作为 value → 不报 error", () => {
|
|
139
|
+
// outputSchema 出现在 value 位置是合法的(schema 才是正确 key)
|
|
140
|
+
const src = `await agent({\n prompt: "x",\n schema: outputSchema,\n});\n`;
|
|
141
|
+
const result = lintScript(src);
|
|
142
|
+
|
|
143
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema")))
|
|
144
|
+
.toBe(false);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ── 文件状态(warning)─────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
describe("文件传状态 / unlinkSync — warning", () => {
|
|
151
|
+
it("readFileSync 读 STATE 文件 → warning", () => {
|
|
152
|
+
const src = `await agent({ prompt: "x" });\nconst s = readFileSync(STATE_PATH, "utf-8");\n`;
|
|
153
|
+
const result = lintScript(src);
|
|
154
|
+
|
|
155
|
+
const ws = warnings(result.findings);
|
|
156
|
+
// 实际 message 是 "Reading a state file between agent calls is fragile..."(不含字面 "readFileSync")
|
|
157
|
+
expect(ws.some((w) => /state file.*fragile/i.test(w.message))).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("unlinkSync 清理 state → warning", () => {
|
|
161
|
+
const src = `await agent({ prompt: "x" });\nunlinkSync(stateFile);\n`;
|
|
162
|
+
const result = lintScript(src);
|
|
163
|
+
|
|
164
|
+
const ws = warnings(result.findings);
|
|
165
|
+
expect(ws.some((w) => w.message.includes("unlinkSync"))).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("warning 不影响 valid 判定(valid 只看 error)", () => {
|
|
169
|
+
const src = `await agent({ prompt: "x" });\nunlinkSync(stateFile);\n`;
|
|
170
|
+
const result = lintScript(src);
|
|
171
|
+
|
|
172
|
+
expect(result.valid).toBe(true);
|
|
173
|
+
expect(warnings(result.findings).length).toBeGreaterThan(0);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ── bare async IIFE ───────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
describe("顶层未 await 的异步 IIFE + 内部调 agent — 子进程被提前 kill", () => {
|
|
180
|
+
it("孤立 fire-and-forget IIFE 内调 agent → error", () => {
|
|
181
|
+
const src = [
|
|
182
|
+
`(async function main() {`,
|
|
183
|
+
` await agent({ prompt: "x" });`,
|
|
184
|
+
`})();`,
|
|
185
|
+
``,
|
|
186
|
+
].join("\n");
|
|
187
|
+
const result = lintScript(src);
|
|
188
|
+
|
|
189
|
+
const errs = errors(result.findings);
|
|
190
|
+
const iifeFinding = errs.find((f) => /fire-and-forget/i.test(f.message));
|
|
191
|
+
expect(iifeFinding).toBeDefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("await 前缀的 IIFE 内调 agent → 不报 error(warning 或无)", () => {
|
|
195
|
+
const src = [
|
|
196
|
+
`await (async () => {`,
|
|
197
|
+
` await agent({ prompt: "x" });`,
|
|
198
|
+
`})();`,
|
|
199
|
+
``,
|
|
200
|
+
].join("\n");
|
|
201
|
+
const result = lintScript(src);
|
|
202
|
+
|
|
203
|
+
expect(errors(result.findings).some((f) => /fire-and-forget/i.test(f.message)))
|
|
204
|
+
.toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("IIFE 被赋值/返回接住 → warning(非 error)", () => {
|
|
208
|
+
const src = [
|
|
209
|
+
`const p = (async () => {`,
|
|
210
|
+
` await agent({ prompt: "x" });`,
|
|
211
|
+
`})();`,
|
|
212
|
+
`await p;`,
|
|
213
|
+
``,
|
|
214
|
+
].join("\n");
|
|
215
|
+
const result = lintScript(src);
|
|
216
|
+
|
|
217
|
+
expect(errors(result.findings).some((f) => /fire-and-forget/i.test(f.message)))
|
|
218
|
+
.toBe(false);
|
|
219
|
+
expect(warnings(result.findings).some((f) => /assigned|returned/i.test(f.message)))
|
|
220
|
+
.toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("IIFE 内不含 agent/parallel/pipeline → 不报(合法的纯 I/O IIFE)", () => {
|
|
224
|
+
const src = [
|
|
225
|
+
`(async () => {`,
|
|
226
|
+
` await new Promise(r => setTimeout(r, 100));`,
|
|
227
|
+
`})();`,
|
|
228
|
+
``,
|
|
229
|
+
].join("\n");
|
|
230
|
+
const result = lintScript(src);
|
|
231
|
+
|
|
232
|
+
// 无 entry point → 入口检查报 error;但不应有 IIFE error
|
|
233
|
+
const iifeErrors = errors(result.findings).filter((f) => /iife|fire-and-forget/i.test(f.message));
|
|
234
|
+
expect(iifeErrors).toHaveLength(0);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ── 注释/字符串内的模式不被计入 ──────────────────────────────
|
|
239
|
+
|
|
240
|
+
describe("注释行内的模式跳过", () => {
|
|
241
|
+
it("// 注释中的 result.output 不报", () => {
|
|
242
|
+
const src = [
|
|
243
|
+
`await agent({ prompt: "x" });`,
|
|
244
|
+
`// here we discuss result.output as a concept`,
|
|
245
|
+
``,
|
|
246
|
+
].join("\n");
|
|
247
|
+
const result = lintScript(src);
|
|
248
|
+
|
|
249
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.output")))
|
|
250
|
+
.toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("/* 块注释行内的 result.output 不报", () => {
|
|
254
|
+
const src = [
|
|
255
|
+
`await agent({ prompt: "x" });`,
|
|
256
|
+
`/* block comment mentions result.output on purpose */`,
|
|
257
|
+
``,
|
|
258
|
+
].join("\n");
|
|
259
|
+
const result = lintScript(src);
|
|
260
|
+
|
|
261
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.output")))
|
|
262
|
+
.toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("* 续行注释中的 result.parsedOutput 不报", () => {
|
|
266
|
+
const src = [
|
|
267
|
+
`await agent({ prompt: "x" });`,
|
|
268
|
+
`/**`,
|
|
269
|
+
` * result.parsedOutput is intentionally mentioned here`,
|
|
270
|
+
` */`,
|
|
271
|
+
``,
|
|
272
|
+
].join("\n");
|
|
273
|
+
const result = lintScript(src);
|
|
274
|
+
|
|
275
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.parsedOutput")))
|
|
276
|
+
.toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("注释中的 readFileSync(STATE) 不报 warning", () => {
|
|
280
|
+
const src = [
|
|
281
|
+
`await agent({ prompt: "x" });`,
|
|
282
|
+
`// we used to readFileSync(STATE) but no longer`,
|
|
283
|
+
``,
|
|
284
|
+
].join("\n");
|
|
285
|
+
const result = lintScript(src);
|
|
286
|
+
|
|
287
|
+
expect(warnings(result.findings).some((w) => /state file/i.test(w.message)))
|
|
288
|
+
.toBe(false);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ── 多错误同时存在 ────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
describe("多种错误同时存在", () => {
|
|
295
|
+
it("缺入口 + result.output + readFileSync 一起报", () => {
|
|
296
|
+
const src = [
|
|
297
|
+
`const x = result.output;`,
|
|
298
|
+
`const s = readFileSync(STATE, "utf-8");`,
|
|
299
|
+
`console.log(x, s);`,
|
|
300
|
+
``,
|
|
301
|
+
].join("\n");
|
|
302
|
+
const result = lintScript(src);
|
|
303
|
+
|
|
304
|
+
const msgs = result.findings.map((f) => f.message).join("\n");
|
|
305
|
+
expect(result.valid).toBe(false);
|
|
306
|
+
expect(msgs).toMatch(/must call agent.*parallel.*pipeline/);
|
|
307
|
+
expect(msgs).toMatch(/result\.output/);
|
|
308
|
+
// readFileSync(STATE) 触发的 warning 文案是 "Reading a state file..."
|
|
309
|
+
expect(msgs).toMatch(/state file/i);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("result.output + result.content + outputSchema 三种错误同报", () => {
|
|
313
|
+
const src = [
|
|
314
|
+
`await agent({`,
|
|
315
|
+
` prompt: "x",`,
|
|
316
|
+
` outputSchema,`,
|
|
317
|
+
`});`,
|
|
318
|
+
`const a = result.output;`,
|
|
319
|
+
`const b = result.content;`,
|
|
320
|
+
``,
|
|
321
|
+
].join("\n");
|
|
322
|
+
const result = lintScript(src);
|
|
323
|
+
|
|
324
|
+
const msgs = result.findings.map((f) => f.message).join("\n");
|
|
325
|
+
expect(msgs).toMatch(/result\.output/);
|
|
326
|
+
expect(msgs).toMatch(/result\.content/);
|
|
327
|
+
expect(msgs).toMatch(/outputSchema/);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("findings 按行号升序排列(稳定输出)", () => {
|
|
331
|
+
const src = [
|
|
332
|
+
`await agent({ prompt: "x" });`, // line 1
|
|
333
|
+
`const a = result.content;`, // line 2 → 应在 outputSchema(line 5) 之前
|
|
334
|
+
`const b = result.output;`, // line 3
|
|
335
|
+
`await agent({`,
|
|
336
|
+
` prompt: "y",`,
|
|
337
|
+
` outputSchema,`, // line 6
|
|
338
|
+
`});`,
|
|
339
|
+
``,
|
|
340
|
+
].join("\n");
|
|
341
|
+
const result = lintScript(src);
|
|
342
|
+
|
|
343
|
+
const lines = result.findings.map((f) => f.line);
|
|
344
|
+
const sorted = [...lines].sort((a, b) => a - b);
|
|
345
|
+
expect(lines).toEqual(sorted);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildWorkerScript — workflow() 全局函数注入测试。
|
|
3
|
+
*
|
|
4
|
+
* 验证生成的 worker 源码字符串包含 workflow 嵌套调用所需的全部契约:
|
|
5
|
+
* - workflow() 全局函数声明
|
|
6
|
+
* - workflow-call 消息(Worker → Main)
|
|
7
|
+
* - workflow-result 消息处理(Main → Worker)
|
|
8
|
+
* - execute() context 包含 workflow
|
|
9
|
+
* - name 参数校验
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { buildWorkerScript } from "../worker-script-builder.ts";
|
|
14
|
+
|
|
15
|
+
describe("buildWorkerScript — workflow() global injection", () => {
|
|
16
|
+
const script = buildWorkerScript("// noop user script");
|
|
17
|
+
|
|
18
|
+
it("injects workflow() global function", () => {
|
|
19
|
+
expect(script).toContain("async function workflow");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('workflow() sends workflow-call message', () => {
|
|
23
|
+
expect(script).toContain('type: "workflow-call"');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('handles workflow-result message', () => {
|
|
27
|
+
// worker 是 workflow-result 的接收方,用条件分支处理(非对象字面量)
|
|
28
|
+
expect(script).toContain('msg.type === "workflow-result"');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("execute() context includes workflow", () => {
|
|
32
|
+
expect(script).toContain(
|
|
33
|
+
"module.exports.execute({ agent, parallel, pipeline, phase, log, workflow, $ARGS, $WORKSPACE, $BUDGET })",
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("workflow() validates name argument", () => {
|
|
38
|
+
expect(script).toContain(
|
|
39
|
+
"workflow() requires a workflow name string as first argument",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|