@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
package/src/execution/idle-gc.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
* 启动幂等由调用方(service.startGcTimer)守卫;返回 stop 函数供 dispose 清理。
|
|
5
5
|
*/
|
|
6
6
|
import { getLogger } from "../core/logger.ts";
|
|
7
|
+
import { getEngineDataDir } from "./engine/common/data-dir.ts";
|
|
8
|
+
import { releasePoolRef } from "./engine/common/pool-manager.ts";
|
|
7
9
|
import { bestEffort } from "./best-effort.ts";
|
|
8
10
|
import { isResumable } from "./lifecycle-predicates.ts";
|
|
9
11
|
import type { RecordStore } from "./record-store.ts";
|
|
12
|
+
import type { ExecutionRecord } from "./types.ts";
|
|
10
13
|
|
|
11
14
|
const logger = getLogger("subagents");
|
|
12
15
|
|
|
@@ -24,6 +27,15 @@ const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
|
24
27
|
* 启动 idle record GC 定时器,返回 stop 函数(清理 interval;幂等)。
|
|
25
28
|
* 每个扫描周期:对 store 内全部 active record 中 resumable 且带 idleSince 的,
|
|
26
29
|
* 超过 IDLE_TTL_MS 的归档(archive 单条失败不阻断其余,bestEffort 留痕)。
|
|
30
|
+
*
|
|
31
|
+
* [D8 池引用计数接线] 归档时同步释放该 record 的引擎池引用(releasePoolRef:
|
|
32
|
+
* journal 跟随 record 删除 + refs 移除 + 归零删池内原生状态)。锚点选在 30 天 TTL
|
|
33
|
+
* 而非 dispose/archive 类时机,依据:idle record 30 天后引擎侧不会再有活动(archive
|
|
34
|
+
* 后 message 续聊走 fork-from/新 start,不触原引擎),且与 pi 域 session 文件 30 天
|
|
35
|
+
* TTL(session-file-gc 同期删①级读源)形成两引擎对称衰减;disposeAllRecords/close
|
|
36
|
+
* 类时机 record 数据仍保留(可 resurrect / GUI 历史重建可见),journal(②级数据源)
|
|
37
|
+
* 不能删——那是 record 主数据死亡(主 session 文件被 pi 侧删除,core 无触发点)才有
|
|
38
|
+
* 的处置,由 cleanupExpiredPoolRefs 的 TTL 兜底覆盖。
|
|
27
39
|
*/
|
|
28
40
|
export function startIdleGc(store: RecordStore): () => void {
|
|
29
41
|
const timer = setInterval(() => {
|
|
@@ -38,6 +50,7 @@ export function startIdleGc(store: RecordStore): () => void {
|
|
|
38
50
|
} catch (err) {
|
|
39
51
|
bestEffort(err, `GC archive record ${record.id}`);
|
|
40
52
|
}
|
|
53
|
+
releaseEnginePoolRef(record);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
43
56
|
}
|
|
@@ -45,3 +58,18 @@ export function startIdleGc(store: RecordStore): () => void {
|
|
|
45
58
|
timer.unref?.();
|
|
46
59
|
return () => clearInterval(timer);
|
|
47
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 释放 record 的引擎池引用(best-effort)。无 engineHandle(存量 record / 非引擎
|
|
64
|
+
* 路径)时 no-op;engine 缺省投影 'pi'(与读侧存量 entry 零迁移口径一致)。
|
|
65
|
+
*/
|
|
66
|
+
function releaseEnginePoolRef(record: ExecutionRecord): void {
|
|
67
|
+
const poolKey = record.engineHandle?.poolKey;
|
|
68
|
+
if (poolKey === undefined) return;
|
|
69
|
+
const engineId = record.engine ?? "pi";
|
|
70
|
+
try {
|
|
71
|
+
releasePoolRef(getEngineDataDir(), engineId, poolKey, record.id);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
bestEffort(err, `GC release pool ref for record ${record.id}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// 故 isIdle 在这两个检查点恒为 true——notify 守卫与 early-return 判据可靠。
|
|
21
21
|
|
|
22
22
|
import { hasIdleTimer } from "./lifecycle-manager.ts";
|
|
23
|
-
import { getChildByRecord } from "./session-runner.ts";
|
|
23
|
+
import { getChildByRecord } from "./engine/engines/pi/session-runner.ts";
|
|
24
24
|
import type { ExecutionRecord } from "./types.ts";
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// [D4-① 通知簇职责轴] background 完成通知的 host 面——原 SubagentService 私有通知簇
|
|
2
|
+
// (notifyComplete / notifyClosed / piAdapter / toNotifyRecord / emitPendingRegister /
|
|
3
|
+
// emitPendingUnregister + notifier 实例持有)整体搬移至此(行为逐字节等价:搬移 +
|
|
4
|
+
// 依赖注入,不重写逻辑)。变化轴:改通知文案 / dedup 身份 / 放行守卫 / pending 注册
|
|
5
|
+
// 注销协议,只改本文件;Service 只保留编排与依赖注入(getPi / listRunning / isIdle)。
|
|
6
|
+
|
|
7
|
+
import { displayAgentName } from "../shared/agent-ref.ts";
|
|
8
|
+
import { snapshot } from "./execution-record.ts";
|
|
9
|
+
import { hasIdleTimer } from "./lifecycle-manager.ts";
|
|
10
|
+
import { hasLiveProcessHandle, isIdle, isResumable } from "./lifecycle-predicates.ts";
|
|
11
|
+
import type { BgNotifier, NotifierHost } from "./notifier.ts";
|
|
12
|
+
import { createNotifier } from "./notifier.ts";
|
|
13
|
+
import type { BgNotifyRecord } from "./notifier.ts";
|
|
14
|
+
import type { ExecutionRecord, RecordSnapshot } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
/** Pi ExtensionAPI 的最小接口(duck-typed)——原定义于 subagent-service.ts,随通知簇
|
|
17
|
+
* (piAdapter / emitPending* 的依赖)搬移至此并导出(Service 的 session 注入参数仍引用)。
|
|
18
|
+
* subagent-service 直接调 pi.sendMessage 发 background 完成通知(BgNotifier 滑动窗口合并),
|
|
19
|
+
* 不委托 pending-notifications EventBus 中继——后者只管 registry 不参与通知发送。 */
|
|
20
|
+
export interface PiLike {
|
|
21
|
+
appendEntry(customType: string, data?: unknown): void;
|
|
22
|
+
events: { emit(channel: string, data: unknown): void };
|
|
23
|
+
sendMessage(
|
|
24
|
+
message: { customType: string; content: string; display: boolean; details?: unknown },
|
|
25
|
+
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }, // g4-allow: 类型注解——PiLike 接口形状(pi.sendMessage 签面子集),非投递调用
|
|
26
|
+
): void;
|
|
27
|
+
/** 订阅 pi 事件(D8:notifier 的 settled 边沿订阅用 'agent_settled')。
|
|
28
|
+
* pi 0.84.1 的 on 返回 void 且无 off——退订语义由调用侧 disposed 标志包装兑现。
|
|
29
|
+
* 可选:旧测试 mock pi 可能未实现 on,缺省时 notifier 退化为内核退避路径。 */
|
|
30
|
+
on?(event: "agent_settled", handler: () => void): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** NotifyHost 的依赖注入(Service 侧供给,全部惰性求值——pi/session 级状态运行时可变)。 */
|
|
34
|
+
export interface NotifyHostDeps {
|
|
35
|
+
/** 当前 pi session handle(session_start 注入 / shutdown 置 null)。 */
|
|
36
|
+
getPi: () => PiLike | null;
|
|
37
|
+
/** store 的 running record 快照(piAdapter.hasRunningBackground 用)。 */
|
|
38
|
+
listRunning: () => RecordSnapshot[];
|
|
39
|
+
/** 主 agent isIdle 查询(ctx.isIdle,initSession 注入;未注入时 undefined)。 */
|
|
40
|
+
getIsIdle: () => (() => boolean) | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 通知簇 host 面(Service 经此消费;notifier 实例封装在内部不外露)。 */
|
|
44
|
+
export interface NotifyHost {
|
|
45
|
+
/** background 完成回注(原 Service.notifyComplete)。 */
|
|
46
|
+
notifyComplete(record: ExecutionRecord): void;
|
|
47
|
+
/** [C-1] chatMode close 终态通知(原 Service.notifyClosed)。 */
|
|
48
|
+
notifyClosed(record: ExecutionRecord, emptyBody?: boolean): void;
|
|
49
|
+
/** pending-notifications 注册(原模块函数 emitPendingRegister,pi 经 deps 取)。 */
|
|
50
|
+
emitPendingRegister(id: string, name?: string): void;
|
|
51
|
+
/** pending-notifications 注销(原模块函数 emitPendingUnregister)。 */
|
|
52
|
+
emitPendingUnregister(id: string, reason: string): void;
|
|
53
|
+
/** dispose 的逆操作(initSession 复活,原 notifier.revive 委托)。 */
|
|
54
|
+
revive(): void;
|
|
55
|
+
/** dispose 前冲刷待发通知(原 notifier.flushPendingNotifications 委托)。 */
|
|
56
|
+
flushPendingNotifications(): void;
|
|
57
|
+
/** 丢弃 pending 通知(原 notifier.dispose 委托)。 */
|
|
58
|
+
dispose(): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** pending-notifications 注册/注销 helper(避免重复代码)。
|
|
62
|
+
* name 是 GUI pending 通知的显示名——取 basename 短名(displayAgentName),
|
|
63
|
+
* 完整路径仍走 record.agent(env 注入 / 持久化)。 */
|
|
64
|
+
function emitPendingRegister(pi: PiLike | null, id: string, name?: string): void {
|
|
65
|
+
pi?.events.emit("pending:register", {
|
|
66
|
+
id,
|
|
67
|
+
type: "subagent",
|
|
68
|
+
name: name ? displayAgentName(name) : id,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function emitPendingUnregister(
|
|
73
|
+
pi: PiLike | null,
|
|
74
|
+
id: string,
|
|
75
|
+
reason: string,
|
|
76
|
+
): void {
|
|
77
|
+
pi?.events.emit("pending:unregister", {
|
|
78
|
+
id,
|
|
79
|
+
reason,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 通知簇工厂:notifier 实例随本函数创建并封装(原 Service 构造器
|
|
84
|
+
* `createNotifier(this.piAdapter())` 的等价形态)。 */
|
|
85
|
+
export function createNotifyHost(deps: NotifyHostDeps): NotifyHost {
|
|
86
|
+
/** notifier 的 NotifierHost 适配器(绑定到 pi.sendMessage + store 查询)。 */
|
|
87
|
+
const piAdapter = (): NotifierHost => {
|
|
88
|
+
return {
|
|
89
|
+
sendMessage: (message, options) => {
|
|
90
|
+
deps.getPi()?.sendMessage(message, options);
|
|
91
|
+
},
|
|
92
|
+
hasRunningBackground: () => {
|
|
93
|
+
// [M3] 「在跑的 background 工作」= 有活进程且非等待续聊(idle timer armed)。
|
|
94
|
+
// v4 B-1 把旧 idle 折入 running 且 record 留 store:轮次完成的 chatMode record
|
|
95
|
+
// (timer armed、无在跑轮)与 one-shot 完成后等待 message 升级的 record 都不再计入。
|
|
96
|
+
// 旧判定 `mode === "background"` 对这两类恒 true → 轮次完成通知恒挂 60s 合并窗口
|
|
97
|
+
// (notifier MERGE_WINDOW_MS),主 agent 的续聊回复固定延迟 60s 送达,持续对话(G1)失效。
|
|
98
|
+
return deps.listRunning().some(
|
|
99
|
+
(r) => r.mode === "background" && hasLiveProcessHandle(r.id) && !hasIdleTimer(r.id),
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
isIdle: () => deps.getIsIdle()?.() ?? true,
|
|
103
|
+
// [must-fix #4 / D8] settled 边沿订阅,与 isIdle 同源(session_start 注入的 pi)。
|
|
104
|
+
// 只注入原生订阅能力;disposed 标志包装(退订语义)在 notifier 的 port 装配完成。
|
|
105
|
+
onAgentSettled: (handler) => { deps.getPi()?.on?.("agent_settled", handler); },
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const notifier: BgNotifier = createNotifier(piAdapter());
|
|
110
|
+
|
|
111
|
+
/** record → BgNotifyRecord(notifier.notify 入参映射,内部不外露)。
|
|
112
|
+
* v4 B-1:守卫放行 closed(终态,含 cancelled)、isIdle(对话模式轮次完成,notify 主 agent G1)
|
|
113
|
+
* 或 isResumable(running + 无活进程——SP-5 one-shot 成功完成 / MF-6 失败轮回退)。
|
|
114
|
+
* 正在执行(running + 活进程 + 非 timer-armed)返回 undefined(调用方 notifyComplete 跳过)。
|
|
115
|
+
* SP-1: closed 统一终态,closedReason 由 BgNotifyRecord 携带。 */
|
|
116
|
+
const toNotifyRecord = (record: ExecutionRecord): BgNotifyRecord | undefined => {
|
|
117
|
+
const snap = snapshot(record);
|
|
118
|
+
const s = snap.status;
|
|
119
|
+
// [N1] isResumable 放行:SP-5 one-shot 成功完成后 finalizeRoundToIdle 把 record 回退
|
|
120
|
+
// running-resumable——进程已死且永不 arm idle timer(armIdleTimer 只在 agent_settled 的
|
|
121
|
+
// chatMode 分支调用),旧守卫(closed / isIdle only)对其恒拒绝 → 完成通知静默丢失,
|
|
122
|
+
// 而 one-shot 失败走 finalizeRecord 保持 closed 反而通知——与 tool 契约「runs once,
|
|
123
|
+
// notifies on completion」完全倒置。isResumable = running + 无活进程,恰为该完成态;
|
|
124
|
+
// 在跑轮的 record 有活进程,不会被误放行。
|
|
125
|
+
if (s !== "closed" && !isIdle(record) && !isResumable(record)) return undefined;
|
|
126
|
+
// closed → BgNotifyRecord.closed(cancelled 区分靠 closedReason);chatMode 的 isIdle/
|
|
127
|
+
// isResumable(轮次完成或 MF-6 失败轮回退,对话可续)→ running(轮次完成)。
|
|
128
|
+
// isResumable 且非 chatMode(SP-5 one-shot 成功完成)→ closed:对主 agent 的语义是
|
|
129
|
+
// completed(非对话轮次),且只有 closed 分支文案携带 worktree patchFile 的 git apply
|
|
130
|
+
// 提示——one-shot worktree 模式的改动回收依赖该提示(running 分支文案不含 patchFile)。
|
|
131
|
+
const notifyStatus: BgNotifyRecord["status"] =
|
|
132
|
+
s === "closed" || !record.chatMode ? "closed" : "running";
|
|
133
|
+
return {
|
|
134
|
+
id: snap.id,
|
|
135
|
+
status: notifyStatus,
|
|
136
|
+
agent: snap.agent,
|
|
137
|
+
model: snap.model,
|
|
138
|
+
result: snap.result,
|
|
139
|
+
error: snap.error,
|
|
140
|
+
startedAt: snap.startedAt,
|
|
141
|
+
endedAt: snap.endedAt,
|
|
142
|
+
patchFile: record.patchFile,
|
|
143
|
+
// round 透传给 notifier 的 dedup key(对话模式按轮次去重,G1 决策 9)。
|
|
144
|
+
round: record.round,
|
|
145
|
+
// SP-1: closedReason 透传给 notifier(L2 原因,供通知文案按需展示)。
|
|
146
|
+
closedReason: record.closedReason,
|
|
147
|
+
// [wave2] chatMode 条件透传 sessionFile:通知末尾追加 Full transcript 指针行
|
|
148
|
+
//(增量语义的全文恢复通道,见 notifier.buildLlmContent)。one-shot(chatMode
|
|
149
|
+
// falsy)不透传——通知输出逐字节不变(G4),该条件由 message-close 测试的
|
|
150
|
+
// 必选用例锁死(漏加条件时 notifier 单测不红——notifier 层只见最终字段)。
|
|
151
|
+
sessionFile: record.chatMode ? record.sessionFile : undefined,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
/** background 完成回注(record → BgNotifyRecord 映射 + notifier.notify)。
|
|
157
|
+
* 正在执行(running + 活进程 + 非 timer-armed)静默跳过——notify 只对 closed(终态)、
|
|
158
|
+
* isIdle(chatMode 轮次完成)或 isResumable(SP-5 one-shot 成功完成 / MF-6 失败轮回退)有意义。
|
|
159
|
+
* SP-1: closed 统一终态(done/failed/crashed 合并),closedReason 携带 L2 原因。 */
|
|
160
|
+
notifyComplete(record: ExecutionRecord): void {
|
|
161
|
+
const notify = toNotifyRecord(record);
|
|
162
|
+
if (notify) notifier.notify(notify);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/** [C-1] chatMode close 终态通知(设计 D2:正文空/本轮增量 + sessionFile 指针行)。
|
|
166
|
+
*
|
|
167
|
+
* 与 notifyComplete 的差异只在 dedup 身份与轮次统计:终态通知必须与最后一轮的轮次通知
|
|
168
|
+
* 区分(轮次通知 key=`id:round`),否则同 key 被 60s dedup 吞——close 后父 agent 永远
|
|
169
|
+
* 收不到带指针行的终态通知(审查 C-1)。故 round 置 undefined(key 回退为裸 id),
|
|
170
|
+
* 轮数改经 totalRounds 进文案 "completed after N rounds."(C-2)。
|
|
171
|
+
*
|
|
172
|
+
* 仅 chatMode close 语义调用(closeChatIdle / closeAfterRoundSettled 终态化成功后)。
|
|
173
|
+
* one-shot 显式拒绝(G4:one-shot close 路径现状无终态通知,字节不变);cancel 走
|
|
174
|
+
* cancelBackground 自己的 notifyComplete,不经本方法。幂等性:两条 close 路径均由
|
|
175
|
+
* closeSubagent 的 status 分流守卫(closed 后幂等 no-op)/ CAS 抢锁保证只执行一次,
|
|
176
|
+
* 本方法自身不重复发送;迟到的轮次收尾 .then 通知与轮次通知同 key=`id:round`,
|
|
177
|
+
* 60s 窗内仍被吞,不构成第三条。 */
|
|
178
|
+
/** @param emptyBody true = 终态通知正文置空串(D2 路径②)。W16 P-1 修复后
|
|
179
|
+
* closeChatIdle 的 doneResult.text 改用 record.result 保真(close 终态
|
|
180
|
+
* subagent-record entry 的 result 不抹空轮终真实值),「正文空」不再由合成空
|
|
181
|
+
* text 的副作用承载,改为显式参数——持久化 result 与通知正文两个关注点解耦。 */
|
|
182
|
+
notifyClosed(record: ExecutionRecord, emptyBody = false): void {
|
|
183
|
+
if (!record.chatMode) return;
|
|
184
|
+
const notify = toNotifyRecord(record);
|
|
185
|
+
if (!notify) return;
|
|
186
|
+
notify.round = undefined;
|
|
187
|
+
if (emptyBody) notify.result = "";
|
|
188
|
+
if (record.round != null) notify.totalRounds = record.round;
|
|
189
|
+
notifier.notify(notify);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
emitPendingRegister(id: string, name?: string): void {
|
|
193
|
+
emitPendingRegister(deps.getPi(), id, name);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
emitPendingUnregister(id: string, reason: string): void {
|
|
197
|
+
emitPendingUnregister(deps.getPi(), id, reason);
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
revive(): void {
|
|
201
|
+
notifier.revive();
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
flushPendingNotifications(): void {
|
|
205
|
+
notifier.flushPendingNotifications();
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
dispose(): void {
|
|
209
|
+
notifier.dispose();
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -159,6 +159,21 @@ interface NegativeFileEntry {
|
|
|
159
159
|
/** fileCache 值类型:正常条目或负缓存条目。 */
|
|
160
160
|
type FileCacheValue = FileCacheEntry | NegativeFileEntry;
|
|
161
161
|
|
|
162
|
+
/** scanFile 单文件本轮 stat 戳集合(jsonl + 3 sidecar)。 */
|
|
163
|
+
interface FileStamps {
|
|
164
|
+
jsonl: Stamp;
|
|
165
|
+
cancelled: Stamp | null;
|
|
166
|
+
finalized: Stamp | null;
|
|
167
|
+
alive: Stamp | null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** sidecar payload 读取结果(索引命中与探测重建两分支共享的读点)。 */
|
|
171
|
+
interface SidecarPayloads {
|
|
172
|
+
tomb: CancelledTombstone | undefined;
|
|
173
|
+
aliveData: AliveMarker | undefined;
|
|
174
|
+
finalReason: string | undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
162
177
|
/** 孤儿判定的末行读取初始窗口(常规 entry 远小于此,避免全文件读)。 */
|
|
163
178
|
// eslint-disable-next-line no-magic-numbers -- 64KB = 64 * 1024 bytes 字节换算常数
|
|
164
179
|
const LAST_LINE_WINDOW_BYTES = 64 * 1024;
|
|
@@ -335,6 +350,41 @@ function sameNullableStamp(a: Stamp | null, b: Stamp | null): boolean {
|
|
|
335
350
|
return sameStamp(a, b);
|
|
336
351
|
}
|
|
337
352
|
|
|
353
|
+
/** 缓存条目与本轮 stat 戳全同(jsonl + 3 sidecar,null 语义对齐)→ 零读取复用。 */
|
|
354
|
+
function isFreshCache(cached: FileCacheValue, stamps: FileStamps): boolean {
|
|
355
|
+
return (
|
|
356
|
+
sameStamp(cached.jsonl, stamps.jsonl) &&
|
|
357
|
+
sameNullableStamp(cached.cancelled, stamps.cancelled) &&
|
|
358
|
+
sameNullableStamp(cached.finalized, stamps.finalized) &&
|
|
359
|
+
sameNullableStamp(cached.alive, stamps.alive)
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* identity 三级定位——头部 64KB(首轮会话,~34%)→ 尾部 64KB(续聊场景最后一轮
|
|
365
|
+
* session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
|
|
366
|
+
* size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
|
|
367
|
+
* 直接判负(head miss = 全文无 identity),省去同内容两连读。
|
|
368
|
+
*/
|
|
369
|
+
function detectIdentity(file: string, size: number): IdentityHeaderRecon | undefined {
|
|
370
|
+
return size <= IDENTITY_HEAD_BYTES
|
|
371
|
+
? readIdentityHeader(file)
|
|
372
|
+
: readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* sidecar payload 读取(读顺序:tombstone → alive marker → finalized reason)。
|
|
377
|
+
* tombstone/alive 是活态数据,调用方沿用每轮重读语义;finalized reason 静态数据
|
|
378
|
+
* 仅在 sidecar 存在时读一次(文件小,成本可忽略)。
|
|
379
|
+
*/
|
|
380
|
+
function readSidecarPayloads(file: string, stamps: FileStamps): SidecarPayloads {
|
|
381
|
+
return {
|
|
382
|
+
tomb: stamps.cancelled !== null ? readCancelledTombstone(file) : undefined,
|
|
383
|
+
aliveData: stamps.alive !== null ? readAliveMarker(file) : undefined,
|
|
384
|
+
finalReason: stamps.finalized !== null ? readFinalizedReason(file) : undefined,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
338
388
|
/** Pi ExtensionAPI 的最小子集(仅 collectRecords 跳过损坏 manifest 时上报用)。
|
|
339
389
|
* 解构为局部类型,避免与 subagent-service 的 PiLike 循环依赖。 */
|
|
340
390
|
export type RecordStorePi = {
|
|
@@ -865,18 +915,15 @@ export class RecordStore {
|
|
|
865
915
|
this.fileCache.delete(file);
|
|
866
916
|
return null;
|
|
867
917
|
}
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
918
|
+
const stamps: FileStamps = {
|
|
919
|
+
jsonl,
|
|
920
|
+
cancelled: statStamp(`${file}.cancelled`),
|
|
921
|
+
finalized: statStamp(`${file}.finalized`),
|
|
922
|
+
alive: statStamp(`${file}.alive`),
|
|
923
|
+
};
|
|
871
924
|
|
|
872
925
|
const cached = this.fileCache.get(file);
|
|
873
|
-
if (
|
|
874
|
-
cached !== undefined &&
|
|
875
|
-
sameStamp(cached.jsonl, jsonl) &&
|
|
876
|
-
sameNullableStamp(cached.cancelled, cancelled) &&
|
|
877
|
-
sameNullableStamp(cached.finalized, finalized) &&
|
|
878
|
-
sameNullableStamp(cached.alive, alive)
|
|
879
|
-
) {
|
|
926
|
+
if (cached !== undefined && isFreshCache(cached, stamps)) {
|
|
880
927
|
if (cached.negative) return null; // 负缓存命中:确认无 identity,零读取跳过
|
|
881
928
|
// pid 探活结果不落盘(进程死亡无 IO)——分支 3 的 running 项每扫重查,
|
|
882
929
|
// 保留原语义(旧实现每次 collectRecords 都重新 isProcessAlive)。
|
|
@@ -885,83 +932,54 @@ export class RecordStore {
|
|
|
885
932
|
}
|
|
886
933
|
|
|
887
934
|
// [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
|
|
888
|
-
// 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。
|
|
889
|
-
//
|
|
890
|
-
|
|
891
|
-
if (
|
|
892
|
-
const hit = this.indexEntries.get(path.basename(file));
|
|
893
|
-
if (hit !== undefined && hit.mtimeMs === jsonl.mtimeMs && hit.size === jsonl.size) {
|
|
894
|
-
if (hit.negative === true) {
|
|
895
|
-
// 负条目命中:「确认无 identity」跨实例持久,零探测跳过(与下方负缓存同款形态)。
|
|
896
|
-
this.fileCache.set(file, { negative: true, jsonl, cancelled, finalized, alive });
|
|
897
|
-
return null;
|
|
898
|
-
}
|
|
899
|
-
const tomb = cancelled !== null ? readCancelledTombstone(file) : undefined;
|
|
900
|
-
const aliveData = alive !== null ? readAliveMarker(file) : undefined;
|
|
901
|
-
const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
|
|
902
|
-
const entry: FileCacheEntry = {
|
|
903
|
-
light: RecordStore.buildRecord(
|
|
904
|
-
{ ...hit, forkDepth: undefined, sessionFile: file },
|
|
905
|
-
{ tomb, finalized: finalized !== null, finalizedReason: finalReason, alive: aliveData, jsonlMtimeMs: jsonl.mtimeMs, now },
|
|
906
|
-
),
|
|
907
|
-
full: undefined,
|
|
908
|
-
jsonl,
|
|
909
|
-
cancelled,
|
|
910
|
-
finalized,
|
|
911
|
-
alive,
|
|
912
|
-
tomb,
|
|
913
|
-
aliveData,
|
|
914
|
-
finalReason,
|
|
915
|
-
};
|
|
916
|
-
this.fileCache.set(file, entry);
|
|
917
|
-
this.idToFile.set(hit.id, file);
|
|
918
|
-
return entry;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
935
|
+
// 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。undefined = 未命中
|
|
936
|
+
// (落到下方探测),null = 负条目命中(零探测跳过)。
|
|
937
|
+
const fromIndex = this.buildEntryFromIndex(file, stamps, now);
|
|
938
|
+
if (fromIndex !== undefined) return fromIndex;
|
|
921
939
|
|
|
922
940
|
// [perf L-1] 索引 miss/戳不匹配落到原三级探测:本轮探测结果必须进索引(含负探测)。
|
|
923
941
|
// 覆盖两种形态:首扫(映像已装载但 miss/不匹配)与后续轮次(映像已释放,凡进重建分支必是戳变化)。
|
|
924
942
|
this.indexDirty = true;
|
|
925
943
|
|
|
926
|
-
|
|
927
|
-
// 最后一轮 session_start 追加,~65%)→ 全文 fallback(~0.2%)。均不解析 message entries。
|
|
928
|
-
// size ≤ 头部读取上限时 head 读到的即全文,tail/anywhere 只会重复读同一份内容——
|
|
929
|
-
// 直接判负(head miss = 全文无 identity),省去同内容两连读。
|
|
930
|
-
const header =
|
|
931
|
-
jsonl.size <= IDENTITY_HEAD_BYTES
|
|
932
|
-
? readIdentityHeader(file)
|
|
933
|
-
: readIdentityHeader(file) ?? readIdentityTail(file) ?? readIdentityAnywhere(file);
|
|
944
|
+
const header = detectIdentity(file, jsonl.size);
|
|
934
945
|
if (!header) {
|
|
935
946
|
// 负缓存:确认无 identity。后续扫描 stat 命中直接跳过;戳变化(文件补写)自动重试。
|
|
936
|
-
this.fileCache.set(file, { negative: true,
|
|
947
|
+
this.fileCache.set(file, { negative: true, ...stamps });
|
|
937
948
|
return null;
|
|
938
949
|
}
|
|
939
|
-
const
|
|
940
|
-
const
|
|
941
|
-
const finalReason = finalized !== null ? readFinalizedReason(file) : undefined;
|
|
942
|
-
const entry: FileCacheEntry = {
|
|
943
|
-
light: RecordStore.buildRecord(header, {
|
|
944
|
-
tomb,
|
|
945
|
-
finalized: finalized !== null,
|
|
946
|
-
finalizedReason: finalReason,
|
|
947
|
-
alive: aliveData,
|
|
948
|
-
jsonlMtimeMs: jsonl.mtimeMs,
|
|
949
|
-
now,
|
|
950
|
-
}),
|
|
951
|
-
full: undefined,
|
|
952
|
-
jsonl,
|
|
953
|
-
cancelled,
|
|
954
|
-
finalized,
|
|
955
|
-
alive,
|
|
956
|
-
tomb,
|
|
957
|
-
aliveData,
|
|
958
|
-
finalReason,
|
|
959
|
-
};
|
|
950
|
+
const payloads = readSidecarPayloads(file, stamps);
|
|
951
|
+
const entry = RecordStore.buildFileCacheEntry(header, file, stamps, payloads, now);
|
|
960
952
|
this.fileCache.set(file, entry);
|
|
961
953
|
this.idToFile.set(header.id, file);
|
|
962
954
|
return entry;
|
|
963
955
|
}
|
|
964
956
|
|
|
957
|
+
/**
|
|
958
|
+
* [perf L-1] 磁盘索引查询(首扫惰性装载,miss/空索引时 get 恒 undefined = 无索引)。
|
|
959
|
+
* 条目戳匹配 jsonl 当前 stat → 零内容读取构造缓存条目。sidecar payload(tombstone/
|
|
960
|
+
* alive)是活态数据,沿用探测分支的每轮重读语义;finalized reason 静态数据仅在
|
|
961
|
+
* sidecar 存在时读一次(文件小,成本可忽略)。
|
|
962
|
+
*
|
|
963
|
+
* 返回 undefined = 索引未命中/戳不匹配(调用方落到原三级探测);null = 负条目命中
|
|
964
|
+
* (「确认无 identity」跨实例持久,零探测跳过,与内存负缓存同款形态)。
|
|
965
|
+
*/
|
|
966
|
+
private buildEntryFromIndex(file: string, stamps: FileStamps, now: number): FileCacheEntry | null | undefined {
|
|
967
|
+
if (this.indexEntries === null) return undefined;
|
|
968
|
+
const hit = this.indexEntries.get(path.basename(file));
|
|
969
|
+
if (hit === undefined || hit.mtimeMs !== stamps.jsonl.mtimeMs || hit.size !== stamps.jsonl.size) {
|
|
970
|
+
return undefined;
|
|
971
|
+
}
|
|
972
|
+
if (hit.negative === true) {
|
|
973
|
+
this.fileCache.set(file, { negative: true, ...stamps });
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const payloads = readSidecarPayloads(file, stamps);
|
|
977
|
+
const entry = RecordStore.buildFileCacheEntry({ ...hit, forkDepth: undefined, sessionFile: file }, file, stamps, payloads, now);
|
|
978
|
+
this.fileCache.set(file, entry);
|
|
979
|
+
this.idToFile.set(hit.id, file);
|
|
980
|
+
return entry;
|
|
981
|
+
}
|
|
982
|
+
|
|
965
983
|
/**
|
|
966
984
|
* [perf L-1] 扫描尾索引落盘(节流):释放映像 → dirty/高版本/60s 节流窗三重门 →
|
|
967
985
|
* fire-and-forget saveIndex(fileCache 全量投影)。写决策与发起在同步栈(collectRecords
|
|
@@ -1089,6 +1107,34 @@ export class RecordStore {
|
|
|
1089
1107
|
entry.light.externalInstance = live ? marker : undefined;
|
|
1090
1108
|
}
|
|
1091
1109
|
|
|
1110
|
+
/** identity 基底 + sidecar 状态矩阵 → 缓存条目(索引命中与探测重建两分支的公共装配点)。 */
|
|
1111
|
+
private static buildFileCacheEntry(
|
|
1112
|
+
base: IdentityHeaderRecon,
|
|
1113
|
+
file: string,
|
|
1114
|
+
stamps: FileStamps,
|
|
1115
|
+
payloads: SidecarPayloads,
|
|
1116
|
+
now: number,
|
|
1117
|
+
): FileCacheEntry {
|
|
1118
|
+
return {
|
|
1119
|
+
light: RecordStore.buildRecord(base, {
|
|
1120
|
+
tomb: payloads.tomb,
|
|
1121
|
+
finalized: stamps.finalized !== null,
|
|
1122
|
+
finalizedReason: payloads.finalReason,
|
|
1123
|
+
alive: payloads.aliveData,
|
|
1124
|
+
jsonlMtimeMs: stamps.jsonl.mtimeMs,
|
|
1125
|
+
now,
|
|
1126
|
+
}),
|
|
1127
|
+
full: undefined,
|
|
1128
|
+
jsonl: stamps.jsonl,
|
|
1129
|
+
cancelled: stamps.cancelled,
|
|
1130
|
+
finalized: stamps.finalized,
|
|
1131
|
+
alive: stamps.alive,
|
|
1132
|
+
tomb: payloads.tomb,
|
|
1133
|
+
aliveData: payloads.aliveData,
|
|
1134
|
+
finalReason: payloads.finalReason,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1092
1138
|
/** identity 基底(头部 light 或全量 recon)+ 四分支 sidecar 状态矩阵 → SubagentRecord。 */
|
|
1093
1139
|
private static buildRecord(
|
|
1094
1140
|
base: IdentityHeaderRecon | ReconstructedRecord,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// [D4-② 轮次结算职责轴] chatMode 轮次完成的业务闭包(原 SubagentService
|
|
2
|
+
// buildSessionRunnerContext 内的 onRoundSettled 回调 ~95 行区域)整体搬移至此
|
|
3
|
+
// (行为逐字节等价:搬移 + 依赖注入,不重写逻辑)。变化轴:改轮次增量派生 /
|
|
4
|
+
// notify 时序 / base 推进 / closeAfterRound 消费语义,只改本文件;Service 经
|
|
5
|
+
// buildSessionRunnerContext 注入三个依赖回调(notify / 持久化上报 / close 兑现)。
|
|
6
|
+
|
|
7
|
+
import { getLogger } from "../core/logger.ts";
|
|
8
|
+
|
|
9
|
+
import { getFullTextFrom, nextRoundBaseTurnIndex } from "./execution-record.ts";
|
|
10
|
+
import type { ExecutionRecord } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
const logger = getLogger("subagents");
|
|
13
|
+
|
|
14
|
+
/** 轮次结算的依赖注入(Service 侧供给)。 */
|
|
15
|
+
export interface RoundSettlementDeps {
|
|
16
|
+
/** 本轮增量的通知投递(Service.notifyComplete——经 notifyHost)。 */
|
|
17
|
+
notifyComplete: (record: ExecutionRecord) => void;
|
|
18
|
+
/** 轮终迁移持久化上报(store.reportRecordTransition——live ≡ reload 等价性)。 */
|
|
19
|
+
reportRecordTransition: (record: ExecutionRecord) => void;
|
|
20
|
+
/** closeAfterRound 兑现(Service.closeAfterRoundSettled——chatMode 优雅关闭的终态化)。 */
|
|
21
|
+
closeAfterRoundSettled: (record: ExecutionRecord) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 构造 onRoundSettled 回调(session-runner agent_settled 时调用)。
|
|
25
|
+
*
|
|
26
|
+
* [V2 决策 2] chatMode 首轮闭环:session-runner 已 arm idle timer,
|
|
27
|
+
* isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled)。
|
|
28
|
+
* 回调体原为 subagent-service.ts buildSessionRunnerContext 的内联闭包,D4-② 拆出。 */
|
|
29
|
+
export function createRoundSettler(deps: RoundSettlementDeps): (record: ExecutionRecord) => void {
|
|
30
|
+
return (record) => {
|
|
31
|
+
// v4 B-1:status 保持 running(旧 idle 折入 running);session-runner 已 arm idle timer,
|
|
32
|
+
// isIdle=true 让 notify 守卫放行(时序:armIdleTimer → onRoundSettled,见 session-runner.ts:670)。
|
|
33
|
+
// round 可能初始 undefined(与 notifier.ts `record.round ?? 0` 兜底一致),
|
|
34
|
+
// 首轮 0+1=1。round 是 notifier dedup key 的组成部分,递增后同 id 下一轮不被 60s dedup 吞。
|
|
35
|
+
record.round = (record.round ?? 0) + 1;
|
|
36
|
+
// [N2][增量] 轮次回复写点(增量语义):roundText 自 roundBaseTurnIndex 起派生本轮增量
|
|
37
|
+
//(undefined 视为 0——首轮增量 = 全量,与改造前首轮逐字节一致)。成功轮次的 MF-2 原写点
|
|
38
|
+
//(doFinalizeRoundToIdle)不可达——agent_settled 恒 arm idle timer → runAndFinalize 恒
|
|
39
|
+
// early return。写入 record.result 后再 notify;本轮无非空增量且无 lastError(纯工具轮 /
|
|
40
|
+
// interrupt 抢占轮 / 模型空回复)时固定占位 "(no output this round)"(D5:增量语义下沿用
|
|
41
|
+
// 旧 record.result = 上一轮增量 → 本轮通知正文 = 上一轮内容,父 agent 误读为原样重复回复;
|
|
42
|
+
// lastError 兜底保留让失败轮通知可读)。后续 closeAfterRoundSettled 的合成 result 读
|
|
43
|
+
// record.result,同样携带本轮增量。
|
|
44
|
+
const roundText = getFullTextFrom(record, record.roundBaseTurnIndex ?? 0);
|
|
45
|
+
record.result = roundText ||
|
|
46
|
+
(record.lastError ? `round did not complete: ${record.lastError}` : "(no output this round)");
|
|
47
|
+
// 先送达本轮增量(notify),再推进 base / 消费 closeAfterRound——终态通知由
|
|
48
|
+
// closeAfterRoundSettled / closeChatIdle 的 notifyClosed 显式发出(dedup 身份为裸 id,
|
|
49
|
+
// 与本次 round notify 的 id:round key 区分),保证「本轮增量 + 终态通知」都送达;
|
|
50
|
+
// 轮次收尾 .then 的冷路径 notifyComplete 仍与本次 round notify 同 key 被 60s
|
|
51
|
+
// dedup 吞(不构成第三条)。
|
|
52
|
+
//
|
|
53
|
+
// 幂等性(覆盖面如实限定):同步路径 at-least-once——notifyComplete(同步 void)抛错时
|
|
54
|
+
// 推进/消费被跳过 → base 不推进 → 增量未消费,下轮 roundText 必含本轮文本(重发载体为
|
|
55
|
+
// 后续轮次增量拼接)。轮次收尾 .then 的冷路径 notifyComplete 不构成重发通道
|
|
56
|
+
// (notifier dedup.set 与 pending.splice 均先于 sendMessage,同 key `${id}:${round}`——
|
|
57
|
+
// round 已递增——60s 窗内重入被吞)。异步 flush 窗口不保证:合并 timer armed(其他 busy
|
|
58
|
+
// background 在场)或 isIdle 退避期间 notify 的『成功』只是入队,实际 sendMessage 发生在
|
|
59
|
+
// base 推进之后的异步时机;该窗口进程崩溃或 sendMessage 失败 → 丢失不可重发(现状全量
|
|
60
|
+
// 重发的次轮自愈在增量语义下消失),wave1 期恢复通道仅父 agent 经 /subagents 详情读取
|
|
61
|
+
// record.result,wave2 指针行落地后补全。反序(先推进后 notify)在同步路径 notify 失败时
|
|
62
|
+
// 静默丢增量且无任何重算机会,故 notify 后推进是定案。重复发送由 notifier dedup key
|
|
63
|
+
// 60s 窗界定。
|
|
64
|
+
deps.notifyComplete(record);
|
|
65
|
+
// R1 观测哨(不变式违反形态):推进前检查末 turn 未闭合且 text 非空——pi 现序下不可达
|
|
66
|
+
//(带 usage 的 message_end 恒先于 turn_end,settle 时 turn 全闭合,见 types.ts
|
|
67
|
+
// roundBaseTurnIndex 注释的行号锚定),ES1 单测自造事件序列锁不住 pi 层变化;pi 升级若
|
|
68
|
+
// 改变 turn_end/agent_end 时序,此哨兵留痕(该形态下公式仍把文本计入本轮,不丢数据)。
|
|
69
|
+
const lastTurn = record.turns[record.turns.length - 1];
|
|
70
|
+
if (lastTurn !== undefined && !lastTurn.closed && lastTurn.text.length > 0) {
|
|
71
|
+
logger.warn(
|
|
72
|
+
`[subagents] round settle with unclosed non-empty turn (record=${record.id}, turnIndex=${record.turns.length - 1}) — pi turn_end/agent_end ordering may have changed`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
// [增量] base 推进(notify 之后):下一轮增量从本轮边界起。滞后空 turn 不计入边界
|
|
76
|
+
//(防御分支,留在下一轮增量内防丢文本——nextRoundBaseTurnIndex 注释)。
|
|
77
|
+
record.roundBaseTurnIndex = nextRoundBaseTurnIndex(record);
|
|
78
|
+
// 轮终迁移持久化(residual-fixes U3 补全):热路径轮终不经 doFinalizeRoundToIdle
|
|
79
|
+
//(agent_settled 恒 arm idle timer → runAndFinalize 恒 early return,MF-2 原写点
|
|
80
|
+
// 不可达)——不 appendEntry 则 runtime/W18 派生缓存不失效,renderer 停留在
|
|
81
|
+
// register 快照(无 result),chat 等续聊的 waiting 形态显示不出来、spinner
|
|
82
|
+
// 卡死。显式上报:entry 携带本轮 result/round/chatMode(§5.4:result 有值 +
|
|
83
|
+
// chatMode=true → waiting)。closeAfterRound 的终态 entry 在此后追加,序不变。
|
|
84
|
+
deps.reportRecordTransition(record);
|
|
85
|
+
// [M5] closeAfterRound 消费点:chatMode 每轮完成的统一汇聚点(热路径轮不经
|
|
86
|
+
// runAndFinalize CAS 分支——agent_settled 恒 arm idle timer → runAndFinalize 恒
|
|
87
|
+
// early return,旧消费点对 chatMode 不可达,标志置了无人消费、tool 谎报 closed:true)。
|
|
88
|
+
if (record.closeAfterRound) {
|
|
89
|
+
record.closeAfterRound = undefined;
|
|
90
|
+
void deps.closeAfterRoundSettled(record);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|