@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,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [OR-2] rebuildRuntime 抛错回灌重试矩阵 + [P-SD] 重建失败注入钩子 测试。
|
|
3
|
+
*
|
|
4
|
+
* 覆盖(unbounded-wait-audit §4.1 OR-2 / §7.3 P-SD / §8.2 S-D 子场景②):
|
|
5
|
+
* - worker 崩溃 → rebuild 抛错(workerHost.start 失败)→ workerErrorCount 递增回灌矩阵
|
|
6
|
+
* → 后续 rebuild 成功 → run 恢复 running(不卡死)
|
|
7
|
+
* - rebuild 连续失败耗尽 MAX_WORKER_RETRIES → 收敛 done,failed(不卡 running)、
|
|
8
|
+
* 持久化、pending:unregister、onRunDone——且 handleWorkerError 的 await 链无
|
|
9
|
+
* rejection 泄漏(旧实现裸抛 → unhandledRejection)
|
|
10
|
+
* - P-SD 钩子:XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE=<N> 使 rebuildRuntime 第 N 次
|
|
11
|
+
* 起抛错 → 重试矩阵确定性耗尽 → done,failed;激活 warn 留痕恰一次(安全约束);
|
|
12
|
+
* 未设置 = 完全不激活;非法值 = warn 指明 + 不激活(杜绝静默失效)
|
|
13
|
+
*/
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
getLogger,
|
|
18
|
+
} from "../../core/logger.ts";
|
|
19
|
+
import {
|
|
20
|
+
handleWorkerError,
|
|
21
|
+
resetRebuildFailureInjectionForTest,
|
|
22
|
+
} from "../error-recovery.ts";
|
|
23
|
+
import { Budget } from "../models/budget.ts";
|
|
24
|
+
import { RunRuntime } from "../models/run-runtime.ts";
|
|
25
|
+
import { Trace } from "../models/trace.ts";
|
|
26
|
+
import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
|
|
27
|
+
import type { AgentResult } from "../models/types.ts";
|
|
28
|
+
import { WorkflowRun } from "../models/workflow-run.ts";
|
|
29
|
+
import type { WorkerHandle } from "../worker-handle.ts";
|
|
30
|
+
|
|
31
|
+
// ── helpers(对齐 error-recovery-handlers.test.ts 的真实聚合根形态) ──
|
|
32
|
+
|
|
33
|
+
const REBUILD_INJECT_ENV = "XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE";
|
|
34
|
+
|
|
35
|
+
function makeRealRun(runId: string, opts: { budgetTimeMs?: number } = {}): WorkflowRun {
|
|
36
|
+
const run = new WorkflowRun(
|
|
37
|
+
runId,
|
|
38
|
+
{
|
|
39
|
+
scriptName: "test-wf",
|
|
40
|
+
scriptSource: "agent('hi')",
|
|
41
|
+
args: {},
|
|
42
|
+
scriptPath: "/tmp/test-wf.js",
|
|
43
|
+
budgetTimeMs: opts.budgetTimeMs,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
status: "running",
|
|
47
|
+
budget: new Budget(),
|
|
48
|
+
calls: new Map(),
|
|
49
|
+
trace: new Trace(),
|
|
50
|
+
errorLogs: [],
|
|
51
|
+
},
|
|
52
|
+
{ startedAt: new Date().toISOString() },
|
|
53
|
+
);
|
|
54
|
+
const initialWorker = {
|
|
55
|
+
postMessage: vi.fn(),
|
|
56
|
+
terminate: vi.fn(async () => {}),
|
|
57
|
+
} as unknown as WorkerHandle;
|
|
58
|
+
run.assignRuntime(new RunRuntime(initialWorker, new AbortController()));
|
|
59
|
+
return run;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function makeDeps(opts: {
|
|
63
|
+
/** workerHost.start 每次调用抛错(模拟线程/内存耗尽等重建失败)。 */
|
|
64
|
+
startThrows?: boolean;
|
|
65
|
+
} = {}): LifecycleDeps & {
|
|
66
|
+
store: { save: ReturnType<typeof vi.fn> };
|
|
67
|
+
workerHost: { start: ReturnType<typeof vi.fn> };
|
|
68
|
+
eventBus: { emit: ReturnType<typeof vi.fn> };
|
|
69
|
+
onRunDone: ReturnType<typeof vi.fn>;
|
|
70
|
+
log: ReturnType<typeof vi.fn>;
|
|
71
|
+
} {
|
|
72
|
+
const startImpl = opts.startThrows
|
|
73
|
+
? (): never => {
|
|
74
|
+
throw new Error("Resource temporarily unavailable (injected start failure)");
|
|
75
|
+
}
|
|
76
|
+
: (): WorkerHandle =>
|
|
77
|
+
({ postMessage: vi.fn(), terminate: vi.fn(async () => {}) }) as unknown as WorkerHandle;
|
|
78
|
+
return {
|
|
79
|
+
store: { save: vi.fn(async () => {}) },
|
|
80
|
+
workerHost: { start: vi.fn(startImpl) },
|
|
81
|
+
runner: { run: vi.fn(async () => ({}) as AgentResult) },
|
|
82
|
+
runs: new Map(),
|
|
83
|
+
eventBus: { emit: vi.fn() },
|
|
84
|
+
onRunDone: vi.fn(),
|
|
85
|
+
log: vi.fn(),
|
|
86
|
+
} as unknown as ReturnType<typeof makeDeps>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function makeHandlers(): WorkerHandlers {
|
|
90
|
+
return {
|
|
91
|
+
onMessage: vi.fn(async () => {}),
|
|
92
|
+
onError: vi.fn(async () => {}),
|
|
93
|
+
onExit: vi.fn(async () => {}),
|
|
94
|
+
} as unknown as WorkerHandlers;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** fake timers 下推进 N 毫秒并让 await delay(...) 链跑完。 */
|
|
98
|
+
async function advance(ms: number): Promise<void> {
|
|
99
|
+
await vi.advanceTimersByTimeAsync(ms);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
vi.useFakeTimers();
|
|
104
|
+
delete process.env[REBUILD_INJECT_ENV];
|
|
105
|
+
resetRebuildFailureInjectionForTest();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
afterEach(() => {
|
|
109
|
+
vi.useRealTimers();
|
|
110
|
+
delete process.env[REBUILD_INJECT_ENV];
|
|
111
|
+
resetRebuildFailureInjectionForTest();
|
|
112
|
+
vi.restoreAllMocks();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── [OR-2] rebuild 抛错回灌重试矩阵 ──────────────────────────
|
|
116
|
+
|
|
117
|
+
describe("[OR-2] rebuildRuntime 抛错回灌重试矩阵", () => {
|
|
118
|
+
it("rebuild 抛错 → workerErrorCount 递增回灌 → 下一次 rebuild 成功 → run 恢复(不卡 running)", async () => {
|
|
119
|
+
const run = makeRealRun("wf-rebuild-1");
|
|
120
|
+
const deps = makeDeps();
|
|
121
|
+
// 第 1 次 start(rebuild #1)抛错,之后成功——模拟瞬时线程耗尽
|
|
122
|
+
let calls = 0;
|
|
123
|
+
deps.workerHost.start.mockImplementation(() => {
|
|
124
|
+
calls += 1;
|
|
125
|
+
if (calls === 1) {
|
|
126
|
+
throw new Error("Resource temporarily unavailable");
|
|
127
|
+
}
|
|
128
|
+
return { postMessage: vi.fn(), terminate: vi.fn(async () => {}) } as unknown as WorkerHandle;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// worker 崩溃(count=1 ≤ 3 → scheduleRebuild → rebuild #1 抛错 → count=2 → 再排程)
|
|
132
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
133
|
+
await advance(1000); // 第 1 次退避 1s
|
|
134
|
+
await advance(2000); // 回灌后第 2 次退避 2s(retryIndex=2)
|
|
135
|
+
await p;
|
|
136
|
+
|
|
137
|
+
// 回灌矩阵生效:两次计数(崩溃 + rebuild 失败)
|
|
138
|
+
expect(run.meta.workerErrorCount).toBe(2);
|
|
139
|
+
// rebuild #2 成功 → run 仍 running(旧 worker 已换新,不卡死不误判 failed)
|
|
140
|
+
expect(run.state.status).toBe("running");
|
|
141
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(2);
|
|
142
|
+
// 未收敛终态:不 save、不注销通知
|
|
143
|
+
expect(deps.store.save).not.toHaveBeenCalled();
|
|
144
|
+
expect(deps.eventBus.emit).not.toHaveBeenCalled();
|
|
145
|
+
expect(deps.onRunDone).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("rebuild 连续失败耗尽(count > MAX=3)→ 收敛 done,failed + 持久化 + 注销 + onRunDone", async () => {
|
|
149
|
+
const run = makeRealRun("wf-rebuild-2");
|
|
150
|
+
const deps = makeDeps({ startThrows: true });
|
|
151
|
+
|
|
152
|
+
// await 链本身不 reject——旧实现此处裸抛 → void handlers.onError → unhandledRejection
|
|
153
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
154
|
+
// 退避序列:1s(crash→rebuild#1)→ 2s(#1 失败→#2)→ 4s(#2 失败→#3)→ 8s(#3 失败→耗尽)
|
|
155
|
+
await advance(1000 + 2000 + 4000 + 8000);
|
|
156
|
+
await expect(p).resolves.toBeUndefined();
|
|
157
|
+
|
|
158
|
+
// 计数:崩溃 1 + rebuild 失败 3 = 4 > MAX
|
|
159
|
+
expect(run.meta.workerErrorCount).toBe(4);
|
|
160
|
+
// 收敛 done,failed(不卡 running)
|
|
161
|
+
expect(run.state.status).toBe("done");
|
|
162
|
+
expect(run.state.reason).toBe("failed");
|
|
163
|
+
expect(run.state.error).toContain("Runtime rebuild failed after 3 retries");
|
|
164
|
+
expect(run.state.error).toContain("Resource temporarily unavailable");
|
|
165
|
+
// workerHost.start 恰好试了 3 次(3 次 rebuild 全失败)
|
|
166
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(3);
|
|
167
|
+
// 终态三件套:持久化 + pending:unregister + onRunDone
|
|
168
|
+
expect(deps.store.save).toHaveBeenCalledTimes(1);
|
|
169
|
+
expect(deps.eventBus.emit).toHaveBeenCalledWith("pending:unregister", {
|
|
170
|
+
id: "wf-rebuild-2",
|
|
171
|
+
reason: "failed",
|
|
172
|
+
});
|
|
173
|
+
expect(deps.onRunDone).toHaveBeenCalledTimes(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("耗尽收敛前 run 已被 abort(退避窗口内转终态)→ 不再 rebuild 也不再误转 failed", async () => {
|
|
177
|
+
const run = makeRealRun("wf-rebuild-3");
|
|
178
|
+
const deps = makeDeps({ startThrows: true });
|
|
179
|
+
|
|
180
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
181
|
+
// 退避窗口内外部 abort(done,aborted)
|
|
182
|
+
run.transition("done", "aborted");
|
|
183
|
+
await advance(1000);
|
|
184
|
+
await expect(p).resolves.toBeUndefined();
|
|
185
|
+
|
|
186
|
+
// scheduleRebuild 退避后重检 isTerminal → 跳过重建;isTerminal(run) 守卫跳过回灌
|
|
187
|
+
expect(deps.workerHost.start).not.toHaveBeenCalled();
|
|
188
|
+
expect(run.meta.workerErrorCount).toBe(1); // 仅崩溃那次,rebuild 失败未计数
|
|
189
|
+
expect(run.state.reason).toBe("aborted");
|
|
190
|
+
expect(deps.onRunDone).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── [P-SD] XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE 注入钩子 ──
|
|
195
|
+
|
|
196
|
+
describe("[P-SD] 重建失败注入钩子(XYZ_SUBAGENT_TEST_INJECT_REBUILD_FAILURE)", () => {
|
|
197
|
+
it("env=1:rebuild 第 1 次起全部抛错 → 重试矩阵确定性耗尽 → done,failed(S-D 子场景②)", async () => {
|
|
198
|
+
process.env[REBUILD_INJECT_ENV] = "1";
|
|
199
|
+
const run = makeRealRun("wf-inject-1");
|
|
200
|
+
const deps = makeDeps(); // start 本身不抛——抛错全部来自钩子注入
|
|
201
|
+
|
|
202
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
203
|
+
await advance(1000 + 2000 + 4000 + 8000);
|
|
204
|
+
await expect(p).resolves.toBeUndefined();
|
|
205
|
+
|
|
206
|
+
// workerHost.start 从未被调到(rebuildRuntime 在 start 前注入抛错)
|
|
207
|
+
expect(deps.workerHost.start).not.toHaveBeenCalled();
|
|
208
|
+
// 注入错误进入重试矩阵并耗尽:崩溃 1 + 注入失败 3 = 4
|
|
209
|
+
expect(run.meta.workerErrorCount).toBe(4);
|
|
210
|
+
expect(run.state.status).toBe("done");
|
|
211
|
+
expect(run.state.reason).toBe("failed");
|
|
212
|
+
expect(run.state.error).toContain("[S-D test hook]");
|
|
213
|
+
expect(run.state.error).toContain("Runtime rebuild failed after 3 retries");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("激活即 warn 留痕且恰一次(安全约束:杜绝静默生效/刷屏)", async () => {
|
|
217
|
+
process.env[REBUILD_INJECT_ENV] = "1";
|
|
218
|
+
const loggerSpy = vi.spyOn(getLogger("subagents"), "warn");
|
|
219
|
+
const run = makeRealRun("wf-inject-warn");
|
|
220
|
+
const deps = makeDeps();
|
|
221
|
+
|
|
222
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
223
|
+
await advance(1000 + 2000 + 4000 + 8000);
|
|
224
|
+
await p;
|
|
225
|
+
|
|
226
|
+
const warnCalls = loggerSpy.mock.calls.map((c) => String(c[0]));
|
|
227
|
+
const hookWarns = warnCalls.filter((m) => m.includes(REBUILD_INJECT_ENV));
|
|
228
|
+
expect(hookWarns).toHaveLength(1);
|
|
229
|
+
expect(hookWarns[0]).toContain("ACTIVE");
|
|
230
|
+
expect(hookWarns[0]).toContain("NEVER set in production");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("env 未设置:钩子完全不激活,rebuild 照常(无 warn、start 正常被调)", async () => {
|
|
234
|
+
delete process.env[REBUILD_INJECT_ENV];
|
|
235
|
+
const loggerSpy = vi.spyOn(getLogger("subagents"), "warn");
|
|
236
|
+
const run = makeRealRun("wf-inject-off");
|
|
237
|
+
const deps = makeDeps();
|
|
238
|
+
|
|
239
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
240
|
+
await advance(1000);
|
|
241
|
+
await p;
|
|
242
|
+
|
|
243
|
+
expect(loggerSpy.mock.calls.filter((c) => String(c[0]).includes(REBUILD_INJECT_ENV))).toHaveLength(0);
|
|
244
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(1);
|
|
245
|
+
expect(run.state.status).toBe("running");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("env 非法值(非正整数):不激活 + warn 指明原值(杜绝静默失效,LC-7 同族)", async () => {
|
|
249
|
+
process.env[REBUILD_INJECT_ENV] = "abc";
|
|
250
|
+
const loggerSpy = vi.spyOn(getLogger("subagents"), "warn");
|
|
251
|
+
const run = makeRealRun("wf-inject-invalid");
|
|
252
|
+
const deps = makeDeps();
|
|
253
|
+
|
|
254
|
+
const p = handleWorkerError(run, new Error("worker boom"), deps, makeHandlers());
|
|
255
|
+
await advance(1000);
|
|
256
|
+
await p;
|
|
257
|
+
|
|
258
|
+
// 非法值 → 不注入:rebuild 正常执行
|
|
259
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(1);
|
|
260
|
+
expect(run.state.status).toBe("running");
|
|
261
|
+
// 且 warn 留痕指明钩子未激活
|
|
262
|
+
const hookWarns = loggerSpy.mock.calls.map((c) => String(c[0])).filter((m) => m.includes(REBUILD_INJECT_ENV));
|
|
263
|
+
expect(hookWarns).toHaveLength(1);
|
|
264
|
+
expect(hookWarns[0]).toContain('"abc"');
|
|
265
|
+
expect(hookWarns[0]).toContain("INACTIVE");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("env=0 / 负数 / 空串:同样不激活(空串 = 未设置语义)", async () => {
|
|
269
|
+
for (const raw of ["0", "-3", ""]) {
|
|
270
|
+
resetRebuildFailureInjectionForTest();
|
|
271
|
+
const loggerSpy = vi.spyOn(getLogger("subagents"), "warn");
|
|
272
|
+
if (raw === "") {
|
|
273
|
+
delete process.env[REBUILD_INJECT_ENV];
|
|
274
|
+
} else {
|
|
275
|
+
process.env[REBUILD_INJECT_ENV] = raw;
|
|
276
|
+
}
|
|
277
|
+
const run = makeRealRun(`wf-inject-${raw || "empty"}`);
|
|
278
|
+
const deps = makeDeps();
|
|
279
|
+
const p = handleWorkerError(run, new Error("boom"), deps, makeHandlers());
|
|
280
|
+
await advance(1000);
|
|
281
|
+
await p;
|
|
282
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(1);
|
|
283
|
+
expect(run.state.status).toBe("running");
|
|
284
|
+
loggerSpy.mockRestore();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("钩子序数跨 rebuild 生效:env=2 时第 1 次 rebuild 正常、第 2 次起注入", async () => {
|
|
289
|
+
process.env[REBUILD_INJECT_ENV] = "2";
|
|
290
|
+
const run = makeRealRun("wf-inject-ordinal");
|
|
291
|
+
const deps = makeDeps();
|
|
292
|
+
const handlers = makeHandlers();
|
|
293
|
+
|
|
294
|
+
// 第 1 次崩溃 → rebuild #1(进程级序数 1 < 2)→ 注入不生效,真实重建成功
|
|
295
|
+
const p1 = handleWorkerError(run, new Error("worker boom"), deps, handlers);
|
|
296
|
+
await advance(1000);
|
|
297
|
+
await p1;
|
|
298
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(1);
|
|
299
|
+
expect(run.state.status).toBe("running");
|
|
300
|
+
|
|
301
|
+
// 第 2 次崩溃(新代际 worker)→ rebuild #2(序数 2 ≥ 2)起注入拦截 → 矩阵耗尽收敛
|
|
302
|
+
const p2 = handleWorkerError(run, new Error("worker boom 2"), deps, handlers);
|
|
303
|
+
await advance(2000 + 4000 + 8000);
|
|
304
|
+
await p2;
|
|
305
|
+
|
|
306
|
+
// rebuild #1 真实执行过;#2/#3 被注入拦截(start 不再增长)
|
|
307
|
+
expect(deps.workerHost.start).toHaveBeenCalledTimes(1);
|
|
308
|
+
expect(run.meta.workerErrorCount).toBe(4);
|
|
309
|
+
expect(run.state.reason).toBe("failed");
|
|
310
|
+
expect(run.state.error).toContain("[S-D test hook]");
|
|
311
|
+
});
|
|
312
|
+
});
|