@selesai/code 0.5.28 → 0.6.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 (341) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +9 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/credential-print.d.ts +23 -0
  10. package/dist/cli/credential-print.d.ts.map +1 -0
  11. package/dist/cli/credential-print.js +117 -0
  12. package/dist/cli/credential-print.js.map +1 -0
  13. package/dist/cli/startup-ui.d.ts.map +1 -1
  14. package/dist/cli/startup-ui.js +1 -1
  15. package/dist/cli/startup-ui.js.map +1 -1
  16. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  17. package/dist/core/agent-session-runtime.js +3 -0
  18. package/dist/core/agent-session-runtime.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +12 -1
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +20 -14
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +11 -3
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/extensions/runner.d.ts +1 -0
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +11 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +14 -1
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js.map +1 -1
  33. package/dist/core/footer-data-provider.d.ts +10 -0
  34. package/dist/core/footer-data-provider.d.ts.map +1 -1
  35. package/dist/core/footer-data-provider.js +1 -1
  36. package/dist/core/footer-data-provider.js.map +1 -1
  37. package/dist/core/llama/provider.d.ts.map +1 -1
  38. package/dist/core/llama/provider.js +8 -3
  39. package/dist/core/llama/provider.js.map +1 -1
  40. package/dist/core/model-config.d.ts +30 -0
  41. package/dist/core/model-config.d.ts.map +1 -1
  42. package/dist/core/model-config.js +6 -0
  43. package/dist/core/model-config.js.map +1 -1
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +2 -2
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts +1 -0
  48. package/dist/core/model-resolver.d.ts.map +1 -1
  49. package/dist/core/model-resolver.js +20 -3
  50. package/dist/core/model-resolver.js.map +1 -1
  51. package/dist/core/model-runtime.d.ts +2 -0
  52. package/dist/core/model-runtime.d.ts.map +1 -1
  53. package/dist/core/model-runtime.js +5 -4
  54. package/dist/core/model-runtime.js.map +1 -1
  55. package/dist/core/package-manager.d.ts.map +1 -1
  56. package/dist/core/package-manager.js +13 -6
  57. package/dist/core/package-manager.js.map +1 -1
  58. package/dist/core/remote-catalog-provider.d.ts +1 -1
  59. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  60. package/dist/core/remote-catalog-provider.js +24 -11
  61. package/dist/core/remote-catalog-provider.js.map +1 -1
  62. package/dist/core/resource-loader.d.ts +15 -0
  63. package/dist/core/resource-loader.d.ts.map +1 -1
  64. package/dist/core/resource-loader.js +66 -9
  65. package/dist/core/resource-loader.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts +1 -1
  67. package/dist/core/settings-manager.d.ts.map +1 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/system-prompt.d.ts.map +1 -1
  70. package/dist/core/system-prompt.js +19 -1
  71. package/dist/core/system-prompt.js.map +1 -1
  72. package/dist/core/system-prompt.test.d.ts +2 -0
  73. package/dist/core/system-prompt.test.d.ts.map +1 -0
  74. package/dist/core/system-prompt.test.js +89 -0
  75. package/dist/core/system-prompt.test.js.map +1 -0
  76. package/dist/core/tools/bash.d.ts +2 -0
  77. package/dist/core/tools/bash.d.ts.map +1 -1
  78. package/dist/core/tools/bash.js +34 -5
  79. package/dist/core/tools/bash.js.map +1 -1
  80. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  81. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  82. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  83. package/dist/defaults/models.json +13 -45
  84. package/dist/defaults/settings.json +1 -2
  85. package/dist/extensions/copy-turn.test.ts +131 -0
  86. package/dist/extensions/copy-turn.ts +6 -1
  87. package/dist/extensions/package.json +0 -1
  88. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  89. package/dist/extensions/pi-intercom/README.md +102 -39
  90. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  91. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  92. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  93. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  94. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  95. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  96. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  97. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  98. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  99. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  100. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  101. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  102. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  103. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  104. package/dist/extensions/pi-intercom/config.ts +55 -6
  105. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  106. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  107. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  108. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  109. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  110. package/dist/extensions/pi-intercom/index.ts +742 -145
  111. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  112. package/dist/extensions/pi-intercom/package.json +15 -5
  113. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  114. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  115. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  116. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  117. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  118. package/dist/extensions/pi-intercom/types.ts +94 -4
  119. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  120. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  121. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  122. package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
  123. package/dist/extensions/pi-subagents/LICENSE +21 -0
  124. package/dist/extensions/pi-subagents/README.md +158 -69
  125. package/dist/extensions/pi-subagents/agents/architect.md +5 -4
  126. package/dist/extensions/pi-subagents/agents/builder.md +6 -4
  127. package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
  128. package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
  129. package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
  130. package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
  131. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  132. package/dist/extensions/pi-subagents/package.json +3 -3
  133. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
  137. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  138. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
  139. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
  140. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  141. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  142. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  143. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
  144. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  145. package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
  146. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  147. package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
  148. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  149. package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
  150. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
  151. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  152. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  153. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
  154. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  155. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  156. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  157. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  158. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  159. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
  160. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
  161. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
  163. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  164. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  165. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
  166. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
  167. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  168. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
  169. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
  170. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  171. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  172. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  173. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  174. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  175. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  176. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  177. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  178. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  179. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  180. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  181. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
  182. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  183. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  184. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
  185. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  186. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  187. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  188. package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
  189. package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
  190. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
  191. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  192. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  193. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  194. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  195. package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
  196. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  197. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  198. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  199. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  200. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  201. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  202. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
  203. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
  204. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  205. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  206. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  207. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
  208. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  209. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
  210. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
  211. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
  212. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  213. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
  214. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
  215. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  216. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  217. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  218. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
  219. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  220. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  221. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  222. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
  223. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
  224. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  225. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  226. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  227. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
  228. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
  229. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  230. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  231. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  232. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  233. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  234. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  235. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
  236. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  237. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  238. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  239. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  240. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
  241. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  242. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  243. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  244. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
  245. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  246. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  247. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  248. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
  249. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  250. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  251. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  252. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  253. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  254. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
  255. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
  256. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  257. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  258. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  259. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  260. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  261. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
  262. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
  263. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
  264. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  265. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  266. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  267. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  268. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  269. package/dist/main.d.ts.map +1 -1
  270. package/dist/main.js +43 -0
  271. package/dist/main.js.map +1 -1
  272. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  273. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/custom-message.js +10 -2
  275. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  277. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  278. package/dist/modes/interactive/components/extension-editor.js +16 -46
  279. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  280. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/model-selector.js +4 -1
  282. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  283. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  285. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  286. package/dist/modes/interactive/external-editor.d.ts +12 -0
  287. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  288. package/dist/modes/interactive/external-editor.js +37 -0
  289. package/dist/modes/interactive/external-editor.js.map +1 -0
  290. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  291. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  292. package/dist/modes/interactive/interactive-mode.js +72 -64
  293. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  294. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  295. package/dist/modes/rpc/rpc-mode.js +14 -0
  296. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  297. package/dist/skills/ponytail/SKILL.md +1 -3
  298. package/dist/utils/clipboard.d.ts.map +1 -1
  299. package/dist/utils/clipboard.js +19 -8
  300. package/dist/utils/clipboard.js.map +1 -1
  301. package/dist/utils/version-check.d.ts.map +1 -1
  302. package/dist/utils/version-check.js +1 -1
  303. package/dist/utils/version-check.js.map +1 -1
  304. package/docs/compaction.md +1 -1
  305. package/docs/custom-provider.md +14 -5
  306. package/docs/environment-variables.md +86 -0
  307. package/docs/extensions.md +15 -5
  308. package/docs/index.md +1 -0
  309. package/docs/models.md +9 -2
  310. package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
  311. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
  312. package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
  313. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
  314. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
  315. package/docs/providers.md +21 -2
  316. package/docs/rpc.md +23 -6
  317. package/docs/session-format.md +2 -0
  318. package/docs/settings.md +1 -1
  319. package/docs/usage.md +0 -13
  320. package/examples/extensions/custom-compaction.ts +5 -2
  321. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  322. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  323. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  324. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  325. package/examples/extensions/gondolin/package-lock.json +2 -2
  326. package/examples/extensions/gondolin/package.json +1 -1
  327. package/examples/extensions/handoff.ts +11 -3
  328. package/examples/extensions/message-renderer.ts +3 -3
  329. package/examples/extensions/sandbox/package-lock.json +2 -2
  330. package/examples/extensions/sandbox/package.json +1 -1
  331. package/examples/extensions/summarize.ts +5 -2
  332. package/examples/extensions/with-deps/package-lock.json +2 -2
  333. package/examples/extensions/with-deps/package.json +1 -1
  334. package/examples/sdk/12-full-control.ts +3 -1
  335. package/package.json +16 -7
  336. package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
  337. package/dist/extensions/caveman/index.js +0 -118
  338. package/dist/extensions/caveman/package.json +0 -8
  339. package/dist/extensions/caveman/test/extension.test.js +0 -203
  340. package/dist/extensions/caveman/test/helpers.test.js +0 -58
  341. package/dist/skills/caveman/SKILL.md +0 -50
