@zhushanwen/subagent-core 0.4.0 → 0.5.1
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/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
- package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
- package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
- package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
- package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
- package/dist/execution/engine/engines/zcode/constants.js +17 -27
- package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
- package/dist/execution/engine/engines/zcode/reader.js +2 -2
- package/dist/index.cjs +16418 -14070
- package/dist/index.d.cts +5136 -4016
- package/dist/index.d.ts +5136 -4016
- package/dist/index.js +16343 -14048
- package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
- package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
- package/package.json +3 -3
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
- package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
- package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
- package/src/__tests__/m2-append-content-probe.test.ts +0 -1
- package/src/__tests__/manifest-store.test.ts +10 -9
- package/src/__tests__/record-store-cache.test.ts +0 -2
- package/src/__tests__/record-store-index.test.ts +1 -2
- package/src/__tests__/review-fix-loop-script.test.ts +4 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
- package/src/__tests__/robustness-low-batch1.test.ts +6 -5
- package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
- package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
- package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
- package/src/__tests__/session-runner.test.ts +1 -1
- package/src/core/__tests__/host-services.test.ts +2 -2
- package/src/core/__tests__/logger.test.ts +1 -1
- package/src/core/error-message.ts +9 -0
- package/src/core/host-services.ts +21 -5
- package/src/core/notify-ports.ts +18 -5
- package/src/execution/__tests__/agent-profile.test.ts +4 -4
- package/src/execution/__tests__/agent-registry.test.ts +370 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
- package/src/execution/__tests__/agents-assembly.test.ts +2 -2
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
- package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
- package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
- package/src/execution/__tests__/delivery-methods.test.ts +75 -62
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
- package/src/execution/__tests__/dialog-queue.test.ts +2 -2
- package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
- package/src/execution/__tests__/execute-nesting.test.ts +34 -188
- package/src/execution/__tests__/execution-record.test.ts +217 -60
- package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
- package/src/execution/__tests__/finalize-record.test.ts +173 -17
- package/src/execution/__tests__/gc-timer.test.ts +55 -2
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
- package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
- package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
- package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
- package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
- package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
- package/src/execution/__tests__/model-resolver.test.ts +2 -2
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
- package/src/execution/__tests__/nested-visibility.test.ts +2 -0
- package/src/execution/__tests__/notify-ledger.test.ts +973 -18
- package/src/execution/__tests__/output-collector.test.ts +117 -6
- package/src/execution/__tests__/pi-invocation.test.ts +1 -1
- package/src/execution/__tests__/record-store.test.ts +10 -9
- package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
- package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
- package/src/execution/__tests__/rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
- package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
- package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
- package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
- package/src/execution/__tests__/session-file-gc.test.ts +35 -0
- package/src/execution/__tests__/session-pending.test.ts +257 -14
- package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
- package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
- package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
- package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
- package/src/execution/__tests__/spawn-args.test.ts +11 -10
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
- package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
- package/src/execution/__tests__/spawned-children.test.ts +2 -1
- package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
- package/src/execution/__tests__/stdin-writer.test.ts +6 -4
- package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
- package/src/execution/__tests__/subagent-actions-core.test.ts +42 -22
- package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
- package/src/execution/__tests__/subagent-service.test.ts +37 -30
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
- package/src/execution/__tests__/temp-prompt.test.ts +1 -1
- package/src/execution/__tests__/timeout-integration.test.ts +6 -5
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
- package/src/execution/__tests__/turn-limiter.test.ts +1 -1
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
- package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
- package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
- package/src/execution/__tests__/worktree-manager.test.ts +14 -5
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
- package/src/execution/agent-registry.ts +74 -30
- package/src/execution/agent-result-mapper.ts +3 -0
- package/src/execution/cold-resurrect.ts +199 -0
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
- package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
- package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
- package/src/execution/engine/__tests__/registry.test.ts +4 -2
- package/src/execution/engine/__tests__/routing.test.ts +115 -6
- package/src/execution/engine/common/capability-gate.ts +92 -0
- package/src/execution/engine/common/journal-wiring.ts +97 -0
- package/src/execution/engine/common/kill-chain.ts +42 -10
- package/src/execution/engine/common/nesting-guard.ts +66 -0
- package/src/execution/engine/common/persona-router.ts +18 -8
- package/src/execution/engine/common/pool-manager.ts +332 -78
- package/src/execution/engine/common/session-view-service.ts +506 -0
- package/src/execution/engine/common/session-view-types.ts +125 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
- package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
- package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
- package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
- package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
- package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
- package/src/execution/engine/engines/pi/reader.ts +11 -0
- package/src/execution/engine/engines/pi/registration.ts +4 -2
- package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
- package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
- package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
- package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
- package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
- package/src/execution/engine/engines/zcode/connection.ts +24 -11
- package/src/execution/engine/engines/zcode/constants.ts +75 -71
- package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
- package/src/execution/engine/engines/zcode/parser.ts +12 -346
- package/src/execution/engine/engines/zcode/preparer.ts +12 -143
- package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
- package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
- package/src/execution/engine/host-task-spec.ts +32 -36
- package/src/execution/engine/model-validation.ts +177 -0
- package/src/execution/engine/port.ts +24 -6
- package/src/execution/engine/routing.ts +75 -1
- package/src/execution/engine/types.ts +30 -103
- package/src/execution/execution-record.ts +222 -144
- package/src/execution/finalize-record.ts +131 -93
- package/src/execution/idle-gc.ts +28 -0
- package/src/execution/lifecycle-predicates.ts +1 -1
- package/src/execution/notify-host.ts +212 -0
- package/src/execution/record-store.ts +119 -73
- package/src/execution/round-settlement.ts +93 -0
- package/src/execution/session-file-gc.ts +96 -62
- package/src/execution/session-reconstructor.ts +256 -143
- package/src/execution/sessions-index.ts +117 -47
- package/src/execution/settled-watchdog.ts +264 -52
- package/src/execution/subagent-actions-core.ts +22 -22
- package/src/execution/subagent-service.ts +815 -963
- package/src/execution/subprocess-agent-runner.ts +144 -97
- package/src/execution/types.ts +9 -0
- package/src/execution/ui-request-handler-factory.ts +102 -74
- package/src/execution/ui-request-queue.ts +50 -19
- package/src/index.ts +442 -316
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
- package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
- package/src/orchestration/__tests__/args-validator.test.ts +14 -0
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
- package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
- package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
- package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
- package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
- package/src/orchestration/__tests__/script-lint.test.ts +17 -0
- package/src/orchestration/__tests__/test-mocks.ts +2 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
- package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
- package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
- package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
- package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
- package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
- package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +1 -1
- package/src/orchestration/args-validator.ts +53 -31
- package/src/orchestration/execute-agent-call.ts +19 -73
- package/src/orchestration/launcher.ts +128 -81
- package/src/orchestration/lifecycle.ts +43 -45
- package/src/orchestration/models/ports.ts +4 -4
- package/src/orchestration/models/run-runtime.ts +2 -2
- package/src/orchestration/models/trace.ts +1 -1
- package/src/orchestration/models/types.ts +111 -22
- package/src/orchestration/models/workflow-run.ts +1 -1
- package/src/orchestration/script-lint.ts +91 -58
- package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
- package/src/shared/__tests__/meta-parser.test.ts +151 -0
- package/src/shared/__tests__/resource-discovery.test.ts +258 -215
- package/src/shared/atomic-write.ts +12 -9
- package/src/shared/meta-parser.ts +158 -84
- package/src/shared/model-ref.ts +15 -2
- package/src/shared/resource-discovery.ts +9 -183
- package/src/shared/schema-jsonify.ts +1 -1
- package/workflows/review-fix-loop-utils.cjs +151 -89
- package/workflows/review-fix-loop-utils.d.cts +287 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
- package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
- package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
- package/src/execution/engine/engines/zcode/launcher.ts +0 -161
- package/src/execution/execute-options-mapper.ts +0 -115
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
|
@@ -36,18 +36,18 @@ function makeTmpAgentDir(): string {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function makeModelService(agentDir: string): ModelConfigService {
|
|
39
|
-
return new ModelConfigService({ agentDir });
|
|
39
|
+
return new ModelConfigService({ agentDir, cwd: agentDir });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function makePi(): PiLike & {
|
|
43
|
-
appendEntry: ReturnType<typeof vi.fn
|
|
44
|
-
events: { emit: ReturnType<typeof vi.fn
|
|
45
|
-
sendMessage: ReturnType<typeof vi.fn
|
|
43
|
+
appendEntry: ReturnType<typeof vi.fn<(customType: string, data?: unknown) => void>>;
|
|
44
|
+
events: { emit: ReturnType<typeof vi.fn<(channel: string, data: unknown) => void>> };
|
|
45
|
+
sendMessage: ReturnType<typeof vi.fn<(message: Parameters<PiLike["sendMessage"]>[0], options?: Parameters<PiLike["sendMessage"]>[1]) => void>>;
|
|
46
46
|
} {
|
|
47
47
|
return {
|
|
48
|
-
appendEntry: vi.fn(),
|
|
49
|
-
events: { emit: vi.fn() },
|
|
50
|
-
sendMessage: vi.fn(),
|
|
48
|
+
appendEntry: vi.fn((customType: string, data?: unknown) => {}),
|
|
49
|
+
events: { emit: vi.fn((channel: string, data: unknown) => {}) },
|
|
50
|
+
sendMessage: vi.fn(() => {}),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ describe("SubagentService", () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
afterEach(() => {
|
|
64
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
64
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
// ============================================================
|
|
@@ -75,7 +75,7 @@ describe("SubagentService", () => {
|
|
|
75
75
|
|
|
76
76
|
it("未 initSession 时 findRecord/cancel 抛 'pi not injected'", () => {
|
|
77
77
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
78
|
-
expect(() => service.findRecord("any")).toThrow(/pi not injected/);
|
|
78
|
+
expect(() => service.queries.findRecord("any")).toThrow(/pi not injected/);
|
|
79
79
|
expect(() => service.cancel("any")).toThrow(/pi not injected/);
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -83,7 +83,7 @@ describe("SubagentService", () => {
|
|
|
83
83
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
84
84
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
85
85
|
// findRecord 现在能过 assertReady,但 record 不存在 → 返回 undefined
|
|
86
|
-
expect(service.findRecord("missing")).toBeUndefined();
|
|
86
|
+
expect(service.queries.findRecord("missing")).toBeUndefined();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
it("dispose 后 findRecord 抛含 'disposed' 且带恢复指引", () => {
|
|
@@ -92,8 +92,8 @@ describe("SubagentService", () => {
|
|
|
92
92
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
93
93
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
94
94
|
service.dispose();
|
|
95
|
-
expect(() => service.findRecord("any")).toThrow(/disposed/);
|
|
96
|
-
expect(() => service.findRecord("any")).toThrow(/session ended|session_start|new session/i);
|
|
95
|
+
expect(() => service.queries.findRecord("any")).toThrow(/disposed/);
|
|
96
|
+
expect(() => service.queries.findRecord("any")).toThrow(/session ended|session_start|new session/i);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
it("dispose 幂等(多次调用不抛)", () => {
|
|
@@ -113,7 +113,7 @@ describe("SubagentService", () => {
|
|
|
113
113
|
// revive
|
|
114
114
|
service.initSession({ pi: makePi(), sessionId: "s2" });
|
|
115
115
|
// 现在 assertReady 又通过(findRecord 返回 undefined 而非 disposed)
|
|
116
|
-
expect(service.findRecord("any")).toBeUndefined();
|
|
116
|
+
expect(service.queries.findRecord("any")).toBeUndefined();
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
|
|
@@ -125,7 +125,7 @@ describe("SubagentService", () => {
|
|
|
125
125
|
it("findRecord 不存在的 id 返回 undefined", () => {
|
|
126
126
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
127
127
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
128
|
-
expect(service.findRecord("nonexistent-id")).toBeUndefined();
|
|
128
|
+
expect(service.queries.findRecord("nonexistent-id")).toBeUndefined();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it("cancel 不存在的 id 返回 false(不抛错,boolean 契约不变)", () => {
|
|
@@ -140,16 +140,13 @@ describe("SubagentService", () => {
|
|
|
140
140
|
// ============================================================
|
|
141
141
|
|
|
142
142
|
describe("状态查询", () => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
146
|
-
expect(service.listRunning()).toEqual([]);
|
|
147
|
-
});
|
|
143
|
+
// [D4] listRunning 已从 Service 删除(零生产调用方)——初始空态语义由
|
|
144
|
+
// collectRecords 用例与 store 层 listRunning 测试覆盖。
|
|
148
145
|
|
|
149
146
|
it("collectRecords 返回数组(空 sessions 目录时为空)", () => {
|
|
150
147
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
151
148
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
152
|
-
const records = service.collectRecords(100);
|
|
149
|
+
const records = service.queries.collectRecords(100);
|
|
153
150
|
expect(Array.isArray(records)).toBe(true);
|
|
154
151
|
});
|
|
155
152
|
|
|
@@ -157,7 +154,7 @@ describe("SubagentService", () => {
|
|
|
157
154
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
158
155
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
159
156
|
const listener = vi.fn();
|
|
160
|
-
const unsubscribe = service.onChange(listener);
|
|
157
|
+
const unsubscribe = service.queries.onChange(listener);
|
|
161
158
|
expect(typeof unsubscribe).toBe("function");
|
|
162
159
|
expect(() => unsubscribe()).not.toThrow();
|
|
163
160
|
});
|
|
@@ -218,6 +215,7 @@ describe("SubagentService", () => {
|
|
|
218
215
|
agent: "general-purpose",
|
|
219
216
|
model: "test/model",
|
|
220
217
|
mode: "background",
|
|
218
|
+
slug: "t",
|
|
221
219
|
task: "long task",
|
|
222
220
|
startedAt: 1_000_000,
|
|
223
221
|
rootSessionId: "s1",
|
|
@@ -233,8 +231,9 @@ describe("SubagentService", () => {
|
|
|
233
231
|
const record = createRecord(id, {
|
|
234
232
|
agent: "general-purpose",
|
|
235
233
|
model: "test/model",
|
|
236
|
-
mode: "
|
|
234
|
+
mode: "background",
|
|
237
235
|
task: "sync task",
|
|
236
|
+
slug: "test",
|
|
238
237
|
startedAt: 1_000_000,
|
|
239
238
|
rootSessionId: "s1",
|
|
240
239
|
// sync 不传 controller → controller === undefined
|
|
@@ -250,13 +249,14 @@ describe("SubagentService", () => {
|
|
|
250
249
|
agent: "general-purpose",
|
|
251
250
|
model: "test/model",
|
|
252
251
|
mode: "background",
|
|
252
|
+
slug: "t",
|
|
253
253
|
task: "done task",
|
|
254
254
|
startedAt: 1_000_000,
|
|
255
255
|
rootSessionId: "s1",
|
|
256
256
|
controller,
|
|
257
257
|
});
|
|
258
258
|
// 直接改 status 模拟终态(不走 CAS——测试不关心状态机,只关心 dispose 的 abort 过滤)
|
|
259
|
-
record.status = "
|
|
259
|
+
record.status = "closed";
|
|
260
260
|
getStore(service).register(record);
|
|
261
261
|
return record;
|
|
262
262
|
}
|
|
@@ -362,7 +362,8 @@ describe("SubagentService", () => {
|
|
|
362
362
|
|
|
363
363
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
364
364
|
service.initSession({ pi: makePi(), sessionId: "s1" });
|
|
365
|
-
|
|
365
|
+
// [D4-④] setUiRequestHandler 已删——handler 注入走 initSession 参数(唯一入口)
|
|
366
|
+
service.initSession({ pi: makePi(), sessionId: "s1", uiRequestHandler: staleHandler });
|
|
366
367
|
|
|
367
368
|
service.dispose();
|
|
368
369
|
|
|
@@ -427,6 +428,7 @@ describe("SubagentService", () => {
|
|
|
427
428
|
try {
|
|
428
429
|
await service.execute({
|
|
429
430
|
task: "worktree without fork (decoupled)",
|
|
431
|
+
slug: "test",
|
|
430
432
|
worktree: true,
|
|
431
433
|
fork: false,
|
|
432
434
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -443,6 +445,7 @@ describe("SubagentService", () => {
|
|
|
443
445
|
try {
|
|
444
446
|
await service.execute({
|
|
445
447
|
task: "worktree with fork",
|
|
448
|
+
slug: "test",
|
|
446
449
|
worktree: true,
|
|
447
450
|
fork: true,
|
|
448
451
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -460,6 +463,7 @@ describe("SubagentService", () => {
|
|
|
460
463
|
try {
|
|
461
464
|
await service.execute({
|
|
462
465
|
task: "default path",
|
|
466
|
+
slug: "test",
|
|
463
467
|
worktree: false,
|
|
464
468
|
fork: false,
|
|
465
469
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -473,7 +477,7 @@ describe("SubagentService", () => {
|
|
|
473
477
|
// ============================================================
|
|
474
478
|
// create-await 竞态守卫(Phase 2):create 的 await 窗口内 dispose/cancel
|
|
475
479
|
// 可把 record CAS 成 closed——守卫须主动 cleanup + early-failed 返回,不 kickOff。
|
|
476
|
-
// 实现约束固化:「赋值 record.worktreeHandle → 终态检查 →
|
|
480
|
+
// 实现约束固化:「赋值 record.worktreeHandle → 终态检查 → 轮次 kick-off」
|
|
477
481
|
// 必须同一同步段(中间禁止 await),本用例即该不变量的回归锚点。
|
|
478
482
|
// ============================================================
|
|
479
483
|
it("守卫:create await 窗口内 dispose 抢先 → cleanup 被调 + early-failed 返回(不 kickOff)", async () => {
|
|
@@ -487,12 +491,13 @@ describe("SubagentService", () => {
|
|
|
487
491
|
}) as Parameters<WorktreeManager["cleanup"]>[0];
|
|
488
492
|
let resolveCreate!: (h: unknown) => void;
|
|
489
493
|
vi.spyOn(wtm, "create").mockImplementation(
|
|
490
|
-
() => new Promise((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
494
|
+
() => new Promise<unknown>((r) => { resolveCreate = r; }) as ReturnType<WorktreeManager["create"]>,
|
|
491
495
|
);
|
|
492
496
|
const cleanupSpy = vi.spyOn(wtm, "cleanup").mockResolvedValue(undefined);
|
|
493
497
|
|
|
494
498
|
const execP = service.execute({
|
|
495
499
|
task: "guard test",
|
|
500
|
+
slug: "test",
|
|
496
501
|
worktree: true,
|
|
497
502
|
fork: false,
|
|
498
503
|
ctxModel: { id: "ctx-model", name: "Ctx", provider: "p", reasoning: false },
|
|
@@ -529,7 +534,7 @@ describe("SubagentService", () => {
|
|
|
529
534
|
//
|
|
530
535
|
// [未覆盖路径] 需 mock spawn 才能跑完 runSpawn 的路径,本文件约定不 mock spawn
|
|
531
536
|
// (见文件头——execute 集成测试在 execute-nesting.test.ts / run-spawn-integration.test.ts):
|
|
532
|
-
// - finalizeRecord status="
|
|
537
|
+
// - finalizeRecord status="closed"(background 正常完成 → unregister(closed))
|
|
533
538
|
// - finalizeRecord status="cancelled" 经 runAndFinalize 路径(cancel 抢先 CAS 时
|
|
534
539
|
// runAndFinalize 侧 tryTransition 失败跳过 finalizeRecord,由 cancelBackground 侧 emit——
|
|
535
540
|
// 本块 cancel 用例覆盖的即此后端 emit)
|
|
@@ -572,6 +577,7 @@ describe("SubagentService", () => {
|
|
|
572
577
|
agent: "general-purpose",
|
|
573
578
|
model: "test/model",
|
|
574
579
|
mode: "background",
|
|
580
|
+
slug: "t",
|
|
575
581
|
task: "cancel target",
|
|
576
582
|
startedAt: 1_000_000,
|
|
577
583
|
rootSessionId: "s1",
|
|
@@ -588,12 +594,13 @@ describe("SubagentService", () => {
|
|
|
588
594
|
|
|
589
595
|
const handle = await service.execute({
|
|
590
596
|
task: "wt fail bg",
|
|
597
|
+
slug: "test",
|
|
591
598
|
worktree: true,
|
|
592
599
|
fork: true,
|
|
593
600
|
ctxModel,
|
|
594
601
|
});
|
|
595
602
|
|
|
596
|
-
// worktree create
|
|
603
|
+
// worktree create 抛错在轮次 kick-off 之前(executeViaEngine 同步 catch),返回 background 形状
|
|
597
604
|
expect(handle.mode).toBe("background");
|
|
598
605
|
|
|
599
606
|
// createRecordForMode 生成的 subagentId 带 sa- 前缀(sa-<uuid>)
|
|
@@ -670,7 +677,7 @@ describe("SubagentService", () => {
|
|
|
670
677
|
injectRunningBackground(service, "bg-running-1");
|
|
671
678
|
injectRunningBackground(service, "bg-running-2");
|
|
672
679
|
const terminal = injectRunningBackground(service, "bg-done");
|
|
673
|
-
terminal.status = "
|
|
680
|
+
terminal.status = "closed"; // 模拟终态,dispose 不应为其 emit
|
|
674
681
|
|
|
675
682
|
service.dispose();
|
|
676
683
|
|
|
@@ -704,7 +711,7 @@ describe("ModelConfigService ctxModel 缓存", () => {
|
|
|
704
711
|
agentDir = makeTmpAgentDir();
|
|
705
712
|
});
|
|
706
713
|
afterEach(() => {
|
|
707
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
714
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
708
715
|
});
|
|
709
716
|
|
|
710
717
|
/** 最小 mock registry:空可用列表(ctxModel 路径不需要 lookup)。 */
|
|
@@ -15,11 +15,11 @@ import * as path from "node:path";
|
|
|
15
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
16
16
|
|
|
17
17
|
import type { AgentCallOpts, AgentResult } from "../../orchestration/models/types.ts";
|
|
18
|
-
import type { RunContext } from "../engine/port.ts";
|
|
18
|
+
import type { RunContext, EnginePort } from "../engine/port.ts";
|
|
19
19
|
import { clearEngines, registerEngine } from "../engine/registry.ts";
|
|
20
|
-
import type {
|
|
20
|
+
import type { ProbeReport } from "../engine/types.ts";
|
|
21
21
|
import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
|
|
22
|
-
import { getChildByRecord } from "../session-runner.ts";
|
|
22
|
+
import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
|
|
23
23
|
import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
|
|
24
24
|
import type { SubagentService } from "../subagent-service.ts";
|
|
25
25
|
import type { ExecuteOptions } from "../types.ts";
|
|
@@ -31,7 +31,10 @@ interface FakeEngineCalls {
|
|
|
31
31
|
runs: Array<{ taskSpec: unknown; ctx: RunContext }>;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/** fake zcode 引擎的池 key 缺省值(机制跟随性用例:任意 key 落对应目录)。 */
|
|
35
|
+
const FAKE_POOL_KEY = "home-test-provider-m1";
|
|
36
|
+
|
|
37
|
+
function makeFakeZcodeEngine(probeOk: boolean, poolKey: string = FAKE_POOL_KEY): { engine: EnginePort; calls: FakeEngineCalls } {
|
|
35
38
|
const calls: FakeEngineCalls = { probed: 0, runs: [] };
|
|
36
39
|
const engine: EnginePort = {
|
|
37
40
|
id: "zcode",
|
|
@@ -46,6 +49,7 @@ function makeFakeZcodeEngine(probeOk: boolean): { engine: EnginePort; calls: Fak
|
|
|
46
49
|
resume: "cold",
|
|
47
50
|
interrupt: "kill-only",
|
|
48
51
|
permissionMode: "native",
|
|
52
|
+
maxTurns: false,
|
|
49
53
|
}),
|
|
50
54
|
probe: () => {
|
|
51
55
|
calls.probed++;
|
|
@@ -62,11 +66,11 @@ function makeFakeZcodeEngine(probeOk: boolean): { engine: EnginePort; calls: Fak
|
|
|
62
66
|
run: (taskSpec, ctx) => {
|
|
63
67
|
calls.runs.push({ taskSpec, ctx });
|
|
64
68
|
// 对齐点③模拟:prepare 期声明池 key → 事件 emit(zcode coarse 形态:终态后合成)
|
|
65
|
-
ctx.onPoolResolved?.(
|
|
69
|
+
ctx.onPoolResolved?.(poolKey);
|
|
66
70
|
ctx.onEvent?.({ type: "message_end", usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 } });
|
|
67
71
|
ctx.onEvent?.({ type: "turn_end" });
|
|
68
72
|
return Promise.resolve({
|
|
69
|
-
handle: { data: { v: 1, engineId: "zcode", sessionRef: {}, poolKey
|
|
73
|
+
handle: { data: { v: 1, engineId: "zcode", sessionRef: {}, poolKey, adapterVersion: "t" } },
|
|
70
74
|
outcome: { engineId: "zcode", content: "from-zcode" },
|
|
71
75
|
});
|
|
72
76
|
},
|
|
@@ -84,7 +88,10 @@ function makeMockPiService() {
|
|
|
84
88
|
executeOpts.push(opts);
|
|
85
89
|
return { content: "from-pi", durationMs: 1, toolCalls: [] };
|
|
86
90
|
});
|
|
87
|
-
|
|
91
|
+
// [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取 PiEngineService——fake 的
|
|
92
|
+
// face 即自身,getter 直接返回 self。
|
|
93
|
+
const service = { executeAndAwait } as unknown as SubagentService & { asEngineService: unknown };
|
|
94
|
+
(service as { asEngineService: unknown }).asEngineService = service;
|
|
88
95
|
return { service, executeOpts, executeAndAwait };
|
|
89
96
|
}
|
|
90
97
|
|
|
@@ -134,7 +141,7 @@ afterEach(() => {
|
|
|
134
141
|
if (prevDataDirEnv === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
|
|
135
142
|
else process.env["XYZ_AGENT_DATA_DIR"] = prevDataDirEnv;
|
|
136
143
|
clearEngines();
|
|
137
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
144
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
138
145
|
});
|
|
139
146
|
|
|
140
147
|
describe("SAR 路由集成(P4 验收 1/2/3)", () => {
|
|
@@ -163,13 +170,31 @@ describe("SAR 路由集成(P4 验收 1/2/3)", () => {
|
|
|
163
170
|
expect(result.content).toBe("from-zcode");
|
|
164
171
|
expect(calls.runs).toHaveLength(1);
|
|
165
172
|
expect(pi.executeAndAwait).not.toHaveBeenCalled();
|
|
166
|
-
//
|
|
167
|
-
|
|
173
|
+
// 机制跟随性断言:journal 落在引擎声明的池 key 目录(任意 key 落对应目录——
|
|
174
|
+
// 真实 zcode 引擎 poolKey 恒 'shared',对齐真实行为的断言见下一用例)
|
|
175
|
+
const poolDir = path.join(tmpRoot, "engine-data", "engines", "zcode", FAKE_POOL_KEY);
|
|
168
176
|
const journals = fs.existsSync(poolDir) ? fs.readdirSync(poolDir) : [];
|
|
169
177
|
expect(journals).toHaveLength(1);
|
|
170
178
|
expect(journals[0]).toMatch(/^journal-sa-.+\.jsonl$/);
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("journal 落盘对齐真实行为:poolKey 'shared' → engines/zcode/shared/journal-<taskId>.jsonl", async () => {
|
|
182
|
+
// 生产 zcode 引擎 poolKey 恒 'shared'(ZCODE_SHARED_POOL_KEY)——fake 以同 key
|
|
183
|
+
// 声明,断言 journal 精确落 shared 池目录(旧池化时代「shared 目录不存在」的
|
|
184
|
+
// 反向锚定已随池化删除,此处锚定现行正向语义)
|
|
185
|
+
const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
|
|
186
|
+
installModelService();
|
|
187
|
+
const { engine, calls } = makeFakeZcodeEngine(true, "shared");
|
|
188
|
+
registerEngine("zcode", () => engine);
|
|
189
|
+
const pi = makeMockPiService();
|
|
190
|
+
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
191
|
+
|
|
192
|
+
await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
|
|
193
|
+
|
|
194
|
+
const taskId = calls.runs[0]?.ctx.taskId;
|
|
195
|
+
expect(taskId).toMatch(/^sa-/);
|
|
196
|
+
const sharedPoolDir = path.join(tmpRoot, "engine-data", "engines", "zcode", "shared");
|
|
197
|
+
expect(fs.readdirSync(sharedPoolDir)).toEqual([`journal-${taskId}.jsonl`]);
|
|
173
198
|
});
|
|
174
199
|
|
|
175
200
|
it("调用参数 engine 覆盖 frontmatter(三层优先级落到 run,A7)", async () => {
|
|
@@ -307,4 +332,55 @@ describe("SAR 路由集成(P4 验收 1/2/3)", () => {
|
|
|
307
332
|
await new Promise<void>((resolve) => child.once("close", () => resolve()));
|
|
308
333
|
expect(getChildByRecord(ctx.taskId)).toBeUndefined();
|
|
309
334
|
});
|
|
335
|
+
|
|
336
|
+
// ── [D3-④] SAR 路径预检(capabilities 驱动;唯一有意行为变化 = zcode+worktree)──
|
|
337
|
+
|
|
338
|
+
it("[D3-④] zcode + worktree:true → engine_capability_unsupported(workflow 域漏拦缺口修复),无 engine.run、无 journal 产物", async () => {
|
|
339
|
+
const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
|
|
340
|
+
installModelService();
|
|
341
|
+
const { engine, calls } = makeFakeZcodeEngine(true);
|
|
342
|
+
registerEngine("zcode", () => engine);
|
|
343
|
+
const pi = makeMockPiService();
|
|
344
|
+
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
345
|
+
|
|
346
|
+
const result = await sar.run(makeOpts({ agent: agentRef, worktree: true }), new AbortController().signal);
|
|
347
|
+
|
|
348
|
+
expect(result.error).toContain("engine_capability_unsupported");
|
|
349
|
+
expect(result.error).toContain("worktree");
|
|
350
|
+
// 无进程创建 + 无 journal 落盘(拒绝在 wiring/run 之前)
|
|
351
|
+
expect(calls.runs).toHaveLength(0);
|
|
352
|
+
expect(pi.executeAndAwait).not.toHaveBeenCalled();
|
|
353
|
+
const enginesRoot = path.join(tmpRoot, "engine-data", "engines");
|
|
354
|
+
expect(fs.existsSync(enginesRoot) ? fs.readdirSync(enginesRoot) : []).toEqual([]);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("[D3-④] zcode + maxTurns / fork → engine_capability_unsupported(现状行为保持的正向守护)", async () => {
|
|
358
|
+
const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
|
|
359
|
+
installModelService();
|
|
360
|
+
const { engine, calls } = makeFakeZcodeEngine(true);
|
|
361
|
+
registerEngine("zcode", () => engine);
|
|
362
|
+
const pi = makeMockPiService();
|
|
363
|
+
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
364
|
+
|
|
365
|
+
const maxTurnsResult = await sar.run(makeOpts({ agent: agentRef, maxTurns: 5 }), new AbortController().signal);
|
|
366
|
+
expect(maxTurnsResult.error).toContain("engine_capability_unsupported");
|
|
367
|
+
expect(maxTurnsResult.error).toContain("maxTurns");
|
|
368
|
+
expect(maxTurnsResult.error).toContain("capabilities.maxTurns = false");
|
|
369
|
+
|
|
370
|
+
const forkResult = await sar.run(makeOpts({ agent: agentRef, fork: true }), new AbortController().signal);
|
|
371
|
+
expect(forkResult.error).toContain("engine_capability_unsupported");
|
|
372
|
+
expect(forkResult.error).toContain("fork");
|
|
373
|
+
expect(calls.runs).toHaveLength(0);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("[V4⑤ 反向] pi + maxTurns → 直通不拦(pi 既有合法能力零回归),executeAndAwait 收到 maxTurns", async () => {
|
|
377
|
+
installModelService();
|
|
378
|
+
const pi = makeMockPiService();
|
|
379
|
+
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
380
|
+
|
|
381
|
+
const result = await sar.run(makeOpts({ maxTurns: 3 }), new AbortController().signal);
|
|
382
|
+
|
|
383
|
+
expect(result.content).toBe("from-pi");
|
|
384
|
+
expect(pi.executeOpts[0]?.maxTurns).toBe(3);
|
|
385
|
+
});
|
|
310
386
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/execution/__tests__/subprocess-agent-runner-timeout.test.ts
|
|
2
|
+
//
|
|
3
|
+
// [D6 合流迁移] mergeTimeoutSignal(D-A9:per-call timeoutMs 合并进 AbortSignal)的
|
|
4
|
+
// 行为测试——原定义在已删除的 execution/execute-options-mapper.ts(测试随迁),函数
|
|
5
|
+
// 现居 subprocess-agent-runner.ts(唯一消费点,运行期件随消费方落位)。
|
|
6
|
+
// 原用例编号(execute-options-mapper.test.ts 的 mergeTimeoutSignal describe,7 用例)
|
|
7
|
+
// 与行为语义逐条保持。
|
|
8
|
+
//
|
|
9
|
+
// 接线测试(timeoutMs → engine 收到 merged signal → abort 收口)在
|
|
10
|
+
// subprocess-agent-runner.test.ts 的 T3.6(经真实 PiEngine 链路),本文件锁纯函数行为。
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it, vi } from "vitest";
|
|
13
|
+
|
|
14
|
+
import { mergeTimeoutSignal } from "../subprocess-agent-runner.ts";
|
|
15
|
+
|
|
16
|
+
describe("mergeTimeoutSignal (D-A9)", () => {
|
|
17
|
+
it("T3.6 timeoutMs===undefined → 原样返回 external signal", () => {
|
|
18
|
+
const ctrl = new AbortController();
|
|
19
|
+
const result = mergeTimeoutSignal(ctrl.signal, undefined);
|
|
20
|
+
expect(result).toBe(ctrl.signal);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("T3.6 timeoutMs<=0 → 原样返回 external signal", () => {
|
|
24
|
+
const ctrl = new AbortController();
|
|
25
|
+
const result = mergeTimeoutSignal(ctrl.signal, 0);
|
|
26
|
+
expect(result).toBe(ctrl.signal);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("T3.6 timeoutMs>0 → 返回新 signal(合并外部+超时两路)", () => {
|
|
30
|
+
const ctrl = new AbortController();
|
|
31
|
+
const result = mergeTimeoutSignal(ctrl.signal, 50);
|
|
32
|
+
expect(result).not.toBe(ctrl.signal);
|
|
33
|
+
expect(result.aborted).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("T3.6 timeoutMs 到期 → merged signal abort", () => {
|
|
37
|
+
vi.useFakeTimers();
|
|
38
|
+
const ctrl = new AbortController();
|
|
39
|
+
const merged = mergeTimeoutSignal(ctrl.signal, 50);
|
|
40
|
+
|
|
41
|
+
expect(merged.aborted).toBe(false);
|
|
42
|
+
vi.advanceTimersByTime(51);
|
|
43
|
+
expect(merged.aborted).toBe(true);
|
|
44
|
+
vi.useRealTimers();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("T3.6 外部 signal abort → merged signal abort", () => {
|
|
48
|
+
const ctrl = new AbortController();
|
|
49
|
+
const merged = mergeTimeoutSignal(ctrl.signal, 5000);
|
|
50
|
+
ctrl.abort();
|
|
51
|
+
expect(merged.aborted).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("T3.6 外部 signal 已 abort → 返回已 abort 的 signal", () => {
|
|
55
|
+
const ctrl = new AbortController();
|
|
56
|
+
ctrl.abort();
|
|
57
|
+
const merged = mergeTimeoutSignal(ctrl.signal, 5000);
|
|
58
|
+
expect(merged.aborted).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("T3.17 NFR: merged signal abort → timeout timer 清理", () => {
|
|
62
|
+
vi.useFakeTimers();
|
|
63
|
+
const ctrl = new AbortController();
|
|
64
|
+
const merged = mergeTimeoutSignal(ctrl.signal, 50);
|
|
65
|
+
|
|
66
|
+
ctrl.abort(); // 外部 abort → merged 也 abort
|
|
67
|
+
expect(merged.aborted).toBe(true);
|
|
68
|
+
|
|
69
|
+
// 推进时间,不应再有副作用
|
|
70
|
+
vi.advanceTimersByTime(100);
|
|
71
|
+
// timer 应被清理(通过 abort event listener)
|
|
72
|
+
// 无异常 = timer 已正确清理
|
|
73
|
+
vi.useRealTimers();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// T3.7 (正常): onEvent 桥接 AgentEvent 透传
|
|
12
12
|
// T3.17 (NFR): mergeTimeoutSignal listener 清理
|
|
13
13
|
// T3.18 (NFR): dispose 兜底覆盖(delegate 后子进程进 spawnedChildren)
|
|
14
|
-
// T3.19 (NFR): AgentCallOpts→ExecuteOptions
|
|
14
|
+
// T3.19 (NFR): AgentCallOpts→ExecuteOptions 直出保真(D6 合流后映射在 PiEngine 边界,
|
|
15
|
+
// 本组用例经真实 PiEngine 链路锁定 spawn 参数形态)
|
|
15
16
|
|
|
16
17
|
import { mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
17
18
|
import { tmpdir } from "node:os";
|
|
@@ -26,9 +27,10 @@ import { configureCore, resetCoreForTests } from "../../core/host-services.ts";
|
|
|
26
27
|
|
|
27
28
|
import type { AgentCallOpts, AgentResult } from "../../orchestration/models/types.ts";
|
|
28
29
|
import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
|
|
29
|
-
import type { ModelRegistryLike } from "../model-resolver.ts";
|
|
30
|
+
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
30
31
|
import { replayJournal } from "../engine/common/event-journal.ts";
|
|
31
32
|
import type { SubprocessAgentRunnerDeps } from "../subprocess-agent-runner.ts";
|
|
33
|
+
import type { SubagentService } from "../subagent-service.ts";
|
|
32
34
|
import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
|
|
33
35
|
|
|
34
36
|
// ── 测试辅助 ──
|
|
@@ -46,17 +48,17 @@ function makeMockResult(overrides: Partial<AgentResult> = {}): AgentResult {
|
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
/** 创建 mock SubagentService(只实现 executeAndAwait
|
|
50
|
-
|
|
51
|
+
/** 创建 mock SubagentService(只实现 executeAndAwait)。
|
|
52
|
+
* [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取 PiEngineService——fake 的
|
|
53
|
+
* face 即自身,getter 直接返回 self。 */
|
|
54
|
+
function createMockService(impl?: typeof vi.fn): SubagentService {
|
|
51
55
|
const executeAndAwait = impl ?? vi.fn().mockResolvedValue(makeMockResult());
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
) => Promise<AgentResult>;
|
|
59
|
-
};
|
|
56
|
+
// partial mock(仅 executeAndAwait + asEngineService self-引用)——SAR 测试路径
|
|
57
|
+
// 不触达其余成员,经 unknown 双跳收敛到 SubagentService 视图
|
|
58
|
+
const partial: { executeAndAwait: typeof executeAndAwait; asEngineService?: unknown } = { executeAndAwait };
|
|
59
|
+
const service = partial as unknown as SubagentService;
|
|
60
|
+
partial.asEngineService = service;
|
|
61
|
+
return service;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
function makeBaseOpts(): AgentCallOpts {
|
|
@@ -171,7 +173,7 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
171
173
|
return Promise.resolve(makeMockResult());
|
|
172
174
|
}),
|
|
173
175
|
);
|
|
174
|
-
const ctxModel = { id: "ctx-model", provider: "test",
|
|
176
|
+
const ctxModel: ModelInfo = { id: "ctx-model", name: "Ctx Model", provider: "test", reasoning: false };
|
|
175
177
|
const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel };
|
|
176
178
|
const sar = new SubprocessAgentRunner(deps);
|
|
177
179
|
|
|
@@ -189,7 +191,7 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
189
191
|
return Promise.resolve(makeMockResult());
|
|
190
192
|
}),
|
|
191
193
|
);
|
|
192
|
-
const ctxModel = { id: "ctx-model", provider: "test",
|
|
194
|
+
const ctxModel: ModelInfo = { id: "ctx-model", name: "Ctx Model", provider: "test", reasoning: false };
|
|
193
195
|
const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel };
|
|
194
196
|
const sar = new SubprocessAgentRunner(deps);
|
|
195
197
|
|
|
@@ -225,7 +227,7 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
225
227
|
const svc = new ModelConfigService({ agentDir: join(tmpRoot, "agent"), cwd: tmpRoot });
|
|
226
228
|
svc.initModel({ modelRegistry: { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => false }, sessionId: "cleanup" });
|
|
227
229
|
setModelConfigService(svc);
|
|
228
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
230
|
+
rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
229
231
|
tmpRoot = "";
|
|
230
232
|
}
|
|
231
233
|
if (prevDataDir === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
|
|
@@ -305,8 +307,8 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
305
307
|
return Promise.resolve(makeMockResult());
|
|
306
308
|
}),
|
|
307
309
|
);
|
|
308
|
-
const oldModel = { id: "old-model", provider: "test",
|
|
309
|
-
const newModel = { id: "new-model", provider: "test",
|
|
310
|
+
const oldModel: ModelInfo = { id: "old-model", name: "Old Model", provider: "test", reasoning: false };
|
|
311
|
+
const newModel: ModelInfo = { id: "new-model", name: "New Model", provider: "test", reasoning: false };
|
|
310
312
|
const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel: oldModel };
|
|
311
313
|
const sar = new SubprocessAgentRunner(deps);
|
|
312
314
|
|
|
@@ -453,7 +455,7 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
453
455
|
} finally {
|
|
454
456
|
if (prevEnv === undefined) delete process.env.XYZ_AGENT_DATA_DIR;
|
|
455
457
|
else process.env.XYZ_AGENT_DATA_DIR = prevEnv;
|
|
456
|
-
rmSync(dataDir, { recursive: true, force: true });
|
|
458
|
+
rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
457
459
|
}
|
|
458
460
|
});
|
|
459
461
|
});
|
|
@@ -491,8 +493,8 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
|
|
|
491
493
|
// ────────────────────────────────────────────────
|
|
492
494
|
// T3.19: AgentCallOpts → ExecuteOptions 映射保真
|
|
493
495
|
// ────────────────────────────────────────────────
|
|
494
|
-
describe("T3.19
|
|
495
|
-
it("prompt → task, agent → agent, schemaEnv
|
|
496
|
+
describe("T3.19 直出保真(D6:映射点在 PiEngine.agentCallToExecuteOptions,SAR 直传零映射)", () => {
|
|
497
|
+
it("prompt → task, agent → agent, schemaEnv 透传(经 pi 边界直出)", async () => {
|
|
496
498
|
let capturedOpts: Record<string, unknown> | undefined;
|
|
497
499
|
const mockService = createMockService(
|
|
498
500
|
vi.fn().mockImplementation((opts: Record<string, unknown>) => {
|
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
|
|
7
|
-
import { cleanupTempPrompt, writePromptToTempFile } from "../temp-prompt.ts";
|
|
7
|
+
import { cleanupTempPrompt, writePromptToTempFile } from "../engine/engines/pi/temp-prompt.ts";
|
|
8
8
|
|
|
9
9
|
describe("writePromptToTempFile", () => {
|
|
10
10
|
it("创建临时目录 + 写入 prompt 文件", async () => {
|
|
@@ -89,7 +89,7 @@ vi.mock("../alive-store.ts", () => ({
|
|
|
89
89
|
writeAliveMarker: vi.fn(),
|
|
90
90
|
}));
|
|
91
91
|
|
|
92
|
-
vi.mock("../temp-prompt.ts", () => ({
|
|
92
|
+
vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
|
|
93
93
|
writePromptToTempFile: vi.fn(async (agent: string) => {
|
|
94
94
|
const safeName = agent.replace(/[^\w.-]+/g, "_");
|
|
95
95
|
return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
|
|
@@ -108,7 +108,7 @@ import {
|
|
|
108
108
|
runSpawn,
|
|
109
109
|
SPAWN_WATCHDOG_ENV,
|
|
110
110
|
type SessionRunnerContext,
|
|
111
|
-
} from "../session-runner.ts";
|
|
111
|
+
} from "../engine/engines/pi/session-runner.ts";
|
|
112
112
|
|
|
113
113
|
const mockSpawn = vi.mocked(spawn);
|
|
114
114
|
const mockExistsSync = vi.mocked(fs.existsSync);
|
|
@@ -160,8 +160,9 @@ function makeRecord() {
|
|
|
160
160
|
return createRecord("run-1", {
|
|
161
161
|
agent: "general-purpose",
|
|
162
162
|
model: "test-model",
|
|
163
|
-
mode: "
|
|
163
|
+
mode: "background",
|
|
164
164
|
task: "do something",
|
|
165
|
+
slug: "test",
|
|
165
166
|
startedAt: 1_000_000,
|
|
166
167
|
rootSessionId: "root-session",
|
|
167
168
|
parentRecordId: undefined,
|
|
@@ -351,8 +352,8 @@ describe("timeoutMs / signal abort → child.kill 端到端路径", () => {
|
|
|
351
352
|
expect(child.killSignal).toBe("SIGTERM");
|
|
352
353
|
|
|
353
354
|
// 子进程响应 SIGTERM 退出:exitCode 143(SIGTERM 终止形态)+ exit + close。
|
|
354
|
-
// exit 事件触发升级 timer clear
|
|
355
|
-
child.exitCode = 143;
|
|
355
|
+
// exit 事件触发升级 timer clear(公共 kill-chain 的 exit 等待 promise settle → timer clear)。
|
|
356
|
+
(child as unknown as { exitCode: number }).exitCode = 143;
|
|
356
357
|
child.emit("exit", 143);
|
|
357
358
|
child.stdout.end();
|
|
358
359
|
child.emit("close", 143);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it, vi } from "vitest";
|
|
7
7
|
|
|
8
|
-
import { createTurnLimiter } from "../turn-limiter.ts";
|
|
8
|
+
import { createTurnLimiter } from "../engine/engines/pi/turn-limiter.ts";
|
|
9
9
|
|
|
10
10
|
describe("SP-9: turn-limiter chatMode semantics (maxTurns reset per round)", () => {
|
|
11
11
|
describe("TC-1: chatMode 多轮后 maxTurns 每轮 reset(不累计)", () => {
|