@zhushanwen/subagent-core 0.4.0 → 0.5.1

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 (272) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +16418 -14070
  11. package/dist/index.d.cts +5136 -4016
  12. package/dist/index.d.ts +5136 -4016
  13. package/dist/index.js +16343 -14048
  14. package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
  16. package/package.json +3 -3
  17. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  18. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  19. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  20. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  21. package/src/__tests__/manifest-store.test.ts +10 -9
  22. package/src/__tests__/record-store-cache.test.ts +0 -2
  23. package/src/__tests__/record-store-index.test.ts +1 -2
  24. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  25. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  26. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  27. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  28. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  29. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  30. package/src/__tests__/session-runner.test.ts +1 -1
  31. package/src/core/__tests__/host-services.test.ts +2 -2
  32. package/src/core/__tests__/logger.test.ts +1 -1
  33. package/src/core/error-message.ts +9 -0
  34. package/src/core/host-services.ts +21 -5
  35. package/src/core/notify-ports.ts +18 -5
  36. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  37. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  38. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  39. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  40. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  41. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  42. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  43. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  44. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  45. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  46. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  47. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  48. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  49. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  50. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  51. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  52. package/src/execution/__tests__/execution-record.test.ts +217 -60
  53. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  54. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  55. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  56. package/src/execution/__tests__/gc-timer.test.ts +55 -2
  57. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  58. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  59. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  60. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  61. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  62. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  63. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  64. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  65. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  66. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  67. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  68. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  69. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  70. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  71. package/src/execution/__tests__/notify-ledger.test.ts +973 -18
  72. package/src/execution/__tests__/output-collector.test.ts +117 -6
  73. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  74. package/src/execution/__tests__/record-store.test.ts +10 -9
  75. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  76. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  77. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  78. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  79. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  80. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  81. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  82. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  83. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  84. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  85. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  86. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  87. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  88. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  89. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  90. package/src/execution/__tests__/session-pending.test.ts +257 -14
  91. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  92. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  93. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  94. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  95. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  96. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  97. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  98. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  99. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  100. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  101. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  102. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  103. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  104. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  105. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  106. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  107. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  108. package/src/execution/__tests__/subagent-actions-core.test.ts +42 -22
  109. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  110. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
  111. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  112. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  113. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  114. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  115. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  116. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  117. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  118. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  119. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  120. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  121. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  122. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  123. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  124. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  125. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  126. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  127. package/src/execution/agent-registry.ts +74 -30
  128. package/src/execution/agent-result-mapper.ts +3 -0
  129. package/src/execution/cold-resurrect.ts +199 -0
  130. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  131. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  132. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  133. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  134. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  135. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  136. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  137. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  138. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  139. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  140. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  141. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  142. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  143. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  144. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  145. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  146. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  147. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  148. package/src/execution/engine/common/capability-gate.ts +92 -0
  149. package/src/execution/engine/common/journal-wiring.ts +97 -0
  150. package/src/execution/engine/common/kill-chain.ts +42 -10
  151. package/src/execution/engine/common/nesting-guard.ts +66 -0
  152. package/src/execution/engine/common/persona-router.ts +18 -8
  153. package/src/execution/engine/common/pool-manager.ts +332 -78
  154. package/src/execution/engine/common/session-view-service.ts +506 -0
  155. package/src/execution/engine/common/session-view-types.ts +125 -0
  156. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  157. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  158. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  159. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  160. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  161. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  162. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  163. package/src/execution/engine/engines/pi/reader.ts +11 -0
  164. package/src/execution/engine/engines/pi/registration.ts +4 -2
  165. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  166. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  167. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  168. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  169. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  170. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  171. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  172. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  173. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  174. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  175. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  176. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  177. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  178. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  179. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
  180. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  181. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  182. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  183. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  184. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  185. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  186. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  187. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  188. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  189. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  190. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  191. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  192. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  193. package/src/execution/engine/host-task-spec.ts +32 -36
  194. package/src/execution/engine/model-validation.ts +177 -0
  195. package/src/execution/engine/port.ts +24 -6
  196. package/src/execution/engine/routing.ts +75 -1
  197. package/src/execution/engine/types.ts +30 -103
  198. package/src/execution/execution-record.ts +222 -144
  199. package/src/execution/finalize-record.ts +131 -93
  200. package/src/execution/idle-gc.ts +28 -0
  201. package/src/execution/lifecycle-predicates.ts +1 -1
  202. package/src/execution/notify-host.ts +212 -0
  203. package/src/execution/record-store.ts +119 -73
  204. package/src/execution/round-settlement.ts +93 -0
  205. package/src/execution/session-file-gc.ts +96 -62
  206. package/src/execution/session-reconstructor.ts +256 -143
  207. package/src/execution/sessions-index.ts +117 -47
  208. package/src/execution/settled-watchdog.ts +264 -52
  209. package/src/execution/subagent-actions-core.ts +22 -22
  210. package/src/execution/subagent-service.ts +815 -963
  211. package/src/execution/subprocess-agent-runner.ts +144 -97
  212. package/src/execution/types.ts +9 -0
  213. package/src/execution/ui-request-handler-factory.ts +102 -74
  214. package/src/execution/ui-request-queue.ts +50 -19
  215. package/src/index.ts +442 -316
  216. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  217. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  218. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  219. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  220. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  221. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  222. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  223. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  224. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  225. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  226. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  227. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  228. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  229. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  230. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  231. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  232. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  233. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  234. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  235. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  236. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  237. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  238. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  239. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  240. package/src/orchestration/agent-opts-resolver.ts +1 -1
  241. package/src/orchestration/args-validator.ts +53 -31
  242. package/src/orchestration/execute-agent-call.ts +19 -73
  243. package/src/orchestration/launcher.ts +128 -81
  244. package/src/orchestration/lifecycle.ts +43 -45
  245. package/src/orchestration/models/ports.ts +4 -4
  246. package/src/orchestration/models/run-runtime.ts +2 -2
  247. package/src/orchestration/models/trace.ts +1 -1
  248. package/src/orchestration/models/types.ts +111 -22
  249. package/src/orchestration/models/workflow-run.ts +1 -1
  250. package/src/orchestration/script-lint.ts +91 -58
  251. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  252. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  253. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  254. package/src/shared/atomic-write.ts +12 -9
  255. package/src/shared/meta-parser.ts +158 -84
  256. package/src/shared/model-ref.ts +15 -2
  257. package/src/shared/resource-discovery.ts +9 -183
  258. package/src/shared/schema-jsonify.ts +1 -1
  259. package/workflows/review-fix-loop-utils.cjs +151 -89
  260. package/workflows/review-fix-loop-utils.d.cts +287 -0
  261. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  262. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  263. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  264. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  265. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  266. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  267. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  268. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  269. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  270. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  271. package/src/execution/execute-options-mapper.ts +0 -115
  272. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -1,7 +1,7 @@
