@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// [D4-③ 冷路径复活职责轴] message action 冷查/复活链(原 SubagentService 私有的
|
|
2
|
+
// findColdLookupCandidate / assertReconnectAllowed / resurrectColdRecord /
|
|
3
|
+
// coldLookupForAction + isReconnectableClosed 判定)整体搬移至 record-store 邻接处
|
|
4
|
+
// (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改跨重启重建 /
|
|
5
|
+
// 透明重生回边 / 可重连守卫语义,只改本文件;Service 的 getRecordForAction 保留
|
|
6
|
+
// 归属校验编排(内存未命中分支委托 coldLookupForAction)。
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
|
|
10
|
+
import { findForeignLiveInstance, writeAliveMarker } from "./alive-store.ts";
|
|
11
|
+
import { createRecord, resurrectClosed } from "./execution-record.ts";
|
|
12
|
+
import type { StatusFilter } from "./record-store.ts";
|
|
13
|
+
import type { ExecutionRecord, SubagentRecord } from "./types.ts";
|
|
14
|
+
import { isReconnectableFinalReason, ResurrectDeniedError } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
/** SP-2 冷路径按 id 查 record 的 collectRecords 扫描上限(全扫兜底的容量 cap)。
|
|
17
|
+
* 原定义于 subagent-service.ts,随冷查链搬移;Service.lookupRecordAnyState
|
|
18
|
+
* (全态查询)同样消费本常量。 */
|
|
19
|
+
export const COLD_LOOKUP_SCAN_LIMIT = 1000;
|
|
20
|
+
|
|
21
|
+
/** 冷查/复活的依赖注入(Service 侧供给,store 查询 + 归属上下文)。 */
|
|
22
|
+
export interface ColdResurrectDeps {
|
|
23
|
+
/** store 的 idToFile 索引直查(light 快照)。 */
|
|
24
|
+
findLightById: (id: string) => SubagentRecord | undefined;
|
|
25
|
+
/** store 的磁盘全扫(内存未命中 / 索引未热时兜底)。rootFilter 恒 undefined
|
|
26
|
+
* (冷查不做 root 过滤——归属校验在候选定位之后,见 coldLookupForAction)。 */
|
|
27
|
+
collectRecords: (limit: number, statusFilter: StatusFilter, rootFilter: undefined) => SubagentRecord[];
|
|
28
|
+
/** 重建 record 注册进内存 store。 */
|
|
29
|
+
register: (record: ExecutionRecord) => void;
|
|
30
|
+
/** 重建后的 transition entry 上报(live ≡ reload 等价性)。 */
|
|
31
|
+
reportRecordTransition: (record: ExecutionRecord) => void;
|
|
32
|
+
/** 当前所属根 session id(归属校验用,运行时可变——initSession 建立)。 */
|
|
33
|
+
getSessionRootId: () => string | null;
|
|
34
|
+
/** 本进程嵌套基线 recordId(直接父校验用;根进程 undefined)。 */
|
|
35
|
+
getBaselineRecordId: () => string | undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 冷查候选定位(coldLookupForAction 步骤 1):idToFile 索引直查 running 命中,
|
|
39
|
+
* 未命中再全目录 collectRecords 兜底(running,或 allowReconnect 且可重连 closed)。
|
|
40
|
+
*
|
|
41
|
+
* [T5③ / PS-7b] running 候选异进程活实例守卫:冷查 running 候选(跨重启 / 内存重建)
|
|
42
|
+
* 此前不经任何探针直接 resurrect + resume spawn——若其 .alive marker 仍指向活着的
|
|
43
|
+
* 异进程实例(父进程重启后旧子进程尚存的窗口),resume 会 spawn 第二个 pi 子进程
|
|
44
|
+
* 写同一 session JSONL(本代码最忌惮的双写者形态,v4 A-5/P7 事故模式)。closed 候选
|
|
45
|
+
* 的同款守卫已在 assertReconnectAllowed(v8.5 D);本守卫闭合 running 候选的防御
|
|
46
|
+
* 不对称。marker 的 pid 是子进程 pi 的 pid(非父进程),本进程持有的 running record
|
|
47
|
+
* 恒在内存(archive 才移出),可达本冷查分支的 running 候选必然来自磁盘重建——
|
|
48
|
+
* 探针命中即拒绝(ResurrectDeniedError,与 closed 候选守卫同异常类型,错误含 pid
|
|
49
|
+
* 与恢复指引)。 */
|
|
50
|
+
function findColdLookupCandidate(
|
|
51
|
+
deps: ColdResurrectDeps,
|
|
52
|
+
id: string,
|
|
53
|
+
allowReconnect: boolean,
|
|
54
|
+
): SubagentRecord | undefined {
|
|
55
|
+
const direct = deps.findLightById(id);
|
|
56
|
+
const found =
|
|
57
|
+
(direct?.status === "running" ? direct : undefined) ??
|
|
58
|
+
deps
|
|
59
|
+
.collectRecords(COLD_LOOKUP_SCAN_LIMIT, "all", undefined)
|
|
60
|
+
.find((r) => r.id === id && (r.status === "running" || (allowReconnect && isReconnectableClosed(r))));
|
|
61
|
+
if (found?.status === "running" && found.sessionFile) {
|
|
62
|
+
const foreign = findForeignLiveInstance(found.sessionFile);
|
|
63
|
+
if (foreign) {
|
|
64
|
+
throw new ResurrectDeniedError(
|
|
65
|
+
`subagent ${id} is currently running in another process instance (pid ${foreign.pid}, ` +
|
|
66
|
+
`startedAt=${new Date(foreign.startedAt).toISOString()}); resuming here would double-write ${found.sessionFile}. ` +
|
|
67
|
+
`Recovery: retry once that process exits; if it never exits, action:'close' this subagent, then action:'start' a fresh one.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return found;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** [v8.5 D] 冷查候选过滤:closed 且死因落在可重连集。判定源 = closedReason(buildRecord
|
|
75
|
+
* 归一化后的对外字段:A 档真实死因直通、旧空 sidecar 兑底 disconnected——SubagentRecord
|
|
76
|
+
* 不暴露 raw finalizedReason);cancelled/user-close/gc 等主动关闭与自然完成死因天然不在集合内。
|
|
77
|
+
* 防线在集合本身而非调用点。 */
|
|
78
|
+
function isReconnectableClosed(r: SubagentRecord): boolean {
|
|
79
|
+
return isReconnectableFinalReason(r.closedReason);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 可重连守卫(coldLookupForAction 步骤 2,[v8.5 D]):先于任何状态突变与注册。
|
|
83
|
+
* worktree 绑定丢失 / 异进程活实例以 ResurrectDeniedError 抛出(endedMessageGuard
|
|
84
|
+
* 必须原样透传,不得改写为 fork-from 指引误导 agent 走已被判死的通道);拒绝时
|
|
85
|
+
* 内存不得残留该记录(findRecord 契约)。 */
|
|
86
|
+
function assertReconnectAllowed(found: SubagentRecord, id: string): void {
|
|
87
|
+
if (found.status !== "closed") return;
|
|
88
|
+
if (found.worktree === true) {
|
|
89
|
+
throw new ResurrectDeniedError(
|
|
90
|
+
`subagent ${id} cannot be transparently resumed: it was created with worktree isolation, ` +
|
|
91
|
+
`and its worktree checkout no longer exists after restart (resuming in place would make spawn cwd fall back to the main repo). ` +
|
|
92
|
+
`Recovery: action:'start' a fresh subagent (with a new worktree if isolation is still needed); ` +
|
|
93
|
+
`its conversation history remains intact at ${found.sessionFile}.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const foreign = found.sessionFile ? findForeignLiveInstance(found.sessionFile) : undefined;
|
|
97
|
+
if (foreign) {
|
|
98
|
+
throw new ResurrectDeniedError(
|
|
99
|
+
`subagent ${id} is not transparently resumable: its previous instance still finishing in another process ` +
|
|
100
|
+
`(pid ${foreign.pid}, startedAt=${new Date(foreign.startedAt).toISOString()}). ` +
|
|
101
|
+
`Resuming in place would double-write ${found.sessionFile}. ` +
|
|
102
|
+
`Recovery: retry once that process exits; if it never exits, action:'start' a fresh subagent and treat the history at ${found.sessionFile} as read-only reference.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 磁盘候选重建为可变 record 并 register + 上报(coldLookupForAction 步骤 4)。 */
|
|
108
|
+
function resurrectColdRecord(
|
|
109
|
+
deps: ColdResurrectDeps,
|
|
110
|
+
found: SubagentRecord,
|
|
111
|
+
id: string,
|
|
112
|
+
): ExecutionRecord {
|
|
113
|
+
const record = createRecord(id, {
|
|
114
|
+
agent: found.agent,
|
|
115
|
+
model: found.model,
|
|
116
|
+
thinkingLevel: found.thinkingLevel,
|
|
117
|
+
mode: found.mode,
|
|
118
|
+
task: found.task,
|
|
119
|
+
slug: found.slug,
|
|
120
|
+
startedAt: found.startedAt,
|
|
121
|
+
rootSessionId: found.rootSessionId,
|
|
122
|
+
parentRecordId: found.parentRecordId,
|
|
123
|
+
depth: found.depth,
|
|
124
|
+
// [v4 A-3] 跨重启恢复入口——message 路径磁盘重建无条件置 chatMode=true(现状机制,
|
|
125
|
+
// V3 方案 A 方向兑现)。改动此处必须带 S3 回归场景(跨重启 message 续聊验证)。
|
|
126
|
+
// V3 SP-5 探针定案:机制已存在,本注释即定案,不再悬置。
|
|
127
|
+
chatMode: true,
|
|
128
|
+
controller: new AbortController(),
|
|
129
|
+
});
|
|
130
|
+
record.sessionFile = found.sessionFile;
|
|
131
|
+
record.round = found.round;
|
|
132
|
+
// [review round2] 跨重启 worktree 绑定丢失防护:原 record 创建时启用了 worktree 隔离
|
|
133
|
+
//(session entry 的 worktree 标志),但 WorktreeHandle 不可序列化、重建后恒缺失。
|
|
134
|
+
// 标记 hadWorktree,冷路径续轮守卫据此拒绝续聊(防 spawn cwd 静默回落主 repo 破坏
|
|
135
|
+
// 隔离——正是 worktree 要防的并发写冲突场景)。close 不受影响(closeChatIdle 走
|
|
136
|
+
// doFinalizeRecord,泄漏的 worktree 由 reaper 兜底回收)。
|
|
137
|
+
record.hadWorktree = found.worktree === true;
|
|
138
|
+
// [v8.5 D] 透明重生回边:独立函数不走 tryTransition 单向语义(closed 单向性对正常
|
|
139
|
+
// 执行流完整保留);准入唯一依据 = A 档 sidecar 真实死因 ∈ 可重连集。死亡语义位由
|
|
140
|
+
// resurrectClosed 清除;register 后立刻上报 transition entry,live/reload 视图同步
|
|
141
|
+
// 翻回 running(等价性由 applyEntry reducer 保证,对齐 SP-2 重建即报告先例)。
|
|
142
|
+
const wasClosed = found.status !== "running";
|
|
143
|
+
if (wasClosed) {
|
|
144
|
+
// [review MF-8] 磁盘终态位同步翻转:record-store buildRecord 分支 2(.finalized
|
|
145
|
+
// 存在 → closed)优先级高于 .alive 活态分支 3,重生若不删 sidecar,任何磁盘扫描
|
|
146
|
+
// (异进程 / reload / session-reader)都会把本进程内存里 running 的 record 报成
|
|
147
|
+
// closed/disconnected——破坏 live ≡ reload,且为跨进程二次 resurrect 开门。
|
|
148
|
+
// best-effort 对齐 BC-4 语义;.alive 刷新为当前进程(后续 resume spawn 会覆盖写)。
|
|
149
|
+
if (record.sessionFile) {
|
|
150
|
+
try {
|
|
151
|
+
fs.rmSync(`${record.sessionFile}.finalized`, { force: true });
|
|
152
|
+
writeAliveMarker(record.sessionFile, { pid: process.pid, id, startedAt: Date.now() });
|
|
153
|
+
} catch (_e) {
|
|
154
|
+
void _e; // best-effort:sidecar 翻转失败不阻断重生主流程
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
resurrectClosed(record);
|
|
158
|
+
}
|
|
159
|
+
deps.register(record);
|
|
160
|
+
if (wasClosed) {
|
|
161
|
+
deps.reportRecordTransition(record);
|
|
162
|
+
}
|
|
163
|
+
return record;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** [D4-③] 冷查编排(原 Service.coldLookupForAction):getRecordForAction 内存未命中
|
|
167
|
+
* 分支——候选定位 → 可重连守卫 → 归属/直接父校验 → 重建注册。
|
|
168
|
+
* @returns 重建的 record;磁盘也无则 undefined
|
|
169
|
+
* @throws ResurrectDeniedError 可重连候选被 worktree/异进程活实例守卫拦截
|
|
170
|
+
* @throws Error parentRecordId 跨层不匹配(direct parent 错误,与外层校验同文案) */
|
|
171
|
+
export function coldLookupForAction(
|
|
172
|
+
deps: ColdResurrectDeps,
|
|
173
|
+
id: string,
|
|
174
|
+
allowReconnect: boolean,
|
|
175
|
+
): ExecutionRecord | undefined {
|
|
176
|
+
const found = findColdLookupCandidate(deps, id, allowReconnect);
|
|
177
|
+
if (!found) return undefined;
|
|
178
|
+
// [v8.5 D] 可重连候选的守卫先于任何状态突变与注册(细节见 assertReconnectAllowed)
|
|
179
|
+
assertReconnectAllowed(found, id);
|
|
180
|
+
// [review MF-9] 归属校验先于任何持久化副作用:coldLookup 是 getRecordForAction 的
|
|
181
|
+
// 内存未命中分支,若先 resurrect/register/report 再由调用方抛归属错误,会在磁盘/
|
|
182
|
+
// 内存留下幽灵 running record + running transition entry(跨进程双 resurrect 窗口)。
|
|
183
|
+
// rootSessionId 不匹配 → 返回 undefined,由 getRecordForAction 抛统一「not found or
|
|
184
|
+
// not owned」(不区分失败形态,防跨 session 探测);parentRecordId 跨层不匹配 →
|
|
185
|
+
// 原样抛 direct parent 错误(与外层校验同文案,保留跨层导航指引)。
|
|
186
|
+
if (found.rootSessionId !== deps.getSessionRootId()) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const baselineRecordId = deps.getBaselineRecordId();
|
|
190
|
+
if (found.parentRecordId !== baselineRecordId) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`subagent ${id} is owned by its direct parent; message it through that parent ` +
|
|
193
|
+
`(see /subagents list, parent=${found.parentRecordId ?? "(root layer)"}). [v4 A-5] cross-layer ` +
|
|
194
|
+
`ownership guard: this process's baseline=${baselineRecordId ?? "(root)"} is not the direct parent of ${id}; ` +
|
|
195
|
+
`operating here would race the owning child process's handle and double-write the session file.`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return resurrectColdRecord(deps, found, id);
|
|
199
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// capability-gate.test.ts —— [D3-④ 预检 capabilities 化] 拦截矩阵单测。
|
|
2
|
+
// 设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-④(r3 裁定:
|
|
3
|
+
// EngineCapabilities +maxTurns 位 pi=true/zcode=false,不保留硬编码 shape 检查)+
|
|
4
|
+
// §3.4 错误规格第 1 行 + §4 V4④⑤(正反向验收)。
|
|
5
|
+
//
|
|
6
|
+
// 覆盖(构建者白盒 + 使用者黑盒):
|
|
7
|
+
// 1. per-engine 拦截矩阵:pi 全放行(含 maxTurns/fork/conversation/worktree——V4⑤
|
|
8
|
+
// 反向守护,pi 既有合法能力零拦截);zcode 四参数全拦(fork/conversation/
|
|
9
|
+
// maxTurns/worktree)
|
|
10
|
+
// 2. 错误族:engine_capability_unsupported + recovery 含可操作指引(换参数/换引擎)
|
|
11
|
+
// 3. fork 判据(借位裁定):session 分叉通道族任一可用即放行——pi conversation=
|
|
12
|
+
// native 放行(steer 虽 unsupported 不参与否决);通道族全缺才拦
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
|
|
16
|
+
import { assertTaskShapeSupported, type TaskShapeForGate } from "../../common/capability-gate.ts";
|
|
17
|
+
import { EngineError } from "../../common/errors.ts";
|
|
18
|
+
import type { EngineCapabilities } from "../../types.ts";
|
|
19
|
+
import { PiEngine } from "../../engines/pi/pi-engine.ts";
|
|
20
|
+
import { ZcodeEngine } from "../../engines/zcode/zcode-engine.ts";
|
|
21
|
+
|
|
22
|
+
/** 真实引擎的 capabilities 面单测直取(D3 声明是判据本体——pi=true/zcode=false 扩位)。 */
|
|
23
|
+
const PI_CAPS = new PiEngine({ getService: () => null }).capabilities();
|
|
24
|
+
const ZCODE_CAPS = new ZcodeEngine({ engineDataDir: () => "/tmp/zcode-gate" }).capabilities();
|
|
25
|
+
|
|
26
|
+
/** 会话分叉通道族可用的 zcode 形态变体(fork 判据用)。 */
|
|
27
|
+
const ZCODE_CAPS_WITH_CONVERSATION: EngineCapabilities = { ...ZCODE_CAPS, conversation: "native" };
|
|
28
|
+
|
|
29
|
+
function gate(
|
|
30
|
+
caps: EngineCapabilities,
|
|
31
|
+
task: TaskShapeForGate,
|
|
32
|
+
engineId = "zcode",
|
|
33
|
+
): void {
|
|
34
|
+
assertTaskShapeSupported(engineId, caps, task);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function gateError(caps: EngineCapabilities, task: TaskShapeForGate, engineId = "zcode"): EngineError {
|
|
38
|
+
try {
|
|
39
|
+
gate(caps, task, engineId);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
expect(err).toBeInstanceOf(EngineError);
|
|
42
|
+
return err as EngineError;
|
|
43
|
+
}
|
|
44
|
+
throw new Error("expected assertTaskShapeSupported to throw");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("capability-gate(D3-④ 拦截矩阵)", () => {
|
|
48
|
+
it("能力位声明:pi maxTurns=true、zcode maxTurns=false(r3 扩位裁定)", () => {
|
|
49
|
+
expect(PI_CAPS.maxTurns).toBe(true);
|
|
50
|
+
expect(ZCODE_CAPS.maxTurns).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("[V4⑤ 反向] pi 引擎全参数放行:maxTurns/fork/forkFromSessionFile/conversation/worktree 零拦截", () => {
|
|
54
|
+
expect(() =>
|
|
55
|
+
gate(PI_CAPS, { maxTurns: 3, fork: true, conversation: true, worktree: true }, "pi"),
|
|
56
|
+
).not.toThrow();
|
|
57
|
+
expect(() => gate(PI_CAPS, { forkFromSessionFile: "/tmp/sess.jsonl", worktree: { path: "/tmp/wt" } }, "pi")).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("[V4④ 正向] zcode 引擎四参数全拦:fork/conversation/maxTurns/worktree → engine_capability_unsupported", () => {
|
|
61
|
+
const cases: Array<[TaskShapeForGate, RegExp]> = [
|
|
62
|
+
[{ fork: true }, /不支持 fork/],
|
|
63
|
+
[{ forkFromSessionFile: "/tmp/sess.jsonl" }, /fork-from 同为父 session 上下文继承/],
|
|
64
|
+
[{ conversation: true }, /不支持 conversation/],
|
|
65
|
+
[{ maxTurns: 10 }, /不支持 maxTurns/],
|
|
66
|
+
[{ worktree: true }, /不支持 worktree 隔离/],
|
|
67
|
+
[{ worktree: { path: "/tmp/wt" } }, /不支持 worktree 隔离/],
|
|
68
|
+
];
|
|
69
|
+
for (const [task, pattern] of cases) {
|
|
70
|
+
const err = gateError(ZCODE_CAPS, task);
|
|
71
|
+
expect(err.code).toBe("engine_capability_unsupported");
|
|
72
|
+
expect(err.message).toMatch(pattern);
|
|
73
|
+
expect(err.message).toContain("capabilities");
|
|
74
|
+
// 错误信息可操作(§3.4):恢复指引指向换参数/换引擎
|
|
75
|
+
expect(err.recovery).toMatch(/engine: pi|不传|去掉/);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("zcode 放行面:无能力参数的任务全放行;maxTurns undefined(未传)不拦", () => {
|
|
80
|
+
expect(() => gate(ZCODE_CAPS, {})).not.toThrow();
|
|
81
|
+
expect(() => gate(ZCODE_CAPS, { maxTurns: undefined, fork: false, conversation: false, worktree: false })).not.toThrow();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("fork 判据(借位裁定):session 分叉通道族任一可用即放行——conversation=native 的引擎 fork 放行", () => {
|
|
85
|
+
// pi 的 steer 声明 unsupported(spawn 链路未接通),fork 仍放行——通道族判定不因
|
|
86
|
+
// 单一通道缺失而误拦(上面 pi 用例已覆盖)。此处补 zcode 形态 + conversation 通道
|
|
87
|
+
// 可用的对照:fork 从拦截翻为放行。
|
|
88
|
+
expect(() => gate(ZCODE_CAPS_WITH_CONVERSATION, { fork: true })).not.toThrow();
|
|
89
|
+
expect(() => gate({ ...ZCODE_CAPS, steer: "native" }, { fork: true })).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -49,7 +49,7 @@ describe("getEngineDataDir", () => {
|
|
|
49
49
|
|
|
50
50
|
it("缺 env → 回退宿主数据根(HostServices.dataRoot(),假宿主值)并 warn 一次", () => {
|
|
51
51
|
const warnings: string[] = [];
|
|
52
|
-
const warn = (m: string): void => warnings.push(m);
|
|
52
|
+
const warn = (m: string): void => { warnings.push(m); };
|
|
53
53
|
const dir1 = getEngineDataDir({}, warn);
|
|
54
54
|
expect(dir1).toBe(FAKE_DATA_ROOT);
|
|
55
55
|
expect(warnings.length).toBe(1);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// journal-wiring.test.ts —— [D3-③ journal 接线合一] host helper 单测(两域共用的
|
|
2
|
+
// 唯一实现:writer 创建 + retarget + journaling onEvent + handle 回填 + close)。
|
|
3
|
+
// 设计权威源:docs/design/subagent-dual-track-convergence.md §3.3 D3-③ + 双轨清单 #6。
|
|
4
|
+
//
|
|
5
|
+
// 覆盖:①占位池 key 初始路径与 retarget 后路径权威(getter 反映 retarget);②先落盘
|
|
6
|
+
// 再转发(forwardEvents);③handle 回填写终态路径;④close 幂等不抛。
|
|
7
|
+
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as os from "node:os";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
|
|
14
|
+
import { JOURNAL_INITIAL_POOL_KEY, wireEventJournal } from "../../common/journal-wiring.ts";
|
|
15
|
+
import { resolveJournalPath } from "../../paths.ts";
|
|
16
|
+
import type { AgentEvent } from "../../types.ts";
|
|
17
|
+
import type { EngineHandle } from "../../types.ts";
|
|
18
|
+
|
|
19
|
+
let dataRoot: string;
|
|
20
|
+
const PREV_DATA_DIR = process.env["XYZ_AGENT_DATA_DIR"];
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
dataRoot = fs.mkdtempSync(path.join(os.tmpdir(), "journal-wiring-"));
|
|
24
|
+
process.env["XYZ_AGENT_DATA_DIR"] = dataRoot;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (PREV_DATA_DIR === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
|
|
29
|
+
else process.env["XYZ_AGENT_DATA_DIR"] = PREV_DATA_DIR;
|
|
30
|
+
fs.rmSync(dataRoot, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function makeHandle(): EngineHandle {
|
|
34
|
+
return { data: { v: 1, engineId: "zcode", sessionRef: {}, poolKey: "p-m1", adapterVersion: "t" } };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("wireEventJournal(D3-③ host helper 唯一实现)", () => {
|
|
38
|
+
it("初始路径用占位池 key;onPoolResolved retarget 后 path getter 反映实际池 key(路径权威 = writer)", () => {
|
|
39
|
+
const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-1" });
|
|
40
|
+
expect(JOURNAL_INITIAL_POOL_KEY).toBe("shared");
|
|
41
|
+
expect(wiring.path).toBe(resolveJournalPath(dataRoot, "zcode", "shared", "sa-1"));
|
|
42
|
+
wiring.onPoolResolved("home-p-m1");
|
|
43
|
+
expect(wiring.path).toBe(resolveJournalPath(dataRoot, "zcode", "home-p-m1", "sa-1"));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("journaling onEvent:先落盘再转发(forwardEvents 收到同一事件)", async () => {
|
|
47
|
+
const forwarded: AgentEvent[] = [];
|
|
48
|
+
const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-2", forwardEvents: (e) => forwarded.push(e) });
|
|
49
|
+
wiring.onEvent({ type: "turn_end" });
|
|
50
|
+
wiring.onEvent({ type: "message_end", usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 } });
|
|
51
|
+
await wiring.close();
|
|
52
|
+
expect(forwarded.map((e) => e.type)).toEqual(["turn_end", "message_end"]);
|
|
53
|
+
// 落盘真实发生(close flush 后文件存在且行数吻合——先落盘语义由文件内容见证)
|
|
54
|
+
const lines = fs.readFileSync(wiring.path, "utf8").trim().split("\n");
|
|
55
|
+
expect(lines).toHaveLength(2);
|
|
56
|
+
expect(JSON.parse(lines[0]).taskId).toBe("sa-2");
|
|
57
|
+
expect(JSON.parse(lines[0]).engineId).toBe("zcode");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("chat 域形态(无 forwardEvents):事件只进 journal,不抛", async () => {
|
|
61
|
+
const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-3" });
|
|
62
|
+
wiring.onEvent({ type: "turn_end" });
|
|
63
|
+
await wiring.close();
|
|
64
|
+
expect(fs.existsSync(wiring.path)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("handle 回填:backfillHandle 写 retarget 后的终态路径(read ②级自描述定位符)", async () => {
|
|
68
|
+
const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-4" });
|
|
69
|
+
wiring.onPoolResolved("home-p-m1");
|
|
70
|
+
wiring.onEvent({ type: "turn_end" });
|
|
71
|
+
const handle = makeHandle();
|
|
72
|
+
wiring.backfillHandle(handle);
|
|
73
|
+
expect(handle.data.journalPath).toBe(resolveJournalPath(dataRoot, "zcode", "home-p-m1", "sa-4"));
|
|
74
|
+
await wiring.close();
|
|
75
|
+
expect(fs.existsSync(handle.data.journalPath!)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("close 幂等(重复调用不抛);无事件任务不产生空 journal 文件", async () => {
|
|
79
|
+
const wiring = wireEventJournal({ engineId: "zcode", taskId: "sa-5" });
|
|
80
|
+
await wiring.close();
|
|
81
|
+
await expect(wiring.close()).resolves.toBeUndefined();
|
|
82
|
+
expect(fs.existsSync(wiring.path)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -37,8 +37,8 @@ function makeFakeChild(): {
|
|
|
37
37
|
child,
|
|
38
38
|
signals,
|
|
39
39
|
emitExit(code, signal) {
|
|
40
|
-
child.exitCode = code;
|
|
41
|
-
child.signalCode = signal;
|
|
40
|
+
(child as { exitCode: number | null }).exitCode = code;
|
|
41
|
+
(child as { signalCode: string | null }).signalCode = signal;
|
|
42
42
|
for (const l of listeners.splice(0)) l(code, signal);
|
|
43
43
|
},
|
|
44
44
|
};
|
|
@@ -88,13 +88,40 @@ describe("killChain", () => {
|
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
describe("killChain grace 参数化(D3-① 杀链合一:pi=30s / zcode=5s,现状语义逐字节保持)", () => {
|
|
92
|
+
it("pi 参数形态(graceMs=30s + unrefTimers + escalationNote):29s 未升级,越 30s 窗口 SIGKILL", async () => {
|
|
93
|
+
const { child, signals } = makeFakeChild();
|
|
94
|
+
const p = killChain(child, {
|
|
95
|
+
graceMs: 30_000,
|
|
96
|
+
unrefTimers: true,
|
|
97
|
+
escalationNote: "child sa-x (source: spawn watchdog)",
|
|
98
|
+
});
|
|
99
|
+
expect(signals).toEqual(["SIGTERM"]); // SIGTERM 同步发出(信号序不变)
|
|
100
|
+
await vi.advanceTimersByTimeAsync(29_000);
|
|
101
|
+
expect(signals).toEqual(["SIGTERM"]); // 窗口内不升级(非贴边断言)
|
|
102
|
+
await vi.advanceTimersByTimeAsync(1_100); // 越过 30s 优雅窗口
|
|
103
|
+
expect(signals).toEqual(["SIGTERM", "SIGKILL"]); // 升级 SIGKILL(race-F4 语义)
|
|
104
|
+
await vi.advanceTimersByTimeAsync(10_000); // 有界收尸窗口
|
|
105
|
+
expect(await p).toBe("killed");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("escalationNote 缺省(zcode 现状形态):graceMs=5s 越窗 SIGKILL,升级路径同构", async () => {
|
|
109
|
+
const { child, signals } = makeFakeChild();
|
|
110
|
+
const p = killChain(child, { graceMs: 5_000 });
|
|
111
|
+
await vi.advanceTimersByTimeAsync(5_000);
|
|
112
|
+
expect(signals).toEqual(["SIGTERM", "SIGKILL"]);
|
|
113
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
114
|
+
expect(await p).toBe("killed");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
91
118
|
describe("synthesizeTimeoutOutcome", () => {
|
|
92
119
|
it("合成 engine_timeout 终态:error 含 slug + stdout 尾部 + engine: pi 重跑建议;exitCode=null", () => {
|
|
93
120
|
const outcome = synthesizeTimeoutOutcome(
|
|
94
|
-
{
|
|
121
|
+
{ prompt: "review files", description: "review-files" },
|
|
95
122
|
"last stdout lines...",
|
|
96
123
|
);
|
|
97
|
-
expect(outcome.error).toContain("engine_timeout");
|
|
124
|
+
expect(outcome.error!).toContain("engine_timeout");
|
|
98
125
|
expect(outcome.error).toContain("review-files");
|
|
99
126
|
expect(outcome.error).toContain("last stdout lines...");
|
|
100
127
|
expect(outcome.error).toMatch(/`engine: pi`/);
|
|
@@ -105,13 +132,13 @@ describe("synthesizeTimeoutOutcome", () => {
|
|
|
105
132
|
|
|
106
133
|
it("stdout 尾部超 2000 字截断;engineId 可指定", () => {
|
|
107
134
|
const outcome = synthesizeTimeoutOutcome(
|
|
108
|
-
{
|
|
135
|
+
{ prompt: "t", description: "s" },
|
|
109
136
|
"y".repeat(3_000),
|
|
110
137
|
"zcode",
|
|
111
138
|
);
|
|
112
139
|
expect(outcome.engineId).toBe("zcode");
|
|
113
|
-
expect(outcome.error).toContain("...");
|
|
114
|
-
expect(outcome.error
|
|
140
|
+
expect(outcome.error!).toContain("...");
|
|
141
|
+
expect(outcome.error!.length).toBeLessThan(3_000);
|
|
115
142
|
});
|
|
116
143
|
});
|
|
117
144
|
|
|
@@ -17,13 +17,14 @@ import type { EngineCapabilities } from "../../types.ts";
|
|
|
17
17
|
const CAPS_FILE: EngineCapabilities = {
|
|
18
18
|
schemaEnforcement: "emulated", steer: "unsupported", conversation: "unsupported",
|
|
19
19
|
personaInjection: "file", eventGranularity: "coarse", sandbox: "none",
|
|
20
|
-
sessionRead: "partial", resume: "cold", interrupt: "kill-only", permissionMode: "native",
|
|
20
|
+
sessionRead: "partial", resume: "cold", interrupt: "kill-only", permissionMode: "native", maxTurns: false,
|
|
21
21
|
};
|
|
22
22
|
const CAPS_FLAG: EngineCapabilities = { ...CAPS_FILE, personaInjection: "flag" };
|
|
23
23
|
const CAPS_PROMPT: EngineCapabilities = { ...CAPS_FILE, personaInjection: "prompt" };
|
|
24
24
|
|
|
25
|
+
// [D6 合流] PersonaSpec 收拢层删除后,路由入参是 PersonaContent(结构子集:
|
|
26
|
+
// appendSystemPrompt 正文 + skillPath 引用行;agentRef 已裁撤)
|
|
25
27
|
const persona = {
|
|
26
|
-
agentRef: "reviewer",
|
|
27
28
|
skillPath: "/skills/review.md",
|
|
28
29
|
appendSystemPrompt: ["You are a careful code reviewer.", "Be terse."],
|
|
29
30
|
};
|
|
@@ -34,7 +35,6 @@ describe("applyPersona 三策略路由", () => {
|
|
|
34
35
|
expect(routing.promptSegment).toBe("");
|
|
35
36
|
expect(routing.fileCandidate).toBeDefined();
|
|
36
37
|
expect(routing.fileCandidate?.suggestedPath).toBe("persona.md");
|
|
37
|
-
expect(routing.fileCandidate?.content).toContain("# Agent: reviewer");
|
|
38
38
|
expect(routing.fileCandidate?.content).toContain("/skills/review.md");
|
|
39
39
|
expect(routing.fileCandidate?.content).toContain("You are a careful code reviewer.");
|
|
40
40
|
});
|
|
@@ -51,7 +51,6 @@ describe("applyPersona 三策略路由", () => {
|
|
|
51
51
|
it("prompt 策略:promptSegment 为带结构头的人设段(拼进最终 prompt)", () => {
|
|
52
52
|
const routing = applyPersona(persona, CAPS_PROMPT);
|
|
53
53
|
expect(routing.promptSegment.startsWith("## Persona")).toBe(true);
|
|
54
|
-
expect(routing.promptSegment).toContain("# Agent: reviewer");
|
|
55
54
|
expect(routing.promptSegment).toContain("You are a careful code reviewer.");
|
|
56
55
|
});
|
|
57
56
|
|
|
@@ -61,7 +60,7 @@ describe("applyPersona 三策略路由", () => {
|
|
|
61
60
|
expect(applyPersona({}, CAPS_PROMPT)).toEqual({ promptSegment: "" });
|
|
62
61
|
});
|
|
63
62
|
|
|
64
|
-
it("仅 appendSystemPrompt:file 通道照常落文件(
|
|
63
|
+
it("仅 appendSystemPrompt:file 通道照常落文件(skillPath 头部行缺省)", () => {
|
|
65
64
|
const routing = applyPersona({ appendSystemPrompt: ["only body"] }, CAPS_FILE);
|
|
66
65
|
expect(routing.fileCandidate?.content).toBe("only body");
|
|
67
66
|
});
|