@selesai/code 0.5.27 → 0.5.29

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 (310) hide show
  1. package/dist/cli/args.d.ts.map +1 -1
  2. package/dist/cli/args.js +9 -1
  3. package/dist/cli/args.js.map +1 -1
  4. package/dist/cli/config-selector.d.ts.map +1 -1
  5. package/dist/cli/config-selector.js +1 -1
  6. package/dist/cli/config-selector.js.map +1 -1
  7. package/dist/cli/credential-print.d.ts +23 -0
  8. package/dist/cli/credential-print.d.ts.map +1 -0
  9. package/dist/cli/credential-print.js +117 -0
  10. package/dist/cli/credential-print.js.map +1 -0
  11. package/dist/cli/startup-ui.d.ts.map +1 -1
  12. package/dist/cli/startup-ui.js +1 -1
  13. package/dist/cli/startup-ui.js.map +1 -1
  14. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  15. package/dist/core/agent-session-runtime.js +3 -0
  16. package/dist/core/agent-session-runtime.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +12 -1
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +20 -14
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/compaction/compaction.d.ts.map +1 -1
  22. package/dist/core/compaction/compaction.js +11 -3
  23. package/dist/core/compaction/compaction.js.map +1 -1
  24. package/dist/core/extensions/runner.d.ts +1 -0
  25. package/dist/core/extensions/runner.d.ts.map +1 -1
  26. package/dist/core/extensions/runner.js +11 -0
  27. package/dist/core/extensions/runner.js.map +1 -1
  28. package/dist/core/extensions/types.d.ts +14 -1
  29. package/dist/core/extensions/types.d.ts.map +1 -1
  30. package/dist/core/extensions/types.js.map +1 -1
  31. package/dist/core/footer-data-provider.d.ts +10 -0
  32. package/dist/core/footer-data-provider.d.ts.map +1 -1
  33. package/dist/core/footer-data-provider.js +1 -1
  34. package/dist/core/footer-data-provider.js.map +1 -1
  35. package/dist/core/llama/provider.d.ts.map +1 -1
  36. package/dist/core/llama/provider.js +8 -3
  37. package/dist/core/llama/provider.js.map +1 -1
  38. package/dist/core/model-config.d.ts +30 -0
  39. package/dist/core/model-config.d.ts.map +1 -1
  40. package/dist/core/model-config.js +6 -0
  41. package/dist/core/model-config.js.map +1 -1
  42. package/dist/core/model-registry.d.ts.map +1 -1
  43. package/dist/core/model-registry.js +2 -2
  44. package/dist/core/model-registry.js.map +1 -1
  45. package/dist/core/model-resolver.d.ts +1 -0
  46. package/dist/core/model-resolver.d.ts.map +1 -1
  47. package/dist/core/model-resolver.js +20 -3
  48. package/dist/core/model-resolver.js.map +1 -1
  49. package/dist/core/model-runtime.d.ts +2 -0
  50. package/dist/core/model-runtime.d.ts.map +1 -1
  51. package/dist/core/model-runtime.js +5 -4
  52. package/dist/core/model-runtime.js.map +1 -1
  53. package/dist/core/package-manager.d.ts.map +1 -1
  54. package/dist/core/package-manager.js +13 -6
  55. package/dist/core/package-manager.js.map +1 -1
  56. package/dist/core/remote-catalog-provider.d.ts +1 -1
  57. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  58. package/dist/core/remote-catalog-provider.js +24 -11
  59. package/dist/core/remote-catalog-provider.js.map +1 -1
  60. package/dist/core/resource-loader.d.ts +15 -0
  61. package/dist/core/resource-loader.d.ts.map +1 -1
  62. package/dist/core/resource-loader.js +66 -9
  63. package/dist/core/resource-loader.js.map +1 -1
  64. package/dist/core/settings-manager.d.ts +1 -1
  65. package/dist/core/settings-manager.d.ts.map +1 -1
  66. package/dist/core/settings-manager.js.map +1 -1
  67. package/dist/core/system-prompt.d.ts.map +1 -1
  68. package/dist/core/system-prompt.js +1 -1
  69. package/dist/core/system-prompt.js.map +1 -1
  70. package/dist/core/tools/bash.d.ts +2 -0
  71. package/dist/core/tools/bash.d.ts.map +1 -1
  72. package/dist/core/tools/bash.js +34 -5
  73. package/dist/core/tools/bash.js.map +1 -1
  74. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  75. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  76. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  77. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  78. package/dist/extensions/pi-intercom/README.md +102 -39
  79. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  80. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  81. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  82. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  83. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  84. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  85. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  86. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  87. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  88. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  89. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  90. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  91. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  92. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  93. package/dist/extensions/pi-intercom/config.ts +55 -6
  94. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  95. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  96. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  97. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  98. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  99. package/dist/extensions/pi-intercom/index.ts +742 -145
  100. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  101. package/dist/extensions/pi-intercom/package.json +15 -5
  102. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  103. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  104. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  105. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  106. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  107. package/dist/extensions/pi-intercom/types.ts +94 -4
  108. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  109. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  110. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  111. package/dist/extensions/pi-subagents/CHANGELOG.md +85 -0
  112. package/dist/extensions/pi-subagents/LICENSE +21 -0
  113. package/dist/extensions/pi-subagents/README.md +146 -52
  114. package/dist/extensions/pi-subagents/agents/architect.md +1 -0
  115. package/dist/extensions/pi-subagents/agents/builder.md +1 -0
  116. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  117. package/dist/extensions/pi-subagents/package.json +3 -3
  118. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  119. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +21 -989
  120. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +256 -0
  121. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +430 -0
  122. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  123. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +282 -0
  124. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +71 -30
  125. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  126. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  127. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  128. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  129. package/dist/extensions/pi-subagents/src/api/preflight.ts +16 -12
  130. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  131. package/dist/extensions/pi-subagents/src/extension/index.ts +17 -7
  132. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  133. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -6
  134. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +8 -7
  135. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  136. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  137. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +63 -16
  138. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  139. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  140. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  141. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  142. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  143. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +29 -4
  144. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +29 -6
  145. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  146. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +372 -28
  147. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  148. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  149. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +143 -43
  150. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +527 -247
  151. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  152. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +426 -123
  153. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +15 -7
  154. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  155. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  156. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  157. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  158. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  159. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  160. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  161. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  162. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  163. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  164. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  165. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  166. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  167. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +1 -1
  168. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  169. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  170. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  171. package/dist/extensions/pi-subagents/src/shared/types.ts +155 -10
  172. package/dist/extensions/pi-subagents/src/shared/utils.ts +73 -4
  173. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -0
  174. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  175. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  176. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  177. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  178. package/dist/extensions/pi-subagents/src/tui/render.ts +27 -28
  179. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  180. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  181. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  182. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  183. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  184. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  185. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  186. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +348 -7
  187. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  188. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  189. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  190. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +26 -1
  191. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  192. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +3 -3
  193. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +213 -13
  194. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +34 -0
  195. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  196. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +118 -12
  197. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +850 -7
  198. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  199. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  200. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  201. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  202. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  203. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +128 -0
  204. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  205. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  206. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  207. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +102 -0
  208. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  209. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  210. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  211. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  212. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  213. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  214. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +4 -2
  215. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  216. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  217. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  218. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  219. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +11 -11
  220. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  221. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  222. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  223. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +10 -2
  224. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  225. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  226. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  227. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +46 -0
  228. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  229. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  230. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  231. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  232. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  233. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +34 -2
  234. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  235. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  236. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  237. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  238. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  239. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +3 -0
  240. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +1 -1
  241. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  245. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  246. package/dist/extensions/question/index.ts +14 -1
  247. package/dist/extensions/question/tests/wizard.test.ts +11 -8
  248. package/dist/main.d.ts.map +1 -1
  249. package/dist/main.js +43 -0
  250. package/dist/main.js.map +1 -1
  251. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  252. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  253. package/dist/modes/interactive/components/custom-message.js +10 -2
  254. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  255. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  256. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  257. package/dist/modes/interactive/components/extension-editor.js +16 -46
  258. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  259. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/model-selector.js +4 -1
  261. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  262. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  263. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  264. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  265. package/dist/modes/interactive/external-editor.d.ts +12 -0
  266. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  267. package/dist/modes/interactive/external-editor.js +37 -0
  268. package/dist/modes/interactive/external-editor.js.map +1 -0
  269. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  270. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  271. package/dist/modes/interactive/interactive-mode.js +72 -64
  272. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  273. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  274. package/dist/modes/rpc/rpc-mode.js +14 -0
  275. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  276. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  277. package/dist/utils/clipboard.d.ts.map +1 -1
  278. package/dist/utils/clipboard.js +19 -8
  279. package/dist/utils/clipboard.js.map +1 -1
  280. package/dist/utils/version-check.d.ts.map +1 -1
  281. package/dist/utils/version-check.js +1 -1
  282. package/dist/utils/version-check.js.map +1 -1
  283. package/docs/compaction.md +1 -1
  284. package/docs/custom-provider.md +14 -5
  285. package/docs/environment-variables.md +86 -0
  286. package/docs/extensions.md +15 -5
  287. package/docs/index.md +1 -0
  288. package/docs/models.md +9 -2
  289. package/docs/providers.md +21 -2
  290. package/docs/rpc.md +23 -6
  291. package/docs/session-format.md +2 -0
  292. package/docs/settings.md +1 -1
  293. package/docs/usage.md +0 -13
  294. package/examples/extensions/custom-compaction.ts +5 -2
  295. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  296. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  297. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/handoff.ts +11 -3
  302. package/examples/extensions/message-renderer.ts +3 -3
  303. package/examples/extensions/sandbox/package-lock.json +2 -2
  304. package/examples/extensions/sandbox/package.json +1 -1
  305. package/examples/extensions/summarize.ts +5 -2
  306. package/examples/extensions/with-deps/package-lock.json +2 -2
  307. package/examples/extensions/with-deps/package.json +1 -1
  308. package/examples/sdk/12-full-control.ts +3 -1
  309. package/package.json +15 -6
  310. package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -520,10 +520,10 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
