@zhushanwen/subagent-core 0.7.1 → 0.9.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.
Files changed (176) hide show
  1. package/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +9799 -7954
  10. package/dist/index.d.cts +1896 -1360
  11. package/dist/index.d.ts +1896 -1360
  12. package/dist/index.js +8952 -7513
  13. package/dist.bundle/index.cjs +7897 -6054
  14. package/package.json +5 -5
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/record-store-last-line.test.ts +12 -0
  17. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  18. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  19. package/src/core/logger.ts +1 -1
  20. package/src/execution/__tests__/alive-store.test.ts +117 -1
  21. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  22. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  23. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  24. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  25. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  26. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  27. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  28. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  29. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  30. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  31. package/src/execution/__tests__/execution-record.test.ts +8 -148
  32. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  33. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  34. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  35. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  36. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  37. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  38. package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
  39. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  40. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  41. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  42. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  43. package/src/execution/__tests__/nested-visibility.test.ts +13 -1
  44. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  45. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  46. package/src/execution/__tests__/record-binding.test.ts +613 -0
  47. package/src/execution/__tests__/record-origin.test.ts +318 -0
  48. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  49. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  50. package/src/execution/__tests__/record-store.test.ts +72 -54
  51. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  52. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  53. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  54. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  55. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  56. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  57. package/src/execution/__tests__/state-marker.test.ts +288 -0
  58. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  59. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  60. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  61. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  62. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  63. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  64. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  65. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  66. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  67. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  68. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  69. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  70. package/src/execution/alive-store.ts +37 -28
  71. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  72. package/src/execution/config.ts +1 -1
  73. package/src/execution/conversation-continuation.ts +541 -0
  74. package/src/execution/dialog-queue.ts +60 -12
  75. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  76. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  77. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  78. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  79. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  80. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  81. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
  82. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  83. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  84. package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
  85. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  86. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  87. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  88. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  89. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  90. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  91. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  92. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  93. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  94. package/src/execution/engine/client/engine-client.ts +111 -26
  95. package/src/execution/engine/client/mirror.ts +9 -0
  96. package/src/execution/engine/client/remote-engine.ts +52 -34
  97. package/src/execution/engine/client/reverse-router.ts +6 -27
  98. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  99. package/src/execution/engine/common/capability-gate.ts +27 -0
  100. package/src/execution/engine/common/data-dir.ts +3 -2
  101. package/src/execution/engine/common/errors.ts +1 -1
  102. package/src/execution/engine/common/journal-wiring.ts +18 -9
  103. package/src/execution/engine/common/run-signals.ts +83 -0
  104. package/src/execution/engine/d8-compat.ts +1 -1
  105. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  106. package/src/execution/engine/host/host-bridge.ts +26 -41
  107. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  108. package/src/execution/engine/host/spawned-children.ts +1 -1
  109. package/src/execution/engine/inflight-snapshot.ts +92 -0
  110. package/src/execution/engine/port.ts +52 -54
  111. package/src/execution/engine/types.ts +11 -14
  112. package/src/execution/execution-record.ts +15 -47
  113. package/src/execution/finalize-record.ts +94 -197
  114. package/src/execution/idle-gc.ts +9 -3
  115. package/src/execution/lifecycle-manager.ts +25 -361
  116. package/src/execution/lifecycle-predicates.ts +22 -19
  117. package/src/execution/manifest-store.ts +40 -42
  118. package/src/execution/model-config-service.ts +1 -1
  119. package/src/execution/notifier.ts +39 -1
  120. package/src/execution/notify-host.ts +13 -4
  121. package/src/execution/path-encoding.ts +6 -0
  122. package/src/execution/record-entry.ts +20 -2
  123. package/src/execution/record-store.ts +925 -160
  124. package/src/execution/relay-env.ts +8 -3
  125. package/src/execution/round-supervisor/domain.ts +17 -5
  126. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  127. package/src/execution/round-supervisor/service-binding.ts +71 -21
  128. package/src/execution/round-supervisor/supervisor.ts +23 -9
  129. package/src/execution/service/record-access.ts +547 -0
  130. package/src/execution/service/record-lifecycle.ts +542 -0
  131. package/src/execution/service/run-orchestration.ts +1647 -0
  132. package/src/execution/service/service-bootstrap.ts +111 -0
  133. package/src/execution/service/service-constants.ts +27 -0
  134. package/src/execution/service/session-baselines.ts +401 -0
  135. package/src/execution/service/sync-collect-domain.ts +442 -0
  136. package/src/execution/service/workflow-dispatch.ts +567 -0
  137. package/src/execution/session-file-gc.ts +11 -8
  138. package/src/execution/session-reconstructor.ts +44 -1
  139. package/src/execution/sessions-index.ts +20 -3
  140. package/src/execution/settled-watchdog.ts +86 -43
  141. package/src/execution/state-marker.ts +516 -0
  142. package/src/execution/stream-sink.ts +1 -1
  143. package/src/execution/subagent-actions-core.ts +53 -31
  144. package/src/execution/subagent-service.ts +643 -2955
  145. package/src/execution/subprocess-agent-runner.ts +46 -323
  146. package/src/execution/types.ts +60 -43
  147. package/src/execution/ui-request-handler-factory.ts +5 -0
  148. package/src/execution/worktree-manager.ts +1 -1
  149. package/src/execution/worktree-registry.ts +12 -8
  150. package/src/index.ts +16 -5
  151. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  152. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  153. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  154. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  155. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  156. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  157. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  158. package/src/orchestration/execute-agent-call.ts +1 -1
  159. package/src/orchestration/file-run-store.ts +1 -1
  160. package/src/orchestration/models/ports.ts +20 -13
  161. package/src/orchestration/models/types.ts +11 -13
  162. package/src/orchestration/run-snapshot.ts +18 -22
  163. package/src/orchestration/worker-message-pump.ts +33 -57
  164. package/src/shared/atomic-write.ts +10 -8
  165. package/dist/chunk-T2SYBW3I.js +0 -24
  166. package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
  167. package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
  168. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  169. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  170. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  171. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  172. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  173. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  174. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  175. package/src/execution/finalized-marker.ts +0 -70
  176. package/src/execution/tombstone-store.ts +0 -72
