@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,197 @@
|
|
|
1
|
+
// model-prompt.test.ts —— [U7] 引擎模型段注入的开关语义(defaultEngine)与段格式;
|
|
2
|
+
// [engine-awareness U2] 恒在状态段三形态(zcode/pi/ghost)+ 清单段双降级文案
|
|
3
|
+
// (未实现/null → 与主体系一致声明;空清单/异常 → 无凭据提示行)+ 渲染确定性(D7)。
|
|
4
|
+
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
+
|
|
11
|
+
import type { EnginePort } from "../port.ts";
|
|
12
|
+
import { clearEngines, registerEngine } from "../registry.ts";
|
|
13
|
+
import { buildEngineModelsPromptAppend, buildSubagentEngineSection } from "../model-prompt.ts";
|
|
14
|
+
|
|
15
|
+
/** 最小 fake 引擎:仅 id + 可选 listModels(其余面 run/read 走不到,测试只触碰注入链)。 */
|
|
16
|
+
function fakeEngine(id: string, models: Array<{ id: string; name?: string }> | null): EnginePort {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
capabilities: () => ({ conversation: "unsupported", steer: "unsupported", sandbox: "none" }),
|
|
20
|
+
probe: async () => ({ ok: true, engineVersion: "test" }),
|
|
21
|
+
run: async () => {
|
|
22
|
+
throw new Error("not used in this test");
|
|
23
|
+
},
|
|
24
|
+
interact: async () => {
|
|
25
|
+
throw new Error("not used in this test");
|
|
26
|
+
},
|
|
27
|
+
read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
|
|
28
|
+
...(models !== null ? { listModels: () => models } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let tmpRoot: string;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "model-prompt-"));
|
|
36
|
+
clearEngines();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
clearEngines();
|
|
41
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("buildEngineModelsPromptAppend(defaultEngine 开关语义)", () => {
|
|
45
|
+
it("defaultEngine 缺省 / 'pi' → 不注入(pi 段由核心提供)", () => {
|
|
46
|
+
registerEngine("zcode", () => fakeEngine("zcode", [{ id: "p/m1" }]));
|
|
47
|
+
expect(buildEngineModelsPromptAppend(undefined)).toBe("");
|
|
48
|
+
expect(buildEngineModelsPromptAppend("pi")).toBe("");
|
|
49
|
+
expect(buildEngineModelsPromptAppend(" ")).toBe("");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("defaultEngine=zcode 且引擎实现 listModels → 注入段(含分界说明与模型 id)", () => {
|
|
53
|
+
registerEngine("zcode", () =>
|
|
54
|
+
fakeEngine("zcode", [
|
|
55
|
+
{ id: "builtin:bigmodel-coding-plan/GLM-5.3", name: "Z.ai - Coding Plan · GLM-5.3" },
|
|
56
|
+
{ id: "e512/mimo-v2.5-pro" },
|
|
57
|
+
]),
|
|
58
|
+
);
|
|
59
|
+
const append = buildEngineModelsPromptAppend("zcode");
|
|
60
|
+
expect(append).toContain("<available_zcode_models>");
|
|
61
|
+
expect(append).toContain("</available_zcode_models>");
|
|
62
|
+
expect(append).toContain("<id>builtin:bigmodel-coding-plan/GLM-5.3</id>");
|
|
63
|
+
expect(append).toContain("<name>Z.ai - Coding Plan · GLM-5.3</name>");
|
|
64
|
+
expect(append).toContain("<id>e512/mimo-v2.5-pro</id>");
|
|
65
|
+
// 分界语义:pi 段的 id 不适用于本引擎
|
|
66
|
+
expect(append).toContain("do NOT apply to engine 'zcode'");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("引擎未实现 listModels → 「与主 agent 模型体系一致」声明段(port 契约:未实现 ≠ 无模型)", () => {
|
|
70
|
+
// fakeEngine(id, null) = 不带 listModels 方法(未实现形态)
|
|
71
|
+
registerEngine("bare", () => fakeEngine("bare", null));
|
|
72
|
+
expect(buildEngineModelsPromptAppend("bare")).toBe(
|
|
73
|
+
[
|
|
74
|
+
"<available_bare_models>",
|
|
75
|
+
"engine 'bare' uses the same model registry as the main agent — use ids from <available_provider_models> above",
|
|
76
|
+
"</available_bare_models>",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
);
|
|
79
|
+
// 文案语义守卫:指向上方核心段、不含 ZCode desktop 指引(那是无凭据形态专属)
|
|
80
|
+
const append = buildEngineModelsPromptAppend("bare");
|
|
81
|
+
expect(append).toContain("use ids from <available_provider_models> above");
|
|
82
|
+
expect(append).not.toContain("ZCode desktop");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("listModels 返回 null → 「与主 agent 模型体系一致」声明段;空清单 → 无凭据提示行段", () => {
|
|
86
|
+
// null:listModels 存在但显式返回 null——port 契约与「未实现」同语义(与主体系一致)
|
|
87
|
+
registerEngine("nul", () => ({ ...fakeEngine("nul", []), listModels: () => null }));
|
|
88
|
+
expect(buildEngineModelsPromptAppend("nul")).toBe(
|
|
89
|
+
[
|
|
90
|
+
"<available_nul_models>",
|
|
91
|
+
"engine 'nul' uses the same model registry as the main agent — use ids from <available_provider_models> above",
|
|
92
|
+
"</available_nul_models>",
|
|
93
|
+
].join("\n"),
|
|
94
|
+
);
|
|
95
|
+
expect(buildEngineModelsPromptAppend("nul")).not.toContain("ZCode desktop");
|
|
96
|
+
// 空清单:引擎枚举面存在但当前无凭据模型——维持「无凭据 + ZCode desktop 指引」文案
|
|
97
|
+
registerEngine("empty", () => fakeEngine("empty", []));
|
|
98
|
+
expect(buildEngineModelsPromptAppend("empty")).toBe(
|
|
99
|
+
[
|
|
100
|
+
"<available_empty_models>",
|
|
101
|
+
"engine 'empty' has no credentialed models right now — configure the provider in ZCode desktop first",
|
|
102
|
+
"</available_empty_models>",
|
|
103
|
+
].join("\n"),
|
|
104
|
+
);
|
|
105
|
+
expect(buildEngineModelsPromptAppend("empty")).toContain("<available_empty_models>");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("未注册引擎 → 清单段不注入(警告归状态段负责,避免双份)", () => {
|
|
109
|
+
registerEngine("zcode", () => fakeEngine("zcode", [{ id: "p/m1" }]));
|
|
110
|
+
expect(buildEngineModelsPromptAppend("ghost")).toBe("");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("listModels 抛异常 → fail-safe 输出提示行段不向外抛", () => {
|
|
114
|
+
const throwing: EnginePort = {
|
|
115
|
+
...fakeEngine("boom", null),
|
|
116
|
+
listModels: () => {
|
|
117
|
+
throw new Error("v2 config broken");
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
registerEngine("boom", () => throwing);
|
|
121
|
+
const append = buildEngineModelsPromptAppend("boom");
|
|
122
|
+
expect(append).toContain("<available_boom_models>");
|
|
123
|
+
expect(append).toContain(
|
|
124
|
+
"engine 'boom' has no credentialed models right now — configure the provider in ZCode desktop first",
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("buildSubagentEngineSection(恒在状态段三形态,§3.1 逐字基准)", () => {
|
|
130
|
+
it("defaultEngine=zcode(已注册非 pi)→ 声明 + 指向下方清单段 + AGENTS.md 冲突裁决 bullet", () => {
|
|
131
|
+
registerEngine("zcode", () => fakeEngine("zcode", [{ id: "p/m1" }]));
|
|
132
|
+
// 逐字基准:设计 §3.1 Turn 1 样例(含 AGENTS.md bullet 的 em dash 与换行拆分)
|
|
133
|
+
expect(buildSubagentEngineSection("zcode")).toBe(
|
|
134
|
+
[
|
|
135
|
+
"<current_subagent_engine>",
|
|
136
|
+
"Default engine for subagent dispatches when neither the call's `engine` param",
|
|
137
|
+
"nor an agent .md `engine` frontmatter overrides: zcode",
|
|
138
|
+
"- Model ids for zcode dispatches are listed in <available_zcode_models> below.",
|
|
139
|
+
" Ids in <available_provider_models> do NOT apply to zcode dispatches.",
|
|
140
|
+
"- Omit `model` to use the engine default.",
|
|
141
|
+
"- If AGENTS.md or other standing guidance names model ids from the pi registry",
|
|
142
|
+
" (e.g. zai-coding-cn/*), those ids apply to pi-engine dispatches ONLY — when",
|
|
143
|
+
" the current engine is not pi, use only ids from the engine section below.",
|
|
144
|
+
"</current_subagent_engine>",
|
|
145
|
+
].join("\n"),
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("defaultEngine=pi / 缺省 / 空白 → 声明 pi + 指向上方核心段(无冲突 bullet)", () => {
|
|
150
|
+
// pi 形态不依赖注册表(缺省形态注册表为空也成立)
|
|
151
|
+
const expected = [
|
|
152
|
+
"<current_subagent_engine>",
|
|
153
|
+
"Default engine for subagent dispatches when neither the call's `engine` param",
|
|
154
|
+
"nor an agent .md `engine` frontmatter overrides: pi",
|
|
155
|
+
"- Model ids for pi dispatches are the ids in <available_provider_models> above.",
|
|
156
|
+
"- Omit `model` to use the engine default.",
|
|
157
|
+
"</current_subagent_engine>",
|
|
158
|
+
].join("\n");
|
|
159
|
+
expect(buildSubagentEngineSection("pi")).toBe(expected);
|
|
160
|
+
expect(buildSubagentEngineSection(undefined)).toBe(expected);
|
|
161
|
+
expect(buildSubagentEngineSection(" ")).toBe(expected);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("defaultEngine=未注册引擎(ghost)→ 显示配置值 + 未注册警告行(失败路径表逐字)", () => {
|
|
165
|
+
// 注册表里有其他引擎也不能让 ghost 误判为已注册
|
|
166
|
+
registerEngine("zcode", () => fakeEngine("zcode", [{ id: "p/m1" }]));
|
|
167
|
+
expect(buildSubagentEngineSection("ghost")).toBe(
|
|
168
|
+
[
|
|
169
|
+
"<current_subagent_engine>",
|
|
170
|
+
"Default engine for subagent dispatches when neither the call's `engine` param",
|
|
171
|
+
"nor an agent .md `engine` frontmatter overrides: ghost",
|
|
172
|
+
"- engine 'ghost' is not registered — dispatches will fail at routing; fix subagents/config.json",
|
|
173
|
+
"</current_subagent_engine>",
|
|
174
|
+
].join("\n"),
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("渲染确定性(D7):同输入两次调用输出逐字节相等", () => {
|
|
179
|
+
registerEngine("zcode", () => fakeEngine("zcode", [{ id: "a/m1" }, { id: "b/m2" }]));
|
|
180
|
+
const inputs: Array<string | undefined> = ["zcode", "pi", "ghost", undefined];
|
|
181
|
+
for (const input of inputs) {
|
|
182
|
+
// 字符串 toBe = 逐 UTF-16 码元值比较,即「同输入恒同输出」的字节稳定契约
|
|
183
|
+
expect(buildSubagentEngineSection(input)).toBe(buildSubagentEngineSection(input));
|
|
184
|
+
// 独立两次取值再比一次,排除实现内共享可变状态的可能
|
|
185
|
+
const first = buildSubagentEngineSection(input);
|
|
186
|
+
const second = buildSubagentEngineSection(input);
|
|
187
|
+
expect(second).toBe(first);
|
|
188
|
+
}
|
|
189
|
+
expect(buildEngineModelsPromptAppend("zcode")).toBe(buildEngineModelsPromptAppend("zcode"));
|
|
190
|
+
expect(buildEngineModelsPromptAppend("pi")).toBe(buildEngineModelsPromptAppend("pi"));
|
|
191
|
+
// 新降级形态(U2 修订)确定性:「与主体系一致」声明段同输入恒同输出
|
|
192
|
+
registerEngine("shared", () => fakeEngine("shared", null));
|
|
193
|
+
registerEngine("nulled", () => ({ ...fakeEngine("nulled", []), listModels: () => null }));
|
|
194
|
+
expect(buildEngineModelsPromptAppend("shared")).toBe(buildEngineModelsPromptAppend("shared"));
|
|
195
|
+
expect(buildEngineModelsPromptAppend("nulled")).toBe(buildEngineModelsPromptAppend("nulled"));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolveEngineDir,
|
|
5
|
+
resolveEnginesRoot,
|
|
6
|
+
resolveJournalPath,
|
|
7
|
+
resolvePoolDir,
|
|
8
|
+
sanitizeSeg,
|
|
9
|
+
} from '../paths.js';
|
|
10
|
+
|
|
11
|
+
describe('engine paths SSOT', () => {
|
|
12
|
+
it('engines 根锚定 dataDir 顶层', () => {
|
|
13
|
+
expect(resolveEnginesRoot('/data')).toBe('/data/engines');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('池目录 = engines/<engineId>/<poolKey>', () => {
|
|
17
|
+
expect(resolvePoolDir('/data', 'zcode', 'home-reviewer')).toBe('/data/engines/zcode/home-reviewer');
|
|
18
|
+
expect(resolveEngineDir('/data', 'zcode')).toBe('/data/engines/zcode');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('journal 路径带 taskId 文件名', () => {
|
|
22
|
+
expect(resolveJournalPath('/data', 'zcode', 'pool1', 'sa-123')).toBe(
|
|
23
|
+
'/data/engines/zcode/pool1/journal-sa-123.jsonl',
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('路径段安全编码:穿越/分隔符/空白折叠为连字符', () => {
|
|
28
|
+
expect(sanitizeSeg('a/b\\c')).toBe('a-b-c');
|
|
29
|
+
expect(sanitizeSeg('..')).toBe('default');
|
|
30
|
+
expect(sanitizeSeg(' ')).toBe('default');
|
|
31
|
+
expect(sanitizeSeg('home-reviewer')).toBe('home-reviewer');
|
|
32
|
+
expect(sanitizeSeg('中文名')).toBe('default');
|
|
33
|
+
expect(sanitizeSeg('x'.repeat(200))).toHaveLength(80);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('engineId 也走安全编码,防注入路径段', () => {
|
|
37
|
+
expect(resolvePoolDir('/data', '../../etc', 'p')).toBe('/data/engines/etc/p');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/execution/engine/__tests__/registry.test.ts
|
|
2
|
+
//
|
|
3
|
+
// registry 专属测试(P1 验收 4):注册/获取/listEngines/hasEngine/
|
|
4
|
+
// 未注册 id 报 engine_not_found(错误文案含已注册清单——错误规格表第 1 行契约)。
|
|
5
|
+
|
|
6
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
|
|
8
|
+
import type { EnginePort, RunContext } from "../port.ts";
|
|
9
|
+
import {
|
|
10
|
+
clearEngines,
|
|
11
|
+
DEFAULT_ENGINE_ID,
|
|
12
|
+
EngineNotFoundError,
|
|
13
|
+
getEngine,
|
|
14
|
+
hasEngine,
|
|
15
|
+
listEngines,
|
|
16
|
+
registerEngine,
|
|
17
|
+
} from "../registry.ts";
|
|
18
|
+
import type { AgentTaskSpec, SessionView } from "../types.ts";
|
|
19
|
+
|
|
20
|
+
/** 最小可运行假引擎(完整实现 EnginePort 五面——不 cast,防接口漂移失检)。 */
|
|
21
|
+
function makeFakeEngine(id: string): EnginePort {
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
capabilities: () => ({
|
|
25
|
+
schemaEnforcement: "native",
|
|
26
|
+
steer: "unsupported",
|
|
27
|
+
conversation: "unsupported",
|
|
28
|
+
personaInjection: "prompt",
|
|
29
|
+
eventGranularity: "coarse",
|
|
30
|
+
sandbox: "none",
|
|
31
|
+
sessionRead: "outcome-only",
|
|
32
|
+
resume: "unsupported",
|
|
33
|
+
interrupt: "kill-only",
|
|
34
|
+
permissionMode: "fixed",
|
|
35
|
+
}),
|
|
36
|
+
probe: () =>
|
|
37
|
+
Promise.resolve({ ok: true, engineVersion: "0.0.0-test", checks: [{ name: "stub", ok: true }] }),
|
|
38
|
+
run: (_task: AgentTaskSpec, _ctx: RunContext) =>
|
|
39
|
+
Promise.reject(new Error("fake engine: run not implemented")),
|
|
40
|
+
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
41
|
+
read: (_handle: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
42
|
+
Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("engine registry", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// 测试隔离:registry 是进程级全局状态,防用例间工厂/单例泄漏串扰
|
|
49
|
+
clearEngines();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("registerEngine + getEngine:按 id 取回引擎实例", () => {
|
|
53
|
+
registerEngine("fake", () => makeFakeEngine("fake"));
|
|
54
|
+
const engine = getEngine("fake");
|
|
55
|
+
expect(engine.id).toBe("fake");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("getEngine 惰性单例:同一 id 重复获取返回同一实例(§3.3.1 registry 持 per-engine 单例)", () => {
|
|
59
|
+
let created = 0;
|
|
60
|
+
registerEngine("fake", () => {
|
|
61
|
+
created++;
|
|
62
|
+
return makeFakeEngine("fake");
|
|
63
|
+
});
|
|
64
|
+
const a = getEngine("fake");
|
|
65
|
+
const b = getEngine("fake");
|
|
66
|
+
expect(a).toBe(b);
|
|
67
|
+
expect(created).toBe(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("registerEngine 覆盖同 id:丢弃旧单例,下次 getEngine 用新工厂重建(幂等重注册)", () => {
|
|
71
|
+
registerEngine("fake", () => makeFakeEngine("fake"));
|
|
72
|
+
const first = getEngine("fake");
|
|
73
|
+
registerEngine("fake", () => makeFakeEngine("fake-v2"));
|
|
74
|
+
const second = getEngine("fake");
|
|
75
|
+
expect(second).not.toBe(first);
|
|
76
|
+
expect(second.id).toBe("fake-v2");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("listEngines 返回注册序清单,hasEngine 判注册态(不触发工厂副作用)", () => {
|
|
80
|
+
let created = 0;
|
|
81
|
+
registerEngine("alpha", () => {
|
|
82
|
+
created++;
|
|
83
|
+
return makeFakeEngine("alpha");
|
|
84
|
+
});
|
|
85
|
+
registerEngine("beta", () => makeFakeEngine("beta"));
|
|
86
|
+
expect(listEngines()).toEqual(["alpha", "beta"]);
|
|
87
|
+
expect(hasEngine("alpha")).toBe(true);
|
|
88
|
+
expect(hasEngine("gamma")).toBe(false);
|
|
89
|
+
// hasEngine 不取实例——工厂未执行
|
|
90
|
+
expect(created).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("未注册 id → EngineNotFoundError(code=engine_not_found,文案含已注册清单)", () => {
|
|
94
|
+
registerEngine("pi", () => makeFakeEngine("pi"));
|
|
95
|
+
let caught: unknown;
|
|
96
|
+
try {
|
|
97
|
+
getEngine("zcode");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
caught = err;
|
|
100
|
+
}
|
|
101
|
+
expect(caught).toBeInstanceOf(EngineNotFoundError);
|
|
102
|
+
if (!(caught instanceof EngineNotFoundError)) throw new Error("unreachable");
|
|
103
|
+
expect(caught.code).toBe("engine_not_found");
|
|
104
|
+
expect(caught.engineId).toBe("zcode");
|
|
105
|
+
// 错误规格表第 1 行契约:指向注册表清单 + 配置文件路径
|
|
106
|
+
expect(caught.message).toContain("engine_not_found");
|
|
107
|
+
expect(caught.message).toContain("'zcode'");
|
|
108
|
+
expect(caught.message).toContain("pi");
|
|
109
|
+
expect(caught.message).toContain("frontmatter");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("空注册表时未注册 id 错误不崩(清单为 none)", () => {
|
|
113
|
+
expect(() => getEngine("anything")).toThrow(EngineNotFoundError);
|
|
114
|
+
expect(() => getEngine("anything")).toThrow(/\(none\)/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("DEFAULT_ENGINE_ID 缺省为 'pi'(D9:回填期零风险默认)", () => {
|
|
118
|
+
expect(DEFAULT_ENGINE_ID).toBe("pi");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// routing.test.ts —— P4 配置路由三层 + probe fallback 三守卫 + strict(验收 1/2 的
|
|
2
|
+
// 单测面)。fake probe/getEngine 注入(不依赖真机引擎与真实探针)。
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import type { EnginePort, RunContext } from "../port.ts";
|
|
7
|
+
import { DEFAULT_ENGINE_ID, EngineNotFoundError } from "../registry.ts";
|
|
8
|
+
import { EngineError } from "../common/errors.ts";
|
|
9
|
+
import type { AgentTaskSpec, ProbeReport, SessionView } from "../types.ts";
|
|
10
|
+
import { resolveEngineRouting, routeEngine, type EngineRouteOptions } from "../routing.ts";
|
|
11
|
+
|
|
12
|
+
/** 最小可运行假引擎(probe 结果可注入)。 */
|
|
13
|
+
function makeFakeEngine(id: string, probeOk: boolean): EnginePort {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
capabilities: () => ({
|
|
17
|
+
schemaEnforcement: "emulated",
|
|
18
|
+
steer: "unsupported",
|
|
19
|
+
conversation: "unsupported",
|
|
20
|
+
personaInjection: "prompt",
|
|
21
|
+
eventGranularity: "coarse",
|
|
22
|
+
sandbox: "none",
|
|
23
|
+
sessionRead: "outcome-only",
|
|
24
|
+
resume: "cold",
|
|
25
|
+
interrupt: "kill-only",
|
|
26
|
+
permissionMode: "native",
|
|
27
|
+
}),
|
|
28
|
+
probe: () =>
|
|
29
|
+
Promise.resolve(
|
|
30
|
+
probeOk
|
|
31
|
+
? { ok: true, engineVersion: "1.0.0", checks: [{ name: "stub", ok: true }] }
|
|
32
|
+
: {
|
|
33
|
+
ok: false,
|
|
34
|
+
engineVersion: "",
|
|
35
|
+
checks: [{ name: "binary", ok: false, detail: "missing" }],
|
|
36
|
+
error: { code: "engine_probe_failed", recovery: "reinstall the engine binary and retry the probe" },
|
|
37
|
+
} satisfies ProbeReport,
|
|
38
|
+
),
|
|
39
|
+
run: (_t: AgentTaskSpec, _c: RunContext) => Promise.reject(new Error("fake: run not implemented")),
|
|
40
|
+
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
41
|
+
read: (_h: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
42
|
+
Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** routeEngine 的装配器:注册 {pi:ok, zcode:probeOk} 两引擎。 */
|
|
47
|
+
function makeRoute(overrides?: {
|
|
48
|
+
zcodeProbeOk?: boolean;
|
|
49
|
+
strict?: boolean;
|
|
50
|
+
routing?: Partial<EngineRouteOptions["routing"]>;
|
|
51
|
+
taskModel?: string;
|
|
52
|
+
/** 额外注册第三引擎(probe ok)——fallback 目标为「非 pi 的其他全局默认」用例。 */
|
|
53
|
+
globalEngine?: string;
|
|
54
|
+
}) {
|
|
55
|
+
const engines = new Map<string, EnginePort>();
|
|
56
|
+
engines.set(DEFAULT_ENGINE_ID, makeFakeEngine("pi", true));
|
|
57
|
+
engines.set("zcode", makeFakeEngine("zcode", overrides?.zcodeProbeOk ?? true));
|
|
58
|
+
if (overrides?.globalEngine !== undefined) {
|
|
59
|
+
engines.set(overrides.globalEngine, makeFakeEngine(overrides.globalEngine, true));
|
|
60
|
+
}
|
|
61
|
+
const probeCalls: string[] = [];
|
|
62
|
+
const opts: EngineRouteOptions = {
|
|
63
|
+
routing: overrides?.routing ?? {},
|
|
64
|
+
taskModel: overrides?.taskModel,
|
|
65
|
+
strict: overrides?.strict ?? false,
|
|
66
|
+
probe: (id) => {
|
|
67
|
+
probeCalls.push(id);
|
|
68
|
+
return engines.get(id)!.probe();
|
|
69
|
+
},
|
|
70
|
+
getEngineFn: (id) => {
|
|
71
|
+
const e = engines.get(id);
|
|
72
|
+
if (e === undefined) throw new EngineNotFoundError(id, [...engines.keys()]);
|
|
73
|
+
return e;
|
|
74
|
+
},
|
|
75
|
+
hasEngineFn: (id) => engines.has(id),
|
|
76
|
+
listEnginesFn: () => [...engines.keys()],
|
|
77
|
+
};
|
|
78
|
+
return { opts, probeCalls, engines };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── 三层优先级(验收 1)──
|
|
82
|
+
|
|
83
|
+
describe("resolveEngineRouting:三层优先级", () => {
|
|
84
|
+
it("无任何指定 → 全局缺省 'pi'", () => {
|
|
85
|
+
expect(resolveEngineRouting({})).toEqual({ engineId: "pi", source: "default" });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("全局默认引擎指定(config defaultEngine=zcode)→ zcode", () => {
|
|
89
|
+
expect(resolveEngineRouting({ globalDefaultEngine: "zcode" })).toEqual({ engineId: "zcode", source: "default" });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("frontmatter 指定 > 全局默认", () => {
|
|
93
|
+
expect(resolveEngineRouting({ agentEngine: "zcode", globalDefaultEngine: "pi" })).toEqual({
|
|
94
|
+
engineId: "zcode",
|
|
95
|
+
source: "frontmatter",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("调用参数 > frontmatter > 全局默认(三层全设时调用参数胜)", () => {
|
|
100
|
+
expect(
|
|
101
|
+
resolveEngineRouting({ callEngine: "pi", agentEngine: "zcode", globalDefaultEngine: "zcode" }),
|
|
102
|
+
).toEqual({ engineId: "pi", source: "call" });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("空串视为未指定(AgentCallOpts.engine='' 落下一层)", () => {
|
|
106
|
+
expect(resolveEngineRouting({ callEngine: "", agentEngine: "zcode" })).toEqual({
|
|
107
|
+
engineId: "zcode",
|
|
108
|
+
source: "frontmatter",
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("routeEngine:路由 + 探针 + 守卫编排(验收 1/2)", () => {
|
|
114
|
+
it("缺省 pi:免探(零行为变化口径)直接返回引擎", async () => {
|
|
115
|
+
const { opts, probeCalls, engines } = makeRoute();
|
|
116
|
+
const result = await routeEngine(opts);
|
|
117
|
+
expect(result.engine).toBe(engines.get("pi"));
|
|
118
|
+
expect(result.engineId).toBe("pi");
|
|
119
|
+
expect(result.engineFallback).toBeUndefined();
|
|
120
|
+
expect(probeCalls).toEqual([]); // pi 缺省路径不探(D7 轻量口径)
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("frontmatter 指定 zcode + probe ok:返回 zcode 引擎,无 fallback", async () => {
|
|
124
|
+
const { opts, probeCalls, engines } = makeRoute({ routing: { agentEngine: "zcode" } });
|
|
125
|
+
const result = await routeEngine(opts);
|
|
126
|
+
expect(result.engine).toBe(engines.get("zcode"));
|
|
127
|
+
expect(result.engineId).toBe("zcode");
|
|
128
|
+
expect(result.engineFallback).toBeUndefined();
|
|
129
|
+
expect(probeCalls).toEqual(["zcode"]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("调用参数指定 zcode:覆盖 frontmatter 的 pi(透传链 A7)", async () => {
|
|
133
|
+
const { opts } = makeRoute({ routing: { callEngine: "zcode", agentEngine: "pi" } });
|
|
134
|
+
const result = await routeEngine(opts);
|
|
135
|
+
expect(result.engineId).toBe("zcode");
|
|
136
|
+
expect(result.source).toBe("call");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("未注册 id(调用参数层):engine_not_found,文案含注册清单(前置暴露)", async () => {
|
|
140
|
+
const { opts } = makeRoute({ routing: { callEngine: "nonexistent" } });
|
|
141
|
+
await expect(routeEngine(opts)).rejects.toThrowError(EngineNotFoundError);
|
|
142
|
+
await expect(routeEngine(opts)).rejects.toThrowError(/Registered engines: pi, zcode/);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── fallback 与三守卫(验收 2)──
|
|
146
|
+
|
|
147
|
+
it("frontmatter 指定 zcode + probe 失败 → 路由回 pi + engineFallback 留痕(A9①)", async () => {
|
|
148
|
+
const { opts, engines } = makeRoute({ zcodeProbeOk: false, routing: { agentEngine: "zcode" } });
|
|
149
|
+
const result = await routeEngine(opts);
|
|
150
|
+
expect(result.engine).toBe(engines.get("pi"));
|
|
151
|
+
expect(result.engineId).toBe("pi");
|
|
152
|
+
expect(result.requestedEngineId).toBe("zcode");
|
|
153
|
+
expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("守卫 a:调用参数显式指定 + probe 失败 → 不兜底,报 engine_probe_failed", async () => {
|
|
157
|
+
const { opts } = makeRoute({ zcodeProbeOk: false, routing: { callEngine: "zcode" } });
|
|
158
|
+
const err = await routeEngine(opts).catch((e: unknown) => e);
|
|
159
|
+
expect(err).toBeInstanceOf(EngineError);
|
|
160
|
+
expect((err as EngineError).code).toBe("engine_probe_failed");
|
|
161
|
+
expect((err as EngineError).message).toContain("调用参数显式指定");
|
|
162
|
+
expect((err as EngineError).recovery).toContain("retry the probe");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("守卫 b(首期与 a 合流):AgentTaskSpec.requires 形状预留存在且不参与首期判定", async () => {
|
|
166
|
+
// 形状预留的类型面由 typecheck 守护;行为面:frontmatter 指定(非 call)+ 无 model
|
|
167
|
+
// → 仍走 fallback(守卫 b 独立生效留给 requires 下钻,见 types.ts 注释)
|
|
168
|
+
const { opts } = makeRoute({ zcodeProbeOk: false, routing: { agentEngine: "zcode" } });
|
|
169
|
+
const result = await routeEngine(opts);
|
|
170
|
+
expect(result.engineFallback).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("守卫 c:显式 model + probe 失败 → 不换引擎,报 model_not_available", async () => {
|
|
174
|
+
const { opts } = makeRoute({
|
|
175
|
+
zcodeProbeOk: false,
|
|
176
|
+
routing: { agentEngine: "zcode" },
|
|
177
|
+
taskModel: "builtin:bigmodel-coding-plan/GLM-5.3",
|
|
178
|
+
});
|
|
179
|
+
const err = await routeEngine(opts).catch((e: unknown) => e);
|
|
180
|
+
expect(err).toBeInstanceOf(EngineError);
|
|
181
|
+
expect((err as EngineError).code).toBe("model_not_available");
|
|
182
|
+
expect((err as EngineError).message).toContain("GLM-5.3");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("守卫 c 不误伤:无显式 model 时 probe 失败正常 fallback", async () => {
|
|
186
|
+
const { opts } = makeRoute({ zcodeProbeOk: false, routing: { agentEngine: "zcode" } });
|
|
187
|
+
const result = await routeEngine(opts);
|
|
188
|
+
expect(result.engineId).toBe("pi");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("strict=true:frontmatter 层 probe 失败也直接报 engine_probe_failed(A5)", async () => {
|
|
192
|
+
const { opts } = makeRoute({ zcodeProbeOk: false, strict: true, routing: { agentEngine: "zcode" } });
|
|
193
|
+
const err = await routeEngine(opts).catch((e: unknown) => e);
|
|
194
|
+
expect((err as EngineError).code).toBe("engine_probe_failed");
|
|
195
|
+
expect((err as EngineError).message).toContain("strict");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("全局默认引擎自身 probe 失败(defaultEngine=zcode):fallback 回内置 pi", async () => {
|
|
199
|
+
const { opts } = makeRoute({ zcodeProbeOk: false, routing: { globalDefaultEngine: "zcode" } });
|
|
200
|
+
const result = await routeEngine(opts);
|
|
201
|
+
expect(result.engineId).toBe("pi");
|
|
202
|
+
expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("frontmatter zcode + config 默认同为 zcode + probe 失败:fallback 回 pi(不回退同一坏引擎)", async () => {
|
|
206
|
+
// review MF3 回归:全局默认 === 请求引擎时,fallback 目标不得 = 刚 probe 失败的
|
|
207
|
+
// 引擎(from==to 原地重试坏引擎 + 误导留痕)——回内置缺省 pi
|
|
208
|
+
const { opts, engines } = makeRoute({
|
|
209
|
+
zcodeProbeOk: false,
|
|
210
|
+
routing: { agentEngine: "zcode", globalDefaultEngine: "zcode" },
|
|
211
|
+
});
|
|
212
|
+
const result = await routeEngine(opts);
|
|
213
|
+
expect(result.engine).toBe(engines.get("pi"));
|
|
214
|
+
expect(result.engineId).toBe("pi");
|
|
215
|
+
expect(result.requestedEngineId).toBe("zcode");
|
|
216
|
+
expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("frontmatter zcode + config 默认为其他引擎:probe 失败回全局默认(既有行为不回归)", async () => {
|
|
220
|
+
// 全局默认 ≠ 请求引擎且非 pi:fallback 目标仍是全局默认(相等比较只拦截 from==to 形态)
|
|
221
|
+
const { opts, engines } = makeRoute({
|
|
222
|
+
zcodeProbeOk: false,
|
|
223
|
+
globalEngine: "claude",
|
|
224
|
+
routing: { agentEngine: "zcode", globalDefaultEngine: "claude" },
|
|
225
|
+
});
|
|
226
|
+
const result = await routeEngine(opts);
|
|
227
|
+
expect(result.engine).toBe(engines.get("claude"));
|
|
228
|
+
expect(result.engineId).toBe("claude");
|
|
229
|
+
expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/execution/engine/common/data-dir.ts
|
|
2
|
+
//
|
|
3
|
+
// 引擎数据目录解析(P2)。设计权威源:
|
|
4
|
+
// docs/architecture/subagent-engine-abstraction.md D5/D6——engines 根锚定 getDataDir()
|
|
5
|
+
// 顶层(journal / 隔离池的父目录),extension 写侧与 runtime 校验侧同源推导。
|
|
6
|
+
//
|
|
7
|
+
// 通道调查结论(2026-08-25,P2 实测证据链):
|
|
8
|
+
// - runtime spawn pi 主会话时 RpcClient.buildSafeEnv 从 runtime process.env 继承
|
|
9
|
+
// XYZ_ 前缀白名单变量(rpc-client.ts:14-30),且显式设置 PI_CODING_AGENT_DIR;
|
|
10
|
+
// - dev 模式 Electron main 设置 XYZ_AGENT_DATA_DIR(apps/electron/main/main.ts:122),
|
|
11
|
+
// 经 process-control.ts:262 透传给 runtime → 透传链成立;
|
|
12
|
+
// - prod 默认路径(~/.xyz-agent 缺省)下 runtime 进程 env 中**没有**该变量
|
|
13
|
+
// (shared getDataDir 缺省时不写 env)→ 透传链断——已在 runtime 侧
|
|
14
|
+
// process-manager.ts spawn env 补注入 XYZ_AGENT_DATA_DIR 修复(跨包改动)。
|
|
15
|
+
//
|
|
16
|
+
// 回退语义与分段归属(u0-data-discovery,设计 D2 计划期细化③):缺 env 时回退宿主
|
|
17
|
+
// 数据根(getHostServices().dataRoot()——pi 壳返回 getAgentDir(),尊重
|
|
18
|
+
// PI_CODING_AGENT_DIR 实例隔离,独立 pi 用户直跑 extension 的场景不漂目录)并 warn
|
|
19
|
+
// 一次——不留静默漂移(journal 落点变化必须可观测)。本文件保留 env 优先段与
|
|
20
|
+
// warn-once 语义,壳不重复实现;未 configureCore 即消费 dataRoot 抛
|
|
21
|
+
// core_host_not_configured(显式报错取代缺省静默漂目录)。
|
|
22
|
+
|
|
23
|
+
import { getHostServices } from "../../../core/host-services.ts";
|
|
24
|
+
import { getLogger } from "../../../core/logger.ts";
|
|
25
|
+
|
|
26
|
+
const logger = getLogger("subagents");
|
|
27
|
+
|
|
28
|
+
/** xyz-agent 数据目录 env 名(与 packages/shared/src/paths.ts 的 SSOT 变量同名)。 */
|
|
29
|
+
export const XYZ_DATA_DIR_ENV = "XYZ_AGENT_DATA_DIR";
|
|
30
|
+
|
|
31
|
+
/** 测试隔离专用:重置 warn-once 标记(生产禁用)。 */
|
|
32
|
+
let warned = false;
|
|
33
|
+
export function resetDataDirWarnForTests(): void {
|
|
34
|
+
warned = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 解析引擎数据目录(journal 落盘 / 隔离池的根):
|
|
39
|
+
* 1. process.env.XYZ_AGENT_DATA_DIR(xyz-agent 宿主注入,权威通道);
|
|
40
|
+
* 2. 缺失 → 宿主数据根(getHostServices().dataRoot())+ warn 一次。
|
|
41
|
+
* 每次调用重新解析(env 读取是零成本操作,不缓存路径防测试/宿主切换时读到旧值)。
|
|
42
|
+
*/
|
|
43
|
+
export function getEngineDataDir(
|
|
44
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
45
|
+
warn: (msg: string) => void = defaultWarn,
|
|
46
|
+
): string {
|
|
47
|
+
const fromEnv = env[XYZ_DATA_DIR_ENV]?.trim();
|
|
48
|
+
if (fromEnv !== undefined && fromEnv !== "") return fromEnv;
|
|
49
|
+
|
|
50
|
+
// warn 文案与 fallback 值逐字保留现语义(零行为变化):pi 壳 dataRoot() 现取
|
|
51
|
+
// getAgentDir(),落点与改造前一致;zsw 壳接入后此处自动跟随其数据根。
|
|
52
|
+
const fallback = getHostServices().dataRoot();
|
|
53
|
+
if (!warned) {
|
|
54
|
+
warned = true;
|
|
55
|
+
warn(
|
|
56
|
+
`[engine-data-dir] ${XYZ_DATA_DIR_ENV} is not set; engine journal/pool fall back to the pi agent dir ` +
|
|
57
|
+
`(${fallback}). The xyz-agent host normally injects this env — if you are running inside xyz-agent, ` +
|
|
58
|
+
`check the runtime spawn env; standalone pi installs intentionally use the pi agent dir.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function defaultWarn(msg: string): void {
|
|
65
|
+
logger.warn(msg);
|
|
66
|
+
}
|