@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,248 @@
|
|
|
1
|
+
// src/core/model-resolver.ts
|
|
2
|
+
//
|
|
3
|
+
// 模型解析(三层):
|
|
4
|
+
// 1. 用户显式 override(tool 参数 model,平铺在 subagent params 顶层)→ registry lookup + auth
|
|
5
|
+
// 2. agent .md frontmatter model(agent 作者指定)→ registry lookup + auth
|
|
6
|
+
// 3. 主 agent 当前模型(ctx.model)→ 直接透传,无需 lookup
|
|
7
|
+
//
|
|
8
|
+
// 设计:默认与主 agent 同模型(零配置)。只有「有人显式指定 model」时才查
|
|
9
|
+
// registry 做解析 + 鉴权校验。thinkingLevel 同链路,无指定时 undefined。
|
|
10
|
+
|
|
11
|
+
// [U1 ModelRef 全等裁决] THINKING_ORDER / ThinkingLevel / strip / 裁决入口收拢到
|
|
12
|
+
// shared/model-ref.ts(单一权威),此处 re-export 保持既有 import 路径不变
|
|
13
|
+
//(subagent-tool / tool-workflow 的 schema 枚举从本模块派生)。
|
|
14
|
+
import {
|
|
15
|
+
THINKING_ORDER,
|
|
16
|
+
assertCanonicalModelRef,
|
|
17
|
+
modelRefFromVerified,
|
|
18
|
+
} from "../shared/model-ref";
|
|
19
|
+
|
|
20
|
+
export { THINKING_ORDER };
|
|
21
|
+
export type { ThinkingLevel } from "../shared/model-ref";
|
|
22
|
+
|
|
23
|
+
/** 解析失败时错误信息列出的可用模型上限(防超长错误信息)。 */
|
|
24
|
+
const MODEL_LIST_LIMIT = 20;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ModelRegistry 的最小接口(duck-typed,测试可 mock)。
|
|
28
|
+
* 字段结构与 Pi SDK 的 ctx.modelRegistry 对齐。
|
|
29
|
+
*/
|
|
30
|
+
export interface ModelRegistryLike {
|
|
31
|
+
/** 返回所有已配置鉴权的可用模型。 */
|
|
32
|
+
getAvailable(): ModelInfo[];
|
|
33
|
+
/** 按 (provider, modelId) 查找。 */
|
|
34
|
+
find(provider: string, modelId: string): ModelInfo | undefined;
|
|
35
|
+
/** 校验模型鉴权是否就绪。 */
|
|
36
|
+
hasConfiguredAuth(model: unknown): boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 模型信息(registry 返回元素 / ctx.model 鸭子类型兼容)。
|
|
41
|
+
* ctx.model(SDK Model<Api>)是此类型的超集,运行时直接当 ModelInfo 用。
|
|
42
|
+
*/
|
|
43
|
+
export interface ModelInfo {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
provider: string;
|
|
47
|
+
reasoning: boolean;
|
|
48
|
+
thinkingLevelMap?: Record<string, unknown>;
|
|
49
|
+
contextWindow?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** agent .md frontmatter 解析结果。 */
|
|
53
|
+
export interface AgentConfig {
|
|
54
|
+
/** agent 名(文件名 basename)。 */
|
|
55
|
+
name: string;
|
|
56
|
+
/** system prompt(markdown 正文)。 */
|
|
57
|
+
systemPrompt: string;
|
|
58
|
+
/** tool allowlist(三层过滤之一)。 */
|
|
59
|
+
tools?: string[];
|
|
60
|
+
/** 默认模型 override("provider/modelId")。agent 作者显式指定。 */
|
|
61
|
+
model?: string;
|
|
62
|
+
/** 默认 thinkingLevel override。 */
|
|
63
|
+
thinkingLevel?: string;
|
|
64
|
+
/** 默认 background 模式(true 时无显式 wait 走 background)。 */
|
|
65
|
+
defaultBackground?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* 执行引擎 id(agent .md frontmatter engine 字段,D9 per-agent 主通道)。
|
|
68
|
+
* 解析期已对注册表校验(未注册 id 在 agent-registry 抛 EngineNotFoundError);
|
|
69
|
+
* 执行侧(P4 路由层)按 调用参数 > 本字段 > 全局默认 三层取值。
|
|
70
|
+
*/
|
|
71
|
+
engine?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 解析结果(model 实例 + 生效的 thinkingLevel)。 */
|
|
75
|
+
export interface ResolvedModel {
|
|
76
|
+
model: ModelInfo;
|
|
77
|
+
thinkingLevel: string | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================
|
|
81
|
+
// 常量
|
|
82
|
+
// ============================================================
|
|
83
|
+
|
|
84
|
+
// MODEL_LIST_LIMIT 与 THINKING_ORDER 随 suggestSimilarModels/lookupModel 一并迁入
|
|
85
|
+
// shared/model-ref.ts(U1 裁决单一入口)。
|
|
86
|
+
|
|
87
|
+
// ============================================================
|
|
88
|
+
// 解析
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 三层模型解析:
|
|
93
|
+
*
|
|
94
|
+
* ╔═══════════════════════════════════════════════════════════════╗
|
|
95
|
+
// ║ 优先级(高→低): ║
|
|
96
|
+
// ║ 1. paramOverride.model (调用方显式指定,tool 参数) ║
|
|
97
|
+
// ║ 2. agentConfig.model (agent .md frontmatter) ║
|
|
98
|
+
// ║ 3. ctxModel (主 agent 当前模型,直接透传) ║
|
|
99
|
+
// ║ ║
|
|
100
|
+
// ║ 1/2 级查 registry + auth 校验;3 级无需 lookup(主 agent 在用 ║
|
|
101
|
+
// ║ 说明 auth OK)。thinkingLevel 无显式指定时兜底「模型最高可用档」║
|
|
102
|
+
// ║ (maxThinkingForModel,含 "max" 时用 max)——不落回 pi 默认 ║
|
|
103
|
+
// ║ medium,subagent 任务需要最大推理深度。 ║
|
|
104
|
+
// ║ ║
|
|
105
|
+
// ║ 显式指定但 lookup/auth 失败 → 抛错(不静默降级到主 agent, ║
|
|
106
|
+
// ║ 因为用户明确要求了某个 model,降级会造成「以为用了 X 实际用 Y」║
|
|
107
|
+
// ╚════════════════════════════════════════════════════════════════╝
|
|
108
|
+
*
|
|
109
|
+
* @param agentConfig agent .md 解析结果(查 model override + thinkingLevel)
|
|
110
|
+
* @param modelRegistry registry(仅 override 路径用)
|
|
111
|
+
* @param paramOverride 调用方显式 override(最高优先级)
|
|
112
|
+
* @param ctxModel 主 agent 当前模型(兜底,直接透传)
|
|
113
|
+
*/
|
|
114
|
+
export function resolveModel(
|
|
115
|
+
agentConfig: AgentConfig | undefined,
|
|
116
|
+
modelRegistry: ModelRegistryLike,
|
|
117
|
+
paramOverride?: { model?: string; thinkingLevel?: string },
|
|
118
|
+
ctxModel?: ModelInfo,
|
|
119
|
+
): ResolvedModel {
|
|
120
|
+
// 1. paramOverride(最高优先级)。显式指定但 lookup/auth 失败 → 直接抛错,
|
|
121
|
+
// 不降级到下层(避免「以为用了 X 实际用 Y」的静默错误)。
|
|
122
|
+
if (paramOverride?.model) {
|
|
123
|
+
return lookupAndResolve(
|
|
124
|
+
paramOverride.model,
|
|
125
|
+
paramOverride.thinkingLevel ?? agentConfig?.thinkingLevel,
|
|
126
|
+
modelRegistry,
|
|
127
|
+
"paramOverride",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 2. agentConfig.model(agent 作者指定)。同样显式 → 失败即抛错。
|
|
132
|
+
if (agentConfig?.model) {
|
|
133
|
+
return lookupAndResolve(
|
|
134
|
+
agentConfig.model,
|
|
135
|
+
agentConfig.thinkingLevel,
|
|
136
|
+
modelRegistry,
|
|
137
|
+
"agentConfig",
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 3. 主 agent model(直接透传)。无显式 thinking → 兜底最高可用档。
|
|
142
|
+
// [U1 D2 豁免口径] ctxModel 是运行时已验证的 ModelInfo 对象,豁免 registry 存在性
|
|
143
|
+
// 复查与 auth 校验;但孪生守卫同等适用(registry 含大小写孪生时拒绝放行,modelRefFromVerified)。
|
|
144
|
+
if (ctxModel) {
|
|
145
|
+
modelRefFromVerified(ctxModel, modelRegistry);
|
|
146
|
+
return {
|
|
147
|
+
model: ctxModel,
|
|
148
|
+
thinkingLevel: paramOverride?.thinkingLevel ?? agentConfig?.thinkingLevel
|
|
149
|
+
?? maxThinkingForModel(ctxModel),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 全部不可用 → 列出可用模型辅助调试
|
|
154
|
+
const available = modelRegistry.getAvailable().map((m) => `${m.provider}/${m.id}`);
|
|
155
|
+
throw new Error(
|
|
156
|
+
`No available model. Main agent has no active model, and no override was resolved.` +
|
|
157
|
+
(available.length > 0
|
|
158
|
+
? `\nAvailable models:\n ${available.slice(0, MODEL_LIST_LIMIT).join("\n ")}`
|
|
159
|
+
: ""),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* lookup + auth 校验 + thinkingLevel clamp。显式指定但失败 → 抛错(不降级)。
|
|
165
|
+
*
|
|
166
|
+
* [U1 D1] 模型串裁决经 assertCanonicalModelRef 单一入口(strip 后缀 → provider 精确 →
|
|
167
|
+
* 全等匹配 → 孪生守卫;未命中同步抛错并附问句式纠错候选)。放行即与 registry 条目全等。
|
|
168
|
+
*
|
|
169
|
+
* 错误信息区分两种失败(避免误导排查方向):
|
|
170
|
+
* - model 非全等(不存在/大小写不符/孪生歧义)→ assertCanonicalModelRef 的问句式报错
|
|
171
|
+
* - model 全等命中但 auth 未配置 → 提示在 models.json 配置鉴权
|
|
172
|
+
*/
|
|
173
|
+
function lookupAndResolve(
|
|
174
|
+
modelStr: string,
|
|
175
|
+
requestedThinking: string | undefined,
|
|
176
|
+
registry: ModelRegistryLike,
|
|
177
|
+
source: "paramOverride" | "agentConfig",
|
|
178
|
+
): ResolvedModel {
|
|
179
|
+
const ref = assertCanonicalModelRef(modelStr, registry, { source });
|
|
180
|
+
const model = registry.find(ref.provider, ref.id);
|
|
181
|
+
if (!model) {
|
|
182
|
+
// 裁决已按 getAvailable 全等命中;find 独立实现(duck-typed mock 可能不同源)时的
|
|
183
|
+
// 类型收窄兜底,非预期路径。
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Model "${modelStr}" (${source}) passed the canonical ref check but registry.find missed it ` +
|
|
186
|
+
`(registry snapshot inconsistent). Retry with an exact entry from the available models list.`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (!registry.hasConfiguredAuth(model)) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Model "${modelStr}" (${source}) exists but auth is not configured. ` +
|
|
192
|
+
`Configure auth in models.json or switch to an authorized model.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
model,
|
|
197
|
+
// 无显式请求时兜底「模型最高可用档」(不落 pi 默认 medium)。
|
|
198
|
+
thinkingLevel: resolveThinkingLevel(model, requestedThinking ?? maxThinkingForModel(model)),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* subagent 默认 thinking:模型最高可用档。
|
|
204
|
+
*
|
|
205
|
+
* - thinkingLevelMap 配置了级别 → map 中最高档(模型显式配置 "max" 时即 max)
|
|
206
|
+
* - reasoning 但无 map → "xhigh"(pi 合法最高档;"max" 非 pi 合法值——
|
|
207
|
+
* spawn 侧 model:"max" 后缀会被 pi 判为非法 thinking 后缀导致 model 解析失败,
|
|
208
|
+
* 故无 map 时不能直接用 "max")
|
|
209
|
+
* - 非 reasoning 模型 → undefined(不支持 thinking)
|
|
210
|
+
*/
|
|
211
|
+
function maxThinkingForModel(
|
|
212
|
+
model: { reasoning: boolean; thinkingLevelMap?: Record<string, unknown> },
|
|
213
|
+
): string | undefined {
|
|
214
|
+
const levels = availableThinkingLevels(model);
|
|
215
|
+
if (levels.length > 0) return levels[levels.length - 1];
|
|
216
|
+
return model.reasoning ? "xhigh" : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 从 model.thinkingLevelMap 提取可用级别,clamp 到最高可用。
|
|
221
|
+
* model.reasoning === false → undefined(不支持 thinking)
|
|
222
|
+
*/
|
|
223
|
+
function resolveThinkingLevel(
|
|
224
|
+
model: { reasoning: boolean; thinkingLevelMap?: Record<string, unknown> },
|
|
225
|
+
requested?: string,
|
|
226
|
+
): string | undefined {
|
|
227
|
+
const levels = availableThinkingLevels(model);
|
|
228
|
+
if (levels.length === 0) return model.reasoning ? requested : undefined;
|
|
229
|
+
if (requested && levels.includes(requested)) return requested;
|
|
230
|
+
// requested 不可用 → 降级到最高可用
|
|
231
|
+
return levels[levels.length - 1];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 列出 model 实际支持的 thinking level(升序)。
|
|
236
|
+
*
|
|
237
|
+
* - model.reasoning === false → [] (不支持 thinking)
|
|
238
|
+
* - 无 thinkingLevelMap → [] (无级别信息,调用方按需透传)
|
|
239
|
+
* - 有 map → THINKING_ORDER 中 map[lvl] != null 的子集(保留升序)
|
|
240
|
+
*/
|
|
241
|
+
export function availableThinkingLevels(
|
|
242
|
+
model: { reasoning: boolean; thinkingLevelMap?: Record<string, unknown> },
|
|
243
|
+
): readonly string[] {
|
|
244
|
+
if (!model.reasoning) return [];
|
|
245
|
+
const map = model.thinkingLevelMap;
|
|
246
|
+
if (!map) return [];
|
|
247
|
+
return THINKING_ORDER.filter((lvl) => map[lvl] != null);
|
|
248
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// src/execution/notifier.ts
|
|
2
|
+
//
|
|
3
|
+
// Background 完成回注主对话。sync 不用(调用方还在 await,结果直接返回)。
|
|
4
|
+
//
|
|
5
|
+
// 职责(U2 后):
|
|
6
|
+
// - buildLlmContent:格式化通知文案(本文件唯一逻辑职责)
|
|
7
|
+
// - createNotifier:薄工厂——ledger 装配时(bindNotifyLedgerHost 已注入)走
|
|
8
|
+
// 四步生命周期(写账 → courier 边沿投递 → 回执销账 → notifyId 幂等重放,
|
|
9
|
+
// notify-ledger.ts);未装配时退回投递内核路径(createDelivery 经通知域窄端口
|
|
10
|
+
// NotifyDomainPorts 注入——core 依赖闭包不含 session-delivery,见 core/notify-ports.ts;
|
|
11
|
+
// 合并窗口 / 去重 / 退避 / flush 委托内核,旧装配 / 无 ledger 测试兼容)
|
|
12
|
+
//
|
|
13
|
+
// 投递通道(D5 单通道化):ledger 路径经 courier 在 settled 边沿直达
|
|
14
|
+
// pi.sendMessage({triggerTurn:true});内核路径的 port.send 同样只传
|
|
15
|
+
// {triggerTurn:true}——steer / followUp / nextTurn 通道已全部删除(nextTurn 的
|
|
16
|
+
// 唯一 drain 点在 session.prompt() 内,主 agent 长 streaming 场景下无限期滞留,
|
|
17
|
+
// 设计 D5 实测证伪);busy 场景由 ledger(settled 边沿 + isIdle 二次复查)或内核
|
|
18
|
+
// settled 订阅驱动在空闲边沿投递。
|
|
19
|
+
|
|
20
|
+
import { getLogger } from "../core/logger.ts";
|
|
21
|
+
import { getNotifyDomainPorts, type DeliveryHandle, type DeliveryPort } from "../core/notify-ports.ts";
|
|
22
|
+
|
|
23
|
+
import { deriveOutcome } from "./execution-record.ts";
|
|
24
|
+
import { getBoundNotifyLedger, NOTIFY_CUSTOM_TYPE } from "./notify-ledger.ts";
|
|
25
|
+
import type { ClosedReason, ExecutionOutcome } from "./types.ts";
|
|
26
|
+
|
|
27
|
+
/** U4:delivery warn 出口注入用——facade 同 component 同引用,与 index.ts 的
|
|
28
|
+
* getLogger("subagents") 共享单例;configureCore 后透明切换到宿主实现。 */
|
|
29
|
+
const notifyLogger = getLogger("subagents");
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 一条待发送的完成通知记录。
|
|
33
|
+
* SP-1: done/failed/crashed 合并为 closed + closedReason L2 子枚举。
|
|
34
|
+
*/
|
|
35
|
+
export interface BgNotifyRecord {
|
|
36
|
+
id: string;
|
|
37
|
+
/**
|
|
38
|
+
* 完成状态。v4 B-1 两态收敛:closed(终态,含 cancelled,closedReason 表达 L2 原因)
|
|
39
|
+
* 或 running(对话模式轮次完成,旧 idle 折入 running,携带本轮结果送回主 agent)。
|
|
40
|
+
* toNotifyRecord 守卫放行后经此联合穷尽。
|
|
41
|
+
*/
|
|
42
|
+
status: "running" | "closed";
|
|
43
|
+
/** L2 关闭原因子枚举(仅 status="closed" 时有意义)。内部诊断 + outcome 兑底派生输入。 */
|
|
44
|
+
closedReason?: ClosedReason;
|
|
45
|
+
/**
|
|
46
|
+
* 终态三态对外语义(U3 C-outcome)。notify() 投影边界物化:closed 入参缺省时按
|
|
47
|
+
* deriveOutcome(closedReason, error) 兑底填充(所有可达流程下与 completeRecord
|
|
48
|
+
* 冻结的 record.outcome 等价——toNotifyRecord 构造点在 completeRecord 之后;该
|
|
49
|
+
* 构造点属 U3 领地外,不透传本字段)。buildLlmContent 与 bg-notify-render 只读本字段。
|
|
50
|
+
*/
|
|
51
|
+
outcome?: ExecutionOutcome;
|
|
52
|
+
agent: string;
|
|
53
|
+
/** 执行所用 model(RecordSnapshot.model),用于完成通知显示。 */
|
|
54
|
+
model?: string;
|
|
55
|
+
result?: string;
|
|
56
|
+
error?: string;
|
|
57
|
+
startedAt: number;
|
|
58
|
+
endedAt: number | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* 对话轮次计数(仅 idle 有意义)。dedup key 按 `id:round` 去重——对话模式每轮 round
|
|
61
|
+
* 不同,60s 内多轮不被吞;非 chatMode round 恒定(0/undefined),key 同旧 id 行为不变。
|
|
62
|
+
*/
|
|
63
|
+
round?: number;
|
|
64
|
+
/** [MF#1] worktree 模式下子 agent 改动的 patch 路径(worktree 外,cleanup 后留存)。
|
|
65
|
+
* done 时通知文本显式提示 `git apply`,否则 background 子 agent 在隔离 worktree 的改动
|
|
66
|
+
* 会静默丢失——父 LLM 不知 patch 路径,无法应用。 */
|
|
67
|
+
patchFile?: string;
|
|
68
|
+
/** [wave2] 子 agent session 文件路径(增量语义的全文恢复通道)。
|
|
69
|
+
* 仅 chatMode 透传(toNotifyRecord 条件透传)——轮次/完成通知末尾追加
|
|
70
|
+
* "Full transcript: <path>" 指针行,父 LLM 可按需读全文;one-shot 不透传,
|
|
71
|
+
* 通知输出逐字节不变。缺失时 buildLlmContent 省略整行。 */
|
|
72
|
+
sessionFile?: string;
|
|
73
|
+
/** [U2] 通知身份键(投影边界物化 = dedupe key:`id` / `id:round`)。账本条目 /
|
|
74
|
+
* 回执匹配 / 幂等去重共用——details 携带(不进文案,G4 字节锁定不受影响),
|
|
75
|
+
* 重复注入条目凭此可识别为同一条(G2 at-least-once 幂等键)。 */
|
|
76
|
+
notifyId?: string;
|
|
77
|
+
/** [C-2] close 终态通知的轮次统计(文案 "completed after N rounds." 用)。
|
|
78
|
+
* 仅 chatMode close 语义(notifyClosed)构造时携带——此时 dedup 身份 round 已被
|
|
79
|
+
* 置 undefined(与轮次通知的 id:round key 区分,终态不被吞),轮数改由本字段进
|
|
80
|
+
* 文案。one-shot 完成通知不设置,文案保持 "completed. Result:" 逐字节(G4)。 */
|
|
81
|
+
totalRounds?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** notifier 依赖的宿主最小接口(解耦,便于测试)。
|
|
85
|
+
* 迁移后:仅用于构造 DeliveryPort 的底层依赖。 */
|
|
86
|
+
export interface NotifierHost {
|
|
87
|
+
/** 注入消息到主对话(U2 单通道:options 只接受 triggerTurn——多通道投递已删)。 */
|
|
88
|
+
sendMessage(
|
|
89
|
+
message: { customType: string; content: string; display: boolean; details?: unknown },
|
|
90
|
+
options?: { triggerTurn?: boolean },
|
|
91
|
+
): void;
|
|
92
|
+
/** 是否还有 running 的 background 任务(用于滑动窗口立即 flush 判断)。 */
|
|
93
|
+
hasRunningBackground(): boolean;
|
|
94
|
+
/** 主 agent 是否空闲(非 streaming)。可选:未注入时 flush 不 gate。 */
|
|
95
|
+
isIdle?: () => boolean;
|
|
96
|
+
/**
|
|
97
|
+
* 原生 settled 事件订阅能力(D8):注册 handler 监听主 agent settled 边沿。
|
|
98
|
+
* 无退订语义(pi 0.84.1 的 `pi.on(...)` 全部重载返回 void、无 off——实装锚点:
|
|
99
|
+
* node_modules `@earendil-works/pi-coding-agent` dist/core/extensions/types.d.ts
|
|
100
|
+
* `on()` 系列重载,0.84.1 实测)——退订由本模块的 disposed
|
|
101
|
+
* 标志包装兑现(见下方 port 装配)。可选:未注入时内核退化退避轮询(busy 消息
|
|
102
|
+
* 靠退避达上限强发,不走 settled 边沿驱动)。
|
|
103
|
+
*/
|
|
104
|
+
onAgentSettled?(handler: () => void): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 将 BgNotifyRecord 格式化为 LLM 可读的 notification content。
|
|
109
|
+
*
|
|
110
|
+
* 模块内唯一消费方是下方 createNotifier 的 notify()(预格式化后传 delivery.send,
|
|
111
|
+
* 内核只做拼接——多条以 "\n\n---\n\n" join)。
|
|
112
|
+
*/
|
|
113
|
+
function buildLlmContent(record: BgNotifyRecord): string {
|
|
114
|
+
const agent = record.agent;
|
|
115
|
+
const id = record.id;
|
|
116
|
+
// [wave2 指针行] 增量语义下轮次通知只携带本轮增量,异步 flush 窗口丢失时不可重发
|
|
117
|
+
//(见 subagent-service.ts onRoundSettled 注释),恢复通道是 session 文件全文。
|
|
118
|
+
// 仅 chatMode 透传 sessionFile,one-shot 通知不含该行;cancelled/gc-failed 不追加
|
|
119
|
+
//(无成功结果可读,指针无意义);缺失时省略整行(追加空串 = 输出逐字节不变)。
|
|
120
|
+
const transcriptPointer = record.sessionFile
|
|
121
|
+
? `\n\nFull transcript: ${record.sessionFile}`
|
|
122
|
+
: "";
|
|
123
|
+
switch (record.status) {
|
|
124
|
+
case "closed": {
|
|
125
|
+
// U3 C-outcome:终态文案只读 outcome(notify() 投影边界已物化;?? 兑底为防御
|
|
126
|
+
// 完整性——单一权威函数,非同构重写)。判定先于 patchFile——失败轮也会写
|
|
127
|
+
// patchFile(doFinalizeRecord Step 0 对 worktreeHandle 无条件 collectPatch),
|
|
128
|
+
// failed 分支不展示 patch 提示,否则 worktree 失败并存时 LLM 被告知 completed
|
|
129
|
+
// (历史 bug 存档见 deriveOutcome 注释)。
|
|
130
|
+
const outcome = record.outcome ?? deriveOutcome(record.closedReason, record.error);
|
|
131
|
+
if (outcome === "cancelled") {
|
|
132
|
+
return `Subagent "${agent}" (${id}) cancelled.`;
|
|
133
|
+
}
|
|
134
|
+
if (outcome === "failed") {
|
|
135
|
+
return `Subagent "${agent}" (${id}) failed: ${record.error}`;
|
|
136
|
+
}
|
|
137
|
+
// 成功完成或通用结束:展示结果。
|
|
138
|
+
// [C-2] chatMode close 终态通知附轮次统计(设计 D2 路径①"completed after N rounds")。
|
|
139
|
+
// totalRounds 仅 close 语义携带(notifyClosed);one-shot 完成通知不设置(round 无轮次
|
|
140
|
+
// 语义),文案保持 "completed. Result:" 逐字节(G4 硬约束,one-shot 字节锁测试锚定)。
|
|
141
|
+
const roundsSuffix =
|
|
142
|
+
record.totalRounds != null && record.totalRounds > 0
|
|
143
|
+
? ` after ${record.totalRounds} round${record.totalRounds === 1 ? "" : "s"}`
|
|
144
|
+
: "";
|
|
145
|
+
const base = `Subagent "${agent}" (${id}) completed${roundsSuffix}. Result:\n${record.result ?? "(empty)"}`;
|
|
146
|
+
if (record.patchFile) {
|
|
147
|
+
// [wave2 review] 长 return 拆行:模板串内不可直接换行(会改变输出内容),提取 patchHint 中转变量
|
|
148
|
+
const patchHint = `\n\nThis subagent ran in an isolated worktree; its file changes were captured as a patch:\n ${record.patchFile}\nTo bring these changes into the current repo, run: \`git apply ${record.patchFile}\``;
|
|
149
|
+
return `${base}${patchHint}${transcriptPointer}`;
|
|
150
|
+
}
|
|
151
|
+
return `${base}${transcriptPointer}`;
|
|
152
|
+
}
|
|
153
|
+
case "running":
|
|
154
|
+
// v4 B-1: 对话模式轮次完成(旧 idle 折入 running):携带本轮结果送回主 agent,等待下一轮 message。
|
|
155
|
+
return `Subagent "${agent}" (${id}) finished a round. Reply:\n${record.result ?? "(empty)"}${transcriptPointer}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Background 完成通知器接口(迁移后由内核实现)。
|
|
161
|
+
*
|
|
162
|
+
* 保持与旧 BgNotifier 类相同的公共 API,避免消费方(subagent-service)改动面过大。
|
|
163
|
+
*/
|
|
164
|
+
export interface BgNotifier {
|
|
165
|
+
/** 入队一条完成通知(去重 + 合批窗口合并)。dispose 后短路。 */
|
|
166
|
+
notify(record: BgNotifyRecord): void;
|
|
167
|
+
/** 立即 flush(session_shutdown 调用,防丢失)。 */
|
|
168
|
+
flushPendingNotifications(): void;
|
|
169
|
+
/** session 结束:清队列,dispose 内核 handle。 */
|
|
170
|
+
dispose(): void;
|
|
171
|
+
/** /resume /fork /new 后复活。 */
|
|
172
|
+
revive(): void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 投递工厂缺席降级(NotifyDomainPorts.createDelivery === undefined)时的最小直发
|
|
177
|
+
* handle:send 直接调 port.send,无 gate / 合批窗口 / dedupe / 退避。
|
|
178
|
+
*
|
|
179
|
+
* 缺席形态 = 宿主漏配通知域窄端口(生产接线在 extension 入口 configureNotifyDomain,
|
|
180
|
+
* 见 index.ts——正常不可达)或测试未装配。降级是「消息仍能发出」的安全侧退化:
|
|
181
|
+
* 丢失的只是 busy 场景的投递时机治理(isIdle gate 由 port.send 装配层兜底语义吸收)。
|
|
182
|
+
*/
|
|
183
|
+
function createDirectSendHandle(port: DeliveryPort): DeliveryHandle {
|
|
184
|
+
return {
|
|
185
|
+
send: (msg) => {
|
|
186
|
+
// D5 单通道下 port.send 装配忽略 intent 参数;传 notifier 装配的唯一 intent
|
|
187
|
+
// 与工厂配置保持语义一致。回执(receipt)为扩展位,直发路径不消费。
|
|
188
|
+
void port.send(msg, "interrupt-at-turn-boundary");
|
|
189
|
+
},
|
|
190
|
+
flush: () => {}, // 无队列(每条 send 即投),无待 flush 内容
|
|
191
|
+
dispose: () => {}, // 无 timer / 订阅需清理
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 创建 Background 完成通知器(薄工厂)。
|
|
197
|
+
*
|
|
198
|
+
* ledger 装配时(bindNotifyLedgerHost):四步生命周期接线(写账 → courier 边沿
|
|
199
|
+
* 投递 → 回执销账 → notifyId 幂等,见 notify-ledger.ts)。未装配时:装配投递内核
|
|
200
|
+
* (createDelivery 经 NotifyDomainPorts 注入,缺席降级直发见 createDirectSendHandle)
|
|
201
|
+
* ——gate(isIdle 退避)/ 合批窗口(滑动 60s)/
|
|
202
|
+
* dedup(按 id:round)/ flush / shutdown flush 均委托内核。
|
|
203
|
+
* 本函数职责:BgNotifyRecord → 预格式化 content + notifyId 物化。
|
|
204
|
+
*
|
|
205
|
+
* @param host 宿主能力注入(pi.sendMessage + hasRunningBackground + isIdle)
|
|
206
|
+
* @returns BgNotifier 接口(与旧类同形)
|
|
207
|
+
*/
|
|
208
|
+
export function createNotifier(host: NotifierHost): BgNotifier {
|
|
209
|
+
let disposed = false;
|
|
210
|
+
|
|
211
|
+
// 构造 DeliveryPort:intent → pi 参数翻译在适配器内(D3)
|
|
212
|
+
const port: DeliveryPort = {
|
|
213
|
+
supportedPayloads: ["custom"],
|
|
214
|
+
isIdle: () => {
|
|
215
|
+
if (host.isIdle) {
|
|
216
|
+
return host.isIdle();
|
|
217
|
+
}
|
|
218
|
+
// 未注入 isIdle → 视为 idle(不 gate,向后兼容旧 host)
|
|
219
|
+
return true;
|
|
220
|
+
},
|
|
221
|
+
hasPendingMessages: () => false, // notifier 不关心 hasPendingMessages
|
|
222
|
+
// D8(must-fix #4):settled 边沿驱动装配——内核 busy 入队后由 settled 事件唤醒
|
|
223
|
+
// flush(watch-dog 兜底事件丢失),替代无订阅时的退避轮询。host 只注入原生订阅
|
|
224
|
+
// 能力;disposed 标志包装(兑现退订语义——pi.on 返回 void 且无 off)在此完成。
|
|
225
|
+
subscribeSettled:
|
|
226
|
+
host.onAgentSettled === undefined
|
|
227
|
+
? undefined
|
|
228
|
+
: (cb) => {
|
|
229
|
+
let disposed = false;
|
|
230
|
+
host.onAgentSettled?.(() => {
|
|
231
|
+
if (!disposed) cb();
|
|
232
|
+
});
|
|
233
|
+
return () => {
|
|
234
|
+
disposed = true;
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
send: (msg, _intent) => {
|
|
238
|
+
// D5 单通道:投递意图唯一化——steer/followUp/nextTurn 多通道全部删除,
|
|
239
|
+
// 唯一发送形态 = sendCustomMessage({triggerTurn:true})。busy 场景由 ledger
|
|
240
|
+
//(settled 边沿 + isIdle 二次复查)或内核 settled 订阅在空闲边沿驱动,
|
|
241
|
+
// 不在此层分流。返回 void = 受理成功(同步无异常;SendReceipt 扩展位留待
|
|
242
|
+
// 升级方接入)。
|
|
243
|
+
host.sendMessage(
|
|
244
|
+
{
|
|
245
|
+
customType: NOTIFY_CUSTOM_TYPE,
|
|
246
|
+
content: msg.payload.content,
|
|
247
|
+
display: true,
|
|
248
|
+
details: msg.payload.kind === "custom" ? msg.payload.details : undefined,
|
|
249
|
+
},
|
|
250
|
+
{ triggerTurn: true },
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// #5(must-fix):内核 handle 的 disposed 不可逆——dispose 后 revive 必须重建 handle,
|
|
256
|
+
// 否则 revive 后所有 notify() 被内核静默吞(外层标志复位救不回已销毁的内核)。
|
|
257
|
+
// revive = 新生命周期:合批窗口 / 在途批次 / dedup LRU 随重建自然复位(可接受)。
|
|
258
|
+
// 工厂经通知域窄端口解析(每次 createHandle 时取——与 facade logger 同款延迟解析,
|
|
259
|
+
// 宿主 configureNotifyDomain 的时序无关紧要);缺席降级直发并 warn 一次(per-notifier
|
|
260
|
+
// 一次:revive 重建 handle 不重复刷屏,新 session 新 notifier 再 warn 可接受)。
|
|
261
|
+
let directFallbackWarned = false;
|
|
262
|
+
const createHandle = (): DeliveryHandle => {
|
|
263
|
+
const factory = getNotifyDomainPorts().createDelivery;
|
|
264
|
+
if (factory === undefined) {
|
|
265
|
+
if (!directFallbackWarned) {
|
|
266
|
+
directFallbackWarned = true;
|
|
267
|
+
// 错误必须可操作:指出恢复动作(configureNotifyDomain 注入)与 pi 壳落点。
|
|
268
|
+
notifyLogger.warn(
|
|
269
|
+
"NotifyDomainPorts.createDelivery not configured - falling back to direct-send delivery " +
|
|
270
|
+
"(no idle gate / merge window / dedupe). Host must call configureNotifyDomain({ createDelivery }) " +
|
|
271
|
+
"during extension initialization (pi shell: createPiNotifyDomainPorts in src/host/pi-host.ts).",
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return createDirectSendHandle(port);
|
|
275
|
+
}
|
|
276
|
+
return factory(port, {
|
|
277
|
+
intent: "interrupt-at-turn-boundary", // D3:turn 边界抢占(F1 教训内化)
|
|
278
|
+
mergeWindowMs: 60_000, // 滑动窗口合批(继承 MERGE_WINDOW_MS=60s)
|
|
279
|
+
mergeHoldActive: () => host.hasRunningBackground(), // D4 must-fix #1:禁止用 isIdle 代替
|
|
280
|
+
busyPolicy: "retry-force", // settled 边沿驱动 + 退避达上限强发
|
|
281
|
+
backoff: { ms: 100, max: 50 }, // 继承 FLUSH_BACKOFF_MS/MAX
|
|
282
|
+
// dedup LRU:语义与旧 DEDUP_TTL_MS=60s **不同**——按 key 永久去重(仅 LRU 逐出后
|
|
283
|
+
// 同 key 可再入)。当前 key 空间(id / id:round,id 每 spawn 唯一)无实际差异;
|
|
284
|
+
// 后续复用方勿按「60s 内不重复」假设接入(同 key 通知会被永久吞)。
|
|
285
|
+
dedupe: { maxKeys: 1000 },
|
|
286
|
+
// U4 warn 出口参数化:内核投递失败警告接 core logger facade(pi 壳下经
|
|
287
|
+
// extension-logger appendEntry 落 session JSONL + XYZ_AGENT_DEBUG 落
|
|
288
|
+
// <dataDir>/logs/),不再走 console.warn(stderr tee 不到日志盘——排查无痕,
|
|
289
|
+
// 设计 §5 U4)。
|
|
290
|
+
warn: (msg, err) => notifyLogger.warn(msg, err),
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
let handle: DeliveryHandle = createHandle();
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
notify(record: BgNotifyRecord): void {
|
|
297
|
+
if (disposed) return;
|
|
298
|
+
|
|
299
|
+
// U3 C-outcome:投影边界物化 outcome——closed payload 缺省时按单一权威
|
|
300
|
+
// deriveOutcome 兑底填充,content 与 details(GUI pane 消费)均携带一等 outcome;
|
|
301
|
+
// 所有可达流程下与 record.outcome 等价(toNotifyRecord 在 completeRecord 之后
|
|
302
|
+
// 构造)。running(轮次通知)语义上无 outcome,不物化。消源自 record 的浅拷贝
|
|
303
|
+
// ——不改写入方对象(BgNotifyRecord 由调用方持有)。notifyId 同批物化(U2:
|
|
304
|
+
// dedupe key 与账本身份键同源,details 携带供回执匹配)。
|
|
305
|
+
const notifyId = record.round != null ? `${record.id}:${record.round}` : record.id;
|
|
306
|
+
const payload: BgNotifyRecord =
|
|
307
|
+
record.status === "closed"
|
|
308
|
+
? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error), notifyId }
|
|
309
|
+
: { ...record, notifyId };
|
|
310
|
+
|
|
311
|
+
const content = buildLlmContent(payload);
|
|
312
|
+
|
|
313
|
+
// U2 B-ledger 四步接线(设计 D4/D5):账本装配时①写账(appendEntry 先于一切
|
|
314
|
+
// 投递尝试)→ ②courier 边沿投递(settled 边沿 + 120s 看门狗 + isIdle 二次
|
|
315
|
+
// 复查)→ ③回执销账 / ④重放幂等均在 ledger 内。record 返回 false = 同
|
|
316
|
+
// notifyId 已在账或已销账(幂等去重,含重启恢复后的已送达账号零重发)。
|
|
317
|
+
const ledger = getBoundNotifyLedger();
|
|
318
|
+
if (ledger) {
|
|
319
|
+
if (!ledger.record(notifyId, content, payload)) return;
|
|
320
|
+
ledger.attemptDeliver();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 无 ledger 装配(旧装配 / 部分测试):内核路径——合批窗口 / settled 边沿 /
|
|
325
|
+
// dedupe(按 notifyId,key 规则与旧 dedupeKey 一致:id 或 id:round)不变。
|
|
326
|
+
// [C-ext-06 配套] 降级留痕:bind 缺失(含 jiti 单例分裂致跨模块读不到绑定的
|
|
327
|
+
// 失效形态)本是无声分岔,U2 at-least-once 在此退化为内核路径——warn 一条
|
|
328
|
+
// 供诊断检索,不改变向后兼容行为。
|
|
329
|
+
notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
|
|
330
|
+
notifyId,
|
|
331
|
+
});
|
|
332
|
+
handle.send({
|
|
333
|
+
payload: {
|
|
334
|
+
kind: "custom",
|
|
335
|
+
customType: NOTIFY_CUSTOM_TYPE,
|
|
336
|
+
content,
|
|
337
|
+
display: true,
|
|
338
|
+
details: payload,
|
|
339
|
+
},
|
|
340
|
+
dedupeKey: notifyId,
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
flushPendingNotifications(): void {
|
|
345
|
+
// ledger 路径:立即投递尝试(isIdle 复查,busy 则挂 pending 等边沿——账已落盘,
|
|
346
|
+
// 重启恢复兑底);内核路径:flush。
|
|
347
|
+
const ledger = getBoundNotifyLedger();
|
|
348
|
+
if (ledger) {
|
|
349
|
+
ledger.attemptDeliver();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
handle.flush();
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
dispose(): void {
|
|
356
|
+
disposed = true;
|
|
357
|
+
handle.dispose();
|
|
358
|
+
// ledger 销毁(清看门狗 + 摘模块级绑定;settled 回调由 disposed 标志静默)。
|
|
359
|
+
// 未 bind 时 no-op。bind 归 index.ts session_start 装配,对称免受。
|
|
360
|
+
getBoundNotifyLedger()?.dispose();
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
revive(): void {
|
|
364
|
+
disposed = false;
|
|
365
|
+
// #5:内核 disposed 不可逆——revive 必须重建 handle(/resume /fork /new 后的
|
|
366
|
+
// session_start 时序:disposeAll → initSession → revive)。旧 handle 若仍存活
|
|
367
|
+
// (首次 session_start 的 revive)dispose 为幂等清理,无副作用。
|
|
368
|
+
handle.dispose();
|
|
369
|
+
handle = createHandle();
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|