@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/execution/__tests__/subagent-service-notify-gate.test.ts
|
|
2
2
|
//
|
|
3
3
|
// [u-svc / T4] 通知可靠性(subagent-service 侧三措施):
|
|
4
|
-
// - T4①/PS-2:
|
|
4
|
+
// - T4①/PS-2:kickOffChatRound.then notify 门按 closedReason 白名单放行——
|
|
5
5
|
// parent-new/parent-fork 不注入(可能已切换的)新 session(A-6 决策落实);
|
|
6
6
|
// - T4②/PS-4:idleTimeoutMs 非法值(>2^31-1 / 非有限值)在 spawn 入口同步 fail-fast,
|
|
7
7
|
// 错误含合法范围(不静默 clamp、不静默不挂);
|
|
@@ -23,7 +23,7 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
23
23
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
24
24
|
|
|
25
25
|
const { killChildSpy } = vi.hoisted(() => ({ killChildSpy: vi.fn() }));
|
|
26
|
-
vi.mock("../session-runner.ts", () => ({
|
|
26
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
27
27
|
runSpawn: vi.fn(),
|
|
28
28
|
killAllSpawnedChildren: vi.fn(),
|
|
29
29
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -76,7 +76,7 @@ function setup(initOverrides: Partial<{ isIdle: () => boolean }> = {}): {
|
|
|
76
76
|
pi: MockPi;
|
|
77
77
|
} {
|
|
78
78
|
const agentDir = makeTmpAgentDir();
|
|
79
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
79
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
80
80
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
81
81
|
const pi = makePi();
|
|
82
82
|
service.initSession({ pi, sessionId: "root-session", isIdle: initOverrides.isIdle });
|
|
@@ -95,18 +95,19 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
95
95
|
expect(notifyGateAllowsDelivery(undefined)).toBe(true);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("
|
|
98
|
+
it("kickOffChatRound.then does not inject parent-new closed records into the session", async () => {
|
|
99
99
|
const { agentDir, service, pi } = setup();
|
|
100
100
|
const record = createRecord("sa-gate-new", {
|
|
101
101
|
agent: "general-purpose",
|
|
102
102
|
model: "test/model",
|
|
103
103
|
mode: "background",
|
|
104
|
+
slug: "t",
|
|
104
105
|
task: "test",
|
|
105
106
|
startedAt: 1000,
|
|
106
107
|
rootSessionId: "root-session",
|
|
107
108
|
controller: new AbortController(),
|
|
108
109
|
});
|
|
109
|
-
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的
|
|
110
|
+
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound.then 回注
|
|
110
111
|
record.closedReason = "parent-new";
|
|
111
112
|
const stub = vi.fn().mockResolvedValue({
|
|
112
113
|
text: "",
|
|
@@ -118,23 +119,27 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
118
119
|
toolCalls: [],
|
|
119
120
|
});
|
|
120
121
|
(service as unknown as Record<string, unknown>).runAndFinalize = stub;
|
|
121
|
-
|
|
122
|
+
// [D4 后形态] 轮次编排入口 kickOffChatRound(私有,bracket 调用先例见
|
|
123
|
+
// subagent-service-recovery-bounds.test.ts privateFn);opts 形参仍是 ExecuteOptions
|
|
124
|
+
//(task/slug),chat 分支由 ticket lossless 携带——task 声明形参不参与校验。
|
|
125
|
+
const kickOffChatRound = (
|
|
122
126
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
123
|
-
)["
|
|
124
|
-
|
|
127
|
+
)["kickOffChatRound"];
|
|
128
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, {}, {}, undefined, 1000, undefined);
|
|
125
129
|
await vi.waitFor(() => expect(stub).toHaveBeenCalled());
|
|
126
130
|
await Promise.resolve();
|
|
127
131
|
await Promise.resolve();
|
|
128
132
|
expect(pi.sendMessage).not.toHaveBeenCalled();
|
|
129
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
133
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
130
134
|
});
|
|
131
135
|
|
|
132
|
-
it("
|
|
136
|
+
it("kickOffChatRound.then still notifies for real failure closures (gc)", async () => {
|
|
133
137
|
const { agentDir, service, pi } = setup();
|
|
134
138
|
const record = createRecord("sa-gate-gc", {
|
|
135
139
|
agent: "general-purpose",
|
|
136
140
|
model: "test/model",
|
|
137
141
|
mode: "background",
|
|
142
|
+
slug: "t",
|
|
138
143
|
task: "test",
|
|
139
144
|
startedAt: 1000,
|
|
140
145
|
rootSessionId: "root-session",
|
|
@@ -142,12 +147,12 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
142
147
|
});
|
|
143
148
|
record.closedReason = "gc";
|
|
144
149
|
(service as unknown as Record<string, unknown>).runAndFinalize = vi.fn().mockResolvedValue({});
|
|
145
|
-
const
|
|
150
|
+
const kickOffChatRound = (
|
|
146
151
|
service as unknown as Record<string, (...args: unknown[]) => void>
|
|
147
|
-
)["
|
|
148
|
-
|
|
152
|
+
)["kickOffChatRound"];
|
|
153
|
+
kickOffChatRound.call(service, record, { task: "t", slug: "gate" }, {}, {}, undefined, 1000, undefined);
|
|
149
154
|
await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalled());
|
|
150
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
155
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
151
156
|
});
|
|
152
157
|
});
|
|
153
158
|
|
|
@@ -160,30 +165,30 @@ describe("T4② idleTimeoutMs entry fail-fast", () => {
|
|
|
160
165
|
});
|
|
161
166
|
|
|
162
167
|
afterEach(() => {
|
|
163
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
168
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
164
169
|
});
|
|
165
170
|
|
|
166
171
|
it("execute rejects idleTimeoutMs above the setTimeout limit with the valid range in the message", async () => {
|
|
167
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: MAX_TIMER_DELAY_MS + 1 })).rejects.toThrow(
|
|
172
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: MAX_TIMER_DELAY_MS + 1 })).rejects.toThrow(
|
|
168
173
|
new RegExp(`${MAX_TIMER_DELAY_MS}`),
|
|
169
174
|
);
|
|
170
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow(/2\^31-1/);
|
|
175
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow(/2\^31-1/);
|
|
171
176
|
});
|
|
172
177
|
|
|
173
178
|
it("execute rejects non-finite idleTimeoutMs", async () => {
|
|
174
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: Number.NaN })).rejects.toThrow(/not a finite number/);
|
|
179
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: Number.NaN })).rejects.toThrow(/not a finite number/);
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
it("rejects before any record is created (no side effects)", async () => {
|
|
178
183
|
const internals = service as unknown as ServiceInternals;
|
|
179
|
-
await expect(service.executeAndAwait({ task: "x", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow();
|
|
184
|
+
await expect(service.executeAndAwait({ task: "x", slug: "test", idleTimeoutMs: 3_000_000_000 })).rejects.toThrow();
|
|
180
185
|
expect(internals.store.listRunning()).toHaveLength(0);
|
|
181
186
|
});
|
|
182
187
|
|
|
183
188
|
it("accepts valid values (0 = explicit disable, positive within limit)", async () => {
|
|
184
189
|
// 校验通过后执行链继续(runSpawn 被 mock,返回 undefined result 会在后续流程抛错/
|
|
185
190
|
// 返回——但绝不能是 idleTimeoutMs 校验错误)
|
|
186
|
-
await expect(service.execute({ task: "x", idleTimeoutMs: 0 })).rejects.not.toThrow(/idleTimeoutMs/);
|
|
191
|
+
await expect(service.execute({ task: "x", slug: "test", idleTimeoutMs: 0 })).rejects.not.toThrow(/idleTimeoutMs/);
|
|
187
192
|
});
|
|
188
193
|
});
|
|
189
194
|
|
|
@@ -199,7 +204,7 @@ describe("T4④ shutdown flush blocked → pending persisted for replay", () =>
|
|
|
199
204
|
|
|
200
205
|
afterEach(() => {
|
|
201
206
|
_resetNotifyLedgerForTest();
|
|
202
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
207
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
203
208
|
});
|
|
204
209
|
|
|
205
210
|
it("persists undelivered pending notifications as ledger entries on dispose when main agent is busy", () => {
|
|
@@ -256,7 +261,7 @@ describe("T4④ shutdown flush blocked → pending persisted for replay", () =>
|
|
|
256
261
|
// idle service:flush 投出(无门拦),dispose 无复写动作
|
|
257
262
|
const idleService = new SubagentService({
|
|
258
263
|
cwd: agentDir,
|
|
259
|
-
modelService: new ModelConfigService({ agentDir }),
|
|
264
|
+
modelService: new ModelConfigService({ agentDir, cwd: agentDir }),
|
|
260
265
|
});
|
|
261
266
|
const piIdle = makePi();
|
|
262
267
|
idleService.initSession({ pi: piIdle, sessionId: "root-session", isIdle: () => true });
|
|
@@ -26,7 +26,7 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
26
26
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
27
27
|
|
|
28
28
|
// mock session-runner(import 链需要 runSpawn/killAllSpawnedChildren/getChildByRecord 存在)
|
|
29
|
-
vi.mock("../session-runner.ts", () => ({
|
|
29
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
30
30
|
runSpawn: vi.fn(),
|
|
31
31
|
killAllSpawnedChildren: vi.fn(),
|
|
32
32
|
getChildByRecord: vi.fn(() => undefined),
|
|
@@ -59,11 +59,12 @@ function makePi(): PiLike {
|
|
|
59
59
|
} as unknown as PiLike;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** 暴露私有字段供测试注入身份。
|
|
62
|
+
/** 暴露私有字段供测试注入身份。[D3-⑤] 基线住进 execNesting(公共层
|
|
63
|
+
* ExecutionNestingContext),注入经 setBaseline 测试面。 */
|
|
63
64
|
interface ServiceInternals {
|
|
64
65
|
store: RecordStore;
|
|
65
66
|
sessionRootId: string | null;
|
|
66
|
-
|
|
67
|
+
execNesting: { setBaseline(state: { recordId: string | undefined; depth: number } | null): void };
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/** 构造一个 chatMode idle record(message/close action 的典型目标态)。 */
|
|
@@ -77,12 +78,12 @@ function makeRecord(
|
|
|
77
78
|
model: "test/model",
|
|
78
79
|
mode: "background",
|
|
79
80
|
task: "test",
|
|
80
|
-
slug: "test",
|
|
81
81
|
startedAt: 1000,
|
|
82
82
|
rootSessionId: sessionRootId,
|
|
83
83
|
chatMode: true,
|
|
84
84
|
controller: new AbortController(),
|
|
85
85
|
...overrides,
|
|
86
|
+
slug: overrides.slug ?? "test",
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -95,7 +96,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
95
96
|
|
|
96
97
|
beforeEach(() => {
|
|
97
98
|
agentDir = makeTmpAgentDir();
|
|
98
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
99
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
99
100
|
service = new SubagentService({ cwd: agentDir, modelService });
|
|
100
101
|
// initSession 建立 sessionRootId=sessionId(本 session 树的根)。execCtxBaseline 的
|
|
101
102
|
// 真实值由 initSession 从 env 读取(见 recursive-visibility-baseline.test.ts),但本测试
|
|
@@ -108,65 +109,65 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
108
109
|
|
|
109
110
|
afterEach(() => {
|
|
110
111
|
service.dispose();
|
|
111
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
112
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
112
113
|
});
|
|
113
114
|
|
|
114
115
|
// ---------- 身份 sa-A:直接父,放行孙级 B ----------
|
|
115
116
|
|
|
116
117
|
it("身份=sa-A(直接父)→ 放行操作孙级 record B(parentRecordId=sa-A)", () => {
|
|
117
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
118
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
118
119
|
store.register(recordB);
|
|
119
|
-
internals.
|
|
120
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
120
121
|
|
|
121
|
-
expect(service.getRecordForAction("sa-B")).toBe(recordB);
|
|
122
|
+
expect(service.chatActions.getRecordForAction("sa-B")).toBe(recordB);
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
// ---------- 身份 undefined(主进程):拒绝孙级,放行根层 ----------
|
|
125
126
|
|
|
126
127
|
it("身份=undefined(主进程)拒绝孙级 record B,错误含 'direct parent' + 'parent=sa-A'", () => {
|
|
127
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
128
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
128
129
|
store.register(recordB);
|
|
129
|
-
internals.
|
|
130
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
130
131
|
|
|
131
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
132
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
132
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
133
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
it("身份=undefined(主进程)放行顶层 record(parentRecordId=undefined 视为根层)", () => {
|
|
136
|
-
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "
|
|
137
|
+
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
137
138
|
store.register(topRecord);
|
|
138
|
-
internals.
|
|
139
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
139
140
|
|
|
140
|
-
expect(service.getRecordForAction("sa-top")).toBe(topRecord);
|
|
141
|
+
expect(service.chatActions.getRecordForAction("sa-top")).toBe(topRecord);
|
|
141
142
|
});
|
|
142
143
|
|
|
143
144
|
// ---------- 身份 sa-B(兄弟进程):拒绝孙级 ----------
|
|
144
145
|
|
|
145
146
|
it("身份=sa-B(兄弟进程)拒绝孙级 record B(parentRecordId=sa-A ≠ baseline sa-B)", () => {
|
|
146
|
-
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "
|
|
147
|
+
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
147
148
|
store.register(recordB);
|
|
148
|
-
internals.
|
|
149
|
+
internals.execNesting.setBaseline(SIBLING_B);
|
|
149
150
|
|
|
150
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
151
|
-
expect(() => service.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
151
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
152
|
+
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
152
153
|
});
|
|
153
154
|
|
|
154
155
|
// ---------- 身份缺省语义 + 更深递归 ----------
|
|
155
156
|
|
|
156
157
|
it("子进程(baseline=sa-A)拒绝顶层 record(身份缺省视为根层,仅主进程可操作)", () => {
|
|
157
|
-
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "
|
|
158
|
+
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
158
159
|
store.register(topRecord);
|
|
159
|
-
internals.
|
|
160
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
160
161
|
|
|
161
|
-
expect(() => service.getRecordForAction("sa-top")).toThrow(/direct parent/);
|
|
162
|
+
expect(() => service.chatActions.getRecordForAction("sa-top")).toThrow(/direct parent/);
|
|
162
163
|
});
|
|
163
164
|
|
|
164
165
|
it("P7 场景:主进程拒绝曾孙 record C(parentRecordId=sa-B),错误含 baseline=root + parent=sa-B", () => {
|
|
165
|
-
const recordC = makeRecord("sa-C", sessionRootId, { parentRecordId: "sa-B", depth: 3, status: "
|
|
166
|
+
const recordC = makeRecord("sa-C", sessionRootId, { parentRecordId: "sa-B", depth: 3, status: "running" });
|
|
166
167
|
store.register(recordC);
|
|
167
|
-
internals.
|
|
168
|
+
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
168
169
|
|
|
169
|
-
const fn = () => service.getRecordForAction("sa-C");
|
|
170
|
+
const fn = () => service.chatActions.getRecordForAction("sa-C");
|
|
170
171
|
expect(fn).toThrow(/direct parent/);
|
|
171
172
|
expect(fn).toThrow(/see \/subagents list, parent=sa-B/);
|
|
172
173
|
expect(fn).toThrow(/baseline=\(root\)/);
|
|
@@ -174,10 +175,10 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
174
175
|
|
|
175
176
|
it("rootSessionId 不匹配仍优先拒绝(直接父校验在 rootSessionId 校验之后,不绕过归属守卫)", () => {
|
|
176
177
|
// 跨 session 树的 record:rootSessionId 不匹配 → 走首个 throw(not found or not owned)
|
|
177
|
-
const foreignRecord = makeRecord("sa-foreign", "other-session", { parentRecordId: "sa-A", depth: 2, status: "
|
|
178
|
+
const foreignRecord = makeRecord("sa-foreign", "other-session", { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
178
179
|
store.register(foreignRecord);
|
|
179
|
-
internals.
|
|
180
|
+
internals.execNesting.setBaseline(PARENT_A);
|
|
180
181
|
|
|
181
|
-
expect(() => service.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
182
|
+
expect(() => service.chatActions.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
182
183
|
});
|
|
183
184
|
});
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
// 收敛到 killRecordChildWithEscalation(spy 断言调用点与参数);
|
|
6
6
|
// - T2⑥/PS-1:disposeAllRecords 补三回收面(controller.abort + kill + disarm idle timer
|
|
7
7
|
// + disarm settled watchdog);
|
|
8
|
-
// - T2③/LC-1
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// - T2③/LC-1 + D9:热路径投递 armMidRoundNoProgress(挂载点 D2 下沉后在编排层
|
|
9
|
+
// deliverChatMessage 的 interact 返回点;挂载中段无进展检测 + 到期 onMidTimeout/
|
|
10
|
+
// onSettleTimeout 处置:kill + 失败终态化 + error 含 'settled watchdog' 标记与恢复
|
|
11
|
+
// 指引;agent_end 交棒收尾段的接线在 session-runner stdout pump,两路径共用);
|
|
12
|
+
// - T2⑧/PS-3:非 EPIPE 热路径写失败 re-arm idle timer(进程不再裸奔;挂载位 D2
|
|
13
|
+
// 下沉后在 PiEngine.deliverPrompt 的非 EPIPE catch——编排层无法区分错误类别)。
|
|
11
14
|
//
|
|
12
15
|
// mock 形态沿用 subagent-service-message-close.test.ts(真实 SubagentService + ServiceInternals
|
|
13
16
|
// cast 暴露 store + mock session-runner/stdin-writer/logger)。
|
|
@@ -24,8 +27,8 @@ const { loggerMock } = vi.hoisted(() => ({
|
|
|
24
27
|
vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
25
28
|
|
|
26
29
|
// killRecordChildWithEscalation 换 spy(收敛调用点断言);spawnedChildren 用真 Map
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
//(热路径投递判定 / EPIPE 兜底按值删除的消费者);getChildByRecord 从同一 Map 查
|
|
31
|
+
//(mock 工厂与测试共享同一实例)。D2 后模块物理下沉 engines/pi/(mock 路径随迁)。
|
|
29
32
|
const { killChildSpy, spawnedMap, getChildByRecordMock } = vi.hoisted(() => {
|
|
30
33
|
const map = new Map<string, unknown>();
|
|
31
34
|
return {
|
|
@@ -34,7 +37,7 @@ const { killChildSpy, spawnedMap, getChildByRecordMock } = vi.hoisted(() => {
|
|
|
34
37
|
getChildByRecordMock: vi.fn((recordId: string) => map.get(recordId)),
|
|
35
38
|
};
|
|
36
39
|
});
|
|
37
|
-
vi.mock("../session-runner.ts", () => ({
|
|
40
|
+
vi.mock("../engine/engines/pi/session-runner.ts", () => ({
|
|
38
41
|
runSpawn: vi.fn(),
|
|
39
42
|
killAllSpawnedChildren: vi.fn(),
|
|
40
43
|
getChildByRecord: getChildByRecordMock,
|
|
@@ -45,7 +48,7 @@ vi.mock("../session-runner.ts", () => ({
|
|
|
45
48
|
|
|
46
49
|
// sendPromptCommand 换 spy(控制热路径写成功 / 抛非 EPIPE 错误)。
|
|
47
50
|
const { sendPromptCommandMock } = vi.hoisted(() => ({ sendPromptCommandMock: vi.fn() }));
|
|
48
|
-
vi.mock("../stdin-writer.ts", () => ({
|
|
51
|
+
vi.mock("../engine/engines/pi/stdin-writer.ts", () => ({
|
|
49
52
|
sendPromptCommand: sendPromptCommandMock,
|
|
50
53
|
clearEpipeFailure: vi.fn(),
|
|
51
54
|
recordEpipeFailure: vi.fn(() => 1),
|
|
@@ -59,7 +62,7 @@ import { ModelConfigService } from "../model-config-service.ts";
|
|
|
59
62
|
import type { RecordStore } from "../record-store.ts";
|
|
60
63
|
import { SubagentService } from "../subagent-service.ts";
|
|
61
64
|
import type { PiLike } from "../subagent-service.ts";
|
|
62
|
-
import { armSettledWatchdog, hasSettledWatchdog,
|
|
65
|
+
import { armSettledWatchdog, hasSettledWatchdog, SETTLED_MID_ROUND_NO_PROGRESS_MS, _resetSettledWatchdogsForTest } from "../settled-watchdog.ts";
|
|
63
66
|
import { armIdleTimer, hasIdleTimer, _resetLifecycleState } from "../lifecycle-manager.ts";
|
|
64
67
|
import type { ExecutionRecord } from "../types.ts";
|
|
65
68
|
|
|
@@ -108,7 +111,7 @@ type MockPi = ReturnType<typeof makePi>;
|
|
|
108
111
|
|
|
109
112
|
function setup(): { agentDir: string; service: SubagentService; store: RecordStore; pi: MockPi } {
|
|
110
113
|
const agentDir = makeTmpAgentDir();
|
|
111
|
-
const modelService = new ModelConfigService({ agentDir });
|
|
114
|
+
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
112
115
|
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
113
116
|
const pi = makePi();
|
|
114
117
|
service.initSession({ pi, sessionId: "root-session" });
|
|
@@ -121,6 +124,26 @@ function privateFn<K extends string>(service: SubagentService, key: K): (...args
|
|
|
121
124
|
return (service as unknown as Record<string, (...args: never[]) => unknown>)[key];
|
|
122
125
|
}
|
|
123
126
|
|
|
127
|
+
/**
|
|
128
|
+
* message 投递入口(D2 后形态:编排层私有 deliverChatMessage → PiEngine.interactRecord
|
|
129
|
+
* → deliverPrompt;旧直调形态 deliverMessage 已随协议知识下沉删除)。
|
|
130
|
+
*/
|
|
131
|
+
async function deliverChat(
|
|
132
|
+
service: SubagentService,
|
|
133
|
+
record: ExecutionRecord,
|
|
134
|
+
text: string,
|
|
135
|
+
interrupt: boolean,
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
return (
|
|
138
|
+
privateFn(service, "deliverChatMessage") as (
|
|
139
|
+
this: SubagentService,
|
|
140
|
+
r: ExecutionRecord,
|
|
141
|
+
t: string,
|
|
142
|
+
i: boolean,
|
|
143
|
+
) => Promise<void>
|
|
144
|
+
).call(service, record, text, interrupt);
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
describe("T2④ service-side kill convergence", () => {
|
|
125
148
|
let agentDir: string;
|
|
126
149
|
let service: SubagentService;
|
|
@@ -138,7 +161,7 @@ describe("T2④ service-side kill convergence", () => {
|
|
|
138
161
|
_resetLifecycleState();
|
|
139
162
|
_resetSettledWatchdogsForTest();
|
|
140
163
|
spawnedMap.clear();
|
|
141
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
164
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
142
165
|
});
|
|
143
166
|
|
|
144
167
|
it("cancelBackground (via cancel) routes through killRecordChildWithEscalation", async () => {
|
|
@@ -156,7 +179,7 @@ describe("T2④ service-side kill convergence", () => {
|
|
|
156
179
|
spawnedMap.set(record.id, new FakeChild());
|
|
157
180
|
armIdleTimer(record.id, () => {}); // Path A:idle timer armed(进程保活)
|
|
158
181
|
armSettledWatchdog(record.id, () => {});
|
|
159
|
-
await service
|
|
182
|
+
await service["closeSubagent"](record, false);
|
|
160
183
|
expect(killChildSpy).toHaveBeenCalledWith(record.id, "closeChatIdle");
|
|
161
184
|
expect(hasIdleTimer(record.id)).toBe(false);
|
|
162
185
|
expect(hasSettledWatchdog(record.id)).toBe(false);
|
|
@@ -223,28 +246,30 @@ describe("T2③ hot-path settled watchdog", () => {
|
|
|
223
246
|
_resetLifecycleState();
|
|
224
247
|
_resetSettledWatchdogsForTest();
|
|
225
248
|
spawnedMap.clear();
|
|
226
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
249
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
227
250
|
});
|
|
228
251
|
|
|
229
252
|
it("arms the settled watchdog after a successful hot-path prompt", async () => {
|
|
230
253
|
const record = makeRecord({ id: "sa-hot-arm" });
|
|
231
254
|
store.register(record);
|
|
232
255
|
spawnedMap.set(record.id, new FakeChild());
|
|
233
|
-
await service
|
|
256
|
+
await deliverChat(service, record, "hello", false);
|
|
234
257
|
expect(sendPromptCommandMock).toHaveBeenCalledTimes(1);
|
|
235
258
|
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
236
259
|
});
|
|
237
260
|
|
|
238
|
-
it("
|
|
261
|
+
it("onMidTimeout: kills the child, fails the round, error carries 'settled watchdog' marker and recovery hint", async () => {
|
|
239
262
|
// fake timers 必须先于 arm 生效(useFakeTimers 不接管已存在的真实 timer)
|
|
240
263
|
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
241
264
|
const record = makeRecord({ id: "sa-hot-timeout" });
|
|
242
265
|
store.register(record);
|
|
243
266
|
spawnedMap.set(record.id, new FakeChild());
|
|
244
|
-
await service
|
|
267
|
+
await deliverChat(service, record, "hello", false);
|
|
245
268
|
expect(hasSettledWatchdog(record.id)).toBe(true);
|
|
246
269
|
|
|
247
|
-
|
|
270
|
+
// [D9 两段式] 热路径 prompt 发出后挂中段:静默满中段窗长触发(本测试 mock 了
|
|
271
|
+
// session-runner,无 stdout pump 驱动事件刷新/交棒,中段静默形态直达)
|
|
272
|
+
await vi.advanceTimersByTimeAsync(SETTLED_MID_ROUND_NO_PROGRESS_MS + 1);
|
|
248
273
|
|
|
249
274
|
// kill 收敛入口被触发(onTimeout 第一步)
|
|
250
275
|
expect(killChildSpy).toHaveBeenCalledWith(record.id, "settled watchdog (hot path)");
|
|
@@ -278,7 +303,7 @@ describe("T2⑧ non-EPIPE hot-path failure re-arms idle timer", () => {
|
|
|
278
303
|
_resetLifecycleState();
|
|
279
304
|
_resetSettledWatchdogsForTest();
|
|
280
305
|
spawnedMap.clear();
|
|
281
|
-
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
306
|
+
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
282
307
|
});
|
|
283
308
|
|
|
284
309
|
it("re-arms the idle timer (default duration) and rethrows on non-EPIPE write failure", async () => {
|
|
@@ -289,7 +314,7 @@ describe("T2⑧ non-EPIPE hot-path failure re-arms idle timer", () => {
|
|
|
289
314
|
throw new Error("write after end: ERR_STREAM_DESTROYED");
|
|
290
315
|
});
|
|
291
316
|
|
|
292
|
-
await expect(service
|
|
317
|
+
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
293
318
|
|
|
294
319
|
// 防泄漏前提恢复:入口 disarm 过的 idle timer 被 re-arm(进程不再裸奔)
|
|
295
320
|
expect(hasIdleTimer(record.id)).toBe(true);
|
|
@@ -303,14 +328,14 @@ describe("T2⑧ non-EPIPE hot-path failure re-arms idle timer", () => {
|
|
|
303
328
|
const record = makeRecord({ id: "sa-nonpipe-bad" });
|
|
304
329
|
// 直接注入非法值模拟「入口校验前已存在的 record」/ 回归形态:re-arm 首选值非法 →
|
|
305
330
|
// 降级挂 DEFAULT(防御性兜底可见,不静默不挂)
|
|
306
|
-
record.idleTimeoutMs = 3_000_000_000;
|
|
331
|
+
(record as { idleTimeoutMs: number }).idleTimeoutMs = 3_000_000_000;
|
|
307
332
|
store.register(record);
|
|
308
333
|
spawnedMap.set(record.id, new FakeChild());
|
|
309
334
|
sendPromptCommandMock.mockImplementation(() => {
|
|
310
335
|
throw new Error("ERR_STREAM_DESTROYED");
|
|
311
336
|
});
|
|
312
337
|
|
|
313
|
-
await expect(service
|
|
338
|
+
await expect(deliverChat(service, record, "hello", false)).rejects.toThrow(/ERR_STREAM_DESTROYED/);
|
|
314
339
|
expect(hasIdleTimer(record.id)).toBe(true);
|
|
315
340
|
});
|
|
316
341
|
});
|