@selesai/code 0.5.19 → 0.5.21

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 (265) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -1,8 +1,11 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { execFileSync } from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
3
5
  import * as path from "node:path";
4
6
  import { fileURLToPath } from "node:url";
5
7
  import { describe, it } from "node:test";
8
+ import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/subagent-wait.ts";
6
9
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../../src/runs/shared/pi-args.ts";
7
10
 
8
11
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
@@ -11,13 +14,14 @@ function parentToolEnv(): NodeJS.ProcessEnv {
11
14
  const env = { ...process.env };
12
15
  delete env[SUBAGENT_CHILD_ENV];
13
16
  delete env[SUBAGENT_FANOUT_CHILD_ENV];
17
+ delete env[WAIT_TOOL_ENABLED_ENV];
14
18
  return env;
15
19
  }
16
20
 
17
21
  describe("subagent extension child mode", () => {
18
22
  it("collapses tool detail before direct subagent tool execution", () => {
19
23
  const script = String.raw`
20
- import registerSubagentExtension from "./src/extension/index.ts";
24
+ import registerSubagentExtension from "./index.ts";
21
25
  const events = { on() { return () => {}; }, emit() {} };
22
26
  let registeredTool;
23
27
  const fakePi = new Proxy({
@@ -69,7 +73,7 @@ describe("subagent extension child mode", () => {
69
73
 
70
74
  it("does not show async badge for explicit foreground clarify chain calls", () => {
71
75
  const script = String.raw`
72
- import registerSubagentExtension from "./src/extension/index.ts";
76
+ import registerSubagentExtension from "./index.ts";
73
77
  const events = { on() { return () => {}; }, emit() {} };
74
78
  let registeredTool;
75
79
  const fakePi = new Proxy({
@@ -111,9 +115,363 @@ describe("subagent extension child mode", () => {
111
115
  );
112
116
  });
113
117
 
118
+ it("does not animate foreground results on a timer", () => {
119
+ const script = String.raw`
120
+ import registerSubagentExtension from "./index.ts";
121
+ const events = { on() { return () => {}; }, emit() {} };
122
+ let registeredTool;
123
+ const fakePi = new Proxy({
124
+ events,
125
+ registerTool(tool) { if (tool.name === "subagent") registeredTool = tool; },
126
+ registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
127
+ sendMessage() {}, getSessionName() { return undefined; },
128
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
129
+ registerSubagentExtension(fakePi);
130
+ if (!registeredTool) throw new Error("tool not registered");
131
+ let invalidations = 0;
132
+ let legacyTicks = 0;
133
+ const context = {
134
+ state: { subagentResultAnimationTimer: setInterval(() => { legacyTicks += 1; }, 10) },
135
+ invalidate() { invalidations += 1; },
136
+ };
137
+ registeredTool.renderResult({
138
+ content: [{ type: "text", text: "running" }],
139
+ details: {
140
+ mode: "single",
141
+ results: [{
142
+ agent: "worker", task: "quiet", exitCode: 0, messages: [],
143
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
144
+ progress: { status: "running", index: 0, agent: "worker", toolCount: 0, tokens: 0, durationMs: 0 },
145
+ }],
146
+ },
147
+ }, { expanded: false }, { fg(_name, text) { return text; }, bold(text) { return text; } }, context);
148
+ await new Promise((resolve) => setTimeout(resolve, 120));
149
+ if (context.state.subagentResultAnimationTimer) clearInterval(context.state.subagentResultAnimationTimer);
150
+ if (context.state.subagentResultAnimationTimer !== undefined) throw new Error("legacy timer was not cleared");
151
+ if (legacyTicks !== 0) throw new Error("legacy timer ticked " + legacyTicks + " times");
152
+ if (invalidations !== 0) throw new Error("foreground result invalidated " + invalidations + " times");
153
+ `;
154
+
155
+ execFileSync(
156
+ process.execPath,
157
+ [
158
+ "--experimental-transform-types",
159
+ "--import",
160
+ "./test/support/register-loader.mjs",
161
+ "--input-type=module",
162
+ "--eval",
163
+ script,
164
+ ],
165
+ { cwd: projectRoot, env: parentToolEnv(), stdio: "pipe" },
166
+ );
167
+ });
168
+
169
+ it("registers only subagent_wait and honors waitTool disabled config", () => {
170
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-wait-tool-config-"));
171
+ try {
172
+ const configDir = path.join(agentDir, "extensions", "subagent");
173
+ fs.mkdirSync(configDir, { recursive: true });
174
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ waitTool: { enabled: false } }), "utf-8");
175
+
176
+ const script = String.raw`
177
+ import registerSubagentExtension from "./index.ts";
178
+ const events = { on() { return () => {}; }, emit() {} };
179
+ let subagentWaitTool;
180
+ let legacyWaitRegistered = false;
181
+ const fakePi = new Proxy({
182
+ events,
183
+ registerTool(tool) {
184
+ if (tool.name === "subagent_wait") subagentWaitTool = tool;
185
+ if (tool.name === "wait") legacyWaitRegistered = true;
186
+ },
187
+ registerCommand() {},
188
+ registerShortcut() {},
189
+ registerMessageRenderer() {},
190
+ sendMessage() {},
191
+ getSessionName() { return undefined; },
192
+ }, {
193
+ get(target, prop) {
194
+ if (prop in target) return target[prop];
195
+ return () => undefined;
196
+ },
197
+ });
198
+ registerSubagentExtension(fakePi);
199
+ if (!subagentWaitTool) throw new Error("subagent_wait tool not registered");
200
+ if (legacyWaitRegistered) throw new Error("legacy wait tool must not be registered");
201
+ const result = await subagentWaitTool.execute("subagent-wait-disabled", {}, new AbortController().signal, undefined, {});
202
+ process.stdout.write(JSON.stringify(result.content[0].text));
203
+ `;
204
+
205
+ const env = parentToolEnv();
206
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
207
+ const output = execFileSync(
208
+ process.execPath,
209
+ [
210
+ "--experimental-transform-types",
211
+ "--import",
212
+ "./test/support/register-loader.mjs",
213
+ "--input-type=module",
214
+ "--eval",
215
+ script,
216
+ ],
217
+ { cwd: projectRoot, env, encoding: "utf-8" },
218
+ );
219
+ assert.match(JSON.parse(output) as string, /disabled/i);
220
+ } finally {
221
+ fs.rmSync(agentDir, { recursive: true, force: true });
222
+ }
223
+ });
224
+
225
+ it("does not restore the async widget from tool results when asyncWidget is disabled", () => {
226
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-async-widget-config-"));
227
+ try {
228
+ const configDir = path.join(agentDir, "extensions", "subagent");
229
+ fs.mkdirSync(configDir, { recursive: true });
230
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ asyncWidget: false }), "utf-8");
231
+ const script = String.raw`
232
+ import registerSubagentExtension from "./index.ts";
233
+ const eventHandlers = new Map();
234
+ const handlers = new Map();
235
+ const events = { on(channel, handler) { eventHandlers.set(channel, handler); return () => {}; }, emit() {} };
236
+ const fakePi = new Proxy({
237
+ events,
238
+ on(channel, handler) { handlers.set(channel, handler); },
239
+ registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
240
+ sendMessage() {}, getSessionName() { return undefined; },
241
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
242
+ const widgets = [];
243
+ const ctx = {
244
+ cwd: process.cwd(), hasUI: true,
245
+ ui: { setWidget(key, value) { widgets.push({ key, value }); }, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
246
+ sessionManager: { getSessionId() { return "session-widget"; }, getSessionFile() { return null; }, getEntries() { return []; } },
247
+ modelRegistry: { getAvailable() { return []; } },
248
+ };
249
+ registerSubagentExtension(fakePi);
250
+ handlers.get("session_start")({}, ctx);
251
+ widgets.length = 0;
252
+ eventHandlers.get("subagent:async-started")({ id: "widget-run", pid: 1, sessionId: "session-widget", mode: "single", agent: "worker", asyncDir: "/tmp/widget-run" });
253
+ handlers.get("tool_result")({ toolName: "subagent" }, ctx);
254
+ const asyncWidgets = widgets.filter((entry) => entry.key === "subagent-async");
255
+ if (asyncWidgets.length < 2 || asyncWidgets.some((entry) => entry.value !== undefined)) throw new Error("async widget rendered despite disabled config: " + JSON.stringify(asyncWidgets));
256
+ handlers.get("session_shutdown")();
257
+ `;
258
+ const env = parentToolEnv();
259
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
260
+ execFileSync(process.execPath, ["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script], { cwd: projectRoot, env, stdio: "pipe" });
261
+ } finally {
262
+ fs.rmSync(agentDir, { recursive: true, force: true });
263
+ }
264
+ });
265
+
266
+ it("disposes pending completion notifications on session shutdown", () => {
267
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-notify-shutdown-"));
268
+ const configDir = path.join(agentDir, "extensions", "subagent");
269
+ fs.mkdirSync(configDir, { recursive: true });
270
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ completionBatch: { enabled: true, debounceMs: 150 } }), "utf-8");
271
+ const script = String.raw`
272
+ import registerSubagentExtension from "./index.ts";
273
+ const pendingTimers = new Map();
274
+ const realSetTimeout = globalThis.setTimeout;
275
+ const realClearTimeout = globalThis.clearTimeout;
276
+ globalThis.setTimeout = (handler) => {
277
+ const token = {};
278
+ pendingTimers.set(token, handler);
279
+ return token;
280
+ };
281
+ globalThis.clearTimeout = (token) => pendingTimers.delete(token);
282
+ const eventListeners = new Map();
283
+ const events = {
284
+ on(channel, handler) {
285
+ let listeners = eventListeners.get(channel);
286
+ if (!listeners) eventListeners.set(channel, listeners = new Set());
287
+ listeners.add(handler);
288
+ return () => listeners.delete(handler);
289
+ },
290
+ emit(channel, payload) {
291
+ for (const handler of [...(eventListeners.get(channel) ?? [])]) handler(payload);
292
+ },
293
+ };
294
+ const handlers = new Map();
295
+ const sent = [];
296
+ const fakePi = new Proxy({
297
+ events,
298
+ on(channel, handler) { handlers.set(channel, handler); },
299
+ registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
300
+ sendMessage(message) { sent.push(message); }, getSessionName() { return undefined; },
301
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
302
+ const ctx = {
303
+ cwd: process.cwd(), hasUI: false,
304
+ ui: { setWidget() {}, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
305
+ sessionManager: { getSessionId() { return "notify-shutdown-session"; }, getSessionFile() { return null; }, getEntries() { return []; } },
306
+ modelRegistry: { getAvailable() { return []; } },
307
+ };
308
+ registerSubagentExtension(fakePi);
309
+ handlers.get("session_start")({}, ctx);
310
+ sent.length = 0;
311
+ events.emit("subagent:async-complete", {
312
+ id: "shutdown-held-completion", agent: "worker", success: true, summary: "Done",
313
+ exitCode: 0, timestamp: Date.now(), sessionId: "notify-shutdown-session",
314
+ });
315
+ if (sent.length !== 0) throw new Error("completion was not queued before shutdown");
316
+ const heldTimers = [...pendingTimers.values()];
317
+ if (heldTimers.length === 0) throw new Error("completion did not schedule a timer");
318
+ handlers.get("session_shutdown")();
319
+ if (pendingTimers.size !== 0) throw new Error("shutdown left completion timers pending");
320
+ for (const handler of heldTimers) handler();
321
+ if (sent.length !== 0) throw new Error("stale completion sent after shutdown");
322
+ globalThis.setTimeout = realSetTimeout;
323
+ globalThis.clearTimeout = realClearTimeout;
324
+ `;
325
+
326
+ try {
327
+ const env = parentToolEnv();
328
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
329
+ execFileSync(
330
+ process.execPath,
331
+ ["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
332
+ { cwd: projectRoot, env, stdio: "pipe" },
333
+ );
334
+ } finally {
335
+ fs.rmSync(agentDir, { recursive: true, force: true });
336
+ }
337
+ });
338
+
339
+ it("disposes pending completion notifications during runtime reload cleanup", () => {
340
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-notify-reload-"));
341
+ const configDir = path.join(agentDir, "extensions", "subagent");
342
+ fs.mkdirSync(configDir, { recursive: true });
343
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ completionBatch: { enabled: true, debounceMs: 150 } }), "utf-8");
344
+ const script = String.raw`
345
+ import registerSubagentExtension from "./index.ts";
346
+ const pendingTimers = new Map();
347
+ const realSetTimeout = globalThis.setTimeout;
348
+ const realClearTimeout = globalThis.clearTimeout;
349
+ globalThis.setTimeout = (handler) => {
350
+ const token = {};
351
+ pendingTimers.set(token, handler);
352
+ return token;
353
+ };
354
+ globalThis.clearTimeout = (token) => pendingTimers.delete(token);
355
+ function createRuntime(sessionId) {
356
+ const eventListeners = new Map();
357
+ const handlers = new Map();
358
+ const sent = [];
359
+ const events = {
360
+ on(channel, handler) {
361
+ let listeners = eventListeners.get(channel);
362
+ if (!listeners) eventListeners.set(channel, listeners = new Set());
363
+ listeners.add(handler);
364
+ return () => listeners.delete(handler);
365
+ },
366
+ emit(channel, payload) {
367
+ for (const handler of [...(eventListeners.get(channel) ?? [])]) handler(payload);
368
+ },
369
+ };
370
+ const pi = new Proxy({
371
+ events,
372
+ on(channel, handler) { handlers.set(channel, handler); },
373
+ registerTool() {}, registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {},
374
+ sendMessage(message) { sent.push(message); }, getSessionName() { return undefined; },
375
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
376
+ const ctx = {
377
+ cwd: process.cwd(), hasUI: false,
378
+ ui: { setWidget() {}, requestRender() {}, theme: { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } } },
379
+ sessionManager: { getSessionId() { return sessionId; }, getSessionFile() { return null; }, getEntries() { return []; } },
380
+ modelRegistry: { getAvailable() { return []; } },
381
+ };
382
+ return { pi, events, handlers, sent, ctx };
383
+ }
384
+ const oldRuntime = createRuntime("notify-reload-old");
385
+ registerSubagentExtension(oldRuntime.pi);
386
+ oldRuntime.handlers.get("session_start")({}, oldRuntime.ctx);
387
+ oldRuntime.sent.length = 0;
388
+ const timersBeforeOldCompletion = new Set(pendingTimers.keys());
389
+ oldRuntime.events.emit("subagent:async-complete", {
390
+ id: "reload-held-completion", agent: "worker", success: true, summary: "Old",
391
+ exitCode: 0, timestamp: Date.now(), sessionId: "notify-reload-old",
392
+ });
393
+ if (oldRuntime.sent.length !== 0) throw new Error("old completion was not queued before reload");
394
+ const oldCompletionTimers = [...pendingTimers.entries()].filter(([token]) => !timersBeforeOldCompletion.has(token));
395
+ if (oldCompletionTimers.length === 0) throw new Error("old completion did not schedule a timer");
396
+
397
+ const newRuntime = createRuntime("notify-reload-new");
398
+ registerSubagentExtension(newRuntime.pi);
399
+ newRuntime.handlers.get("session_start")({}, newRuntime.ctx);
400
+ for (const [, handler] of oldCompletionTimers) handler();
401
+ if (oldRuntime.sent.length !== 0) throw new Error("stale completion sent after runtime cleanup");
402
+
403
+ const timersBeforeNewCompletion = new Set(pendingTimers.keys());
404
+ newRuntime.events.emit("subagent:async-complete", {
405
+ id: "reload-new-completion", agent: "reviewer", success: true, summary: "New",
406
+ exitCode: 0, timestamp: Date.now(), sessionId: "notify-reload-new",
407
+ });
408
+ const newCompletionTimers = [...pendingTimers.entries()].filter(([token]) => !timersBeforeNewCompletion.has(token));
409
+ if (newCompletionTimers.length === 0) throw new Error("new completion did not schedule a timer");
410
+ for (const [token, handler] of newCompletionTimers) {
411
+ pendingTimers.delete(token);
412
+ handler();
413
+ }
414
+ if (newRuntime.sent.length !== 1) throw new Error("new notifier was not active after reload cleanup");
415
+ newRuntime.handlers.get("session_shutdown")();
416
+ globalThis.setTimeout = realSetTimeout;
417
+ globalThis.clearTimeout = realClearTimeout;
418
+ `;
419
+
420
+ try {
421
+ const env = parentToolEnv();
422
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
423
+ execFileSync(
424
+ process.execPath,
425
+ ["--experimental-transform-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script],
426
+ { cwd: projectRoot, env, stdio: "pipe" },
427
+ );
428
+ } finally {
429
+ fs.rmSync(agentDir, { recursive: true, force: true });
430
+ }
431
+ });
432
+
433
+ it("registers the main watchdog command and renderer in parent mode", () => {
434
+ const script = String.raw`
435
+ import registerSubagentExtension from "./index.ts";
436
+ const events = { on() { return () => {}; }, emit() {} };
437
+ const commands = [];
438
+ const renderers = [];
439
+ const fakePi = new Proxy({
440
+ events,
441
+ registerTool() {},
442
+ registerCommand(name) { commands.push(name); },
443
+ registerShortcut() {},
444
+ registerMessageRenderer(type) { renderers.push(type); },
445
+ sendMessage() {},
446
+ getSessionName() { return undefined; },
447
+ }, {
448
+ get(target, prop) {
449
+ if (prop in target) return target[prop];
450
+ return () => undefined;
451
+ },
452
+ });
453
+ registerSubagentExtension(fakePi);
454
+ if (!commands.includes("subagents-watchdog")) throw new Error("watchdog command not registered: " + commands.join(", "));
455
+ if (!renderers.includes("subagent_watchdog_warning")) throw new Error("watchdog renderer not registered: " + renderers.join(", "));
456
+ `;
457
+
458
+ execFileSync(
459
+ process.execPath,
460
+ [
461
+ "--experimental-transform-types",
462
+ "--import",
463
+ "./test/support/register-loader.mjs",
464
+ "--input-type=module",
465
+ "--eval",
466
+ script,
467
+ ],
468
+ { cwd: projectRoot, env: parentToolEnv(), stdio: "pipe" },
469
+ );
470
+ });
471
+
114
472
  it("returns before registering anything for non-fanout children", () => {
115
473
  const script = String.raw`
116
- import registerSubagentExtension from "./src/extension/index.ts";
474
+ import registerSubagentExtension from "./index.ts";
117
475
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
118
476
  process.env[SUBAGENT_CHILD_ENV] = "1";
119
477
  process.env[SUBAGENT_FANOUT_CHILD_ENV] = "0";
@@ -148,7 +506,7 @@ describe("subagent extension child mode", () => {
148
506
 
149
507
  it("returns before registering anything for fanout children", () => {
150
508
  const script = String.raw`
151
- import registerSubagentExtension from "./src/extension/index.ts";
509
+ import registerSubagentExtension from "./index.ts";
152
510
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
153
511
  process.env[SUBAGENT_CHILD_ENV] = "1";
154
512
  process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
@@ -184,7 +542,7 @@ describe("subagent extension child mode", () => {
184
542
 
185
543
  it("does not double-register the child-safe subagent tool when index and fanout-child both load", () => {
186
544
  const script = String.raw`
187
- import registerSubagentExtension from "./src/extension/index.ts";
545
+ import registerSubagentExtension from "./index.ts";
188
546
  import registerFanoutChildSubagentExtension from "./src/extension/fanout-child.ts";
189
547
  import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "./src/runs/shared/pi-args.ts";
190
548
  process.env[SUBAGENT_CHILD_ENV] = "1";
@@ -253,6 +611,10 @@ describe("subagent extension child mode", () => {
253
611
  if (!create.isError) throw new Error("create should be blocked");
254
612
  const text = create.content?.[0]?.text ?? "";
255
613
  if (!text.includes("not available from child-safe subagent fanout mode")) throw new Error("unexpected create error: " + text);
614
+ const grant = await registeredTool.execute("grant-check", { action: "grant-spawn-budget", additional: 1 }, new AbortController().signal, undefined, { ...ctx, hasUI: true });
615
+ if (!grant.isError) throw new Error("grant-spawn-budget should be blocked");
616
+ const grantText = grant.content?.[0]?.text ?? "";
617
+ if (!grantText.includes("root interactive parent session")) throw new Error("unexpected grant error: " + grantText);
256
618
  `;
257
619
 
258
620
  execFileSync(
@@ -170,4 +170,15 @@ describe("applyIntercomBridgeToAgent", () => {
170
170
  assert.deepEqual(updated.tools, ["read", "intercom", "contact_supervisor"]);
171
171
  assert.match(updated.systemPrompt, /contact_supervisor/);
172
172
  });
173
+
174
+ it("does not widen explicit empty or MCP-only builtin allowlists", () => {
175
+ const emptyTools = applyIntercomBridgeToAgent(makeAgent({ tools: [] }), activeBridge);
176
+ assert.deepEqual(emptyTools.tools, []);
177
+ assert.match(emptyTools.systemPrompt, /contact_supervisor/);
178
+
179
+ const mcpOnly = applyIntercomBridgeToAgent(makeAgent({ tools: [], mcpDirectTools: ["github/search_repositories"] }), activeBridge);
180
+ assert.deepEqual(mcpOnly.tools, []);
181
+ assert.deepEqual(mcpOnly.mcpDirectTools, ["github/search_repositories"]);
182
+ assert.match(mcpOnly.systemPrompt, /contact_supervisor/);
183
+ });
173
184
  });
@@ -5,6 +5,7 @@ import {
5
5
  fuzzyResolveModel,
6
6
  isRetryableModelFailure,
7
7
  normalizeModelSegment,
8
+ resolveEffectiveSubagentModel,
8
9
  resolveModelCandidate,
9
10
  resolveSubagentModelOverride,
10
11
  } from "../../src/runs/shared/model-fallback.ts";
@@ -71,6 +72,8 @@ describe("model fallback helpers", () => {
71
72
  assert.equal(isRetryableModelFailure("authentication failed"), true);
72
73
  assert.equal(isRetryableModelFailure("Subagent produced no output (possible model cold-start or empty response)."), true);
73
74
  assert.equal(isRetryableModelFailure("model load failed"), true);
75
+ assert.equal(isRetryableModelFailure("Stream ended without finish_reason"), true);
76
+ assert.equal(isRetryableModelFailure("Request timed out."), true);
74
77
  });
75
78
 
76
79
  it("does not treat ordinary task/tool failures as retryable model failures", () => {
@@ -78,6 +81,16 @@ describe("model fallback helpers", () => {
78
81
  assert.equal(isRetryableModelFailure("read failed (exit 1): no such file or directory"), false);
79
82
  assert.equal(isRetryableModelFailure(undefined), false);
80
83
  });
84
+
85
+ it("does not treat network-flavored tool failures as retryable model failures", () => {
86
+ assert.equal(isRetryableModelFailure("bash failed (exit 1): requests.exceptions.ConnectionError: Connection error."), false);
87
+ assert.equal(isRetryableModelFailure("bash failed (exit 1): urllib.error.URLError: request timed out"), false);
88
+ assert.equal(isRetryableModelFailure("fetch_content failed with exit code 1"), false);
89
+ assert.equal(isRetryableModelFailure("mcp.server/write failed (exit 1): request timed out"), false);
90
+ assert.equal(isRetryableModelFailure("mcp:tools.search failed with exit code 1"), false);
91
+ assert.equal(isRetryableModelFailure("Provider error: bash failed (exit 1): request timed out"), true);
92
+ assert.equal(isRetryableModelFailure("bash failed (exit unknown): request timed out"), true);
93
+ });
81
94
  });
82
95
 
83
96
  describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", () => {
@@ -151,6 +164,29 @@ describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", ()
151
164
  });
152
165
  });
153
166
 
167
+ describe("resolveEffectiveSubagentModel", () => {
168
+ const availableModels = [
169
+ { provider: "openai", id: "gpt-5-mini", fullId: "openai/gpt-5-mini" },
170
+ ];
171
+
172
+ it("falls back to the agent model when inheritance has no parent", () => {
173
+ assert.equal(resolveEffectiveSubagentModel("", "openai/gpt-5-mini", undefined, availableModels), "openai/gpt-5-mini");
174
+ assert.equal(resolveEffectiveSubagentModel("inherit", "openai/gpt-5-mini", undefined, availableModels), "openai/gpt-5-mini");
175
+ });
176
+
177
+ it("keeps agent models inherited for scope enforcement", () => {
178
+ const warnings: string[] = [];
179
+ assert.equal(
180
+ resolveEffectiveSubagentModel(undefined, "openai/gpt-5-mini", undefined, availableModels, undefined, {
181
+ scope: { enforce: true, allow: ["anthropic/*"] },
182
+ onWarn: (violation) => warnings.push(violation.message),
183
+ }),
184
+ "openai/gpt-5-mini",
185
+ );
186
+ assert.equal(warnings.length, 1);
187
+ });
188
+ });
189
+
154
190
  describe("fuzzyResolveModel / normalizeModelSegment", () => {
155
191
  const registry = [
156
192
  { provider: "anthropic", id: "claude-sonnet-4", fullId: "anthropic/claude-sonnet-4" },
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { findModelInfo, getSupportedThinkingLevels, type ModelInfo } from "../../src/shared/model-info.ts";
3
+ import { findModelInfo, getSupportedThinkingLevels, splitKnownThinkingSuffix, toModelInfo, type ModelInfo } from "../../src/shared/model-info.ts";
4
4
 
5
5
  describe("model info helpers", () => {
6
6
  const ambiguousModels: ModelInfo[] = [
@@ -20,14 +20,19 @@ describe("model info helpers", () => {
20
20
  assert.equal(findModelInfo("openai/gpt-5-mini:high", ambiguousModels, "github-copilot")?.fullId, "openai/gpt-5-mini");
21
21
  });
22
22
 
23
- it("keeps the legacy full thinking list for reasoning models without per-level metadata", () => {
23
+ it("preserves registry API metadata", () => {
24
+ assert.equal(toModelInfo({ provider: "gateway", id: "model", api: "anthropic-messages" }).api, "anthropic-messages");
25
+ });
26
+
27
+ it("keeps the legacy thinking list for models without per-level metadata", () => {
24
28
  assert.deepEqual(
25
29
  getSupportedThinkingLevels({ provider: "openai", id: "gpt-5", fullId: "openai/gpt-5", reasoning: true }),
26
30
  ["off", "minimal", "low", "medium", "high", "xhigh"],
27
31
  );
32
+ assert.deepEqual(getSupportedThinkingLevels(undefined), ["off", "minimal", "low", "medium", "high", "xhigh"]);
28
33
  });
29
34
 
30
- it("keeps the legacy full thinking list when older model metadata omits reasoning", () => {
35
+ it("keeps the legacy thinking list when older model metadata omits reasoning", () => {
31
36
  assert.deepEqual(
32
37
  getSupportedThinkingLevels({ provider: "openai", id: "gpt-5", fullId: "openai/gpt-5" }),
33
38
  ["off", "minimal", "low", "medium", "high", "xhigh"],
@@ -59,4 +64,21 @@ describe("model info helpers", () => {
59
64
  ["high"],
60
65
  );
61
66
  });
67
+
68
+ it("honors an explicit max mapping and recognizes max suffixes", () => {
69
+ assert.deepEqual(
70
+ getSupportedThinkingLevels({
71
+ provider: "openai",
72
+ id: "gpt-5",
73
+ fullId: "openai/gpt-5",
74
+ reasoning: true,
75
+ thinkingLevelMap: { off: null, minimal: null, low: null, medium: null, high: null, xhigh: null, max: "max" },
76
+ }),
77
+ ["max"],
78
+ );
79
+ assert.deepEqual(
80
+ splitKnownThinkingSuffix("openai/gpt-5:max"),
81
+ { baseModel: "openai/gpt-5", thinkingSuffix: ":max" },
82
+ );
83
+ });
62
84
  });
@@ -97,7 +97,7 @@ describe("checkModelScope", () => {
97
97
  });
98
98
 
99
99
  describe("parseModelScopeConfig", () => {
100
- const meta = { filePath: "~/.pi/agent/settings.json" };
100
+ const meta = { filePath: "~/.selesai/agent/settings.json" };
101
101
 
102
102
  it("returns undefined when the field is absent", () => {
103
103
  assert.equal(parseModelScopeConfig(undefined, meta), undefined);