@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
|
@@ -104,26 +104,34 @@ function truncateLabel(label: string): string {
|
|
|
104
104
|
return label.length > TOOL_LABEL_MAX ? label.slice(0, TOOL_LABEL_MAX) : label;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** usage 单字段求和(undefined 视为 0——与旧 `(a ?? 0) + (b ?? 0)` 内联式逐字等价)。 */
|
|
108
|
+
function sumUsageField(a: number | undefined, b: number | undefined): number {
|
|
109
|
+
return (a ?? 0) + (b ?? 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** prev 为空时 next 的规范化拷贝(cost 保留原值,可能 undefined——与旧首条分支逐字等价)。 */
|
|
113
|
+
function usageFromNext(next: AgentUsage): AgentUsage {
|
|
114
|
+
return {
|
|
115
|
+
input: next.input ?? 0,
|
|
116
|
+
output: next.output ?? 0,
|
|
117
|
+
cacheRead: next.cacheRead ?? 0,
|
|
118
|
+
cacheWrite: next.cacheWrite ?? 0,
|
|
119
|
+
cost: next.cost,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
107
123
|
/**
|
|
108
124
|
* 累加两个 AgentUsage(field-wise)。prev 为空时返回 next 的拷贝。
|
|
109
125
|
* 供 message_end 把 usage 增量并入 turn.usageDelta。
|
|
110
126
|
*/
|
|
111
127
|
function addUsage(prev: AgentUsage | undefined, next: AgentUsage): AgentUsage {
|
|
112
|
-
if (prev === undefined)
|
|
113
|
-
return {
|
|
114
|
-
input: next.input ?? 0,
|
|
115
|
-
output: next.output ?? 0,
|
|
116
|
-
cacheRead: next.cacheRead ?? 0,
|
|
117
|
-
cacheWrite: next.cacheWrite ?? 0,
|
|
118
|
-
cost: next.cost,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
128
|
+
if (prev === undefined) return usageFromNext(next);
|
|
121
129
|
return {
|
|
122
|
-
input: (prev.input
|
|
123
|
-
output: (prev.output
|
|
124
|
-
cacheRead: (prev.cacheRead
|
|
125
|
-
cacheWrite: (prev.cacheWrite
|
|
126
|
-
cost: (prev.cost
|
|
130
|
+
input: sumUsageField(prev.input, next.input),
|
|
131
|
+
output: sumUsageField(prev.output, next.output),
|
|
132
|
+
cacheRead: sumUsageField(prev.cacheRead, next.cacheRead),
|
|
133
|
+
cacheWrite: sumUsageField(prev.cacheWrite, next.cacheWrite),
|
|
134
|
+
cost: sumUsageField(prev.cost, next.cost),
|
|
127
135
|
};
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -274,6 +282,141 @@ function indexToolStart(record: ExecutionRecord, turn: Turn, toolName: string):
|
|
|
274
282
|
}
|
|
275
283
|
}
|
|
276
284
|
|
|
285
|
+
// ── 各事件处理器(updateFromEvent 按 case 分发,每个处理器单一职责)──
|
|
286
|
+
|
|
287
|
+
/** text_delta:流式累积进当前 turn 的 text(完整内容,非切片)。 */
|
|
288
|
+
function applyTextDelta(
|
|
289
|
+
record: ExecutionRecord,
|
|
290
|
+
event: Extract<AgentEvent, { type: "text_delta" }>,
|
|
291
|
+
): void {
|
|
292
|
+
currentTurn(record).text += event.delta;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** thinking_delta:流式累积进当前 turn 的 thinking(完整内容)。 */
|
|
296
|
+
function applyThinkingDelta(
|
|
297
|
+
record: ExecutionRecord,
|
|
298
|
+
event: Extract<AgentEvent, { type: "thinking_delta" }>,
|
|
299
|
+
): void {
|
|
300
|
+
currentTurn(record).thinking += event.delta;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** tool_start:push 一个 running 的 InternalToolCall(带 startedTs)+ 弹尾索引入册。 */
|
|
304
|
+
function applyToolStart(
|
|
305
|
+
record: ExecutionRecord,
|
|
306
|
+
event: Extract<AgentEvent, { type: "tool_start" }>,
|
|
307
|
+
): void {
|
|
308
|
+
const tc: InternalToolCall = {
|
|
309
|
+
toolName: event.toolName,
|
|
310
|
+
args: event.args,
|
|
311
|
+
result: undefined,
|
|
312
|
+
isError: false,
|
|
313
|
+
_status: "running",
|
|
314
|
+
startedTs: Date.now(),
|
|
315
|
+
};
|
|
316
|
+
const turn = currentTurn(record);
|
|
317
|
+
turn.toolCalls.push(tc);
|
|
318
|
+
indexToolStart(record, turn, event.toolName);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* tool_end 定位:索引弹尾 O(1) 命中 running 同名 toolCall;索引 miss(无记录 / 槽位
|
|
323
|
+
* 已非 running)回退 findRunningToolCall 跨 turn 倒序全扫兜底。
|
|
324
|
+
* 返回 [turn, index];两路都 miss 返回 undefined。
|
|
325
|
+
*/
|
|
326
|
+
function matchRunningToolCall(
|
|
327
|
+
record: ExecutionRecord,
|
|
328
|
+
toolName: string,
|
|
329
|
+
): readonly [Turn, number] | undefined {
|
|
330
|
+
const byName = runningToolIndex.get(record);
|
|
331
|
+
const arr = byName?.get(toolName);
|
|
332
|
+
if (arr !== undefined && arr.length > 0) {
|
|
333
|
+
const item = arr[arr.length - 1];
|
|
334
|
+
arr.pop();
|
|
335
|
+
const tc = item.turn.toolCalls[item.idx];
|
|
336
|
+
if (tc !== undefined && tc._status === "running") {
|
|
337
|
+
return [item.turn, item.idx] as const;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// 兜底:重建 record 的历史 running toolCall(索引未覆盖)、索引项被外部路径
|
|
341
|
+
// 置非 running 等场景——保持与旧实现一致的跨 turn 倒序全扫。
|
|
342
|
+
return findRunningToolCall(record, toolName);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* tool_end:命中则回填 result/isError/_status;未命中(SDK 发了 tool_end 但无对应
|
|
347
|
+
* tool_start,如外部注入的工具)直接 push 一个已完成的 InternalToolCall,避免数据丢失。
|
|
348
|
+
*/
|
|
349
|
+
function applyToolEnd(
|
|
350
|
+
record: ExecutionRecord,
|
|
351
|
+
event: Extract<AgentEvent, { type: "tool_end" }>,
|
|
352
|
+
): void {
|
|
353
|
+
const matched = matchRunningToolCall(record, event.toolName);
|
|
354
|
+
if (matched !== undefined) {
|
|
355
|
+
const [turn, i] = matched;
|
|
356
|
+
const tc = turn.toolCalls[i]!;
|
|
357
|
+
tc.args = event.args ?? tc.args;
|
|
358
|
+
tc.result = event.result;
|
|
359
|
+
tc.isError = event.isError ?? false;
|
|
360
|
+
tc._status = event.isError ? "failed" : "done";
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
currentTurn(record).toolCalls.push({
|
|
364
|
+
toolName: event.toolName,
|
|
365
|
+
args: event.args,
|
|
366
|
+
result: event.result,
|
|
367
|
+
isError: event.isError ?? false,
|
|
368
|
+
_status: event.isError ? "failed" : "done",
|
|
369
|
+
startedTs: Date.now(),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* turn_end:闭合当前 turn,记 closedTs(真实墙钟),turnCount++,清 lastError。
|
|
375
|
+
* 正常闭合清 lastError:瞬态 error 恢复后不应误判 success=false
|
|
376
|
+
* (若 turn_end 后 message_end 报 error,会在 message_end 处理器重新写回)。
|
|
377
|
+
*/
|
|
378
|
+
function applyTurnEnd(record: ExecutionRecord): void {
|
|
379
|
+
const turn = currentTurn(record);
|
|
380
|
+
turn.closed = true;
|
|
381
|
+
turn.closedTs = Date.now();
|
|
382
|
+
record.turnCount += 1;
|
|
383
|
+
record.lastError = undefined;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* message_end:usage 增量存进末 turn.usageDelta(直接写末 turn,不开新 turn);
|
|
388
|
+
* totalTokens 累加;error(stopReason=error)记进 lastError。
|
|
389
|
+
*
|
|
390
|
+
* usageDelta 按 message_end **累加**(非覆盖)——同一 turn 内若多次 message_end
|
|
391
|
+
* 到达(或 turn_end 后的滞后 message_end 落到 currentTurn 开的新 turn),
|
|
392
|
+
* 累加保证不丢 usage。getTotalUsage 扁平求和所有 turn,归属 turn 的精确性
|
|
393
|
+
* 不影响最终 total(无消费方读单 turn usage)。
|
|
394
|
+
*/
|
|
395
|
+
function applyMessageEnd(
|
|
396
|
+
record: ExecutionRecord,
|
|
397
|
+
event: Extract<AgentEvent, { type: "message_end" }>,
|
|
398
|
+
): void {
|
|
399
|
+
if (event.usage) {
|
|
400
|
+
const turn = currentTurn(record);
|
|
401
|
+
turn.usageDelta = addUsage(turn.usageDelta, event.usage);
|
|
402
|
+
// totalTokens 累加四项之和(保留旧语义,投影直接读)
|
|
403
|
+
record.totalTokens +=
|
|
404
|
+
(event.usage.input ?? 0) + (event.usage.output ?? 0) +
|
|
405
|
+
(event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
406
|
+
}
|
|
407
|
+
if (event.error) {
|
|
408
|
+
record.lastError = event.error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** error:存 record.lastError(getEventLog 派生 error 条目用)。 */
|
|
413
|
+
function applyErrorEvent(
|
|
414
|
+
record: ExecutionRecord,
|
|
415
|
+
event: Extract<AgentEvent, { type: "error" }>,
|
|
416
|
+
): void {
|
|
417
|
+
record.lastError = event.message;
|
|
418
|
+
}
|
|
419
|
+
|
|
277
420
|
/**
|
|
278
421
|
* 从 AgentEvent 更新 record。所有数据收口进 record.turns[]。
|
|
279
422
|
* - text/thinking:流式累积进 currentTurn()(完整内容,非切片)
|
|
@@ -293,116 +436,32 @@ function indexToolStart(record: ExecutionRecord, turn: Turn, toolName: string):
|
|
|
293
436
|
export function updateFromEvent(record: ExecutionRecord, event: AgentEvent): void {
|
|
294
437
|
switch (event.type) {
|
|
295
438
|
// ── text / thinking:流式累积进当前 turn ──
|
|
296
|
-
case "text_delta":
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
case "thinking_delta": {
|
|
301
|
-
currentTurn(record).thinking += event.delta;
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ── tool_start:push 一个 running 的 InternalToolCall(带 startedTs)──
|
|
306
|
-
case "tool_start": {
|
|
307
|
-
const tc: InternalToolCall = {
|
|
308
|
-
toolName: event.toolName,
|
|
309
|
-
args: event.args,
|
|
310
|
-
result: undefined,
|
|
311
|
-
isError: false,
|
|
312
|
-
_status: "running",
|
|
313
|
-
startedTs: Date.now(),
|
|
314
|
-
};
|
|
315
|
-
const turn = currentTurn(record);
|
|
316
|
-
turn.toolCalls.push(tc);
|
|
317
|
-
indexToolStart(record, turn, event.toolName);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
439
|
+
case "text_delta":
|
|
440
|
+
return applyTextDelta(record, event);
|
|
441
|
+
case "thinking_delta":
|
|
442
|
+
return applyThinkingDelta(record, event);
|
|
320
443
|
|
|
321
|
-
// ──
|
|
322
|
-
case "
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (arr !== undefined && arr.length > 0) {
|
|
327
|
-
const item = arr[arr.length - 1];
|
|
328
|
-
arr.pop();
|
|
329
|
-
const tc = item.turn.toolCalls[item.idx];
|
|
330
|
-
if (tc !== undefined && tc._status === "running") {
|
|
331
|
-
matched = [item.turn, item.idx] as const;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (matched === undefined) {
|
|
335
|
-
// 兜底:重建 record 的历史 running toolCall(索引未覆盖)、索引项被外部路径
|
|
336
|
-
// 置非 running 等场景——保持与旧实现一致的跨 turn 倒序全扫。
|
|
337
|
-
matched = findRunningToolCall(record, event.toolName);
|
|
338
|
-
}
|
|
339
|
-
if (matched !== undefined) {
|
|
340
|
-
const [turn, i] = matched;
|
|
341
|
-
const tc = turn.toolCalls[i]!;
|
|
342
|
-
tc.args = event.args ?? tc.args;
|
|
343
|
-
tc.result = event.result;
|
|
344
|
-
tc.isError = event.isError ?? false;
|
|
345
|
-
tc._status = event.isError ? "failed" : "done";
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
// 匹配失败(SDK 发了 tool_end 但无对应 tool_start,如外部注入的工具):
|
|
349
|
-
// 直接 push 一个已完成的 InternalToolCall,避免数据丢失。
|
|
350
|
-
currentTurn(record).toolCalls.push({
|
|
351
|
-
toolName: event.toolName,
|
|
352
|
-
args: event.args,
|
|
353
|
-
result: event.result,
|
|
354
|
-
isError: event.isError ?? false,
|
|
355
|
-
_status: event.isError ? "failed" : "done",
|
|
356
|
-
startedTs: Date.now(),
|
|
357
|
-
});
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
444
|
+
// ── tool_start/end:push 进 currentTurn().toolCalls(含完整 result)──
|
|
445
|
+
case "tool_start":
|
|
446
|
+
return applyToolStart(record, event);
|
|
447
|
+
case "tool_end":
|
|
448
|
+
return applyToolEnd(record, event);
|
|
360
449
|
|
|
361
|
-
// ── turn_end:闭合当前 turn
|
|
362
|
-
case "turn_end":
|
|
363
|
-
|
|
364
|
-
turn.closed = true;
|
|
365
|
-
turn.closedTs = Date.now();
|
|
366
|
-
record.turnCount += 1;
|
|
367
|
-
// turn 正常闭合意味着本段执行成功——清掉运行期可能记录的瞬态 error,
|
|
368
|
-
// 避免瞬态 error 恢复后 session-runner 仍据 lastError 误判 success=false。
|
|
369
|
-
// (若 turn_end 后 message_end 报 error,会在 message_end 分支重新写回 lastError。)
|
|
370
|
-
record.lastError = undefined;
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
450
|
+
// ── turn_end:闭合当前 turn ──
|
|
451
|
+
case "turn_end":
|
|
452
|
+
return applyTurnEnd(record);
|
|
373
453
|
|
|
374
|
-
// ── message_end:usage
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// 到达(或 turn_end 后的滞后 message_end 落到 currentTurn 开的新 turn),
|
|
378
|
-
// 累加保证不丢 usage。getTotalUsage 扁平求和所有 turn,归属 turn 的精确性
|
|
379
|
-
// 不影响最终 total(无消费方读单 turn usage)。
|
|
380
|
-
case "message_end": {
|
|
381
|
-
if (event.usage) {
|
|
382
|
-
const turn = currentTurn(record);
|
|
383
|
-
turn.usageDelta = addUsage(turn.usageDelta, event.usage);
|
|
384
|
-
// totalTokens 累加四项之和(保留旧语义,投影直接读)
|
|
385
|
-
record.totalTokens +=
|
|
386
|
-
(event.usage.input ?? 0) + (event.usage.output ?? 0) +
|
|
387
|
-
(event.usage.cacheRead ?? 0) + (event.usage.cacheWrite ?? 0);
|
|
388
|
-
}
|
|
389
|
-
// message_end 的 error(stopReason=error)也记进 lastError
|
|
390
|
-
if (event.error) {
|
|
391
|
-
record.lastError = event.error;
|
|
392
|
-
}
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
454
|
+
// ── message_end:usage 增量累加 + totalTokens 累加 ──
|
|
455
|
+
case "message_end":
|
|
456
|
+
return applyMessageEnd(record, event);
|
|
395
457
|
|
|
396
|
-
// ── error:存 record.lastError
|
|
397
|
-
case "error":
|
|
398
|
-
record
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
458
|
+
// ── error:存 record.lastError ──
|
|
459
|
+
case "error":
|
|
460
|
+
return applyErrorEvent(record, event);
|
|
401
461
|
|
|
402
462
|
// ── compaction:不产生数据(不变)──
|
|
403
|
-
case "compaction":
|
|
463
|
+
case "compaction":
|
|
404
464
|
return;
|
|
405
|
-
}
|
|
406
465
|
|
|
407
466
|
default: {
|
|
408
467
|
// 穷尽性检查:新增 AgentEvent variant 时编译期报错
|
|
@@ -881,6 +940,46 @@ export function snapshot(record: ExecutionRecord): RecordSnapshot {
|
|
|
881
940
|
/** subprocess JSONL 事件(JSON.parse 结果)。duck-typed,对应 SDK SdkEvent。 */
|
|
882
941
|
type JsonlEvent = Record<string, unknown>;
|
|
883
942
|
|
|
943
|
+
// ── 各 case 翻译器(jsonlToAgentEvent 按 case 分发,每个翻译器单一职责)──
|
|
944
|
+
|
|
945
|
+
/** tool_execution_start → tool_start。toolName 非字符串归一空串(与原实现一致)。 */
|
|
946
|
+
function translateToolExecutionStart(raw: JsonlEvent): AgentEvent[] {
|
|
947
|
+
const toolName = typeof raw.toolName === "string" ? raw.toolName : "";
|
|
948
|
+
return [{ type: "tool_start", toolName, args: raw.args }];
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/** tool_execution_end → tool_end。isError 仅在 === true 时成立;result 原样透传。 */
|
|
952
|
+
function translateToolExecutionEnd(raw: JsonlEvent): AgentEvent[] {
|
|
953
|
+
const toolName = typeof raw.toolName === "string" ? raw.toolName : "";
|
|
954
|
+
const isError = raw.isError === true;
|
|
955
|
+
return [{
|
|
956
|
+
type: "tool_end",
|
|
957
|
+
toolName,
|
|
958
|
+
args: raw.args,
|
|
959
|
+
result: raw.result as ToolCallResult | undefined,
|
|
960
|
+
isError,
|
|
961
|
+
}];
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* message_update → thinking_delta / text_delta。
|
|
966
|
+
* ame.type === "thinking_delta"(delta 非字符串归一空串)
|
|
967
|
+
* 其余 ame(delta 有值)→ text_delta(delta 非字符串 String() 归一)
|
|
968
|
+
* ame 缺失 / delta 缺失 → 不产出。
|
|
969
|
+
*/
|
|
970
|
+
function translateMessageUpdate(raw: JsonlEvent): AgentEvent[] {
|
|
971
|
+
const ame = raw.assistantMessageEvent as Record<string, unknown> | undefined;
|
|
972
|
+
if (ame?.type === "thinking_delta") {
|
|
973
|
+
const delta = typeof ame.delta === "string" ? ame.delta : "";
|
|
974
|
+
return [{ type: "thinking_delta", delta }];
|
|
975
|
+
}
|
|
976
|
+
if (ame !== undefined && ame.delta !== undefined) {
|
|
977
|
+
const delta = typeof ame.delta === "string" ? ame.delta : String(ame.delta);
|
|
978
|
+
return [{ type: "text_delta", delta }];
|
|
979
|
+
}
|
|
980
|
+
return [];
|
|
981
|
+
}
|
|
982
|
+
|
|
884
983
|
/**
|
|
885
984
|
* 把一条 JSONL 事件翻译成 AgentEvent。
|
|
886
985
|
*
|
|
@@ -900,35 +999,14 @@ export function jsonlToAgentEvent(raw: JsonlEvent): AgentEvent[] {
|
|
|
900
999
|
case "tool_execution_update":
|
|
901
1000
|
return [];
|
|
902
1001
|
|
|
903
|
-
case "tool_execution_start":
|
|
904
|
-
|
|
905
|
-
return [{ type: "tool_start", toolName, args: raw.args }];
|
|
906
|
-
}
|
|
1002
|
+
case "tool_execution_start":
|
|
1003
|
+
return translateToolExecutionStart(raw);
|
|
907
1004
|
|
|
908
|
-
case "tool_execution_end":
|
|
909
|
-
|
|
910
|
-
const isError = raw.isError === true;
|
|
911
|
-
return [{
|
|
912
|
-
type: "tool_end",
|
|
913
|
-
toolName,
|
|
914
|
-
args: raw.args,
|
|
915
|
-
result: raw.result as ToolCallResult | undefined,
|
|
916
|
-
isError,
|
|
917
|
-
}];
|
|
918
|
-
}
|
|
1005
|
+
case "tool_execution_end":
|
|
1006
|
+
return translateToolExecutionEnd(raw);
|
|
919
1007
|
|
|
920
|
-
case "message_update":
|
|
921
|
-
|
|
922
|
-
if (ame?.type === "thinking_delta") {
|
|
923
|
-
const delta = typeof ame.delta === "string" ? ame.delta : "";
|
|
924
|
-
return [{ type: "thinking_delta", delta }];
|
|
925
|
-
}
|
|
926
|
-
if (ame !== undefined && ame.delta !== undefined) {
|
|
927
|
-
const delta = typeof ame.delta === "string" ? ame.delta : String(ame.delta);
|
|
928
|
-
return [{ type: "text_delta", delta }];
|
|
929
|
-
}
|
|
930
|
-
return [];
|
|
931
|
-
}
|
|
1008
|
+
case "message_update":
|
|
1009
|
+
return translateMessageUpdate(raw);
|
|
932
1010
|
|
|
933
1011
|
case "turn_end": {
|
|
934
1012
|
return [{ type: "turn_end" }];
|
|
@@ -86,115 +86,104 @@ function findSessionFileByRecordIdentity(
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* [T1/PS-9] Step 序言:sessionFile 缺失时按 record 身份在 sessionDir 反查回填。
|
|
90
90
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
91
|
+
* 放在一切步骤前,让 archive 投影 / Step 3 marker / Step 4 manifest 统一受益。
|
|
92
|
+
* RC-1(握手失败)+ LC-4(收尾反查未命中)的残余形态下,磁盘上 session 文件仍可能
|
|
93
|
+
* 真实存在(identity 携带 record.id)——反查命中则 tombstone/finalized sidecar 与
|
|
94
|
+
* removeAliveMarker 有了依据,终态原因不再丢失、alive marker 不再残留;未命中则
|
|
95
|
+
* 保持旧行为(best-effort 跳过)。
|
|
94
96
|
*/
|
|
95
|
-
|
|
96
|
-
deps
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Step 3 marker / Step 4 manifest 统一受益)。RC-1(握手失败)+ LC-4(收尾反查未命中)
|
|
104
|
-
// 的残余形态下,磁盘上 session 文件仍可能真实存在(identity 携带 record.id)——
|
|
105
|
-
// 反查命中则 tombstone/finalized sidecar 与 removeAliveMarker 有了依据,终态原因
|
|
106
|
-
// 不再丢失、alive marker 不再残留;未命中则保持旧行为(best-effort 跳过)。
|
|
107
|
-
if (!record.sessionFile && deps.sessionDir) {
|
|
108
|
-
const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
|
|
109
|
-
if (resolved) {
|
|
110
|
-
record.sessionFile = resolved;
|
|
111
|
-
logger.warn(
|
|
112
|
-
`[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
97
|
+
function resolveMissingSessionFile(deps: FinalizeDeps, record: ExecutionRecord): void {
|
|
98
|
+
if (record.sessionFile || !deps.sessionDir) return;
|
|
99
|
+
const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
|
|
100
|
+
if (resolved) {
|
|
101
|
+
record.sessionFile = resolved;
|
|
102
|
+
logger.warn(
|
|
103
|
+
`[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`,
|
|
104
|
+
);
|
|
115
105
|
}
|
|
106
|
+
}
|
|
116
107
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Step 0: collectPatch(best-effort,仅 worktree 绑定时执行)。
|
|
110
|
+
* [MF#3] patchFile 写到 worktree 之外(sessionsDir/<branch>.patch),避免被 cleanup 删除;
|
|
111
|
+
* 路径回填 record.patchFile,供调用方(tool result / /subagents list)应用。
|
|
112
|
+
*/
|
|
113
|
+
async function collectPatchIfWorktree(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
114
|
+
if (!record.worktreeHandle) return;
|
|
115
|
+
try {
|
|
116
|
+
const sessionsDir = getSubagentSessionDir(
|
|
117
|
+
deps.modelService.getAgentDir(),
|
|
118
|
+
record.worktreeHandle.mainCwd,
|
|
119
|
+
);
|
|
120
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
121
|
+
const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
|
|
122
|
+
const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
|
|
123
|
+
if (patch.written) record.patchFile = patchFile;
|
|
124
|
+
} catch (pe: unknown) {
|
|
125
|
+
bestEffort(pe, "collectPatch (finalizeRecord Step0)");
|
|
133
126
|
}
|
|
127
|
+
}
|
|
134
128
|
|
|
135
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Step 3a: finalized/tombstone sidecar(best-effort 幂等,仅 sessionFile 存在时执行)。
|
|
131
|
+
* MF-1 fix / v4 B-1: cancelled(closedReason='cancelled')写 tombstone 而非 finalized,
|
|
132
|
+
* 防重建丢失 cancelled;其余 reason 写 finalized sidecar(真实 reason 进 sidecar 内容,
|
|
133
|
+
* 磁盘重建用它还原 closedReason,不再一律硬编码 gc)。
|
|
134
|
+
*/
|
|
135
|
+
function writeFinalizedOrTombstone(record: ExecutionRecord, closedReason: ClosedReason | undefined): void {
|
|
136
|
+
if (!record.sessionFile) return;
|
|
136
137
|
try {
|
|
137
|
-
|
|
138
|
+
if (closedReason === "cancelled") {
|
|
139
|
+
writeCancelledTombstone(record.sessionFile, {
|
|
140
|
+
id: record.id,
|
|
141
|
+
status: "cancelled",
|
|
142
|
+
agent: record.agent,
|
|
143
|
+
startedAt: record.startedAt,
|
|
144
|
+
endedAt: record.endedAt ?? Date.now(),
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
writeFinalized(record.sessionFile, closedReason);
|
|
148
|
+
}
|
|
138
149
|
} catch (err) {
|
|
139
|
-
bestEffort(err, "
|
|
150
|
+
bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
|
|
140
151
|
}
|
|
152
|
+
}
|
|
141
153
|
|
|
142
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Step 3b: worktree cleanup(best-effort 幂等,仅 worktree 绑定时执行)。
|
|
156
|
+
* [Critical] 绝不能因 manifest 写失败而跳过(否则 worktree 泄漏)。
|
|
157
|
+
*/
|
|
158
|
+
async function cleanupWorktreeIfBound(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
159
|
+
if (!record.worktreeHandle) return;
|
|
143
160
|
try {
|
|
144
|
-
deps.
|
|
161
|
+
await deps.worktreeManager.cleanup(record.worktreeHandle);
|
|
145
162
|
} catch (err) {
|
|
146
|
-
bestEffort(err, "
|
|
163
|
+
bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
|
|
147
164
|
}
|
|
165
|
+
}
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (closedReason === "cancelled") {
|
|
157
|
-
writeCancelledTombstone(record.sessionFile, {
|
|
158
|
-
id: record.id,
|
|
159
|
-
status: "cancelled",
|
|
160
|
-
agent: record.agent,
|
|
161
|
-
startedAt: record.startedAt,
|
|
162
|
-
endedAt: record.endedAt ?? Date.now(),
|
|
163
|
-
});
|
|
164
|
-
} else {
|
|
165
|
-
// [v8.5 A2] 真实 reason 写入 sidecar 内容(旧格式是空文件):磁盘重建时用它
|
|
166
|
-
// 还原 closedReason,不再一律硬编码 gc。cancelled 之外的全部原因(gc /
|
|
167
|
-
// user-close / parent-*)都经此路径持久化。
|
|
168
|
-
writeFinalized(record.sessionFile, closedReason);
|
|
169
|
-
}
|
|
170
|
-
} catch (err) {
|
|
171
|
-
bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (record.worktreeHandle) {
|
|
175
|
-
try {
|
|
176
|
-
await deps.worktreeManager.cleanup(record.worktreeHandle);
|
|
177
|
-
} catch (err) {
|
|
178
|
-
bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (record.sessionFile) {
|
|
182
|
-
try {
|
|
183
|
-
removeAliveMarker(record.sessionFile);
|
|
184
|
-
} catch (err) {
|
|
185
|
-
bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
|
|
186
|
-
}
|
|
167
|
+
/** Step 3c: 删 .alive marker(best-effort 幂等,仅 sessionFile 存在时执行)。 */
|
|
168
|
+
function removeAliveMarkerIfPresent(record: ExecutionRecord): void {
|
|
169
|
+
if (!record.sessionFile) return;
|
|
170
|
+
try {
|
|
171
|
+
removeAliveMarker(record.sessionFile);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
|
|
187
174
|
}
|
|
175
|
+
}
|
|
188
176
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)。
|
|
179
|
+
*
|
|
180
|
+
* [Critical #1] manifest 是诊断辅助(orphan recovery),不是正确性依赖。写失败时
|
|
181
|
+
* 仅记录(console.error + appendEntry),绝不让 manifest 写失败跳过 Step 3 cleanup
|
|
182
|
+
* 或抛出打断 finalize 链。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup。
|
|
183
|
+
* task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord 补字段),
|
|
184
|
+
* manifestToSubagent 投影时用真实值而非硬编码空串。
|
|
185
|
+
*/
|
|
186
|
+
async function writeManifestBestEffort(deps: FinalizeDeps, record: ExecutionRecord): Promise<void> {
|
|
198
187
|
try {
|
|
199
188
|
await deps.manifestStore.writeManifest({
|
|
200
189
|
id: record.id,
|
|
@@ -220,6 +209,55 @@ export async function doFinalizeRecord(
|
|
|
220
209
|
}
|
|
221
210
|
}
|
|
222
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 时序收尾(D-017)。步骤 0→4 全部 best-effort 互不阻断(除 manifest 外都幂等)。
|
|
214
|
+
*
|
|
215
|
+
* [Critical #1] Step 3 cleanup 全部在 Step 4 manifest 之前——manifest 写失败仅 console.error +
|
|
216
|
+
* appendEntry,不 throw 不跳过 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord
|
|
217
|
+
* 补字段),manifestToSubagent 投影真实值而非硬编码空串。
|
|
218
|
+
*/
|
|
219
|
+
export async function doFinalizeRecord(
|
|
220
|
+
deps: FinalizeDeps,
|
|
221
|
+
record: ExecutionRecord,
|
|
222
|
+
result: AgentResult,
|
|
223
|
+
status: "closed",
|
|
224
|
+
closedReason?: ClosedReason,
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
// [T1/PS-9] sessionFile 缺失时 sessionDir 反查(序言,先于一切步骤)。
|
|
227
|
+
resolveMissingSessionFile(deps, record);
|
|
228
|
+
|
|
229
|
+
// ── Step 0: collectPatch(best-effort)──
|
|
230
|
+
await collectPatchIfWorktree(deps, record);
|
|
231
|
+
|
|
232
|
+
// ── Step 1: completeRecord(B9: 抛错→后续仍执行)──
|
|
233
|
+
try {
|
|
234
|
+
completeRecord(record, result, status, closedReason);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
bestEffort(err, "completeRecord (finalizeRecord B9)", "error");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── Step 2: archive(B9: 抛错→后续仍执行)──
|
|
240
|
+
try {
|
|
241
|
+
deps.store.archive(record);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
bestEffort(err, "store.archive (finalizeRecord B9)", "error");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Step 3: finalized + cleanup + aliveMarker(全部先执行,幂等)──
|
|
247
|
+
// [Critical] 清理必须在 manifest 写入之前:worktree cleanup / finalized marker / aliveMarker
|
|
248
|
+
// 都是幂等且不可跳过的副作用。绝不能因 manifest 写失败而跳过 worktree cleanup
|
|
249
|
+
// (否则 worktree 泄漏)。各件独立 try/catch,互不阻断。
|
|
250
|
+
writeFinalizedOrTombstone(record, closedReason);
|
|
251
|
+
await cleanupWorktreeIfBound(deps, record);
|
|
252
|
+
removeAliveMarkerIfPresent(record);
|
|
253
|
+
|
|
254
|
+
// pending-notifications:终态注销(只记 registry 状态,通知由 BgNotifier 发)
|
|
255
|
+
deps.emitUnregister(record.id, status);
|
|
256
|
+
|
|
257
|
+
// ── Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)──
|
|
258
|
+
await writeManifestBestEffort(deps, record);
|
|
259
|
+
}
|
|
260
|
+
|
|
223
261
|
/**
|
|
224
262
|
* 对话模式轮次完成收尾:record 进 idle 态(非终态化,等待续聊)。
|
|
225
263
|
*
|