@zhushanwen/subagent-core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/chunk-3VOERJPJ.js +22 -0
- package/dist/chunk-A4IVZWVX.js +31 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
- package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
- package/dist/execution/engine/engines/zcode/constants.js +22 -0
- package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
- package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
- package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
- package/dist/execution/engine/engines/zcode/reader.js +239 -0
- package/dist/execution/engine/paths.cjs +55 -0
- package/dist/execution/engine/paths.d.cts +8 -0
- package/dist/execution/engine/paths.d.ts +8 -0
- package/dist/execution/engine/paths.js +26 -0
- package/dist/execution/relay-env.cjs +62 -0
- package/dist/execution/relay-env.d.cts +33 -0
- package/dist/execution/relay-env.d.ts +33 -0
- package/dist/execution/relay-env.js +20 -0
- package/dist/index.cjs +1852 -0
- package/dist/index.d.cts +1184 -0
- package/dist/index.d.ts +1184 -0
- package/dist/index.js +1810 -0
- package/dist/types-BxyAidGf.d.cts +875 -0
- package/dist/types-BxyAidGf.d.ts +875 -0
- package/package.json +99 -0
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
- package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
- package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
- package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
- package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
- package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
- package/src/__tests__/m2-append-content-probe.test.ts +73 -0
- package/src/__tests__/manifest-store.test.ts +368 -0
- package/src/__tests__/record-store-cache.test.ts +335 -0
- package/src/__tests__/record-store-index.test.ts +560 -0
- package/src/__tests__/record-store-last-line.test.ts +129 -0
- package/src/__tests__/review-fix-loop-script.test.ts +288 -0
- package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
- package/src/__tests__/robustness-low-batch1.test.ts +286 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
- package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
- package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
- package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
- package/src/__tests__/session-runner.test.ts +69 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/stream-sink.test.ts +203 -0
- package/src/core/__tests__/host-services.test.ts +144 -0
- package/src/core/__tests__/logger.test.ts +78 -0
- package/src/core/__tests__/notify-ports.test.ts +104 -0
- package/src/core/host-services.ts +97 -0
- package/src/core/logger.ts +47 -0
- package/src/core/notify-ports.ts +126 -0
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
- package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
- package/src/execution/__tests__/config.test.ts +302 -0
- package/src/execution/__tests__/delivery-methods.test.ts +424 -0
- package/src/execution/__tests__/dialog-queue.test.ts +299 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
- package/src/execution/__tests__/execute-nesting.test.ts +323 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
- package/src/execution/__tests__/execution-record.test.ts +1394 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/finalize-record.test.ts +367 -0
- package/src/execution/__tests__/finalized-marker.test.ts +104 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
- package/src/execution/__tests__/host-mode.test.ts +87 -0
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
- package/src/execution/__tests__/model-resolver.test.ts +465 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/output-collector.test.ts +358 -0
- package/src/execution/__tests__/path-encoding.test.ts +104 -0
- package/src/execution/__tests__/pi-invocation.test.ts +134 -0
- package/src/execution/__tests__/record-store.test.ts +1057 -0
- package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
- package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/rpc-mode.test.ts +89 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +293 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
- package/src/execution/__tests__/spawn-args.test.ts +445 -0
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/stdin-writer.test.ts +462 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +788 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +615 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/ui-channels.test.ts +187 -0
- package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
- package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
- package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
- package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
- package/src/execution/__tests__/worktree-manager.test.ts +633 -0
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +199 -0
- package/src/execution/agent-registry.ts +199 -0
- package/src/execution/agent-result-mapper.ts +90 -0
- package/src/execution/alive-store.ts +119 -0
- package/src/execution/argv-mirror.ts +113 -0
- package/src/execution/best-effort.ts +38 -0
- package/src/execution/channel-registry-access.ts +144 -0
- package/src/execution/concurrency-pool.ts +116 -0
- package/src/execution/config.ts +165 -0
- package/src/execution/dialog-queue.ts +330 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
- package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
- package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
- package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
- package/src/execution/engine/__tests__/paths.test.ts +39 -0
- package/src/execution/engine/__tests__/registry.test.ts +120 -0
- package/src/execution/engine/__tests__/routing.test.ts +231 -0
- package/src/execution/engine/common/data-dir.ts +66 -0
- package/src/execution/engine/common/errors.ts +183 -0
- package/src/execution/engine/common/event-journal.ts +254 -0
- package/src/execution/engine/common/journal-replay.ts +62 -0
- package/src/execution/engine/common/kill-chain.ts +221 -0
- package/src/execution/engine/common/nesting-guard.ts +50 -0
- package/src/execution/engine/common/persona-router.ts +108 -0
- package/src/execution/engine/common/pool-manager.ts +226 -0
- package/src/execution/engine/common/schema-emulation.ts +189 -0
- package/src/execution/engine/common/session-view-projection.ts +51 -0
- package/src/execution/engine/engine-discovery.ts +65 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
- package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
- package/src/execution/engine/engines/pi/reader.ts +48 -0
- package/src/execution/engine/engines/pi/registration.ts +35 -0
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +158 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +133 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +115 -0
- package/src/execution/execution-record.ts +984 -0
- package/src/execution/finalize-record.ts +254 -0
- package/src/execution/finalized-marker.ts +70 -0
- package/src/execution/get-state-handshake.ts +104 -0
- package/src/execution/host-mode.ts +56 -0
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +513 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +256 -0
- package/src/execution/model-config-service.ts +250 -0
- package/src/execution/model-resolver.ts +248 -0
- package/src/execution/notifier.ts +372 -0
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +228 -0
- package/src/execution/path-encoding.ts +60 -0
- package/src/execution/pi-invocation.ts +120 -0
- package/src/execution/record-entry.ts +132 -0
- package/src/execution/record-store.ts +1265 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +120 -0
- package/src/execution/session-pending.ts +170 -0
- package/src/execution/session-reconstructor.ts +678 -0
- package/src/execution/session-runner.ts +1789 -0
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/spawn-event-adapter.ts +363 -0
- package/src/execution/stdin-writer.ts +198 -0
- package/src/execution/stream-sink.ts +126 -0
- package/src/execution/subagent-service.ts +2268 -0
- package/src/execution/subprocess-agent-runner.ts +317 -0
- package/src/execution/temp-prompt.ts +62 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +102 -0
- package/src/execution/types.ts +1023 -0
- package/src/execution/ui-channels.ts +216 -0
- package/src/execution/ui-interaction-model.ts +48 -0
- package/src/execution/ui-request-handler-factory.ts +243 -0
- package/src/execution/ui-request-observability.ts +81 -0
- package/src/execution/ui-request-queue.ts +184 -0
- package/src/execution/worktree-manager.ts +688 -0
- package/src/execution/worktree-registry.ts +279 -0
- package/src/index.ts +87 -0
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
- package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +503 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
- package/src/orchestration/__tests__/script-lint.test.ts +831 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/agent-opts-resolver.ts +174 -0
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +316 -0
- package/src/orchestration/error-recovery.ts +1018 -0
- package/src/orchestration/execute-agent-call.ts +256 -0
- package/src/orchestration/launcher.ts +455 -0
- package/src/orchestration/lifecycle.ts +399 -0
- package/src/orchestration/models/__tests__/budget.test.ts +307 -0
- package/src/orchestration/models/agent-call.ts +83 -0
- package/src/orchestration/models/budget.ts +118 -0
- package/src/orchestration/models/ports.ts +164 -0
- package/src/orchestration/models/run-runtime.ts +104 -0
- package/src/orchestration/models/run-spec.ts +83 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +183 -0
- package/src/orchestration/models/types.ts +301 -0
- package/src/orchestration/models/workflow-run.ts +254 -0
- package/src/orchestration/models/workflow-script-registry.ts +35 -0
- package/src/orchestration/models/workflow-script.ts +117 -0
- package/src/orchestration/script-lint.ts +766 -0
- package/src/orchestration/skill-discovery.ts +135 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +98 -0
- package/src/orchestration/worker-script-builder.ts +421 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +133 -0
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
- package/src/shared/__tests__/resource-discovery.test.ts +749 -0
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/agent-ref.ts +57 -0
- package/src/shared/meta-parser.ts +261 -0
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-discovery.ts +835 -0
- package/src/shared/resource-meta.ts +65 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +58 -0
- package/src/shared/timer-delay.ts +54 -0
- package/src/shared/xml-injection.ts +35 -0
- package/workflows/README.md +81 -0
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +137 -0
- package/workflows/map-reduce.js +180 -0
- package/workflows/parallel.js +154 -0
- package/workflows/review-fix-loop-utils.cjs +1542 -0
- package/workflows/review-fix-loop.js +1605 -0
- package/workflows/scatter-gather.js +184 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
// src/core/session-reconstructor.ts
|
|
2
|
+
//
|
|
3
|
+
// 从 subagent 的 session.jsonl 重建完整的 SubagentRecord 数据。
|
|
4
|
+
//
|
|
5
|
+
// 背景:subagent 执行时 SDK 把完整对话(assistant text/thinking/toolCall + usage +
|
|
6
|
+
// toolResult)实时 flush 进 session.jsonl。终态后内存 record 被立即淘汰(archive),
|
|
7
|
+
// collectRecords 读时用本模块从 session.jsonl 重建 turns[],恢复 eventLog/result/error
|
|
8
|
+
// 等富数据——session.jsonl 是唯一 source of truth(history.jsonl 已废弃)。
|
|
9
|
+
//
|
|
10
|
+
// 身份恢复:session.jsonl 的 header 不含 ExecutionRecord.id / agent / mode,故
|
|
11
|
+
// session-runner 在创建 session 后立即写一条 custom entry(customType:"subagent-identity")
|
|
12
|
+
// 携带 {id, agent, mode, task, startedAt}。本模块读这条 entry 恢复身份。
|
|
13
|
+
//
|
|
14
|
+
// 纯函数 + 防御性 I/O:任何文件缺失/损坏/格式漂移/缺 identity entry 均返回 undefined
|
|
15
|
+
// (不抛),消费方降级跳过该 record 而非崩溃。与 execution-record.ts 同层(Core 纯数据变换)。
|
|
16
|
+
//
|
|
17
|
+
// 直接读文件 + 逐行 JSON.parse(session.jsonl = newline-delimited JSON),不依赖 SDK
|
|
18
|
+
// SessionManager 类——避免 tsconfig paths fallback 把 class 降级为 interface 的坑。
|
|
19
|
+
|
|
20
|
+
import * as fs from "node:fs";
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
AgentEventLogEntry,
|
|
24
|
+
AgentUsage,
|
|
25
|
+
ExecutionMode,
|
|
26
|
+
ExecutionStatus,
|
|
27
|
+
InternalToolCall,
|
|
28
|
+
Turn,
|
|
29
|
+
} from "./types.ts";
|
|
30
|
+
import { extractLabelFromArgs } from "./execution-record.ts";
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// 类型(SDK jsonl 结构的最小子集——窄化为重建所需字段)
|
|
34
|
+
// ============================================================
|
|
35
|
+
|
|
36
|
+
/** assistant message 的 usage(pi-ai Usage 的最小消费字段)。 */
|
|
37
|
+
interface JsonlUsage {
|
|
38
|
+
input?: number;
|
|
39
|
+
output?: number;
|
|
40
|
+
cacheRead?: number;
|
|
41
|
+
cacheWrite?: number;
|
|
42
|
+
cost?: { total?: number };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** assistant content block 联合(text/thinking/toolCall——重建只消费这三类)。 */
|
|
46
|
+
interface TextBlock { type: "text"; text: string; }
|
|
47
|
+
interface ThinkingBlock { type: "thinking"; thinking: string; }
|
|
48
|
+
interface ToolCallBlock { type: "toolCall"; id: string; name: string; arguments?: unknown; }
|
|
49
|
+
type AssistantContentBlock = TextBlock | ThinkingBlock | ToolCallBlock;
|
|
50
|
+
|
|
51
|
+
/** assistant message(pi-ai AssistantMessage 的最小消费字段)。 */
|
|
52
|
+
interface JsonlAssistantMessage {
|
|
53
|
+
role: "assistant";
|
|
54
|
+
content: AssistantContentBlock[];
|
|
55
|
+
usage?: JsonlUsage;
|
|
56
|
+
stopReason?: string;
|
|
57
|
+
errorMessage?: string;
|
|
58
|
+
timestamp: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** toolResult message(pi-ai ToolResultMessage 的最小消费字段)。 */
|
|
62
|
+
interface JsonlToolResultMessage {
|
|
63
|
+
role: "toolResult";
|
|
64
|
+
toolCallId: string;
|
|
65
|
+
toolName: string;
|
|
66
|
+
content?: unknown[];
|
|
67
|
+
details?: unknown;
|
|
68
|
+
isError?: boolean;
|
|
69
|
+
timestamp: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** user message(重建 turns[] 时跳过,仅用于类型穷尽)。 */
|
|
73
|
+
interface JsonlUserMessage {
|
|
74
|
+
role: "user";
|
|
75
|
+
content: unknown;
|
|
76
|
+
timestamp: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type JsonlMessage = JsonlUserMessage | JsonlAssistantMessage | JsonlToolResultMessage;
|
|
80
|
+
|
|
81
|
+
/** custom entry 的 data(session-runner 写入的 subagent-identity)。 */
|
|
82
|
+
export interface SubagentIdentityData {
|
|
83
|
+
id: string;
|
|
84
|
+
agent: string;
|
|
85
|
+
mode: ExecutionMode;
|
|
86
|
+
task: string;
|
|
87
|
+
/**
|
|
88
|
+
* 短标签(≤20 字符)。旧文件缺失→读取兜底空串(见 reconstructFromFile)。
|
|
89
|
+
* 写入时(session-runner)必填;读取时可选以兼容旧文件。
|
|
90
|
+
*/
|
|
91
|
+
slug?: string;
|
|
92
|
+
startedAt: number;
|
|
93
|
+
/** 根 Pi session ID(session 隔离过滤用)。旧文件可能缺失。 */
|
|
94
|
+
rootSessionId?: string;
|
|
95
|
+
/** 直接父 subagent record ID。旧文件缺失 → undefined(顶层)。 */
|
|
96
|
+
parentRecordId?: string;
|
|
97
|
+
/** subagent 递归深度。旧文件缺失 → 0(顶层)。 */
|
|
98
|
+
depth?: number;
|
|
99
|
+
/** [MF#4] 本 session 的 fork 深度(session-runner 写入 parentForkDepth+1)。旧文件可能缺失。 */
|
|
100
|
+
forkDepth?: number;
|
|
101
|
+
/**
|
|
102
|
+
* 对话模式标志(可持续对话 subagent)。旧文件缺失 → undefined(按一次性模式处理)。
|
|
103
|
+
* session-runner 写入;reconstructFromFile 经 ...identity 展开自动透传到 ReconstructedRecord。
|
|
104
|
+
*/
|
|
105
|
+
chatMode?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* [review round2] worktree 隔离标志(该 subagent 创建时 worktree:true)。旧文件缺失 →
|
|
108
|
+
* undefined。仅供跨重启重建路径判定「worktree 绑定已丢失」——WorktreeHandle 本身不可
|
|
109
|
+
* 序列化,跨重启后无法 reattach,续聊须拒绝(防 resume cwd 静默回落主 repo 破坏隔离)。
|
|
110
|
+
*/
|
|
111
|
+
worktree?: boolean;
|
|
112
|
+
/** @deprecated 兼容旧文件:旧 identity entry 写的是 parentSessionId,读取时 fallback 到 rootSessionId。 */
|
|
113
|
+
parentSessionId?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** SDK jsonl entry(getEntries() 返回,header 已排除)。 */
|
|
117
|
+
interface JsonlEntry {
|
|
118
|
+
type: string;
|
|
119
|
+
message?: JsonlMessage;
|
|
120
|
+
customType?: string;
|
|
121
|
+
data?: unknown;
|
|
122
|
+
/** model_change entry 的字段。 */
|
|
123
|
+
provider?: string;
|
|
124
|
+
modelId?: string;
|
|
125
|
+
/** thinking_level_change entry 的字段。 */
|
|
126
|
+
thinkingLevel?: string;
|
|
127
|
+
/** entry 级别的时间戳(ISO 字符串,SDK 逐行写入)。供 endedAt 推导。 */
|
|
128
|
+
timestamp?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// 公开类型
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
/** custom entry 的 customType 标识(session-runner 写 / reconstructor 读,约定常量)。 */
|
|
136
|
+
export const IDENTITY_CUSTOM_TYPE = "subagent-identity";
|
|
137
|
+
|
|
138
|
+
/** 重建产出的完整 SubagentRecord 数据(身份 + 可变状态 + 派生 eventLog)。 */
|
|
139
|
+
export interface ReconstructedRecord {
|
|
140
|
+
// ── 身份(来自 custom entry)──
|
|
141
|
+
id: string;
|
|
142
|
+
agent: string;
|
|
143
|
+
mode: ExecutionMode;
|
|
144
|
+
task: string;
|
|
145
|
+
/** 短标签(≤20 字符)。旧文件缺失→兜底空串。 */
|
|
146
|
+
slug: string;
|
|
147
|
+
startedAt: number;
|
|
148
|
+
/** 根 Pi session ID(session 隔离过滤用)。旧文件可能缺失。 */
|
|
149
|
+
rootSessionId: string | undefined;
|
|
150
|
+
/** 直接父 subagent record ID。旧文件缺失 → undefined(顶层)。 */
|
|
151
|
+
parentRecordId: string | undefined;
|
|
152
|
+
/** subagent 递归深度。旧文件缺失 → 0(顶层)。 */
|
|
153
|
+
depth: number;
|
|
154
|
+
/** [MF#4] 本 session 的 fork 深度(来自 identity custom entry;旧文件为 undefined)。 */
|
|
155
|
+
forkDepth: number | undefined;
|
|
156
|
+
/** 对话模式标志(来自 identity custom entry;旧文件为 undefined)。 */
|
|
157
|
+
chatMode?: boolean;
|
|
158
|
+
/** [review round2] worktree 隔离标志(见 SubagentIdentityData.worktree)。 */
|
|
159
|
+
worktree?: boolean;
|
|
160
|
+
/**
|
|
161
|
+
* 对话轮次计数(非 identity entry 字段,reconstructFromFile 不填)。
|
|
162
|
+
* V2 idle record 的 round 只在内存维护(doFinalizeRoundToIdle 递增),磁盘重建不恢复。
|
|
163
|
+
*/
|
|
164
|
+
round?: number;
|
|
165
|
+
sessionFile: string;
|
|
166
|
+
// ── 可变状态(来自 message entries)──
|
|
167
|
+
status: ExecutionStatus;
|
|
168
|
+
/** L2 关闭原因子枚举(仅 status="closed" 时有意义)。SP-1 新增。
|
|
169
|
+
* 从 stopReason 推导:error/aborted → gc;其余 → gc。 */
|
|
170
|
+
closedReason?: import("./types.ts").ClosedReason;
|
|
171
|
+
turns: Turn[];
|
|
172
|
+
turnCount: number;
|
|
173
|
+
totalTokens: number;
|
|
174
|
+
lastError: string | undefined;
|
|
175
|
+
model: string;
|
|
176
|
+
thinkingLevel: string | undefined;
|
|
177
|
+
/** 最后一条 entry 的时间戳(ms)。供 finalize/crashed 重建填 endedAt,避免耗时无限增长。 */
|
|
178
|
+
endedAt: number | undefined;
|
|
179
|
+
/** 各 turn text 拼接的完整正文(镜像 getFullText)。 */
|
|
180
|
+
result: string | undefined;
|
|
181
|
+
/** 来自最后一条 error/aborted assistant message(无则 undefined)。 */
|
|
182
|
+
error: string | undefined;
|
|
183
|
+
/** 从 turns[] 派生的离散语义事件序列(与活态 record 同形)。 */
|
|
184
|
+
eventLog: AgentEventLogEntry[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ============================================================
|
|
188
|
+
// 内部 helper
|
|
189
|
+
// ============================================================
|
|
190
|
+
|
|
191
|
+
/** turn_end 摘要截断长度(镜像 execution-record.ts TURN_SUMMARY_MAX)。 */
|
|
192
|
+
const TURN_SUMMARY_MAX = 80;
|
|
193
|
+
|
|
194
|
+
/** 从 turns[] 派生 eventLog(镜像 execution-record.ts getEventLog,但接受最小结构)。 */
|
|
195
|
+
function deriveEventLog(
|
|
196
|
+
turns: Turn[],
|
|
197
|
+
lastError: string | undefined,
|
|
198
|
+
startedAt: number,
|
|
199
|
+
): AgentEventLogEntry[] {
|
|
200
|
+
const log: AgentEventLogEntry[] = [];
|
|
201
|
+
for (const turn of turns) {
|
|
202
|
+
for (const tc of turn.toolCalls) {
|
|
203
|
+
const label = extractLabelFromArgs(tc.toolName, tc.args);
|
|
204
|
+
const ts = tc.startedTs;
|
|
205
|
+
log.push({ type: "tool_start", label, ts, status: "running" });
|
|
206
|
+
if (tc._status !== "running") {
|
|
207
|
+
log.push({ type: "tool_end", label, ts, status: tc._status });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (turn.closed) {
|
|
211
|
+
const summary = turn.text.length > 0
|
|
212
|
+
? (turn.text.length > TURN_SUMMARY_MAX ? turn.text.slice(0, TURN_SUMMARY_MAX) : turn.text)
|
|
213
|
+
: "turn";
|
|
214
|
+
log.push({ type: "turn_end", label: summary, ts: turn.closedTs ?? startedAt });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (lastError) {
|
|
218
|
+
log.push({ type: "error", label: lastError, ts: Date.now() });
|
|
219
|
+
}
|
|
220
|
+
return log;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 空 turn(镜像 execution-record.ts 的 emptyTurn,但本模块独立——避免循环依赖)。 */
|
|
224
|
+
function emptyTurn(): Turn {
|
|
225
|
+
return { text: "", thinking: "", toolCalls: [], usageDelta: undefined, closed: false };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** JsonlUsage → AgentUsage(cost 取 cost.total,与 session-runner 扁平化一致)。 */
|
|
229
|
+
function toAgentUsage(u: JsonlUsage): AgentUsage {
|
|
230
|
+
return {
|
|
231
|
+
input: u.input ?? 0,
|
|
232
|
+
output: u.output ?? 0,
|
|
233
|
+
cacheRead: u.cacheRead ?? 0,
|
|
234
|
+
cacheWrite: u.cacheWrite ?? 0,
|
|
235
|
+
cost: u.cost?.total,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 累加 AgentUsage(field-wise,镜像 execution-record.ts addUsage)。 */
|
|
240
|
+
function addUsage(prev: AgentUsage | undefined, next: AgentUsage): AgentUsage {
|
|
241
|
+
if (prev === undefined) return { ...next };
|
|
242
|
+
return {
|
|
243
|
+
input: prev.input + next.input,
|
|
244
|
+
output: prev.output + next.output,
|
|
245
|
+
cacheRead: prev.cacheRead + next.cacheRead,
|
|
246
|
+
cacheWrite: prev.cacheWrite + next.cacheWrite,
|
|
247
|
+
cost: (prev.cost ?? 0) + (next.cost ?? 0),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** 校验 custom entry data 是否为合法 SubagentIdentityData。 */
|
|
252
|
+
function isIdentityData(data: unknown): data is SubagentIdentityData {
|
|
253
|
+
if (typeof data !== "object" || data === null) return false;
|
|
254
|
+
const d = data as Record<string, unknown>;
|
|
255
|
+
return (
|
|
256
|
+
typeof d.id === "string" &&
|
|
257
|
+
typeof d.agent === "string" &&
|
|
258
|
+
(d.mode === "sync" || d.mode === "background") &&
|
|
259
|
+
typeof d.task === "string" &&
|
|
260
|
+
typeof d.startedAt === "number"
|
|
261
|
+
);
|
|
262
|
+
}/**
|
|
263
|
+
* 待匹配的 toolCall(assistant 发起,等 toolResult 回填)。
|
|
264
|
+
* 记录在 assistant Turn 上,toolResult 到达时按 toolCallId 找到并填充。
|
|
265
|
+
*/
|
|
266
|
+
interface PendingToolCall {
|
|
267
|
+
toolCallId: string;
|
|
268
|
+
toolName: string;
|
|
269
|
+
args: unknown;
|
|
270
|
+
/** 所属 Turn(toolResult 回填时推进这个 Turn 的 toolCalls)。 */
|
|
271
|
+
turn: Turn;
|
|
272
|
+
/** assistant message timestamp(作为 InternalToolCall.startedTs)。 */
|
|
273
|
+
startedTs: number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================
|
|
277
|
+
// 公开函数
|
|
278
|
+
// ============================================================
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 从 session.jsonl 重建完整 SubagentRecord 数据。
|
|
282
|
+
*
|
|
283
|
+
* ╔══════════════════════════════════════════════════════════════════╗
|
|
284
|
+
* ║ 1. readFileSync(sessionFile) + 逐行 JSON.parse(跳 header) ║
|
|
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 懒加载)。
|
|
305
|
+
*/
|
|
306
|
+
export function reconstructFromFile(sessionFile: string): ReconstructedRecord | undefined {
|
|
307
|
+
// 读文件 + 逐行 JSON.parse(session.jsonl = newline-delimited JSON)。
|
|
308
|
+
// 第 1 行是 session header(跳过);后续每行一个 entry。
|
|
309
|
+
// 损坏行静默跳过(与 SDK parseSessionEntries 行为一致)。
|
|
310
|
+
let raw: string;
|
|
311
|
+
try {
|
|
312
|
+
raw = fs.readFileSync(sessionFile, "utf-8");
|
|
313
|
+
} catch {
|
|
314
|
+
return undefined; // 文件缺失 → 降级。
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const entries: JsonlEntry[] = [];
|
|
318
|
+
const lines = raw.split("\n");
|
|
319
|
+
for (let i = 0; i < lines.length; i++) {
|
|
320
|
+
const line = lines[i]?.trim();
|
|
321
|
+
if (!line) continue;
|
|
322
|
+
if (i === 0) {
|
|
323
|
+
// 第 1 行是 session header(type:"session"),跳过。
|
|
324
|
+
try {
|
|
325
|
+
const head = JSON.parse(line) as JsonlEntry;
|
|
326
|
+
if (head.type === "session") continue;
|
|
327
|
+
} catch (_e) {
|
|
328
|
+
void _e; // 首行损坏 → 跳过,继续解析后续。
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const parsed = JSON.parse(line) as JsonlEntry;
|
|
334
|
+
if (parsed && typeof parsed.type === "string") {
|
|
335
|
+
entries.push(parsed);
|
|
336
|
+
}
|
|
337
|
+
} catch (_e) {
|
|
338
|
+
void _e; // 损坏行跳过(不阻断后续行)。
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (entries.length === 0) return undefined;
|
|
343
|
+
|
|
344
|
+
// ── 扫身份 custom entry(必须存在,否则无法构造 record)──
|
|
345
|
+
let identity: SubagentIdentityData | undefined;
|
|
346
|
+
let model = "";
|
|
347
|
+
let thinkingLevel: string | undefined;
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
if (entry.type === "custom" && entry.customType === IDENTITY_CUSTOM_TYPE) {
|
|
350
|
+
if (isIdentityData(entry.data)) identity = entry.data;
|
|
351
|
+
} else if (entry.type === "model_change") {
|
|
352
|
+
// model_change: {provider, modelId} → "provider/modelId"(与 record.model 同形)。
|
|
353
|
+
if (typeof entry.provider === "string" && typeof entry.modelId === "string") {
|
|
354
|
+
model = `${entry.provider}/${entry.modelId}`;
|
|
355
|
+
}
|
|
356
|
+
} else if (entry.type === "thinking_level_change") {
|
|
357
|
+
if (typeof entry.thinkingLevel === "string") thinkingLevel = entry.thinkingLevel;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!identity) return undefined; // 缺身份 → 无法构造 record。
|
|
361
|
+
|
|
362
|
+
// ── 重建 turns[] ──
|
|
363
|
+
const turns: Turn[] = [];
|
|
364
|
+
const pending: PendingToolCall[] = [];
|
|
365
|
+
let lastError: string | undefined;
|
|
366
|
+
let totalTokens = 0;
|
|
367
|
+
/** 最后一条 entry 的时间戳(ms),推导 endedAt(避免重建 record 耗时随墙钟无限增长)。 */
|
|
368
|
+
let lastEntryTsMs: number | undefined;
|
|
369
|
+
|
|
370
|
+
for (const entry of entries) {
|
|
371
|
+
// entry 级别 timestamp(ISO)→ ms。优先用 entry 级别(每条都有),message.timestamp 作兼底。
|
|
372
|
+
if (typeof entry.timestamp === "string") {
|
|
373
|
+
const ms = Date.parse(entry.timestamp);
|
|
374
|
+
if (!Number.isNaN(ms)) lastEntryTsMs = ms;
|
|
375
|
+
}
|
|
376
|
+
if (entry.type !== "message") continue;
|
|
377
|
+
const msg = entry.message;
|
|
378
|
+
if (!msg) continue;
|
|
379
|
+
if (typeof msg.timestamp === "number") {
|
|
380
|
+
lastEntryTsMs = msg.timestamp;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (msg.role === "assistant") {
|
|
384
|
+
const turn = emptyTurn();
|
|
385
|
+
turns.push(turn);
|
|
386
|
+
|
|
387
|
+
if (!Array.isArray(msg.content)) continue;
|
|
388
|
+
for (const block of msg.content) {
|
|
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
|
+
}
|
|
419
|
+
} else if (msg.role === "toolResult") {
|
|
420
|
+
const idx = pending.findIndex((p) => p.toolCallId === msg.toolCallId);
|
|
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)→ 丢弃。
|
|
435
|
+
}
|
|
436
|
+
// role:"user" → 跳过(task 来自 identity custom entry)。
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (turns.length === 0) return undefined; // 无 assistant message → 空壳,降级。
|
|
440
|
+
|
|
441
|
+
// 闭合所有 turn(重建的是历史,全是已完成 turn)。
|
|
442
|
+
for (const turn of turns) {
|
|
443
|
+
turn.closed = true;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const turnCount = turns.length;
|
|
447
|
+
const resultText = turns
|
|
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
|
+
|
|
455
|
+
// 终态 status:最后一条 assistant message 的 stopReason 推导(与 finalizeRecord 的判定一致)。
|
|
456
|
+
// SP-1:done/failed 合并为 closed,cancelled 由 tombstone override(record-store 层),本函数不感知。
|
|
457
|
+
const status: ExecutionStatus = "closed";
|
|
458
|
+
// closedReason 统 gc(通用完成/失败)。error/aborted 的区分由 error 字段保留。
|
|
459
|
+
const closedReason: import("./types.ts").ClosedReason = "gc";
|
|
460
|
+
|
|
461
|
+
// rootSessionId 归一化:新文件读 rootSessionId,旧文件 fallback parentSessionId。
|
|
462
|
+
const rootSessionId = identity.rootSessionId ?? identity.parentSessionId;
|
|
463
|
+
// slug 兜底:旧 identity entry 无 slug 字段 → 空串(与 SubagentRecord.slug: string 契约一致)。
|
|
464
|
+
const slug = identity.slug ?? "";
|
|
465
|
+
return {
|
|
466
|
+
...identity,
|
|
467
|
+
slug,
|
|
468
|
+
rootSessionId,
|
|
469
|
+
parentRecordId: identity.parentRecordId,
|
|
470
|
+
depth: identity.depth ?? 0,
|
|
471
|
+
forkDepth: identity.forkDepth,
|
|
472
|
+
sessionFile,
|
|
473
|
+
status,
|
|
474
|
+
closedReason,
|
|
475
|
+
turns,
|
|
476
|
+
turnCount,
|
|
477
|
+
totalTokens,
|
|
478
|
+
lastError,
|
|
479
|
+
model,
|
|
480
|
+
thinkingLevel,
|
|
481
|
+
endedAt: lastEntryTsMs,
|
|
482
|
+
result: resultText.length > 0 ? resultText : undefined,
|
|
483
|
+
error: lastError,
|
|
484
|
+
eventLog,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ============================================================
|
|
489
|
+
// 轻量头部读取(列表扫描用)
|
|
490
|
+
// ============================================================
|
|
491
|
+
|
|
492
|
+
/** 头部读取字节数。首轮会话 identity 在第 2-3 行;但续聊(resume)场景 session_start
|
|
493
|
+
* 再次触发,identity append 到文件尾部附近(实测真实目录 1186/1744 文件的 identity
|
|
494
|
+
* 不在头 64KB)。头部 miss 时由 RecordStore.scanFile 调 readIdentityAnywhere 全文 fallback。 */
|
|
495
|
+
export const IDENTITY_HEAD_BYTES = 65536;
|
|
496
|
+
|
|
497
|
+
/** 轻量重建产出:仅身份字段 + 头部可见的 model/thinkingLevel(详情字段一律缺省)。 */
|
|
498
|
+
export interface IdentityHeaderRecon {
|
|
499
|
+
id: string;
|
|
500
|
+
agent: string;
|
|
501
|
+
mode: ExecutionMode;
|
|
502
|
+
task: string;
|
|
503
|
+
slug: string;
|
|
504
|
+
startedAt: number;
|
|
505
|
+
rootSessionId: string | undefined;
|
|
506
|
+
parentRecordId: string | undefined;
|
|
507
|
+
depth: number;
|
|
508
|
+
forkDepth: number | undefined;
|
|
509
|
+
chatMode?: boolean;
|
|
510
|
+
/** [review round2] worktree 隔离标志(见 SubagentIdentityData.worktree)。 */
|
|
511
|
+
worktree?: boolean;
|
|
512
|
+
model: string;
|
|
513
|
+
thinkingLevel: string | undefined;
|
|
514
|
+
sessionFile: string;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* 只读文件头部,解析 identity custom entry(+途经的 model/thinking_level change)。
|
|
519
|
+
*
|
|
520
|
+
* 列表扫描专用:单次 readSync 读头部 IDENTITY_HEAD_BYTES 字节,逐行解析到 identity
|
|
521
|
+
* 即停。不解析 message entries(turns/eventLog/result 等重数据由
|
|
522
|
+
* RecordStore.getFullRecord 懒加载 reconstructFromFile 补齐)。
|
|
523
|
+
*
|
|
524
|
+
* 与 reconstructFromFile 的行为差异(有意):本函数只要头部有 identity 就返回,
|
|
525
|
+
* 不要求存在 assistant message——pi 延迟写入策略下无 assistant 的文件几乎不存在,
|
|
526
|
+
* 若存在(崩溃前 flush)也按分支 4 running 呈现(v4 B-1 可续聊语义),
|
|
527
|
+
* 而非静默不可见。
|
|
528
|
+
*
|
|
529
|
+
* 返回 undefined:文件缺失/读失败/头部无 identity。不抛。
|
|
530
|
+
* 头部无 identity ≠ 文件无 identity(续聊场景 identity 在尾部附近)——需要覆盖全文件
|
|
531
|
+
* 时用 readIdentityAnywhere。
|
|
532
|
+
*/
|
|
533
|
+
export function readIdentityHeader(sessionFile: string): IdentityHeaderRecon | undefined {
|
|
534
|
+
let text: string;
|
|
535
|
+
try {
|
|
536
|
+
const fd = fs.openSync(sessionFile, "r");
|
|
537
|
+
try {
|
|
538
|
+
const buf = Buffer.alloc(IDENTITY_HEAD_BYTES);
|
|
539
|
+
let total = 0;
|
|
540
|
+
while (total < buf.length) {
|
|
541
|
+
const n = fs.readSync(fd, buf, total, buf.length - total, total);
|
|
542
|
+
if (n <= 0) break;
|
|
543
|
+
total += n;
|
|
544
|
+
}
|
|
545
|
+
text = buf.toString("utf-8", 0, total);
|
|
546
|
+
} finally {
|
|
547
|
+
fs.closeSync(fd);
|
|
548
|
+
}
|
|
549
|
+
} catch {
|
|
550
|
+
return undefined; // 文件缺失/不可读 → 降级跳过
|
|
551
|
+
}
|
|
552
|
+
return parseIdentityFromText(text, sessionFile);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* 全文件找 identity:头部/尾部都 miss 后的最后 fallback(真实数据仅 ~0.2% 走到)。
|
|
557
|
+
*
|
|
558
|
+
* 全量 readFileSync + indexOf 预筛(只有含特征串的行才 JSON.parse),从后往前找
|
|
559
|
+
* (续聊轮次的 identity 靠尾部,最后一次的 identity 最新鲜)。读全文是成本主体
|
|
560
|
+
* (预筛/parse 可忽略)——调用方(RecordStore)应缓存结果避免重复读。
|
|
561
|
+
*/
|
|
562
|
+
export function readIdentityAnywhere(sessionFile: string): IdentityHeaderRecon | undefined {
|
|
563
|
+
let text: string;
|
|
564
|
+
try {
|
|
565
|
+
text = fs.readFileSync(sessionFile, "utf-8");
|
|
566
|
+
} catch {
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
const lines = text.split("\n");
|
|
570
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
571
|
+
const s = lines[i];
|
|
572
|
+
if (!s.includes(IDENTITY_CUSTOM_TYPE)) continue; // 预筛:identity 行必含特征串
|
|
573
|
+
const recon = parseIdentityFromText(s, sessionFile);
|
|
574
|
+
if (recon) return recon;
|
|
575
|
+
}
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* 只读文件尾部找 identity(续聊场景主力命中点:最后一轮 session_start 追加的
|
|
581
|
+
* identity 离文件尾近,实测真实目录 65% 文件 identity 在尾 64KB)。
|
|
582
|
+
*
|
|
583
|
+
* 尾部块首行可能残缺(从行中间开始)——JSON.parse 失败自然跳过,不影响后续行。
|
|
584
|
+
* model/thinking_level 在尾部块中拿不到 identity 之前的途经 entry,返回空/undefined
|
|
585
|
+
* (best-effort,详情场景由全量重建补齐)。
|
|
586
|
+
*/
|
|
587
|
+
export function readIdentityTail(sessionFile: string): IdentityHeaderRecon | undefined {
|
|
588
|
+
let text: string;
|
|
589
|
+
try {
|
|
590
|
+
const { size } = fs.statSync(sessionFile);
|
|
591
|
+
const start = Math.max(0, size - IDENTITY_HEAD_BYTES);
|
|
592
|
+
const fd = fs.openSync(sessionFile, "r");
|
|
593
|
+
try {
|
|
594
|
+
const buf = Buffer.alloc(size - start);
|
|
595
|
+
let total = 0;
|
|
596
|
+
while (total < buf.length) {
|
|
597
|
+
const n = fs.readSync(fd, buf, total, buf.length - total, start + total);
|
|
598
|
+
if (n <= 0) break;
|
|
599
|
+
total += n;
|
|
600
|
+
}
|
|
601
|
+
text = buf.toString("utf-8", 0, total);
|
|
602
|
+
} finally {
|
|
603
|
+
fs.closeSync(fd);
|
|
604
|
+
}
|
|
605
|
+
} catch {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
const lines = text.split("\n");
|
|
609
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
610
|
+
const s = lines[i];
|
|
611
|
+
if (!s.includes(IDENTITY_CUSTOM_TYPE)) continue;
|
|
612
|
+
const recon = parseIdentityFromText(s, sessionFile);
|
|
613
|
+
if (recon) return recon;
|
|
614
|
+
}
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** 从文本片段解析 identity(头部/全文两入口共享)。解析到 identity 即停(返回);
|
|
619
|
+
* 找不到返回 undefined。model/thinking_level 仅在 identity 之前的途经 entry 中
|
|
620
|
+
* best-effort 提取,详情场景由全量重建补齐。 */
|
|
621
|
+
function parseIdentityFromText(text: string, sessionFile: string): IdentityHeaderRecon | undefined {
|
|
622
|
+
let identity: SubagentIdentityData | undefined;
|
|
623
|
+
let model = "";
|
|
624
|
+
let thinkingLevel: string | undefined;
|
|
625
|
+
for (const line of text.split("\n")) {
|
|
626
|
+
const s = line.trim();
|
|
627
|
+
if (!s) continue;
|
|
628
|
+
// 预筛([S-4] 同款值匹配):只有含三种目标 entry 值串的行才值得 JSON.parse——
|
|
629
|
+
// head 64KB 内大 toolResult 行(数十 KB/行)占多数,全量 parse 是冷扫描 CPU 大头。
|
|
630
|
+
// 值字符串在任意序列化格式下连续出现,不耦合 pi 的空格习惯。
|
|
631
|
+
if (
|
|
632
|
+
!s.includes(IDENTITY_CUSTOM_TYPE) &&
|
|
633
|
+
!s.includes('"model_change"') &&
|
|
634
|
+
!s.includes('"thinking_level_change"')
|
|
635
|
+
) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
let entry: JsonlEntry;
|
|
639
|
+
try {
|
|
640
|
+
entry = JSON.parse(s) as JsonlEntry;
|
|
641
|
+
} catch {
|
|
642
|
+
continue; // 损坏行/截断行(头部读取边界)跳过
|
|
643
|
+
}
|
|
644
|
+
if (entry.type === "custom" && entry.customType === IDENTITY_CUSTOM_TYPE) {
|
|
645
|
+
if (isIdentityData(entry.data)) {
|
|
646
|
+
identity = entry.data;
|
|
647
|
+
break; // 找到即停(后续行不再解析)
|
|
648
|
+
}
|
|
649
|
+
} else if (entry.type === "model_change") {
|
|
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;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (!identity) return undefined;
|
|
658
|
+
|
|
659
|
+
// 归一化与全量 recon 同源:rootSessionId fallback 旧字段、slug 兜底空串。
|
|
660
|
+
const rootSessionId = identity.rootSessionId ?? identity.parentSessionId;
|
|
661
|
+
return {
|
|
662
|
+
id: identity.id,
|
|
663
|
+
agent: identity.agent,
|
|
664
|
+
mode: identity.mode,
|
|
665
|
+
task: identity.task,
|
|
666
|
+
slug: identity.slug ?? "",
|
|
667
|
+
startedAt: identity.startedAt,
|
|
668
|
+
rootSessionId,
|
|
669
|
+
parentRecordId: identity.parentRecordId,
|
|
670
|
+
depth: identity.depth ?? 0,
|
|
671
|
+
forkDepth: identity.forkDepth,
|
|
672
|
+
chatMode: identity.chatMode,
|
|
673
|
+
worktree: identity.worktree,
|
|
674
|
+
model,
|
|
675
|
+
thinkingLevel,
|
|
676
|
+
sessionFile,
|
|
677
|
+
};
|
|
678
|
+
}
|