@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
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// src/shared/__tests__/injection-render.test.ts
|
|
2
|
+
//
|
|
3
|
+
// C3-core-render(convergence W3)单元测试:注入渲染面下沉的行为锁定。
|
|
4
|
+
// 覆盖 §4.2 W3 门全部条目:
|
|
5
|
+
// - 与 pi 现输出逐字节等价(同一组入参,core 输出 === 按 pi-sw injector 现函数
|
|
6
|
+
// 模板手工展开的期望串——期望串在本测试内构造,不 import pi-sw);
|
|
7
|
+
// - ModelEntry 守卫(红线 5):undefined input 不抛且 caps 无 vision /
|
|
8
|
+
// contextWindow undefined 不渲染该元素且无 "undefined" 字样;
|
|
9
|
+
// - provider 口径:存在时 provider/id 拼接 + (provider, id) 两段码点序、
|
|
10
|
+
// 缺席/空串时裸 id + id 码点序、混合形态归一口径;
|
|
11
|
+
// - 分段条目预算(红线 7):码点序 + 截尾 + 兜底指引行、预算边界(恰好 15/10
|
|
12
|
+
// 不截)、先排后截(乱序输入)、models 段无预算完整渲染;
|
|
13
|
+
// - guide 宿主注入(渲染源码无内嵌平台文案);
|
|
14
|
+
// - summarizeDescription / sortByCodepoint / barrel 逐名探针。
|
|
15
|
+
// 设计权威源:docs/design/subagent-core-convergence.md §3.2 D-3 / §3.3 红线 5、7。
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
import { describe, expect, it } from "vitest";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
formatAgentList,
|
|
24
|
+
formatModelList,
|
|
25
|
+
formatWorkflowList,
|
|
26
|
+
sortByCodepoint,
|
|
27
|
+
summarizeDescription,
|
|
28
|
+
type AgentEntry,
|
|
29
|
+
type ListFormatOptions,
|
|
30
|
+
type ModelEntry,
|
|
31
|
+
type WorkflowEntry,
|
|
32
|
+
} from "../injection-render.ts";
|
|
33
|
+
import type {
|
|
34
|
+
AgentEntry as BarrelAgentEntry,
|
|
35
|
+
ModelEntry as BarrelModelEntry,
|
|
36
|
+
WorkflowEntry as BarrelWorkflowEntry,
|
|
37
|
+
} from "../../index.ts";
|
|
38
|
+
import * as subagentCore from "../../index.ts";
|
|
39
|
+
|
|
40
|
+
// ============================================================
|
|
41
|
+
// fixtures
|
|
42
|
+
// ============================================================
|
|
43
|
+
|
|
44
|
+
const AGENT_GUIDE = "AGENT-GUIDE";
|
|
45
|
+
const WORKFLOW_GUIDE = "WORKFLOW-GUIDE";
|
|
46
|
+
const MODEL_GUIDE = "MODEL-GUIDE";
|
|
47
|
+
|
|
48
|
+
const agentOpts: ListFormatOptions = { guide: AGENT_GUIDE };
|
|
49
|
+
const workflowOpts: ListFormatOptions = { guide: WORKFLOW_GUIDE };
|
|
50
|
+
|
|
51
|
+
/** pi 现调用形态的 agent 数据(discoverAllAgents 输出投影:已按 name 码点序) */
|
|
52
|
+
const piShapedAgents: AgentEntry[] = [
|
|
53
|
+
{
|
|
54
|
+
name: "coder",
|
|
55
|
+
description: 'writes <code> & "quotes"',
|
|
56
|
+
path: "/abs/coder.md",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "reviewer",
|
|
60
|
+
description: "reviews stuff",
|
|
61
|
+
when: "after code changes",
|
|
62
|
+
examples: [
|
|
63
|
+
{ match: "review my diff", action: "delegate to reviewer", positive: true },
|
|
64
|
+
{ match: "write new code", action: "不调用(职责不符)", positive: false },
|
|
65
|
+
],
|
|
66
|
+
path: "/abs/reviewer.md",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const piShapedWorkflows: WorkflowEntry[] = [
|
|
71
|
+
{
|
|
72
|
+
name: "review-fix-loop",
|
|
73
|
+
description: "8-dim review & fix loop",
|
|
74
|
+
path: "/abs/review-fix-loop.js",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** pi 现调用形态的 model 数据(toModelEntry 投影:全字段给齐且 provider 非空) */
|
|
79
|
+
const piShapedModels: ModelEntry[] = [
|
|
80
|
+
{
|
|
81
|
+
provider: "xai",
|
|
82
|
+
id: "grok",
|
|
83
|
+
name: 'Fast & "cheap"',
|
|
84
|
+
reasoning: false,
|
|
85
|
+
input: [],
|
|
86
|
+
contextWindow: 32000,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
provider: "zai-coding-cn",
|
|
90
|
+
id: "glm-5.3",
|
|
91
|
+
name: "GLM 5.3",
|
|
92
|
+
reasoning: true,
|
|
93
|
+
input: ["text", "image"],
|
|
94
|
+
contextWindow: 128000,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
provider: "zai-coding-cn",
|
|
98
|
+
id: "glm-5.3-flash",
|
|
99
|
+
name: "GLM 5.3 Flash",
|
|
100
|
+
reasoning: false,
|
|
101
|
+
input: ["text"],
|
|
102
|
+
contextWindow: 64000,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
/** 批量条目工厂:n01..nNN(同长零填充,码点字典序 = 数值序) */
|
|
107
|
+
function genAgents(count: number): AgentEntry[] {
|
|
108
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
109
|
+
name: `n${String(i + 1).padStart(2, "0")}`,
|
|
110
|
+
description: `agent ${i + 1}`,
|
|
111
|
+
path: `/abs/a${i + 1}.md`,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function genWorkflows(count: number): WorkflowEntry[] {
|
|
116
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
117
|
+
name: `w${String(i + 1).padStart(2, "0")}`,
|
|
118
|
+
description: `workflow ${i + 1}`,
|
|
119
|
+
path: `/abs/w${i + 1}.js`,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function genModels(count: number): ModelEntry[] {
|
|
124
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
125
|
+
provider: "p",
|
|
126
|
+
id: `m${String(i + 1).padStart(2, "0")}`,
|
|
127
|
+
name: `model ${i + 1}`,
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// 与 pi 现输出逐字节等价(pi 现调用形态:数据已排、字段给齐)
|
|
133
|
+
// 期望串按 pi-sw injector 现函数模板手工展开(不 import pi-sw)
|
|
134
|
+
// ============================================================
|
|
135
|
+
|
|
136
|
+
describe("byte-exact parity with pi-sw current formatters", () => {
|
|
137
|
+
it("formatAgentList 输出与 pi 现实现逐字节等价(含 when/examples 分支与 XML 转义)", () => {
|
|
138
|
+
const expected =
|
|
139
|
+
"\n\n<available_subagents>\n" +
|
|
140
|
+
"AGENT-GUIDE\n" +
|
|
141
|
+
' <agent><name>coder</name><description>writes <code> & "quotes"</description><location>/abs/coder.md</location></agent>\n' +
|
|
142
|
+
' <agent><name>reviewer</name><description>reviews stuff</description><when>after code changes</when>\n' +
|
|
143
|
+
" <examples>\n" +
|
|
144
|
+
' - "review my diff" → delegate to reviewer\n' +
|
|
145
|
+
' - "write new code" → 不调用(职责不符)\n' +
|
|
146
|
+
" </examples>" +
|
|
147
|
+
"<location>/abs/reviewer.md</location></agent>\n" +
|
|
148
|
+
"</available_subagents>";
|
|
149
|
+
expect(formatAgentList(piShapedAgents, agentOpts)).toBe(expected);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("formatWorkflowList 输出与 pi 现实现逐字节等价(含 XML 转义)", () => {
|
|
153
|
+
const expected =
|
|
154
|
+
"\n\n<available_workflows>\n" +
|
|
155
|
+
"WORKFLOW-GUIDE\n" +
|
|
156
|
+
' <workflow><name>review-fix-loop</name><description>8-dim review & fix loop</description><location>/abs/review-fix-loop.js</location></workflow>\n' +
|
|
157
|
+
"</available_workflows>";
|
|
158
|
+
expect(formatWorkflowList(piShapedWorkflows, workflowOpts)).toBe(expected);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("formatModelList 输出与 pi 现实现逐字节等价((provider, id) 排序 + caps 有无 + 转义)", () => {
|
|
162
|
+
const expected =
|
|
163
|
+
"\n\n<available_provider_models>\n" +
|
|
164
|
+
"MODEL-GUIDE\n" +
|
|
165
|
+
' <model><id>xai/grok</id><name>Fast & "cheap"</name><contextWindow>32000</contextWindow></model>\n' +
|
|
166
|
+
' <model><id>zai-coding-cn/glm-5.3</id><name>GLM 5.3</name><caps>reasoning,vision</caps><contextWindow>128000</contextWindow></model>\n' +
|
|
167
|
+
' <model><id>zai-coding-cn/glm-5.3-flash</id><name>GLM 5.3 Flash</name><contextWindow>64000</contextWindow></model>\n' +
|
|
168
|
+
"</available_provider_models>";
|
|
169
|
+
expect(formatModelList(piShapedModels, { guide: MODEL_GUIDE })).toBe(expected);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("空列表三函数均返回空串(不注入)", () => {
|
|
173
|
+
expect(formatAgentList([], agentOpts)).toBe("");
|
|
174
|
+
expect(formatWorkflowList([], workflowOpts)).toBe("");
|
|
175
|
+
expect(formatModelList([], { guide: MODEL_GUIDE })).toBe("");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ============================================================
|
|
180
|
+
// ModelEntry 并集守卫(红线 5:除 id/name 外全字段 optional)
|
|
181
|
+
// ============================================================
|
|
182
|
+
|
|
183
|
+
describe("ModelEntry union guards (red-line 5)", () => {
|
|
184
|
+
it("input undefined 不抛且 caps 无 vision(reasoning 对象形态按 truthy → caps 含 reasoning)", () => {
|
|
185
|
+
const entry: ModelEntry = {
|
|
186
|
+
id: "glm",
|
|
187
|
+
name: "GLM",
|
|
188
|
+
reasoning: { variants: ["high", "medium"], defaultVariant: "high" },
|
|
189
|
+
// input 缺席——zsw 投影形态;pi 版此处抛 TypeError
|
|
190
|
+
};
|
|
191
|
+
const out = formatModelList([entry], { guide: MODEL_GUIDE });
|
|
192
|
+
expect(out).toContain("<caps>reasoning</caps>");
|
|
193
|
+
expect(out).not.toContain("vision");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("reasoning 空对象形态(无 variants)仍按 truthy 处理 → caps 含 reasoning", () => {
|
|
197
|
+
const entry: ModelEntry = { id: "glm", name: "GLM", reasoning: {} };
|
|
198
|
+
expect(formatModelList([entry], { guide: MODEL_GUIDE })).toContain(
|
|
199
|
+
"<caps>reasoning</caps>",
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("input 空数组:无 vision;reasoning false:无 reasoning → 无 caps 段", () => {
|
|
204
|
+
const entry: ModelEntry = {
|
|
205
|
+
id: "glm",
|
|
206
|
+
name: "GLM",
|
|
207
|
+
reasoning: false,
|
|
208
|
+
input: [],
|
|
209
|
+
};
|
|
210
|
+
const out = formatModelList([entry], { guide: MODEL_GUIDE });
|
|
211
|
+
expect(out).not.toContain("<caps>");
|
|
212
|
+
expect(out).not.toContain("vision");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("contextWindow undefined:无 <contextWindow> 元素且输出无 'undefined' 字样", () => {
|
|
216
|
+
const entry: ModelEntry = { id: "glm", name: "GLM" };
|
|
217
|
+
const out = formatModelList([entry], { guide: MODEL_GUIDE });
|
|
218
|
+
expect(out).not.toContain("<contextWindow>");
|
|
219
|
+
expect(out).not.toContain("undefined");
|
|
220
|
+
expect(out).toContain("</model>");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("contextWindow 为 0 是有效值:渲染 <contextWindow>0</contextWindow>(守卫只挡 undefined)", () => {
|
|
224
|
+
const entry: ModelEntry = { id: "glm", name: "GLM", contextWindow: 0 };
|
|
225
|
+
expect(formatModelList([entry], { guide: MODEL_GUIDE })).toContain(
|
|
226
|
+
"<contextWindow>0</contextWindow>",
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("label 进类型并集但渲染面不消费(输出无 label 内容)", () => {
|
|
231
|
+
const entry: ModelEntry = { id: "glm", name: "GLM", label: "secret-label" };
|
|
232
|
+
const out = formatModelList([entry], { guide: MODEL_GUIDE });
|
|
233
|
+
expect(out).not.toContain("secret-label");
|
|
234
|
+
expect(out).not.toContain("label");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ============================================================
|
|
239
|
+
// provider 口径(本仓补充:并集下 provider 可选)
|
|
240
|
+
// ============================================================
|
|
241
|
+
|
|
242
|
+
describe("provider presence semantics", () => {
|
|
243
|
+
it("provider 存在:<id> 渲染 provider/id 拼接,排序为 (provider, id) 两段码点序", () => {
|
|
244
|
+
// 乱序输入:pi 版 comparator = provider 相同比 id、不同比 provider
|
|
245
|
+
const models: ModelEntry[] = [
|
|
246
|
+
{ provider: "b-provider", id: "a-id", name: "B-A" },
|
|
247
|
+
{ provider: "a-provider", id: "z-id", name: "A-Z" },
|
|
248
|
+
{ provider: "a-provider", id: "b-id", name: "A-B" },
|
|
249
|
+
];
|
|
250
|
+
const out = formatModelList(models, { guide: MODEL_GUIDE });
|
|
251
|
+
const ids = [...out.matchAll(/<id>([^<]+)<\/id>/g)].map((m) => m[1]);
|
|
252
|
+
expect(ids).toEqual([
|
|
253
|
+
"a-provider/b-id",
|
|
254
|
+
"a-provider/z-id",
|
|
255
|
+
"b-provider/a-id",
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("provider 缺席:id 裸渲染 + 排序退化为 id 码点序(zsw 投影形态)", () => {
|
|
260
|
+
const models: ModelEntry[] = [
|
|
261
|
+
{ id: "z-model", name: "Z" },
|
|
262
|
+
{ id: "a-model", name: "A" },
|
|
263
|
+
];
|
|
264
|
+
const out = formatModelList(models, { guide: MODEL_GUIDE });
|
|
265
|
+
const ids = [...out.matchAll(/<id>([^<]+)<\/id>/g)].map((m) => m[1]);
|
|
266
|
+
expect(ids).toEqual(["a-model", "z-model"]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("provider 空串与缺席等价:裸 id 渲染", () => {
|
|
270
|
+
const models: ModelEntry[] = [
|
|
271
|
+
{ provider: "", id: "x-model", name: "X" },
|
|
272
|
+
];
|
|
273
|
+
const out = formatModelList(models, { guide: MODEL_GUIDE });
|
|
274
|
+
expect(out).toContain("<id>x-model</id>");
|
|
275
|
+
expect(out).not.toContain("/x-model");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("混合形态(部分有 provider):无 provider 条目归一空串排最前,口径确定", () => {
|
|
279
|
+
const models: ModelEntry[] = [
|
|
280
|
+
{ provider: "b", id: "x", name: "BX" },
|
|
281
|
+
{ id: "a", name: "bare-A" },
|
|
282
|
+
{ provider: "a", id: "z", name: "AZ" },
|
|
283
|
+
{ provider: "", id: "c", name: "bare-C" },
|
|
284
|
+
];
|
|
285
|
+
const ids = [
|
|
286
|
+
...formatModelList(models, { guide: MODEL_GUIDE })
|
|
287
|
+
.matchAll(/<id>([^<]+)<\/id>/g),
|
|
288
|
+
].map((m) => m[1]);
|
|
289
|
+
expect(ids).toEqual(["a", "c", "a/z", "b/x"]);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ============================================================
|
|
294
|
+
// 分段条目预算(红线 7:码点序 + 截尾 + 兜底指引;内置无豁免;models 永不截)
|
|
295
|
+
// ============================================================
|
|
296
|
+
|
|
297
|
+
describe("per-section entry budget (red-line 7)", () => {
|
|
298
|
+
const NOTICE = "TRUNCATED: run host list command for full inventory";
|
|
299
|
+
|
|
300
|
+
it("agents 超预算:按 name 码点序截尾(保留靠前),兜底指引行出现在段末", () => {
|
|
301
|
+
const out = formatAgentList(genAgents(20), {
|
|
302
|
+
guide: AGENT_GUIDE,
|
|
303
|
+
maxEntries: 15,
|
|
304
|
+
truncationNotice: NOTICE,
|
|
305
|
+
});
|
|
306
|
+
for (let i = 1; i <= 15; i++) {
|
|
307
|
+
expect(out).toContain(`n${String(i).padStart(2, "0")}`);
|
|
308
|
+
}
|
|
309
|
+
for (let i = 16; i <= 20; i++) {
|
|
310
|
+
expect(out).not.toContain(`n${String(i).padStart(2, "0")}`);
|
|
311
|
+
}
|
|
312
|
+
// 兜底指引是段内最后一行(闭合标签前)
|
|
313
|
+
const noticeIdx = out.indexOf(NOTICE);
|
|
314
|
+
const closeIdx = out.lastIndexOf("</available_subagents>");
|
|
315
|
+
expect(noticeIdx).toBeGreaterThan(0);
|
|
316
|
+
expect(noticeIdx).toBeLessThan(closeIdx);
|
|
317
|
+
expect(out.slice(noticeIdx + NOTICE.length, closeIdx)).toBe("\n");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("乱序输入先排后截:截掉的是码点序尾部而非传入序尾部", () => {
|
|
321
|
+
const reversed = genAgents(20).reverse();
|
|
322
|
+
const out = formatAgentList(reversed, {
|
|
323
|
+
guide: AGENT_GUIDE,
|
|
324
|
+
maxEntries: 15,
|
|
325
|
+
});
|
|
326
|
+
expect(out).toContain("<name>n01</name>");
|
|
327
|
+
expect(out).toContain("<name>n15</name>");
|
|
328
|
+
expect(out).not.toContain("<name>n16</name>");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("预算边界:恰好 15 个 agent 传 15 不截、无兜底指引", () => {
|
|
332
|
+
const out = formatAgentList(genAgents(15), {
|
|
333
|
+
guide: AGENT_GUIDE,
|
|
334
|
+
maxEntries: 15,
|
|
335
|
+
truncationNotice: NOTICE,
|
|
336
|
+
});
|
|
337
|
+
expect(out).toContain("<name>n15</name>");
|
|
338
|
+
expect(out).not.toContain(NOTICE);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("workflows 预算 10:11 条截 10,边界恰好 10 不截", () => {
|
|
342
|
+
const truncated = formatWorkflowList(genWorkflows(11), {
|
|
343
|
+
guide: WORKFLOW_GUIDE,
|
|
344
|
+
maxEntries: 10,
|
|
345
|
+
truncationNotice: NOTICE,
|
|
346
|
+
});
|
|
347
|
+
expect(truncated).toContain("<name>w10</name>");
|
|
348
|
+
expect(truncated).not.toContain("<name>w11</name>");
|
|
349
|
+
expect(truncated).toContain(NOTICE);
|
|
350
|
+
|
|
351
|
+
const exact = formatWorkflowList(genWorkflows(10), {
|
|
352
|
+
guide: WORKFLOW_GUIDE,
|
|
353
|
+
maxEntries: 10,
|
|
354
|
+
truncationNotice: NOTICE,
|
|
355
|
+
});
|
|
356
|
+
expect(exact).toContain("<name>w10</name>");
|
|
357
|
+
expect(exact).not.toContain(NOTICE);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("未传 maxEntries 默认全量(pi 现行为)", () => {
|
|
361
|
+
const out = formatAgentList(genAgents(20), agentOpts);
|
|
362
|
+
expect(out).toContain("<name>n20</name>");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("截断但未提供 truncationNotice:不追加任何行", () => {
|
|
366
|
+
const out = formatAgentList(genAgents(20), { guide: AGENT_GUIDE, maxEntries: 15 });
|
|
367
|
+
expect(out).toContain("<name>n15</name>");
|
|
368
|
+
expect(out).not.toContain("<name>n16</name>");
|
|
369
|
+
expect(out.endsWith("</available_subagents>")).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("models 段无预算参数:25 条完整渲染永不截", () => {
|
|
373
|
+
const out = formatModelList(genModels(25), { guide: MODEL_GUIDE });
|
|
374
|
+
expect(out).toContain("<id>p/m01</id>");
|
|
375
|
+
expect(out).toContain("<id>p/m25</id>");
|
|
376
|
+
expect((out.match(/<model>/g) ?? []).length).toBe(25);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// ============================================================
|
|
381
|
+
// 码点序契约(禁 localeCompare——跨环境字节一致)
|
|
382
|
+
// ============================================================
|
|
383
|
+
|
|
384
|
+
describe("codepoint ordering", () => {
|
|
385
|
+
it("formatAgentList 按 name 码点序(大写码点小于小写:A < a < b)", () => {
|
|
386
|
+
const agents: AgentEntry[] = [
|
|
387
|
+
{ name: "b", description: "d", path: "/b.md" },
|
|
388
|
+
{ name: "A", description: "d", path: "/A.md" },
|
|
389
|
+
{ name: "a", description: "d", path: "/a.md" },
|
|
390
|
+
];
|
|
391
|
+
const names = [
|
|
392
|
+
...formatAgentList(agents, agentOpts).matchAll(/<name>([^<]+)<\/name>/g),
|
|
393
|
+
].map((m) => m[1]);
|
|
394
|
+
expect(names).toEqual(["A", "a", "b"]);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("sortByCodepoint 非变异:入参数组不动,返回排序副本", () => {
|
|
398
|
+
const input = ["b", "A", "a"];
|
|
399
|
+
const sorted = sortByCodepoint(input, (s) => s);
|
|
400
|
+
expect(sorted).toEqual(["A", "a", "b"]);
|
|
401
|
+
expect(input).toEqual(["b", "A", "a"]);
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// ============================================================
|
|
406
|
+
// guide 宿主注入(core 渲染源码无内嵌平台文案)
|
|
407
|
+
// ============================================================
|
|
408
|
+
|
|
409
|
+
describe("guide host injection", () => {
|
|
410
|
+
it("自定义 guide 原样出现在段首第二行", () => {
|
|
411
|
+
const out = formatAgentList(
|
|
412
|
+
[{ name: "x", description: "d", path: "/x.md" }],
|
|
413
|
+
{ guide: "host-provided-guide" },
|
|
414
|
+
);
|
|
415
|
+
// 输出以 "\n\n<available_subagents>" 开头:split 后 [0]="" [1]="" [2]=tag [3]=guide
|
|
416
|
+
expect(out.split("\n")[3]).toBe("host-provided-guide");
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("渲染源码无内嵌英文平台 guide 文案(pi guide 特征片段 grep 全 0)", () => {
|
|
420
|
+
const srcDir = path.dirname(fileURLToPath(import.meta.url));
|
|
421
|
+
const renderSource = fs.readFileSync(
|
|
422
|
+
path.join(srcDir, "../injection-render.ts"),
|
|
423
|
+
"utf8",
|
|
424
|
+
);
|
|
425
|
+
const xmlInjectionSource = fs.readFileSync(
|
|
426
|
+
path.join(srcDir, "../xml-injection.ts"),
|
|
427
|
+
"utf8",
|
|
428
|
+
);
|
|
429
|
+
// 三个 pi guide 的特征片段 + 共同开头 "The following "
|
|
430
|
+
const piGuideFragments = [
|
|
431
|
+
"The following ",
|
|
432
|
+
"pass systemPrompt alongside",
|
|
433
|
+
"Do NOT call list",
|
|
434
|
+
"use list only for running state",
|
|
435
|
+
"PRIORITY: when a task involves",
|
|
436
|
+
"run directly via action:run",
|
|
437
|
+
"Do NOT switch the main conversation model",
|
|
438
|
+
"/model command",
|
|
439
|
+
];
|
|
440
|
+
for (const fragment of piGuideFragments) {
|
|
441
|
+
expect(renderSource.includes(fragment), `injection-render.ts 内嵌: ${fragment}`).toBe(false);
|
|
442
|
+
expect(xmlInjectionSource.includes(fragment), `xml-injection.ts 内嵌: ${fragment}`).toBe(false);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// ============================================================
|
|
448
|
+
// summarizeDescription(随 WorkflowEntry 链下沉,zsw 同口径)
|
|
449
|
+
// ============================================================
|
|
450
|
+
|
|
451
|
+
describe("summarizeDescription", () => {
|
|
452
|
+
it("短文本 trim 后原样返回", () => {
|
|
453
|
+
expect(summarizeDescription(" hello world ")).toBe("hello world");
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("恰好 maxLen 原样(不截不加省略号)", () => {
|
|
457
|
+
const text = "a".repeat(160);
|
|
458
|
+
expect(summarizeDescription(text)).toBe(text);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it("超长无断点:硬截断 + 省略号", () => {
|
|
462
|
+
const text = "a".repeat(200);
|
|
463
|
+
expect(summarizeDescription(text)).toBe(`${"a".repeat(160)}…`);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("句号断句:maxLen 内最后的句末标点处断(含标点)", () => {
|
|
467
|
+
// maxLen=10:slice 内位置 9 的句号 >= 10*0.4=4,采用
|
|
468
|
+
expect(summarizeDescription("abc def g。xyz tail", 10)).toBe("abc def g。");
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("英文句点断句:'. ' 位置 >= 阈值时采用(slice(0, boundary+1),含点不含尾随空格)", () => {
|
|
472
|
+
// maxLen=10:'. ' 的 "." 在位置 5 >= 4,采用 → slice(0, 6) = "hello."
|
|
473
|
+
expect(summarizeDescription("hello. worldxxxx", 10)).toBe("hello.");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it("断点过靠前不采用:硬截断 + 省略号", () => {
|
|
477
|
+
// maxLen=20,阈值 8;位置 2 的句号 < 8,不采用
|
|
478
|
+
expect(summarizeDescription("ab。cdefghijklmnopqrstuv", 20)).toBe(
|
|
479
|
+
"ab。cdefghijklmnopqrs…",
|
|
480
|
+
);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// ============================================================
|
|
485
|
+
// barrel 逐名探针(红线 9:新增导出必须进 barrel,逐名列出)
|
|
486
|
+
// ============================================================
|
|
487
|
+
|
|
488
|
+
describe("barrel exports probe", () => {
|
|
489
|
+
it("值导出逐名可达(typeof function)", () => {
|
|
490
|
+
const valueExports = [
|
|
491
|
+
"formatAgentList",
|
|
492
|
+
"formatWorkflowList",
|
|
493
|
+
"formatModelList",
|
|
494
|
+
"sortByCodepoint",
|
|
495
|
+
"summarizeDescription",
|
|
496
|
+
"escapeXml",
|
|
497
|
+
"renderXmlSection",
|
|
498
|
+
"discoverResources",
|
|
499
|
+
"findWorkspaceRoot",
|
|
500
|
+
"getCachedFileContent",
|
|
501
|
+
"getCachedParsed",
|
|
502
|
+
"parseResourceMeta",
|
|
503
|
+
] as const;
|
|
504
|
+
const barrel = subagentCore as Record<string, unknown>;
|
|
505
|
+
for (const name of valueExports) {
|
|
506
|
+
expect(typeof barrel[name], `barrel 缺值导出: ${name}`).toBe("function");
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("类型导出经 barrel 可引用(typecheck 即探针)", () => {
|
|
511
|
+
const agent: BarrelAgentEntry = { name: "n", description: "d", path: "/p" };
|
|
512
|
+
const workflow: BarrelWorkflowEntry = { name: "n", description: "d", path: "/p" };
|
|
513
|
+
const model: BarrelModelEntry = { id: "i", name: "n" };
|
|
514
|
+
expect(agent.name).toBe("n");
|
|
515
|
+
expect(workflow.name).toBe("n");
|
|
516
|
+
expect(model.id).toBe("i");
|
|
517
|
+
});
|
|
518
|
+
});
|