@@ -0,0 +1,52 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { describe, it } from "node:test";
6
+ import { MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES, isRuntimeAcknowledgedExtensionId, projectRuntimeAcknowledgedExtensions, readRuntimeAcknowledgedExtensions, sanitizeRuntimeAcknowledgedExtensions } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
7
+
8
+ describe("runtime acknowledged extension projection", () => {
9
+ it("validates opaque ids without path-like content", () => {
10
+ assert.equal(isRuntimeAcknowledgedExtensionId("com.example-extension:v1"), true);
11
+ assert.equal(isRuntimeAcknowledgedExtensionId(""), false);
12
+ assert.equal(isRuntimeAcknowledgedExtensionId("has space"), false);
13
+ assert.equal(isRuntimeAcknowledgedExtensionId("../secret"), false);
14
+ assert.equal(isRuntimeAcknowledgedExtensionId("path/to/ext"), false);
15
+ assert.equal(isRuntimeAcknowledgedExtensionId("path\\to\\ext"), false);
16
+ assert.equal(isRuntimeAcknowledgedExtensionId("x".repeat(129)), false);
17
+ });
18
+
19
+ it("deduplicates, caps ids, and counts omitted valid ids", () => {
20
+ const ids = ["dup", "dup", ...Array.from({ length: 35 }, (_, index) => `ext-${index}`), "bad id"];
21
+ const projected = projectRuntimeAcknowledgedExtensions(ids);
22
+
23
+ assert.equal(projected?.source, "child-runtime");
24
+ assert.equal(projected?.ids.length, 32);
25
+ assert.equal(projected?.ids[0], "dup");
26
+ assert.equal(projected?.omitted, 4);
27
+ assert.equal(projected?.ids.includes("bad id"), false);
28
+ });
29
+
30
+ it("sanitizes persisted projections and rejects invalid shapes", () => {
31
+ assert.deepEqual(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "child-runtime", ids: ["ok", "ok", "bad/path"], omitted: 2 }), {
32
+ version: 1,
33
+ source: "child-runtime",
34
+ ids: ["ok"],
35
+ omitted: 2,
36
+ });
37
+ assert.equal(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "launch-resolved", ids: ["ok"] }), undefined);
38
+ assert.equal(sanitizeRuntimeAcknowledgedExtensions({ version: 1, source: "child-runtime", ids: ["bad/path"] }), undefined);
39
+ });
40
+
41
+ it("rejects oversized capture files without reading them", (t) => {
42
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-ack-"));
43
+ t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
44
+ const filePath = path.join(dir, "ack.json");
45
+
46
+ fs.writeFileSync(filePath, "x".repeat(MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES + 1));
47
+ assert.equal(readRuntimeAcknowledgedExtensions(filePath), undefined);
48
+
49
+ fs.writeFileSync(filePath, JSON.stringify({ version: 1, source: "child-runtime", ids: ["ok"], omitted: 0 }));
50
+ assert.deepEqual(readRuntimeAcknowledgedExtensions(filePath), { version: 1, source: "child-runtime", ids: ["ok"], omitted: 0 });
51
+ });
52
+ });
@@ -38,6 +38,13 @@ interface SubagentParamsSchema {
38
38
  graceTurns?: { minimum?: number };
39
39
  };
