@zhushanwen/subagent-core 0.6.0 → 0.7.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 +1 -1
- package/dist/chunk-KXVBIVOU.js +16 -0
- package/dist/chunk-T2SYBW3I.js +24 -0
- package/dist/chunk-VURUAGMM.js +2305 -0
- package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
- package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
- package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
- package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
- package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
- package/dist/execution/engine/engine-discovery-scan.js +20 -0
- package/dist/execution/engine/paths.cjs +6 -23
- package/dist/execution/engine/paths.d.cts +1 -8
- package/dist/execution/engine/paths.d.ts +1 -8
- package/dist/execution/engine/paths.js +1 -1
- package/dist/execution/relay-env.cjs +12 -28
- package/dist/execution/relay-env.d.cts +1 -33
- package/dist/execution/relay-env.d.ts +1 -33
- package/dist/execution/relay-env.js +5 -3
- package/dist/index.cjs +14045 -16059
- package/dist/index.d.cts +1035 -1579
- package/dist/index.d.ts +1035 -1579
- package/dist/index.js +9838 -13800
- package/dist.bundle/index.cjs +28875 -30323
- package/package.json +20 -26
- package/src/__tests__/record-store-last-line.test.ts +16 -7
- package/src/core/error-message.ts +7 -6
- package/src/core/host-services.ts +8 -2
- package/src/core/notify-ports.ts +17 -2
- package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
- package/src/execution/__tests__/delivery-methods.test.ts +179 -237
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -13
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
- package/src/execution/__tests__/gc-timer.test.ts +7 -22
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
- package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
- package/src/execution/__tests__/idle-gc.test.ts +200 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
- package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
- package/src/execution/__tests__/record-store.test.ts +60 -6
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
- package/src/execution/__tests__/round-supervisor.test.ts +293 -0
- package/src/execution/__tests__/session-pending.test.ts +119 -17
- package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
- package/src/execution/__tests__/subagent-service.test.ts +2 -2
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
- package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
- package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
- package/src/execution/agent-registry.ts +6 -2
- package/src/execution/dialog-queue.ts +6 -73
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
- package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
- package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
- package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
- package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
- package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
- package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
- package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
- package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
- package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
- package/src/execution/engine/__tests__/registry.test.ts +206 -1
- package/src/execution/engine/__tests__/routing.test.ts +97 -0
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
- package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
- package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
- package/src/execution/engine/client/client-options.ts +103 -0
- package/src/execution/engine/client/engine-client.ts +687 -0
- package/src/execution/engine/client/mirror.ts +167 -0
- package/src/execution/engine/client/pid-file.ts +392 -0
- package/src/execution/engine/client/reaper.ts +95 -0
- package/src/execution/engine/client/remote-engine.ts +441 -0
- package/src/execution/engine/client/reverse-router.ts +255 -0
- package/src/execution/engine/common/capability-gate.ts +109 -22
- package/src/execution/engine/common/errors.ts +9 -25
- package/src/execution/engine/common/event-journal.ts +4 -50
- package/src/execution/engine/common/journal-replay.ts +3 -1
- package/src/execution/engine/common/kill-chain.ts +18 -251
- package/src/execution/engine/common/nesting-guard.ts +18 -114
- package/src/execution/engine/common/pool-manager.ts +4 -0
- package/src/execution/engine/common/session-view-service.ts +56 -84
- package/src/execution/engine/config.ts +151 -0
- package/src/execution/engine/d8-compat.ts +346 -0
- package/src/execution/engine/engine-discovery-roots.ts +107 -0
- package/src/execution/engine/engine-discovery-scan.ts +269 -0
- package/src/execution/engine/engine-discovery.ts +30 -7
- package/src/execution/engine/engine-inspect-package.ts +282 -0
- package/src/execution/engine/engine-manifest.ts +244 -0
- package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
- package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
- package/src/execution/engine/host/host-bridge.ts +150 -0
- package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
- package/src/execution/engine/host/pi-host-binding.ts +91 -0
- package/src/execution/engine/host/spawned-children.ts +139 -0
- package/src/execution/engine/host-task-spec.ts +29 -2
- package/src/execution/engine/model-validation.ts +55 -2
- package/src/execution/engine/paths.ts +16 -41
- package/src/execution/engine/port.ts +48 -2
- package/src/execution/engine/registry.ts +153 -34
- package/src/execution/engine/routing.ts +225 -52
- package/src/execution/engine/types.ts +25 -200
- package/src/execution/finalize-record.ts +25 -0
- package/src/execution/idle-gc.ts +89 -14
- package/src/execution/lifecycle-manager.ts +10 -7
- package/src/execution/lifecycle-predicates.ts +10 -8
- package/src/execution/record-store.ts +29 -2
- package/src/execution/relay-env.ts +19 -36
- package/src/execution/round-supervisor/domain.ts +89 -0
- package/src/execution/round-supervisor/index.ts +33 -0
- package/src/execution/round-supervisor/notify-accounting.ts +87 -0
- package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
- package/src/execution/round-supervisor/service-binding.test.ts +661 -0
- package/src/execution/round-supervisor/service-binding.ts +273 -0
- package/src/execution/round-supervisor/supervisor.ts +426 -0
- package/src/execution/session-pending.ts +68 -74
- package/src/execution/settled-watchdog.ts +39 -0
- package/src/execution/subagent-service.ts +746 -400
- package/src/execution/subprocess-agent-runner.ts +16 -8
- package/src/execution/types.ts +41 -117
- package/src/execution/ui-channels.ts +13 -214
- package/src/execution/ui-request-handler-factory.ts +7 -4
- package/src/execution/ui-request-observability.ts +2 -2
- package/src/execution/workflow-state-root.ts +67 -0
- package/src/execution/worktree-git-ops.ts +5 -0
- package/src/execution/worktree-manager.ts +11 -1
- package/src/index.ts +55 -26
- package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +144 -59
- package/src/orchestration/models/types.ts +43 -42
- package/src/shared/zcode-model-ref.ts +31 -0
- package/dist/chunk-43ONBFZX.js +0 -240
- package/dist/chunk-A4IVZWVX.js +0 -31
- package/dist/chunk-APZY4IME.js +0 -27
- package/dist/chunk-V3VHZ2VX.js +0 -59
- package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
- package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
- package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
- package/dist/execution/engine/engines/zcode/constants.js +0 -54
- package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
- package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
- package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
- package/dist/execution/engine/engines/zcode/reader.js +0 -9
- package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
- package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
- package/src/__tests__/session-runner.test.ts +0 -69
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
- package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
- package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
- package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
- package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
- package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
- package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
- package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
- package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
- package/src/execution/__tests__/output-collector.test.ts +0 -469
- package/src/execution/__tests__/pi-invocation.test.ts +0 -166
- package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
- package/src/execution/__tests__/rpc-mode.test.ts +0 -89
- package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
- package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
- package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
- package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
- package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
- package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
- package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
- package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
- package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
- package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
- package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
- package/src/execution/__tests__/spawn-args.test.ts +0 -446
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
- package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
- package/src/execution/__tests__/spawned-children.test.ts +0 -236
- package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
- package/src/execution/__tests__/stdin-writer.test.ts +0 -464
- package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
- package/src/execution/__tests__/temp-prompt.test.ts +0 -53
- package/src/execution/__tests__/timeout-integration.test.ts +0 -616
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
- package/src/execution/__tests__/turn-limiter.test.ts +0 -65
- package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
- package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
- package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
- package/src/execution/engine/__tests__/paths.test.ts +0 -39
- package/src/execution/engine/common/schema-emulation.ts +0 -189
- package/src/execution/engine/common/session-view-projection.ts +0 -51
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
- package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
- package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
- package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
- package/src/execution/engine/engines/pi/output-collector.ts +0 -317
- package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
- package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
- package/src/execution/engine/engines/pi/reader.ts +0 -59
- package/src/execution/engine/engines/pi/registration.ts +0 -37
- package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
- package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
- package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
- package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
- package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
- package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
- package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
- package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
- package/src/execution/engine/engines/zcode/connection.ts +0 -598
- package/src/execution/engine/engines/zcode/constants.ts +0 -176
- package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
- package/src/execution/engine/engines/zcode/parser.ts +0 -102
- package/src/execution/engine/engines/zcode/preparer.ts +0 -235
- package/src/execution/engine/engines/zcode/reader.ts +0 -381
- package/src/execution/engine/engines/zcode/registration.ts +0 -37
- package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
- package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
- package/src/execution/round-settlement.ts +0 -93
- package/src/execution/ui-request-queue.ts +0 -215
- package/src/shared/schema-env.ts +0 -44
|
@@ -1,741 +0,0 @@
|
|
|
1
|
-
// engine-model-validation.test.ts —— [u-h2] 引擎感知 model 校验单测。设计权威源:
|
|
2
|
-
// docs/design/timeout-audit-hygiene-batch.md §3.2(D2-1 路由先行 / D2-2 validateModel
|
|
3
|
-
// 可选面 / D2-3 场景 2/3 文案 / D2-4 跨引擎候选)+ §4.2 V2-1~V2-4 可单测部分 +
|
|
4
|
-
// §5.4 P2-1 时序回归(record/emit 顺序)单测锁定。
|
|
5
|
-
//
|
|
6
|
-
// 覆盖:
|
|
7
|
-
// A. chat 路径 execute():路由先行(zcode id + zcode 引擎不再被 pi registry 拒绝,
|
|
8
|
-
// V2-1)+ pi id 用在 zcode 的派发同步期报错(V2-2,场景 2 文案逐句断言)+
|
|
9
|
-
// frontmatter model 归趋(D2-1)+ ctxModel 不透传 + 未实现 validateModel 的
|
|
10
|
-
// 引擎透传兜底(现状语义)。
|
|
11
|
-
// B. pi registry 未命中的跨引擎候选(V2-3 场景 3;唯一命中才提示;大小写错误 id
|
|
12
|
-
// 原文案零回归 V2-4③)。
|
|
13
|
-
// C. workflow 路径 SAR:非 pi 校验调用点(V2-4②成功形态不回归 / V2-4④错误形态
|
|
14
|
-
// 同步期报错,不进 engine.run)。
|
|
15
|
-
// D. P2-1 时序回归:pi 缺省路径 record entry → pending:register 既有顺序锁定;
|
|
16
|
-
// 非 pi 校验失败零 record 零 emit。
|
|
17
|
-
// E. zcode 引擎 validateModel 委托 resolveZcodeModelRef(真实 preparer + 临时 v2
|
|
18
|
-
// config——同一函数两处消费,无重复实现)。
|
|
19
|
-
//
|
|
20
|
-
// mock 策略:node:child_process.spawn mock(pi 原路径不真 spawn);非 pi 引擎用
|
|
21
|
-
// registry 注入的 fake EnginePort(validateModel/listModels 行为可注入);v2 config
|
|
22
|
-
// 用真实临时文件(D 组)。
|
|
23
|
-
|
|
24
|
-
import { spawn } from "node:child_process";
|
|
25
|
-
import * as fs from "node:fs";
|
|
26
|
-
import * as os from "node:os";
|
|
27
|
-
import * as path from "node:path";
|
|
28
|
-
|
|
29
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
30
|
-
|
|
31
|
-
import { configureCore, resetCoreForTests } from "../../core/host-services.ts";
|
|
32
|
-
|
|
33
|
-
vi.mock("node:child_process", async () => {
|
|
34
|
-
const { EventEmitter: EE } = await import("node:events");
|
|
35
|
-
const { PassThrough: PT } = await import("node:stream");
|
|
36
|
-
class FakeChild extends EE {
|
|
37
|
-
pid = 12345;
|
|
38
|
-
stdout = new PT();
|
|
39
|
-
stderr = new PT();
|
|
40
|
-
stdin = new PT();
|
|
41
|
-
killed = false;
|
|
42
|
-
kill(sig?: string): boolean {
|
|
43
|
-
this.killed = true;
|
|
44
|
-
return sig !== undefined;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
spawn: vi.fn(() => new FakeChild()),
|
|
49
|
-
execFile: vi.fn(
|
|
50
|
-
(
|
|
51
|
-
_cmd: string,
|
|
52
|
-
_args: readonly string[],
|
|
53
|
-
_opts: unknown,
|
|
54
|
-
cb: (err: Error | null, stdout?: string, stderr?: string) => void,
|
|
55
|
-
) => cb(new Error("execFile not configured in this test")),
|
|
56
|
-
),
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
import { spawn as spawnMock } from "node:child_process";
|
|
61
|
-
|
|
62
|
-
import type { AgentCallOpts } from "../../orchestration/models/types.ts";
|
|
63
|
-
import type { EnginePort, RunContext } from "../engine/port.ts";
|
|
64
|
-
import { ZcodeEngine } from "../engine/engines/zcode/zcode-engine.ts";
|
|
65
|
-
import { ZcodePrepareError } from "../engine/engines/zcode/preparer.ts";
|
|
66
|
-
import { clearEngines, registerEngine } from "../engine/registry.ts";
|
|
67
|
-
import type {
|
|
68
|
-
EngineCapabilities,
|
|
69
|
-
EngineHandle,
|
|
70
|
-
ProbeReport,
|
|
71
|
-
} from "../engine/types.ts";
|
|
72
|
-
import { ModelConfigService } from "../model-config-service.ts";
|
|
73
|
-
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
74
|
-
import { SubagentService } from "../subagent-service.ts";
|
|
75
|
-
import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
|
|
76
|
-
import type { SubagentService as SubagentServiceType } from "../subagent-service.ts";
|
|
77
|
-
import type { ExecuteOptions } from "../types.ts";
|
|
78
|
-
|
|
79
|
-
const mockSpawn = vi.mocked(spawnMock);
|
|
80
|
-
|
|
81
|
-
// ── 既有 pi registry 内容(pi id 空间)与 zcode id 空间(互不认识——设计 §2.2)──
|
|
82
|
-
|
|
83
|
-
const PI_ID = "pi-org/pi-main";
|
|
84
|
-
const CTX_MODEL: ModelInfo = { id: "pi-main", name: "PI Main", provider: "pi-org", reasoning: false };
|
|
85
|
-
|
|
86
|
-
const ZCODE_DEFAULT = "builtin:bigmodel-coding-plan/GLM-5.3";
|
|
87
|
-
const ZCODE_FLASH = "builtin:bigmodel-coding-plan/GLM-5.3-Flash";
|
|
88
|
-
|
|
89
|
-
// ============================================================
|
|
90
|
-
// fake 引擎(validateModel/listModels 行为可注入)
|
|
91
|
-
// ============================================================
|
|
92
|
-
|
|
93
|
-
const ZCODE_LIKE_CAPS: EngineCapabilities = {
|
|
94
|
-
schemaEnforcement: "emulated",
|
|
95
|
-
steer: "unsupported",
|
|
96
|
-
conversation: "unsupported",
|
|
97
|
-
personaInjection: "prompt",
|
|
98
|
-
eventGranularity: "coarse",
|
|
99
|
-
sandbox: "none",
|
|
100
|
-
sessionRead: "full",
|
|
101
|
-
resume: "cold",
|
|
102
|
-
interrupt: "kill-only",
|
|
103
|
-
permissionMode: "native",
|
|
104
|
-
maxTurns: false,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
interface ValidatingEngineOpts {
|
|
108
|
-
id: string;
|
|
109
|
-
models: string[];
|
|
110
|
-
/** validateModel 对非清单 id 抛出的错误(缺省按 model_not_available 形态)。 */
|
|
111
|
-
validateModelError?: Error;
|
|
112
|
-
/** false = 不实现 validateModel(透传兜底形态用例)。 */
|
|
113
|
-
withValidateModel?: boolean;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
class ValidatingFakeEngine implements EnginePort {
|
|
117
|
-
readonly id: string;
|
|
118
|
-
readonly models: string[];
|
|
119
|
-
readonly runs: Array<{ task: AgentCallOpts; ctx: RunContext }> = [];
|
|
120
|
-
private readonly validateModelError: Error | undefined;
|
|
121
|
-
private readonly withValidateModel: boolean;
|
|
122
|
-
|
|
123
|
-
constructor(opts: ValidatingEngineOpts) {
|
|
124
|
-
this.id = opts.id;
|
|
125
|
-
this.models = opts.models;
|
|
126
|
-
this.validateModelError = opts.validateModelError;
|
|
127
|
-
this.withValidateModel = opts.withValidateModel !== false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
capabilities(): EngineCapabilities {
|
|
131
|
-
return ZCODE_LIKE_CAPS;
|
|
132
|
-
}
|
|
133
|
-
async probe(): Promise<ProbeReport> {
|
|
134
|
-
return { ok: true, engineVersion: "fake", checks: [{ name: "bin", ok: true }] };
|
|
135
|
-
}
|
|
136
|
-
async run(task: AgentCallOpts, ctx: RunContext): Promise<{ handle: EngineHandle; outcome: { engineId: string; content: string } }> {
|
|
137
|
-
this.runs.push({ task, ctx });
|
|
138
|
-
return {
|
|
139
|
-
handle: { data: { v: 1, engineId: this.id, sessionRef: {}, poolKey: "shared", adapterVersion: "test" } },
|
|
140
|
-
outcome: { engineId: this.id, content: "ok" },
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
async interact(_handle: EngineHandle, _action: unknown): Promise<{ ok: false; code: string; message: string }> {
|
|
144
|
-
return { ok: false, code: "engine_capability_unsupported", message: "fake" };
|
|
145
|
-
}
|
|
146
|
-
async read(_handle: EngineHandle): Promise<{ engineId: string; turns: never[]; source: "outcome-only" }> {
|
|
147
|
-
return { engineId: this.id, turns: [], source: "outcome-only" };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
listModels(): Array<{ id: string; name?: string }> {
|
|
151
|
-
return this.models.map((id) => ({ id }));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
validateModel(modelRef: string | undefined): { canonicalRef: string } {
|
|
155
|
-
if (!this.withValidateModel) throw new Error("validateModel not implemented on this fake");
|
|
156
|
-
if (this.validateModelError !== undefined) throw this.validateModelError;
|
|
157
|
-
if (modelRef === undefined || modelRef.trim() === "") return { canonicalRef: this.models[0]! };
|
|
158
|
-
if (this.models.includes(modelRef)) return { canonicalRef: modelRef };
|
|
159
|
-
throw new Error(`[model_not_available] unknown model "${modelRef}" (fake registry)`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ============================================================
|
|
164
|
-
// chat 路径装配(chat-engine-routing.test.ts 同款范式)
|
|
165
|
-
// ============================================================
|
|
166
|
-
|
|
167
|
-
function fakePiRegistry(): ModelRegistryLike {
|
|
168
|
-
const AVAILABLE: ModelInfo[] = [CTX_MODEL];
|
|
169
|
-
return {
|
|
170
|
-
getAvailable: () => AVAILABLE,
|
|
171
|
-
find: (provider: string, id: string) => AVAILABLE.find((m) => m.provider === provider && m.id === id),
|
|
172
|
-
hasConfiguredAuth: () => true,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
interface ChatSetup {
|
|
177
|
-
service: SubagentService;
|
|
178
|
-
zcode: ValidatingFakeEngine;
|
|
179
|
-
pi: {
|
|
180
|
-
sendMessage: ReturnType<typeof vi.fn>;
|
|
181
|
-
appendEntry: ReturnType<typeof vi.fn>;
|
|
182
|
-
events: { emit: ReturnType<typeof vi.fn> };
|
|
183
|
-
};
|
|
184
|
-
/** 统一时间线(appendEntry 与 events.emit 的调用顺序——P2-1 断言用)。 */
|
|
185
|
-
eventLog: string[];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function setupChat(agentDir: string, opts?: { defaultEngine?: string; extraEngine?: ValidatingFakeEngine }): ChatSetup {
|
|
189
|
-
const zcode = new ValidatingFakeEngine({ id: "zcode", models: [ZCODE_DEFAULT, ZCODE_FLASH] });
|
|
190
|
-
registerEngine("zcode", () => zcode);
|
|
191
|
-
if (opts?.extraEngine !== undefined) registerEngine(opts.extraEngine.id, () => opts.extraEngine!);
|
|
192
|
-
const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
|
|
193
|
-
modelService.initModel({
|
|
194
|
-
modelRegistry: fakePiRegistry(),
|
|
195
|
-
sessionId: "test-session",
|
|
196
|
-
ctxModel: CTX_MODEL,
|
|
197
|
-
});
|
|
198
|
-
const eventLog: string[] = [];
|
|
199
|
-
const appendEntry = vi.fn((type: string) => {
|
|
200
|
-
eventLog.push(`entry:${type}`);
|
|
201
|
-
});
|
|
202
|
-
const emit = vi.fn((channel: string) => {
|
|
203
|
-
eventLog.push(`emit:${channel}`);
|
|
204
|
-
});
|
|
205
|
-
const pi = { sendMessage: vi.fn(), appendEntry, events: { emit } };
|
|
206
|
-
const service = new SubagentService({ cwd: agentDir, modelService });
|
|
207
|
-
service.initSession({ pi, sessionId: "test-session" });
|
|
208
|
-
return { service, zcode, pi, eventLog };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function writeGlobalConfig(agentDir: string, cfg: Record<string, unknown>): void {
|
|
212
|
-
fs.mkdirSync(path.join(agentDir, "subagents"), { recursive: true });
|
|
213
|
-
fs.writeFileSync(path.join(agentDir, "subagents", "config.json"), JSON.stringify(cfg));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function writeAgentMd(dir: string, frontmatter: string): string {
|
|
217
|
-
fs.mkdirSync(path.join(dir, "agents"), { recursive: true });
|
|
218
|
-
const p = path.join(dir, "agents", "fm-agent.md");
|
|
219
|
-
fs.writeFileSync(p, `---\n${frontmatter}\n---\nbody\n`);
|
|
220
|
-
return p;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function baseOpts(agentDir: string, extra: Partial<ExecuteOptions> = {}): ExecuteOptions {
|
|
224
|
-
return { task: "do work", slug: "validation-test", cwd: agentDir, ctxModel: CTX_MODEL, ...extra };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* 等待 detached finalize 链完全静止后再离开用例(flake 加固 2026-09-05)。
|
|
229
|
-
*
|
|
230
|
-
* fake run 立即 resolve → kickOffEngineRun 的 detached 链推进 finalizeEngineOutcome →
|
|
231
|
-
* finalizeRecord(内含 writeManifest——fs.promises 真异步,threadpool 落盘)→
|
|
232
|
-
* notifyComplete → pi.sendMessage。sendMessage 被调 = runEngineTask 已 await 完成
|
|
233
|
-
* = 链上全部异步落盘已 resolve(与 chat-engine-routing 终态通知同一等待语义)。
|
|
234
|
-
*
|
|
235
|
-
* 缺失本等待时,链的在途写盘会跨过 afterEach 的 fs.rmSync——threadpool 写与主线程
|
|
236
|
-
* 删除目录树竞争,rmSync 以 ENOTEMPTY 失败打翻清理(单文件跑时间隙足够恒绿,
|
|
237
|
-
* 全量并发负载下写延迟跨窗即偶发——本文件曾因此 6 轮中 2 轮失败)。
|
|
238
|
-
*/
|
|
239
|
-
async function settleEngineRunChain(pi: ChatSetup["pi"]): Promise<void> {
|
|
240
|
-
await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalled());
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
describe("chat 路径:路由先行 + 按目标引擎校验 model(u-h2 V2-1/V2-2)", () => {
|
|
244
|
-
let agentDir: string;
|
|
245
|
-
|
|
246
|
-
beforeEach(() => {
|
|
247
|
-
configureCore({ dataRoot: () => "/fake-model-validation-data-root", log: () => {} });
|
|
248
|
-
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "engine-model-validation-"));
|
|
249
|
-
writeGlobalConfig(agentDir, { version: 1, maxConcurrent: 6 });
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
afterEach(() => {
|
|
253
|
-
clearEngines();
|
|
254
|
-
resetCoreForTests();
|
|
255
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
256
|
-
vi.clearAllMocks();
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// ── V2-1(原 F2-A):zcode 合法模型不再被 pi registry 误导性拒绝 ──
|
|
260
|
-
|
|
261
|
-
it("[V2-1a] defaultEngine=zcode 不传 engine + zcode id → 成功派发(修复前报 not a registry entry)", async () => {
|
|
262
|
-
writeGlobalConfig(agentDir, { version: 1, maxConcurrent: 6, defaultEngine: "zcode" });
|
|
263
|
-
const { service, zcode, pi } = setupChat(agentDir);
|
|
264
|
-
|
|
265
|
-
const handle = await service.execute(baseOpts(agentDir, { model: ZCODE_FLASH }));
|
|
266
|
-
// record 身份字段产生于 execute 同步段——resolve 后立即快照:fake run 立即 resolve,
|
|
267
|
-
// 轮询窗口内 finalize/archive 会把 record 移出 running(waitFor 后再读是负载敏感竞态)
|
|
268
|
-
const rec = service["collectRecords"](10, "running").find((r) => r.id === handle.subagentId);
|
|
269
|
-
expect(rec?.engine).toBe("zcode");
|
|
270
|
-
expect(rec?.model).toBe(ZCODE_FLASH);
|
|
271
|
-
// 引擎派发事实单独确定性等待(runs 数组只增不删)
|
|
272
|
-
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
273
|
-
expect(zcode.runs[0]!.task.model).toBe(ZCODE_FLASH);
|
|
274
|
-
// 链静止后再离开用例(afterEach rmSync 竞争防御,见 helper 注释)
|
|
275
|
-
await settleEngineRunChain(pi);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it("[V2-1b] 显式 engine='zcode' + zcode id → 成功派发", async () => {
|
|
279
|
-
const { service, zcode, pi } = setupChat(agentDir);
|
|
280
|
-
const handle = await service.execute(baseOpts(agentDir, { engine: "zcode", model: ZCODE_FLASH }));
|
|
281
|
-
// 同步段快照(竞态依据同 V2-1a)
|
|
282
|
-
const rec = service["collectRecords"](10, "running").find((r) => r.id === handle.subagentId);
|
|
283
|
-
expect(rec?.engine).toBe("zcode");
|
|
284
|
-
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
285
|
-
expect(zcode.runs[0]!.task.model).toBe(ZCODE_FLASH);
|
|
286
|
-
await settleEngineRunChain(pi);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// ── V2-2(原 F2-B):pi id 用在 zcode → 派发同步期明确报错(场景 2 文案逐句)──
|
|
290
|
-
|
|
291
|
-
it("[V2-2] pi id 派发 zcode → 同步期 EngineModelMismatchError(registry 独立 + 清单 + 修正动作)", async () => {
|
|
292
|
-
const { service, zcode } = setupChat(agentDir);
|
|
293
|
-
|
|
294
|
-
const err = await service
|
|
295
|
-
.execute(baseOpts(agentDir, { engine: "zcode", model: PI_ID }))
|
|
296
|
-
.catch((e: unknown) => e);
|
|
297
|
-
|
|
298
|
-
expect(err).toBeInstanceOf(Error);
|
|
299
|
-
expect((err as Error).name).toBe("EngineModelMismatchError");
|
|
300
|
-
expect((err as { code?: string }).code).toBe("model_not_available");
|
|
301
|
-
const msg = (err as Error).message;
|
|
302
|
-
// ① 点破引擎与模型不配套 + registry 独立
|
|
303
|
-
expect(msg).toContain(`model '${PI_ID}' is not available on engine 'zcode'.`);
|
|
304
|
-
expect(msg).toContain(
|
|
305
|
-
"Engine registries are independent — ids in <available_provider_models> (pi registry) do NOT apply to 'zcode' dispatches.",
|
|
306
|
-
);
|
|
307
|
-
// ② 目标引擎可用清单(数据源 listModels,带凭据)
|
|
308
|
-
expect(msg).toContain("zcode models with configured credentials:");
|
|
309
|
-
expect(msg).toContain(ZCODE_DEFAULT);
|
|
310
|
-
expect(msg).toContain(ZCODE_FLASH);
|
|
311
|
-
// ③ 修正动作:清单重试 + 按引擎区分的省略语义(zcode 引擎缺省,非「继承主 agent」)
|
|
312
|
-
expect(msg).toContain(
|
|
313
|
-
`👉 Retry with one of the above (exact string), or omit \`model\` to use the 'zcode' engine default (${ZCODE_DEFAULT}).`,
|
|
314
|
-
);
|
|
315
|
-
expect(msg).not.toContain("inherit the main agent model");
|
|
316
|
-
// recovery 字段(错误 → 权威源 → 重试闭环)
|
|
317
|
-
expect((err as { recovery?: string }).recovery).toContain(`'zcode' engine default (${ZCODE_DEFAULT})`);
|
|
318
|
-
// 同步期拒绝:零执行副作用
|
|
319
|
-
expect(zcode.runs.length).toBe(0);
|
|
320
|
-
expect(mockSpawn).not.toHaveBeenCalled();
|
|
321
|
-
expect(service["collectRecords"](10, "all")).toHaveLength(0);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// ── D2-1 逐层语义:frontmatter model / ctxModel / thinkingLevel ──
|
|
325
|
-
|
|
326
|
-
it("[D2-1] frontmatter model(agent .md)+ engine: zcode → 原样透传给 taskSpec(作者声明不忽略)", async () => {
|
|
327
|
-
const agentRef = writeAgentMd(agentDir, `name: fm-agent\ndescription: d\nengine: zcode\nmodel: ${ZCODE_FLASH}`);
|
|
328
|
-
const { service, zcode, pi } = setupChat(agentDir);
|
|
329
|
-
|
|
330
|
-
await service.execute(baseOpts(agentDir, { agent: agentRef }));
|
|
331
|
-
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
332
|
-
|
|
333
|
-
expect(zcode.runs[0]!.task.model).toBe(ZCODE_FLASH);
|
|
334
|
-
await settleEngineRunChain(pi);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("[D2-1] frontmatter model 配错(pi id + engine: zcode)→ 同步期场景 2 报错(不落引擎缺省静默续跑)", async () => {
|
|
338
|
-
const agentRef = writeAgentMd(agentDir, `name: fm-agent\ndescription: d\nengine: zcode\nmodel: ${PI_ID}`);
|
|
339
|
-
const { service, zcode } = setupChat(agentDir);
|
|
340
|
-
|
|
341
|
-
const err = await service.execute(baseOpts(agentDir, { agent: agentRef })).catch((e: unknown) => e);
|
|
342
|
-
expect((err as Error).message).toContain(`model '${PI_ID}' is not available on engine 'zcode'.`);
|
|
343
|
-
expect(zcode.runs.length).toBe(0);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("[D2-1] 无显式 model → ctxModel 不透传;validateModel(undefined) 裁决引擎缺省进 record", async () => {
|
|
347
|
-
writeGlobalConfig(agentDir, { version: 1, maxConcurrent: 6, defaultEngine: "zcode" });
|
|
348
|
-
const { service, zcode, pi } = setupChat(agentDir);
|
|
349
|
-
|
|
350
|
-
const handle = await service.execute(baseOpts(agentDir));
|
|
351
|
-
// 同步段快照 record 身份(竞态依据同 V2-1a)
|
|
352
|
-
const rec = service["collectRecords"](10, "running").find((r) => r.id === handle.subagentId);
|
|
353
|
-
expect(rec?.model).toBe(ZCODE_DEFAULT);
|
|
354
|
-
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
355
|
-
|
|
356
|
-
// taskSpec.model 不带主 agent 的 pi id(引擎缺省语义归引擎)
|
|
357
|
-
expect(zcode.runs[0]!.task.model).toBeUndefined();
|
|
358
|
-
await settleEngineRunChain(pi);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("[现状语义] 未实现 validateModel 的引擎 → model 原样透传(prepare 期兜底,零强制接入)", async () => {
|
|
362
|
-
// plain object 构造(无 validateModel/listModels 成员——未来引擎未接入校验面的形态)
|
|
363
|
-
const bare: EnginePort = {
|
|
364
|
-
id: "bare",
|
|
365
|
-
capabilities: () => ZCODE_LIKE_CAPS,
|
|
366
|
-
probe: () => Promise.resolve({ ok: true, engineVersion: "bare", checks: [{ name: "bin", ok: true }] }),
|
|
367
|
-
run: (task) => {
|
|
368
|
-
bareRunTasks.push(task);
|
|
369
|
-
return Promise.resolve({
|
|
370
|
-
handle: { data: { v: 1, engineId: "bare", sessionRef: {}, poolKey: "shared", adapterVersion: "test" } },
|
|
371
|
-
outcome: { engineId: "bare", content: "ok" },
|
|
372
|
-
});
|
|
373
|
-
},
|
|
374
|
-
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "bare" }),
|
|
375
|
-
read: () => Promise.resolve({ engineId: "bare", turns: [], source: "outcome-only" }),
|
|
376
|
-
};
|
|
377
|
-
const bareRunTasks: AgentCallOpts[] = [];
|
|
378
|
-
registerEngine("bare", () => bare);
|
|
379
|
-
|
|
380
|
-
const { service, pi } = setupChat(agentDir);
|
|
381
|
-
await service.execute(baseOpts(agentDir, { engine: "bare", model: "future-eng/future-model" }));
|
|
382
|
-
await vi.waitFor(() => expect(bareRunTasks.length).toBe(1));
|
|
383
|
-
|
|
384
|
-
expect(bareRunTasks[0]!.model).toBe("future-eng/future-model");
|
|
385
|
-
await settleEngineRunChain(pi);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("[V2-4①] pi id 派发 pi 引擎(现状主路径)行为不变:resolveModel 正常解析 + spawn 启动", async () => {
|
|
389
|
-
const { service } = setupChat(agentDir);
|
|
390
|
-
// mock spawn 计数文件级共享(跨用例累积)——以计数差归因本用例(单调递增,可确定性等待)
|
|
391
|
-
const spawnCallsBefore = mockSpawn.mock.calls.length;
|
|
392
|
-
const handle = await service.execute(baseOpts(agentDir, { model: PI_ID }));
|
|
393
|
-
|
|
394
|
-
// record 身份字段产生于 execute 同步段——resolve 后立即快照(竞态依据同 V2-1a)。
|
|
395
|
-
// pi 路径的 kickOffBackground → runSpawn 是 detached 链,不作为断言前置条件。
|
|
396
|
-
const rec = service["collectRecords"](10, "running").find((r) => r.id === handle.subagentId);
|
|
397
|
-
expect(rec?.engine).toBeUndefined(); // pi 缺省不盖章(D5 零变化)
|
|
398
|
-
expect(rec?.model).toBe(PI_ID);
|
|
399
|
-
|
|
400
|
-
await vi.waitFor(() => expect(mockSpawn.mock.calls.length).toBeGreaterThan(spawnCallsBefore));
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
// ============================================================
|
|
405
|
-
// B. pi 未命中跨引擎候选(V2-3 场景 3 + V2-4③ 零回归)
|
|
406
|
-
// ============================================================
|
|
407
|
-
|
|
408
|
-
describe("pi registry 未命中的跨引擎候选(u-h2 D2-4)", () => {
|
|
409
|
-
let agentDir: string;
|
|
410
|
-
|
|
411
|
-
beforeEach(() => {
|
|
412
|
-
configureCore({ dataRoot: () => "/fake-cross-engine-data-root", log: () => {} });
|
|
413
|
-
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "cross-engine-"));
|
|
414
|
-
writeGlobalConfig(agentDir, { version: 1, maxConcurrent: 6 });
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
afterEach(() => {
|
|
418
|
-
clearEngines();
|
|
419
|
-
resetCoreForTests();
|
|
420
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
421
|
-
vi.clearAllMocks();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("[V2-3] defaultEngine=pi + zcode id(pi 未命中,zcode 清单唯一命中)→ 错误附跨引擎候选段", async () => {
|
|
425
|
-
const { service } = setupChat(agentDir);
|
|
426
|
-
|
|
427
|
-
const err = await service.execute(baseOpts(agentDir, { model: ZCODE_FLASH })).catch((e: unknown) => e);
|
|
428
|
-
const msg = (err as Error).message;
|
|
429
|
-
|
|
430
|
-
// 既有全等裁决文案保留(V2-4③ 零回归面;首行带 (paramOverride) 来源前缀)
|
|
431
|
-
expect(msg).toContain(`Model "${ZCODE_FLASH}" (paramOverride) is not a registry entry.`);
|
|
432
|
-
// 场景 3 追加段:点破归属引擎 + 修正动作
|
|
433
|
-
expect(msg).toContain(`This id matches the registry of engine 'zcode'.`);
|
|
434
|
-
expect(msg).toContain(
|
|
435
|
-
`👉 Retry with engine: 'zcode', or use a pi model from <available_provider_models>, ` +
|
|
436
|
-
`or omit \`model\` to inherit the main agent model.`,
|
|
437
|
-
);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it("[D2-4] 多引擎清单同时命中 → 不附候选(唯一命中才提示,避免误导)", async () => {
|
|
441
|
-
const second = new ValidatingFakeEngine({ id: "zcode2", models: [ZCODE_FLASH] });
|
|
442
|
-
const { service } = setupChat(agentDir, { extraEngine: second });
|
|
443
|
-
|
|
444
|
-
const err = await service.execute(baseOpts(agentDir, { model: ZCODE_FLASH })).catch((e: unknown) => e);
|
|
445
|
-
expect((err as Error).message).toContain("is not a registry entry");
|
|
446
|
-
expect((err as Error).message).not.toContain("matches the registry of engine");
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it("[V2-4③] pi id 大小写错误 → 仍报全等裁决错误(case variant 建议),不附跨引擎段", async () => {
|
|
450
|
-
const { service } = setupChat(agentDir);
|
|
451
|
-
|
|
452
|
-
const err = await service.execute(baseOpts(agentDir, { model: "pi-org/PI-MAIN" })).catch((e: unknown) => e);
|
|
453
|
-
const msg = (err as Error).message;
|
|
454
|
-
expect(msg).toContain('Model "pi-org/PI-MAIN" (paramOverride) is not a registry entry.');
|
|
455
|
-
expect(msg).toContain("case variant");
|
|
456
|
-
expect(msg).not.toContain("matches the registry of engine");
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it("[D2-4] 跨引擎匹配按 strip thinking 后缀后的全名全等——含合法后缀的 id 仍可命中", async () => {
|
|
460
|
-
const { service } = setupChat(agentDir);
|
|
461
|
-
const err = await service.execute(baseOpts(agentDir, { model: `${ZCODE_FLASH}:max` })).catch((e: unknown) => e);
|
|
462
|
-
expect((err as Error).message).toContain("This id matches the registry of engine 'zcode'.");
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// ============================================================
|
|
467
|
-
// C. workflow 路径 SAR:非 pi 校验调用点(V2-4②/④)
|
|
468
|
-
// ============================================================
|
|
469
|
-
|
|
470
|
-
function makeSarFakeZcode(opts?: { withValidateModel?: boolean }): { engine: EnginePort; runs: Array<{ task: AgentCallOpts }> } {
|
|
471
|
-
const engine = new ValidatingFakeEngine({ id: "zcode", models: [ZCODE_DEFAULT, ZCODE_FLASH], ...(opts ?? {}) });
|
|
472
|
-
if (opts?.withValidateModel === false) {
|
|
473
|
-
// 原型方法不可 deleteProperty——以同形状 plain object 替换(成员裁剪后的形态)
|
|
474
|
-
const bare: EnginePort = {
|
|
475
|
-
id: "zcode",
|
|
476
|
-
capabilities: () => engine!.capabilities(),
|
|
477
|
-
probe: () => engine!.probe(),
|
|
478
|
-
run: (task, ctx) => engine!.run(task, ctx),
|
|
479
|
-
interact: (handle, action) => engine!.interact(handle, action),
|
|
480
|
-
read: (handle) => engine!.read(handle),
|
|
481
|
-
};
|
|
482
|
-
return { engine: bare, runs: engine.runs };
|
|
483
|
-
}
|
|
484
|
-
return { engine, runs: engine.runs };
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function makeMockPiService() {
|
|
488
|
-
const executeAndAwait = vi.fn(async (): Promise<{ content: string; toolCalls: never[] }> => ({
|
|
489
|
-
content: "from-pi",
|
|
490
|
-
toolCalls: [],
|
|
491
|
-
}));
|
|
492
|
-
// [D4 聚合连带] dev SAR 构造经 createPiEngine(() => service.asEngineService) 取服务
|
|
493
|
-
// 视图,PiEngine.run 的 workflow 域委托消费 service.executeAndAwait——mock 需同时
|
|
494
|
-
// 提供两者(asEngineService 视图与本体共享同一 spy)。
|
|
495
|
-
const service = { executeAndAwait, asEngineService: { executeAndAwait } };
|
|
496
|
-
return { service: service as unknown as SubagentServiceType, executeAndAwait };
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function sarOpts(overrides?: Partial<AgentCallOpts>): AgentCallOpts {
|
|
500
|
-
return { prompt: "task", description: "validation-test", ...overrides };
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
describe("workflow 路径 SAR:非 pi 引擎派发同步期 model 校验(u-h2 V2-4②/④)", () => {
|
|
504
|
-
let tmpRoot: string;
|
|
505
|
-
let agentDir: string;
|
|
506
|
-
let prevDataDirEnv: string | undefined;
|
|
507
|
-
|
|
508
|
-
beforeEach(() => {
|
|
509
|
-
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sar-model-validation-"));
|
|
510
|
-
agentDir = path.join(tmpRoot, "pi-agent");
|
|
511
|
-
fs.mkdirSync(agentDir, { recursive: true });
|
|
512
|
-
prevDataDirEnv = process.env["XYZ_AGENT_DATA_DIR"];
|
|
513
|
-
process.env["XYZ_AGENT_DATA_DIR"] = path.join(tmpRoot, "engine-data");
|
|
514
|
-
clearEngines();
|
|
515
|
-
fs.mkdirSync(path.join(agentDir, "subagents"), { recursive: true });
|
|
516
|
-
fs.writeFileSync(
|
|
517
|
-
path.join(agentDir, "subagents", "config.json"),
|
|
518
|
-
JSON.stringify({ version: 1, maxConcurrent: 6 }),
|
|
519
|
-
);
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
afterEach(() => {
|
|
523
|
-
if (prevDataDirEnv === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
|
|
524
|
-
else process.env["XYZ_AGENT_DATA_DIR"] = prevDataDirEnv;
|
|
525
|
-
clearEngines();
|
|
526
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
527
|
-
vi.clearAllMocks();
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it("[V2-4②] agent({engine:'zcode', model:<zcode id>}) → 不回归:engine.run 收到原样 model", async () => {
|
|
531
|
-
const zcode = makeSarFakeZcode();
|
|
532
|
-
registerEngine("zcode", () => zcode.engine);
|
|
533
|
-
const pi = makeMockPiService();
|
|
534
|
-
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
535
|
-
|
|
536
|
-
const result = await sar.run(sarOpts({ engine: "zcode", model: ZCODE_FLASH }), new AbortController().signal);
|
|
537
|
-
|
|
538
|
-
expect(result.error).toBeUndefined();
|
|
539
|
-
expect(zcode.runs).toHaveLength(1);
|
|
540
|
-
expect(zcode.runs[0]!.task.model).toBe(ZCODE_FLASH);
|
|
541
|
-
expect(pi.executeAndAwait).not.toHaveBeenCalled();
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
it("[V2-4④] agent({engine:'zcode', model:<pi id>}) → 同步期场景 2 错误入 result.error,不进 engine.run", async () => {
|
|
545
|
-
const zcode = makeSarFakeZcode();
|
|
546
|
-
registerEngine("zcode", () => zcode.engine);
|
|
547
|
-
const pi = makeMockPiService();
|
|
548
|
-
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
549
|
-
|
|
550
|
-
const result = await sar.run(sarOpts({ engine: "zcode", model: PI_ID }), new AbortController().signal);
|
|
551
|
-
|
|
552
|
-
// 「不 reject」契约:错误入 result.error(与 chat 路径同文案)
|
|
553
|
-
expect(result.error).toContain(`model '${PI_ID}' is not available on engine 'zcode'.`);
|
|
554
|
-
expect(result.error).toContain("Engine registries are independent");
|
|
555
|
-
expect(result.error).toContain(ZCODE_FLASH);
|
|
556
|
-
expect(result.error).toContain("engine default");
|
|
557
|
-
// 同步期拒绝:未进入引擎执行(与 chat 路径 V2-2 同语义,无 prepare 期晚炸)
|
|
558
|
-
expect(zcode.runs).toHaveLength(0);
|
|
559
|
-
expect(pi.executeAndAwait).not.toHaveBeenCalled();
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
it("[现状语义] SAR fake 引擎未实现 validateModel → 校验透传,既有路由测试场景零回归", async () => {
|
|
563
|
-
const zcode = makeSarFakeZcode({ withValidateModel: false });
|
|
564
|
-
registerEngine("zcode", () => zcode.engine);
|
|
565
|
-
const pi = makeMockPiService();
|
|
566
|
-
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
567
|
-
|
|
568
|
-
const result = await sar.run(sarOpts({ engine: "zcode", model: "any-provider/any-model" }), new AbortController().signal);
|
|
569
|
-
|
|
570
|
-
expect(result.error).toBeUndefined();
|
|
571
|
-
expect(zcode.runs).toHaveLength(1);
|
|
572
|
-
expect(zcode.runs[0]!.task.model).toBe("any-provider/any-model");
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it("[V2-4①] workflow pi 路径不受影响:pi id 走 executeAndAwait 委托", async () => {
|
|
576
|
-
const pi = makeMockPiService();
|
|
577
|
-
const sar = new SubprocessAgentRunner({ subagentService: pi.service });
|
|
578
|
-
|
|
579
|
-
const result = await sar.run(sarOpts({ model: PI_ID }), new AbortController().signal);
|
|
580
|
-
|
|
581
|
-
expect(result.content).toBe("from-pi");
|
|
582
|
-
expect(pi.executeAndAwait).toHaveBeenCalledTimes(1);
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
// ============================================================
|
|
587
|
-
// D. P2-1 时序回归:record 注册 / emit 顺序
|
|
588
|
-
//
|
|
589
|
-
// 确定性依据(flake 加固 2026-09-05):record entry(createRecordForMode → store.register
|
|
590
|
-
// → pi.appendEntry,record-store.ts 同步调用)与 pending:register emit
|
|
591
|
-
//(emitPendingRegister)都产生于 execute()/executeViaEngine() 的同一同步段——
|
|
592
|
-
// service.execute() 的 Promise resolve 时两笔必然已写入 eventLog。
|
|
593
|
-
//
|
|
594
|
-
// 因此本组断言【不等待 detached 引擎链】(不 waitFor zcode.runs / mockSpawn):那条链
|
|
595
|
-
// 会继续向 eventLog 追加噪声(archive 的第二笔 subagent-record entry、pending:unregister),
|
|
596
|
-
// waitFor 的轮询窗口让断言评估时机落在链推进的任意交错点——重负载下即偶发失败形态
|
|
597
|
-
//(单跑全绿、全量偶发)。顺序锁定语义不变:第一笔 entry:subagent-record 必须先于
|
|
598
|
-
// 第一笔 emit:pending:register。
|
|
599
|
-
// ============================================================
|
|
600
|
-
|
|
601
|
-
describe("P2-1 路由先行 reorder 的时序回归锁定", () => {
|
|
602
|
-
let agentDir: string;
|
|
603
|
-
|
|
604
|
-
beforeEach(() => {
|
|
605
|
-
configureCore({ dataRoot: () => "/fake-p21-data-root", log: () => {} });
|
|
606
|
-
agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "p21-reorder-"));
|
|
607
|
-
writeGlobalConfig(agentDir, { version: 1, maxConcurrent: 6 });
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
afterEach(() => {
|
|
611
|
-
clearEngines();
|
|
612
|
-
resetCoreForTests();
|
|
613
|
-
fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
614
|
-
vi.clearAllMocks();
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
it("[pi 缺省] record entry 写点先于 pending:register emit(既有顺序不变)", async () => {
|
|
618
|
-
const { service, eventLog } = setupChat(agentDir);
|
|
619
|
-
|
|
620
|
-
await service.execute(baseOpts(agentDir));
|
|
621
|
-
|
|
622
|
-
const entryIdx = eventLog.indexOf("entry:subagent-record");
|
|
623
|
-
const registerIdx = eventLog.indexOf("emit:pending:register");
|
|
624
|
-
expect(entryIdx).toBeGreaterThanOrEqual(0);
|
|
625
|
-
expect(registerIdx).toBeGreaterThanOrEqual(0);
|
|
626
|
-
expect(entryIdx).toBeLessThan(registerIdx);
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
it("[非 pi 校验失败] 零 record entry、零 pending:register emit(不产生孤儿 record)", async () => {
|
|
630
|
-
const { service, eventLog, zcode } = setupChat(agentDir);
|
|
631
|
-
|
|
632
|
-
await expect(service.execute(baseOpts(agentDir, { engine: "zcode", model: PI_ID }))).rejects.toThrow(
|
|
633
|
-
/is not available on engine 'zcode'/,
|
|
634
|
-
);
|
|
635
|
-
|
|
636
|
-
expect(eventLog).not.toContain("entry:subagent-record");
|
|
637
|
-
expect(eventLog).not.toContain("emit:pending:register");
|
|
638
|
-
expect(zcode.runs.length).toBe(0);
|
|
639
|
-
expect(service["collectRecords"](10, "all")).toHaveLength(0);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
it("[非 pi 成功] record entry → pending:register 顺序与 pi 路径一致", async () => {
|
|
643
|
-
const { service, eventLog, zcode, pi } = setupChat(agentDir);
|
|
644
|
-
|
|
645
|
-
// execute() resolve 即同步段完成点:entry 与 emit 两笔此时已确定写入 eventLog
|
|
646
|
-
//(不依赖 detached 引擎链推进——等待该链只会给断言引入负载敏感的评估时机,
|
|
647
|
-
// 且链的后续写入〔archive 第二笔 entry / pending:unregister〕与断言语义无关)。
|
|
648
|
-
await service.execute(baseOpts(agentDir, { engine: "zcode", model: ZCODE_FLASH }));
|
|
649
|
-
|
|
650
|
-
const entryIdx = eventLog.indexOf("entry:subagent-record");
|
|
651
|
-
const registerIdx = eventLog.indexOf("emit:pending:register");
|
|
652
|
-
expect(entryIdx).toBeGreaterThanOrEqual(0);
|
|
653
|
-
expect(registerIdx).toBeGreaterThan(entryIdx);
|
|
654
|
-
// 引擎派发事实单独确定性等待(runs 数组只增不删,与 eventLog 时序断言解耦)
|
|
655
|
-
await vi.waitFor(() => expect(zcode.runs.length).toBe(1));
|
|
656
|
-
// 链静止后再离开用例(afterEach rmSync 竞争防御,见 helper 注释)
|
|
657
|
-
await settleEngineRunChain(pi);
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
// ============================================================
|
|
662
|
-
// E. zcode 引擎 validateModel 委托(真实 preparer + 临时 v2 config)
|
|
663
|
-
// ============================================================
|
|
664
|
-
|
|
665
|
-
describe("ZcodeEngine.validateModel 委托 resolveZcodeModelRef(u-h2 D2-2)", () => {
|
|
666
|
-
let tmpRoot: string;
|
|
667
|
-
let v2Path: string;
|
|
668
|
-
|
|
669
|
-
const PROVIDER_A = "builtin:bigmodel-coding-plan";
|
|
670
|
-
const PROVIDER_B = "e512d53e-test-provider";
|
|
671
|
-
|
|
672
|
-
beforeEach(() => {
|
|
673
|
-
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-validate-model-"));
|
|
674
|
-
v2Path = path.join(tmpRoot, "v2-config.json");
|
|
675
|
-
fs.writeFileSync(
|
|
676
|
-
v2Path,
|
|
677
|
-
JSON.stringify({
|
|
678
|
-
provider: {
|
|
679
|
-
[PROVIDER_A]: {
|
|
680
|
-
options: { apiKey: "key-a", baseURL: "https://a.example" },
|
|
681
|
-
models: { "GLM-5.3": {}, "GLM-5.3-Flash": {} },
|
|
682
|
-
},
|
|
683
|
-
[PROVIDER_B]: {
|
|
684
|
-
options: { apiKey: "key-b" },
|
|
685
|
-
models: { "mimo-v2.5-pro": {} },
|
|
686
|
-
},
|
|
687
|
-
},
|
|
688
|
-
}),
|
|
689
|
-
);
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
afterEach(() => {
|
|
693
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
function makeEngine(): ZcodeEngine {
|
|
697
|
-
return new ZcodeEngine({ engineDataDir: () => path.join(tmpRoot, "data"), sources: { v2ConfigPath: v2Path } });
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
it("显式全名命中 → canonicalRef 原样返回", () => {
|
|
701
|
-
expect(makeEngine().validateModel(ZCODE_FLASH).canonicalRef).toBe(ZCODE_FLASH);
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
it("undefined → 引擎缺省模型(ZCODE_FALLBACK_DEFAULT_MODEL 链)", () => {
|
|
705
|
-
expect(makeEngine().validateModel(undefined).canonicalRef).toBe(ZCODE_DEFAULT);
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
it("短名 → 缺省 provider 归一化 canonical 全名", () => {
|
|
709
|
-
expect(makeEngine().validateModel("GLM-5.3-Flash").canonicalRef).toBe(ZCODE_FLASH);
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
it("未知 provider → ZcodePrepareError(model_not_available)", () => {
|
|
713
|
-
expect(() => makeEngine().validateModel("pi-org/pi-main")).toThrow(ZcodePrepareError);
|
|
714
|
-
try {
|
|
715
|
-
makeEngine().validateModel("pi-org/pi-main");
|
|
716
|
-
} catch (err) {
|
|
717
|
-
expect((err as ZcodePrepareError).code).toBe("model_not_available");
|
|
718
|
-
}
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
it("provider 存在但未配 apiKey → ZcodePrepareError(engine_credential_missing)", () => {
|
|
722
|
-
// withKey 非空(PROVIDER_A 带凭据)+ 目标 provider 无 key → 命中 preparer「provider
|
|
723
|
-
// 存在但未配置 apiKey」分支(非「找不到任何带 apiKey provider」分支)
|
|
724
|
-
fs.writeFileSync(
|
|
725
|
-
v2Path,
|
|
726
|
-
JSON.stringify({
|
|
727
|
-
provider: {
|
|
728
|
-
[PROVIDER_A]: { options: { apiKey: "key-a" }, models: { "GLM-5.3": {} } },
|
|
729
|
-
"no-key-provider": { options: { baseURL: "https://x.example" }, models: { M1: {} } },
|
|
730
|
-
},
|
|
731
|
-
}),
|
|
732
|
-
);
|
|
733
|
-
try {
|
|
734
|
-
makeEngine().validateModel("no-key-provider/M1");
|
|
735
|
-
throw new Error("should have thrown ZcodePrepareError");
|
|
736
|
-
} catch (err) {
|
|
737
|
-
expect(err).toBeInstanceOf(ZcodePrepareError);
|
|
738
|
-
expect((err as ZcodePrepareError).code).toBe("engine_credential_missing");
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
});
|