@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.
- package/README.md +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-4KN4TTG7.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-X7SZ5HLQ.js +66 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +65 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +93 -1
- package/dist/execution/engine/engines/zcode/constants.d.ts +93 -1
- package/dist/execution/engine/engines/zcode/constants.js +43 -1
- package/dist/execution/engine/engines/zcode/reader.d.cts +1 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +14709 -100
- package/dist/index.d.cts +4053 -200
- package/dist/index.d.ts +4053 -200
- package/dist/index.js +14358 -102
- package/dist/{types-BxyAidGf.d.cts → types-DpUO16pj.d.cts} +464 -1
- package/dist/{types-BxyAidGf.d.ts → types-DpUO16pj.d.ts} +464 -1
- package/package.json +6 -3
- package/src/__tests__/review-fix-loop-script.test.ts +90 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +21 -0
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +71 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +1 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +2 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/epipe-fallback.test.ts +2 -0
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/finalize-record.test.ts +106 -0
- package/src/execution/__tests__/gc-timer.test.ts +2 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +2 -0
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +378 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/notify-ledger.test.ts +294 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +83 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +4 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +1 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +407 -17
- package/src/execution/__tests__/run-spawn-integration.test.ts +1 -1
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +12 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +89 -0
- package/src/execution/__tests__/session-pending.test.ts +228 -0
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +1 -0
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +226 -0
- package/src/execution/__tests__/settled-watchdog.test.ts +289 -0
- package/src/execution/__tests__/spawned-children.test.ts +153 -10
- package/src/execution/__tests__/subagent-actions-core.test.ts +997 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +71 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +266 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +3 -0
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +316 -0
- package/src/execution/__tests__/timeout-integration.test.ts +7 -7
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +95 -1
- package/src/execution/__tests__/ui-request-queue.test.ts +25 -0
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +225 -1
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +49 -2
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +87 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +26 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +83 -1
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +131 -0
- package/src/execution/engine/__tests__/registry.test.ts +90 -1
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +3 -3
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +274 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +472 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +387 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +780 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +815 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +462 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +119 -1
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +136 -6
- package/src/execution/engine/engines/zcode/appserver-home.ts +442 -0
- package/src/execution/engine/engines/zcode/appserver-probe.ts +141 -0
- package/src/execution/engine/engines/zcode/connection.ts +585 -0
- package/src/execution/engine/engines/zcode/constants.ts +129 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +54 -8
- package/src/execution/engine/engines/zcode/preparer.ts +25 -22
- package/src/execution/engine/engines/zcode/session-channel.ts +656 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +909 -41
- package/src/execution/engine/host-task-spec.ts +3 -3
- package/src/execution/engine/port.ts +41 -1
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/finalize-record.ts +63 -5
- package/src/execution/get-state-handshake.ts +85 -10
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +29 -15
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-runner.ts +974 -128
- package/src/execution/sessions-index.ts +10 -55
- package/src/execution/settled-watchdog.ts +99 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +396 -49
- package/src/execution/ui-request-handler-factory.ts +27 -4
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +413 -6
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -4
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +1 -8
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +382 -0
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +389 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +77 -0
- package/src/orchestration/__tests__/run-snapshot.test.ts +353 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +7 -7
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/error-recovery.ts +416 -146
- package/src/orchestration/execute-agent-call.ts +9 -9
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +35 -26
- package/src/orchestration/lifecycle.ts +355 -79
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/run-runtime.ts +13 -13
- package/src/orchestration/models/trace.ts +8 -8
- package/src/orchestration/models/workflow-run.ts +27 -27
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +33 -33
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +3 -2
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +320 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +41 -1
- package/src/shared/resource-discovery.ts +104 -37
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +14 -3
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
|
|
3
4
|
import { CORE_PACKAGE_VERSION } from "../index";
|
|
4
5
|
|
|
6
|
+
// 对 package.json version 断言(单一事实源):本测试是 src/index.ts 字面量与
|
|
7
|
+
// package.json 双源同步的执法者——任何一侧 bump 后未同步另一侧立即红,
|
|
8
|
+
// 替代旧硬编码 "0.1.0" 字面量(版本 bump 时必忘改的第三处双源)。
|
|
5
9
|
describe("subagent-core scaffold smoke", () => {
|
|
6
|
-
it("exposes CORE_PACKAGE_VERSION", () => {
|
|
7
|
-
|
|
10
|
+
it("exposes CORE_PACKAGE_VERSION synced with package.json", () => {
|
|
11
|
+
const pkgVersion = JSON.parse(
|
|
12
|
+
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
|
|
13
|
+
).version;
|
|
14
|
+
expect(CORE_PACKAGE_VERSION).toBe(pkgVersion);
|
|
8
15
|
});
|
|
9
16
|
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/execution/__tests__/agent-profile.test.ts
|
|
2
|
+
//
|
|
3
|
+
// parseAgentProfile 宽容解析测试(sink 设计 U2 / D3 / u-core-agent 验收①)。
|
|
4
|
+
//
|
|
5
|
+
// 字段形态覆盖矩阵(D3 定稿):单行 key:value、行内数组、block-scalar、
|
|
6
|
+
// 多行 `- item` 列表、无 frontmatter 宽容降级不抛;另含 legacy fallback
|
|
7
|
+
// (meta=null 单行兜底)与 parseResourceMeta 严格层新字段投影断言
|
|
8
|
+
// (AgentMeta 扩 maxTurns/disallowedTools/skills 的 typecheckMeta 行为)。
|
|
9
|
+
|
|
10
|
+
import { describe, expect, fail, it } from "vitest";
|
|
11
|
+
|
|
12
|
+
import { parseResourceMeta } from "../../shared/meta-parser.ts";
|
|
13
|
+
import { parseAgentProfile } from "../agent-registry.ts";
|
|
14
|
+
|
|
15
|
+
describe("parseAgentProfile(D3 宽容解析)", () => {
|
|
16
|
+
it("单行 key:value 全字段(IF1 通过)", () => {
|
|
17
|
+
const profile = parseAgentProfile(
|
|
18
|
+
`---
|
|
19
|
+
name: coder
|
|
20
|
+
description: coding agent
|
|
21
|
+
model: anthropic/claude-3.5-sonnet
|
|
22
|
+
thinkingLevel: high
|
|
23
|
+
tools: read, bash
|
|
24
|
+
maxTurns: 2
|
|
25
|
+
disallowedTools: write, edit
|
|
26
|
+
skills: code-review
|
|
27
|
+
---
|
|
28
|
+
body text`,
|
|
29
|
+
"/x/coder.md",
|
|
30
|
+
);
|
|
31
|
+
expect(profile.name).toBe("coder");
|
|
32
|
+
expect(profile.description).toBe("coding agent");
|
|
33
|
+
expect(profile.body).toBe("body text");
|
|
34
|
+
expect(profile.model).toBe("anthropic/claude-3.5-sonnet");
|
|
35
|
+
expect(profile.thinkingLevel).toBe("high");
|
|
36
|
+
expect(profile.tools).toEqual(["read", "bash"]);
|
|
37
|
+
expect(profile.maxTurns).toBe(2);
|
|
38
|
+
expect(profile.disallowedTools).toEqual(["write", "edit"]);
|
|
39
|
+
expect(profile.skills).toEqual(["code-review"]);
|
|
40
|
+
expect(profile.meta).not.toBeNull();
|
|
41
|
+
expect(profile.warnings).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("行内数组形态(tools: [read, bash])", () => {
|
|
45
|
+
const profile = parseAgentProfile(
|
|
46
|
+
`---
|
|
47
|
+
name: coder
|
|
48
|
+
description: coding agent
|
|
49
|
+
tools: [read, bash]
|
|
50
|
+
---
|
|
51
|
+
body`,
|
|
52
|
+
"/x/coder.md",
|
|
53
|
+
);
|
|
54
|
+
expect(profile.tools).toEqual(["read", "bash"]);
|
|
55
|
+
expect(profile.meta).not.toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("block-scalar description 完整保留(多行描述不截断)", () => {
|
|
59
|
+
const profile = parseAgentProfile(
|
|
60
|
+
`---
|
|
61
|
+
name: reviewer
|
|
62
|
+
description: |
|
|
63
|
+
first line of description
|
|
64
|
+
second line of description
|
|
65
|
+
---
|
|
66
|
+
body`,
|
|
67
|
+
"/x/reviewer.md",
|
|
68
|
+
);
|
|
69
|
+
// `|` clip chomping 保留单个末尾换行(eemeli/yaml 标准行为)
|
|
70
|
+
expect(profile.description).toBe("first line of description\nsecond line of description\n");
|
|
71
|
+
expect(profile.meta?.description).toBe(profile.description);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("多行 `- item` 列表形态(tools/disallowedTools/skills)", () => {
|
|
75
|
+
const profile = parseAgentProfile(
|
|
76
|
+
`---
|
|
77
|
+
name: builder
|
|
78
|
+
description: build agent
|
|
79
|
+
tools:
|
|
80
|
+
- read
|
|
81
|
+
- bash
|
|
82
|
+
disallowedTools:
|
|
83
|
+
- write
|
|
84
|
+
skills:
|
|
85
|
+
- code-review
|
|
86
|
+
- test-runner
|
|
87
|
+
---
|
|
88
|
+
body`,
|
|
89
|
+
"/x/builder.md",
|
|
90
|
+
);
|
|
91
|
+
expect(profile.tools).toEqual(["read", "bash"]);
|
|
92
|
+
expect(profile.disallowedTools).toEqual(["write"]);
|
|
93
|
+
expect(profile.skills).toEqual(["code-review", "test-runner"]);
|
|
94
|
+
expect(profile.maxTurns).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("无 frontmatter:宽容降级不抛——name=stem、description 空串、全文 body、无 warning", () => {
|
|
98
|
+
const profile = parseAgentProfile("# README\n\nplain markdown.", "/x/README.md");
|
|
99
|
+
expect(profile.name).toBe("README");
|
|
100
|
+
expect(profile.description).toBe("");
|
|
101
|
+
expect(profile.body).toBe("# README\n\nplain markdown.");
|
|
102
|
+
expect(profile.meta).toBeNull();
|
|
103
|
+
expect(profile.warnings).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("未闭合 frontmatter:name 经 legacy fallback、全文作 body、warning 可见", () => {
|
|
107
|
+
const text = `---\nname: broken\nmodel: x/y`;
|
|
108
|
+
const profile = parseAgentProfile(text, "/x/broken.md");
|
|
109
|
+
expect(profile.name).toBe("broken");
|
|
110
|
+
expect(profile.body).toBe(text);
|
|
111
|
+
expect(profile.meta).toBeNull();
|
|
112
|
+
expect(profile.warnings).toHaveLength(1);
|
|
113
|
+
expect(profile.warnings[0]).toContain("未闭合");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("IF1 失败(缺 description):执行字段经单行 legacy fallback 不丢失 + warning", () => {
|
|
117
|
+
const profile = parseAgentProfile(
|
|
118
|
+
`---
|
|
119
|
+
name: legacy
|
|
120
|
+
model: anthropic/claude-3.5-sonnet
|
|
121
|
+
tools: read, bash
|
|
122
|
+
maxTurns: 5
|
|
123
|
+
---
|
|
124
|
+
body text`,
|
|
125
|
+
"/x/legacy.md",
|
|
126
|
+
);
|
|
127
|
+
expect(profile.meta).toBeNull();
|
|
128
|
+
expect(profile.name).toBe("legacy");
|
|
129
|
+
expect(profile.description).toBe("");
|
|
130
|
+
expect(profile.model).toBe("anthropic/claude-3.5-sonnet");
|
|
131
|
+
expect(profile.tools).toEqual(["read", "bash"]);
|
|
132
|
+
// legacy fallback 只认字符串形态:`maxTurns: 5` 单行 → Number 换算
|
|
133
|
+
expect(profile.maxTurns).toBe(5);
|
|
134
|
+
expect(profile.warnings).toHaveLength(1);
|
|
135
|
+
expect(profile.warnings[0]).toContain("legacy fallback");
|
|
136
|
+
expect(profile.body).toBe("body text");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("IF1 失败 + maxTurns 非数字:忽略该字段并追加 warning(不抛)", () => {
|
|
140
|
+
const profile = parseAgentProfile(
|
|
141
|
+
`---
|
|
142
|
+
name: weird
|
|
143
|
+
maxTurns: many
|
|
144
|
+
---
|
|
145
|
+
body`,
|
|
146
|
+
"/x/weird.md",
|
|
147
|
+
);
|
|
148
|
+
expect(profile.meta).toBeNull();
|
|
149
|
+
expect(profile.maxTurns).toBeUndefined();
|
|
150
|
+
expect(profile.warnings.some((w) => w.includes("maxTurns"))).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("name 缺省 stem(frontmatter 无 name 字段且 IF1 失败时)", () => {
|
|
154
|
+
const profile = parseAgentProfile("---\nmodel: x/y\n---\nbody", "/x/unnamed.md");
|
|
155
|
+
expect(profile.name).toBe("unnamed");
|
|
156
|
+
expect(profile.model).toBe("x/y");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("parseResourceMeta 严格层新字段投影(AgentMeta D3 扩展的 typecheckMeta 行为)", () => {
|
|
161
|
+
it("数字 maxTurns + 数组 disallowedTools/skills 投影进 AgentMeta", () => {
|
|
162
|
+
const meta = parseResourceMeta(
|
|
163
|
+
`---
|
|
164
|
+
name: coder
|
|
165
|
+
description: coding agent
|
|
166
|
+
maxTurns: 2
|
|
167
|
+
disallowedTools: [write]
|
|
168
|
+
skills:
|
|
169
|
+
- code-review
|
|
170
|
+
---
|
|
171
|
+
body`,
|
|
172
|
+
"agent",
|
|
173
|
+
);
|
|
174
|
+
expect(meta).not.toBeNull();
|
|
175
|
+
if (meta?.kind !== "agent") return fail("kind 应为 agent");
|
|
176
|
+
expect(meta.maxTurns).toBe(2);
|
|
177
|
+
expect(meta.disallowedTools).toEqual(["write"]);
|
|
178
|
+
expect(meta.skills).toEqual(["code-review"]);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("maxTurns 非有限数字 → reject(严格层不静默丢弃非法字段)", () => {
|
|
182
|
+
const meta = parseResourceMeta(
|
|
183
|
+
"---\nname: x\ndescription: y\nmaxTurns: many\n---\nbody",
|
|
184
|
+
"agent",
|
|
185
|
+
);
|
|
186
|
+
expect(meta).toBeNull();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("disallowedTools 数组含非字符串 → reject", () => {
|
|
190
|
+
const meta = parseResourceMeta(
|
|
191
|
+
"---\nname: x\ndescription: y\ndisallowedTools: [write, 42]\n---\nbody",
|
|
192
|
+
"agent",
|
|
193
|
+
);
|
|
194
|
+
expect(meta).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("disallowedTools/skills 空数组 → 键保留为 [](与 tools: [] 空列表语义一致)", () => {
|
|
198
|
+
const meta = parseResourceMeta(
|
|
199
|
+
"---\nname: x\ndescription: y\ntools: []\ndisallowedTools: []\nskills: []\n---\nbody",
|
|
200
|
+
"agent",
|
|
201
|
+
);
|
|
202
|
+
expect(meta).not.toBeNull();
|
|
203
|
+
if (meta?.kind !== "agent") return fail("kind 应为 agent");
|
|
204
|
+
expect("disallowedTools" in meta).toBe(true);
|
|
205
|
+
expect(meta.disallowedTools).toEqual([]);
|
|
206
|
+
expect("skills" in meta).toBe(true);
|
|
207
|
+
expect(meta.skills).toEqual([]);
|
|
208
|
+
// 同 fixture 的 tools 基准:空数组三字段族同语义
|
|
209
|
+
expect("tools" in meta).toBe(true);
|
|
210
|
+
expect(meta.tools).toEqual([]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("workflow meta 含 agent 专属新字段 → 串类 reject", () => {
|
|
214
|
+
const meta = parseResourceMeta(
|
|
215
|
+
"/* @pi-meta\nname: wf\ndescription: d\nphases: [a]\nmaxTurns: 2\n*/\ncode",
|
|
216
|
+
"workflow",
|
|
217
|
+
);
|
|
218
|
+
expect(meta).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("缺 maxTurns/disallowedTools/skills 时 AgentMeta 不出现这些键(零回归)", () => {
|
|
222
|
+
const meta = parseResourceMeta(
|
|
223
|
+
"---\nname: x\ndescription: y\n---\nbody",
|
|
224
|
+
"agent",
|
|
225
|
+
);
|
|
226
|
+
expect(meta).not.toBeNull();
|
|
227
|
+
if (meta?.kind !== "agent") return fail("kind 应为 agent");
|
|
228
|
+
expect("maxTurns" in meta).toBe(false);
|
|
229
|
+
expect("disallowedTools" in meta).toBe(false);
|
|
230
|
+
expect("skills" in meta).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// src/execution/__tests__/agents-assembly.test.ts
|
|
2
|
+
//
|
|
3
|
+
// discoverAgents 装配函数测试(sink 设计 U2 / A6 / u-core-agent 验收②)。
|
|
4
|
+
//
|
|
5
|
+
// 等值对照口径:fixture 目录下,discoverAgents 产出与 pi 壳现装配循环
|
|
6
|
+
// (subagent-list-injector.discoverAllAgents)产出「同序同名同字段」。
|
|
7
|
+
// pi 壳装配循环无法直接 import 进 core 测试(core 对 pi-coding-agent /
|
|
8
|
+
// pi-extension-logger 零运行时触点——vitest.config 头注红线),故 oracle 在
|
|
9
|
+
// 测试内按 pi 循环逐句同构复刻,但全部消费 core 既有原语
|
|
10
|
+
// (discoverResources + parseResourceMeta 严格层 + sortByCodepoint)——
|
|
11
|
+
// 这正是 pi 循环消费的同一批 core 原语(pi 的 parseAgentFrontmatter 内部即
|
|
12
|
+
// parseResourceMeta(content, "agent") 投影)。oracle 与被测实现走不同解析路径
|
|
13
|
+
// (严格层 vs parseAgentProfile 宽容层),非自证。
|
|
14
|
+
//
|
|
15
|
+
// fixture 含全部清单可见性分支:正常条目(多形态)、同名遮蔽(hostRoots
|
|
16
|
+
// last-writer-wins)、无 frontmatter(README,两边都不进清单)、IF1 失败
|
|
17
|
+
// (缺 description,两边都不进清单)。
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as os from "node:os";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
|
|
23
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
24
|
+
|
|
25
|
+
import { configureCore, resetCoreForTests } from "../../core/host-services.ts";
|
|
26
|
+
import type { AgentEntry } from "../../shared/injection-render.ts";
|
|
27
|
+
import { sortByCodepoint } from "../../shared/injection-render.ts";
|
|
28
|
+
import { parseResourceMeta } from "../../shared/meta-parser.ts";
|
|
29
|
+
import {
|
|
30
|
+
clearFileCache,
|
|
31
|
+
discoverResources,
|
|
32
|
+
getCachedFileContent,
|
|
33
|
+
type DiscoveryRoot,
|
|
34
|
+
type ScanConfig,
|
|
35
|
+
} from "../../shared/resource-discovery.ts";
|
|
36
|
+
import { discoverAgents } from "../agents-assembly.ts";
|
|
37
|
+
|
|
38
|
+
// ── oracle:pi 壳 discoverAllAgents 装配循环的逐句同构(消费同一批 core 原语)──
|
|
39
|
+
|
|
40
|
+
async function piAssemblyOracle(
|
|
41
|
+
workspaceRoot: string,
|
|
42
|
+
hostRoots: DiscoveryRoot[],
|
|
43
|
+
): Promise<AgentEntry[]> {
|
|
44
|
+
const config: ScanConfig = { kind: "agents", workspaceRoot, hostRoots };
|
|
45
|
+
const resources = await discoverResources(config);
|
|
46
|
+
|
|
47
|
+
const agentMap = new Map<string, AgentEntry>();
|
|
48
|
+
for (const resource of resources) {
|
|
49
|
+
if (!resource.available) continue;
|
|
50
|
+
const content = getCachedFileContent(resource.path);
|
|
51
|
+
if (content === null) continue; // pi: catch → logger.error → skip
|
|
52
|
+
// pi parseAgentFrontmatter 的投影面(parseResourceMeta 严格层)
|
|
53
|
+
const meta = parseResourceMeta(content, "agent");
|
|
54
|
+
if (meta && meta.kind === "agent") {
|
|
55
|
+
agentMap.set(meta.name, {
|
|
56
|
+
name: meta.name,
|
|
57
|
+
description: meta.description,
|
|
58
|
+
when: meta.when,
|
|
59
|
+
examples: meta.examples,
|
|
60
|
+
path: resource.path,
|
|
61
|
+
});
|
|
62
|
+
} else if (content.trimStart().startsWith("---")) {
|
|
63
|
+
// pi: startsWithFrontmatter → logger.warn(清单可见性口径:无 frontmatter 不 warn)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sortByCodepoint([...agentMap.values()], (a) => a.name);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── fixture ──────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function writeAgent(root: string, rel: string, content: string): string {
|
|
72
|
+
const abs = path.join(root, rel);
|
|
73
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
74
|
+
fs.writeFileSync(abs, content, "utf-8");
|
|
75
|
+
return abs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("discoverAgents", () => {
|
|
79
|
+
let outerDir: string;
|
|
80
|
+
let workspaceRoot: string;
|
|
81
|
+
let hostRoot: string;
|
|
82
|
+
let logCalls: Array<{ level: string; component: string; message: string; data?: unknown }>;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
outerDir = fs.mkdtempSync(path.join(os.tmpdir(), "agents-assembly-"));
|
|
86
|
+
workspaceRoot = path.join(outerDir, "ws");
|
|
87
|
+
hostRoot = path.join(outerDir, "host", "agents");
|
|
88
|
+
fs.mkdirSync(workspaceRoot, { recursive: true });
|
|
89
|
+
logCalls = [];
|
|
90
|
+
configureCore({
|
|
91
|
+
dataRoot: () => outerDir,
|
|
92
|
+
log: (level, component, message, data) => {
|
|
93
|
+
logCalls.push({ level, component, message, data });
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
resetCoreForTests();
|
|
100
|
+
clearFileCache();
|
|
101
|
+
fs.rmSync(outerDir, { recursive: true, force: true });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("与 pi 壳现装配循环产出等值(同序同名同字段)——多形态 fixture 对照", async () => {
|
|
105
|
+
// project-pi 源(workspaceRoot/.pi/agents)
|
|
106
|
+
writeAgent(
|
|
107
|
+
workspaceRoot,
|
|
108
|
+
".pi/agents/alpha.md",
|
|
109
|
+
`---
|
|
110
|
+
name: alpha
|
|
111
|
+
description: single-line agent
|
|
112
|
+
model: provider/model-a
|
|
113
|
+
---
|
|
114
|
+
alpha body`,
|
|
115
|
+
);
|
|
116
|
+
// block-scalar description + 多行 - item(zsw mini parser 全形态)
|
|
117
|
+
writeAgent(
|
|
118
|
+
workspaceRoot,
|
|
119
|
+
".pi/agents/beta.md",
|
|
120
|
+
`---
|
|
121
|
+
name: beta
|
|
122
|
+
description: |
|
|
123
|
+
beta line one
|
|
124
|
+
beta line two
|
|
125
|
+
tools:
|
|
126
|
+
- read
|
|
127
|
+
- bash
|
|
128
|
+
---
|
|
129
|
+
beta body`,
|
|
130
|
+
);
|
|
131
|
+
// 无 frontmatter(两边都不进清单、都不 warn)
|
|
132
|
+
writeAgent(workspaceRoot, ".pi/agents/README.md", "# docs\n");
|
|
133
|
+
// IF1 失败(缺 description,两边都不进清单、都 warn)
|
|
134
|
+
writeAgent(
|
|
135
|
+
workspaceRoot,
|
|
136
|
+
".pi/agents/broken.md",
|
|
137
|
+
`---
|
|
138
|
+
name: broken
|
|
139
|
+
model: provider/x
|
|
140
|
+
---
|
|
141
|
+
broken body`,
|
|
142
|
+
);
|
|
143
|
+
// hostRoots 注入源(序位高于 project-pi)——同名遮蔽 alpha + 新增 gamma
|
|
144
|
+
writeAgent(
|
|
145
|
+
hostRoot,
|
|
146
|
+
"alpha.md",
|
|
147
|
+
`---
|
|
148
|
+
name: alpha
|
|
149
|
+
description: host shadowing version
|
|
150
|
+
---
|
|
151
|
+
host alpha body`,
|
|
152
|
+
);
|
|
153
|
+
writeAgent(
|
|
154
|
+
hostRoot,
|
|
155
|
+
"gamma.md",
|
|
156
|
+
`---
|
|
157
|
+
name: gamma
|
|
158
|
+
description: inline array agent
|
|
159
|
+
tools: [read, grep]
|
|
160
|
+
maxTurns: 2
|
|
161
|
+
---
|
|
162
|
+
gamma body`,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const hostRoots: DiscoveryRoot[] = [{ dir: hostRoot, source: "project-host" }];
|
|
166
|
+
const [actual, oracle] = await Promise.all([
|
|
167
|
+
discoverAgents(workspaceRoot, hostRoots),
|
|
168
|
+
piAssemblyOracle(workspaceRoot, hostRoots),
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
// 同序同名同字段(逐条目投影对比,path 为绝对路径逐字节一致)
|
|
172
|
+
expect(actual).toEqual(oracle);
|
|
173
|
+
// 码点序:alpha < beta < gamma
|
|
174
|
+
expect(actual.map((a) => a.name)).toEqual(["alpha", "beta", "gamma"]);
|
|
175
|
+
// 同名遮蔽:alpha = hostRoots 胜出版(path + description 都是注入根的)
|
|
176
|
+
const alpha = actual.find((a) => a.name === "alpha");
|
|
177
|
+
expect(alpha?.path).toBe(path.join(hostRoot, "alpha.md"));
|
|
178
|
+
expect(alpha?.description).toBe("host shadowing version");
|
|
179
|
+
// 无 frontmatter / IF1 失败不进清单
|
|
180
|
+
expect(actual.some((a) => a.path.endsWith("README.md"))).toBe(false);
|
|
181
|
+
expect(actual.some((a) => a.path.endsWith("broken.md"))).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("hostRoots 为空时仍扫硬编码槽(project .pi/agents),等值口径不破", async () => {
|
|
185
|
+
writeAgent(
|
|
186
|
+
workspaceRoot,
|
|
187
|
+
".pi/agents/solo.md",
|
|
188
|
+
"---\nname: solo\ndescription: only one\n---\nbody",
|
|
189
|
+
);
|
|
190
|
+
const [actual, oracle] = await Promise.all([
|
|
191
|
+
discoverAgents(workspaceRoot, []),
|
|
192
|
+
piAssemblyOracle(workspaceRoot, []),
|
|
193
|
+
]);
|
|
194
|
+
expect(actual).toEqual(oracle);
|
|
195
|
+
expect(actual.map((a) => a.name)).toEqual(["solo"]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("IF1 失败(有 frontmatter)→ warn 口径内聚:仅该类文件 warn,README 不 warn", async () => {
|
|
199
|
+
writeAgent(
|
|
200
|
+
workspaceRoot,
|
|
201
|
+
".pi/agents/broken.md",
|
|
202
|
+
"---\nname: broken\nmodel: provider/x\n---\nbody",
|
|
203
|
+
);
|
|
204
|
+
writeAgent(workspaceRoot, ".pi/agents/README.md", "# plain doc\n");
|
|
205
|
+
|
|
206
|
+
const actual = await discoverAgents(workspaceRoot, []);
|
|
207
|
+
expect(actual).toEqual([]);
|
|
208
|
+
|
|
209
|
+
const warns = logCalls.filter((c) => c.level === "warn");
|
|
210
|
+
expect(warns).toHaveLength(1);
|
|
211
|
+
expect(warns[0]!.message).toContain("broken.md");
|
|
212
|
+
expect(warns[0]!.message).toContain("IF1 校验不通过");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("空目录 / 不存在的 workspaceRoot:空清单不抛", async () => {
|
|
216
|
+
const actual = await discoverAgents(path.join(outerDir, "nonexistent"), []);
|
|
217
|
+
expect(actual).toEqual([]);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -561,6 +561,77 @@ describe("chat 引擎分支 U2:probe 兜底 / journal / engineHandle", () => {
|
|
|
561
561
|
expect(h?.journalPath).toBe(resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId));
|
|
562
562
|
});
|
|
563
563
|
|
|
564
|
+
// ============================================================
|
|
565
|
+
// [R4 §3.4 不变量 3] onHandleReady 运行中回填:create 应答后立即落 entry——
|
|
566
|
+
// 运行中 GUI 经 entry 重建 record 即得 ①②级读取钥匙(不等 run resolve)
|
|
567
|
+
// ============================================================
|
|
568
|
+
|
|
569
|
+
it("[onHandleReady] create 应答后回调 → record.engineHandle 立即回填 + reportRecordTransition 落 entry(record 仍 running)", async () => {
|
|
570
|
+
process.env.XYZ_AGENT_DATA_DIR = agentDir;
|
|
571
|
+
const { service, zcode, pi } = setup(agentDir);
|
|
572
|
+
const POOL = "zcode-appserver-home";
|
|
573
|
+
let releaseRun!: (v: { handle: EngineHandle; outcome: AgentOutcome }) => void;
|
|
574
|
+
zcode.runImpl = (task, ctx) => {
|
|
575
|
+
ctx.onPoolResolved?.(POOL);
|
|
576
|
+
// create 应答后的回调时点(app-server 引擎在 session/create 应答后触发)
|
|
577
|
+
ctx.onHandleReady?.({
|
|
578
|
+
sessionRef: { dbPath: ".zcode/cli/db/db.sqlite", sessionId: "sess-live-1" },
|
|
579
|
+
poolKey: POOL,
|
|
580
|
+
});
|
|
581
|
+
return new Promise((resolve) => {
|
|
582
|
+
releaseRun = resolve; // 挂起 run——模拟运行中任务
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
|
|
586
|
+
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
587
|
+
|
|
588
|
+
// run 尚未 resolve(record 仍 running):engineHandle 已回填且 entry 已落盘
|
|
589
|
+
await vi.waitFor(() => {
|
|
590
|
+
const entries = pi.appendEntry.mock.calls.filter((c) => c[0] === "subagent-record");
|
|
591
|
+
const withHandle = entries.filter(
|
|
592
|
+
(c) => (c[1] as Record<string, unknown>).engineHandle !== undefined,
|
|
593
|
+
);
|
|
594
|
+
expect(withHandle.length).toBeGreaterThan(0);
|
|
595
|
+
});
|
|
596
|
+
const running = service.collectRecords(10, "running").find((r) => r.id === handle.subagentId);
|
|
597
|
+
expect(running?.engineHandle).toEqual({
|
|
598
|
+
sessionRef: { dbPath: ".zcode/cli/db/db.sqlite", sessionId: "sess-live-1" },
|
|
599
|
+
poolKey: POOL,
|
|
600
|
+
journalPath: resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId),
|
|
601
|
+
});
|
|
602
|
+
// 回填 entry 的 journalPath 与 onPoolResolved retarget 后的实际落盘路径一致(同源)
|
|
603
|
+
expect(fs.existsSync(resolveJournalPath(agentDir, "zcode", POOL, handle.subagentId))).toBe(false);
|
|
604
|
+
|
|
605
|
+
// 终态收口(防 dangling)
|
|
606
|
+
releaseRun({ handle: fakeHandle(), outcome: doneOutcome("ok") });
|
|
607
|
+
await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
|
|
608
|
+
}, 10_000);
|
|
609
|
+
|
|
610
|
+
it("[onHandleReady] 引擎不回调(spawn 形态)时零回填——终态回填仍兜底(行为不变)", async () => {
|
|
611
|
+
process.env.XYZ_AGENT_DATA_DIR = agentDir;
|
|
612
|
+
const { service, zcode, pi } = setup(agentDir);
|
|
613
|
+
const POOL = "zcode-p-glm";
|
|
614
|
+
zcode.runImpl = (task, ctx) => {
|
|
615
|
+
ctx.onPoolResolved?.(POOL); // 只 onPoolResolved——spawn 引擎形态(无 onHandleReady)
|
|
616
|
+
return Promise.resolve({
|
|
617
|
+
handle: {
|
|
618
|
+
data: { v: 1, engineId: "zcode", sessionRef: { dbPath: "sessions.db", sessionId: "sess-1" }, poolKey: POOL, adapterVersion: "test" },
|
|
619
|
+
},
|
|
620
|
+
outcome: doneOutcome("ok"),
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
const handle = await service.execute(baseOpts(agentDir, { engine: "zcode" }));
|
|
624
|
+
await vi.waitFor(() => expect(service.findRecord(handle.subagentId)).toBeUndefined());
|
|
625
|
+
const entries = pi.appendEntry.mock.calls.filter((c) => c[0] === "subagent-record");
|
|
626
|
+
// 运行中回填 entry 不存在(register 写点无 engineHandle;archive 终态侧才有)
|
|
627
|
+
const runningEntries = entries.slice(0, -1);
|
|
628
|
+
for (const c of runningEntries) {
|
|
629
|
+
expect((c[1] as Record<string, unknown>).engineHandle).toBeUndefined();
|
|
630
|
+
}
|
|
631
|
+
const final = entries[entries.length - 1][1] as Record<string, unknown>;
|
|
632
|
+
expect(final.engineHandle).toMatchObject({ poolKey: POOL });
|
|
633
|
+
});
|
|
634
|
+
|
|
564
635
|
it("[D5 回归] pi 纯缺省路径 entry 不含 engine/engineFallback/engineHandle 键", async () => {
|
|
565
636
|
const { service, pi } = setup(agentDir);
|
|
566
637
|
const handle = await service.execute(baseOpts(agentDir));
|
|
@@ -58,6 +58,7 @@ vi.mock("../alive-store.ts", () => ({ writeAliveMarker: vi.fn() }));
|
|
|
58
58
|
// chatMode agent_end 早返回(continue),不读 session-pending;统一 count=0 防干扰。
|
|
59
59
|
vi.mock("../session-pending.ts", () => ({
|
|
60
60
|
readActivePendingFromSessionFile: vi.fn(() => ({ count: 0 })),
|
|
61
|
+
prunePendingCursor: vi.fn(),
|
|
61
62
|
}));
|
|
62
63
|
|
|
63
64
|
vi.mock("../temp-prompt.ts", () => ({
|