40
40
  };
41
+ usageBudget?: {
42
+ properties?: {
43
+ tokens?: { properties?: { soft?: { exclusiveMinimum?: number }; hard?: { exclusiveMinimum?: number } } };
44
+ costUsd?: { properties?: { soft?: { exclusiveMinimum?: number }; hard?: { exclusiveMinimum?: number } } };
45
+ };
46
+ description?: string;
47
+ };
41
48
  id?: {
42
49
  type?: string;
43
50
  description?: string;
@@ -187,6 +194,18 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
187
194
  assert.doesNotMatch(description, /orchestration\./);
188
195
  });
189
196
 
197
+ it("documents list task as an optional advisory intent and keeps action a free string", () => {
198
+ const taskSchema = (SubagentParams?.properties as Record<string, JsonSchemaNode> | undefined)?.task;
199
+ assert.ok(taskSchema, "task schema should exist");
200
+ const description = String(taskSchema?.description ?? "");
201
+ assert.match(description, /action:'list'/);
202
+ assert.match(description, /never launches/);
203
+ assert.match(description, /explicitly call subagent/);
204
+ const actionSchema = SubagentParams?.properties?.action;
205
+ assert.equal(actionSchema?.type, "string");
206
+ assert.equal(actionSchema?.enum, undefined);
207
+ });
208
+
190
209
  it("includes foreground timeout aliases and turn budget", () => {
191
210
  const timeoutSchema = SubagentParams?.properties?.timeoutMs;
192
211
  const maxRuntimeSchema = SubagentParams?.properties?.maxRuntimeMs;
@@ -206,6 +225,19 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
206
225
  assert.equal(toolBudgetSchema?.properties?.hard?.minimum, 1);
207
226
  });
