@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,330 @@
|
|
|
1
|
+
// src/execution/dialog-queue.ts
|
|
2
|
+
//
|
|
3
|
+
// L2 跨子进程全局 dialog 串行队列。
|
|
4
|
+
//
|
|
5
|
+
// 进程单例语义:跨所有子进程共享,串行所有 dialog 类 UI 请求
|
|
6
|
+
//(isDialogMethod(method)===true,即 select/confirm/input/editor)。
|
|
7
|
+
//
|
|
8
|
+
// 设计动机(.fix-plans/00-master-summary.md §一 冲突 3):
|
|
9
|
+
// - L1 per-child 队列(session-runner.createUiRequestQueue)只解决同一子进程内的串行,
|
|
10
|
+
// 多个并行子进程仍可同时把 dialog 请求涌向父 UI(争输入焦点)。
|
|
11
|
+
// - L2 全局队列在主 agent handler 入口前再串行一次,保证同一时刻主 agent 只呈现一个 dialog。
|
|
12
|
+
// - 排队绑定 method 交互模型(dialog 才排队),不绑定 channel——排队是 Pi 协议固有属性。
|
|
13
|
+
//
|
|
14
|
+
// SR-4(child close reject):入队项带 child 引用,child close 时把该 child 的 pending dialog
|
|
15
|
+
// 全部 resolve 为 {cancelled:true},防 Promise 永挂 + 内存泄漏。
|
|
16
|
+
//
|
|
17
|
+
// handler 抛错兜底:catch → 回 {cancelled:true} → 继续处理下一个。不能让一个失败卡死队列。
|
|
18
|
+
//
|
|
19
|
+
// 调用方约定:只对 dialog 类(isDialogMethod===true)调 enqueue;fire-and-forget 由调用方
|
|
20
|
+
// 直接调 handler(见 ui-request-handler-factory.ts),不经过本队列。enqueue 内仍防御性兼容
|
|
21
|
+
// fire-and-forget(万一调用方未判):直接调 handler 返回,不入队串行。调用方不应依赖此防御。
|
|
22
|
+
|
|
23
|
+
import { isDialogMethod } from "./ui-interaction-model.ts";
|
|
24
|
+
|
|
25
|
+
// ── 类型定义(本模块是 UiRequest/UiResponse/UiRequestHandler 的规范来源) ──
|
|
26
|
+
// session-runner.ts 和 ui-channels.ts 复用这些类型(session-runner 再导出供测试 import)。
|
|
27
|
+
|
|
28
|
+
/** Pi extension_ui_request 的方法枚举(dialog + fire-and-forget 两类)。
|
|
29
|
+
* dialog 类:select/confirm/input/editor(占输入焦点,等响应)。
|
|
30
|
+
* fire-and-forget 类:notify/setStatus/setWidget/setTitle/set_editor_text(纯展示/写入)。
|
|
31
|
+
* (string & {}) 兜底:Pi 未来新增 method 或未知 method 走字符串字面量类型。 */
|
|
32
|
+
export type UiMethod =
|
|
33
|
+
| "select"
|
|
34
|
+
| "confirm"
|
|
35
|
+
| "input"
|
|
36
|
+
| "editor"
|
|
37
|
+
| "notify"
|
|
38
|
+
| "setStatus"
|
|
39
|
+
| "setWidget"
|
|
40
|
+
| "setTitle"
|
|
41
|
+
| "set_editor_text"
|
|
42
|
+
| (string & {});
|
|
43
|
+
|
|
44
|
+
/** UI 请求(session-runner 构造后传给 handler)。
|
|
45
|
+
*
|
|
46
|
+
* method 是判别字段,决定排队策略(dialog 排队)和业务路由(channel 分发)。
|
|
47
|
+
* method 特定字段按 method 可选出现(与 ExtensionUiRequest 1:1,由 session-runner 从
|
|
48
|
+
* ExtensionUiRequest 平铺构造)。channel/channelPayload 由 parseChannel 填充。
|
|
49
|
+
*
|
|
50
|
+
* 契约来源:.fix-plans/00-master-summary.md §二 2.2。 */
|
|
51
|
+
export interface UiRequest {
|
|
52
|
+
/** Pi rpc-types.ts 的 method(select/confirm/input/editor 为 dialog 类)。 */
|
|
53
|
+
method: UiMethod;
|
|
54
|
+
/** 请求 id(从 extension_ui_request envelope 顶层提取,用于 response 关联)。 */
|
|
55
|
+
id: string;
|
|
56
|
+
// method 特定字段(按 method 可选,与 ExtensionUiRequest 1:1)
|
|
57
|
+
title?: string;
|
|
58
|
+
options?: string[];
|
|
59
|
+
message?: string;
|
|
60
|
+
placeholder?: string;
|
|
61
|
+
prefill?: string;
|
|
62
|
+
notifyType?: string;
|
|
63
|
+
statusKey?: string;
|
|
64
|
+
statusText?: string | undefined;
|
|
65
|
+
widgetKey?: string;
|
|
66
|
+
widgetLines?: string[] | undefined;
|
|
67
|
+
widgetPlacement?: "aboveEditor" | "belowEditor";
|
|
68
|
+
text?: string;
|
|
69
|
+
timeout?: number;
|
|
70
|
+
/** channel 名(从 method 对应字段的 NUL 前缀解析)。
|
|
71
|
+
* select → 从 title 解析;setWidget → 从 widgetLines[0] 解析;其他 → undefined。
|
|
72
|
+
* 已知值:"ask_user"(select)、"gui_widget"(setWidget)。handler 按 channel 分发。 */
|
|
73
|
+
channel?: string;
|
|
74
|
+
/** channel 解析后的结构化 payload(已 JSON.parse)。
|
|
75
|
+
* ask_user: {questions, allowCancel};gui_widget: {component};无 channel: undefined。 */
|
|
76
|
+
channelPayload?: unknown;
|
|
77
|
+
/** 内部元数据字段:发起该 UI 请求的子进程 pid(由 session-runner.handleUiRequest 从
|
|
78
|
+
* child.pid 填入)。L2 队列据此关联 rejectChildDialogs(child close 时批量 reject)。
|
|
79
|
+
* 下划线前缀表示内部字段,非 Pi 协议字段,不参与 stdin 回写。 */
|
|
80
|
+
_childPid?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** UI 响应(handler 返回,session-runner 按 shape 回写 stdin)。
|
|
84
|
+
* - {value}: select/input/editor 的答案
|
|
85
|
+
* - {confirmed}: confirm 的答案
|
|
86
|
+
* - {cancelled}: 取消(child close / handler 抛错 / 用户取消)
|
|
87
|
+
* - {ack}: fire-and-forget(当前不透传到 TUI,留作协议完整) */
|
|
88
|
+
export type UiResponse =
|
|
89
|
+
| { value: string }
|
|
90
|
+
| { confirmed: boolean }
|
|
91
|
+
| { cancelled: true }
|
|
92
|
+
| { ack: true };
|
|
93
|
+
|
|
94
|
+
/** UI 请求 handler 签名(单函数,按 req.method 内部路由)。
|
|
95
|
+
* 实现方负责:channel 业务路由(ask_user → AskUserComponent)+ 默认转发(ctx.ui.*)。
|
|
96
|
+
* 抛错由调用方(DialogGlobalQueue / session-runner)兜底为 {cancelled:true}。 */
|
|
97
|
+
export type UiRequestHandler = (req: UiRequest) => Promise<UiResponse>;
|
|
98
|
+
|
|
99
|
+
// ── DialogGlobalQueue 实现 ──
|
|
100
|
+
|
|
101
|
+
/** 入队项的 child 引用形状(只取 pid 用于 rejectChildDialogs 匹配)。 */
|
|
102
|
+
export interface DialogChildRef {
|
|
103
|
+
pid: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** enqueue 的可选项。child 用于 rejectChildDialogs 关联(child close 时批量 reject)。 */
|
|
107
|
+
export interface EnqueueOptions {
|
|
108
|
+
child?: DialogChildRef;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 队列内一项:请求 + handler + resolve + 所属 child。
|
|
112
|
+
* pending 状态持有 resolve,handler 完成 / rejectChildDialogs 时调它 settle Promise。
|
|
113
|
+
* settled 标志保证只 settle 一次(rejectChildDialogs 与 handler 完成可能竞争)。 */
|
|
114
|
+
interface QueueItem {
|
|
115
|
+
req: UiRequest;
|
|
116
|
+
handler: UiRequestHandler;
|
|
117
|
+
resolve: (resp: UiResponse) => void;
|
|
118
|
+
childPid: number | undefined;
|
|
119
|
+
/** 是否已 settle(防 handler 完成 / rejectChildDialogs 重复 resolve)。 */
|
|
120
|
+
settled: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* L2 跨子进程全局 dialog 串行队列(进程单例)。
|
|
125
|
+
*
|
|
126
|
+
* 用法(createUiRequestHandlerForMode 返回的总 handler 内):
|
|
127
|
+
* ```ts
|
|
128
|
+
* const dialogQueue = new DialogGlobalQueue();
|
|
129
|
+
* return async (req: UiRequest) => {
|
|
130
|
+
* // 调用方负责判断:dialog 入队,fire-and-forget 直接调 realHandler
|
|
131
|
+
* if (isDialogMethod(req.method)) return dialogQueue.enqueue(req, realHandler);
|
|
132
|
+
* return realHandler(req);
|
|
133
|
+
* };
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* 语义保证:
|
|
137
|
+
* - FIFO 串行:前一个 handler settle 后才处理下一个
|
|
138
|
+
* - SR-4:rejectChildDialogs(child) 把该 child 的 pending 全部 resolve 为 {cancelled:true}
|
|
139
|
+
* - handler 抛错兜底:catch → {cancelled:true} → 继续下一个(队列不卡死)
|
|
140
|
+
* - 调用方约定只对 dialog 类调 enqueue;fire-and-forget 由调用方直接调 handler 不入队
|
|
141
|
+
*(enqueue 内仍防御性兼容 fire-and-forget,但不保证行为)
|
|
142
|
+
*
|
|
143
|
+
* 线程模型:纯 Promise + 微任务驱动,无锁。Node 单线程 event loop 保证队列状态一致。
|
|
144
|
+
*
|
|
145
|
+
* 单 session 假设(M-2,与 index.ts lastSessionId 同源):本队列是进程级单例(实例挂在
|
|
146
|
+
* globalThis[Symbol.for("@zhushanwen/pi-subagents.dialogQueue")],见 getOrCreateDialogQueue)。
|
|
147
|
+
* rejectAll() 清空所有 pending dialog——无 per-session 隔离。Pi 当前架构保证单进程
|
|
148
|
+
* 单 session 串行(同进程不会并发多个 session),故 session_shutdown 调 rejectAll() 只会清掉
|
|
149
|
+
* 当前 session 的 pending。若未来 Pi 支持同进程多 session 并发,session A 退出会误清 session B
|
|
150
|
+
* 的 pending dialog——届时需改为 per-session 隔离(入队项 QueueItem 带 sessionId,rejectAll
|
|
151
|
+
* 改 rejectAllForSession(sessionId),session_shutdown 只清当前 session)。
|
|
152
|
+
*/
|
|
153
|
+
export class DialogGlobalQueue {
|
|
154
|
+
/** 等待处理的队列(FIFO)。正在处理的项从 queue shift 出后由 current 持有。 */
|
|
155
|
+
private queue: QueueItem[] = [];
|
|
156
|
+
/** 正在处理的项(handler 已调、未 settle)。用于 rejectChildDialogs 取消占位中的 dialog。 */
|
|
157
|
+
private current: QueueItem | undefined;
|
|
158
|
+
private processing = false;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 入队一个 UI 请求,返回 Promise<UiResponse>。
|
|
162
|
+
*
|
|
163
|
+
* 调用方约定:只对 dialog 类(isDialogMethod===true)调 enqueue。fire-and-forget 由
|
|
164
|
+
* 调用方(ui-request-handler-factory.ts)在 enqueue 前判 isDialogMethod 后直接调 handler,
|
|
165
|
+
* 不经过本队列。enqueue 内仍防御性兼容 fire-and-forget(万一调用方未判):直接调 handler 返回,
|
|
166
|
+
* 不入队串行,但调用方不应依赖此防御行为。
|
|
167
|
+
*
|
|
168
|
+
* dialog 项处理(TC-E4 case 1):进队列 FIFO 串行,等前一个 settle 后才调 handler
|
|
169
|
+
*(争输入焦点,防并发弹窗)。
|
|
170
|
+
*
|
|
171
|
+
* handler 抛错兜底:catch → 回 {cancelled:true}(dialog 路径,队列不卡死)。
|
|
172
|
+
* SR-4:opts.child 用于 rejectChildDialogs 关联(dialog 项会被批量 reject,含 current)。
|
|
173
|
+
*
|
|
174
|
+
* @param req UI 请求(约定只传 dialog 类;fire-and-forget 防御性兼容)
|
|
175
|
+
* @param handler 真正执行请求的 handler(TUI/GUI 模式分流后的 realHandler)
|
|
176
|
+
* @param opts 可选 child 引用(用于 rejectChildDialogs 关联)
|
|
177
|
+
* @returns handler 的响应;dialog 抛错时回 {cancelled:true};child close 时回 {cancelled:true}
|
|
178
|
+
*/
|
|
179
|
+
enqueue(
|
|
180
|
+
req: UiRequest,
|
|
181
|
+
handler: UiRequestHandler,
|
|
182
|
+
opts?: EnqueueOptions,
|
|
183
|
+
): Promise<UiResponse> {
|
|
184
|
+
// 防御性兼容 fire-and-forget:调用方约定不应传此类 req 进 enqueue(factory 层已判
|
|
185
|
+
// isDialogMethod 直接调 handler),但万一调用方未判,这里直接调 handler 不入队,避免阻塞。
|
|
186
|
+
// 直接返回 handler 真实结果(不做兜底形变为 cancelled——fire-and-forget 无串行语义)。
|
|
187
|
+
if (!isDialogMethod(req.method)) {
|
|
188
|
+
return handler(req);
|
|
189
|
+
}
|
|
190
|
+
// dialog:入队 FIFO 串行
|
|
191
|
+
return new Promise<UiResponse>((resolve) => {
|
|
192
|
+
this.queue.push({
|
|
193
|
+
req,
|
|
194
|
+
handler,
|
|
195
|
+
resolve,
|
|
196
|
+
childPid: opts?.child?.pid,
|
|
197
|
+
settled: false,
|
|
198
|
+
});
|
|
199
|
+
void this.processNext();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* settle 一个 item(幂等)。handler 完成 / rejectChildDialogs / rejectAll 都通过本方法,
|
|
205
|
+
* settled 标志保证只 settle 一次(防竞争)。
|
|
206
|
+
*
|
|
207
|
+
* #19 单一推进点:本方法 settle Promise + 清状态后,**唯一**调 processNext 推进队列。
|
|
208
|
+
* processNext 尾部不再调 processNext(旧代码双重推进,虽靠 processing 标志幂等,但语义混乱)。
|
|
209
|
+
* 为什么推进必须在 settleItem 而非 processNext 尾部:rejectChildDialogs 取消一个永不 settle
|
|
210
|
+
* 的 current(handler 等用户输入卡死)时,processNext 的 `await item.handler` 永不 resume,
|
|
211
|
+
* 尾部不会执行;只有 settleItem 里的 processNext 才能打破死锁,推进下一个。
|
|
212
|
+
*/
|
|
213
|
+
private settleItem(item: QueueItem, resp: UiResponse): void {
|
|
214
|
+
if (item.settled) return;
|
|
215
|
+
item.settled = true;
|
|
216
|
+
item.resolve(resp);
|
|
217
|
+
// 若是正在处理的项,清空 current/processing 并推进队列(唯一推进点)。
|
|
218
|
+
// 非当前项(队列中被 reject)只 settle Promise,不影响 current/推进。
|
|
219
|
+
if (this.current === item) {
|
|
220
|
+
this.current = undefined;
|
|
221
|
+
this.processing = false;
|
|
222
|
+
void this.processNext();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* SR-4:把指定 child 的所有 pending dialog resolve 为 {cancelled:true}。
|
|
228
|
+
*
|
|
229
|
+
* 触发场景:子进程 close(用户取消 / crash / 超时 kill)时,其 pending dialog 的 handler
|
|
230
|
+
* 可能永不 settle(等用户输入),导致 Promise 永挂 + 内存泄漏。本方法批量清理。
|
|
231
|
+
*
|
|
232
|
+
* 处理范围(TC-E4 case 2):
|
|
233
|
+
* - 正在处理中(current)的该 child 项:settle {cancelled:true},解阻塞队列推进下一个
|
|
234
|
+
* (关键:handler 可能永不 settle,必须由这里打破死锁)
|
|
235
|
+
* - 队列中等待处理的该 child 项:settle {cancelled:true} 并移除
|
|
236
|
+
*
|
|
237
|
+
* 不影响其他 child 的 pending dialog(TC-E4 case 2 子测试 2)。
|
|
238
|
+
*/
|
|
239
|
+
rejectChildDialogs(child: DialogChildRef): void {
|
|
240
|
+
// 先处理正在处理的项(可能永不 settle,必须由这里解阻塞)
|
|
241
|
+
if (this.current && this.current.childPid === child.pid) {
|
|
242
|
+
this.settleItem(this.current, { cancelled: true });
|
|
243
|
+
}
|
|
244
|
+
// 再处理队列中等待的项
|
|
245
|
+
if (this.queue.length === 0) return;
|
|
246
|
+
const remaining: QueueItem[] = [];
|
|
247
|
+
for (const item of this.queue) {
|
|
248
|
+
if (item.childPid === child.pid) {
|
|
249
|
+
// settle 该 child 的 pending Promise 为 cancelled
|
|
250
|
+
this.settleItem(item, { cancelled: true });
|
|
251
|
+
} else {
|
|
252
|
+
remaining.push(item);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.queue = remaining;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 处理队列下一项(FIFO)。
|
|
260
|
+
*
|
|
261
|
+
* processing 标志保证串行:handler 运行期间 processing=true,新的 processNext 调用直接返回;
|
|
262
|
+
* handler settle 后由 settleItem 清 processing=false 并推进下一项(#19 单一推进点)。
|
|
263
|
+
*
|
|
264
|
+
* handler 抛错兜底(TC-E4 case 3):catch → settle {cancelled:true} → 继续。
|
|
265
|
+
* 不能让一个失败卡死队列(processing 永远 true)。
|
|
266
|
+
*/
|
|
267
|
+
private async processNext(): Promise<void> {
|
|
268
|
+
if (this.processing) return;
|
|
269
|
+
if (this.queue.length === 0) return;
|
|
270
|
+
this.processing = true;
|
|
271
|
+
const item = this.queue.shift()!;
|
|
272
|
+
this.current = item;
|
|
273
|
+
try {
|
|
274
|
+
const resp = await item.handler(item.req);
|
|
275
|
+
// handler 完成:settle(若已被 rejectChildDialogs 抢先 settle 则 noop)。
|
|
276
|
+
// settleItem 内会清 current/processing 并推进队列(#19 单一推进点)。
|
|
277
|
+
this.settleItem(item, resp);
|
|
278
|
+
} catch {
|
|
279
|
+
// handler 抛错兜底:回 cancelled,不向上抛(队列不能卡死)
|
|
280
|
+
this.settleItem(item, { cancelled: true });
|
|
281
|
+
}
|
|
282
|
+
// #19:不在尾部再调 processNext——推进统一由 settleItem 负责(避免双重推进)。
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* #10:把所有 pending dialog(queue + current)全部 settle 为 {cancelled:true},
|
|
287
|
+
* 并清空 queue/current/processing 状态。session_shutdown 调用,保证不留永挂 Promise。
|
|
288
|
+
*
|
|
289
|
+
* 约定签名:rejectAll(): void(无参,返 void)。Group C 的 index.ts session_shutdown 依赖此契约。
|
|
290
|
+
*
|
|
291
|
+
* 幂等:依赖 settleItem 的 settled 标志——重复调用只会对已 settled 项 noop。
|
|
292
|
+
* 顺序敏感(#19 推进点在 settleItem):必须先清空 queue 数组再 settle current,
|
|
293
|
+
* 否则 settleItem(current) 同步触发的 processNext 会从旧 queue 抢占下一项作为新 current,
|
|
294
|
+
* 避开本方法的 cancel 语义。清空后 processNext 看到空队列直接返回,新 current 不会被抢占。
|
|
295
|
+
*
|
|
296
|
+
* 单 session 假设(M-2):见类注释。本方法清空所有 pending 不分 session——依赖 Pi 单进程
|
|
297
|
+
* 单 session 串行保证。session_shutdown handler(index.ts)调用本方法时,进程内只会有当前
|
|
298
|
+
* session 的 pending dialog。多 session 并发场景的迁移策略(rejectAllForSession)见类注释。
|
|
299
|
+
*/
|
|
300
|
+
rejectAll(): void {
|
|
301
|
+
// 先捕获并清空队列——防 settleItem(current) 触发的 processNext 抢占同队列下一项
|
|
302
|
+
const items = this.queue;
|
|
303
|
+
this.queue = [];
|
|
304
|
+
// 再 settle current(processNext 此时看到空队列,不会抢占)
|
|
305
|
+
if (this.current) {
|
|
306
|
+
this.settleItem(this.current, { cancelled: true });
|
|
307
|
+
}
|
|
308
|
+
// settle 所有原队列项(Promise 必须 resolve,不能挂)
|
|
309
|
+
for (const item of items) {
|
|
310
|
+
this.settleItem(item, { cancelled: true });
|
|
311
|
+
}
|
|
312
|
+
// 状态清理(幂等:settleItem(current) 可能已清)
|
|
313
|
+
this.current = undefined;
|
|
314
|
+
this.processing = false;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** 清空队列状态(仅在 rejectAll 之后调用)。pending Promise 必须先由 rejectAll settle。
|
|
318
|
+
* 不 settle Promise 的纯状态重置——单独调用会导致 Promise 永挂(footgun),故设为 private。
|
|
319
|
+
* 外部调用方应使用 rejectAll()(它 settle 所有 pending + 重置状态,是原子操作)。 */
|
|
320
|
+
private resetState(): void {
|
|
321
|
+
this.queue = [];
|
|
322
|
+
this.current = undefined;
|
|
323
|
+
this.processing = false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** 当前队列长度(测试/诊断用)。含等待处理项(不含 current)。 */
|
|
327
|
+
get size(): number {
|
|
328
|
+
return this.queue.length;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// data-dir.test.ts —— dataDir 通道解析(XYZ_AGENT_DATA_DIR 权威通道 + 宿主数据根回退)。
|
|
2
|
+
//
|
|
3
|
+
// 三视角:①构建者——两级解析顺序正确;②使用者——独立 pi 用户(无 xyz env)拿到
|
|
4
|
+
// 宿主数据根(pi 壳 dataRoot() = getAgentDir())作根;③观察者——回退路径 warn 一次
|
|
5
|
+
// (不留静默漂移),不刷屏。
|
|
6
|
+
//
|
|
7
|
+
// 隔离(u0-data-discovery 注入化后):fallback 值来自 HostServices.dataRoot() 端口,
|
|
8
|
+
// 每用例 resetCoreForTests + configureCore 假宿主控制端口态,避免跨用例泄漏;
|
|
9
|
+
// 不再依赖 vitest alias 的 pi-coding-agent mock。
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
12
|
+
|
|
13
|
+
import { configureCore, resetCoreForTests } from "../../../../core/host-services.ts";
|
|
14
|
+
import {
|
|
15
|
+
getEngineDataDir,
|
|
16
|
+
resetDataDirWarnForTests,
|
|
17
|
+
XYZ_DATA_DIR_ENV,
|
|
18
|
+
} from "../../common/data-dir.ts";
|
|
19
|
+
|
|
20
|
+
const FAKE_DATA_ROOT = "/home/user/.pi/agent";
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
resetCoreForTests();
|
|
24
|
+
configureCore({
|
|
25
|
+
dataRoot: () => FAKE_DATA_ROOT,
|
|
26
|
+
log: () => {},
|
|
27
|
+
});
|
|
28
|
+
resetDataDirWarnForTests();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
resetCoreForTests();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("getEngineDataDir", () => {
|
|
36
|
+
it("XYZ_AGENT_DATA_DIR 存在 → 权威通道直取,不 warn", () => {
|
|
37
|
+
const warnings: string[] = [];
|
|
38
|
+
const dir = getEngineDataDir({ [XYZ_DATA_DIR_ENV]: "/data/xyz-agent" }, (m) => warnings.push(m));
|
|
39
|
+
expect(dir).toBe("/data/xyz-agent");
|
|
40
|
+
expect(warnings).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("空串/空白串视为缺失(truthy 语义与 shared getDataDir 的 || 归一对齐)", () => {
|
|
44
|
+
const warnings: string[] = [];
|
|
45
|
+
const dir = getEngineDataDir({ [XYZ_DATA_DIR_ENV]: " " }, (m) => warnings.push(m));
|
|
46
|
+
expect(dir).not.toBe(" ");
|
|
47
|
+
expect(warnings.length).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("缺 env → 回退宿主数据根(HostServices.dataRoot(),假宿主值)并 warn 一次", () => {
|
|
51
|
+
const warnings: string[] = [];
|
|
52
|
+
const warn = (m: string): void => warnings.push(m);
|
|
53
|
+
const dir1 = getEngineDataDir({}, warn);
|
|
54
|
+
expect(dir1).toBe(FAKE_DATA_ROOT);
|
|
55
|
+
expect(warnings.length).toBe(1);
|
|
56
|
+
expect(warnings[0]).toContain("XYZ_AGENT_DATA_DIR");
|
|
57
|
+
|
|
58
|
+
// warn once:第二次调用不再刷
|
|
59
|
+
const dir2 = getEngineDataDir({}, warn);
|
|
60
|
+
expect(dir2).toBe(dir1);
|
|
61
|
+
expect(warnings.length).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("reset 后再次回退会重新 warn(测试隔离钩子可用)", () => {
|
|
65
|
+
const warnings: string[] = [];
|
|
66
|
+
getEngineDataDir({}, (m) => warnings.push(m));
|
|
67
|
+
resetDataDirWarnForTests();
|
|
68
|
+
getEngineDataDir({}, (m) => warnings.push(m));
|
|
69
|
+
expect(warnings.length).toBe(2);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("未 configureCore 即消费回退段 → 抛 core_host_not_configured(端口必需语义)", () => {
|
|
73
|
+
resetCoreForTests();
|
|
74
|
+
expect(() => getEngineDataDir({}, (m) => warningsNoop(m))).toThrow(/core_host_not_configured/);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function warningsNoop(_msg: string): void {}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// errors.test.ts —— 引擎层错误 SSOT 的结构锁定。
|
|
2
|
+
//
|
|
3
|
+
// 三视角:①构建者——11 条 code 与设计 §3.3.3 全表一致;②使用者——错误消息
|
|
4
|
+
// code 前缀格式可被字符串匹配分流;③观察者——每条错误必有非空恢复指引(可操作)。
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_RECOVERY_HINTS,
|
|
10
|
+
ENGINE_ERROR_CODES,
|
|
11
|
+
EngineError,
|
|
12
|
+
engineRunFailedDetail,
|
|
13
|
+
engineTimeoutDetail,
|
|
14
|
+
isEngineErrorCode,
|
|
15
|
+
nestedSpawnRejectedError,
|
|
16
|
+
promptTooLargeError,
|
|
17
|
+
schemaEmulationFailedDetail,
|
|
18
|
+
STDOUT_TAIL_ECHO_CHARS,
|
|
19
|
+
} from "../../common/errors.ts";
|
|
20
|
+
|
|
21
|
+
describe("ENGINE_ERROR_CODES(§3.3.3 全表)", () => {
|
|
22
|
+
it("11 条错误码与设计文档错误规格表逐条一致", () => {
|
|
23
|
+
expect([...ENGINE_ERROR_CODES]).toEqual([
|
|
24
|
+
"engine_not_found",
|
|
25
|
+
"engine_probe_failed",
|
|
26
|
+
"engine_credential_missing",
|
|
27
|
+
"nested_spawn_rejected",
|
|
28
|
+
"schema_emulation_failed",
|
|
29
|
+
"engine_timeout",
|
|
30
|
+
"engine_capability_unsupported",
|
|
31
|
+
"engine_session_not_resumable",
|
|
32
|
+
"model_not_available",
|
|
33
|
+
"prompt_too_large",
|
|
34
|
+
"engine_run_failed",
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("isEngineErrorCode 收窄:表内 code 为 true,表外/非字符串为 false", () => {
|
|
39
|
+
expect(isEngineErrorCode("engine_timeout")).toBe(true);
|
|
40
|
+
expect(isEngineErrorCode("engine_not_found")).toBe(true);
|
|
41
|
+
expect(isEngineErrorCode("engine_oops")).toBe(false);
|
|
42
|
+
expect(isEngineErrorCode(42)).toBe(false);
|
|
43
|
+
expect(isEngineErrorCode(undefined)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("DEFAULT_RECOVERY_HINTS(恢复指引全集)", () => {
|
|
48
|
+
it("11 条 code 每条都有非空恢复指引(可操作,非安慰性文案)", () => {
|
|
49
|
+
for (const code of ENGINE_ERROR_CODES) {
|
|
50
|
+
const hint = DEFAULT_RECOVERY_HINTS[code];
|
|
51
|
+
expect(hint, `recovery hint for ${code}`).toMatch(/\S/);
|
|
52
|
+
expect(hint.length).toBeGreaterThan(20);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("关键恢复动作在指引中(抽查三条错误规格的载体系数)", () => {
|
|
57
|
+
expect(DEFAULT_RECOVERY_HINTS.engine_timeout).toMatch(/stdout tail/i);
|
|
58
|
+
expect(DEFAULT_RECOVERY_HINTS.prompt_too_large).toMatch(/stdin/);
|
|
59
|
+
expect(DEFAULT_RECOVERY_HINTS.nested_spawn_rejected).toMatch(/inside the current task/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("EngineError", () => {
|
|
64
|
+
it("message 为 <code>: <detail> 前缀格式(outcome.error 分流依据)", () => {
|
|
65
|
+
const err = new EngineError("engine_timeout", "chain exhausted", "retry with pi");
|
|
66
|
+
expect(err.message).toBe("engine_timeout: chain exhausted");
|
|
67
|
+
expect(err.code).toBe("engine_timeout");
|
|
68
|
+
expect(err.recovery).toBe("retry with pi");
|
|
69
|
+
expect(err.name).toBe("EngineError");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("toStructured 投影 code/message/recovery 三字段", () => {
|
|
73
|
+
const structured = new EngineError("model_not_available", "no such model", "pick another").toStructured();
|
|
74
|
+
expect(structured).toEqual({
|
|
75
|
+
code: "model_not_available",
|
|
76
|
+
message: "model_not_available: no such model",
|
|
77
|
+
recovery: "pick another",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("具名构造器", () => {
|
|
83
|
+
it("promptTooLargeError:detail 含实际/上限字节数,recovery 含三条恢复建议", () => {
|
|
84
|
+
const err = promptTooLargeError(200_000, 131_072);
|
|
85
|
+
expect(err.code).toBe("prompt_too_large");
|
|
86
|
+
expect(err.message).toContain("200000");
|
|
87
|
+
expect(err.message).toContain("131072");
|
|
88
|
+
expect(err.recovery).toMatch(/shorten the task/i);
|
|
89
|
+
expect(err.recovery).toMatch(/file channel/);
|
|
90
|
+
expect(err.recovery).toMatch(/stdin/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("nestedSpawnRejectedError:说明防护规则 + 指向 task 内自行完成", () => {
|
|
94
|
+
const err = nestedSpawnRejectedError();
|
|
95
|
+
expect(err.code).toBe("nested_spawn_rejected");
|
|
96
|
+
expect(err.message).toContain("XYZ_AGENT_SUBAGENT");
|
|
97
|
+
expect(err.recovery).toMatch(/inside the current task/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("engineTimeoutDetail:含 stdout 尾部 + engine: pi 重跑建议", () => {
|
|
101
|
+
const detail = engineTimeoutDetail("partial stdout output");
|
|
102
|
+
expect(detail).toContain("partial stdout output");
|
|
103
|
+
expect(detail).toMatch(/SIGTERM/);
|
|
104
|
+
expect(detail).toMatch(/SIGKILL/);
|
|
105
|
+
expect(detail).toMatch(/`engine: pi`/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it(`engineTimeoutDetail:stdout 尾部截断到 ${STDOUT_TAIL_ECHO_CHARS} 字符`, () => {
|
|
109
|
+
const longTail = "x".repeat(STDOUT_TAIL_ECHO_CHARS + 500);
|
|
110
|
+
const detail = engineTimeoutDetail(longTail);
|
|
111
|
+
// 截断后含省略号标记,总长有界(不含前后固定文案不超过 2000 + 常量开销)
|
|
112
|
+
expect(detail).toContain("...");
|
|
113
|
+
expect(detail.length).toBeLessThan(STDOUT_TAIL_ECHO_CHARS + 600);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("engineRunFailedDetail:含 reason / exit code / 尾部 / 恢复指引", () => {
|
|
117
|
+
const detail = engineRunFailedDetail("stdout parse failed", 3, "some output");
|
|
118
|
+
expect(detail).toContain("stdout parse failed");
|
|
119
|
+
expect(detail).toContain("exit code 3");
|
|
120
|
+
expect(detail).toContain("some output");
|
|
121
|
+
expect(detail).toMatch(/probe/);
|
|
122
|
+
// exitCode=null(被信号杀死)的形态
|
|
123
|
+
expect(engineRunFailedDetail("crash", null, "t")).toContain("killed by signal");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("schemaEmulationFailedDetail:含错误明细 + 原始输出尾部 + 重试一次语义", () => {
|
|
127
|
+
const detail = schemaEmulationFailedDetail("Schema validation failed: /a must be number", '{"a":"x"}');
|
|
128
|
+
expect(detail).toContain("Schema validation failed");
|
|
129
|
+
expect(detail).toContain('{"a":"x"}');
|
|
130
|
+
expect(detail).toMatch(/retry once/i);
|
|
131
|
+
});
|
|
132
|
+
});
|