520
520
 
521
521
  const ctx = createCommandContext({ sessionManager });
522
522
  registerSlashCommands!(pi, createState(process.cwd()));
523
- await commands.get("run")!.handler("scout", ctx);
523
+ await commands.get("run")!.handler("explorer", ctx);
524
524
  await new Promise<void>((resolve) => setImmediate(resolve));
525
525
 
526
- assert.deepEqual(requestedParams, { agent: "scout", task: "", clarify: false, agentScope: "both" });
526
+ assert.deepEqual(requestedParams, { agent: "explorer", task: "", clarify: false, agentScope: "both" });
527
527
  assert.equal(requestedCtx, ctx);
528
528
  assert.equal(sent.length, 2);
529
529
  assert.equal((sent[0] as { display?: boolean }).display, true);
@@ -565,7 +565,7 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
565
565
  };
566
566
 
567
567
  registerSlashCommands!(pi, createState(process.cwd()));
568
- await commands.get("run")!.handler("scout inspect this", createCommandContext({
568
+ await commands.get("run")!.handler("explorer inspect this", createCommandContext({
569
569
  hasUI: true,
570
570
  setStatus: (_key, text) => {
571
571
  log.push(`status:${text ?? "clear"}`);
@@ -615,7 +615,7 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
615
615
  };
616
616
 
617
617
  registerSlashCommands!(pi, createState(process.cwd()));
618
- await commands.get("run")!.handler("scout inspect this", createCommandContext({
618
+ await commands.get("run")!.handler("explorer inspect this", createCommandContext({
619
619
  hasUI: true,
620
620
  setToolsExpanded: (expanded) => log.push(`expanded:${String(expanded)}`),
621
621
  }));
@@ -655,7 +655,7 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
655
655
  };
656
656
 
657
657
  registerSlashCommands!(pi, createState(process.cwd()));
658
- await commands.get("run")!.handler("scout inspect this", createCommandContext({
658
+ await commands.get("run")!.handler("explorer inspect this", createCommandContext({
659
659
  hasUI: true,
660
660
  setStatus: (_key, text) => {
661
661
  log.push(`status:${text ?? "clear"}`);
@@ -706,10 +706,10 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
706
706
  };
707
707
 
708
708
  registerSlashCommands!(pi, createState(process.cwd()));
709
- await commands.get("parallel")!.handler("scout[output=x.md,outputMode=file-only,reads=a.md+b.md,progress] -- Review", createCommandContext());
709
+ await commands.get("parallel")!.handler("explorer[output=x.md,outputMode=file-only,reads=a.md+b.md,progress] -- Review", createCommandContext());
710
710
 
711
711
  assert.deepEqual(requestedParams, {
712
- tasks: [{ agent: "scout", task: "Review", output: "x.md", outputMode: "file-only", reads: ["a.md", "b.md"], progress: true }],
712
+ tasks: [{ agent: "explorer", task: "Review", output: "x.md", outputMode: "file-only", reads: ["a.md", "b.md"], progress: true }],
713
713
  clarify: false,
714
714
  agentScope: "both",
715
715
  });
@@ -746,7 +746,7 @@ describe("slash command custom message delivery", { skip: !available ? "slash-co
746
746
  };
747
747
 
748
748
  registerSlashCommands!(pi, createState(process.cwd()));
749
- const args = Array.from({ length: 9 }, (_, index) => `scout \"task ${index + 1}\"`).join(" -> ");
749
+ const args = Array.from({ length: 9 }, (_, index) => `explorer \"task ${index + 1}\"`).join(" -> ");
750
750
  await commands.get("parallel")!.handler(args, createCommandContext());
751
751
  await new Promise<void>((resolve) => setImmediate(resolve));
752
752
 
@@ -1271,8 +1271,8 @@ describe("subagents-models slash command", { skip: !available ? "slash-commands.
1271
1271
  });
1272
1272
 
1273
1273
  it("passes an optional builtin filter", async () => {
1274
- const { params } = await captureSlashCommandParams("subagents-models", "scout", process.cwd());
1275
- assert.deepEqual(params, { action: "models", agent: "scout" });
1274
+ const { params } = await captureSlashCommandParams("subagents-models", "explorer", process.cwd());
1275
+ assert.deepEqual(params, { action: "models", agent: "explorer" });
1276
1276
  });
1277
1277
 
1278
1278
  it("rejects invalid builtin filters without launching", async () => {
@@ -1294,8 +1294,8 @@ describe("subagents-models slash command", { skip: !available ? "slash-commands.
1294
1294
  };
1295
1295
 
1296
1296
  registerSlashCommands!(pi, createState(process.cwd()));
1297
- const completions = commands.get("subagents-models")!.getArgumentCompletions!("sc") as Array<{ value: string; label: string }>;
1298
- assert.deepEqual(completions.map((completion) => completion.value), ["scout"]);
1297
+ const completions = commands.get("subagents-models")!.getArgumentCompletions!("ex") as Array<{ value: string; label: string }>;
1298
+ assert.deepEqual(completions.map((completion) => completion.value), ["explorer"]);
1299
1299
  });
1300
1300
  });
1301
1301
  });
@@ -1421,7 +1421,7 @@ describe("subagent profiles slash commands", { skip: !available ? "slash-command
1421
1421
  const profilesDir = path.join(process.env.HOME!, ".selesai", "agent", "profiles", "pi-subagents");
1422
1422
  fs.mkdirSync(profilesDir, { recursive: true });
1423
1423
  fs.writeFileSync(path.join(profilesDir, "openai-codex.quota.json"), JSON.stringify({
1424
- subagents: { agentOverrides: { worker: { model: "gpt-5.4:high" } } },
1424
+ subagents: { agentOverrides: { builder: { model: "gpt-5.4:high" } } },
1425
1425
  }, null, 2));
1426
1426
  const sent: unknown[] = [];
1427
1427
  const commands = new Map<string, RegisteredSlashCommand>();
@@ -1593,7 +1593,7 @@ describe("subagents admin slash command", { skip: !available ? "slash-commands.t
1593
1593
  notify: (message) => notifications.push(message),
1594
1594
  }));
1595
1595
 
1596
- await commands.get("subagents")!.handler("worker model", createCommandContext({
1596
+ await commands.get("subagents")!.handler("builder model", createCommandContext({
1597
1597
  cwd: root,
1598
1598
  hasUI: true,
1599
1599
  modelRegistry: { getAvailable: () => [{ provider: "test", id: "new-model", reasoning: true }] },
@@ -1611,13 +1611,13 @@ describe("subagents admin slash command", { skip: !available ? "slash-commands.t
1611
1611
  it("reports duplicate names as ambiguous without UI", async () => {
1612
1612
  await withIsolatedHome(async () => {
1613
1613
  await withTempProject("pi-subagents-admin-ambiguous-", async (root) => {
1614
- fs.writeFileSync(path.join(root, ".selesai", "agents", "worker.md"), "---\nname: worker\ndescription: Project worker\n---\n\nWork.\n", "utf-8");
1614
+ fs.writeFileSync(path.join(root, ".selesai", "agents", "builder.md"), "---\nname: builder\ndescription: Project builder\n---\n\nWork.\n", "utf-8");
1615
1615
  const sent: unknown[] = [];
1616
1616
  const commands = registerAdmin(sent);
1617
- await commands.get("subagents")!.handler("worker", createCommandContext({ cwd: root }));
1617
+ await commands.get("subagents")!.handler("builder", createCommandContext({ cwd: root }));
1618
1618
  assert.equal(sent.length, 1);
1619
1619
  const content = (sent[0] as { content?: string }).content ?? "";
1620
- assert.match(content, /Subagent 'worker' is ambiguous/);
1620
+ assert.match(content, /Subagent 'builder' is ambiguous/);
1621
1621
  assert.match(content, /project:/);
1622
1622
  assert.match(content, /builtin:/);
1623
1623
  });
@@ -1729,7 +1729,7 @@ describe("subagents admin slash command", { skip: !available ? "slash-commands.t
1729
1729
  fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
1730
1730
  fs.writeFileSync(settingsPath, JSON.stringify({ subagents: { disableThinking: true } }, null, 2), "utf-8");
1731
1731
  const commands = registerAdmin([]);
1732
- await commands.get("subagents")!.handler("worker model", createCommandContext({
1732
+ await commands.get("subagents")!.handler("builder model", createCommandContext({
1733
1733
  cwd: root,
1734
1734
  hasUI: true,
1735
1735
  modelRegistry: { getAvailable: () => [{ provider: "test", id: "new-model", reasoning: true }] },
@@ -1738,7 +1738,7 @@ describe("subagents admin slash command", { skip: !available ? "slash-commands.t
1738
1738
 
1739
1739
  assert.deepEqual(JSON.parse(fs.readFileSync(settingsPath, "utf-8")).subagents, {
1740
1740
  disableThinking: true,
1741
- agentOverrides: { worker: { model: "test/new-model" } },
1741
+ agentOverrides: { builder: { model: "test/new-model" } },
1742
1742
  });
1743
1743
  });
1744
1744
  });
@@ -2063,7 +2063,7 @@ describe("subagents-doctor slash command", { skip: !available ? "slash-commands.
2063
2063
 
2064
2064
  assert.equal(opened, 2);
2065
2065
  assert.match(rendered, /Subagent fleet/);
2066
- assert.match(rendered, /inspection only/);
2066
+ assert.match(rendered, /live controls/);
2067
2067
  });
2068
2068
 
2069
2069
  it("routes subagents-stop with an id directly to the stop action", async () => {
@@ -2071,6 +2071,66 @@ describe("subagents-doctor slash command", { skip: !available ? "slash-commands.
2071
2071
  assert.deepEqual(params, { action: "stop", id: "run-123" });
2072
2072
  });
2073
2073
 
2074
+ it("detaches the latest active foreground single run without terminating it", async () => {
2075
+ const commands = new Map<string, RegisteredSlashCommand>();
2076
+ const sent: unknown[] = [];
2077
+ const state = createState(process.cwd());
2078
+ let detached = 0;
2079
+ state.foregroundControls.set("foreground-single", {
2080
+ runId: "foreground-single",
2081
+ mode: "single",
2082
+ startedAt: 1,
2083
+ updatedAt: 2,
2084
+ detach: () => { detached++; return true; },
2085
+ });
2086
+ state.foregroundControls.set("newer-parallel", {
2087
+ runId: "newer-parallel",
2088
+ mode: "parallel",
2089
+ startedAt: 2,
2090
+ updatedAt: 3,
2091
+ });
2092
+ state.lastForegroundControlId = "newer-parallel";
2093
+ registerSlashCommands!({
2094
+ events: createEventBus(),
2095
+ registerCommand(name: string, spec: RegisteredSlashCommand) { commands.set(name, spec); },
2096
+ registerShortcut() {},
2097
+ sendMessage(message: unknown) { sent.push(message); },
2098
+ }, state);
2099
+
2100
+ await commands.get("subagents-detach")!.handler("", createCommandContext());
2101
+
2102
+ assert.equal(detached, 1);
2103
+ const content = String((sent[0] as { content?: unknown }).content ?? "");
2104
+ assert.match(content, /Detached foreground run foreground-single without terminating its child/);
2105
+ assert.match(content, /does not daemonize the process or guarantee survival across Pi reload\/restart/);
2106
+ });
2107
+
2108
+ it("reports missing and unsupported foreground detach targets", async () => {
2109
+ const commands = new Map<string, RegisteredSlashCommand>();
2110
+ const notifications: string[] = [];
2111
+ const state = createState(process.cwd());
2112
+ state.foregroundControls.set("parallel-run", {
2113
+ runId: "parallel-run",
2114
+ mode: "parallel",
2115
+ startedAt: 1,
2116
+ updatedAt: 1,
2117
+ detach: () => true,
2118
+ });
2119
+ registerSlashCommands!({
2120
+ events: createEventBus(),
2121
+ registerCommand(name: string, spec: RegisteredSlashCommand) { commands.set(name, spec); },
2122
+ registerShortcut() {},
2123
+ sendMessage() {},
2124
+ }, state);
2125
+ const ctx = createCommandContext({ notify: (message) => notifications.push(message) });
2126
+
2127
+ await commands.get("subagents-detach")!.handler("missing", ctx);
2128
+ await commands.get("subagents-detach")!.handler("parallel", ctx);
2129
+
2130
+ assert.match(notifications[0] ?? "", /No active foreground run found for 'missing'/);
2131
+ assert.match(notifications[1] ?? "", /currently supports single-subagent runs only/);
2132
+ });
2133
+
2074
2134
  it("prints exact stop commands when subagents-stop has no UI", async () => {
2075
2135
  await withIsolatedHome(async () => {
2076
2136
  const runId = `slash-stop-${Date.now().toString(36)}`;
@@ -2162,6 +2222,17 @@ describe("subagents-doctor slash command", { skip: !available ? "slash-commands.
2162
2222
  cwd: root,
2163
2223
  sessionId: "session-test",
2164
2224
  jobs: [{
2225
+ id: "job-1",
2226
+ name: "early scout",
2227
+ schedule: "+5m",
2228
+ runAt: Date.now() + 300_000,
2229
+ state: "scheduled",
2230
+ createdAt: Date.now(),
2231
+ updatedAt: Date.now(),
2232
+ cwd: root,
2233
+ sessionId: "session-test",
2234
+ params: { agent: "scout", task: "soon", async: true },
2235
+ }, {
2165
2236
  id: "job-2",
2166
2237
  name: "delayed worker",
2167
2238
  schedule: "+10m",
@@ -2200,9 +2271,27 @@ describe("subagents-doctor slash command", { skip: !available ? "slash-commands.
2200
2271
  await commands.get("subagents-stop")!.handler("", createCommandContext({
2201
2272
  cwd: root,
2202
2273
  hasUI: true,
2203
- custom: async () => ({
2204
- confirmed: true,
2205
- target: { kind: "scheduled", id: "job-2", label: "job-2", detail: "scheduled", actionLabel: "cancel scheduled run" },
2274
+ custom: async (...args: unknown[]) => new Promise((resolve) => {
2275
+ const factory = args[0] as (
2276
+ tui: { requestRender(): void },
2277
+ theme: { fg(key: string, text: string): string; bold(text: string): string },
2278
+ keybindings: unknown,
2279
+ done: (result: unknown) => void,
2280
+ ) => { handleInput(data: string): void; render(width: number): string[] };
2281
+ const component = factory(
2282
+ { requestRender() {} },
2283
+ { fg(_key, text) { return text; }, bold(text) { return text; } },
2284
+ {},
2285
+ resolve,
2286
+ );
2287
+ assert.match(component.render(84).join("\n"), /↑↓\/jk select/);
2288
+ component.handleInput("j");
2289
+ assert.ok(component.render(84).some((line) => line.startsWith("›") && line.includes("job-2")));
2290
+ component.handleInput("k");
2291
+ assert.ok(component.render(84).some((line) => line.startsWith("›") && line.includes("job-1")));
2292
+ component.handleInput("j");
2293
+ component.handleInput("\r");
2294
+ component.handleInput("y");
2206
2295
  }),
2207
2296
  }));
2208
2297
 
@@ -185,6 +185,14 @@ function writeStructuredOutputCapture(response) {
185
185
  fs.writeFileSync(outputPath, JSON.stringify(response.structuredOutputCapture), "utf-8");
186
186
  }
187
187
 
188
+ function writeRuntimeAcknowledgedExtensions(response) {
189
+ if (!Object.prototype.hasOwnProperty.call(response, "runtimeAcknowledgedExtensions")) return;
190
+ const outputPath = process.env.SELESAI_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS;
191
+ if (!outputPath) return;
192
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
193
+ fs.writeFileSync(outputPath, JSON.stringify(response.runtimeAcknowledgedExtensions), "utf-8");
194
+ }
195
+
188
196
  function writeToolDiagnostic(response) {
189
197
  if (!Array.isArray(response.missingTools) || response.missingTools.length === 0) return;
190
198
  const diagnosticPath = process.env.SELESAI_SUBAGENT_TOOL_DIAGNOSTIC_PATH;
@@ -327,11 +335,10 @@ async function main() {
327
335
  }
328
336
  writeSessionFile(args);
329
337
  writeToolDiagnostic(response);
330
- fs.writeFileSync(
331
- path.join(queueDir, `call-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.json`),
332
- JSON.stringify({ args, systemPrompts: readSystemPromptRecords(args) }),
333
- "utf-8",
334
- );
338
+ const callPath = path.join(queueDir, `call-${Date.now()}-${process.pid}-${Math.random().toString(16).slice(2)}.json`);
339
+ const callTempPath = `${callPath}.tmp-${process.pid}-${Date.now()}`;
340
+ fs.writeFileSync(callTempPath, JSON.stringify({ args, systemPrompts: readSystemPromptRecords(args) }), "utf-8");
341
+ fs.renameSync(callTempPath, callPath);
335
342
 
336
343
  if (typeof response.delay === "number" && response.delay > 0) {
337
344
  await new Promise((resolve) => setTimeout(resolve, response.delay));
@@ -346,6 +353,7 @@ async function main() {
346
353
 
347
354
  writeDeclaredFiles(response);
348
355
  writeStructuredOutputCapture(response);
356
+ writeRuntimeAcknowledgedExtensions(response);
349
357
 
350
358
  if (Array.isArray(response.steps) && response.steps.length > 0) {
351
359
  for (const step of response.steps) {
@@ -384,6 +392,10 @@ async function main() {
384
392
  await new Promise((resolve) => setTimeout(resolve, response.keepAliveAfterFinalMessageMs));
385
393
  }
386
394
 
395
+ if (typeof response.signal === "string") {
396
+ process.kill(process.pid, response.signal);
397
+ return;
398
+ }
387
399
  exitAfterFlush(typeof response.exitCode === "number" ? response.exitCode : 0);
388
400
  }
389
401
 
@@ -7,6 +7,7 @@ interface MockPiResponse {
7
7
  output?: string;
8
8
  stderr?: string;
9
9
  exitCode?: number;
10
+ signal?: NodeJS.Signals;
10
11
  delay?: number;
11
12
  waitForPath?: string;
12
13
  keepAliveAfterFinalMessageMs?: number;
@@ -28,6 +29,7 @@ interface MockPiResponse {
28
29
  writeFiles?: Array<{ path: string; content: string }>;
29
30
  /** Writes the structured-output capture file without emitting a structured_output tool event. */
30
31
  structuredOutputCapture?: unknown;
32
+ runtimeAcknowledgedExtensions?: unknown;
31
33
  }
32
34
 
33
35
  export interface MockPi {
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Register the .js → .ts loader hook for integration tests.
3
3
  *
4
- * Usage: node --experimental-transform-types --import ./test/support/register-loader.mjs --test test/integration/*.test.ts
4
+ * Usage: node --experimental-strip-types --import ./test/support/register-loader.mjs --test test/integration/*.test.ts
5
5
  *
6
6
  * Handles two issues:
7
7
  * 1. Source files use .js import extensions (TypeScript ESM convention) but
8
8
  * files on disk are .ts — the loader rewrites .js → .ts at resolve time.
9
- * 2. Some source files use TypeScript parameter properties (constructor(private x: T))
10
- * which require --experimental-transform-types (not just strip-types).
9
+ * 2. Source stays within Node's erasable TypeScript syntax so the supported
10
+ * --experimental-strip-types path is sufficient.
11
11
  */
12
12
 
13
13
  import { register } from "node:module";
@@ -1046,7 +1046,9 @@ describe("acceptance gates", () => {
1046
1046
  { id: "release check", must: "second" },
1047
1047
  ] }).join("\n"), /acceptance\.criteria\[1\]\.id duplicates normalized criterion id 'release-check'/);
1048
1048
  assert.match(validateAcceptanceInput({ criteria: [123] }).join("\n"), /acceptance\.criteria\[0\] must be a string or an object/);
1049
- assert.match(validateAcceptanceInput({ evidence: ["bogus"] }).join("\n"), /acceptance\.evidence\[0\] is not a supported evidence kind/);
1049
+ assert.match(validateAcceptanceInput({ evidence: "commands-run" }).join("\n"), /acceptance\.evidence must be an array.*Supported evidence kinds:.*commands-run.*changed-files/s);
1050
+ assert.match(validateAcceptanceInput({ evidence: ["bogus"] }).join("\n"), /acceptance\.evidence\[0\] "bogus" is not a supported evidence kind.*Supported evidence kinds:.*manual-notes.*Example:/s);
1051
+ assert.match(validateAcceptanceInput({ criteria: [{ id: "ship", must: "ship", evidence: ["commands_run"] }] }).join("\n"), /acceptance\.criteria\[0\]\.evidence\[0\] "commands_run" is not a supported evidence kind.*Supported evidence kinds:/s);
1050
1052
  assert.match(validateAcceptanceInput({ review: true }).join("\n"), /acceptance\.review must be false or an object/);
1051
1053
  assert.match(validateAcceptanceInput({ review: { required: "yes" } }).join("\n"), /acceptance\.review\.required must be a boolean/);
1052
1054
  assert.match(validateAcceptanceInput({ stopRules: [123] }).join("\n"), /acceptance\.stopRules\[0\] must be a string/);
@@ -153,6 +153,47 @@ body`);
153
153
  }));
154
154
  });
155
155
 
156
+ describe("agent aliases", () => {
157
+ it("parses and serializes agent aliases", () => withTempHome(() => {
158
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-alias-agent-"));
159
+ tempDirs.push(project);
160
+ writeAgent(path.join(project, ".selesai", "agents", "worker.md"), `---
161
+ name: worker
162
+ description: Worker
163
+ aliases: shipper, tester, worker
164
+ ---
165
+ body`);
166
+
167
+ const worker = discoverAgents(project, "both").agents.find((agent) => agent.name === "worker")!;
168
+ assert.deepEqual(worker.aliases, ["shipper", "tester"]);
169
+ assert.match(serializeAgent(worker), /^aliases: shipper, tester$/m);
170
+
171
+ const ctx = { cwd: project, modelRegistry: { getAvailable: () => [] } };
172
+ assert.match(handleManagementAction("list", {}, ctx).content[0]?.text ?? "", /aliases: shipper, tester/);
173
+ assert.match(handleManagementAction("get", { agent: "shipper" }, ctx).content[0]?.text ?? "", /Agent: worker/);
174
+ }));
175
+
176
+ it("reports management alias collisions as ambiguous", () => withTempHome(() => {
177
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-alias-collision-"));
178
+ tempDirs.push(project);
179
+ writeAgent(path.join(project, ".selesai", "agents", "review-agent.md"), `---
180
+ name: review-agent
181
+ description: Custom reviewer
182
+ aliases: developer
183
+ ---
184
+ body`);
185
+
186
+ const ctx = { cwd: project, modelRegistry: { getAvailable: () => [] } };
187
+ const getResult = handleManagementAction("get", { agent: "developer" }, ctx);
188
+ assert.equal(getResult.isError, true);
189
+ assert.match(getResult.content[0]?.text ?? "", /Ambiguous agent alias or name 'developer': builder, review-agent|Ambiguous agent alias or name 'developer': review-agent, builder/);
190
+
191
+ const disableResult = handleManagementAction("disable", { agent: "developer" }, ctx);
192
+ assert.equal(disableResult.isError, true);
193
+ assert.match(disableResult.content[0]?.text ?? "", /Ambiguous agent alias 'developer': builder, review-agent|Ambiguous agent alias 'developer': review-agent, builder/);
194
+ }));
195
+ });
196
+
156
197
  describe("agent simple-scalar list frontmatter", () => {
157
198
  it("discovers newline block lists for all list fields and routes MCP tools", () => {
158
199
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-agent-block-list-frontmatter-"));
@@ -603,6 +644,72 @@ Review nested project work.
603
644
  assert.equal(agent.filePath, path.join(packageRoot, "agents", "reviewer.md"));
604
645
  }));
605
646
 
647
+ it("keeps nearest project root discovery by default when a nested .pi exists", () => withTempHome(() => {
648
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-nearest-root-default-"));
649
+ tempDirs.push(dir);
650
+ const nested = path.join(dir, "packages", "app", "src");
651
+ const nestedConfigDir = path.join(dir, "packages", "app", ".selesai");
652
+ const packageRoot = path.join(dir, ".selesai", "npm", "node_modules", "outer-workflow");
653
+ fs.mkdirSync(nested, { recursive: true });
654
+ fs.mkdirSync(nestedConfigDir, { recursive: true });
655
+ fs.mkdirSync(path.join(dir, ".git"), { recursive: true });
656
+ writeJson(path.join(packageRoot, "package.json"), {
657
+ name: "outer-workflow",
658
+ "pi-subagents": { agents: ["./agents"] },
659
+ });
660
+ writeAgent(path.join(packageRoot, "agents", "planner.md"), `---
661
+ name: planner
662
+ package: outer-workflow
663
+ description: Plan from the outer project package.
664
+ ---
665
+
666
+ Plan outer project work.
667
+ `);
668
+
669
+ const agent = discoverAgents(nested, "both").agents.find((candidate) => candidate.name === "outer-workflow.planner");
670
+ assert.equal(agent, undefined);
671
+ }));
672
+
673
+ it("can resolve project packages and overrides from the git root", () => withTempHome(() => {
674
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-git-root-resolution-"));
675
+ tempDirs.push(dir);
676
+ const nested = path.join(dir, "packages", "app", "src");
677
+ const nestedConfigDir = path.join(dir, "packages", "app", ".selesai");
678
+ const packageRoot = path.join(dir, ".selesai", "npm", "node_modules", "outer-workflow");
679
+ fs.mkdirSync(nested, { recursive: true });
680
+ fs.mkdirSync(nestedConfigDir, { recursive: true });
681
+ fs.writeFileSync(path.join(dir, ".git"), "gitdir: ../.git/worktrees/app\n", "utf-8");
682
+ writeJson(path.join(dir, ".selesai", "settings.json"), {
683
+ subagents: {
684
+ projectRootResolution: "git-root",
685
+ agentOverrides: {
686
+ commentator: { model: "openai/gpt-5.4" },
687
+ },
688
+ },
689
+ });
690
+ writeJson(path.join(packageRoot, "package.json"), {
691
+ name: "outer-workflow",
692
+ "pi-subagents": { agents: ["./agents"] },
693
+ });
694
+ writeAgent(path.join(packageRoot, "agents", "planner.md"), `---
695
+ name: planner
696
+ package: outer-workflow
697
+ description: Plan from the outer project package.
698
+ ---
699
+
700
+ Plan outer project work.
701
+ `);
702
+
703
+ const agents = discoverAgents(nested, "both").agents;
704
+ const packageAgent = agents.find((candidate) => candidate.name === "outer-workflow.planner");
705
+ assert.ok(packageAgent);
706
+ assert.equal(packageAgent.source, "package");
707
+ assert.equal(packageAgent.filePath, path.join(packageRoot, "agents", "planner.md"));
708
+ const commentator = agents.find((candidate) => candidate.name === "commentator");
709
+ assert.equal(commentator?.model, "openai/gpt-5.4");
710
+ assert.equal(commentator?.override?.path, path.join(dir, ".selesai", "settings.json"));
711
+ }));
712
+
606
713
  it("does not register legacy skill files from broad package agent roots", () => withTempHome(() => {
607
714
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-broad-package-skills-"));
608
715
  tempDirs.push(dir);
@@ -1203,6 +1310,27 @@ Do work
1203
1310
  }
1204
1311
  });
1205
1312
 
1313
+ it("keeps the bundled architect read-only for repository tools", () => {
1314
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-builtin-architect-tools-"));
1315
+ const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-builtin-architect-home-"));
1316
+ tempDirs.push(dir, homeDir);
1317
+ const previousHome = process.env.HOME;
1318
+ const previousUserProfile = process.env.USERPROFILE;
1319
+ try {
1320
+ process.env.HOME = homeDir;
1321
+ process.env.USERPROFILE = homeDir;
1322
+ const architect = discoverAgentsAll(dir).builtin.find((candidate) => candidate.name === "architect");
1323
+ assert.ok(architect);
1324
+ assert.deepEqual(architect.tools, ["read", "grep", "find", "ls"]);
1325
+ assert.equal(architect.acceptanceRole, "read-only");
1326
+ } finally {
1327
+ if (previousHome === undefined) delete process.env.HOME;
1328
+ else process.env.HOME = previousHome;
1329
+ if (previousUserProfile === undefined) delete process.env.USERPROFILE;
1330
+ else process.env.USERPROFILE = previousUserProfile;
1331
+ }
1332
+ });
1333
+
1206
1334
  it("builder includes the child-facing supervisor tool", () => {
1207
1335
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-builtin-supervisor-tool-"));
1208
1336
  const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-builtin-supervisor-tool-home-"));
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { afterEach, beforeEach, describe, it } from "node:test";
6
- import { buildBuiltinOverrideConfig, discoverAgents, discoverAgentsAll, removeBuiltinAgentOverride } from "../../src/agents/agents.ts";
6
+ import { buildBuiltinOverrideConfig, discoverAgents, discoverAgentsAll, removeBuiltinAgentOverride, saveBuiltinAgentOverride } from "../../src/agents/agents.ts";
7
7
 
8
8
  let tempHome = "";
9
9
  let tempProject = "";
@@ -178,6 +178,22 @@ describe("builtin agent overrides", () => {
178
178
  assert.equal(agents.find((agent) => agent.name === "architect-copy")?.model, "deepseek-v4-flash");
179
179
  });
180
180
 
181
+ it("overrides builtin and custom agent descriptions from settings", () => {
182
+ writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
183
+ subagents: {
184
+ agentOverrides: {
185
+ commentator: { description: " Priced commentator " },
186
+ implementer: { description: "Priced implementer" },
187
+ },
188
+ },
189
+ });
190
+ writeProjectAgent(tempProject, "implementer", `---\nname: implementer\ndescription: Original implementer\n---\n\nImplement it.\n`);
191
+
192
+ const agents = discoverAgents(tempProject, "both").agents;
193
+ assert.equal(agents.find((agent) => agent.name === "commentator")?.description, "Priced commentator");
194
+ assert.equal(agents.find((agent) => agent.name === "implementer")?.description, "Priced implementer");
195
+ });
196
+
181
197
  it("applies user settings overrides to builtin agents", () => {
182
198
  writeJson(path.join(tempHome, ".selesai", "agent", "settings.json"), {
183
199
  subagents: {
@@ -625,6 +641,22 @@ describe("builtin agent overrides", () => {
625
641
  );
626
642
  });
627
643
 
644
+ it("surfaces malformed description override values", () => {
645
+ const settingsPath = path.join(tempHome, ".selesai", "agent", "settings.json");
646
+ for (const description of ["", 42]) {
647
+ writeJson(settingsPath, {
648
+ subagents: { agentOverrides: { commentator: { description } } },
649
+ });
650
+ assert.throws(
651
+ () => discoverAgents(tempProject, "both"),
652
+ (error: unknown) => error instanceof Error
653
+ && error.message.includes(settingsPath)
654
+ && error.message.includes("commentator")
655
+ && error.message.includes("description"),
656
+ );
657
+ }
658
+ });
659
+
628
660
  it("surfaces malformed completion guard override values", () => {
629
661
  const settingsPath = path.join(tempHome, ".selesai", "agent", "settings.json");
630
662
  writeJson(settingsPath, {
@@ -646,9 +678,10 @@ describe("builtin agent overrides", () => {
646
678
  );
647
679
  });
648
680
 
649
- it("builds false sentinels when an override clears builtin fields", () => {
681
+ it("builds description changes and false sentinels when an override clears builtin fields", () => {
650
682
  const override = buildBuiltinOverrideConfig(
651
683
  {
684
+ description: "Base description",
652
685
  model: "openai-codex/gpt-5.4-mini",
653
686
  fallbackModels: ["openai/gpt-5-mini"],
654
687
  thinking: "high",
@@ -665,6 +698,7 @@ describe("builtin agent overrides", () => {
665
698
  completionGuard: false,
666
699
  },
667
700
  {
701
+ description: "Override description",
668
702
  model: undefined,
669
703
  fallbackModels: undefined,
670
704
  thinking: undefined,
@@ -683,6 +717,7 @@ describe("builtin agent overrides", () => {
683
717
  );
684
718
 
685
719
  assert.deepEqual(override, {
720
+ description: "Override description",
686
721
  model: false,
687
722
  fallbackModels: false,
688
723
  thinking: false,
@@ -695,5 +730,15 @@ describe("builtin agent overrides", () => {
695
730
  subagentOnlyExtensions: false,
696
731
  completionGuard: true,
697
732
  });
733
+ assert.ok(override);
734
+ fs.mkdirSync(path.join(tempProject, ".selesai"), { recursive: true });
735
+ saveBuiltinAgentOverride(tempProject, "commentator", "project", override);
736
+ assert.equal(discoverAgents(tempProject, "both").agents.find((agent) => agent.name === "commentator")?.description, "Override description");
737
+
738
+ const whitespaceDescription = buildBuiltinOverrideConfig(
739
+ { description: "Base description" },
740
+ { description: " " } as Parameters<typeof buildBuiltinOverrideConfig>[1],
741
+ );
742
+ assert.equal(whitespaceDescription, undefined);
698
743
  });
699
744
  });
@@ -0,0 +1,60 @@
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 { readAsyncRecoveryDescriptor } from "../../src/runs/background/async-resume.ts";
7
+
8
+ describe("async recovery descriptor", () => {
9
+ it("accepts launchContractDigest written by async execution", () => {
10
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-async-recovery-digest-"));
11
+ try {
12
+ const digest = "launch-contract-digest";
13
+ fs.writeFileSync(path.join(root, "recovery-descriptor.json"), JSON.stringify({
14
+ version: 1,
15
+ launchContractDigest: digest,
16
+ sourceRunId: "run-digest",
17
+ agent: "worker",
18
+ cwd: root,
19
+ systemPromptMode: "replace",
20
+ inheritProjectContext: false,
21
+ inheritSkills: false,
22
+ outputMode: "inline",
23
+ maxSubagentDepth: 2,
24
+ share: false,
25
+ }), "utf-8");
26
+
27
+ const descriptor = readAsyncRecoveryDescriptor(root);
28
+
29
+ assert.equal(descriptor?.launchContractDigest, digest);
30
+ } finally {
31
+ fs.rmSync(root, { recursive: true, force: true });
32
+ }
33
+ });
34
+
35
+ it("rejects malformed launchContractDigest values", () => {
36
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-async-recovery-bad-digest-"));
37
+ try {
38
+ fs.writeFileSync(path.join(root, "recovery-descriptor.json"), JSON.stringify({
39
+ version: 1,
40
+ launchContractDigest: {},
41
+ sourceRunId: "run-digest",
42
+ agent: "worker",
43
+ cwd: root,
44
+ systemPromptMode: "replace",
45
+ inheritProjectContext: false,
46
+ inheritSkills: false,
47
+ outputMode: "inline",
48
+ maxSubagentDepth: 2,
49
+ share: false,
50
+ }), "utf-8");
51
+
52
+ assert.throws(
53
+ () => readAsyncRecoveryDescriptor(root),
54
+ /launchContractDigest must be a non-empty string/,
55
+ );
56
+ } finally {
57
+ fs.rmSync(root, { recursive: true, force: true });
58
+ }
59
+ });
60
+ });