208
227
 
228
+ it("includes root-only reported usage budget", () => {
229
+ const usageBudgetSchema = SubagentParams?.properties?.usageBudget;
230
+ assert.ok(usageBudgetSchema, "usageBudget schema should exist");
231
+ assert.equal(usageBudgetSchema.properties?.tokens?.properties?.soft?.exclusiveMinimum, 0);
232
+ assert.equal(usageBudgetSchema.properties?.tokens?.properties?.hard?.exclusiveMinimum, 0);
233
+ assert.equal(usageBudgetSchema.properties?.costUsd?.properties?.soft?.exclusiveMinimum, 0);
234
+ assert.equal(usageBudgetSchema.properties?.costUsd?.properties?.hard?.exclusiveMinimum, 0);
235
+ assert.match(String(usageBudgetSchema.description ?? ""), /root-only/);
236
+ assert.match(String(usageBudgetSchema.description ?? ""), /running children are not stopped/i);
237
+ assert.equal(getPropertySchema(SubagentParams?.properties?.tasks?.items as JsonSchemaNode | undefined, ["usageBudget"]), undefined);
238
+ assert.equal(getPropertySchema(SubagentParams?.properties?.chain?.items as JsonSchemaNode | undefined, ["usageBudget"]), undefined);
239
+ });
240
+
209
241
  it("includes subagent control fields", () => {
210
242
  const idSchema = SubagentParams?.properties?.id;
211
243
  assert.ok(idSchema, "id schema should exist");
@@ -214,6 +246,12 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
214
246
  assert.match(String(idSchema.description ?? ""), /interrupt/i);
215
247
  assert.match(String(idSchema.description ?? ""), /steer/i);
216
248
  assert.match(String(idSchema.description ?? ""), /append-step/i);
249
+ assert.match(String(idSchema.description ?? ""), /approve-checkpoint/i);
250
+ assert.match(String(idSchema.description ?? ""), /reject-checkpoint/i);
251
+
252
+ const chainItemSchema = SubagentParams?.properties?.chain?.items;
253
+ assert.equal(chainItemSchema?.properties?.checkpoint?.type, "string");
254
+ assert.equal(chainItemSchema?.properties?.message?.type, "string");
217
255
 
218
256
  const runIdSchema = SubagentParams?.properties?.runId;
219
257
  assert.ok(runIdSchema, "runId schema should exist");
@@ -318,12 +356,18 @@ describe("SubagentParams schema", { skip: !schemasAvailable ? "typebox not avail
318
356
  assert.ok(SubagentParams, "SubagentParams schema should exist");
319
357
  const schema = SubagentParams as unknown as JsonSchemaNode;
320
358
  const serialized = JSON.stringify(schema);
321
- assert.ok(serialized.length < 15_000, `expected compact schema under 15k chars, got ${serialized.length}`);
359
+ assert.ok(serialized.length < 16_000, `expected compact schema under 16k chars, got ${serialized.length}`);
322
360
  assert.equal(serialized.includes('"$ref"'), false);
323
361
  assert.equal(serialized.includes('"$defs"'), false);
324
362
  assert.equal(serialized.split("Optional acceptance policy.").length - 1, 1);
325
363
  assert.match(String((schema.properties as Record<string, JsonSchemaNode> | undefined)?.agent?.description ?? ""), /SINGLE mode/);
326
- assert.match(String((schema.properties as Record<string, JsonSchemaNode> | undefined)?.acceptance?.description ?? ""), /acceptance policy/);
364
+ const acceptanceDescription = String((schema.properties as Record<string, JsonSchemaNode> | undefined)?.acceptance?.description ?? "");
365
+ assert.match(acceptanceDescription, /acceptance policy/);
366
+ assert.match(acceptanceDescription, /Supported evidence kinds:/);
367
+ assert.match(acceptanceDescription, /commands-run/);
368
+ assert.match(acceptanceDescription, /changed-files/);
369
+ assert.match(acceptanceDescription, /manual-notes/);
370
+ assert.match(acceptanceDescription, /\{ level: "checked", evidence: \["commands-run", "changed-files"\] \}/);
327
371
 
328
372
  const nestedDescriptionPaths: string[] = [];
329
373
  const stack: Array<{ path: string; value: unknown }> = [{ path: "SubagentParams", value: schema }];
@@ -6,8 +6,10 @@ import * as path from "node:path";
6
6
  import type { Message, Usage } from "@earendil-works/pi-ai";
7
7
  import {
8
8
  captureSingleOutputSnapshot,
9
+ CONTEXT_FALLBACK_LIMIT,
9
10
  extractChildWrittenOutput,
10
11
  finalizeSingleOutput,
12
+ formatBoundedPersistenceFallback,
11
13
  formatSavedOutputReference,
12
14
  injectOutputPathSystemPrompt,
13
15
  injectSingleOutputInstruction,
@@ -269,6 +271,49 @@ describe("validateFileOnlyOutputMode", () => {
269
271
  });
270
272
  });
271
273
 
274
+ describe("CONTEXT_FALLBACK_LIMIT and formatBoundedPersistenceFallback", () => {
275
+ it("exports the fixed internal 80-line/4-KiB context fallback cap", () => {
276
+ assert.equal(CONTEXT_FALLBACK_LIMIT.lines, 80);
277
+ assert.equal(CONTEXT_FALLBACK_LIMIT.bytes, 4096);
278
+ });
279
+
280
+ it("includes error, process status, intended path, and a bounded slice", () => {
281
+ const fallback = formatBoundedPersistenceFallback({
282
+ error: "EACCES: permission denied",
283
+ outputPath: "/tmp/report.md",
284
+ fullOutput: "line one\nline two",
285
+ exitCode: 1,
286
+ processError: "boom",
287
+ });
288
+ assert.match(fallback, /\[Full output unavailable\] EACCES: permission denied/);
289
+ assert.match(fallback, /Process status: boom/);
290
+ assert.match(fallback, /Intended output path: \/tmp\/report\.md/);
291
+ assert.match(fallback, /Full output is unavailable; showing a bounded excerpt \(first 80 lines \/ 4 KiB\)\./);
292
+ assert.match(fallback, /line one\nline two/);
293
+ assert.doesNotMatch(fallback, /Output saved to:/);
294
+ });
295
+
296
+ it("never returns unbounded output when the source exceeds the cap", () => {
297
+ const longOutput = Array.from({ length: 500 }, (_, i) => `line ${i} ${`x`.repeat(50)}`).join("\n");
298
+ const fallback = formatBoundedPersistenceFallback({
299
+ error: "disk full",
300
+ fullOutput: longOutput,
301
+ exitCode: 0,
302
+ });
303
+ const body = fallback.split("\n").filter((line) => /^line \d+/.test(line)).length;
304
+ assert.ok(body <= 80, `fallback kept ${body} lines, expected at most 80`);
305
+ assert.ok(Buffer.byteLength(fallback, "utf-8") <= 12 * 1024, "fallback header plus 4-KiB slice stays bounded");
306
+ assert.doesNotMatch(fallback, /line 400/);
307
+ });
308
+
309
+ it("states completed status for exit code 0 and failed status otherwise", () => {
310
+ const completed = formatBoundedPersistenceFallback({ error: "read failed", fullOutput: "x", exitCode: 0 });
311
+ assert.match(completed, /Process status: completed/);
312
+ const failed = formatBoundedPersistenceFallback({ error: "read failed", fullOutput: "x", exitCode: 2 });
313
+ assert.match(failed, /Process status: failed \(exit 2\)/);
314
+ });
315
+ });
316
+
272
317
  describe("finalizeSingleOutput", () => {
273
318
  it("formats saved-path messaging around the already-resolved output", () => {
274
319
  const result = finalizeSingleOutput({
@@ -298,13 +343,58 @@ describe("finalizeSingleOutput", () => {
298
343
  assert.match(result.displayOutput, /3 lines/);
299
344
  });
300
345
 
301
- it("does not add save messaging on failed runs", () => {
346
+ it("returns error/status plus the saved-output reference on failed runs with a persisted result", () => {
302
347
  const result = finalizeSingleOutput({
303
348
  fullOutput: "full output",
304
349
  truncatedOutput: "truncated output",
305
350
  outputPath: "/tmp/review.md",
306
351
  savedPath: "/tmp/review.md",
307
352
  exitCode: 1,
353
+ error: "exploded",
354
+ });
355
+
356
+ assert.match(result.displayOutput, /^exploded\n\nOutput saved to: \/tmp\/review\.md/);
357
+ assert.doesNotMatch(result.displayOutput, /truncated output/);
358
+ assert.equal(result.savedPath, "/tmp/review.md");
359
+ assert.ok(result.outputReference);
360
+ });
361
+
362
+ it("returns only the bounded persistence-failure fallback when saving failed", () => {
363
+ const result = finalizeSingleOutput({
364
+ fullOutput: "full output text",
365
+ truncatedOutput: "truncated output",
366
+ outputPath: "/tmp/review.md",
367
+ exitCode: 0,
368
+ saveError: "EACCES: permission denied",
369
+ });
370
+
371
+ assert.match(result.displayOutput, /\[Full output unavailable\]/);
372
+ assert.match(result.displayOutput, /Intended output path: \/tmp\/review\.md/);
373
+ assert.match(result.displayOutput, /full output text/);
374
+ assert.doesNotMatch(result.displayOutput, /Output saved to:/);
375
+ assert.equal(result.savedPath, undefined);
376
+ assert.equal(result.saveError, "EACCES: permission denied");
377
+ });
378
+
379
+ it("returns only the bounded persistence-failure fallback on failed runs with a save error", () => {
380
+ const result = finalizeSingleOutput({
381
+ fullOutput: "partial child output",
382
+ outputPath: "/tmp/review.md",
383
+ exitCode: 1,
384
+ saveError: "Failed to read changed output file: boom",
385
+ error: "step failed",
386
+ });
387
+
388
+ assert.match(result.displayOutput, /\[Full output unavailable\] Failed to read changed output file: boom/);
389
+ assert.match(result.displayOutput, /Process status: step failed/);
390
+ assert.doesNotMatch(result.displayOutput, /Output saved to:/);
391
+ });
392
+
393
+ it("keeps the legacy full inline output for successful inline runs without a saved path", () => {
394
+ const result = finalizeSingleOutput({
395
+ fullOutput: "legacy inline output",
396
+ truncatedOutput: "truncated output",
397
+ exitCode: 0,
308
398
  });
309
399
 
310
400
  assert.equal(result.displayOutput, "truncated output");
@@ -340,7 +340,7 @@ describe("skills filesystem fallback", () => {
340
340
  `;
341
341
  execFileSync(
342
342
  process.execPath,
343
- ["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
343
+ ["--experimental-strip-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
344
344
  {
345
345
  cwd: projectRoot,
346
346
  env: {
@@ -60,7 +60,7 @@ function projectRequest(status: AsyncStatus, request: SteerRequest, states: Stee
60
60
  }
61
61
  }
62
62
 
63
- async function waitUntil<T>(read: () => T | undefined, timeoutMs = 1_000): Promise<T> {
63
+ async function waitUntil<T>(read: () => T | undefined, timeoutMs = 30_000): Promise<T> {
64
64
  const deadline = Date.now() + timeoutMs;
65
65
  while (true) {
66
66
  const value = read();
@@ -0,0 +1,78 @@
1
+ import assert from "node:assert/strict";
2
+ import { Buffer } from "node:buffer";
3
+ import { describe, it } from "node:test";
4
+ import {
5
+ boundStreamedRecentOutput,
6
+ boundStreamedRecentTools,
7
+ boundStreamedToolCalls,
8
+ MAX_STREAMED_OUTPUT_LINE_CHARS,
9
+ MAX_STREAMED_RECENT_TOOLS,
10
+ MAX_STREAMED_TOOL_CALLS,
11
+ } from "../../src/shared/utils.ts";
12
+
13
+ describe("streamed progress snapshot bounds", () => {
14
+ it("keeps only the most recent tool-history entries and clones them", () => {
15
+ const recentTools = Array.from({ length: MAX_STREAMED_RECENT_TOOLS + 40 }, (_, i) => ({
16
+ tool: "read",
17
+ args: `file-${i}.ts`,
18
+ endMs: i,
19
+ }));
20
+ const bounded = boundStreamedRecentTools(recentTools);
21
+ assert.equal(bounded.length, MAX_STREAMED_RECENT_TOOLS);
22
+ // most-recent retained (chronological)
23
+ assert.equal(bounded.at(-1)?.endMs, recentTools.at(-1)?.endMs);
24
+ assert.equal(bounded[0]?.endMs, recentTools.at(-MAX_STREAMED_RECENT_TOOLS)?.endMs);
25
+ // cloned, not aliased
26
+ bounded[0]!.args = "mutated";
27
+ assert.notEqual(recentTools.at(-MAX_STREAMED_RECENT_TOOLS)?.args, "mutated");
28
+ });
29
+
30
+ it("truncates long recent-output lines but leaves short lines intact", () => {
31
+ const shortLine = "ok";
32
+ const longLine = "x".repeat(MAX_STREAMED_OUTPUT_LINE_CHARS + 5000);
33
+ const bounded = boundStreamedRecentOutput([shortLine, longLine]);
34
+ assert.equal(bounded[0], shortLine);
35
+ assert.ok(bounded[1]!.length < longLine.length);
36
+ assert.ok(bounded[1]!.startsWith("x".repeat(MAX_STREAMED_OUTPUT_LINE_CHARS)));
37
+ assert.match(bounded[1]!, /\[truncated\]$/);
38
+ });
39
+
40
+ it("prefers an existing toolCalls summary and caps it", () => {
41
+ const toolCalls = Array.from({ length: MAX_STREAMED_TOOL_CALLS + 20 }, (_, i) => ({
42
+ text: `read(${i})`,
43
+ expandedText: `read(file-${i}.ts)`,
44
+ }));
45
+ const bounded = boundStreamedToolCalls({ toolCalls, messages: undefined });
46
+ assert.equal(bounded?.length, MAX_STREAMED_TOOL_CALLS);
47
+ assert.equal(bounded?.at(-1)?.text, toolCalls.at(-1)?.text);
48
+ });
49
+
50
+ it("derives tool-call summaries from messages when none are present", () => {
51
+ const messages = [
52
+ { role: "assistant", content: [{ type: "toolCall", name: "read", arguments: { path: "a.ts" } }] },
53
+ { role: "assistant", content: [{ type: "toolCall", name: "grep", arguments: { pattern: "foo" } }] },
54
+ { role: "user", content: [{ type: "text", text: "ignored" }] },
55
+ ] as never;
56
+ const bounded = boundStreamedToolCalls({ toolCalls: undefined, messages });
57
+ assert.equal(bounded?.length, 2);
58
+ assert.match(bounded![0]!.text, /read/);
59
+ assert.match(bounded![1]!.text, /grep/);
60
+ });
61
+
62
+ it("returns undefined when there are no tool calls", () => {
63
+ assert.equal(boundStreamedToolCalls({ toolCalls: [], messages: [] as never }), undefined);
64
+ assert.equal(boundStreamedToolCalls({ toolCalls: undefined, messages: undefined }), undefined);
65
+ });
66
+
67
+ it("bounds a pathological running snapshot well under the child-stdout protocol cap", () => {
68
+ // Simulates a long, deep nested fan-out: many tool calls + long output lines.
69
+ const recentTools = Array.from({ length: 5000 }, (_, i) => ({ tool: "read", args: `services/pkg/file-${i}.ts`, endMs: i }));
70
+ const recentOutput = Array.from({ length: 50 }, () => "y".repeat(60_000));
71
+ const snapshot = {
72
+ recentTools: boundStreamedRecentTools(recentTools),
73
+ recentOutput: boundStreamedRecentOutput(recentOutput),
74
+ };
75
+ const bytes = Buffer.byteLength(JSON.stringify(snapshot), "utf8");
76
+ assert.ok(bytes < 256 * 1024, `bounded snapshot should be small, was ${bytes} bytes`);
77
+ });
78
+ });
@@ -16,6 +16,7 @@ import {
16
16
  SUBAGENT_STEER_INBOX_ENV,
17
17
  SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
18
18
  } from "../../src/runs/shared/pi-args.ts";
19
+ import { RUNTIME_EXTENSION_ACK_EVENT, RUNTIME_EXTENSION_ACK_PATH_ENV } from "../../src/runs/shared/runtime-acknowledged-extensions.ts";
19
20
  import { STRUCTURED_OUTPUT_CAPTURE_ENV, STRUCTURED_OUTPUT_SCHEMA_ENV } from "../../src/runs/shared/structured-output.ts";
20
21
  import { TOOL_BUDGET_ENV } from "../../src/runs/shared/tool-budget.ts";
21
22
  import { CHILD_TOOL_DIAGNOSTIC_PATH_ENV, formatChildToolDiagnostic, MCP_DIRECT_CHILD_TOOLS_ENV, readChildToolDiagnostic, REQUIRED_CHILD_TOOLS_ENV } from "../../src/runs/shared/tool-availability.ts";
@@ -128,6 +129,42 @@ function setSupervisorEnv(): void {
128
129
  }
129
130
 
130
131
  describe("subagent prompt runtime", () => {
132
+ it("collects runtime extension acknowledgements until terminal serialization", () => {
133
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "subagent-runtime-ack-"));
134
+ try {
135
+ const outputPath = path.join(dir, "acks.json");
136
+ process.env[RUNTIME_EXTENSION_ACK_PATH_ENV] = outputPath;
137
+ const runtimeHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
138
+ const extensionHandlers = new Map<string, Array<(payload?: unknown) => unknown>>();
139
+ const pushHandler = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, handler: (payload?: unknown) => unknown): void => {
140
+ target.set(event, [...(target.get(event) ?? []), handler]);
141
+ };
142
+ const emitAll = (target: Map<string, Array<(payload?: unknown) => unknown>>, event: string, payload?: unknown): void => {
143
+ for (const handler of target.get(event) ?? []) handler(payload);
144
+ };
145
+
146
+ registerSubagentPromptRuntime({
147
+ events: { on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(extensionHandlers, event, handler); } },
148
+ on(event: string, handler: (payload?: unknown) => unknown) { pushHandler(runtimeHandlers, event, handler); },
149
+ } as never);
150
+
151
+ emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
152
+ emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "ext.one" });
153
+ emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "bad/path" });
154
+ runtimeHandlers.get("agent_end")?.[0]?.({});
155
+ emitAll(extensionHandlers, RUNTIME_EXTENSION_ACK_EVENT, { id: "late" });
156
+
157
+ assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf-8")), {
158
+ version: 1,
159
+ source: "child-runtime",
160
+ ids: ["ext.one"],
161
+ omitted: 0,
162
+ });
163
+ } finally {
164
+ fs.rmSync(dir, { recursive: true, force: true });
165
+ }
166
+ });
167
+
131
168
  it("nudges after the tool budget soft limit and blocks configured tools after hard", () => {
132
169
  const handlers = new Map<string, (payload: { toolName?: string }) => unknown>();
133
170
  const sent: string[] = [];
@@ -299,6 +336,10 @@ describe("subagent prompt runtime", () => {
299
336
  });
300
337
 
301
338
  it("registers child watchdog lifecycle handlers only when enabled by env", () => {
339
+ delete process.env[CHILD_WATCHDOG_CONFIG_ENV];
340
+ // Clear the ack capture env explicitly: when this test suite itself runs inside a
341
+ // pi-subagents child, the runner sets it and an extra agent_end handler registers.
342
+ delete process.env[RUNTIME_EXTENSION_ACK_PATH_ENV];
302
343
  const handlersWithout = new Map<string, unknown[]>();
303
344
  registerSubagentPromptRuntime({
304
345
  on(event: string, handler: unknown) {
@@ -0,0 +1,77 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import type { Usage } from "../../src/shared/types.ts";
4
+ import {
5
+ MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS,
6
+ SUBAGENT_STARTUP_RETRY_DELAYS_MS,
7
+ formatSubagentStartupRetryExhaustedError,
8
+ formatSubagentStartupRetryNote,
9
+ isRetryableSubagentStartupFailure,
10
+ waitForSubagentStartupRetry,
11
+ type SubagentStartupFailureEvidence,
12
+ } from "../../src/runs/shared/subagent-startup-retry.ts";
13
+
14
+ function emptyUsage(): Usage {
15
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
16
+ }
17
+
18
+ function startupFailure(overrides: Partial<SubagentStartupFailureEvidence> = {}): SubagentStartupFailureEvidence {
19
+ return {
20
+ exitCode: 1,
21
+ messageCount: 0,
22
+ toolCount: 0,
23
+ usage: emptyUsage(),
24
+ durationMs: 20,
25
+ ...overrides,
26
+ };
27
+ }
28
+
29
+ describe("subagent startup retry", () => {
30
+ it("uses a bounded retry schedule", () => {
31
+ assert.deepEqual(SUBAGENT_STARTUP_RETRY_DELAYS_MS, [250, 750, 1500]);
32
+ });
33
+
34
+ it("classifies a short non-zero exit with no child activity", () => {
35
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure()), true);
36
+ });
37
+
38
+ it("rejects successful, long-running, and diagnosed exits", () => {
39
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ exitCode: 0 })), false);
40
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ durationMs: MAX_SUBAGENT_STARTUP_FAILURE_DURATION_MS + 1 })), false);
41
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ error: "authentication failed" })), false);
42
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ finalOutput: "partial response" })), false);
43
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ protocolError: { code: "protocol_output_limit" } })), false);
44
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ processSignal: "SIGKILL" })), false);
45
+ });
46
+
47
+ it("rejects any model, tool, usage, mutation, or lifecycle activity", () => {
48
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ messageCount: 1 })), false);
49
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ toolCount: 1 })), false);
50
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ usage: { ...emptyUsage(), input: 1 } })), false);
51
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ usage: { ...emptyUsage(), turns: 1 } })), false);
52
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ observedMutationAttempt: true })), false);
53
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ detached: true })), false);
54
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ interrupted: true })), false);
55
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ timedOut: true })), false);
56
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ stopped: true })), false);
57
+ assert.equal(isRetryableSubagentStartupFailure(startupFailure({ turnBudgetExceeded: true })), false);
58
+ });
59
+
60
+ it("formats retry and exhaustion diagnostics without task content", () => {
61
+ assert.equal(
62
+ formatSubagentStartupRetryNote({ model: "openai/test", attempt: 1, maxAttempts: 4, delayMs: 250 }),
63
+ "[startup-retry] openai/test exited before model or tool activity (attempt 1/4). Retrying the same model in 250ms.",
64
+ );
65
+ assert.match(
66
+ formatSubagentStartupRetryExhaustedError({ model: "openai/test", attempts: 4 }),
67
+ /failed to start after 4 attempts.*concurrent Pi startup race.*lower subagent concurrency/i,
68
+ );
69
+ });
70
+
71
+ it("cancels a pending retry delay", async () => {
72
+ const controller = new AbortController();
73
+ const wait = waitForSubagentStartupRetry(5000, [controller.signal]);
74
+ controller.abort();
75
+ assert.equal(await wait, false);
76
+ });
77
+ });