@zhushanwen/subagent-core 0.6.0 → 0.7.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 +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- package/src/execution/__tests__/delivery-methods.test.ts +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/finalize-record.ts +25 -0
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/record-store.ts +29 -2
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -166
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -22,16 +22,10 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
22
22
|
}));
|
|
23
23
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
getChildByRecord: vi.fn(() => undefined),
|
|
30
|
-
registerSpawnedChildForRecord: vi.fn(),
|
|
31
|
-
spawnedChildren: new Map(),
|
|
32
|
-
killRecordChildWithEscalation: killChildSpy,
|
|
33
|
-
}));
|
|
34
|
-
|
|
25
|
+
// [W3 改写] 原 vi.mock(inproc pi 引擎目录/session-runner) 随删件消亡——chat 轮次走协议 seam
|
|
26
|
+
//(registerFakePiEngine 替身,kickOffChatRound 的 notify 门经 engine.run 应答驱动)。
|
|
27
|
+
import { registerFakePiEngine } from "./helpers/fake-engine-port.ts";
|
|
28
|
+
import { clearEngines } from "../engine/registry.ts";
|
|
35
29
|
import { bindNotifyLedgerHost, NOTIFY_LEDGER_CUSTOM_TYPE, _resetNotifyLedgerForTest } from "../notify-ledger.ts";
|
|
36
30
|
import { createRecord } from "../execution-record.ts";
|
|
37
31
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
@@ -95,8 +89,10 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
95
89
|
expect(notifyGateAllowsDelivery(undefined)).toBe(true);
|
|
96
90
|
});
|
|
97
91
|
|
|
98
|
-
it("kickOffChatRound
|
|
92
|
+
it("kickOffChatRound 应答回注不注入 parent-new closed records(notify 门白名单)", async () => {
|
|
99
93
|
const { agentDir, service, pi } = setup();
|
|
94
|
+
clearEngines();
|
|
95
|
+
const fake = registerFakePiEngine();
|
|
100
96
|
const record = createRecord("sa-gate-new", {
|
|
101
97
|
agent: "general-purpose",
|
|
102
98
|
model: "test/model",
|
|
@@ -107,34 +103,33 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
107
103
|
rootSessionId: "root-session",
|
|
108
104
|
controller: new AbortController(),
|
|
109
105
|
});
|
|
110
|
-
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound
|
|
106
|
+
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound 应答回注
|
|
111
107
|
record.closedReason = "parent-new";
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
turns: 0,
|
|
115
|
-
durationMs: 1,
|
|
116
|
-
success: false,
|
|
117
|
-
error: "closed due to parent-new",
|
|
118
|
-
sessionId: record.id,
|
|
119
|
-
toolCalls: [],
|
|
120
|
-
});
|
|
121
|
-
(service as unknown as Record<string, unknown>).runAndFinalize = stub;
|
|
122
|
-
// [D4 后形态] 轮次编排入口 kickOffChatRound(私有,bracket 调用先例见
|
|
123
|
-
// subagent-service-recovery-bounds.test.ts privateFn);opts 形参仍是 ExecuteOptions
|
|
124
|
-
//(task/slug),chat 分支由 ticket lossless 携带——task 声明形参不参与校验。
|
|
108
|
+
// [W3] 轮次编排入口 kickOffChatRound(私有,bracket 调用先例)——协议 run 发起后
|
|
109
|
+
// 挂起,编排性关闭先行,再模拟引擎应答(迟到回注被门拦)。
|
|
125
110
|
const kickOffChatRound = (
|
|
126
111
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
127
112
|
)["kickOffChatRound"];
|
|
128
|
-
|
|
129
|
-
|
|
113
|
+
const identity = {
|
|
114
|
+
agent: "general-purpose",
|
|
115
|
+
agentConfig: undefined,
|
|
116
|
+
resolved: { model: { id: "model", name: "Model", provider: "test", reasoning: false }, thinkingLevel: undefined },
|
|
117
|
+
};
|
|
118
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, identity, undefined, 1000);
|
|
119
|
+
await vi.waitFor(() => expect(fake.runs.length).toBe(1));
|
|
120
|
+
fake.runs[0]!.settle({ content: "late round text" });
|
|
130
121
|
await Promise.resolve();
|
|
131
122
|
await Promise.resolve();
|
|
132
123
|
expect(pi.sendMessage).not.toHaveBeenCalled();
|
|
124
|
+
service.dispose();
|
|
125
|
+
clearEngines();
|
|
133
126
|
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
134
127
|
});
|
|
135
128
|
|
|
136
|
-
it("kickOffChatRound
|
|
129
|
+
it("kickOffChatRound 应答回注对真实失败关闭(gc)仍通知", async () => {
|
|
137
130
|
const { agentDir, service, pi } = setup();
|
|
131
|
+
clearEngines();
|
|
132
|
+
const fake = registerFakePiEngine();
|
|
138
133
|
const record = createRecord("sa-gate-gc", {
|
|
139
134
|
agent: "general-purpose",
|
|
140
135
|
model: "test/model",
|
|
@@ -146,12 +141,20 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
146
141
|
controller: new AbortController(),
|
|
147
142
|
});
|
|
148
143
|
record.closedReason = "gc";
|
|
149
|
-
(service as unknown as Record<string, unknown>).runAndFinalize = vi.fn().mockResolvedValue({});
|
|
150
144
|
const kickOffChatRound = (
|
|
151
145
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
152
146
|
)["kickOffChatRound"];
|
|
153
|
-
|
|
147
|
+
const identity = {
|
|
148
|
+
agent: "general-purpose",
|
|
149
|
+
agentConfig: undefined,
|
|
150
|
+
resolved: { model: { id: "model", name: "Model", provider: "test", reasoning: false }, thinkingLevel: undefined },
|
|
151
|
+
};
|
|
152
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, identity, undefined, 1000);
|
|
153
|
+
await vi.waitFor(() => expect(fake.runs.length).toBe(1));
|
|
154
|
+
fake.runs[0]!.settle({ content: "round" });
|
|
154
155
|
await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalled());
|
|
156
|
+
service.dispose();
|
|
157
|
+
clearEngines();
|
|
155
158
|
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
156
159
|
});
|
|
157
160
|
});
|
|
@@ -25,15 +25,8 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
25
25
|
}));
|
|
26
26
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
27
27
|
|
|
28
|
-
// mock session-runner
|
|
29
|
-
|
|
30
|
-
runSpawn: vi.fn(),
|
|
31
|
-
killAllSpawnedChildren: vi.fn(),
|
|
32
|
-
getChildByRecord: vi.fn(() => undefined),
|
|
33
|
-
registerSpawnedChildForRecord: vi.fn(),
|
|
34
|
-
killRecordChildWithEscalation: vi.fn(),
|
|
35
|
-
spawnedChildren: new Map(),
|
|
36
|
-
}));
|
|
28
|
+
// [W3 改写] 原 vi.mock(inproc pi 引擎目录/session-runner) 随删件消亡——本文件只测
|
|
29
|
+
// getRecordForAction 守卫(纯内存判定,不触引擎链路),现存 import 无需再 mock 引擎替身。
|
|
37
30
|
|
|
38
31
|
import { createRecord } from "../execution-record.ts";
|
|
39
32
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
// src/execution/__tests__/subagent-service-recovery-bounds.test.ts
|
|
2
2
|
//
|
|
3
3
|
// [u-svc / T2] 服务侧回收上界与 kill 收敛:
|
|
4
|
-
// - T2④/LC-2:closeChatIdle / closeAfterRoundSettled / cancelBackground
|
|
5
|
-
// 收敛到 killRecordChildWithEscalation(spy
|
|
4
|
+
// - T2④/LC-2:closeChatIdle / closeAfterRoundSettled / cancelBackground 三条终止路径
|
|
5
|
+
// 收敛到 killRecordChildWithEscalation(spy 断言调用点与参数)+ [W3] 引擎侧终止
|
|
6
|
+
// 意图(协议 interact cancel/close fire-and-forget);
|
|
6
7
|
// - T2⑥/PS-1:disposeAllRecords 补三回收面(controller.abort + kill + disarm idle timer
|
|
7
8
|
// + disarm settled watchdog);
|
|
8
|
-
// - T2③/LC-1 + D9:热路径投递 armMidRoundNoProgress
|
|
9
|
-
// deliverChatMessage 的 interact
|
|
10
|
-
//
|
|
11
|
-
// 指引;agent_end 交棒收尾段的接线在 session-runner stdout pump,两路径共用);
|
|
12
|
-
// - T2⑧/PS-3:非 EPIPE 热路径写失败 re-arm idle timer(进程不再裸奔;挂载位 D2
|
|
13
|
-
// 下沉后在 PiEngine.deliverPrompt 的非 EPIPE catch——编排层无法区分错误类别)。
|
|
9
|
+
// - T2③/LC-1 + D9:热路径投递 armMidRoundNoProgress(挂载点在编排层
|
|
10
|
+
// deliverChatMessage 的 interact 受理点;中段无进展检测 + 到期处置:kill + 失败
|
|
11
|
+
// 终态化 + error 含 'settled watchdog' 标记与恢复指引)。
|
|
14
12
|
//
|
|
15
|
-
//
|
|
16
|
-
|
|
13
|
+
// [W3 改写] 替身从 inproc session-runner/stdin-writer mock 改为协议 seam
|
|
14
|
+
//(registerFakePiEngine):投递 = engine.interact(message) 受理;终止意图 =
|
|
15
|
+
// engine.interact(cancel/close)。T2⑧(非 EPIPE 写失败 re-arm idle timer)随
|
|
16
|
+
// inproc pi 引擎目录 删除归 pi-subagent-cli 包内(写失败语义在引擎进程内,core 已无
|
|
17
|
+
// 可泄漏的本地方案进程),core 侧不再可等价改写。
|
|
18
|
+
//
|
|
19
|
+
// mock 形态:真实 SubagentService + ServiceInternals cast 暴露 store + logger +
|
|
20
|
+
// spawned-children 的 kill 入口换 spy(importOriginal 保留镜像真实现)。
|
|
17
21
|
|
|
18
22
|
import * as fs from "node:fs";
|
|
19
23
|
import * as os from "node:os";
|
|
@@ -26,37 +30,15 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
26
30
|
}));
|
|
27
31
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
28
32
|
|
|
29
|
-
// killRecordChildWithEscalation 换 spy
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
killChildSpy: vi.fn(),
|
|
36
|
-
spawnedMap: map,
|
|
37
|
-
getChildByRecordMock: vi.fn((recordId: string) => map.get(recordId)),
|
|
38
|
-
};
|
|
33
|
+
// killRecordChildWithEscalation 换 spy(收敛调用点断言);镜像真实现保留。
|
|
34
|
+
const { killChildSpy } = vi.hoisted(() => ({ killChildSpy: vi.fn() }));
|
|
35
|
+
vi.mock("../engine/host/spawned-children.ts", async (importOriginal) => {
|
|
36
|
+
const actual = await importOriginal<typeof import("../engine/host/spawned-children.ts")>();
|
|
37
|
+
return { ...actual, killRecordChildWithEscalation: killChildSpy };
|
|
39
38
|
});
|
|
40
|
-
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
41
|
-
runSpawn: vi.fn(),
|
|
42
|
-
killAllSpawnedChildren: vi.fn(),
|
|
43
|
-
getChildByRecord: getChildByRecordMock,
|
|
44
|
-
registerSpawnedChildForRecord: vi.fn(),
|
|
45
|
-
spawnedChildren: spawnedMap,
|
|
46
|
-
killRecordChildWithEscalation: killChildSpy,
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
// sendPromptCommand 换 spy(控制热路径写成功 / 抛非 EPIPE 错误)。
|
|
50
|
-
const { sendPromptCommandMock } = vi.hoisted(() => ({ sendPromptCommandMock: vi.fn() }));
|
|
51
|
-
vi.mock("../engine/engines/pi/stdin-writer.ts", () => ({
|
|
52
|
-
sendPromptCommand: sendPromptCommandMock,
|
|
53
|
-
clearEpipeFailure: vi.fn(),
|
|
54
|
-
recordEpipeFailure: vi.fn(() => 1),
|
|
55
|
-
resetAllEpipeFailures: vi.fn(),
|
|
56
|
-
EPIPE_FAILURE_THRESHOLD: 3,
|
|
57
|
-
}));
|
|
58
39
|
|
|
59
|
-
import {
|
|
40
|
+
import { clearEngines } from "../engine/registry.ts";
|
|
41
|
+
import { registerFakePiEngine, type FakePiEnginePort } from "./helpers/fake-engine-port.ts";
|
|
60
42
|
import { createRecord } from "../execution-record.ts";
|
|
61
43
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
62
44
|
import type { RecordStore } from "../record-store.ts";
|
|
@@ -64,26 +46,19 @@ import { SubagentService } from "../subagent-service.ts";
|
|
|
64
46
|
import type { PiLike } from "../subagent-service.ts";
|
|
65
47
|
import { armSettledWatchdog, hasSettledWatchdog, SETTLED_MID_ROUND_NO_PROGRESS_MS, _resetSettledWatchdogsForTest } from "../settled-watchdog.ts";
|
|
66
48
|
import { armIdleTimer, hasIdleTimer, _resetLifecycleState } from "../lifecycle-manager.ts";
|
|
49
|
+
import { _resetCoreSpawnedChildrenMirrorForTest } from "../engine/host/spawned-children.ts";
|
|
67
50
|
import type { ExecutionRecord } from "../types.ts";
|
|
68
51
|
|
|
69
52
|
function makeTmpAgentDir(): string {
|
|
70
53
|
return fs.mkdtempSync(path.join(os.tmpdir(), "svc-recovery-bounds-"));
|
|
71
54
|
}
|
|
72
55
|
|
|
73
|
-
function makePi(): PiLike
|
|
74
|
-
appendEntry: ReturnType<typeof vi.fn>;
|
|
75
|
-
events: { emit: ReturnType<typeof vi.fn> };
|
|
76
|
-
sendMessage: ReturnType<typeof vi.fn>;
|
|
77
|
-
} {
|
|
56
|
+
function makePi(): PiLike {
|
|
78
57
|
return {
|
|
79
58
|
appendEntry: vi.fn(),
|
|
80
59
|
events: { emit: vi.fn() },
|
|
81
60
|
sendMessage: vi.fn(),
|
|
82
|
-
} as unknown as PiLike
|
|
83
|
-
appendEntry: ReturnType<typeof vi.fn>;
|
|
84
|
-
events: { emit: ReturnType<typeof vi.fn> };
|
|
85
|
-
sendMessage: ReturnType<typeof vi.fn>;
|
|
86
|
-
};
|
|
61
|
+
} as unknown as PiLike;
|
|
87
62
|
}
|
|
88
63
|
|
|
89
64
|
function makeRecord(overrides: Partial<ExecutionRecord> & { id?: string } = {}): ExecutionRecord {
|
|
@@ -109,14 +84,16 @@ interface ServiceInternals {
|
|
|
109
84
|
|
|
110
85
|
type MockPi = ReturnType<typeof makePi>;
|
|
111
86
|
|
|
112
|
-
function setup(): { agentDir: string; service: SubagentService; store: RecordStore; pi: MockPi } {
|
|
87
|
+
function setup(): { agentDir: string; service: SubagentService; store: RecordStore; pi: MockPi; fake: FakePiEnginePort } {
|
|
113
88
|
const agentDir = makeTmpAgentDir();
|
|
89
|
+
clearEngines();
|
|
90
|
+
const fake = registerFakePiEngine();
|
|
114
91
|
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
115
92
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
116
93
|
const pi = makePi();
|
|
117
94
|
service.initSession({ pi, sessionId: "root-session" });
|
|
118
95
|
const store = (service as unknown as ServiceInternals).store;
|
|
119
|
-
return { agentDir, service, store, pi };
|
|
96
|
+
return { agentDir, service, store, pi, fake };
|
|
120
97
|
}
|
|
121
98
|
|
|
122
99
|
/** bracket 调 private 方法(ServiceInternals cast 先例的扩展形态)。 */
|
|
@@ -125,8 +102,8 @@ function privateFn<K extends string>(service: SubagentService, key: K): (...args
|
|
|
125
102
|
}
|
|
126
103
|
|
|
127
104
|
/**
|
|
128
|
-
* message 投递入口(
|
|
129
|
-
* →
|
|
105
|
+
* message 投递入口([W3] 协议形态:编排层私有 deliverChatMessage → engine.interact(message);
|
|
106
|
+
* 冷路径 = engine_session_not_resumable → run chat + resume)。
|
|
130
107
|
*/
|
|
131
108
|
async function deliverChat(
|
|
132
109
|
service: SubagentService,
|
|
@@ -149,40 +126,49 @@ describe("T2④ service-side kill convergence", () => {
|
|
|
149
126
|
let service: SubagentService;
|
|
150
127
|
let store: RecordStore;
|
|
151
128
|
let pi: MockPi;
|
|
129
|
+
let fake: FakePiEnginePort;
|
|
152
130
|
|
|
153
131
|
beforeEach(() => {
|
|
154
132
|
vi.restoreAllMocks();
|
|
155
133
|
killChildSpy.mockClear();
|
|
156
|
-
|
|
157
|
-
({ agentDir, service, store, pi } = setup());
|
|
134
|
+
({ agentDir, service, store, pi, fake } = setup());
|
|
158
135
|
});
|
|
159
136
|
|
|
160
137
|
afterEach(() => {
|
|
138
|
+
service.dispose();
|
|
139
|
+
clearEngines();
|
|
161
140
|
_resetLifecycleState();
|
|
162
141
|
_resetSettledWatchdogsForTest();
|
|
163
|
-
|
|
142
|
+
_resetCoreSpawnedChildrenMirrorForTest();
|
|
164
143
|
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
165
144
|
});
|
|
166
145
|
|
|
167
|
-
it("cancelBackground (via cancel) routes through killRecordChildWithEscalation", async () => {
|
|
146
|
+
it("cancelBackground (via cancel) routes through killRecordChildWithEscalation + 引擎侧 cancel", async () => {
|
|
168
147
|
const record = makeRecord({ id: "sa-cancel" });
|
|
169
148
|
store.register(record);
|
|
170
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
171
149
|
const ok = service.cancel(record.id);
|
|
172
150
|
expect(ok).toBe(true);
|
|
173
151
|
expect(killChildSpy).toHaveBeenCalledWith(record.id, "cancelBackground");
|
|
152
|
+
// [W3] 实际终止在引擎进程内:协议 interact cancel fire-and-forget
|
|
153
|
+
await vi.waitFor(() => {
|
|
154
|
+
expect(fake.interacts.some((c) => c.action.kind === "cancel")).toBe(true);
|
|
155
|
+
});
|
|
174
156
|
});
|
|
175
157
|
|
|
176
158
|
it("closeChatIdle (via closeSubagent force:false on idle) routes through killRecordChildWithEscalation and disarms timers", async () => {
|
|
177
159
|
const record = makeRecord({ id: "sa-close-idle" });
|
|
178
160
|
store.register(record);
|
|
179
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
180
161
|
armIdleTimer(record.id, () => {}); // Path A:idle timer armed(进程保活)
|
|
181
162
|
armSettledWatchdog(record.id, () => {});
|
|
182
163
|
await service["closeSubagent"](record, false);
|
|
183
164
|
expect(killChildSpy).toHaveBeenCalledWith(record.id, "closeChatIdle");
|
|
184
165
|
expect(hasIdleTimer(record.id)).toBe(false);
|
|
185
166
|
expect(hasSettledWatchdog(record.id)).toBe(false);
|
|
167
|
+
// [W3] 引擎侧 close force(idle 进程回收)
|
|
168
|
+
await vi.waitFor(() => {
|
|
169
|
+
const close = fake.interacts.find((c) => c.action.kind === "close");
|
|
170
|
+
expect(close?.action).toMatchObject({ kind: "close", payload: { force: true } });
|
|
171
|
+
});
|
|
186
172
|
});
|
|
187
173
|
|
|
188
174
|
it("closeAfterRoundSettled routes through killRecordChildWithEscalation", async () => {
|
|
@@ -198,7 +184,6 @@ describe("T2④ service-side kill convergence", () => {
|
|
|
198
184
|
const idle = makeRecord({ id: "sa-dispose-idle" });
|
|
199
185
|
store.register(running);
|
|
200
186
|
store.register(idle);
|
|
201
|
-
spawnedMap.set(running.id, new FakeChild());
|
|
202
187
|
armIdleTimer(idle.id, () => {});
|
|
203
188
|
armSettledWatchdog(idle.id, () => {});
|
|
204
189
|
|
|
@@ -226,49 +211,42 @@ describe("T2③ hot-path settled watchdog", () => {
|
|
|
226
211
|
let service: SubagentService;
|
|
227
212
|
let store: RecordStore;
|
|
228
213
|
let pi: MockPi;
|
|
214
|
+
let fake: FakePiEnginePort;
|
|
229
215
|
|
|
230
216
|
beforeEach(() => {
|
|
231
217
|
vi.restoreAllMocks();
|
|
232
218
|
killChildSpy.mockClear();
|
|
233
|
-
|
|
234
|
-
// 放行依赖它——真实 kill 后 close 事件把句柄移出 spawnedChildren,语义相同。
|
|
235
|
-
killChildSpy.mockImplementation((recordId: string) => {
|
|
236
|
-
const c = spawnedMap.get(recordId) as FakeChild | undefined;
|
|
237
|
-
if (c) c.killed = true;
|
|
238
|
-
});
|
|
239
|
-
sendPromptCommandMock.mockReset();
|
|
240
|
-
sendPromptCommandMock.mockImplementation(() => {});
|
|
241
|
-
({ agentDir, service, store, pi } = setup());
|
|
219
|
+
({ agentDir, service, store, pi, fake } = setup());
|
|
242
220
|
});
|
|
243
221
|
|
|
244
222
|
afterEach(() => {
|
|
223
|
+
service.dispose();
|
|
224
|
+
clearEngines();
|
|
245
225
|
vi.useRealTimers();
|
|
246
226
|
_resetLifecycleState();
|
|
247
227
|
_resetSettledWatchdogsForTest();
|
|
248
|
-
|
|
228
|
+
_resetCoreSpawnedChildrenMirrorForTest();
|
|
249
229
|
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
250
230
|
});
|
|
251
231
|
|
|
252
232
|
it("arms the settled watchdog after a successful hot-path prompt", async () => {
|
|
253
233
|
const record = makeRecord({ id: "sa-hot-arm" });
|
|
254
234
|
store.register(record);
|
|
255
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
256
235
|
await deliverChat(service, record, "hello", false);
|
|
257
|
-
expect(
|
|
236
|
+
expect(fake.interacts.length).toBe(1);
|
|
258
237
|
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
259
238
|
});
|
|
260
239
|
|
|
261
|
-
it("onMidTimeout: kills the child, fails the round, error carries 'settled watchdog' marker and recovery hint", async () => {
|
|
240
|
+
it("onMidTimeout: kills the child (engine cancel), fails the round, error carries 'settled watchdog' marker and recovery hint", async () => {
|
|
262
241
|
// fake timers 必须先于 arm 生效(useFakeTimers 不接管已存在的真实 timer)
|
|
263
242
|
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
264
243
|
const record = makeRecord({ id: "sa-hot-timeout" });
|
|
265
244
|
store.register(record);
|
|
266
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
267
245
|
await deliverChat(service, record, "hello", false);
|
|
268
246
|
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
269
247
|
|
|
270
|
-
// [D9 两段式] 热路径 prompt
|
|
271
|
-
//
|
|
248
|
+
// [D9 两段式] 热路径 prompt 发出后挂中段:静默满中段窗长触发(本测试替身不驱动
|
|
249
|
+
// 协议事件,中段静默形态直达)
|
|
272
250
|
await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS + 1);
|
|
273
251
|
|
|
274
252
|
// kill 收敛入口被触发(onTimeout 第一步)
|
|
@@ -283,59 +261,11 @@ describe("T2③ hot-path settled watchdog", () => {
|
|
|
283
261
|
expect(record.result).toContain("action:'list'");
|
|
284
262
|
// 失败通知送达(内核直发路径)
|
|
285
263
|
expect(pi.sendMessage).toHaveBeenCalled();
|
|
286
|
-
const
|
|
264
|
+
const sendMessageCalls = (pi.sendMessage as unknown as ReturnType<typeof vi.fn>).mock.calls as Array<[{ content?: string }]>;
|
|
265
|
+
const notifyContent = sendMessageCalls[0]?.[0]?.content ?? "";
|
|
287
266
|
expect(notifyContent).toContain("settled watchdog");
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
describe("T2⑧ non-EPIPE hot-path failure re-arms idle timer", () => {
|
|
292
|
-
let agentDir: string;
|
|
293
|
-
let service: SubagentService;
|
|
294
|
-
let store: RecordStore;
|
|
295
|
-
|
|
296
|
-
beforeEach(() => {
|
|
297
|
-
vi.restoreAllMocks();
|
|
298
|
-
killChildSpy.mockClear();
|
|
299
|
-
({ agentDir, service, store } = setup());
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
afterEach(() => {
|
|
303
|
-
_resetLifecycleState();
|
|
304
|
-
_resetSettledWatchdogsForTest();
|
|
305
|
-
spawnedMap.clear();
|
|
306
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it("re-arms the idle timer (default duration) and rethrows on non-EPIPE write failure", async () => {
|
|
310
|
-
const record = makeRecord({ id: "sa-nonpipe" });
|
|
311
|
-
store.register(record);
|
|
312
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
313
|
-
sendPromptCommandMock.mockImplementation(() => {
|
|
314
|
-
throw new Error("write after end: ERR_STREAM_DESTROYED");
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
318
|
-
|
|
319
|
-
// 防泄漏前提恢复:入口 disarm 过的 idle timer 被 re-arm(进程不再裸奔)
|
|
320
|
-
expect(hasIdleTimer(record.id)).toBe(true);
|
|
321
|
-
expect(loggerMock.warn).toHaveBeenCalledWith(
|
|
322
|
-
expect.stringContaining("idle timer re-armed"),
|
|
323
|
-
expect.objectContaining({ detail: expect.stringContaining("ERR_STREAM_DESTROYED") }),
|
|
324
|
-
);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("falls back to DEFAULT idle duration (visible) when record.idleTimeoutMs is invalid", async () => {
|
|
328
|
-
const record = makeRecord({ id: "sa-nonpipe-bad" });
|
|
329
|
-
// 直接注入非法值模拟「入口校验前已存在的 record」/ 回归形态:re-arm 首选值非法 →
|
|
330
|
-
// 降级挂 DEFAULT(防御性兜底可见,不静默不挂)
|
|
331
|
-
(record as { idleTimeoutMs: number }).idleTimeoutMs = 3_000_000_000;
|
|
332
|
-
store.register(record);
|
|
333
|
-
spawnedMap.set(record.id, new FakeChild());
|
|
334
|
-
sendPromptCommandMock.mockImplementation(() => {
|
|
335
|
-
throw new Error("ERR_STREAM_DESTROYED");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
339
|
-
expect(hasIdleTimer(record.id)).toBe(true);
|
|
267
|
+
// [W3] 引擎侧终止意图(fire-and-forget;fake timers 下只断言受理动作已发出)
|
|
268
|
+
const cancel = fake.interacts.find((c) => c.action.kind === "cancel");
|
|
269
|
+
expect(cancel).toBeDefined();
|
|
340
270
|
});
|
|
341
271
|
});
|
|
@@ -323,7 +323,7 @@ describe("SubagentService", () => {
|
|
|
323
323
|
// [背景] Pi 单进程 session 串行接管。session A shutdown 发 SIGTERM 后、子进程彻底
|
|
324
324
|
// close 前(graceful shutdown 窗口几十~几百 ms),子进程的 trailing extension_ui_request
|
|
325
325
|
// 仍会被父进程 pump 解析,调到 A 的 handler 闭包(仍持有 A 的 ctx)。旧实现 dispose 不清
|
|
326
|
-
// uiRequestHandler,该闭包触发
|
|
326
|
+
// uiRequestHandler,该闭包触发 inproc UI 请求队列(已删) catch 分支打 `[subagents] uiRequestHandler
|
|
327
327
|
// threw` 误导性 console.error。dispose 现注入 stub(始终返回 {cancelled:true}),让 trailing
|
|
328
328
|
// ui_request 干净降级,不再走旧 handler 闭包。
|
|
329
329
|
//
|
|
@@ -376,7 +376,7 @@ describe("SubagentService", () => {
|
|
|
376
376
|
|
|
377
377
|
// 关键价值断言:无 "[subagents] uiRequestHandler threw" 误导性噪声
|
|
378
378
|
// (旧实现 dispose 不清 handler 时,trailing ui_request 走 staleHandler 抛错 →
|
|
379
|
-
//
|
|
379
|
+
// inproc UI 请求队列(已删) catch 打该日志。stub 覆盖后该路径不再触发。)
|
|
380
380
|
const subagentsErrors = errorSpy.mock.calls.filter(
|
|
381
381
|
(args: unknown[]) => typeof args[0] === "string" && args[0].includes("[subagents]"),
|
|
382
382
|
);
|
|
@@ -15,11 +15,16 @@ 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 {
|
|
18
|
+
import type { EnginePort, RunContext } from "../engine/port.ts";
|
|
19
19
|
import { clearEngines, registerEngine } from "../engine/registry.ts";
|
|
20
|
-
import type { ProbeReport } from "../engine/types.ts";
|
|
20
|
+
import type { AgentOutcome, EngineCapabilities, ProbeReport } from "../engine/types.ts";
|
|
21
21
|
import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
|
|
22
|
-
|
|
22
|
+
// W10(§2.10 ②):子进程句柄断言改读 core 侧状态镜像(host/spawned-children——
|
|
23
|
+
// 协议化后 spawnedChildren 持有方在引擎进程,core 消费镜像面;判据 pid 同构)。
|
|
24
|
+
import {
|
|
25
|
+
coreSpawnedChildrenMirror,
|
|
26
|
+
_resetCoreSpawnedChildrenMirrorForTest,
|
|
27
|
+
} from "../engine/host/spawned-children.ts";
|
|
23
28
|
import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
|
|
24
29
|
import type { SubagentService } from "../subagent-service.ts";
|
|
25
30
|
import type { ExecuteOptions } from "../types.ts";
|
|
@@ -88,13 +93,86 @@ function makeMockPiService() {
|
|
|
88
93
|
executeOpts.push(opts);
|
|
89
94
|
return { content: "from-pi", durationMs: 1, toolCalls: [] };
|
|
90
95
|
});
|
|
91
|
-
// [D4 聚合连带] SAR 构造器经 asEngineService
|
|
96
|
+
// [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取引擎服务面——fake 的
|
|
92
97
|
// face 即自身,getter 直接返回 self。
|
|
93
98
|
const service = { executeAndAwait } as unknown as SubagentService & { asEngineService: unknown };
|
|
94
99
|
(service as { asEngineService: unknown }).asEngineService = service;
|
|
100
|
+
// [W3] 登记当前 mock 服务——委托式替身 port 的工厂闭包按它路由(getEngine 惰性)。
|
|
101
|
+
currentPiService = service as unknown as SubagentService;
|
|
95
102
|
return { service, executeOpts, executeAndAwait };
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
// ── [W3] 委托式替身 pi port(原 inproc PiEngine 的 workflow 分支形态)──
|
|
106
|
+
// run() 还原引擎内 task→ExecuteOptions 映射(含 engine='pi' 留痕与 engineFallback
|
|
107
|
+
// 透传——两断言的契约面)后委托 mock 服务的 executeAndAwait。
|
|
108
|
+
|
|
109
|
+
let currentPiService: SubagentService | undefined;
|
|
110
|
+
|
|
111
|
+
function piTaskToExecuteOptions(task: AgentCallOpts, ctx: RunContext): ExecuteOptions {
|
|
112
|
+
const rawSlug = task.description ?? task.agent ?? "workflow-agent";
|
|
113
|
+
return {
|
|
114
|
+
task: task.prompt,
|
|
115
|
+
slug: rawSlug.length > 35 ? rawSlug.slice(0, 35) : rawSlug,
|
|
116
|
+
agent: task.agent,
|
|
117
|
+
model: task.model,
|
|
118
|
+
thinkingLevel: task.thinkingLevel,
|
|
119
|
+
skillPath: task.skillPath,
|
|
120
|
+
appendSystemPrompt: task.appendSystemPrompt,
|
|
121
|
+
schema: task.schema,
|
|
122
|
+
schemaEnv: task.schemaEnv,
|
|
123
|
+
maxTurns: task.maxTurns,
|
|
124
|
+
graceTurns: task.graceTurns,
|
|
125
|
+
ctxModel: ctx.ctxModel,
|
|
126
|
+
fork: task.fork,
|
|
127
|
+
worktree: task.worktree,
|
|
128
|
+
cwd: task.cwd,
|
|
129
|
+
conversation: task.conversation,
|
|
130
|
+
idleTimeoutMs: task.idleTimeoutMs,
|
|
131
|
+
// P4 引擎留痕(D9①):实际执行引擎 id 恒 pi(原 PiEngine.run workflow 分支同款)
|
|
132
|
+
engine: "pi",
|
|
133
|
+
...(ctx.engineFallback !== undefined ? { engineFallback: ctx.engineFallback } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function makeDelegatingPiPort(getService: () => SubagentService | undefined): EnginePort {
|
|
138
|
+
return {
|
|
139
|
+
id: "pi",
|
|
140
|
+
capabilities: (): EngineCapabilities => ({
|
|
141
|
+
schemaEnforcement: "native",
|
|
142
|
+
steer: "unsupported",
|
|
143
|
+
conversation: "native",
|
|
144
|
+
personaInjection: "flag",
|
|
145
|
+
eventGranularity: "stream",
|
|
146
|
+
sandbox: "emulated",
|
|
147
|
+
sessionRead: "full",
|
|
148
|
+
resume: "native",
|
|
149
|
+
interrupt: "kill-only",
|
|
150
|
+
permissionMode: "native",
|
|
151
|
+
maxTurns: true,
|
|
152
|
+
}),
|
|
153
|
+
probe: async (): Promise<ProbeReport> => ({ ok: true, engineVersion: "fake", checks: [] }),
|
|
154
|
+
run: async (task, ctx) => {
|
|
155
|
+
const service = getService();
|
|
156
|
+
if (service === undefined) throw new Error("mock pi service not installed");
|
|
157
|
+
const result = await (service as unknown as {
|
|
158
|
+
executeAndAwait: (opts: ExecuteOptions, signal?: AbortSignal) => Promise<AgentResult>;
|
|
159
|
+
}).executeAndAwait(piTaskToExecuteOptions(task, ctx), ctx.signal);
|
|
160
|
+
const outcome: AgentOutcome = {
|
|
161
|
+
content: result.content,
|
|
162
|
+
durationMs: result.durationMs,
|
|
163
|
+
error: result.error,
|
|
164
|
+
engineId: "pi",
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
handle: { data: { v: 1, engineId: "pi", sessionRef: {}, poolKey: "shared", adapterVersion: "fake-sar" } },
|
|
168
|
+
outcome,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
interact: async () => ({ ok: false, code: "engine_interact_failed", message: "not supported in this test" }),
|
|
172
|
+
read: async () => ({ engineId: "pi", turns: [], source: "outcome-only" }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
98
176
|
// ── 环境 ──
|
|
99
177
|
|
|
100
178
|
let tmpRoot: string;
|
|
@@ -128,6 +206,7 @@ function installModelService(cfg?: Record<string, unknown>): void {
|
|
|
128
206
|
}
|
|
129
207
|
|
|
130
208
|
beforeEach(() => {
|
|
209
|
+
_resetCoreSpawnedChildrenMirrorForTest();
|
|
131
210
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sar-routing-"));
|
|
132
211
|
agentDir = path.join(tmpRoot, "pi-agent");
|
|
133
212
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
@@ -135,6 +214,10 @@ beforeEach(() => {
|
|
|
135
214
|
prevDataDirEnv = process.env["XYZ_AGENT_DATA_DIR"];
|
|
136
215
|
process.env["XYZ_AGENT_DATA_DIR"] = path.join(tmpRoot, "engine-data");
|
|
137
216
|
clearEngines();
|
|
217
|
+
currentPiService = undefined;
|
|
218
|
+
// [U-2 一致性修复 → W3] resolveHostPiEnginePort 对已注册 port 原样返回——登记
|
|
219
|
+
// 委托式替身 pi port(registry 命中即路由命中;mock 服务的 DI 语义保持不变)。
|
|
220
|
+
registerEngine("pi", () => makeDelegatingPiPort(() => currentPiService));
|
|
138
221
|
});
|
|
139
222
|
|
|
140
223
|
afterEach(() => {
|
|
@@ -327,10 +410,11 @@ describe("SAR 路由集成(P4 验收 1/2/3)", () => {
|
|
|
327
410
|
// 引擎回调(真实引擎在 spawn 成功后同步调)→ 按 taskId('sa-' 记账 key)注册可见
|
|
328
411
|
const child = spawn(process.execPath, ["-e", ""]);
|
|
329
412
|
ctx?.onChildSpawned?.(child);
|
|
330
|
-
expect(getChildByRecord(ctx.taskId)).toBe(child);
|
|
331
|
-
//
|
|
413
|
+
expect(coreSpawnedChildrenMirror().getChildByRecord(ctx.taskId)?.pid).toBe(child.pid);
|
|
414
|
+
// 按句移除断言(W10 注):inproc 双模下子进程退出不回灌 host/childStateChanged
|
|
415
|
+
// (该通道是 cli 协议形态专属)——镜像移除断言由 protocol-blackbox 的
|
|
416
|
+
// childStateChanged 用例承载;此处等待 close 仅保证进程面收口不悬挂。
|
|
332
417
|
await new Promise<void>((resolve) => child.once("close", () => resolve()));
|
|
333
|
-
expect(getChildByRecord(ctx.taskId)).toBeUndefined();
|
|
334
418
|
});
|
|
335
419
|
|
|
336
420
|
// ── [D3-④] SAR 路径预检(capabilities 驱动;唯一有意行为变化 = zcode+worktree)──
|
|
@@ -384,3 +468,21 @@ describe("SAR 路由集成(P4 验收 1/2/3)", () => {
|
|
|
384
468
|
expect(pi.executeOpts[0]?.maxTurns).toBe(3);
|
|
385
469
|
});
|
|
386
470
|
});
|
|
471
|
+
|
|
472
|
+
// [U-2 一致性修复] D4 错误契约:pi 未注册(引擎包未装/发现失败)时普通 run 路径
|
|
473
|
+
// 不再静默直构 inproc——派发期显式 engine_not_found + 安装指引(与 zcode 对称)。
|
|
474
|
+
describe("pi 未注册 → engine_not_found(D4 契约,U-2 修复锚定)", () => {
|
|
475
|
+
it("pi 不在 registry → run 结果 error 含 engine_not_found + 安装指引,不静默走 inproc", async () => {
|
|
476
|
+
clearEngines(); // 覆盖 beforeEach 的 inproc pi 登记——模拟发现失败/未装包
|
|
477
|
+
installModelService();
|
|
478
|
+
const pi = makeMockPiService();
|
|
479
|
+
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
480
|
+
|
|
481
|
+
const result = await sar.run(makeOpts(), new AbortController().signal);
|
|
482
|
+
|
|
483
|
+
expect(result.error).toContain("engine_not_found");
|
|
484
|
+
expect(result.error).toContain("No engine packages were discovered");
|
|
485
|
+
expect(result.error).toMatch(/install an engine package/i);
|
|
486
|
+
expect(pi.executeAndAwait).not.toHaveBeenCalled();
|
|
487
|
+
});
|
|
488
|
+
});
|