@@ -1,686 +1,131 @@
1
1
  // src/execution/__tests__/subprocess-agent-runner.test.ts
2
2
  //
3
- // Wave 4: SubprocessAgentRunner 委托重写测试
3
+ // [H2 W4] SAR 壳装配契约测试——run() 已掏空为纯转调
4
+ // SubagentService.executeWorkflowAgent(编排归 service 单点,设计
5
+ // subagent-workflow-record-unification.md §5 W4 / §3.5 终态数据流)。
4
6
  //
5
- // 覆盖 test-matrix 用例:
6
- // T3.1 (正常): SAR 委托 executeAndAwait 返回 content
7
- // T3.2 (正常): parsedOutput 透传
8
- // T3.4 (边界): cwd 透传(非 git worktree)
9
- // T3.5 (边界): model 填底(opts.model 空 → ctxModel)
10
- // T3.6 (异常): timeoutMs 超时 signal abort → error
11
- // T3.7 (正常): onEvent 桥接 AgentEvent 透传
12
- // T3.17 (NFR): mergeTimeoutSignal listener 清理
13
- // T3.18 (NFR): dispose 兜底覆盖(delegate 后子进程进 spawnedChildren)
14
- // T3.19 (NFR): AgentCallOpts→ExecuteOptions 直出保真(D6 合流后映射在 PiEngine 边界,
15
- // 本组用例经真实 PiEngine 链路锁定 spawn 参数形态)
16
-
17
- import { mkdtempSync, readdirSync, rmSync } from "node:fs";
18
- import { tmpdir } from "node:os";
19
- import { join } from "node:path";
20
-
21
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+ // 本文件只锁壳契约面:
8
+ // 1. 构造签名(deps.subagentService + ctxModel 兼容——session-lifecycle.ts
9
+ // 装配点零改定的锚点);
10
+ // 2. run() 纯转调(opts/signal/onEvent/stream 原样透传 + parentRunId 用直调
11
+ // 占位 SAR_UNATTACHED_PARENT_RUN_ID + 返回值原样返回,零映射零吞错);
12
+ // 3. updateCtxModel 装配链兼容(model_select 刷新不炸;run() 不再消费 ctxModel);
13
+ // 4. mergeRunSignals re-export 可用(既有 import 路径契约)。
14
+ //
15
+ // [H2 W4 删除面] 旧编排内部用例(路由集成 / model 填底 / ctxModel 孪生守卫 /
16
+ // timeoutMs 合并 / onEvent-journal 桥接 / 直传保真 / no-progress 守护落点 /
17
+ // 全链 killAll)随 SAR 编排退役删除——service 落点的行为守护由
18
+ // workflow-agent-dispatch.test.ts(W2 承接:注册面/池顺序/守护 arm 键 record.id/
19
+ // 双刷新源/fire 追注/stream 自构/D7/D6/adopt 豁免)与 engine/__tests__/routing.test.ts
20
+ // (路由 helper 单点:三层优先级 + 守卫 a/b/c + strict + fallback)承接。
22
21
 
23
- // u0-data-discovery 注入化:getEngineDataDir 回退段经 HostServices.dataRoot() 端口,
24
- // 未 configureCore 即消费会抛 core_host_not_configured(SAR.run 内部调用,dataRoot
25
- // 值不被本文件断言)——端口态显式配置,测试隔离经 resetCoreForTests
26
- import { configureCore, resetCoreForTests } from "../../core/host-services.ts";
22
+ import { describe, expect, it, vi } from "vitest";
27
23
 
28
24
  import type { AgentCallOpts, AgentResult } from "../../orchestration/models/types.ts";
29
- import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
30
- import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
31
- import { replayJournal } from "../engine/common/event-journal.ts";
32
- import { clearEngines, registerEngine } from "../engine/registry.ts";
33
- import type { EnginePort, RunContext } from "../engine/port.ts";
34
- import type {
35
- AgentOutcome,
36
- EngineCapabilities,
37
- ProbeReport,
38
- } from "../engine/types.ts";
39
- import type { SubprocessAgentRunnerDeps } from "../subprocess-agent-runner.ts";
25
+ import type { ModelInfo } from "../model-resolver.ts";
26
+ import { SubagentStream } from "../stream-sink.ts";
40
27
  import type { SubagentService } from "../subagent-service.ts";
