@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
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 概率性清理过期 subagent session 文件(TTL 30 天)。
|
|
4
4
|
// session_start 时调用,best-effort(失败不影响启动)。
|
|
5
|
+
//
|
|
6
|
+
// [D8 池引用计数接线] 同概率门内追加引擎池 TTL 兜底(cleanupExpiredPoolRefs):
|
|
7
|
+
// journal / refs.json 条目按同 30 天 mtime 回收。record 主数据(主 session 文件里
|
|
8
|
+
// 的 subagent-record entry)由 pi 侧管理,其删除对 core 无触发点——done record 的
|
|
9
|
+
// journal 没有精确回收锚,只能靠此兜底;workflow 域(taskId 占位无 record 生命周期
|
|
10
|
+
// 锚)同样依赖它(D8 分域口径「journal 依赖 30 天 TTL 自然回收」的落地)。
|
|
5
11
|
|
|
6
12
|
import * as fs from "node:fs";
|
|
7
13
|
import * as path from "node:path";
|
|
8
14
|
|
|
9
15
|
import { isProcessAlive, readAliveMarker } from "./alive-store.ts";
|
|
16
|
+
import { getEngineDataDir } from "./engine/common/data-dir.ts";
|
|
17
|
+
import { cleanupExpiredPoolRefs } from "./engine/common/pool-manager.ts";
|
|
10
18
|
|
|
11
19
|
/** 30 天 TTL(毫秒)。 */
|
|
12
20
|
const TTL_DAYS = 30;
|
|
@@ -29,21 +37,105 @@ const SUBAGENTS_DIR = "subagents";
|
|
|
29
37
|
* 1. 概率性触发(CLEANUP_PROBABILITY)
|
|
30
38
|
* 2. 递归扫描 <agentDir>/subagents 下所有 .jsonl 文件
|
|
31
39
|
* 3. mtime 超 TTL → unlink
|
|
40
|
+
* 4. [D8] 引擎池 TTL 兜底:journal / refs 条目超龄回收(见文件头注释)
|
|
32
41
|
*/
|
|
33
42
|
export function maybeCleanupExpiredSessionFiles(agentDir: string, cwd: string): void {
|
|
34
43
|
void cwd;
|
|
35
44
|
try {
|
|
36
45
|
if (Math.random() >= CLEANUP_PROBABILITY) return;
|
|
37
46
|
const dir = path.join(agentDir, SUBAGENTS_DIR);
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
if (fs.existsSync(dir)) {
|
|
48
|
+
walkAndClean(dir, Date.now());
|
|
49
|
+
}
|
|
50
|
+
cleanupEnginePoolsBestEffort();
|
|
41
51
|
} catch (_e) {
|
|
42
52
|
// best-effort:任何异常不阻断 session_start
|
|
43
53
|
void _e;
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
/** 引擎池 TTL 兜底(dataDir 解析失败 = 未配置宿主服务,best-effort 吞掉)。 */
|
|
58
|
+
function cleanupEnginePoolsBestEffort(): void {
|
|
59
|
+
try {
|
|
60
|
+
cleanupExpiredPoolRefs(getEngineDataDir(), TTL_MS);
|
|
61
|
+
} catch (_e) {
|
|
62
|
+
void _e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 孤儿 sidecar 名判定:.cancelled/.finalized/.alive,以及 [MF#2] patch 回传
|
|
67
|
+
* (<branch>.patch 按 branch 命名,与 .jsonl basename 无关联,删除 .jsonl 时无法同删;
|
|
68
|
+
* 作为孤儿按 TTL 清理,避免永久堆积)。 */
|
|
69
|
+
function isOrphanSidecarName(name: string): boolean {
|
|
70
|
+
return (
|
|
71
|
+
name.endsWith(".cancelled") ||
|
|
72
|
+
name.endsWith(".finalized") ||
|
|
73
|
+
name.endsWith(".alive") ||
|
|
74
|
+
name.endsWith(".patch")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** stat 超 TTL 即 unlink(manifest .json 与孤儿 sidecar 共用原语)。失败静默(文件可能已被删除)。 */
|
|
79
|
+
function unlinkIfExpired(full: string, now: number): void {
|
|
80
|
+
try {
|
|
81
|
+
const stat = fs.statSync(full);
|
|
82
|
+
if (now - stat.mtimeMs > TTL_MS) {
|
|
83
|
+
fs.unlinkSync(full);
|
|
84
|
+
}
|
|
85
|
+
} catch (_e) {
|
|
86
|
+
// 文件可能已被删除,忽略
|
|
87
|
+
void _e;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** .jsonl 超 TTL 清理:.alive 探活保护(活进程的 session 不删,D-024 安全网)+
|
|
92
|
+
* 同名 sidecar 一起清理。失败静默(文件可能已被删除)。 */
|
|
93
|
+
function cleanExpiredJsonl(full: string, now: number): void {
|
|
94
|
+
try {
|
|
95
|
+
const stat = fs.statSync(full);
|
|
96
|
+
if (now - stat.mtimeMs > TTL_MS) {
|
|
97
|
+
// .alive 探活:活进程的 session 不删(D-024 安全网)。
|
|
98
|
+
const aliveMarker = readAliveMarker(full);
|
|
99
|
+
if (aliveMarker !== undefined && isProcessAlive(aliveMarker.pid)) {
|
|
100
|
+
return; // 活进程 → 跳过,不清理
|
|
101
|
+
}
|
|
102
|
+
fs.unlinkSync(full);
|
|
103
|
+
// 同名 sidecar 一起清理。
|
|
104
|
+
for (const ext of [".cancelled", ".finalized", ".alive"]) {
|
|
105
|
+
try { fs.unlinkSync(`${full}${ext}`); } catch (_e) { void _e; /* sidecar 可能不存在 */ }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (_e) {
|
|
109
|
+
// 文件可能已被删除,忽略
|
|
110
|
+
void _e;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 单条 dirent 分发。分支判断顺序与 fs 副作用时序保持与原实现一致:
|
|
115
|
+
* 目录递归 →(records 内)manifest .json → .jsonl → 孤儿 sidecar。 */
|
|
116
|
+
function cleanDirent(dir: string, entry: fs.Dirent, now: number, allowManifestJson: boolean): void {
|
|
117
|
+
const full = path.join(dir, entry.name);
|
|
118
|
+
if (entry.isDirectory()) {
|
|
119
|
+
// 只在进入名为 records 的子目录时打开 manifest .json 清理。
|
|
120
|
+
// records 在 <enc>/records/ 下递归自动覆盖;其他位置(如 subagents/worktrees.json)
|
|
121
|
+
// 不能匹配 .json——否则会误删 worktree reaper 依赖的状态文件。
|
|
122
|
+
walkAndClean(full, now, entry.name === "records");
|
|
123
|
+
} else if (
|
|
124
|
+
allowManifestJson &&
|
|
125
|
+
entry.name.endsWith(".json") &&
|
|
126
|
+
// 跳过 .tmp.:recoverTmpFiles(session_start)同步处理 tmp,GC 不重复。
|
|
127
|
+
// 不校验内容——30 天 mtime 已是强 orphan 信号,扩展名 + 文件名足够。
|
|
128
|
+
!entry.name.includes(".tmp.")
|
|
129
|
+
) {
|
|
130
|
+
unlinkIfExpired(full, now);
|
|
131
|
+
} else if (entry.name.endsWith(".jsonl")) {
|
|
132
|
+
cleanExpiredJsonl(full, now);
|
|
133
|
+
} else if (isOrphanSidecarName(entry.name)) {
|
|
134
|
+
// 孤儿 sidecar(兄弟 .jsonl 已被外部删除):按同 TTL 清理。
|
|
135
|
+
unlinkIfExpired(full, now);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
47
139
|
/** 递归扫描目录,unlink 超 TTL 的 .jsonl 文件及其 .cancelled sidecar。
|
|
48
140
|
* [F2] 进入名为 records 的子目录时,额外清理超 TTL 的 manifest .json(跳过 .tmp.——
|
|
49
141
|
* recoverTmpFiles 同步处理)。allowManifestJson 仅由父调用按目录名开启,其他位置
|
|
@@ -57,64 +149,6 @@ function walkAndClean(dir: string, now: number, allowManifestJson = false): void
|
|
|
57
149
|
return;
|
|
58
150
|
}
|
|
59
151
|
for (const entry of entries) {
|
|
60
|
-
|
|
61
|
-
if (entry.isDirectory()) {
|
|
62
|
-
// 只在进入名为 records 的子目录时打开 manifest .json 清理。
|
|
63
|
-
// records 在 <enc>/records/ 下递归自动覆盖;其他位置(如 subagents/worktrees.json)
|
|
64
|
-
// 不能匹配 .json——否则会误删 worktree reaper 依赖的状态文件。
|
|
65
|
-
walkAndClean(full, now, entry.name === "records");
|
|
66
|
-
} else if (
|
|
67
|
-
allowManifestJson &&
|
|
68
|
-
entry.name.endsWith(".json") &&
|
|
69
|
-
// 跳过 .tmp.:recoverTmpFiles(session_start)同步处理 tmp,GC 不重复。
|
|
70
|
-
// 不校验内容——30 天 mtime 已是强 orphan 信号,扩展名 + 文件名足够。
|
|
71
|
-
!entry.name.includes(".tmp.")
|
|
72
|
-
) {
|
|
73
|
-
try {
|
|
74
|
-
const stat = fs.statSync(full);
|
|
75
|
-
if (now - stat.mtimeMs > TTL_MS) {
|
|
76
|
-
fs.unlinkSync(full);
|
|
77
|
-
}
|
|
78
|
-
} catch (_e) {
|
|
79
|
-
// 文件可能已被删除,忽略
|
|
80
|
-
void _e;
|
|
81
|
-
}
|
|
82
|
-
} else if (entry.name.endsWith(".jsonl")) {
|
|
83
|
-
try {
|
|
84
|
-
const stat = fs.statSync(full);
|
|
85
|
-
if (now - stat.mtimeMs > TTL_MS) {
|
|
86
|
-
// .alive 探活:活进程的 session 不删(D-024 安全网)。
|
|
87
|
-
const aliveMarker = readAliveMarker(full);
|
|
88
|
-
if (aliveMarker !== undefined && isProcessAlive(aliveMarker.pid)) {
|
|
89
|
-
continue; // 活进程 → 跳过,不清理
|
|
90
|
-
}
|
|
91
|
-
fs.unlinkSync(full);
|
|
92
|
-
// 同名 sidecar 一起清理。
|
|
93
|
-
for (const ext of [".cancelled", ".finalized", ".alive"]) {
|
|
94
|
-
try { fs.unlinkSync(`${full}${ext}`); } catch (_e) { void _e; /* sidecar 可能不存在 */ }
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
} catch (_e) {
|
|
98
|
-
// 文件可能已被删除,忽略
|
|
99
|
-
void _e;
|
|
100
|
-
}
|
|
101
|
-
} else if (
|
|
102
|
-
entry.name.endsWith(".cancelled") ||
|
|
103
|
-
entry.name.endsWith(".finalized") ||
|
|
104
|
-
entry.name.endsWith(".alive") ||
|
|
105
|
-
// [MF#2] patch 回传(<branch>.patch)按 branch 命名,与 .jsonl basename 无关联,
|
|
106
|
-
// 删除 .jsonl 时无法同删;作为孤儿按 TTL 清理,避免永久堆积。
|
|
107
|
-
entry.name.endsWith(".patch")
|
|
108
|
-
) {
|
|
109
|
-
// 孤儿 sidecar(兄弟 .jsonl 已被外部删除):按同 TTL 清理。
|
|
110
|
-
try {
|
|
111
|
-
const stat = fs.statSync(full);
|
|
112
|
-
if (now - stat.mtimeMs > TTL_MS) {
|
|
113
|
-
fs.unlinkSync(full);
|
|
114
|
-
}
|
|
115
|
-
} catch (_e) {
|
|
116
|
-
void _e;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
152
|
+
cleanDirent(dir, entry, now, allowManifestJson);
|
|
119
153
|
}
|
|
120
154
|
}
|
|
@@ -274,39 +274,16 @@ interface PendingToolCall {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
// ============================================================
|
|
277
|
-
//
|
|
277
|
+
// 全量重建内部 helper(按处理阶段拆分:读文件 → 扫身份 → 重建 turns → 组装)
|
|
278
278
|
// ============================================================
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* ║ (损坏行静默跳过;失败 → undefined) ║
|
|
286
|
-
* ║ 2. 扫 custom entry(customType:"subagent-identity")→ 身份 ║
|
|
287
|
-
* ║ 缺 identity → undefined(无法构造 record) ║
|
|
288
|
-
* ║ 3. 顺序遍历 message entries: ║
|
|
289
|
-
* ║ - role:"assistant" → 开新 Turn ║
|
|
290
|
-
* ║ text/thinking 累积;usage → usageDelta ║
|
|
291
|
-
* ║ toolCall block → 待匹配队列;stopReason error → lastError ║
|
|
292
|
-
* ║ - role:"toolResult" → 配对 toolCallId ║
|
|
293
|
-
* ║ 产 InternalToolCall(done/failed)推进请求 Turn ║
|
|
294
|
-
* ║ 4. 闭合所有 turn;算 turnCount/totalTokens/result/eventLog ║
|
|
295
|
-
* ╚══════════════════════════════════════════════════════════════════╝
|
|
296
|
-
*
|
|
297
|
-
* 返回 undefined:文件缺失/空/损坏/缺 identity entry/无 assistant message。
|
|
298
|
-
* 不抛——消费方(collectRecords)降级跳过该 record。
|
|
299
|
-
*
|
|
300
|
-
* status 由最后一条 assistant message 的 stopReason 推导(error/aborted → failed,
|
|
301
|
-
* 其余 → done)。cancelled 由 tombstone override(record-store 层),本函数不感知。
|
|
302
|
-
*
|
|
303
|
-
* [perf] 本函数是重路径(全文读 + 全 entry 解析)。列表扫描用 readIdentityHeader
|
|
304
|
-
* (只读头部 identity),详情才走本函数(RecordStore.getFullRecord 懒加载)。
|
|
281
|
+
* 读文件 + 逐行 JSON.parse(session.jsonl = newline-delimited JSON)。
|
|
282
|
+
* 第 1 行是 session header(type:"session",跳过);后续每行一个 entry。
|
|
283
|
+
* 损坏行静默跳过(与 SDK parseSessionEntries 行为一致)。
|
|
284
|
+
* @returns 文件缺失/读失败 → undefined;否则 entry 数组(可能为空)。
|
|
305
285
|
*/
|
|
306
|
-
|
|
307
|
-
// 读文件 + 逐行 JSON.parse(session.jsonl = newline-delimited JSON)。
|
|
308
|
-
// 第 1 行是 session header(跳过);后续每行一个 entry。
|
|
309
|
-
// 损坏行静默跳过(与 SDK parseSessionEntries 行为一致)。
|
|
286
|
+
function readJsonlEntries(sessionFile: string): JsonlEntry[] | undefined {
|
|
310
287
|
let raw: string;
|
|
311
288
|
try {
|
|
312
289
|
raw = fs.readFileSync(sessionFile, "utf-8");
|
|
@@ -338,10 +315,18 @@ export function reconstructFromFile(sessionFile: string): ReconstructedRecord |
|
|
|
338
315
|
void _e; // 损坏行跳过(不阻断后续行)。
|
|
339
316
|
}
|
|
340
317
|
}
|
|
318
|
+
return entries;
|
|
319
|
+
}
|
|
341
320
|
|
|
342
|
-
|
|
321
|
+
/** 身份/模型扫描产出(identity custom entry + 途经的 model/thinking_level change)。 */
|
|
322
|
+
interface IdentityScan {
|
|
323
|
+
identity: SubagentIdentityData | undefined;
|
|
324
|
+
model: string;
|
|
325
|
+
thinkingLevel: string | undefined;
|
|
326
|
+
}
|
|
343
327
|
|
|
344
|
-
|
|
328
|
+
/** 扫 identity custom entry(必须存在,否则无法构造 record)+ 途经 model/thinking_level。 */
|
|
329
|
+
function scanIdentityAndModel(entries: JsonlEntry[]): IdentityScan {
|
|
345
330
|
let identity: SubagentIdentityData | undefined;
|
|
346
331
|
let model = "";
|
|
347
332
|
let thinkingLevel: string | undefined;
|
|
@@ -357,14 +342,95 @@ export function reconstructFromFile(sessionFile: string): ReconstructedRecord |
|
|
|
357
342
|
if (typeof entry.thinkingLevel === "string") thinkingLevel = entry.thinkingLevel;
|
|
358
343
|
}
|
|
359
344
|
}
|
|
360
|
-
|
|
345
|
+
return { identity, model, thinkingLevel };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** message entries 重建产出(turns + 跨 entry 累积状态)。 */
|
|
349
|
+
interface RebuiltTurns {
|
|
350
|
+
turns: Turn[];
|
|
351
|
+
lastError: string | undefined;
|
|
352
|
+
totalTokens: number;
|
|
353
|
+
/** 最后一条 entry 的时间戳(ms),推导 endedAt(避免重建 record 耗时随墙钟无限增长)。 */
|
|
354
|
+
lastEntryTsMs: number | undefined;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* assistant message → 填充调用方已创建的空 Turn:text/thinking 累积;toolCall block 进
|
|
359
|
+
* 待匹配队列;usage → usageDelta(token 总量增量返回给调用方累加)。
|
|
360
|
+
*
|
|
361
|
+
* stopReason 语义(与 updateFromEvent 一致):error/aborted → 设 lastError;
|
|
362
|
+
* stop(正常结束)→ 清 lastError(镜像 turn_end 的清除语义,即前序 turn 的瞬态
|
|
363
|
+
* error 在后续成功 turn 后恢复,不误判 success=false);其余 stopReason → 保持
|
|
364
|
+
* prevLastError 原值。 [v4 B-1] status 恒 closed 后 lastStopReason 不再参与推导,死变量已删。
|
|
365
|
+
*
|
|
366
|
+
* content 非数组 → 跳过 usage/stopReason 处理(镜像原实现的 continue):turn 仍存在
|
|
367
|
+
* (由调用方 push),lastError 透传 prevLastError、token 增量为 0。
|
|
368
|
+
*/
|
|
369
|
+
function applyAssistantMessage(
|
|
370
|
+
msg: JsonlAssistantMessage,
|
|
371
|
+
turn: Turn,
|
|
372
|
+
pending: PendingToolCall[],
|
|
373
|
+
prevLastError: string | undefined,
|
|
374
|
+
): { lastError: string | undefined; totalTokensDelta: number } {
|
|
375
|
+
if (!Array.isArray(msg.content)) {
|
|
376
|
+
return { lastError: prevLastError, totalTokensDelta: 0 };
|
|
377
|
+
}
|
|
378
|
+
for (const block of msg.content) {
|
|
379
|
+
if (block.type === "text") {
|
|
380
|
+
turn.text += block.text;
|
|
381
|
+
} else if (block.type === "thinking") {
|
|
382
|
+
turn.thinking += block.thinking;
|
|
383
|
+
} else if (block.type === "toolCall") {
|
|
384
|
+
pending.push({
|
|
385
|
+
toolCallId: block.id,
|
|
386
|
+
toolName: block.name,
|
|
387
|
+
args: block.arguments,
|
|
388
|
+
turn,
|
|
389
|
+
startedTs: msg.timestamp,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
let totalTokensDelta = 0;
|
|
395
|
+
if (msg.usage) {
|
|
396
|
+
const u = toAgentUsage(msg.usage);
|
|
397
|
+
turn.usageDelta = addUsage(turn.usageDelta, u);
|
|
398
|
+
totalTokensDelta = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let lastError = prevLastError;
|
|
402
|
+
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
403
|
+
lastError = msg.errorMessage ?? msg.stopReason;
|
|
404
|
+
} else if (msg.stopReason === "stop") {
|
|
405
|
+
lastError = undefined;
|
|
406
|
+
}
|
|
407
|
+
return { lastError, totalTokensDelta };
|
|
408
|
+
}
|
|
361
409
|
|
|
362
|
-
|
|
410
|
+
/** toolResult message → 按 toolCallId 配对回填 InternalToolCall;未配对(孤儿)→ 丢弃。 */
|
|
411
|
+
function applyToolResultMessage(msg: JsonlToolResultMessage, pending: PendingToolCall[]): void {
|
|
412
|
+
const idx = pending.findIndex((p) => p.toolCallId === msg.toolCallId);
|
|
413
|
+
if (idx >= 0) {
|
|
414
|
+
const p = pending[idx];
|
|
415
|
+
pending.splice(idx, 1);
|
|
416
|
+
const tc: InternalToolCall = {
|
|
417
|
+
toolName: p.toolName,
|
|
418
|
+
args: p.args,
|
|
419
|
+
result: { content: msg.content, details: msg.details },
|
|
420
|
+
isError: msg.isError ?? false,
|
|
421
|
+
_status: msg.isError ? "failed" : "done",
|
|
422
|
+
startedTs: p.startedTs,
|
|
423
|
+
};
|
|
424
|
+
p.turn.toolCalls.push(tc);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** 顺序遍历 entries 重建 turns[](assistant 开 turn / toolResult 配对回填;user 跳过)。 */
|
|
429
|
+
function rebuildTurns(entries: JsonlEntry[]): RebuiltTurns {
|
|
363
430
|
const turns: Turn[] = [];
|
|
364
431
|
const pending: PendingToolCall[] = [];
|
|
365
432
|
let lastError: string | undefined;
|
|
366
433
|
let totalTokens = 0;
|
|
367
|
-
/** 最后一条 entry 的时间戳(ms),推导 endedAt(避免重建 record 耗时随墙钟无限增长)。 */
|
|
368
434
|
let lastEntryTsMs: number | undefined;
|
|
369
435
|
|
|
370
436
|
for (const entry of entries) {
|
|
@@ -383,81 +449,38 @@ export function reconstructFromFile(sessionFile: string): ReconstructedRecord |
|
|
|
383
449
|
if (msg.role === "assistant") {
|
|
384
450
|
const turn = emptyTurn();
|
|
385
451
|
turns.push(turn);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (block.type === "text") {
|
|
390
|
-
turn.text += block.text;
|
|
391
|
-
} else if (block.type === "thinking") {
|
|
392
|
-
turn.thinking += block.thinking;
|
|
393
|
-
} else if (block.type === "toolCall") {
|
|
394
|
-
pending.push({
|
|
395
|
-
toolCallId: block.id,
|
|
396
|
-
toolName: block.name,
|
|
397
|
-
args: block.arguments,
|
|
398
|
-
turn,
|
|
399
|
-
startedTs: msg.timestamp,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (msg.usage) {
|
|
405
|
-
const u = toAgentUsage(msg.usage);
|
|
406
|
-
turn.usageDelta = addUsage(turn.usageDelta, u);
|
|
407
|
-
totalTokens += u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// stopReason 驱动 lastError(与 updateFromEvent 一致):
|
|
411
|
-
// error/aborted → 设 lastError;stop(正常结束)→ 清 lastError(镜像 turn_end 的清除语义,
|
|
412
|
-
// 即前序 turn 的瞬态 error 在后续成功 turn 后恢复,不误判 success=false)。
|
|
413
|
-
// [v4 B-1] status 恒 closed 后 lastStopReason 不再参与推导,死变量已删。
|
|
414
|
-
if (msg.stopReason === "error" || msg.stopReason === "aborted") {
|
|
415
|
-
lastError = msg.errorMessage ?? msg.stopReason;
|
|
416
|
-
} else if (msg.stopReason === "stop") {
|
|
417
|
-
lastError = undefined;
|
|
418
|
-
}
|
|
452
|
+
const applied = applyAssistantMessage(msg, turn, pending, lastError);
|
|
453
|
+
totalTokens += applied.totalTokensDelta;
|
|
454
|
+
lastError = applied.lastError;
|
|
419
455
|
} else if (msg.role === "toolResult") {
|
|
420
|
-
|
|
421
|
-
if (idx >= 0) {
|
|
422
|
-
const p = pending[idx];
|
|
423
|
-
pending.splice(idx, 1);
|
|
424
|
-
const tc: InternalToolCall = {
|
|
425
|
-
toolName: p.toolName,
|
|
426
|
-
args: p.args,
|
|
427
|
-
result: { content: msg.content, details: msg.details },
|
|
428
|
-
isError: msg.isError ?? false,
|
|
429
|
-
_status: msg.isError ? "failed" : "done",
|
|
430
|
-
startedTs: p.startedTs,
|
|
431
|
-
};
|
|
432
|
-
p.turn.toolCalls.push(tc);
|
|
433
|
-
}
|
|
434
|
-
// 未配对(孤儿 toolResult)→ 丢弃。
|
|
456
|
+
applyToolResultMessage(msg, pending);
|
|
435
457
|
}
|
|
436
458
|
// role:"user" → 跳过(task 来自 identity custom entry)。
|
|
437
459
|
}
|
|
460
|
+
return { turns, lastError, totalTokens, lastEntryTsMs };
|
|
461
|
+
}
|
|
438
462
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
.map((t) => t.text)
|
|
449
|
-
.filter((t) => t.length > 0)
|
|
450
|
-
.join("\n\n");
|
|
451
|
-
|
|
452
|
-
// eventLog 从 turns[] 派生(与活态 record 的 getEventLog 同形,消费方无感知差异)。
|
|
453
|
-
const eventLog = deriveEventLog(turns, lastError, identity.startedAt);
|
|
454
|
-
|
|
463
|
+
/** 组装 ReconstructedRecord(终态投影:status 恒 closed/gc + 身份字段归一化 + 派生数据)。 */
|
|
464
|
+
function buildReconstructedRecord(
|
|
465
|
+
sessionFile: string,
|
|
466
|
+
identity: SubagentIdentityData,
|
|
467
|
+
rebuilt: RebuiltTurns,
|
|
468
|
+
eventLog: AgentEventLogEntry[],
|
|
469
|
+
model: string,
|
|
470
|
+
thinkingLevel: string | undefined,
|
|
471
|
+
): ReconstructedRecord {
|
|
455
472
|
// 终态 status:最后一条 assistant message 的 stopReason 推导(与 finalizeRecord 的判定一致)。
|
|
456
473
|
// SP-1:done/failed 合并为 closed,cancelled 由 tombstone override(record-store 层),本函数不感知。
|
|
457
474
|
const status: ExecutionStatus = "closed";
|
|
458
475
|
// closedReason 统 gc(通用完成/失败)。error/aborted 的区分由 error 字段保留。
|
|
459
476
|
const closedReason: import("./types.ts").ClosedReason = "gc";
|
|
460
477
|
|
|
478
|
+
const turnCount = rebuilt.turns.length;
|
|
479
|
+
const resultText = rebuilt.turns
|
|
480
|
+
.map((t) => t.text)
|
|
481
|
+
.filter((t) => t.length > 0)
|
|
482
|
+
.join("\n\n");
|
|
483
|
+
|
|
461
484
|
// rootSessionId 归一化:新文件读 rootSessionId,旧文件 fallback parentSessionId。
|
|
462
485
|
const rootSessionId = identity.rootSessionId ?? identity.parentSessionId;
|
|
463
486
|
// slug 兜底:旧 identity entry 无 slug 字段 → 空串(与 SubagentRecord.slug: string 契约一致)。
|
|
@@ -472,19 +495,74 @@ export function reconstructFromFile(sessionFile: string): ReconstructedRecord |
|
|
|
472
495
|
sessionFile,
|
|
473
496
|
status,
|
|
474
497
|
closedReason,
|
|
475
|
-
turns,
|
|
498
|
+
turns: rebuilt.turns,
|
|
476
499
|
turnCount,
|
|
477
|
-
totalTokens,
|
|
478
|
-
lastError,
|
|
500
|
+
totalTokens: rebuilt.totalTokens,
|
|
501
|
+
lastError: rebuilt.lastError,
|
|
479
502
|
model,
|
|
480
503
|
thinkingLevel,
|
|
481
|
-
endedAt: lastEntryTsMs,
|
|
504
|
+
endedAt: rebuilt.lastEntryTsMs,
|
|
482
505
|
result: resultText.length > 0 ? resultText : undefined,
|
|
483
|
-
error: lastError,
|
|
506
|
+
error: rebuilt.lastError,
|
|
484
507
|
eventLog,
|
|
485
508
|
};
|
|
486
509
|
}
|
|
487
510
|
|
|
511
|
+
// ============================================================
|
|
512
|
+
// 公开函数
|
|
513
|
+
// ============================================================
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* 从 session.jsonl 重建完整 SubagentRecord 数据。
|
|
517
|
+
*
|
|
518
|
+
* ╔══════════════════════════════════════════════════════════════════╗
|
|
519
|
+
* ║ 1. readFileSync(sessionFile) + 逐行 JSON.parse(跳 header) ║
|
|
520
|
+
* ║ (损坏行静默跳过;失败 → undefined) ║
|
|
521
|
+
* ║ 2. 扫 custom entry(customType:"subagent-identity")→ 身份 ║
|
|
522
|
+
* ║ 缺 identity → undefined(无法构造 record) ║
|
|
523
|
+
* ║ 3. 顺序遍历 message entries: ║
|
|
524
|
+
* ║ - role:"assistant" → 开新 Turn ║
|
|
525
|
+
* ║ text/thinking 累积;usage → usageDelta ║
|
|
526
|
+
* ║ toolCall block → 待匹配队列;stopReason error → lastError ║
|
|
527
|
+
* ║ - role:"toolResult" → 配对 toolCallId ║
|
|
528
|
+
* ║ 产 InternalToolCall(done/failed)推进请求 Turn ║
|
|
529
|
+
* ║ 4. 闭合所有 turn;算 turnCount/totalTokens/result/eventLog ║
|
|
530
|
+
* ╚══════════════════════════════════════════════════════════════════╝
|
|
531
|
+
*
|
|
532
|
+
* 返回 undefined:文件缺失/空/损坏/缺 identity entry/无 assistant message。
|
|
533
|
+
* 不抛——消费方(collectRecords)降级跳过该 record。
|
|
534
|
+
*
|
|
535
|
+
* status 由最后一条 assistant message 的 stopReason 推导(error/aborted → failed,
|
|
536
|
+
* 其余 → done)。cancelled 由 tombstone override(record-store 层),本函数不感知。
|
|
537
|
+
*
|
|
538
|
+
* [perf] 本函数是重路径(全文读 + 全 entry 解析)。列表扫描用 readIdentityHeader
|
|
539
|
+
* (只读头部 identity),详情才走本函数(RecordStore.getFullRecord 懒加载)。
|
|
540
|
+
*/
|
|
541
|
+
export function reconstructFromFile(sessionFile: string): ReconstructedRecord | undefined {
|
|
542
|
+
// 1. 读文件 + 逐行 JSON.parse(readJsonlEntries:损坏行静默跳过;文件缺失 → undefined)。
|
|
543
|
+
const entries = readJsonlEntries(sessionFile);
|
|
544
|
+
if (!entries || entries.length === 0) return undefined;
|
|
545
|
+
|
|
546
|
+
// 2. 扫身份 custom entry(scanIdentityAndModel:identity 缺失 → 无法构造 record)。
|
|
547
|
+
const { identity, model, thinkingLevel } = scanIdentityAndModel(entries);
|
|
548
|
+
if (!identity) return undefined; // 缺身份 → 无法构造 record。
|
|
549
|
+
|
|
550
|
+
// 3. 重建 turns[](rebuildTurns:assistant 开 turn / toolResult 配对回填)。
|
|
551
|
+
const rebuilt = rebuildTurns(entries);
|
|
552
|
+
if (rebuilt.turns.length === 0) return undefined; // 无 assistant message → 空壳,降级。
|
|
553
|
+
|
|
554
|
+
// 4. 闭合所有 turn(重建的是历史,全是已完成 turn)。
|
|
555
|
+
for (const turn of rebuilt.turns) {
|
|
556
|
+
turn.closed = true;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// eventLog 从 turns[] 派生(与活态 record 的 getEventLog 同形,消费方无感知差异)。
|
|
560
|
+
// 必须在闭合 turn 之后派生(deriveEventLog 读 turn.closed)。
|
|
561
|
+
const eventLog = deriveEventLog(rebuilt.turns, rebuilt.lastError, identity.startedAt);
|
|
562
|
+
|
|
563
|
+
return buildReconstructedRecord(sessionFile, identity, rebuilt, eventLog, model, thinkingLevel);
|
|
564
|
+
}
|
|
565
|
+
|
|
488
566
|
// ============================================================
|
|
489
567
|
// 轻量头部读取(列表扫描用)
|
|
490
568
|
// ============================================================
|
|
@@ -615,48 +693,62 @@ export function readIdentityTail(sessionFile: string): IdentityHeaderRecon | und
|
|
|
615
693
|
return undefined;
|
|
616
694
|
}
|
|
617
695
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
696
|
+
// ============================================================
|
|
697
|
+
// 轻量 identity 扫描内部 helper(parseIdentityFromText 按阶段分发)
|
|
698
|
+
// ============================================================
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* 预筛([S-4] 同款值匹配):只有含三种目标 entry 值串的行才值得 JSON.parse——
|
|
702
|
+
* head 64KB 内大 toolResult 行(数十 KB/行)占多数,全量 parse 是冷扫描 CPU 大头。
|
|
703
|
+
* 值字符串在任意序列化格式下连续出现,不耦合 pi 的空格习惯。
|
|
704
|
+
*/
|
|
705
|
+
function lineMayCarryScannableEntry(s: string): boolean {
|
|
706
|
+
return (
|
|
707
|
+
s.includes(IDENTITY_CUSTOM_TYPE) ||
|
|
708
|
+
s.includes('"model_change"') ||
|
|
709
|
+
s.includes('"thinking_level_change"')
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** 轻量扫描的可变累积态(identity + 途经 model/thinkingLevel)。 */
|
|
714
|
+
interface LightIdentityScan {
|
|
715
|
+
identity: SubagentIdentityData | undefined;
|
|
716
|
+
model: string;
|
|
717
|
+
thinkingLevel: string | undefined;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* 单条 entry 并入扫描态。identity 命中(custom + customType 匹配 + data 合法)返回
|
|
722
|
+
* true 供调用方 break(找到即停,后续行不再解析);其余恒 false。三个目标 type 互斥,
|
|
723
|
+
* 与原 if/else-if 链语义一致;custom 但 data 非法 → 不并入、不中断扫描。
|
|
724
|
+
*/
|
|
725
|
+
function absorbScannedEntry(state: LightIdentityScan, entry: JsonlEntry): boolean {
|
|
726
|
+
if (entry.type === "custom" && entry.customType === IDENTITY_CUSTOM_TYPE) {
|
|
727
|
+
if (isIdentityData(entry.data)) {
|
|
728
|
+
state.identity = entry.data;
|
|
729
|
+
return true;
|
|
643
730
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (typeof entry.provider === "string" && typeof entry.modelId === "string") {
|
|
651
|
-
model = `${entry.provider}/${entry.modelId}`;
|
|
652
|
-
}
|
|
653
|
-
} else if (entry.type === "thinking_level_change") {
|
|
654
|
-
if (typeof entry.thinkingLevel === "string") thinkingLevel = entry.thinkingLevel;
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
if (entry.type === "model_change") {
|
|
734
|
+
// model_change: {provider, modelId} → "provider/modelId"(与 record.model 同形)。
|
|
735
|
+
if (typeof entry.provider === "string" && typeof entry.modelId === "string") {
|
|
736
|
+
state.model = `${entry.provider}/${entry.modelId}`;
|
|
655
737
|
}
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
if (entry.type === "thinking_level_change") {
|
|
741
|
+
if (typeof entry.thinkingLevel === "string") state.thinkingLevel = entry.thinkingLevel;
|
|
656
742
|
}
|
|
657
|
-
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
658
745
|
|
|
659
|
-
|
|
746
|
+
/** 扫描态 → IdentityHeaderRecon(归一化与全量 recon 同源:rootSessionId fallback 旧字段、slug 兜底空串)。 */
|
|
747
|
+
function toIdentityRecon(
|
|
748
|
+
state: LightIdentityScan,
|
|
749
|
+
identity: SubagentIdentityData,
|
|
750
|
+
sessionFile: string,
|
|
751
|
+
): IdentityHeaderRecon {
|
|
660
752
|
const rootSessionId = identity.rootSessionId ?? identity.parentSessionId;
|
|
661
753
|
return {
|
|
662
754
|
id: identity.id,
|
|
@@ -671,8 +763,29 @@ function parseIdentityFromText(text: string, sessionFile: string): IdentityHeade
|
|
|
671
763
|
forkDepth: identity.forkDepth,
|
|
672
764
|
chatMode: identity.chatMode,
|
|
673
765
|
worktree: identity.worktree,
|
|
674
|
-
model,
|
|
675
|
-
thinkingLevel,
|
|
766
|
+
model: state.model,
|
|
767
|
+
thinkingLevel: state.thinkingLevel,
|
|
676
768
|
sessionFile,
|
|
677
769
|
};
|
|
678
770
|
}
|
|
771
|
+
|
|
772
|
+
/** 从文本片段解析 identity(头部/全文两入口共享)。解析到 identity 即停(返回);
|
|
773
|
+
* 找不到返回 undefined。model/thinking_level 仅在 identity 之前的途经 entry 中
|
|
774
|
+
* best-effort 提取,详情场景由全量重建补齐。 */
|
|
775
|
+
function parseIdentityFromText(text: string, sessionFile: string): IdentityHeaderRecon | undefined {
|
|
776
|
+
const state: LightIdentityScan = { identity: undefined, model: "", thinkingLevel: undefined };
|
|
777
|
+
for (const line of text.split("\n")) {
|
|
778
|
+
const s = line.trim();
|
|
779
|
+
if (!s) continue;
|
|
780
|
+
if (!lineMayCarryScannableEntry(s)) continue;
|
|
781
|
+
let entry: JsonlEntry;
|
|
782
|
+
try {
|
|
783
|
+
entry = JSON.parse(s) as JsonlEntry;
|
|
784
|
+
} catch {
|
|
785
|
+
continue; // 损坏行/截断行(头部读取边界)跳过
|
|
786
|
+
}
|
|
787
|
+
if (absorbScannedEntry(state, entry)) break; // 找到即停(后续行不再解析)
|
|
788
|
+
}
|
|
789
|
+
if (!state.identity) return undefined;
|
|
790
|
+
return toIdentityRecon(state, state.identity, sessionFile);
|
|
791
|
+
}
|