1
1
  // src/__tests__/turn-limiter.test.ts
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
- import { createTurnLimiter } from "../turn-limiter.ts";
4
+ import { createTurnLimiter } from "../engine/engines/pi/turn-limiter.ts";
5
5
 
6
6
  describe("createTurnLimiter", () => {
7
7
  it("does nothing before maxTurns", () => {
@@ -9,7 +9,10 @@
9
9
  // realHandler 路由:channel 命中 → channelHandler(经 coerceUiResponse 形变);未命中 → defaultDialogForward(dialog 转发结果,fire-and-forget 转发 ctx.ui.* 后回 ack,未知 method warn + ack)。
10
10
  // 测接口契约,不测实现细节。
11
11
 
12
- import type { ExtensionContext, ExtensionMode } from "@earendil-works/pi-coding-agent";
12
+ import type { ExtensionContext, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
13
+
14
+ // ExtensionMode 未从 pi 包根 re-export(实装 0.84.4 核对),从权威 ExtensionContext 派生
15
+ type ExtensionMode = ExtensionContext["mode"];
13
16
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
17
 
15
18
  const { loggerMock } = vi.hoisted(() => ({
@@ -39,14 +42,17 @@ function makeCtx(mode: ExtensionMode): ExtensionContext {
39
42
  },
40
43
  modelRegistry: undefined,
41
44
  model: undefined,
42
- } as ExtensionContext;
45
+ } as unknown as ExtensionContext;
43
46
  }
44
47
 
45
48
  /** 带 mock ctx.ui 的 ExtensionContext(GUI fire-and-forget 转发测试用)。
46
49
  * dialog method(select/confirm/input/editor)返回 undefined/true/"" 兜底,
47
50
  * fire-and-forget method(notify/setStatus/setWidget/setTitle/setEditorText)是 void spy。 */
48
- function makeCtxWithUi(mode: ExtensionMode = "rpc"): ExtensionContext & { ui: Record<string, ReturnType<typeof vi.fn>> } {
49
- const ui: Record<string, ReturnType<typeof vi.fn>> = {
51
+ // ui 成员 = 真实签名 & vi.fn 能力(映射交叉):调用点走真实签名,mock 打点方法可达
52
+ type MockedUi = { [K in keyof ExtensionUIContext]: ExtensionUIContext[K] & ReturnType<typeof vi.fn> };
53
+
54
+ function makeCtxWithUi(mode: ExtensionMode = "rpc"): Omit<ExtensionContext, "ui"> & { ui: MockedUi } {
55
+ const ui = {
50
56
  select: vi.fn(async () => undefined),
51
57
  confirm: vi.fn(async () => true),
52
58
  input: vi.fn(async () => ""),
@@ -67,8 +73,8 @@ function makeCtxWithUi(mode: ExtensionMode = "rpc"): ExtensionContext & { ui: Re
67
73
  },
68
74
  modelRegistry: undefined,
69
75
  model: undefined,
70
- ui,
71
- } as unknown as ExtensionContext & { ui: Record<string, ReturnType<typeof vi.fn>> };
76
+ ui: ui as unknown as MockedUi,
77
+ } as unknown as Omit<ExtensionContext, "ui"> & { ui: MockedUi };
72
78
  }
73
79
 
74
80
  function dialogReq(id: string, channel?: string): UiRequest {
@@ -18,8 +18,8 @@ import { PassThrough } from "node:stream";
18
18
 
19
19
  import { describe, expect, it, vi } from "vitest";
20
20
 
21
- import { ASK_USER_RPC_PROMPT } from "../session-runner.ts";
22
- import { parseSpawnLine } from "../spawn-event-adapter.ts";
21
+ import { ASK_USER_RPC_PROMPT } from "../engine/engines/pi/session-runner.ts";
22
+ import { parseSpawnLine } from "../engine/engines/pi/spawn-event-adapter.ts";
23
23
  import { parseChannel } from "../ui-channels.ts";
24
24
  import { createUiRequestQueue, type UiRequest, type UiRequestHandler } from "../ui-request-queue.ts";
25
25
 
@@ -69,7 +69,8 @@ describe("parseSpawnLine — ask_user 请求解析(Pi 原生格式)", () =>
69
69
  it("request.title === ASK_USER_MARKER(NUL 前缀 marker 原样保留,由 parseChannel 提取)", () => {
70
70
  const result = parseSpawnLine(askUserLine("ui-req-001"));
71
71
  if (result?.kind !== "extension_ui_request") return;
72
- expect(result.request.title).toBe(ASK_USER_MARKER);
72
+ if (result.request.method !== "select") return expect.fail("request.method 应为 select");
73
+ expect((result.request as { title?: string }).title).toBe(ASK_USER_MARKER);
73
74
  });
74
75
 
75
76
  it("id 被正确提取(用于 response 关联)", () => {
@@ -55,7 +55,7 @@ describe("UI 请求队列", () => {
55
55
  it("多个 extension_ui_request 按 FIFO 顺序处理", async () => {
56
56
  const callOrder: string[] = [];
57
57
  // 每个请求返回独立的可控 Promise
58
- const resolvers: Array<(v: unknown) => void> = [];
58
+ const resolvers: Array<(v: UiResponse) => void> = [];
59
59
 
60
60
  const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
61
61
  callOrder.push(req.title ?? "");
@@ -94,7 +94,7 @@ describe("UI 请求队列", () => {
94
94
 
95
95
  it("第一个请求未 resolve 时第二个不调用 uiRequestHandler", async () => {
96
96
  const callOrder: string[] = [];
97
- let firstResolve: (v: unknown) => void;
97
+ let firstResolve: (v: UiResponse) => void;
98
98
 
99
99
  const handler: UiRequestHandler = vi.fn((req: UiRequest) => {
100
100
  callOrder.push(req.title ?? "");
@@ -156,3 +156,143 @@ describe("UI 请求队列 — timeout 字段透传(LC-3/T2⑦)", () => {
156
156
  expect(received[0].timeout).toBe(5000);
157
157
  });
158
158
  });
159
+
160
+ // ── extractMethodFields 字段复制链路(经 createUiRequestQueue → handleUiRequest 锁定)──
161
+ // 13 个 method-specific 字段的复制语义:typed 守卫字段(string/number/array)验型通过才复制、
162
+ // presence-only 字段(statusText/widgetLines/widgetPlacement)仅 in 检查直赋、缺失字段不复制。
163
+ // 协议形状来自 JSON 反序列化,坏类型运行时可达——用例以 as unknown as 构造越界形状做防御锁定。
164
+
165
+ describe("UI 请求队列 — method-specific 字段复制(extractMethodFields 链路)", () => {
166
+ function makeQueue(handler: UiRequestHandler): { enqueue: ReturnType<typeof createUiRequestQueue> } {
167
+ const child = makeFakeChild();
168
+ const ctx = { uiRequestHandler: handler } as Parameters<
169
+ typeof createUiRequestQueue
170
+ >[1];
171
+ return { enqueue: createUiRequestQueue(child, ctx) };
172
+ }
173
+
174
+ it("全字段请求:typed 守卫字段与 presence-only 字段全部复制到 UiRequest", async () => {
175
+ const received: UiRequest[] = [];
176
+ const { enqueue } = makeQueue((req) => {
177
+ received.push(req);
178
+ return Promise.resolve<UiResponse>({ ack: true });
179
+ });
180
+
181
+ // 13 字段全量形状(协议 union 无单变体携带全部字段,运行时平铺可达)
182
+ const req = {
183
+ method: "select",
184
+ title: "t",
185
+ options: ["a", "b"],
186
+ message: "m",
187
+ placeholder: "p",
188
+ prefill: "pf",
189
+ notifyType: "warning",
190
+ statusKey: "sk",
191
+ statusText: "st",
192
+ widgetKey: "wk",
193
+ widgetLines: ["l1"],
194
+ widgetPlacement: "belowEditor",
195
+ text: "tx",
196
+ timeout: 2500,
197
+ } as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
198
+
199
+ enqueue("r1", req);
200
+ await vi.advanceTimersByTimeAsync(0);
201
+
202
+ expect(received).toHaveLength(1);
203
+ expect(received[0]).toMatchObject({
204
+ title: "t",
205
+ options: ["a", "b"],
206
+ message: "m",
207
+ placeholder: "p",
208
+ prefill: "pf",
209
+ notifyType: "warning",
210
+ statusKey: "sk",
211
+ statusText: "st",
212
+ widgetKey: "wk",
213
+ widgetLines: ["l1"],
214
+ widgetPlacement: "belowEditor",
215
+ text: "tx",
216
+ timeout: 2500,
217
+ });
218
+ });
219
+
220
+ it("typed 守卫字段类型不符时跳过复制,其余字段照常", async () => {
221
+ const received: UiRequest[] = [];
222
+ const { enqueue } = makeQueue((req) => {
223
+ received.push(req);
224
+ return Promise.resolve<UiResponse>({ ack: true });
225
+ });
226
+
227
+ const req = {
228
+ method: "select",
229
+ title: "kept-title", // 合法(title 非法值会在链路上游 startsWith 处炸掉整条请求,见下一条用例)
230
+ options: "not-array", // string ≠ array → 跳过
231
+ timeout: "5000", // string ≠ number → 跳过
232
+ message: "kept",
233
+ } as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
234
+
235
+ enqueue("r1", req);
236
+ await vi.advanceTimersByTimeAsync(0);
237
+
238
+ expect(received).toHaveLength(1);
239
+ expect(received[0].title).toBe("kept-title");
240
+ expect("options" in received[0]).toBe(false);
241
+ expect("timeout" in received[0]).toBe(false);
242
+ expect(received[0].message).toBe("kept");
243
+ });
244
+
245
+ it("typed 非法 title(number)在上游 startsWith 处炸链路 → handler 不被调、单请求失败被吞不阻塞队列", async () => {
246
+ // HEAD 真实行为锚定(探针实证):title: 42 在 extractMethodFields 之前的环节
247
+ // 触发 str.startsWith is not a function,异常被 handleUiRequest 的 .catch 吞掉
248
+ //(注释声明的「单个请求失败不阻塞后续」语义)——守卫的「跳过该字段」对 title 不可达。
249
+ const received: UiRequest[] = [];
250
+ const { enqueue } = makeQueue((req) => {
251
+ received.push(req);
252
+ return Promise.resolve<UiResponse>({ ack: true });
253
+ });
254
+
255
+ const badReq = {
256
+ method: "select",
257
+ title: 42,
258
+ message: "kept",
259
+ } as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
260
+ enqueue("r1", badReq);
261
+ await vi.advanceTimersByTimeAsync(0);
262
+ expect(received).toHaveLength(0); // handler 未被调(链路在上游失败)
263
+
264
+ // 队列不阻塞:后续合法请求照常处理
265
+ enqueue("r2", {
266
+ method: "select",
267
+ title: "Q2",
268
+ options: [JSON.stringify({ question: "Q2", options: [{ label: "A" }] })],
269
+ });
270
+ await vi.advanceTimersByTimeAsync(0);
271
+ expect(received).toHaveLength(1);
272
+ expect(received[0].title).toBe("Q2");
273
+ });
274
+
275
+ it("presence-only 字段仅 in 检查直赋;未声明的字段不复制(保持 UiRequest 可选)", async () => {
276
+ const received: UiRequest[] = [];
277
+ const { enqueue } = makeQueue((req) => {
278
+ received.push(req);
279
+ return Promise.resolve<UiResponse>({ ack: true });
280
+ });
281
+
282
+ // statusText 存在但值为 undefined(statusText: string | undefined 变体合法形态)→ 仍复制
283
+ const req = {
284
+ method: "setStatus",
285
+ statusKey: "k",
286
+ statusText: undefined,
287
+ unknownField: "should-not-copy",
288
+ } as unknown as Parameters<ReturnType<typeof createUiRequestQueue>>[1];
289
+
290
+ enqueue("r1", req);
291
+ await vi.advanceTimersByTimeAsync(0);
292
+
293
+ expect(received).toHaveLength(1);
294
+ expect("statusText" in received[0]).toBe(true);
295
+ expect(received[0].statusText).toBeUndefined();
296
+ expect("unknownField" in received[0]).toBe(false);
297
+ });
298
+ });
@@ -80,7 +80,16 @@ import { isProcessAlive } from "../alive-store.ts";
80
80
  import { encodeCwd } from "../path-encoding.ts";
81
81
  import { WorktreeManager } from "../worktree-manager.ts";
82
82
 
83
- const mockExecFile = vi.mocked(execFile);
83
+ // 被测链路(gitRunAsync)调用四参形态(file, args, options, callback);vi.mocked
84
+ // 直接包 execFile 会推导到无 options 重载,显式绑定四参签名
85
+ const mockExecFile = vi.mocked(
86
+ execFile as unknown as (
87
+ file: string,
88
+ args: readonly string[],
89
+ options: unknown,
90
+ callback: (err: Error | null, stdout: string, stderr: string) => void,
91
+ ) => void,
92
+ );
84
93
  const mockExistsSync = vi.mocked(fs.existsSync);
85
94
  const mockIsProcessAlive = vi.mocked(isProcessAlive);
86
95
 
@@ -95,7 +104,7 @@ type ExecFileResult = {
95
104
  };
96
105
  function setupExecFile(impl?: (args: readonly string[]) => ExecFileResult): void {
97
106
  mockExecFile.mockImplementation(
98
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
107
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
99
108
  const r = impl ? impl(args) : { stdout: "" };
100
109
  if (r.err) cb(r.err, r.stdout ?? "", r.stderr ?? "");
101
110
  else cb(null, r.stdout ?? "", r.stderr ?? "");
@@ -178,7 +187,7 @@ describe("WorktreeManager", () => {
178
187
  await mgr.create(MAIN_CWD, RECORD_ID);
179
188
 
180
189
  expect(mockAdd).toHaveBeenCalledTimes(1);
181
- const entry = mockAdd.mock.calls[0][0] as { repo: string; branch: string; pid: number };
190
+ const entry = mockAdd.mock.calls[0]![0] as { repo: string; branch: string; pid: number };
182
191
  expect(entry.repo).toBe(MAIN_CWD);
183
192
  expect(entry.branch).toBe(`pi-sub-${RECORD_ID}`);
184
193
  expect(entry.pid).toBe(0);
@@ -266,7 +275,7 @@ describe("WorktreeManager", () => {
266
275
  let maxWriteActive = 0;
267
276
  let addCount = 0;
268
277
  mockExecFile.mockImplementation(
269
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
278
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
270
279
  const isAdd = args[0] === "worktree" && args[1] === "add";
271
280
  if (isAdd) {
272
281
  // 只对写命令(worktree add)计并发——读命令(status/rev-parse)无锁可并行是设计预期
@@ -442,7 +451,7 @@ describe("WorktreeManager", () => {
442
451
  let maxActive = 0;
443
452
  // 异步完成 cb:若队列失效,后继命令的 impl 入口会先于前驱 cb 到达 → active=2
444
453
  mockExecFile.mockImplementation(
445
- (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout?: string, stderr?: string) => void) => {
454
+ (_cmd: string, args: readonly string[], _opts: unknown, cb: (err: Error | null, stdout: string, stderr: string) => void) => {
446
455
  active++;
447
456
  maxActive = Math.max(maxActive, active);
448
457
  setTimeout(() => {
@@ -52,7 +52,7 @@ vi.mock("./pi-invocation.ts", () => ({
52
52
 
53
53
  import { WorktreeManager } from "../worktree-manager.ts";
54
54
  import { WorktreeRegistry, SPAWN_GRACE_MS } from "../worktree-registry.ts";
55
- import { runSpawn } from "../session-runner.ts";
55
+ import { runSpawn } from "../engine/engines/pi/session-runner.ts";
56
56
  import type { WorktreeHandle } from "../types.ts";
57
57
  import { makeCtx, makeOpts, makeRecord } from "./helpers/spawn-mock.ts";
58
58
 
@@ -118,7 +118,7 @@ async function cleanup(): Promise<void> {
118
118
  handle = undefined;
119
119
  }
120
120
  try {
121
- fs.rmSync(tmpRoot, { recursive: true, force: true });
121
+ fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
122
122
  } catch {
123
123
  // best-effort
124
124
  }
@@ -213,7 +213,8 @@ describe("worktree pid 注册链路(真实 spawn 集成)", { timeout: 30_000
213
213
  makeOpts({ worktree: handle }),
214
214
  ctx,
215
215
  );
216
- expect(result.status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
216
+ // 历史「status !== error」弱断言:AgentResult status 字段,经视图访问保持断言语义不变
217
+ expect((result as { status?: string }).status).not.toBe("error"); // 进程正常退出(exit 0),非 spawn 失败
217
218
 
218
219
  // 3. pid 已补全(短命进程退出后 pid 仍有效,registerPid 同步执行不受退出影响)
219
220
  const entry = readEntry(handle.branch);
@@ -56,41 +56,64 @@ export function parseAgentWithMeta(
56
56
  content: string,
57
57
  ): { config: AgentConfig; meta: AgentMeta | null } {
58
58
  const name = path.basename(filePath, ".md");
59
+ // 结构切分三态(与 parseAgentProfile 共用 splitAgentFrontmatter,切分逻辑逐字节一致)
60
+ const fm = splitAgentFrontmatter(content);
59
61
 
60
62
  // 无 frontmatter → 整个内容作为 systemPrompt
61
- if (!content.startsWith(FM_DELIM)) {
63
+ if (fm.kind === "none") {
62
64
  return { config: { name, systemPrompt: content.trim() }, meta: null };
63
65
  }
64
66
 
65
- const closeIdx = content.indexOf(FM_DELIM, FM_DELIM.length);
66
- if (closeIdx === -1) {
67
- // 未闭合 frontmatter:提取 name,其余作为 systemPrompt
68
- const yamlBlock = content.slice(FM_DELIM.length);
67
+ // 未闭合 frontmatter:提取 name,其余作为 systemPrompt
68
+ if (fm.kind === "unclosed") {
69
69
  return {
70
70
  config: {
71
- name: extractYamlField(yamlBlock, "name") ?? name,
71
+ name: extractYamlField(fm.yamlBlock, "name") ?? name,
72
72
  systemPrompt: content.trim(),
73
73
  },
74
74
  meta: null,
75
75
  };
76
76
  }
77
77
 
78
- const yamlBlock = content.slice(FM_DELIM.length, closeIdx);
79
- const body = content.slice(closeIdx + FM_DELIM.length).trim();
78
+ return parseClosedFrontmatterAgent(filePath, name, content, fm.yamlBlock, fm.body);
79
+ }
80
80
 
81
+ /** closed frontmatter 的主路径:IF1 严格层 + legacy fallback 字段解析 + config 投影。
82
+ * 字段解析/warn/throw 时序与提取前一致(parseResourceMeta → fallback+warn → engine+校验 → 投影)。 */
83
+ function parseClosedFrontmatterAgent(
84
+ filePath: string,
85
+ name: string,
86
+ content: string,
87
+ yamlBlock: string,
88
+ body: string,
89
+ ): { config: AgentConfig; meta: AgentMeta | null } {
81
90
  // m2:结构化路由字段(name/model/tools)经 IF1 parseResourceMeta(统一 parser),
82
91
  // 消灭本地 frontmatter parser 与 subagent-list-injector 的重复。thinkingLevel/defaultBackground
83
92
  // 是执行配置(非 AgentMeta 路由字段),仍用 extractYamlField 取。
84
93
  const meta = parseResourceMeta(content, "agent");
85
94
  const agentMeta = meta?.kind === "agent" ? meta : null;
95
+ const legacyFallbacks = resolveLegacyRoutingFallbacks(agentMeta, yamlBlock, filePath);
96
+ // engine 字段(D9):结构化优先(IF1),legacy fallback 与 model/tools 同判——
97
+ // agentMeta 未通过 IF1 时配置不丢
98
+ const engine = resolveAgentEngine(agentMeta, yamlBlock, filePath);
99
+
100
+ return {
101
+ config: projectAgentConfig(name, body, agentMeta, legacyFallbacks, engine, yamlBlock),
102
+ meta: agentMeta,
103
+ };
104
+ }
105
+
106
+ /** legacy fallback 路由字段(model/tools):IF1 未通过时不静默丢失(MF-3),并按需 warn。 */
107
+ function resolveLegacyRoutingFallbacks(
108
+ agentMeta: AgentMeta | null,
109
+ yamlBlock: string,
110
+ filePath: string,
111
+ ): { modelFallback: string | undefined; toolsFallback: string[] | undefined } {
86
112
  // MF-3 regression fix:agentMeta=null(IF1 要求 name/description 必填,缺则 parseResourceMeta
87
113
  // 返 null)时,model/tools 不能静默丢失——fallback 用 extractYamlField 取(保留重构前行为)。
88
114
  // 注入路由可见性由 discovery/injector 按 available 标志决定,与本处(direct-path loadByPath)无关。
89
115
  const modelFallback = extractYamlField(yamlBlock, "model");
90
- const toolsFallbackRaw = extractYamlField(yamlBlock, "tools");
91
- const toolsFallback = toolsFallbackRaw
92
- ? toolsFallbackRaw.split(",").map((s) => s.trim()).filter(Boolean)
93
- : undefined;
116
+ const toolsFallback = parseCommaListFallback(extractYamlField(yamlBlock, "tools"));
94
117
  if (!agentMeta && /^model:|^tools:/m.test(yamlBlock)) {
95
118
  // m2 exec-review MINOR-2 + MF-3:IF1 未通过(缺 name/description)→ model/tools 经 legacy
96
119
  // fallback 取(direct-path loadByPath 不丢配置,与重构前一致);但结构化路由注入不可见,
@@ -100,32 +123,53 @@ export function parseAgentWithMeta(
100
123
  "model/tools 经 legacy fallback 生效(直接路径不丢配置),但结构化路由不可见——请补充 description",
101
124
  );
102
125
  }
103
- const defaultBackgroundRaw = extractYamlField(yamlBlock, "defaultBackground");
104
- // engine 字段(D9):结构化优先(IF1),legacy fallback 与 model/tools 同判——
105
- // agentMeta 未通过 IF1 时配置不丢
126
+ return { modelFallback, toolsFallback };
127
+ }
128
+
129
+ /** engine 字段解析 + 解析期注册校验(D9:未注册 id 前置暴露,不留到运行时神秘失败)。
130
+ * 为什么在解析期而非路由期:配置错误的根源在 .md 文件,越早报错定位越准(错误含文件路径 + 注册清单)。 */
131
+ function resolveAgentEngine(
132
+ agentMeta: AgentMeta | null,
133
+ yamlBlock: string,
134
+ filePath: string,
135
+ ): string | undefined {
106
136
  const engine = agentMeta?.engine ?? extractYamlField(yamlBlock, "engine");
107
- // 解析期校验(D9:未注册 id 前置暴露,不留到运行时神秘失败)。为什么在解析期而非
108
- // 路由期:配置错误的根源在 .md 文件,越早报错定位越准(错误含文件路径 + 注册清单)
109
137
  if (engine !== undefined && !hasEngine(engine)) {
110
138
  throw new EngineNotFoundError(engine, listEngines(), filePath);
111
139
  }
140
+ return engine;
141
+ }
112
142
 
143
+ /** closed frontmatter 的 AgentConfig 投影(IF1 严格层优先,逐字段 legacy fallback)。 */
144
+ function projectAgentConfig(
145
+ name: string,
146
+ body: string,
147
+ agentMeta: AgentMeta | null,
148
+ fallbacks: { modelFallback: string | undefined; toolsFallback: string[] | undefined },
149
+ engine: string | undefined,
150
+ yamlBlock: string,
151
+ ): AgentConfig {
113
152
  return {
114
- config: {
115
- name: agentMeta?.name ?? name,
116
- systemPrompt: body,
117
- model: agentMeta?.model ?? modelFallback ?? undefined,
118
- thinkingLevel: extractYamlField(yamlBlock, "thinkingLevel") ?? undefined,
119
- ...(engine !== undefined ? { engine } : {}),
120
- tools: agentMeta?.tools && agentMeta.tools.length > 0
121
- ? agentMeta.tools
122
- : (toolsFallback && toolsFallback.length > 0 ? toolsFallback : undefined),
123
- defaultBackground: defaultBackgroundRaw === "true" ? true : undefined,
124
- },
125
- meta: agentMeta,
153
+ name: agentMeta?.name ?? name,
154
+ systemPrompt: body,
155
+ model: agentMeta?.model ?? fallbacks.modelFallback ?? undefined,
156
+ thinkingLevel: extractYamlField(yamlBlock, "thinkingLevel") ?? undefined,
157
+ ...(engine !== undefined ? { engine } : {}),
158
+ tools: resolveAgentTools(agentMeta, fallbacks.toolsFallback),
159
+ defaultBackground: extractYamlField(yamlBlock, "defaultBackground") === "true" ? true : undefined,
126
160
  };
127
161
  }
128
162
 
163
+ /** tools 投影:IF1 严格层非空清单优先,否则 legacy fallback 非空清单,均缺省 undefined。 */
164
+ function resolveAgentTools(
165
+ agentMeta: AgentMeta | null,
166
+ toolsFallback: string[] | undefined,
167
+ ): string[] | undefined {
168
+ return agentMeta?.tools && agentMeta.tools.length > 0
169
+ ? agentMeta.tools
170
+ : (toolsFallback && toolsFallback.length > 0 ? toolsFallback : undefined);
171
+ }
172
+
129
173
  /** 提取简单 `key: value` 字段,剥离引号。 */
130
174
  function extractYamlField(yaml: string, key: string): string | undefined {
131
175
  const regex = new RegExp(`^${key}:\\s*(.+)$`, "m");
@@ -255,7 +299,7 @@ type AgentFrontmatterSlice =
255
299
  | { kind: "unclosed"; yamlBlock: string }
256
300
  | { kind: "closed"; yamlBlock: string; body: string };
257
301
 
258
- /** frontmatter 结构切分(与 parseAgentWithMeta 同款定位逻辑,产出三态供分派)。 */
302
+ /** frontmatter 结构切分(产出三态供分派;parseAgentWithMeta parseAgentProfile 共用单点)。 */
259
303
  function splitAgentFrontmatter(text: string): AgentFrontmatterSlice {
260
304
  if (!text.startsWith(FM_DELIM)) return { kind: "none" };
261
305
  const closeIdx = text.indexOf(FM_DELIM, FM_DELIM.length);
@@ -35,6 +35,9 @@ export function mapToWorkflowAgentResult(
35
35
  ): WorkflowAgentResult {
36
36
  return {
37
37
  content: r.text,
38
+ // [D5-③] 失败分诊标签透传(产出侧 output-collector 写入,消费侧
39
+ // executeAgentCall 读字段分诊)。成功路径 undefined 不落键。
40
+ ...(r.failureKind !== undefined ? { failureKind: r.failureKind } : {}),
38
41
  parsedOutput: r.parsedOutput,
39
42
  error: r.success ? undefined : (r.error || "Agent call failed (aborted or unknown error)"),
40
43
  durationMs: r.durationMs,