41
- import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
42
-
43
- // ── 测试辅助 ──
44
-
45
- /** 构造 mock SubagentService.executeAndAwait 返回值 */
46
- function makeMockResult(overrides: Partial<AgentResult> = {}): AgentResult {
47
- return {
48
- content: "OK",
49
- parsedOutput: undefined,
50
- durationMs: 100,
51
- error: undefined,
52
- sessionId: undefined,
53
- toolCalls: [],
54
- ...overrides,
55
- };
28
+ import {
29
+ SAR_UNATTACHED_PARENT_RUN_ID,
30
+ SubprocessAgentRunner,
31
+ type SubprocessAgentRunnerDeps,
32
+ } from "../subprocess-agent-runner.ts";
33
+ import { mergeRunSignals } from "../engine/common/run-signals.ts";
34
+
35
+ function makeOpts(): AgentCallOpts {
36
+ return { prompt: "test task", agent: "worker" };
56
37
  }
57
38
 
58
- /** 协议替身的引擎内「任务声明 执行选项」映射(原 core PiEngine
59
- * agentCallToExecuteOptions 的最小同构还原——[W3] 该映射本体已随 inproc pi 引擎目录 迁入
60
- * pi-subagent-cli 引擎包,此处仅为保留 executeAndAwait 委托断言面)。 */
61
- function fakeAgentCallToExecuteOptions(task: AgentCallOpts, ctxModel?: ModelInfo): Record<string, unknown> {
62
- const rawSlug = task.description ?? task.agent ?? "workflow-agent";
63
- return {
64
- task: task.prompt,
65
- slug: rawSlug.length > 35 ? rawSlug.slice(0, 35) : rawSlug,
66
- agent: task.agent,
67
- model: task.model,
68
- thinkingLevel: task.thinkingLevel,
69
- skillPath: task.skillPath,
70
- appendSystemPrompt: task.appendSystemPrompt,
71
- schema: task.schema,
72
- schemaEnv: task.schemaEnv,
73
- maxTurns: task.maxTurns,
74
- graceTurns: task.graceTurns,
75
- ctxModel,
76
- fork: task.fork,
77
- worktree: task.worktree,
78
- cwd: task.cwd,
79
- conversation: task.conversation,
80
- idleTimeoutMs: task.idleTimeoutMs,
81
- };
39
+ function makeResult(): AgentResult {
40
+ return { content: "OK", durationMs: 100, toolCalls: [] };
82
41
  }
83
42
 
84
- /** 引擎 run 捕获面(T3.19 直传保真断言用:SAR → engine.run 的 task 原样性)。 */
85
- const engineRunSpy = vi.fn();
86
-
87
- /** 创建 mock SubagentService(只实现 executeAndAwait)。
88
- * [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取引擎服务面——fake 的 face
89
- * 即自身,getter 直接返回 self。
90
- * [W3 改写] 同步注册「委托式」替身 pi 引擎:run() 还原引擎内 task→ExecuteOptions
91
- * 映射后委托 mock 的 executeAndAwait(原 inproc PiEngine 的 workflow 分支形态)——
92
- * resolveHostPiEnginePort 对已注册 port 原样返回,SAR 路由命中替身。 */
93
- function createMockService(impl?: typeof vi.fn): SubagentService {
94
- const executeAndAwait = impl ?? vi.fn().mockResolvedValue(makeMockResult());
95
- // partial mock(仅 executeAndAwait + asEngineService self-引用)——SAR 测试路径
96
- // 不触达其余成员,经 unknown 双跳收敛到 SubagentService 视图
97
- const partial: { executeAndAwait: typeof executeAndAwait; asEngineService?: unknown } = { executeAndAwait };
98
- const service = partial as unknown as SubagentService;
99
- partial.asEngineService = service;
100
-
101
- const fakePort: EnginePort = {
102
- id: "pi",
103
- capabilities: (): EngineCapabilities => ({
104
- schemaEnforcement: "native",
105
- steer: "unsupported",
106
- conversation: "native",
107
- personaInjection: "flag",
108
- eventGranularity: "stream",
109
- sandbox: "emulated",
110
- sessionRead: "full",
111
- resume: "native",
112
- interrupt: "kill-only",
113
- permissionMode: "native",
114
- maxTurns: true,
115
- }),
116
- probe: async (): Promise<ProbeReport> => ({ ok: true, engineVersion: "fake", checks: [] }),
117
- run: async (task: AgentCallOpts, ctx: RunContext) => {
118
- engineRunSpy(task, ctx);
119
- const wfResult = await executeAndAwait(
120
- fakeAgentCallToExecuteOptions(task, ctx.ctxModel),
121
- ctx.signal,
122
- ctx.onEvent,
123
- ctx.stream,
124
- ) as { content: string; parsedOutput?: unknown; durationMs?: number; error?: string };
125
- const outcome: AgentOutcome = {
126
- content: wfResult.content,
127
- parsedOutput: wfResult.parsedOutput,
128
- durationMs: wfResult.durationMs,
129
- error: wfResult.error,
130
- engineId: "pi",
131
- };
132
- return {
133
- handle: {
134
- data: { v: 1, engineId: "pi", sessionRef: {}, poolKey: "shared", adapterVersion: "fake-sar" },
135
- },
136
- outcome,
137
- };
138
- },
139
- interact: async () => ({ ok: false, code: "engine_interact_failed", message: "not supported in this test" }),
140
- read: async () => ({ engineId: "pi", turns: [], source: "outcome-only" }),
141
- };
142
- clearEngines();
143
- registerEngine("pi", () => fakePort);
144
- return service;
145
- }
146
-
147
- function makeBaseOpts(): AgentCallOpts {
148
- return {
149
- prompt: "test task",
150
- agent: "worker",
151
- cwd: "/some/path",
152
- schema: undefined,
153
- model: undefined,
154
- scene: undefined,
155
- description: undefined,
156
- timeoutMs: undefined,
157
- skill: undefined,
158
- skillPath: undefined,
159
- appendSystemPrompt: undefined,
160
- schemaEnv: undefined,
161
- };
43
+ /** mock SubagentService——只实现 executeWorkflowAgent(转调目标),其余成员不触达。 */
44
+ function createMockService(impl?: ReturnType<typeof vi.fn>): SubagentService {
45
+ const executeWorkflowAgent = impl ?? vi.fn().mockResolvedValue(makeResult());
46
+ const partial = { executeWorkflowAgent };
47
+ return partial as unknown as SubagentService;
162
48
  }
163
49
 
164
- // ── T3.1: 正常路径 ──
165
-
166
- describe("SubprocessAgentRunner (wave-4 delegate)", () => {
167
- beforeEach(() => {
168
- configureCore({ dataRoot: () => "/fake-sar-data-root", log: () => {} });
169
- // [U-2 一致性修复] pi 未注册时 resolveHostPiEnginePort 返回 engine_not_found stub
170
- // ——替身 pi 引擎在 createMockService 内按测试服务注册([W3] 委托式协议替身)。
171
- engineRunSpy.mockClear();
172
- });
173
-
174
- afterEach(() => {
175
- resetCoreForTests();
176
- });
177
-
178
- // ────────────────────────────────────────────────
179
- // T3.1: 正常路径 — SAR 委托 executeAndAwait 返回 content
180
- // ────────────────────────────────────────────────
181
- describe("T3.1 主流程", () => {
182
- it("委托 executeAndAwait 并返回 content", async () => {
183
- const mockService = createMockService(
184
- vi.fn().mockResolvedValue(makeMockResult({ content: "hello world" })),
185
- );
186
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
187
- const sar = new SubprocessAgentRunner(deps);
188
-
189
- const opts = makeBaseOpts();
190
- const signal = new AbortController().signal;
191
-
192
- const result = await sar.run(opts, signal);
193
-
194
- expect(result.content).toBe("hello world");
195
- expect(mockService.executeAndAwait).toHaveBeenCalledTimes(1);
196
- });
197
-
198
- it("不 reject — 失败信息入 result.error", async () => {
199
- const mockService = createMockService(
200
- vi.fn().mockResolvedValue(makeMockResult({ error: "some error" })),
201
- );
202
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
203
- const sar = new SubprocessAgentRunner(deps);
204
-
205
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
206
- expect(result.error).toBe("some error");
207
- });
208
- });
209
-
210
- // ────────────────────────────────────────────────
211
- // T3.2: parsedOutput 透传
212
- // ────────────────────────────────────────────────
213
- describe("T3.2 parsedOutput 透传", () => {
214
- it("parsedOutput 直通", async () => {
215
- const parsedData = { x: 1, y: 2 };
216
- const mockService = createMockService(
217
- vi.fn().mockResolvedValue(makeMockResult({ content: "ok", parsedOutput: parsedData })),
218
- );
219
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
220
- const sar = new SubprocessAgentRunner(deps);
221
-
222
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
223
- expect(result.parsedOutput).toEqual(parsedData);
224
- });
225
- });
226
-
227
- // ────────────────────────────────────────────────
228
- // T3.4: cwd 透传
229
- // ────────────────────────────────────────────────
230
- describe("T3.4 cwd 透传", () => {
231
- it("cwd 传入 executeAndAwait 的 ExecuteOptions", async () => {
232
- let capturedOpts: Record<string, unknown> | undefined;
233
- const mockService = createMockService(
234
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
235
- capturedOpts = opts;
236
- return Promise.resolve(makeMockResult());
237
- }),
238
- );
239
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
240
- const sar = new SubprocessAgentRunner(deps);
241
-
242
- const opts = { ...makeBaseOpts(), cwd: "/custom/cwd" };
243
- await sar.run(opts, new AbortController().signal);
244
-
245
- expect(capturedOpts).toBeDefined();
246
- expect(capturedOpts!.cwd).toBe("/custom/cwd");
247
- });
248
- });
249
-
250
- // ────────────────────────────────────────────────
251
- // T3.5: model 填底
252
- // ────────────────────────────────────────────────
253
- describe("T3.5 model 填底 (D-008)", () => {
254
- it("opts.model 优先", async () => {
255
- let capturedOpts: Record<string, unknown> | undefined;
256
- const mockService = createMockService(
257
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
258
- capturedOpts = opts;
259
- return Promise.resolve(makeMockResult());
260
- }),
261
- );
262
- const ctxModel: ModelInfo = { id: "ctx-model", name: "Ctx Model", provider: "test", reasoning: false };
263
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel };
264
- const sar = new SubprocessAgentRunner(deps);
265
-
266
- const opts = { ...makeBaseOpts(), model: "explicit-model" };
267
- await sar.run(opts, new AbortController().signal);
268
-
269
- expect(capturedOpts!.model).toBe("explicit-model");
270
- });
271
-
272
- it("opts.model 空 → ctxModel", async () => {
273
- let capturedOpts: Record<string, unknown> | undefined;
274
- const mockService = createMockService(
275
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
276
- capturedOpts = opts;
277
- return Promise.resolve(makeMockResult());
278
- }),
279
- );
280
- const ctxModel: ModelInfo = { id: "ctx-model", name: "Ctx Model", provider: "test", reasoning: false };
281
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel };
282
- const sar = new SubprocessAgentRunner(deps);
283
-
284
- const opts = { ...makeBaseOpts(), model: undefined };
285
- await sar.run(opts, new AbortController().signal);
286
-
287
- // 修复后:opts.model 不再从 ctxModel.id 填底,ctxModel 作为完整对象透传
288
- expect(capturedOpts!.model).toBeUndefined();
289
- expect(capturedOpts!.ctxModel).toBe(ctxModel);
290
- });
291
- });
292
-
293
- // ────────────────────────────────────────────────
294
- // [U1 D2] RunContext.modelRef 接入:ctxModel 继承路径孪生守卫
295
- // ────────────────────────────────────────────────
296
- describe("U1 ctxModel twin guard (RunContext.modelRef 接入)", () => {
297
- const prevDataDir = process.env["XYZ_AGENT_DATA_DIR"];
298
- let tmpRoot = "";
299
-
300
- /** 装配含指定快照的 ModelConfigService 单例(initModel 注入 registry)。 */
301
- function installModelService(registry: ModelRegistryLike): void {
302
- tmpRoot = mkdtempSync(join(tmpdir(), "sar-model-ref-"));
303
- process.env["XYZ_AGENT_DATA_DIR"] = join(tmpRoot, "engine-data");
304
- const svc = new ModelConfigService({ agentDir: join(tmpRoot, "agent"), cwd: tmpRoot });
305
- svc.initModel({ modelRegistry: registry, sessionId: "sar-guard" });
306
- setModelConfigService(svc);
307
- }
308
-
309
- afterEach(() => {
310
- // 单例 slot 无 reset API,重装一个空注册服务隔离后续用例(同文件后续
311
- // describe 不消费 ctxModel 守卫,getGlobalConfig 对空目录返回默认值)
312
- if (tmpRoot) {
313
- const svc = new ModelConfigService({ agentDir: join(tmpRoot, "agent"), cwd: tmpRoot });
314
- svc.initModel({ modelRegistry: { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => false }, sessionId: "cleanup" });
315
- setModelConfigService(svc);
316
- rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
317
- tmpRoot = "";
318
- }
319
- if (prevDataDir === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
320
- else process.env["XYZ_AGENT_DATA_DIR"] = prevDataDir;
321
- });
322
-
323
- it("P-A2 拒单路径:ctxModel 命中但 registry 含孪生 → run 返回 error(ambiguous),executeAndAwait 未被调", async () => {
324
- const models = [
325
- { id: "GLM-5.3-Flash", name: "flash", provider: "zai-coding-cn", reasoning: false },
326
- { id: "glm-5.3-flash", name: "flash lower", provider: "zai-coding-cn", reasoning: false },
327
- ];
328
- installModelService({
329
- getAvailable: () => models,
330
- find: (p, id) => models.find((m) => m.provider === p && m.id === id),
331
- hasConfiguredAuth: () => true,
332
- });
333
- const mockService = createMockService();
334
- const sar = new SubprocessAgentRunner({
335
- subagentService: mockService as unknown as SubprocessAgentRunnerDeps["subagentService"],
336
- ctxModel: { id: "GLM-5.3-Flash", name: "flash", provider: "zai-coding-cn", reasoning: false },
337
- });
338
-
339
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
340
-
341
- expect(result.error).toMatch(/ambiguous case variants/);
342
- expect(result.content).toBe("");
343
- // 守卫在 engine.run 之前:不产生任何 record/spawn(委托零发生)
344
- expect(mockService.executeAndAwait).not.toHaveBeenCalled();
345
- });
346
-
347
- it("P-A2 放行路径:无孪生 registry 下同 ctxModel 正常委托(守卫零误伤)", async () => {
348
- const models = [
349
- { id: "GLM-5.3-Flash", name: "flash", provider: "zai-coding-cn", reasoning: false },
350
- ];
351
- installModelService({
352
- getAvailable: () => models,
353
- find: (p, id) => models.find((m) => m.provider === p && m.id === id),
354
- hasConfiguredAuth: () => true,
355
- });
356
- const mockService = createMockService(
357
- vi.fn().mockResolvedValue(makeMockResult({ content: "ok" })),
358
- );
359
- const sar = new SubprocessAgentRunner({
360
- subagentService: mockService as unknown as SubprocessAgentRunnerDeps["subagentService"],
361
- ctxModel: { id: "GLM-5.3-Flash", name: "flash", provider: "zai-coding-cn", reasoning: false },
362
- });
363
-
364
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
365
-
366
- expect(result.error).toBeUndefined();
367
- expect(result.content).toBe("ok");
368
- expect(mockService.executeAndAwait).toHaveBeenCalledTimes(1);
369
- });
370
-
371
- it("ctxModel 未指定时守卫跳过(无单例也不阻断)", async () => {
372
- const mockService = createMockService(
373
- vi.fn().mockResolvedValue(makeMockResult({ content: "ok" })),
374
- );
375
- const sar = new SubprocessAgentRunner({
376
- subagentService: mockService as unknown as SubprocessAgentRunnerDeps["subagentService"],
377
- });
378
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
379
- expect(result.content).toBe("ok");
380
- expect(mockService.executeAndAwait).toHaveBeenCalledTimes(1);
381
- });
50
+ describe("SubprocessAgentRunner (H2 W4 纯转调壳)", () => {
51
+ it("run() 纯转调 executeWorkflowAgent:opts/signal/onEvent/stream 原样透传,parentRunId 用直调占位", async () => {
52
+ const executeWorkflowAgent = vi.fn().mockResolvedValue(makeResult());
53
+ const deps: SubprocessAgentRunnerDeps = { subagentService: createMockService(executeWorkflowAgent) };
54
+ const sar = new SubprocessAgentRunner(deps);
55
+
56
+ const opts = makeOpts();
57
+ const controller = new AbortController();
58
+ const onEvent = vi.fn();
59
+ const stream = new SubagentStream("sar-test-stream", { setWidget: () => {} });
60
+ const result = await sar.run(opts, controller.signal, onEvent, stream);
61
+
62
+ expect(executeWorkflowAgent).toHaveBeenCalledTimes(1);
63
+ expect(executeWorkflowAgent).toHaveBeenCalledWith(
64
+ opts,
65
+ SAR_UNATTACHED_PARENT_RUN_ID,
66
+ controller.signal,
67
+ onEvent,
68
+ stream,
69
+ );
70
+ // 返回值原样返回(executeWorkflowAgent 返回类型即 orchestration AgentResult,零映射)
71
+ expect(result).toEqual(makeResult());
382
72
  });
383
73
 
384
- // ────────────────────────────────────────────────
385
- // H1: SAR ctxModel 刷新(model_select 后不再 stale)
386
- // ────────────────────────────────────────────────
387
- describe("H1 ctxModel refresh via updateCtxModel", () => {
388
- it("updateCtxModel 后 run() 传入新的 ctxModel 而非旧值", async () => {
389
- let capturedOpts: Record<string, unknown> | undefined;
390
- const mockService = createMockService(
391
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
392
- capturedOpts = opts;
393
- return Promise.resolve(makeMockResult());
394
- }),
395
- );
396
- const oldModel: ModelInfo = { id: "old-model", name: "Old Model", provider: "test", reasoning: false };
397
- const newModel: ModelInfo = { id: "new-model", name: "New Model", provider: "test", reasoning: false };
398
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService, ctxModel: oldModel };
399
- const sar = new SubprocessAgentRunner(deps);
74
+ it("onEvent/stream 缺省时占位 undefined 透传(位置参数对齐 service 签名)", async () => {
75
+ const executeWorkflowAgent = vi.fn().mockResolvedValue(makeResult());
76
+ const sar = new SubprocessAgentRunner({ subagentService: createMockService(executeWorkflowAgent) });
400
77
 
401
- // 模拟 model_select:刷新 ctxModel
402
- sar.updateCtxModel(newModel);
78
+ const controller = new AbortController();
79
+ await sar.run(makeOpts(), controller.signal);
403
80
 
404
- const opts = { ...makeBaseOpts(), model: undefined };
405
- await sar.run(opts, new AbortController().signal);
406
-
407
- expect(capturedOpts!.ctxModel).toBe(newModel);
408
- expect(capturedOpts!.ctxModel).not.toBe(oldModel);
409
- });
81
+ expect(executeWorkflowAgent).toHaveBeenCalledWith(
82
+ makeOpts(),
83
+ SAR_UNATTACHED_PARENT_RUN_ID,
84
+ controller.signal,
85
+ undefined,
86
+ undefined,
87
+ );
410
88
  });
411
89
 
412
- // ────────────────────────────────────────────────
413
- // T3.6: timeoutMs 超时 signal abort → error
414
- // ────────────────────────────────────────────────
415
- describe("T3.6 timeoutMs 超时", () => {
416
- it("timeoutMs > 0 → merged signal 传给 executeAndAwait", async () => {
417
- let capturedSignal: AbortSignal | undefined;
418
- const mockService = createMockService(
419
- vi.fn().mockImplementation((_opts: Record<string, unknown>, signal?: AbortSignal) => {
420
- capturedSignal = signal;
421
- return Promise.resolve(makeMockResult());
422
- }),
423
- );
424
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
425
- const sar = new SubprocessAgentRunner(deps);
426
-
427
- const opts = { ...makeBaseOpts(), timeoutMs: 5000 };
428
- await sar.run(opts, new AbortController().signal);
429
-
430
- // 有 timeoutMs 时 merged signal 不等于原始 signal
431
- expect(capturedSignal).toBeDefined();
432
- });
90
+ it("service 同步抛错(路由失败/预检命中/嵌套超限)原样传播——壳不吞错不包装", async () => {
91
+ const boom = new Error("engine_not_found: zcode not registered");
92
+ const executeWorkflowAgent = vi.fn().mockRejectedValue(boom);
93
+ const sar = new SubprocessAgentRunner({ subagentService: createMockService(executeWorkflowAgent) });
433
94
 
434
- it("timeoutMs 到期 → merged signal.aborted=true", async () => {
435
- vi.useFakeTimers();
436
- let capturedSignal: AbortSignal | undefined;
437
- const mockService = createMockService(
438
- vi.fn().mockImplementation((_opts: Record<string, unknown>, signal?: AbortSignal) => {
439
- capturedSignal = signal;
440
- return Promise.resolve(makeMockResult());
441
- }),
442
- );
443
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
444
- const sar = new SubprocessAgentRunner(deps);
445
-
446
- const opts = { ...makeBaseOpts(), timeoutMs: 50 };
447
- sar.run(opts, new AbortController().signal);
448
-
449
- // 推进 51ms 后 merged signal 应 abort
450
- vi.advanceTimersByTime(51);
451
-
452
- expect(capturedSignal?.aborted).toBe(true);
453
- vi.useRealTimers();
454
- });
95
+ await expect(sar.run(makeOpts(), new AbortController().signal)).rejects.toThrow(boom);
455
96
  });
456
97
 
457
- // ────────────────────────────────────────────────
458
- // T3.7: onEvent 桥接
459
- // ────────────────────────────────────────────────
460
- describe("T3.7 onEvent 桥接", () => {
461
- it("executeAndAwait 的 onEvent 透传到 workflow onEvent", async () => {
462
- const mockService = createMockService(
463
- vi.fn().mockImplementation(
464
- (_opts: Record<string, unknown>, _signal?: AbortSignal, onEvent?: (e: Record<string, unknown>) => void) => {
465
- // 模拟 executeAndAwait 触发 onEvent
466
- onEvent?.({ type: "tool_start", toolName: "read", args: { path: "/a.txt" } });
467
- return Promise.resolve(makeMockResult());
468
- },
469
- ),
470
- );
471
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
472
- const sar = new SubprocessAgentRunner(deps);
473
-
474
- const workflowOnEvent = vi.fn();
475
- await sar.run(makeBaseOpts(), new AbortController().signal, workflowOnEvent);
476
-
477
- // workflow 的 onEvent 应被调用
478
- expect(workflowOnEvent).toHaveBeenCalledTimes(1);
479
- expect(workflowOnEvent).toHaveBeenCalledWith({
480
- type: "tool_start",
481
- toolName: "read",
482
- args: { path: "/a.txt" },
483
- });
484
- });
485
-
486
- it("无 workflow onEvent → 引擎仍收到包装 onEvent(P2 journal 通道恒开)", async () => {
487
- let engineOnEvent: ((e: Record<string, unknown>) => void) | undefined;
488
- const mockService = createMockService(
489
- vi.fn().mockImplementation(
490
- (_opts: Record<string, unknown>, _signal?: AbortSignal, onEvent?: (e: Record<string, unknown>) => void) => {
491
- engineOnEvent = onEvent;
492
- return Promise.resolve(makeMockResult());
493
- },
494
- ),
495
- );
496
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
497
- const sar = new SubprocessAgentRunner(deps);
498
-
499
- await sar.run(makeBaseOpts(), new AbortController().signal);
500
-
501
- // P2 接线(设计 D6 第②级):SAR 包装 onEvent 写 journal 后转发——原 onEvent
502
- // 未传时也恒传包装版(全引擎免费获得 journal;P1「undefined 透传」细节已被
503
- // 有意变更,本用例锁定新语义:引擎侧 onEvent 通道恒开)
504
- expect(typeof engineOnEvent).toBe("function");
505
- // 包装版不向外抛(journal append 是同步入队)
506
- expect(() => engineOnEvent?.({ type: "turn_end" })).not.toThrow();
507
- });
508
-
509
- it("P2 journal 接线:run 期间事件落盘 journal-<taskId>.jsonl(中立格式,可重放)", async () => {
510
- vi.useFakeTimers({ toFake: [] }); // 真实 timers:journal 写盘走真实 fs
511
- const dataDir = mkdtempSync(join(tmpdir(), "sar-journal-test-"));
512
- const prevEnv = process.env.XYZ_AGENT_DATA_DIR;
513
- process.env.XYZ_AGENT_DATA_DIR = dataDir;
514
- try {
515
- const mockService = createMockService(
516
- vi.fn().mockImplementation(
517
- (_opts: Record<string, unknown>, _signal?: AbortSignal, onEvent?: (e: Record<string, unknown>) => void) => {
518
- onEvent?.({ type: "tool_start", toolName: "bash", args: { cmd: "ls" } });
519
- onEvent?.({ type: "turn_end" });
520
- return Promise.resolve(makeMockResult());
521
- },
522
- ),
523
- );
524
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
525
- const sar = new SubprocessAgentRunner(deps);
526
-
527
- const workflowOnEvent = vi.fn();
528
- await sar.run(makeBaseOpts(), new AbortController().signal, workflowOnEvent);
529
-
530
- // journal 落在 <dataDir>/inproc pi 引擎目录/shared/journal-<taskId>.jsonl
531
- const journalFile = join(dataDir, "engines", "pi", "shared");
532
- const files = readdirSync(journalFile).filter((f) => f.startsWith("journal-") && f.endsWith(".jsonl"));
533
- expect(files.length).toBe(1);
534
- const replayed = replayJournal(join(journalFile, files[0] ?? ""));
535
- expect(replayed).toEqual([
536
- { type: "tool_start", toolName: "bash", args: { cmd: "ls" } },
537
- { type: "turn_end" },
538
- ]);
539
- // workflow onEvent 照常透传(journal 包装不吞事件)
540
- expect(workflowOnEvent).toHaveBeenCalledTimes(2);
541
- } finally {
542
- if (prevEnv === undefined) delete process.env.XYZ_AGENT_DATA_DIR;
543
- else process.env.XYZ_AGENT_DATA_DIR = prevEnv;
544
- rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
545
- }
546
- });
98
+ it("构造签名兼容:ctxModel 可选注入(装配点零改定锚点)+ updateCtxModel 刷新不炸", async () => {
99
+ const ctxModel: ModelInfo = {
100
+ id: "test/model-x",
101
+ name: "Model X",
102
+ provider: "test",
103
+ reasoning: false,
104
+ contextWindow: 128_000,
105
+ };
106
+ const executeWorkflowAgent = vi.fn().mockResolvedValue(makeResult());
107
+ // ctxModel 注入构造(session-lifecycle.ts:537 形态)
108
+ const sar = new SubprocessAgentRunner({ subagentService: createMockService(executeWorkflowAgent), ctxModel });
109
+ // model_select 刷新链(extension index.ts 调用形态):不炸且不影响转调
110
+ sar.updateCtxModel(undefined);
111
+ sar.updateCtxModel(ctxModel);
112
+
113
+ await sar.run(makeOpts(), new AbortController().signal);
114
+ // run() 不再消费 ctxModel:转调用参只有 opts/parentRunId/signal/onEvent/stream
115
+ expect(executeWorkflowAgent).toHaveBeenCalledWith(
116
+ makeOpts(),
117
+ SAR_UNATTACHED_PARENT_RUN_ID,
118
+ expect.any(AbortSignal),
119
+ undefined,
120
+ undefined,
121
+ );
547
122
  });
548
123
 
549
- // ────────────────────────────────────────────────
550
- // T3.17: mergeTimeoutSignal listener 清理
551
- // ────────────────────────────────────────────────
552
- describe("T3.17 mergeTimeoutSignal listener 清理", () => {
553
- it("外部 signal abort → timer 清理", async () => {
554
- vi.useFakeTimers();
555
- const mockService = createMockService(
556
- vi.fn().mockResolvedValue(makeMockResult()),
557
- );
558
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
559
- const sar = new SubprocessAgentRunner(deps);
560
-
561
- const ctrl = new AbortController();
562
- const opts = { ...makeBaseOpts(), timeoutMs: 5000 };
563
-
564
- // 启动 run(不 await,让它在后台)
565
- const runPromise = sar.run(opts, ctrl.signal);
566
-
567
- // 外部 abort
568
- ctrl.abort();
569
-
570
- await runPromise;
571
-
572
- // 推进时间——timer 应已清理,不会造成副作用
573
- vi.advanceTimersByTime(6000);
574
- // 无异常 = listener 已正确清理
575
- vi.useRealTimers();
576
- });
577
- });
578
-
579
- // ────────────────────────────────────────────────
580
- // T3.19: SAR → engine.run 任务声明直传保真
581
- // [W3 改写] 「AgentCallOpts → ExecuteOptions 映射点在 PiEngine 边界」随 inproc pi 引擎目录
582
- // 删除迁入 pi-subagent-cli(引擎包 spawn-args 测试守护);core 侧存留契约 =
583
- // SAR 对 AgentCallOpts 直传零映射(engine.run 的 task 原样性)。
584
- // ────────────────────────────────────────────────
585
- describe("T3.19 直传保真(D6:SAR 直传零映射,任务声明原样到达 engine.run)", () => {
586
- it("prompt/agent/skillPath/schemaEnv 原样到达 engine.run", async () => {
587
- const mockService = createMockService();
588
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
589
- const sar = new SubprocessAgentRunner(deps);
590
-
591
- const opts: AgentCallOpts = {
592
- ...makeBaseOpts(),
593
- prompt: "do the thing",
594
- agent: "code-reviewer",
595
- schemaEnv: '{"type":"object"}',
596
- skillPath: "/skills/code-review.md",
597
- };
598
- await sar.run(opts, new AbortController().signal);
599
-
600
- expect(engineRunSpy).toHaveBeenCalledTimes(1);
601
- const [task] = engineRunSpy.mock.calls[0] as [AgentCallOpts, unknown];
602
- expect(task.prompt).toBe("do the thing");
603
- expect(task.agent).toBe("code-reviewer");
604
- expect(task.skillPath).toBe("/skills/code-review.md");
605
- expect(task.schemaEnv).toBe('{"type":"object"}');
606
- });
607
-
608
- it("schema 原样透传为原始对象", async () => {
609
- const mockService = createMockService();
610
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
611
- const sar = new SubprocessAgentRunner(deps);
612
-
613
- const schema = { type: "object", properties: { name: { type: "string" } } };
614
- await sar.run({ ...makeBaseOpts(), schema }, new AbortController().signal);
615
-
616
- const [task] = engineRunSpy.mock.calls[0] as [AgentCallOpts, unknown];
617
- expect(task.schema).toEqual(schema);
618
- });
619
- });
620
-
621
- // ────────────────────────────────────────────────
622
- // T3.18: executeAndAwait throw → catch → error
623
- // ────────────────────────────────────────────────
624
- describe("T3.18 异常处理", () => {
625
- it("executeAndAwait throw → 不 reject,返回 error", async () => {
626
- const mockService = createMockService(
627
- vi.fn().mockRejectedValue(new Error("nesting depth exceeded")),
628
- );
629
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
630
- const sar = new SubprocessAgentRunner(deps);
631
-
632
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
633
- expect(result.error).toContain("nesting depth exceeded");
634
- expect(result.content).toBe("");
635
- });
636
-
637
- it("非 Error throw → error 字段含 message", async () => {
638
- const mockService = createMockService(
639
- vi.fn().mockRejectedValue("raw string error"),
640
- );
641
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
642
- const sar = new SubprocessAgentRunner(deps);
643
-
644
- const result = await sar.run(makeBaseOpts(), new AbortController().signal);
645
- expect(result.error).toBe("raw string error");
646
- });
647
- });
648
-
649
- // ────────────────────────────────────────────────
650
- // U1: stream 透传给 executeAndAwait
651
- // ────────────────────────────────────────────────
652
- describe("U1 stream 透传", () => {
653
- it("SAR.run 传 stream → executeAndAwait 第 4 参收到同一 stream 对象", async () => {
654
- let capturedStream: unknown;
655
- const mockService = createMockService(
656
- vi.fn().mockImplementation((_opts, _sig, _onEvt, stream) => {
657
- capturedStream = stream;
658
- return Promise.resolve(makeMockResult());
659
- }),
660
- );
661
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
662
- const sar = new SubprocessAgentRunner(deps);
663
-
664
- const fakeStream = { onDelta: vi.fn(), dispose: vi.fn() };
665
- await sar.run(makeBaseOpts(), new AbortController().signal, undefined, fakeStream as never);
666
-
667
- expect(capturedStream).toBe(fakeStream);
668
- });
669
-
670
- it("SAR.run 不传 stream → executeAndAwait 第 4 参为 undefined", async () => {
671
- let capturedStream: unknown = "sentinel";
672
- const mockService = createMockService(
673
- vi.fn().mockImplementation((_opts, _sig, _onEvt, stream) => {
674
- capturedStream = stream;
675
- return Promise.resolve(makeMockResult());
676
- }),
677
- );
678
- const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
679
- const sar = new SubprocessAgentRunner(deps);
680
-
681
- await sar.run(makeBaseOpts(), new AbortController().signal);
682
-
683
- expect(capturedStream).toBeUndefined();
684
- });
124
+ it("re-export 契约:mergeRunSignals 经本模块路径可用(既有 import 面)", () => {
125
+ // no-progress-killall 测试经本模块 import mergeRunSignals——锁 re-export 行存活
126
+ //(行为本体由 engine/common/run-signals 权威实现及其测试锁定)。
127
+ const handle = mergeRunSignals(new AbortController().signal, 60_000);
128
+ expect(handle.signal.aborted).toBe(false);
129
+ handle.dispose();
685
130
  });
686
131
  });