@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,144 @@
|
|
|
1
|
+
// src/execution/channel-registry-access.ts
|
|
2
|
+
//
|
|
3
|
+
// Channel registry 的访问入口(本扩展内部用 + 契约文档化导出)。
|
|
4
|
+
//
|
|
5
|
+
// ask-user 等扩展通过本模块注册 channel handler,让 subagent 子进程的 UI 请求
|
|
6
|
+
// 能透传到主进程渲染。所有扩展各自直接读写
|
|
7
|
+
// globalThis[Symbol.for("@zhushanwen/pi-subagents.channelHandshake")],
|
|
8
|
+
// 拿到结构兼容 ChannelRegistryHandshake 的同一握手对象。
|
|
9
|
+
//
|
|
10
|
+
// ── 升级说明(决策 D 修复 M4)─────────────────────────────────
|
|
11
|
+
// 历史问题:原协议槽位直接存 UiChannelRegistry 实例,谁先 session_start 谁创建。
|
|
12
|
+
// ask-user 先到时会自建简化 registry 占位,劫持 canonical 槽位,subagent-workflow
|
|
13
|
+
// 后到时拿到的是 ask-user 的非 canonical 实例,行为不一致。
|
|
14
|
+
//
|
|
15
|
+
// 修复:槽位从「存 registry 实例」升级为「存握手对象 {version, registry?, pending[]}」。
|
|
16
|
+
// canonical registry 实例永远由 subagent-workflow 单一创建点(本模块的
|
|
17
|
+
// getOrCreateChannelRegistry)实例化。ask-user 先到时只往 pending 推 handler,
|
|
18
|
+
// 等 subagent-workflow 来 flush;subagent-workflow 先到时直接创建 registry 并
|
|
19
|
+
// 接收后续 ask-user 推入的 pending。
|
|
20
|
+
//
|
|
21
|
+
// ── 跨扩展协议契约(必须与 ask-user 侧严格一致)──────────────
|
|
22
|
+
// key 字面量: "@zhushanwen/pi-subagents.channelHandshake"
|
|
23
|
+
// ↑ 必须与 ask-user/src/channel-registry-register.ts 完全一致
|
|
24
|
+
// handshake 形状: ChannelRegistryHandshake(version=1)
|
|
25
|
+
// version 守卫: slot.version !== 1 时 console.warn + 丢弃重建(向前兼容未来升级)
|
|
26
|
+
|
|
27
|
+
import { getLogger } from "../core/logger.ts";
|
|
28
|
+
|
|
29
|
+
import { createUiChannelRegistry, type UiChannelRegistry, type ChannelHandler } from "./ui-channels.ts";
|
|
30
|
+
|
|
31
|
+
const logger = getLogger("subagents");
|
|
32
|
+
|
|
33
|
+
/** 进程级 channel 握手的 globalThis key(Symbol.for 跨模块共享)。
|
|
34
|
+
*
|
|
35
|
+
* **协议契约**:字面量 `"@zhushanwen/pi-subagents.channelHandshake"` 必须与
|
|
36
|
+
* ask-user 扩展的 `ask-user/src/channel-registry-register.ts` 完全一致——
|
|
37
|
+
* 两边读写同一个 Symbol.for key 才能拿到同一握手对象。
|
|
38
|
+
*
|
|
39
|
+
* 改名历史:原 `CHANNEL_REGISTRY_KEY`(字面量 `...channelRegistry`)在决策 D
|
|
40
|
+
* 中升级为 `CHANNEL_HANDSHAKE_KEY`(字面量 `...channelHandshake`),槽位形状
|
|
41
|
+
* 从 registry 实例改为握手对象。 */
|
|
42
|
+
export const CHANNEL_HANDSHAKE_KEY = Symbol.for("@zhushanwen/pi-subagents.channelHandshake");
|
|
43
|
+
|
|
44
|
+
/** 握手版本。未来若形状不兼容升级,递增此常量并在 getOrCreateChannelRegistry
|
|
45
|
+
* 的 version 守卫里加迁移逻辑。 */
|
|
46
|
+
const HANDSHAKE_VERSION = 1 as const;
|
|
47
|
+
|
|
48
|
+
/** pending channel handler 条目。ask-user 先到、registry 未就绪时临时存放。 */
|
|
49
|
+
export interface ChannelRegistryHandshakePendingEntry {
|
|
50
|
+
channel: string;
|
|
51
|
+
handler: ChannelHandler;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 跨扩展 channel registry 握手对象(globalThis 槽位形状)。
|
|
55
|
+
*
|
|
56
|
+
* - `version`:协议版本,当前固定 1。读取时若 ≠1 视为不兼容,丢弃重建。
|
|
57
|
+
* - `registry`:canonical UiChannelRegistry 实例。仅由 subagent-workflow 的
|
|
58
|
+
* getOrCreateChannelRegistry 创建并填充。未就绪时为 undefined。
|
|
59
|
+
* - `pending`:ask-user 在 registry 就绪前推入的 handler 条目。
|
|
60
|
+
* subagent-workflow 创建 registry 后逐条 flush(注册),然后清空。 */
|
|
61
|
+
export interface ChannelRegistryHandshake {
|
|
62
|
+
version: typeof HANDSHAKE_VERSION;
|
|
63
|
+
registry?: UiChannelRegistry;
|
|
64
|
+
pending: ChannelRegistryHandshakePendingEntry[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 读取握手槽位;若 version 不匹配则视为缺失(warn + 返回 undefined)。
|
|
68
|
+
* 类型不安全的 globalThis 反射访问集中在本函数,外层逻辑保持类型严谨。 */
|
|
69
|
+
function readHandshakeSlot(): ChannelRegistryHandshake | undefined {
|
|
70
|
+
const slot = Reflect.get(globalThis, CHANNEL_HANDSHAKE_KEY) as unknown;
|
|
71
|
+
if (slot === undefined) return undefined;
|
|
72
|
+
// 形状校验:必须是对象且 version===1,否则视为不兼容
|
|
73
|
+
if (typeof slot !== "object" || slot === null) {
|
|
74
|
+
logger.warn(
|
|
75
|
+
"[pi-subagent-workflow] channel handshake slot is not an object; discarding and recreating.",
|
|
76
|
+
);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
// 断言目标含必填 version 字段(object/null 已守卫;version 值本身在下一行校验),
|
|
80
|
+
// 对齐 format.ts extractAgentName 的必填字段断言先例,避免全可选断言零校验。
|
|
81
|
+
const version = (slot as { version: unknown }).version;
|
|
82
|
+
if (version !== HANDSHAKE_VERSION) {
|
|
83
|
+
logger.warn(
|
|
84
|
+
`[pi-subagent-workflow] channel handshake version mismatch (got ${String(
|
|
85
|
+
version,
|
|
86
|
+
)}, expected ${HANDSHAKE_VERSION}); discarding and recreating.`,
|
|
87
|
+
);
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
// version 正确,但 pending 可能被恶意/错误地塞了非数组;防御性处理
|
|
91
|
+
const candidate = slot as ChannelRegistryHandshake;
|
|
92
|
+
if (!Array.isArray(candidate.pending)) {
|
|
93
|
+
logger.warn(
|
|
94
|
+
"[pi-subagent-workflow] channel handshake pending is not an array; discarding and recreating.",
|
|
95
|
+
);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 将 pending 条目逐个注册进 registry,注册完清空 pending。
|
|
102
|
+
* pending 内部 entry 的形状由 ask-user 侧保证,此处不再二次校验。 */
|
|
103
|
+
function flushPending(registry: UiChannelRegistry, pending: ChannelRegistryHandshakePendingEntry[]): void {
|
|
104
|
+
for (const entry of pending) {
|
|
105
|
+
registry.register(entry.channel, entry.handler);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 获取或创建进程级 channel registry(canonical 单例)。
|
|
110
|
+
*
|
|
111
|
+
* **唯一创建点**:canonical UiChannelRegistry 实例仅由本函数创建。
|
|
112
|
+
* ask-user 扩展绝不自建 registry——它在 registry 未就绪时只往 slot.pending 推条目。
|
|
113
|
+
*
|
|
114
|
+
* 行为分支:
|
|
115
|
+
* 1. 槽位不存在或 version ≠ 1 → warn(version 不匹配时)+ 建新 slot
|
|
116
|
+
* `{version:1, pending:[], registry:<newly created>}`
|
|
117
|
+
* 2. 槽位存在但 registry 未就绪(ask-user 先到过,塞过 pending)→
|
|
118
|
+
* 创建 canonical registry + flush pending + 清空 pending
|
|
119
|
+
* 3. 槽位存在且 registry 已就绪(本函数已被调用过)→
|
|
120
|
+
* 直接返回同一实例引用(===),不重建、不重复 flush
|
|
121
|
+
*
|
|
122
|
+
* @returns 进程级 canonical UiChannelRegistry 单例(永不返回 undefined) */
|
|
123
|
+
export function getOrCreateChannelRegistry(): UiChannelRegistry {
|
|
124
|
+
let slot = readHandshakeSlot();
|
|
125
|
+
if (slot === undefined) {
|
|
126
|
+
// 分支 1:无合规槽位,新建并立即创建 canonical registry
|
|
127
|
+
const registry = createUiChannelRegistry();
|
|
128
|
+
slot = { version: HANDSHAKE_VERSION, registry, pending: [] };
|
|
129
|
+
Reflect.set(globalThis, CHANNEL_HANDSHAKE_KEY, slot);
|
|
130
|
+
return registry;
|
|
131
|
+
}
|
|
132
|
+
if (slot.registry === undefined) {
|
|
133
|
+
// 分支 2:ask-user 先到过,flush pending 进 canonical registry
|
|
134
|
+
const registry = createUiChannelRegistry();
|
|
135
|
+
flushPending(registry, slot.pending);
|
|
136
|
+
slot.pending = [];
|
|
137
|
+
slot.registry = registry;
|
|
138
|
+
return registry;
|
|
139
|
+
}
|
|
140
|
+
// 分支 3:registry 已就绪,返回同一实例
|
|
141
|
+
return slot.registry;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type { UiChannelRegistry, ChannelHandler };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/core/concurrency-pool.ts
|
|
2
|
+
//
|
|
3
|
+
// 并发控制 + 优先级排队。background=1000(单一优先级,保留 priority 机制供未来扩展)。
|
|
4
|
+
|
|
5
|
+
/** 队列条目:优先级 + resolver + rejecter + 入队序号(同优先级 FIFO)。 */
|
|
6
|
+
interface QueueEntry {
|
|
7
|
+
priority: number;
|
|
8
|
+
resolve: () => void;
|
|
9
|
+
reject: (err: Error) => void;
|
|
10
|
+
seq: number;
|
|
11
|
+
/** abort listener(用于 resolve/abort 时清理)。 */
|
|
12
|
+
onAbort?: () => void;
|
|
13
|
+
/** 关联的 signal(用于 resolve 时 removeEventListener)。 */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 并发池接口(可注入,便于测试 mock)。 */
|
|
18
|
+
export interface ConcurrencyPool {
|
|
19
|
+
/** 按优先级排队获取槽位(0=最高)。可选 effectiveMaxConcurrent 覆盖实例级默认配额。可选 AbortSignal 在 abort 时 reject 排队条目。 */
|
|
20
|
+
acquire(priority: number, effectiveMaxConcurrent?: number, signal?: AbortSignal): Promise<void>;
|
|
21
|
+
/** 归还槽位。必须无条件执行(finally)。 */
|
|
22
|
+
release(): void;
|
|
23
|
+
/** 当前已占用槽位数(诊断/widget 用)。 */
|
|
24
|
+
readonly active: number;
|
|
25
|
+
/** 实例级最大并发配额。调用方可据此计算分层配额(max(1, maxConcurrent - depth))。 */
|
|
26
|
+
readonly maxConcurrent: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 默认实现:maxConcurrent 槽位 + 优先级队列。
|
|
31
|
+
*
|
|
32
|
+
* acquire(priority, effectiveMaxConcurrent?):
|
|
33
|
+
* effective = effectiveMaxConcurrent ?? maxConcurrent
|
|
34
|
+
* active < effective → active++, resolve
|
|
35
|
+
* 否则 → 入队 { priority, resolve, seq }, 队列按 priority 升序 + seq FIFO
|
|
36
|
+
*
|
|
37
|
+
* release():
|
|
38
|
+
* queue 非空 → 出队最高优先级 resolve(active 不变)
|
|
39
|
+
* queue 空 → active--(防下溢)
|
|
40
|
+
*/
|
|
41
|
+
export class DefaultConcurrencyPool implements ConcurrencyPool {
|
|
42
|
+
private _active = 0;
|
|
43
|
+
private readonly queue: QueueEntry[] = [];
|
|
44
|
+
private seqCounter = 0;
|
|
45
|
+
|
|
46
|
+
/** 下限 1——maxConcurrent=0 会让 acquire 永久排队死锁(C3 修复)。 */
|
|
47
|
+
readonly maxConcurrent: number;
|
|
48
|
+
|
|
49
|
+
constructor(maxConcurrent: number) {
|
|
50
|
+
this.maxConcurrent = Math.max(1, maxConcurrent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
acquire(priority: number, effectiveMaxConcurrent?: number, signal?: AbortSignal): Promise<void> {
|
|
54
|
+
// effectiveMaxConcurrent 覆盖实例级默认配额(分层配额:调用方传 max(1, maxConcurrent - depth))。
|
|
55
|
+
// 不修改实例级 maxConcurrent——实例配额是全局共享上限,分层配额是本次 acquire 的局部上限。
|
|
56
|
+
const effective = effectiveMaxConcurrent ?? this.maxConcurrent;
|
|
57
|
+
if (this._active < effective) {
|
|
58
|
+
this._active += 1;
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
return new Promise<void>((resolve, reject) => {
|
|
62
|
+
const entry: QueueEntry = { priority, resolve, reject, seq: this.seqCounter++ };
|
|
63
|
+
// H2: abort 时 reject 排队条目并从 queue 移除,防止永久挂起
|
|
64
|
+
if (signal) {
|
|
65
|
+
if (signal.aborted) {
|
|
66
|
+
// S1: abort reject 需带 name="AbortError",对齐包内 AbortError 错误语义约定(消费方按 err.name 判别)
|
|
67
|
+
const err = new Error("acquire aborted");
|
|
68
|
+
err.name = "AbortError";
|
|
69
|
+
reject(err);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
entry.signal = signal;
|
|
73
|
+
entry.onAbort = (): void => {
|
|
74
|
+
const idx = this.queue.indexOf(entry);
|
|
75
|
+
if (idx >= 0) {
|
|
76
|
+
this.queue.splice(idx, 1);
|
|
77
|
+
// S1: abort reject 需带 name="AbortError",与 pre-aborted 分支一致
|
|
78
|
+
const err = new Error("acquire aborted");
|
|
79
|
+
err.name = "AbortError";
|
|
80
|
+
reject(err);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
signal.addEventListener("abort", entry.onAbort, { once: true });
|
|
84
|
+
}
|
|
85
|
+
this.queue.push(entry);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
release(): void {
|
|
90
|
+
if (this.queue.length > 0) {
|
|
91
|
+
// 取优先级最高(priority 最小)的;同优先级 FIFO(seq 最小)
|
|
92
|
+
let bestIdx = 0;
|
|
93
|
+
for (let i = 1; i < this.queue.length; i++) {
|
|
94
|
+
const cur = this.queue[i];
|
|
95
|
+
const best = this.queue[bestIdx];
|
|
96
|
+
if (cur.priority < best.priority || (cur.priority === best.priority && cur.seq < best.seq)) {
|
|
97
|
+
bestIdx = i;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const next = this.queue.splice(bestIdx, 1)[0];
|
|
101
|
+
// H2: 条目已获槽位——移除 abort listener(防 listener 泄漏到长生命周期 signal)
|
|
102
|
+
if (next.onAbort && next.signal) {
|
|
103
|
+
next.signal.removeEventListener("abort", next.onAbort);
|
|
104
|
+
}
|
|
105
|
+
next.resolve();
|
|
106
|
+
// active 不变(一个离开队列立即进入活跃)
|
|
107
|
+
} else if (this._active > 0) {
|
|
108
|
+
// 防御性下界:release 调用次数多于 acquire 时不让 active 为负
|
|
109
|
+
this._active -= 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get active(): number {
|
|
114
|
+
return this._active;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/runtime/config/config.ts
|
|
2
|
+
//
|
|
3
|
+
// 全局配置(~/.pi/agent/subagents/config.json)。
|
|
4
|
+
// 仅保留 maxConcurrent(pool 大小)。模型解析已退化为「主 agent model 优先」,
|
|
5
|
+
// 不再有 category/fallback/session 级覆盖——相关字段读取时忽略。
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
import { getLogger } from "../core/logger.ts";
|
|
11
|
+
|
|
12
|
+
import type { SubagentsGlobalConfig } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
// 包内 execution 模块统一具名 logger(getLogger 缓存单例,见 notify-ledger.ts 同款)。
|
|
15
|
+
const logger = getLogger("subagents");
|
|
16
|
+
|
|
17
|
+
// ============================================================
|
|
18
|
+
// 常量
|
|
19
|
+
// ============================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 开箱默认配置(单一真相源,内联在代码里)。
|
|
23
|
+
*
|
|
24
|
+
* 历史教训 [HISTORICAL]:曾用包内 config.json(与 src/ 同级)作为默认值源,
|
|
25
|
+
* 但 config.json 被 .gitignore 排除且不应随 npm 包分发用户私有配置——导致
|
|
26
|
+
* npm pack 后读不到文件,catch 兜底用空字段,pi install 后首次执行抛错。
|
|
27
|
+
* 修复:默认值内联在代码里,不依赖任何包内文件。
|
|
28
|
+
*
|
|
29
|
+
* export 供守护测试断言 package.json startupConfig 声明与此深相等(防漂移)。
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_CONFIG: SubagentsGlobalConfig = {
|
|
32
|
+
version: 1,
|
|
33
|
+
maxConcurrent: 6,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** 默认 maxConcurrent(DEFAULT_CONFIG 的镜像,sanitize 用)。 */
|
|
37
|
+
const DEFAULT_MAX_CONCURRENT = 6;
|
|
38
|
+
// ============================================================
|
|
39
|
+
// 路径
|
|
40
|
+
// ============================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 配置文件路径(<agentDir>/subagents/config.json)。
|
|
44
|
+
* agentDir 由 Pi 核心 getAgentDir() 决定(读 PI_CODING_AGENT_DIR,默认 ~/.pi/agent),
|
|
45
|
+
* 与 Pi 主进程的目录约定完全一致——支持宿主经环境变量整体重定向。
|
|
46
|
+
*/
|
|
47
|
+
export function getGlobalConfigPath(agentDir: string): string {
|
|
48
|
+
return path.join(agentDir, "subagents", "config.json");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================
|
|
52
|
+
// 全局配置加载
|
|
53
|
+
// ============================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 加载全局配置。文件不存在 / JSON 解析失败 / 字段缺失时返回默认配置。
|
|
57
|
+
* 旧 config.json 中的 categories/fallback/yoloByDefault 等字段读取时忽略
|
|
58
|
+
* (模型解析已退化为「主 agent model 优先」)。
|
|
59
|
+
*
|
|
60
|
+
* 注意:本函数是「静默回落」形态(ENOENT 与坏 JSON 同归默认值,历史行为保持
|
|
61
|
+
* 不动)。需要区分三态的调用方(引擎感知检测器,设计 D5)用 readGlobalConfig。
|
|
62
|
+
*/
|
|
63
|
+
export function loadGlobalConfig(agentDir: string): SubagentsGlobalConfig {
|
|
64
|
+
try {
|
|
65
|
+
const raw = fs.readFileSync(getGlobalConfigPath(agentDir), "utf-8");
|
|
66
|
+
const parsed = JSON.parse(raw) as Partial<SubagentsGlobalConfig>;
|
|
67
|
+
return sanitizeParsedConfig(parsed);
|
|
68
|
+
} catch {
|
|
69
|
+
return { ...DEFAULT_CONFIG };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============================================================
|
|
74
|
+
// 三态读取(设计 D5:明确值 / 明确缺省 / 读失败)
|
|
75
|
+
// ============================================================
|
|
76
|
+
|
|
77
|
+
/** 三态读取结果(判别联合,调用方按 status 分派)。 */
|
|
78
|
+
export type GlobalConfigReadResult =
|
|
79
|
+
/** 读到明确值:JSON 可解析,字段已经 sanitize。 */
|
|
80
|
+
| { status: "ok"; config: SubagentsGlobalConfig }
|
|
81
|
+
/** 明确缺省:文件不存在(ENOENT)。这是用户意图(删配置切回缺省 pi),不是故障。 */
|
|
82
|
+
| { status: "absent"; config: SubagentsGlobalConfig }
|
|
83
|
+
/** 读失败:坏 JSON / 权限等。携带原始错误消息供诊断;调用方保持 lastEngine 不动。 */
|
|
84
|
+
| { status: "failed"; reason: string };
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 三态读取全局配置(设计 D5)。
|
|
88
|
+
*
|
|
89
|
+
* 为什么不用 loadGlobalConfig:后者 ENOENT 与坏 JSON 同归 DEFAULT_CONFIG(静默
|
|
90
|
+
* 回落),检测器若消费该形态会把「用户删配置切回缺省」(合法变更,须生效)误判成
|
|
91
|
+
* 「读失败」(须保持 lastEngine),也会把坏 JSON(torn write)误判成合法缺省——
|
|
92
|
+
* 两个方向都会产生错误的引擎切换信号。
|
|
93
|
+
*/
|
|
94
|
+
export function readGlobalConfig(agentDir: string): GlobalConfigReadResult {
|
|
95
|
+
const configPath = getGlobalConfigPath(agentDir);
|
|
96
|
+
let raw: string;
|
|
97
|
+
try {
|
|
98
|
+
raw = fs.readFileSync(configPath, "utf-8");
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// ENOENT 单独成态:subagents/ 目录不存在同样意味着没有配置文件,语义等同
|
|
101
|
+
if (errnoCodeOf(err) === "ENOENT") {
|
|
102
|
+
return { status: "absent", config: { ...DEFAULT_CONFIG } };
|
|
103
|
+
}
|
|
104
|
+
return readFailure(configPath, err);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(raw) as Partial<SubagentsGlobalConfig>;
|
|
108
|
+
return { status: "ok", config: sanitizeParsedConfig(parsed) };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return readFailure(configPath, err);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 读失败统一出口:落 warn 日志(现状 loadGlobalConfig catch 静默无日志的补漏)+ 携带原因。 */
|
|
115
|
+
function readFailure(configPath: string, err: unknown): GlobalConfigReadResult {
|
|
116
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
117
|
+
logger.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
118
|
+
return { status: "failed", reason };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 从 unknown 错误提取 Node errno code(运行时 guard,不用类型断言)。 */
|
|
122
|
+
function errnoCodeOf(err: unknown): string | undefined {
|
|
123
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return undefined;
|
|
124
|
+
const code = Reflect.get(err, "code");
|
|
125
|
+
return typeof code === "string" ? code : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 已解析 JSON 的 sanitize(loadGlobalConfig 与 readGlobalConfig 共用,防两处漂移)。
|
|
130
|
+
* P4 引擎路由(D9):非法值静默回缺省('pi' / false)——config.json 是用户手编
|
|
131
|
+
* 文件,坏值不炸启动(与 maxConcurrent 同判)。
|
|
132
|
+
*/
|
|
133
|
+
function sanitizeParsedConfig(parsed: Partial<SubagentsGlobalConfig>): SubagentsGlobalConfig {
|
|
134
|
+
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
135
|
+
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
136
|
+
return {
|
|
137
|
+
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
138
|
+
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
139
|
+
...(defaultEngine !== undefined ? { defaultEngine } : {}),
|
|
140
|
+
...(engineRouting !== undefined ? { engineRouting } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** maxConcurrent 校验:正整数,否则默认。 */
|
|
145
|
+
function sanitizeMaxConcurrent(value: unknown): number {
|
|
146
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
147
|
+
? value
|
|
148
|
+
: DEFAULT_MAX_CONCURRENT;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* defaultEngine 校验:非空字符串透传,其余 undefined(缺省引擎由路由层落 'pi')。
|
|
153
|
+
* 为什么不在加载期对注册表校验 hasEngine:加载早于组合根注册(agentDir 解析在
|
|
154
|
+
* session_start),注册表此刻可能为空——校验归路由层(getEngine 抛 EngineNotFoundError)。
|
|
155
|
+
*/
|
|
156
|
+
function sanitizeDefaultEngine(value: unknown): string | undefined {
|
|
157
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** engineRouting 校验:仅认 strict 布尔,其余键忽略(向前兼容追加)。 */
|
|
161
|
+
function sanitizeEngineRouting(value: unknown): { strict: boolean } | undefined {
|
|
162
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
163
|
+
const strict = (value as Record<string, unknown>).strict;
|
|
164
|
+
return typeof strict === "boolean" ? { strict } : undefined;
|
|
165
|
+
}
|