@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,580 @@
|
|
|
1
|
+
// src/execution/notify-ledger.ts
|
|
2
|
+
//
|
|
3
|
+
// U2 B-ledger:后台通知的持久账本与 courier(设计 docs/design/subagent-dispatch-reliability.md
|
|
4
|
+
// §3.3 D4/D5)。
|
|
5
|
+
//
|
|
6
|
+
// 通知拆为两个正交关注点:
|
|
7
|
+
// - 存在性(账本):pi.appendEntry("subagent-bg-notify-ledger") 内存同步入账,
|
|
8
|
+
// 先于一切投递尝试(进程内时序);文件落盘随 pi flush 管线 debounce(非 fsync,
|
|
9
|
+
// PS-17)——强杀落在 flush 窗口时账目丢失、该通知跨重启不重放(真丢失面,
|
|
10
|
+
// 与 PS-14 首写延迟同族;日常触发概率低,观察项见 pi-semantics PS-17);
|
|
11
|
+
// - 可达性(courier):只在「主 session 确定空闲」的时刻投递——agent_settled 边沿
|
|
12
|
+
// (边沿回调内 isIdle 恒真:_emitAgentSettled 先复位 _isAgentRunActive 再发事件,
|
|
13
|
+
// agent-session.js:327-331)+ 120s 超时看门狗兜底(主 session 长期无 settled 时)。
|
|
14
|
+
// 发送前二次复查 isIdle,竞态窗口内新 run 已启动则放弃本次、消息挂回 pending
|
|
15
|
+
// 等下一边沿(D5 零宽容:busy 场景无任何依赖)。
|
|
16
|
+
//
|
|
17
|
+
// 四步生命周期(D4):
|
|
18
|
+
// ① record:appendEntry(ledger entry) 落盘(先于一切投递尝试)
|
|
19
|
+
// ② deliver:settled 边沿 / 看门狗触发 attemptDeliver——单通道
|
|
20
|
+
// pi.sendMessage({triggerTurn:true})(steer/followUp/nextTurn 通道已全部删除,
|
|
21
|
+
// D5);同一边沿的多条 pending 合并为一条注入
|
|
22
|
+
// ③ ack:回执判定成功(主 session 出现 notifyId 匹配的 subagent-bg-notify
|
|
23
|
+
// custom_message entry)后 appendEntry("subagent-bg-notify-ack")
|
|
24
|
+
// ④ replay:重启恢复扫描 ledger/ack 两列 entry 差集重放;重放按 notifyId 幂等去重
|
|
25
|
+
// (details 携带 notifyId,重复条目可识别)
|
|
26
|
+
//
|
|
27
|
+
// 通道分工(D4):ledger/ack 用 plain appendEntry(type=custom 不进 LLM 上下文——
|
|
28
|
+
// session-manager sessionEntryToContextMessages 对 custom 返回 []);送达消息用
|
|
29
|
+
// pi.sendMessage({triggerTurn:true})(custom_message 进上下文)。两通道不得混用。
|
|
30
|
+
//
|
|
31
|
+
// fork/compaction 归属(D4):扫描域 = 单 session 文件,幂等键作用域随文件域隔离。
|
|
32
|
+
// fork 复制 session 文件时继承未销账 pending 属可接受语义(分身重放补投 + notifyId
|
|
33
|
+
// 去重保证至多一次送达;「送达已落盘、销账未落盘」的强杀窗口允许重复,凭 notifyId
|
|
34
|
+
// 可识别,见 G2)。compaction 对 entry 的保留行为实装未验证(P-B4 探针,阶段 5 实测)
|
|
35
|
+
// ——compactionCheck() 提供条件降级:检测到 ledger/ack entry 被 compaction 清除时按
|
|
36
|
+
// 内存态补写(record-store 重建矩阵的内存重建同构思路,降级路径而非主路径)。
|
|
37
|
+
//
|
|
38
|
+
// 模块级绑定:bindNotifyLedgerHost 由 index.ts 的 session_start handler 装配(pi +
|
|
39
|
+
// ctx 在该处可用),notifier.notify 经 getBoundNotifyLedger 消费——不经
|
|
40
|
+
// SubagentService.piAdapter(该适配层不在 U2 改动面)。未 bind 时 notifier 退回
|
|
41
|
+
// delivery 内核路径(向后兼容旧装配 / 无 ledger 的测试场景)。
|
|
42
|
+
|
|
43
|
+
import { getLogger } from "../core/logger.ts";
|
|
44
|
+
|
|
45
|
+
/** U4 分桶日志与 index.ts 装配层共用同一具名 logger(getLogger 缓存单例)。 */
|
|
46
|
+
const logger = getLogger("subagents");
|
|
47
|
+
|
|
48
|
+
/** 账本 entry customType(plain custom entry,不进 LLM 上下文)。 */
|
|
49
|
+
export const NOTIFY_LEDGER_CUSTOM_TYPE = "subagent-bg-notify-ledger";
|
|
50
|
+
/** 销账 entry customType(plain custom entry,不进 LLM 上下文)。 */
|
|
51
|
+
export const NOTIFY_ACK_CUSTOM_TYPE = "subagent-bg-notify-ack";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 送达消息的 customType。notifier.ts / bg-notify-render / index.ts(messageRenderer
|
|
55
|
+
* 注册)同名字符串的单一常量源——本模块不 import notifier(避免循环依赖:
|
|
56
|
+
* notifier.notify → getBoundNotifyLedger),等值由 notify-ledger.test.ts 钉住。
|
|
57
|
+
*/
|
|
58
|
+
export const NOTIFY_CUSTOM_TYPE = "subagent-bg-notify";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 看门狗周期与超时(ms):主 session 长期无 settled 时的兜底触发面(D5 ②)。
|
|
62
|
+
* 周期即超时——sent 后一个周期无回执即重投(正常时序 settled 远早于周期到达,
|
|
63
|
+
* 回执销账先行;只有消息真丢失(如 steer 滞留内存队列后 session 异常)才触发重投,
|
|
64
|
+
* at-least-once + notifyId 幂等兜底)。
|
|
65
|
+
*/
|
|
66
|
+
export const NOTIFY_WATCHDOG_MS = 120_000;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* U4 投递计数分桶(设计 §5 U4:分桶口径与 §2.2 三条丢失路径一一对应,回归时定位到
|
|
70
|
+
* 具体环节):
|
|
71
|
+
* - ②settleRejected → §2.2「delivery busy parked / 投递尝试被拒」:sendDelivery
|
|
72
|
+
* 受理失败(抛异常)次数(ledger 主路径下投递被拒的唯一形态;降级内核路径的
|
|
73
|
+
* settle rejected 由 delivery warn 注入覆盖);
|
|
74
|
+
* - ①watchdogReplays → §2.2「busy 窗口滞留(steer 内存队列滞留 / 合批窗口顺延)」:
|
|
75
|
+
* sent 超期无回执 → 看门狗转回 pending 重投的条数(同一消息多次超时累计);
|
|
76
|
+
* - ③recoveryReplays → §2.2「重启内存态清零」:session_start 恢复重放条数。
|
|
77
|
+
*/
|
|
78
|
+
export interface NotifyDeliveryBucketMetrics {
|
|
79
|
+
/** 投递尝试被拒(sendDelivery 受理失败)次数。 */
|
|
80
|
+
settleRejected: number;
|
|
81
|
+
/** 销账超时 → 看门狗重投条数(累计)。 */
|
|
82
|
+
watchdogReplays: number;
|
|
83
|
+
/** session_start 恢复重放条数(累计)。 */
|
|
84
|
+
recoveryReplays: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** ledger entry 的 data schema(v1)。content 为预格式化文案(notifier
|
|
88
|
+
* buildLlmContent 产物)——恢复重放直接复用,ledger 不依赖格式化函数。
|
|
89
|
+
* record 为投递 details(BgNotifyRecord 投影,含 notifyId)——对 ledger 不透明
|
|
90
|
+
* (仅透传给送达 details / 重放),恢复扫描时经 isPlainObject 运行时校验。 */
|
|
91
|
+
export interface NotifyLedgerEntryData {
|
|
92
|
+
v: 1;
|
|
93
|
+
notifyId: string;
|
|
94
|
+
/** 预格式化通知正文(重放时原样投递,G4 字节锁定由此保真)。 */
|
|
95
|
+
content: string;
|
|
96
|
+
/** details record(回执匹配键 notifyId 在其内)。 */
|
|
97
|
+
record: object;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** ack entry 的 data schema(v1)。 */
|
|
101
|
+
export interface NotifyAckEntryData {
|
|
102
|
+
v: 1;
|
|
103
|
+
notifyId: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** ledger 依赖的宿主最小接口(index.ts session_start 装配;解耦便于测试)。 */
|
|
107
|
+
export interface NotifyLedgerHost {
|
|
108
|
+
/** 写 plain custom entry(ledger/ack 通道;pi.appendEntry)。 */
|
|
109
|
+
appendLedgerEntry(customType: string, data: unknown): void;
|
|
110
|
+
/** 读当前 session 全部 entry(回执扫描 + 恢复扫描;ctx.sessionManager.getEntries())。 */
|
|
111
|
+
readSessionEntries(): readonly unknown[];
|
|
112
|
+
/** 主 agent 是否空闲(发送前二次复查用;ctx.isIdle())。 */
|
|
113
|
+
isIdle(): boolean;
|
|
114
|
+
/** 订阅 settled 边沿(pi.on("agent_settled")——无退订语义:createExtensionAPI.on
|
|
115
|
+
* 只 push 进 extension.handlers、无 off(pi 0.84.1 loader.js:209-214),由 ledger
|
|
116
|
+
* disposed 标志包装)。注意与 ctx.events.on(channel) 消歧:后者走 eventBus、有
|
|
117
|
+
* trackEventBusSubscription 退订通路(loader.js:174、:338),语义不同。 */
|
|
118
|
+
onAgentSettled(handler: () => void): void;
|
|
119
|
+
/** 单通道送达(pi.sendMessage({triggerTurn:true}))。 */
|
|
120
|
+
sendDelivery(message: { customType: string; content: string; display: boolean; details?: unknown }): void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 账面一条通知(entry 持久形态 + 运行时投递状态)。 */
|
|
124
|
+
interface NotifyLedgerItem {
|
|
125
|
+
notifyId: string;
|
|
126
|
+
content: string;
|
|
127
|
+
record: object;
|
|
128
|
+
recordedAt: number;
|
|
129
|
+
/** 最近一次投递受理时刻;undefined = 尚未投递(pending,等下一边沿)。 */
|
|
130
|
+
sentAt: number | undefined;
|
|
131
|
+
/** 投递尝试次数(看门狗重投计数,诊断用)。 */
|
|
132
|
+
attempts: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 通知账本(四步生命周期载体)。 */
|
|
136
|
+
export interface NotifyLedger {
|
|
137
|
+
/** ① 写账(appendEntry 先于一切投递尝试)。幂等:同 notifyId 已在账(pending/sent)
|
|
138
|
+
* 或已销账 → 返回 false(调用方跳过投递——notifyId 幂等去重)。 */
|
|
139
|
+
record(notifyId: string, content: string, record: object): boolean;
|
|
140
|
+
/** ② 投递尝试:isIdle 二次复查,busy / 探测异常 → 挂回 pending 等下一边沿;idle →
|
|
141
|
+
* 同批 pending 合并单条送达(triggerTurn 直达)。 */
|
|
142
|
+
attemptDeliver(): void;
|
|
143
|
+
/** ③ 回执销账:扫 session entries,出现 notifyId 匹配的送达 custom_message entry
|
|
144
|
+
* → appendEntry(ack) + 摘账(内存态不承担销账职责,权威 = 两列 entry 差集)。 */
|
|
145
|
+
checkReceipts(): void;
|
|
146
|
+
/** ④ 重启恢复:扫 ledger/ack 两列 entry 差集,未销账号重新入账并投递(已销账零重发)。
|
|
147
|
+
* @returns 重放条数。 */
|
|
148
|
+
recoverFromSession(): number;
|
|
149
|
+
/** compaction 降级(P-B4 未验证):检测 ledger/ack entry 被清除 → 按内存态补写。
|
|
150
|
+
* @returns 补写条数。 */
|
|
151
|
+
compactionCheck(): number;
|
|
152
|
+
/** 诊断/测试:pending(已记账未投递)条数。 */
|
|
153
|
+
pendingCount(): number;
|
|
154
|
+
/** 诊断/测试:已投递待回执条数。 */
|
|
155
|
+
waitingReceiptCount(): number;
|
|
156
|
+
/** U4 诊断:三桶计数快照(副本;增量同时经 extensionLogger 通道落日志)。 */
|
|
157
|
+
deliveryMetrics(): NotifyDeliveryBucketMetrics;
|
|
158
|
+
/** 销毁:清看门狗 timer + 摘模块级绑定。settled 边沿静默:直调实例由闭包内
|
|
159
|
+
* disposed 标志短路;bind 路径实例从 boundLedger 摘除(单例 handler 不再分发到它)。 */
|
|
160
|
+
dispose(): void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── entry 形态判定(运行时 guard,无 unsafe cast) ──────────────
|
|
164
|
+
|
|
165
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
166
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 扫 ledger/ack 两列 plain custom entry(恢复 / compaction 检查共用)。 */
|
|
170
|
+
function scanSessionLedgerEntries(entries: readonly unknown[]): {
|
|
171
|
+
ledger: Map<string, NotifyLedgerEntryData>;
|
|
172
|
+
acked: Set<string>;
|
|
173
|
+
} {
|
|
174
|
+
const ledger = new Map<string, NotifyLedgerEntryData>();
|
|
175
|
+
const acked = new Set<string>();
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
if (!isPlainObject(entry) || entry["type"] !== "custom") continue;
|
|
178
|
+
const customType = entry["customType"];
|
|
179
|
+
const data = entry["data"];
|
|
180
|
+
if (!isPlainObject(data)) continue;
|
|
181
|
+
const notifyId = data["notifyId"];
|
|
182
|
+
if (typeof notifyId !== "string") continue;
|
|
183
|
+
if (customType === NOTIFY_LEDGER_CUSTOM_TYPE) {
|
|
184
|
+
const content = data["content"];
|
|
185
|
+
const record = data["record"];
|
|
186
|
+
if (typeof content === "string" && isPlainObject(record)) {
|
|
187
|
+
// 后写覆盖:fork 文件含同 notifyId 多条 ledger entry 时取最新
|
|
188
|
+
ledger.set(notifyId, { v: 1, notifyId, content, record });
|
|
189
|
+
}
|
|
190
|
+
} else if (customType === NOTIFY_ACK_CUSTOM_TYPE) {
|
|
191
|
+
acked.add(notifyId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { ledger, acked };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** 收集 wanted 集合中已送达(custom_message entry 出现)的 notifyId。
|
|
198
|
+
* 送达 entry 两种形态都匹配:单条 details.notifyId / 批量 details.items[].notifyId
|
|
199
|
+
* (对齐 courier 合并投递的 details 结构)。 */
|
|
200
|
+
function collectDeliveredNotifyIds(entries: readonly unknown[], wanted: Set<string>): Set<string> {
|
|
201
|
+
const delivered = new Set<string>();
|
|
202
|
+
if (wanted.size === 0) return delivered;
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
if (!isPlainObject(entry) || entry["type"] !== "custom_message") continue;
|
|
205
|
+
if (entry["customType"] !== NOTIFY_CUSTOM_TYPE) continue;
|
|
206
|
+
const details = entry["details"];
|
|
207
|
+
if (!isPlainObject(details)) continue;
|
|
208
|
+
const notifyId = details["notifyId"];
|
|
209
|
+
if (typeof notifyId === "string" && wanted.has(notifyId)) {
|
|
210
|
+
delivered.add(notifyId);
|
|
211
|
+
}
|
|
212
|
+
const items = details["items"];
|
|
213
|
+
if (Array.isArray(items)) {
|
|
214
|
+
for (const item of items) {
|
|
215
|
+
if (isPlainObject(item)) {
|
|
216
|
+
const id = item["notifyId"];
|
|
217
|
+
if (typeof id === "string" && wanted.has(id)) delivered.add(id);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return delivered;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── 账本实现 ────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
export function createNotifyLedger(
|
|
228
|
+
host: NotifyLedgerHost,
|
|
229
|
+
options?: { registerSettledListener?: boolean },
|
|
230
|
+
): NotifyLedger {
|
|
231
|
+
/** 在账未销账(recorded / sent 两态)。 */
|
|
232
|
+
const items = new Map<string, NotifyLedgerItem>();
|
|
233
|
+
/** 已销账内存索引(notifyId 幂等判重 + compaction 补写源;权威 = ack entry 列)。 */
|
|
234
|
+
const ackedIds = new Set<string>();
|
|
235
|
+
/** U4 投递计数三桶(诊断快照源;增量经 emitBucketLog 落 extensionLogger)。 */
|
|
236
|
+
const buckets: NotifyDeliveryBucketMetrics = {
|
|
237
|
+
settleRejected: 0,
|
|
238
|
+
watchdogReplays: 0,
|
|
239
|
+
recoveryReplays: 0,
|
|
240
|
+
};
|
|
241
|
+
let disposed = false;
|
|
242
|
+
let watchdogTimer: ReturnType<typeof setInterval> | undefined;
|
|
243
|
+
|
|
244
|
+
const api: NotifyLedger = {
|
|
245
|
+
record(notifyId, content, record): boolean {
|
|
246
|
+
if (disposed) return false;
|
|
247
|
+
if (items.has(notifyId) || ackedIds.has(notifyId)) return false;
|
|
248
|
+
host.appendLedgerEntry(NOTIFY_LEDGER_CUSTOM_TYPE, {
|
|
249
|
+
v: 1,
|
|
250
|
+
notifyId,
|
|
251
|
+
content,
|
|
252
|
+
record,
|
|
253
|
+
} satisfies NotifyLedgerEntryData);
|
|
254
|
+
items.set(notifyId, { notifyId, content, record, recordedAt: Date.now(), sentAt: undefined, attempts: 0 });
|
|
255
|
+
ensureWatchdog();
|
|
256
|
+
return true;
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
attemptDeliver(): void {
|
|
260
|
+
if (disposed) return;
|
|
261
|
+
const pending = [...items.values()].filter((i) => i.sentAt === undefined);
|
|
262
|
+
if (pending.length === 0) return;
|
|
263
|
+
// 发送前二次复查(D5 零宽容):busy / 探测异常(session 关闭等)→ 放弃本次,
|
|
264
|
+
// 消息挂回 pending 等下一边沿 / 看门狗
|
|
265
|
+
try {
|
|
266
|
+
if (!host.isIdle()) return;
|
|
267
|
+
} catch {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const message = mergeItems(pending);
|
|
271
|
+
try {
|
|
272
|
+
host.sendDelivery(message);
|
|
273
|
+
} catch {
|
|
274
|
+
// 受理失败:留 pending(账已落盘,下一边沿重试 + 重启恢复兜底)。
|
|
275
|
+
// U4 ②settleRejected 桶:投递尝试被拒按事件次计数(对齐内核 onSettled
|
|
276
|
+
// per-批次口径),增量落日志供回归定位。
|
|
277
|
+
buckets.settleRejected += 1;
|
|
278
|
+
emitBucketLog("settleRejected", buckets.settleRejected, { pending: pending.length });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
for (const item of pending) {
|
|
283
|
+
item.sentAt = now;
|
|
284
|
+
item.attempts += 1;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
checkReceipts(): void {
|
|
289
|
+
if (disposed || items.size === 0) return;
|
|
290
|
+
const delivered = collectDeliveredNotifyIds(host.readSessionEntries(), new Set(items.keys()));
|
|
291
|
+
for (const notifyId of delivered) {
|
|
292
|
+
ack(notifyId);
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
recoverFromSession(): number {
|
|
297
|
+
if (disposed) return 0;
|
|
298
|
+
const state = scanSessionLedgerEntries(host.readSessionEntries());
|
|
299
|
+
for (const notifyId of state.acked) ackedIds.add(notifyId);
|
|
300
|
+
let replayed = 0;
|
|
301
|
+
for (const entry of state.ledger.values()) {
|
|
302
|
+
if (state.acked.has(entry.notifyId)) continue; // 已销账零重发
|
|
303
|
+
if (items.has(entry.notifyId) || ackedIds.has(entry.notifyId)) continue; // 幂等
|
|
304
|
+
items.set(entry.notifyId, {
|
|
305
|
+
...entry,
|
|
306
|
+
recordedAt: Date.now(),
|
|
307
|
+
sentAt: undefined,
|
|
308
|
+
attempts: 0,
|
|
309
|
+
});
|
|
310
|
+
replayed += 1;
|
|
311
|
+
}
|
|
312
|
+
if (replayed > 0) {
|
|
313
|
+
// U4 ③recoveryReplays 桶:重启恢复重放条数(index.ts 装配层的重复日志已并入)
|
|
314
|
+
buckets.recoveryReplays += replayed;
|
|
315
|
+
emitBucketLog("recoveryReplays", buckets.recoveryReplays, { replayed });
|
|
316
|
+
ensureWatchdog();
|
|
317
|
+
// 「送达已落盘、销账未落盘」的强杀窗口(custom_message entry 已写、ack 尚未写):
|
|
318
|
+
// 回执已在 session 文件里,先消费它补写 ack,避免对已送达条目必然重投一次
|
|
319
|
+
//(边沿/看门狗路径都是先 checkReceipts 再 attemptDeliver,恢复路径对齐同序;
|
|
320
|
+
// G2 仍保留 at-least-once 兜底语义,此处只是零成本消除常见重复)。
|
|
321
|
+
checkReceipts();
|
|
322
|
+
attemptDeliver();
|
|
323
|
+
}
|
|
324
|
+
return replayed;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
compactionCheck(): number {
|
|
328
|
+
if (disposed) return 0;
|
|
329
|
+
const state = scanSessionLedgerEntries(host.readSessionEntries());
|
|
330
|
+
let rewritten = 0;
|
|
331
|
+
for (const item of items.values()) {
|
|
332
|
+
if (!state.ledger.has(item.notifyId)) {
|
|
333
|
+
host.appendLedgerEntry(NOTIFY_LEDGER_CUSTOM_TYPE, {
|
|
334
|
+
v: 1,
|
|
335
|
+
notifyId: item.notifyId,
|
|
336
|
+
content: item.content,
|
|
337
|
+
record: item.record,
|
|
338
|
+
} satisfies NotifyLedgerEntryData);
|
|
339
|
+
rewritten += 1;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const notifyId of ackedIds) {
|
|
343
|
+
if (!state.acked.has(notifyId)) {
|
|
344
|
+
host.appendLedgerEntry(NOTIFY_ACK_CUSTOM_TYPE, { v: 1, notifyId } satisfies NotifyAckEntryData);
|
|
345
|
+
rewritten += 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return rewritten;
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
pendingCount(): number {
|
|
352
|
+
let n = 0;
|
|
353
|
+
for (const item of items.values()) {
|
|
354
|
+
if (item.sentAt === undefined) n += 1;
|
|
355
|
+
}
|
|
356
|
+
return n;
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
waitingReceiptCount(): number {
|
|
360
|
+
let n = 0;
|
|
361
|
+
for (const item of items.values()) {
|
|
362
|
+
if (item.sentAt !== undefined) n += 1;
|
|
363
|
+
}
|
|
364
|
+
return n;
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
deliveryMetrics(): NotifyDeliveryBucketMetrics {
|
|
368
|
+
return { ...buckets };
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
dispose(): void {
|
|
372
|
+
disposed = true;
|
|
373
|
+
if (watchdogTimer !== undefined) {
|
|
374
|
+
clearInterval(watchdogTimer);
|
|
375
|
+
watchdogTimer = undefined;
|
|
376
|
+
}
|
|
377
|
+
if (getBoundLedger() === api) setBoundLedger(undefined);
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// ─── 内部函数(闭包) ─────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
function ack(notifyId: string): void {
|
|
384
|
+
if (!items.has(notifyId)) return;
|
|
385
|
+
host.appendLedgerEntry(NOTIFY_ACK_CUSTOM_TYPE, { v: 1, notifyId } satisfies NotifyAckEntryData);
|
|
386
|
+
items.delete(notifyId);
|
|
387
|
+
ackedIds.add(notifyId);
|
|
388
|
+
maybeStopWatchdog();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** 同一边沿的多条 pending 合并为一条注入(D5)。合并形态对齐 delivery 内核
|
|
392
|
+
* buildBatchPayload:content 以 "\n\n---\n\n" join;details 包装 {batch:true,
|
|
393
|
+
* items}(bg-notify-render 的 extractBgNotifyRecord 按 item 顶层字段读取)。 */
|
|
394
|
+
function mergeItems(batch: NotifyLedgerItem[]): {
|
|
395
|
+
customType: string;
|
|
396
|
+
content: string;
|
|
397
|
+
display: boolean;
|
|
398
|
+
details?: unknown;
|
|
399
|
+
} {
|
|
400
|
+
if (batch.length === 1) {
|
|
401
|
+
return { customType: NOTIFY_CUSTOM_TYPE, content: batch[0]!.content, display: true, details: batch[0]!.record };
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
customType: NOTIFY_CUSTOM_TYPE,
|
|
405
|
+
content: batch.map((i) => i.content).join("\n\n---\n\n"),
|
|
406
|
+
display: true,
|
|
407
|
+
details: { batch: true, items: batch.map((i) => i.record) },
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function ensureWatchdog(): void {
|
|
412
|
+
if (watchdogTimer !== undefined || disposed) return;
|
|
413
|
+
// 看门狗(D5 ②兜底触发面):回执检查 + 超时重投 + pending 补投。
|
|
414
|
+
watchdogTimer = setInterval(() => {
|
|
415
|
+
if (disposed) return;
|
|
416
|
+
checkReceipts();
|
|
417
|
+
// sent 超过一个周期无回执 → 转回 pending(attempts 累加),本次 tick 的
|
|
418
|
+
// attemptDeliver 即重投——正常时序 settled 边沿早已销账,只有消息真丢失才到这
|
|
419
|
+
const cutoff = Date.now() - NOTIFY_WATCHDOG_MS;
|
|
420
|
+
let timedOut = 0;
|
|
421
|
+
for (const item of items.values()) {
|
|
422
|
+
if (item.sentAt !== undefined && item.sentAt <= cutoff) {
|
|
423
|
+
item.sentAt = undefined;
|
|
424
|
+
timedOut += 1;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (timedOut > 0) {
|
|
428
|
+
// U4 ①watchdogReplays 桶:busy 窗口滞留兜底的重投条数(同一消息反复超时累计)
|
|
429
|
+
buckets.watchdogReplays += timedOut;
|
|
430
|
+
emitBucketLog("watchdogReplays", buckets.watchdogReplays, { timedOut });
|
|
431
|
+
}
|
|
432
|
+
attemptDeliver();
|
|
433
|
+
}, NOTIFY_WATCHDOG_MS);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function maybeStopWatchdog(): void {
|
|
437
|
+
if (items.size === 0 && watchdogTimer !== undefined) {
|
|
438
|
+
clearInterval(watchdogTimer);
|
|
439
|
+
watchdogTimer = undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function attemptDeliver(): void {
|
|
444
|
+
api.attemptDeliver();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function checkReceipts(): void {
|
|
448
|
+
api.checkReceipts();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** U4 分桶日志:计数经既有 extensionLogger 通道暴露(appendEntry 落 session JSONL
|
|
452
|
+
* 不进 LLM/TUI + XYZ_AGENT_DEBUG=1 落 `<dataDir>/logs/`),替代无痕内存态。
|
|
453
|
+
* msg 固定 key(限流命中面),动态值按 D4 约定放 data 参数。 */
|
|
454
|
+
function emitBucketLog(
|
|
455
|
+
bucket: keyof NotifyDeliveryBucketMetrics,
|
|
456
|
+
total: number,
|
|
457
|
+
extra: Record<string, unknown>,
|
|
458
|
+
): void {
|
|
459
|
+
logger.warn(`notify delivery bucket [${bucket}]`, { total, ...extra });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// settled 边沿(D5 ①触发点):先查回执(销账上一轮投递),再投递新 pending。
|
|
463
|
+
// 回执可见性时序(custom message 落盘 message_end → appendCustomMessageEntry 先于
|
|
464
|
+
// _emitAgentSettled)= 设计 P-B1(b) 探针门待证项(subagent-dispatch-reliability.md
|
|
465
|
+
// D5)——实测若晚于 S3 阈值,按既定降级路径收敛为「只记账 + 超时看门狗直达」。
|
|
466
|
+
// at-least-once 正确性不承重于该时序:checkReceipts 错过边沿由看门狗 + 重启恢复兜底
|
|
467
|
+
// (P-B0 相邻机制已源码级锚定,agent-session.js:327-331,PS-07)。
|
|
468
|
+
// [MF-5] registerSettledListener=false(bind 路径)时跳过注册:pi.on("agent_settled")
|
|
469
|
+
// 无退订语义——createExtensionAPI.on 只 push 进 extension.handlers、无 off
|
|
470
|
+
//(pi 0.84.1 dist/core/extensions/loader.js:209-214)。注意消歧两类订阅面:此处的
|
|
471
|
+
// pi.on 是生命周期事件订阅(无退订);ctx.events.on(channel) 走 eventBus、有
|
|
472
|
+
// trackEventBusSubscription 退订通路(loader.js:174、:338)——勿按后者的可退订
|
|
473
|
+
// 语义「修复」此处。per-bind 注册会随 session 切换累积死 handler
|
|
474
|
+
// (旧实例 disposed 短路但物理监听永存)——bind 用模块级单例 handler
|
|
475
|
+
// (settledEdgeDispatch)+ boundLedger 引用切换替代(见 bindNotifyLedgerHost)。
|
|
476
|
+
if (options?.registerSettledListener !== false) {
|
|
477
|
+
host.onAgentSettled(() => {
|
|
478
|
+
if (disposed) return;
|
|
479
|
+
checkReceipts();
|
|
480
|
+
attemptDeliver();
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return api;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ─── 模块级绑定(notifier 消费入口) ──────────────────────────
|
|
488
|
+
|
|
489
|
+
// C-ext-06:跨模块单例经 globalThis[Symbol.for] slot 持有,不用裸模块级 let——
|
|
490
|
+
// jiti 因路径字符串不同加载多份模块时裸 let 会单例分裂:index.ts 的 bind 与
|
|
491
|
+
// notifier.ts 的 getBoundNotifyLedger 各持一份绑定 → notify 静默走「未 bind →
|
|
492
|
+
// 退回 delivery 内核路径」分岔(无任何 warn),U2 at-least-once 退化回 C-ext-19
|
|
493
|
+
// 立约要防的 at-most-once 事故基线。先例:model-config-service MODEL_SERVICE_SLOT_KEY /
|
|
494
|
+
// dialogQueue / channelHandshake / ui-observability 同款(docs/standards.md §7.5)。
|
|
495
|
+
const NOTIFY_LEDGER_SLOT_KEY = Symbol.for("@zhushanwen/pi-subagents.notifyLedger");
|
|
496
|
+
|
|
497
|
+
type NotifyLedgerSlot = {
|
|
498
|
+
current: NotifyLedger | undefined;
|
|
499
|
+
/**
|
|
500
|
+
* [MF-5] settled 物理监听注册标志:物理监听(host.onAgentSettled → pi.on)只在
|
|
501
|
+
* 首次 bind 时注册一次;真实环境 pi 是同一 emitter,后续 /resume /fork /new 的
|
|
502
|
+
* session_start 只切换 boundLedger 引用,不新增监听。与 boundLedger 同 slot 持有
|
|
503
|
+
* (C-ext-06)——裸模块级 let 在 jiti 路径分裂下双实例各持旗标 → 同一 emitter
|
|
504
|
+
* 双注册 settledEdgeDispatch;并入 slot 后旗标与绑定单介质同源。
|
|
505
|
+
*/
|
|
506
|
+
listenerRegistered: boolean;
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
function getNotifyLedgerSlot(): NotifyLedgerSlot {
|
|
510
|
+
// globalThis 无 symbol 索引签名,但运行时支持 symbol 键——用 Reflect 安全读写,
|
|
511
|
+
// 避免双重断言。NotifyLedgerSlot 是运行时保证的固定形状(本文件唯一写入点)。
|
|
512
|
+
let slot = Reflect.get(globalThis, NOTIFY_LEDGER_SLOT_KEY) as NotifyLedgerSlot | undefined;
|
|
513
|
+
if (!slot) {
|
|
514
|
+
slot = { current: undefined, listenerRegistered: false };
|
|
515
|
+
Reflect.set(globalThis, NOTIFY_LEDGER_SLOT_KEY, slot);
|
|
516
|
+
}
|
|
517
|
+
return slot;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** 当前绑定的 ledger(bind/dispose/reset 三个写点与消费读点全部经 slot,单介质同源)。 */
|
|
521
|
+
function getBoundLedger(): NotifyLedger | undefined {
|
|
522
|
+
return getNotifyLedgerSlot().current;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function setBoundLedger(ledger: NotifyLedger | undefined): void {
|
|
526
|
+
getNotifyLedgerSlot().current = ledger;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* [MF-5] settled 边沿模块级单例 handler:分发目标恒为当前活跃 ledger。
|
|
531
|
+
* disposed 实例已在 dispose() 中从 boundLedger 摘除 → 不会成为分发目标
|
|
532
|
+
* (短路语义与旧闭包 handler 的 `if (disposed) return` 等价;api 方法内
|
|
533
|
+
* disposed 守卫双保险)。boundLedger 为 undefined(未 bind / 已全部 dispose)时
|
|
534
|
+
* 静默返回,对齐旧闭包在实例不存在时的无动作语义。
|
|
535
|
+
*/
|
|
536
|
+
function settledEdgeDispatch(): void {
|
|
537
|
+
const ledger = getBoundLedger();
|
|
538
|
+
if (ledger === undefined) return;
|
|
539
|
+
ledger.checkReceipts();
|
|
540
|
+
ledger.attemptDeliver();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* session_start 装配(index.ts):构造新 ledger 并绑定为模块级单例(notifier.notify
|
|
545
|
+
* 经 getBoundNotifyLedger 消费)。重复 bind(/resume /fork /new 的 session_start)
|
|
546
|
+
* 替换旧实例——内存态清零符合「内存不承担销账职责」,权威 = entry 两列差集
|
|
547
|
+
* (调用方随后 recoverFromSession 重建)。
|
|
548
|
+
*
|
|
549
|
+
* [MF-5] 监听单例化:首次 bind 经当次 host 注册模块级单例 handler
|
|
550
|
+
* (settledEdgeDispatch),后续 bind 只换 boundLedger 引用——物理监听数不随
|
|
551
|
+
* session 切换增长。直调 createNotifyLedger(旧装配 / 测试)不经过此路径,
|
|
552
|
+
* 保留 per-instance 注册(默认 registerSettledListener 语义不变)。
|
|
553
|
+
*/
|
|
554
|
+
export function bindNotifyLedgerHost(host: NotifyLedgerHost): NotifyLedger {
|
|
555
|
+
getBoundLedger()?.dispose();
|
|
556
|
+
if (!getNotifyLedgerSlot().listenerRegistered) {
|
|
557
|
+
getNotifyLedgerSlot().listenerRegistered = true;
|
|
558
|
+
host.onAgentSettled(settledEdgeDispatch);
|
|
559
|
+
}
|
|
560
|
+
const ledger = createNotifyLedger(host, { registerSettledListener: false });
|
|
561
|
+
setBoundLedger(ledger);
|
|
562
|
+
return ledger;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* notifier.notify 消费入口:未 bind(旧装配 / 无 ledger 的测试)→ undefined,
|
|
567
|
+
* notifier 退回 delivery 内核路径(向后兼容)。
|
|
568
|
+
*/
|
|
569
|
+
export function getBoundNotifyLedger(): NotifyLedger | undefined {
|
|
570
|
+
return getBoundLedger();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** 测试隔离:dispose 并清模块级绑定。[MF-5] 同步重置监听注册标志——测试的 mock
|
|
574
|
+
* host 是新 emitter(真实 pi 同一 emitter,本函数仅测试路径调用),不复位则后续
|
|
575
|
+
* bind 不再注册、新 mock 的 fireSettled 失效。 */
|
|
576
|
+
export function _resetNotifyLedgerForTest(): void {
|
|
577
|
+
getBoundLedger()?.dispose();
|
|
578
|
+
setBoundLedger(undefined);
|
|
579
|
+
getNotifyLedgerSlot().listenerRegistered = false;
|
|
580
|
+
}
|