@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
@@ -0,0 +1,428 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
4
+ import {
5
+ agentHasFrontmatterField,
6
+ discoverAgentsAll,
7
+ EXTRA_AGENT_DIRS_ENV,
8
+ frontmatterNameForConfig,
9
+ mergeBuiltinAgentOverride,
10
+ removeBuiltinAgentOverrideFields,
11
+ type AgentConfig,
12
+ type BuiltinAgentOverrideBase,
13
+ } from "../agents/agents.ts";
14
+ import { serializeAgent } from "../agents/agent-serializer.ts";
15
+ import { editableAgentConfig, preservedAgentFrontmatterFields } from "../agents/agent-management.ts";
16
+ import { findModelInfo, getSupportedThinkingLevels, toModelInfo } from "../shared/model-info.ts";
17
+ import { SelectorComponent, type SelectorItem, type SelectorResult } from "./selector.ts";
18
+
19
+ const ADMIN_MESSAGE_TYPE = "subagents-admin";
20
+ const INHERIT_MODEL_CHOICE = "Default / inherit session model";
21
+ const INHERIT_THINKING_CHOICE = "Default / inherit session thinking";
22
+
23
+ type ModelInfo = { provider: string; id: string };
24
+
25
+ function sourceRank(source: AgentConfig["source"]): number {
26
+ if (source === "project") return 0;
27
+ if (source === "user") return 1;
28
+ if (source === "package") return 2;
29
+ return 3;
30
+ }
31
+
32
+ function allVisibleAgents(cwd: string): AgentConfig[] {
33
+ const d = discoverAgentsAll(cwd);
34
+ return [...d.project, ...d.user, ...d.package, ...d.builtin]
35
+ .filter((agent) => !agent.disabled)
36
+ .sort((a, b) => a.name.localeCompare(b.name) || sourceRank(a.source) - sourceRank(b.source));
37
+ }
38
+
39
+ function agentLabel(agent: AgentConfig): string {
40
+ const model = agent.model ? ` · ${agent.model}` : "";
41
+ return `${agent.name} [${agent.source}]${model} — ${agent.description}`;
42
+ }
43
+
44
+ function agentChoices(agents: AgentConfig[]): Map<string, AgentConfig> {
45
+ const labels = agents.map(agentLabel);
46
+ const counts = new Map<string, number>();
47
+ for (const label of labels) counts.set(label, (counts.get(label) ?? 0) + 1);
48
+ return new Map(agents.map((agent, index) => {
49
+ const label = labels[index]!;
50
+ return [counts.get(label) === 1 ? label : `${label} · ${agent.filePath}`, agent] as const;
51
+ }));
52
+ }
53
+
54
+ function agentSelectItems(byLabel: Map<string, AgentConfig>): SelectorItem[] {
55
+ return [...byLabel.keys()].map((label) => ({ value: label, label }));
56
+ }
57
+
58
+ function agentMatches(agent: AgentConfig, rawName: string): boolean {
59
+ const name = rawName.trim();
60
+ return agent.name === name || frontmatterNameForConfig(agent) === name;
61
+ }
62
+
63
+ function sendAdminMessage(pi: ExtensionAPI, content: string): void {
64
+ pi.sendMessage({
65
+ customType: ADMIN_MESSAGE_TYPE,
66
+ content,
67
+ display: true,
68
+ });
69
+ }
70
+
71
+ function modelFullId(model: ModelInfo): string {
72
+ return `${model.provider}/${model.id}`;
73
+ }
74
+
75
+ function liveAvailableModels(ctx: ExtensionContext) {
76
+ try {
77
+ ctx.modelRegistry.refresh?.();
78
+ } catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ ctx.ui.notify(`Could not refresh the model registry; using the last loaded choices. ${message}`, "warning");
81
+ }
82
+ return ctx.modelRegistry.getAvailable();
83
+ }
84
+
85
+ function buildBuiltinBase(agent: AgentConfig): BuiltinAgentOverrideBase {
86
+ return {
87
+ model: agent.model,
88
+ fallbackModels: agent.fallbackModels ? [...agent.fallbackModels] : undefined,
89
+ thinking: agent.thinking,
90
+ systemPromptMode: agent.systemPromptMode,
91
+ inheritProjectContext: agent.inheritProjectContext,
92
+ inheritSkills: agent.inheritSkills,
93
+ defaultContext: agent.defaultContext,
94
+ acceptanceRole: agent.acceptanceRole,
95
+ disabled: agent.disabled,
96
+ systemPrompt: agent.systemPrompt,
97
+ skills: agent.skills ? [...agent.skills] : undefined,
98
+ tools: agent.tools ? [...agent.tools] : undefined,
99
+ mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
100
+ subagentOnlyExtensions: agent.subagentOnlyExtensions ? [...agent.subagentOnlyExtensions] : undefined,
101
+ completionGuard: agent.completionGuard,
102
+ toolBudget: agent.toolBudget,
103
+ };
104
+ }
105
+
106
+ type EditableOverrideField = "model" | "thinking" | "systemPrompt";
107
+
108
+ type AgentSelection =
109
+ | { kind: "selected"; agent: AgentConfig }
110
+ | { kind: "cancelled" }
111
+ | { kind: "not-found"; agents: AgentConfig[]; requestedName?: string }
112
+ | { kind: "ambiguous"; requestedName: string; matches: AgentConfig[] };
113
+
114
+ function savesThroughSettings(agent: AgentConfig, field: EditableOverrideField): boolean {
115
+ if (agent.source === "builtin") return true;
116
+ if (agent.source === "package") {
117
+ if (field === "systemPrompt") return false;
118
+ return !agentHasFrontmatterField(agent, field);
119
+ }
120
+ if (!agent.override) return false;
121
+ // A lower-scope override can flow into a higher-scope custom agent with the
122
+ // same name. Persist that agent's edits in its own frontmatter instead of
123
+ // rewriting the shared lower-scope override used by another agent.
124
+ if (agent.source !== agent.override.scope) return false;
125
+ // Custom-agent overrides fill only fields absent from frontmatter. Compare the
126
+ // effective value with the pre-override base so an override on one field does
127
+ // not redirect edits to an unrelated frontmatter-owned field.
128
+ return agent[field] !== agent.override.base[field];
129
+ }
130
+
131
+ function isReadOnlyExtraAgent(agent: AgentConfig): boolean {
132
+ const configured = process.env[EXTRA_AGENT_DIRS_ENV];
133
+ if (!configured || agent.source !== "user") return false;
134
+ const filePath = path.resolve(agent.filePath);
135
+ return configured.split(path.delimiter).map((dir) => dir.trim()).filter(Boolean).some((dir) => {
136
+ const root = path.resolve(dir);
137
+ const relative = path.relative(root, filePath);
138
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
139
+ });
140
+ }
141
+
142
+ function readOnlyAgentMessage(agent: AgentConfig, field: EditableOverrideField): string | undefined {
143
+ if (agent.source === "package") {
144
+ return `Cannot update '${agent.name}' ${field} because that field is owned by its read-only package definition.`;
145
+ }
146
+ return isReadOnlyExtraAgent(agent)
147
+ ? `Cannot update '${agent.name}' because its definition in SELESAI_SUBAGENT_EXTRA_AGENT_DIRS is read-only.`
148
+ : undefined;
149
+ }
150
+
151
+ async function selectAgent(ctx: ExtensionContext, args: string): Promise<AgentSelection> {
152
+ const agents = allVisibleAgents(ctx.cwd);
153
+ const requestedName = args.trim().split(/\s+/)[0] ?? "";
154
+ if (agents.length === 0) return { kind: "not-found", agents, requestedName: requestedName || undefined };
155
+
156
+ if (requestedName) {
157
+ const matches = agents.filter((agent) => agentMatches(agent, requestedName));
158
+ if (matches.length === 1) return { kind: "selected", agent: matches[0]! };
159
+ if (matches.length > 1 && !ctx.hasUI) return { kind: "ambiguous", requestedName, matches };
160
+ if (matches.length > 1) {
161
+ const byLabel = agentChoices(matches);
162
+ const choice = await selectFromList(ctx, `Multiple subagents named '${requestedName}'`, undefined, agentSelectItems(byLabel));
163
+ return choice ? { kind: "selected", agent: byLabel.get(choice)! } : { kind: "cancelled" };
164
+ }
165
+ return { kind: "not-found", agents, requestedName };
166
+ }
167
+
168
+ if (!ctx.hasUI) return { kind: "not-found", agents };
169
+ const byLabel = agentChoices(agents);
170
+ const choice = await selectFromList(ctx, "Select subagent", undefined, agentSelectItems(byLabel));
171
+ return choice ? { kind: "selected", agent: byLabel.get(choice)! } : { kind: "cancelled" };
172
+ }
173
+
174
+ function metadataFor(agent: AgentConfig): string {
175
+ const tools = [...(agent.tools ?? []), ...(agent.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)];
176
+ const lines = [
177
+ `Agent: ${agent.name} (${agent.source})`,
178
+ `Path: ${agent.filePath}`,
179
+ `Description: ${agent.description}`,
180
+ ];
181
+ if (agent.packageName) {
182
+ lines.push(`Local name: ${frontmatterNameForConfig(agent)}`);
183
+ lines.push(`Package: ${agent.packageName}`);
184
+ }
185
+ lines.push(`Model: ${agent.model ?? "default / inherit"}`);
186
+ if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`);
187
+ if (agent.thinking !== undefined) lines.push(`Thinking: ${agent.thinking === false ? "off" : agent.thinking}`);
188
+ if (tools.length) lines.push(`Tools: ${tools.join(", ")}`);
189
+ if (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(", ")}`);
190
+ lines.push(`System prompt mode: ${agent.systemPromptMode}`);
191
+ lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
192
+ lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
193
+ if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
194
+ if (agent.output) lines.push(`Output: ${agent.output}`);
195
+ if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
196
+ if (agent.defaultProgress) lines.push("Progress: true");
197
+ if (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`);
198
+ if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`);
199
+ if (agent.override) lines.push(`Override: ${agent.override.scope} (${agent.override.path})`);
200
+ if (agent.systemPrompt.trim()) lines.push("", "System Prompt:", agent.systemPrompt);
201
+ return lines.join("\n");
202
+ }
203
+
204
+ async function selectFromList(ctx: ExtensionContext, title: string, subtitle: string | undefined, items: SelectorItem[]): Promise<string | undefined> {
205
+ if (typeof ctx.ui.custom === "function") {
206
+ const result = await ctx.ui.custom<SelectorResult>(
207
+ (tui, theme, kb, done) => new SelectorComponent(tui, theme, kb, { title, subtitle, items, done }),
208
+ { overlay: false },
209
+ );
210
+ return result?.confirmed ? result.value : undefined;
211
+ }
212
+ const flatTitle = subtitle ? `${title}\nCurrent: ${subtitle}` : title;
213
+ const labelToValue = new Map(items.map((item) => [item.label, item.value] as const));
214
+ const choice = await ctx.ui.select(flatTitle, items.map((item) => item.value));
215
+ return choice ? (items.find((item) => item.value === choice)?.value ?? labelToValue.get(choice)) : undefined;
216
+ }
217
+
218
+ async function chooseModel(ctx: ExtensionContext, agent: AgentConfig): Promise<string | undefined | null> {
219
+ const models = liveAvailableModels(ctx);
220
+ const current = agent.model ?? INHERIT_MODEL_CHOICE;
221
+ const items: SelectorItem[] = [{ value: INHERIT_MODEL_CHOICE, label: INHERIT_MODEL_CHOICE, current: !agent.model }];
222
+ if (agent.model && !models.some((model) => modelFullId(model) === agent.model)) {
223
+ items.push({ value: agent.model, label: agent.model, current: true });
224
+ }
225
+ for (const model of models) {
226
+ const fullId = modelFullId(model);
227
+ items.push({ value: fullId, label: model.id, badge: model.provider, current: fullId === agent.model });
228
+ }
229
+ const choice = await selectFromList(ctx, `Select model for ${agent.name}`, current, items);
230
+ if (choice === undefined) return null;
231
+ return choice === INHERIT_MODEL_CHOICE ? undefined : choice;
232
+ }
233
+
234
+ async function chooseThinking(ctx: ExtensionContext, agent: AgentConfig): Promise<string | undefined | null> {
235
+ const availableModels = liveAvailableModels(ctx).map(toModelInfo);
236
+ const effectiveModel = agent.model ?? (ctx.model ? modelFullId(ctx.model) : undefined);
237
+ const modelInfo = findModelInfo(effectiveModel, availableModels, ctx.model?.provider);
238
+ const levels = getSupportedThinkingLevels(modelInfo);
239
+ const current = agent.thinking === false ? "off" : agent.thinking ?? INHERIT_THINKING_CHOICE;
240
+ const values: string[] = [INHERIT_THINKING_CHOICE, ...levels];
241
+ if (current !== INHERIT_THINKING_CHOICE && !values.includes(current)) values.splice(1, 0, current);
242
+ const modelNote = agent.model
243
+ ? `Model: ${agent.model}`
244
+ : effectiveModel
245
+ ? `Session model: ${effectiveModel}`
246
+ : "Model: default / inherit";
247
+ const items: SelectorItem[] = values.map((value) => ({ value, label: value, current: value === current }));
248
+ const choice = await selectFromList(ctx, `Select thinking level for ${agent.name}`, `${modelNote} · ${current}`, items);
249
+ if (choice === undefined) return null;
250
+ return choice === INHERIT_THINKING_CHOICE ? undefined : choice;
251
+ }
252
+
253
+ async function chooseOverrideScope(ctx: ExtensionContext, agent: AgentConfig): Promise<"user" | "project" | undefined> {
254
+ if (agent.override?.scope) return agent.override.scope;
255
+ const d = discoverAgentsAll(ctx.cwd);
256
+ if (!d.projectSettingsPath || !ctx.hasUI) return "user";
257
+ const choice = await ctx.ui.select(`Save builtin override for ${agent.name}`, ["user", "project"]);
258
+ return choice === "user" || choice === "project" ? choice : undefined;
259
+ }
260
+
261
+ function persistSettingsField(
262
+ ctx: ExtensionContext,
263
+ agent: AgentConfig,
264
+ scope: "user" | "project",
265
+ field: EditableOverrideField,
266
+ value: string | undefined,
267
+ ): { filePath: string; overridden: boolean } {
268
+ const base = agent.override?.base ?? buildBuiltinBase(agent);
269
+ if (value === undefined || value === base[field]) {
270
+ return {
271
+ filePath: removeBuiltinAgentOverrideFields(ctx.cwd, agent.name, scope, [field]).path,
272
+ overridden: false,
273
+ };
274
+ }
275
+ return {
276
+ filePath: mergeBuiltinAgentOverride(ctx.cwd, agent.name, scope, { [field]: value }),
277
+ overridden: true,
278
+ };
279
+ }
280
+
281
+ async function saveAgentModel(ctx: ExtensionContext, agent: AgentConfig, selectedModel: string | undefined): Promise<string | null> {
282
+ if (savesThroughSettings(agent, "model")) {
283
+ const scope = await chooseOverrideScope(ctx, agent);
284
+ if (!scope) return null;
285
+ const { filePath, overridden } = persistSettingsField(ctx, agent, scope, "model", selectedModel);
286
+ return overridden
287
+ ? `Saved ${scope} settings override for '${agent.name}' with model '${selectedModel}' in ${filePath}.`
288
+ : `Cleared model settings override for '${agent.name}' in ${filePath}.`;
289
+ }
290
+
291
+ const readOnlyMessage = readOnlyAgentMessage(agent, "model");
292
+ if (readOnlyMessage) return readOnlyMessage;
293
+ const updated: AgentConfig = { ...editableAgentConfig(agent), model: selectedModel };
294
+ fs.writeFileSync(updated.filePath, serializeAgent(updated, {
295
+ preserveFrontmatterFields: preservedAgentFrontmatterFields(agent, { model: selectedModel }),
296
+ }), "utf-8");
297
+ return selectedModel
298
+ ? `Updated '${agent.name}' model to '${selectedModel}' in ${updated.filePath}.`
299
+ : `Cleared '${agent.name}' model in ${updated.filePath}.`;
300
+ }
301
+
302
+ async function saveAgentThinking(ctx: ExtensionContext, agent: AgentConfig, selectedThinking: string | undefined): Promise<string | null> {
303
+ if (savesThroughSettings(agent, "thinking")) {
304
+ const scope = await chooseOverrideScope(ctx, agent);
305
+ if (!scope) return null;
306
+ const { filePath, overridden } = persistSettingsField(ctx, agent, scope, "thinking", selectedThinking);
307
+ return overridden
308
+ ? `Saved ${scope} settings override for '${agent.name}' with thinking '${selectedThinking}' in ${filePath}.`
309
+ : `Cleared thinking settings override for '${agent.name}' in ${filePath}.`;
310
+ }
311
+
312
+ const readOnlyMessage = readOnlyAgentMessage(agent, "thinking");
313
+ if (readOnlyMessage) return readOnlyMessage;
314
+ const updated: AgentConfig = { ...editableAgentConfig(agent), thinking: selectedThinking };
315
+ fs.writeFileSync(updated.filePath, serializeAgent(updated, {
316
+ preserveFrontmatterFields: preservedAgentFrontmatterFields(agent, { thinking: selectedThinking }),
317
+ }), "utf-8");
318
+ return selectedThinking
319
+ ? `Updated '${agent.name}' thinking to '${selectedThinking}' in ${updated.filePath}.`
320
+ : `Cleared '${agent.name}' thinking in ${updated.filePath}.`;
321
+ }
322
+
323
+ /** Compact one-line summary shown in the interactive picker (no full system prompt dump). */
324
+ function metadataSummary(agent: AgentConfig): string {
325
+ return [
326
+ `Source: ${agent.source}`,
327
+ `Model: ${agent.model ?? "default / inherit"}`,
328
+ `Thinking: ${agent.thinking === false ? "off" : agent.thinking ?? "default / inherit"}`,
329
+ ].join(" · ");
330
+ }
331
+
332
+ async function saveAgentSystemPrompt(ctx: ExtensionContext, agent: AgentConfig, systemPrompt: string): Promise<string | null> {
333
+ const nextPrompt = systemPrompt.replace(/\s+$/, "");
334
+ if (savesThroughSettings(agent, "systemPrompt")) {
335
+ const scope = await chooseOverrideScope(ctx, agent);
336
+ if (!scope) return null;
337
+ const { filePath, overridden } = persistSettingsField(ctx, agent, scope, "systemPrompt", nextPrompt);
338
+ return overridden
339
+ ? `Saved ${scope} settings override for '${agent.name}' system prompt in ${filePath}.`
340
+ : `Cleared system prompt settings override for '${agent.name}' in ${filePath}.`;
341
+ }
342
+ const readOnlyMessage = readOnlyAgentMessage(agent, "systemPrompt");
343
+ if (readOnlyMessage) return readOnlyMessage;
344
+ const updated: AgentConfig = { ...editableAgentConfig(agent), systemPrompt: nextPrompt };
345
+ fs.writeFileSync(updated.filePath, serializeAgent(updated, {
346
+ preserveFrontmatterFields: preservedAgentFrontmatterFields(agent, { systemPrompt: nextPrompt }),
347
+ }), "utf-8");
348
+ return `Updated '${agent.name}' system prompt in ${updated.filePath}.`;
349
+ }
350
+
351
+ async function editSystemPrompt(ctx: ExtensionContext, agent: AgentConfig): Promise<string | null> {
352
+ if (!savesThroughSettings(agent, "systemPrompt")) {
353
+ const readOnlyMessage = readOnlyAgentMessage(agent, "systemPrompt");
354
+ if (readOnlyMessage) return readOnlyMessage;
355
+ }
356
+ const edited = await ctx.ui.editor(`Edit '${agent.name}' system prompt`, agent.systemPrompt ?? "");
357
+ if (edited === undefined) return null;
358
+ if (edited.replace(/\s+$/, "") === (agent.systemPrompt ?? "").replace(/\s+$/, "")) {
359
+ return `System prompt for '${agent.name}' left unchanged.`;
360
+ }
361
+ return saveAgentSystemPrompt(ctx, agent, edited);
362
+ }
363
+
364
+ export async function openSubagentsAdmin(pi: ExtensionAPI, ctx: ExtensionContext, args = ""): Promise<void> {
365
+ const selection = await selectAgent(ctx, args);
366
+ if (selection.kind === "cancelled") return;
367
+ if (selection.kind === "ambiguous") {
368
+ sendAdminMessage(pi, `Subagent '${selection.requestedName}' is ambiguous. Choose a scope in interactive mode:\n${selection.matches.map((agent) => `- ${agent.source}: ${agent.filePath}`).join("\n")}`);
369
+ return;
370
+ }
371
+ if (selection.kind === "not-found") {
372
+ const text = selection.requestedName
373
+ ? `Subagent '${selection.requestedName}' not found.\n\nAvailable subagents:\n${selection.agents.map((agent) => `- ${agent.name} (${agent.source})`).join("\n") || "- (none)"}`
374
+ : `Available subagents:\n${selection.agents.map((agent) => `- ${agent.name} (${agent.source})`).join("\n") || "- (none)"}`;
375
+ sendAdminMessage(pi, text);
376
+ return;
377
+ }
378
+ const agent = selection.agent;
379
+
380
+ if (!ctx.hasUI) {
381
+ // Non-interactive (tool/headless): emit full metadata as the inspection result.
382
+ sendAdminMessage(pi, metadataFor(agent));
383
+ return;
384
+ }
385
+
386
+ const requestedAction = args.trim().split(/\s+/)[1]?.toLowerCase();
387
+ let action: string | undefined;
388
+ if (requestedAction === "model") action = "Change model";
389
+ else if (requestedAction === "thinking") action = "Change thinking level";
390
+ else if (requestedAction === "prompt" || requestedAction === "system-prompt" || requestedAction === "edit") action = "Edit system prompt";
391
+ else if (requestedAction === "details" || requestedAction === "info") action = "Show details";
392
+ if (!action) {
393
+ action = await ctx.ui.select(
394
+ `Administer ${agent.name}\n${metadataSummary(agent)}`,
395
+ ["Change model", "Change thinking level", "Edit system prompt", "Show details", "Done"],
396
+ );
397
+ }
398
+
399
+ try {
400
+ if (action === "Change model") {
401
+ const selectedModel = await chooseModel(ctx, agent);
402
+ if (selectedModel === null) return;
403
+ const message = await saveAgentModel(ctx, agent, selectedModel);
404
+ if (message === null) return;
405
+ ctx.ui.notify(message, "info");
406
+ sendAdminMessage(pi, message);
407
+ } else if (action === "Change thinking level") {
408
+ const selectedThinking = await chooseThinking(ctx, agent);
409
+ if (selectedThinking === null) return;
410
+ const message = await saveAgentThinking(ctx, agent, selectedThinking);
411
+ if (message === null) return;
412
+ ctx.ui.notify(message, "info");
413
+ sendAdminMessage(pi, message);
414
+ } else if (action === "Edit system prompt") {
415
+ const message = await editSystemPrompt(ctx, agent);
416
+ if (message === null) return;
417
+ ctx.ui.notify(message, "info");
418
+ sendAdminMessage(pi, message);
419
+ } else if (action === "Show details") {
420
+ // Full metadata is now opt-in (previously always posted to the thread).
421
+ sendAdminMessage(pi, metadataFor(agent));
422
+ }
423
+ } catch (error) {
424
+ const message = error instanceof Error ? error.message : String(error);
425
+ ctx.ui.notify(message, "error");
426
+ sendAdminMessage(pi, `Failed to update '${agent.name}': ${message}`);
427
+ }
428
+ }