@songsid/agend 2.3.4-beta.1 → 2.3.4-beta.10

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 (284) hide show
  1. package/THIRD_PARTY_NOTICES.md +37 -0
  2. package/dist/agend-home.d.ts +13 -0
  3. package/dist/agend-home.js +42 -0
  4. package/dist/agend-home.js.map +1 -1
  5. package/dist/agent-cli-instructions.md +1 -1
  6. package/dist/agent-credentials.d.ts +24 -0
  7. package/dist/agent-credentials.js +66 -0
  8. package/dist/agent-credentials.js.map +1 -0
  9. package/dist/agent-endpoint.d.ts +3 -1
  10. package/dist/agent-endpoint.js +13 -0
  11. package/dist/agent-endpoint.js.map +1 -1
  12. package/dist/auth-api.js +10 -2
  13. package/dist/auth-api.js.map +1 -1
  14. package/dist/authority/session-authorizations.d.ts +28 -4
  15. package/dist/authority/session-authorizations.js +63 -17
  16. package/dist/authority/session-authorizations.js.map +1 -1
  17. package/dist/backend/antigravity.js +3 -1
  18. package/dist/backend/antigravity.js.map +1 -1
  19. package/dist/backend/claude-code.d.ts +3 -0
  20. package/dist/backend/claude-code.js +89 -4
  21. package/dist/backend/claude-code.js.map +1 -1
  22. package/dist/backend/codex-footer-hints.d.ts +4 -0
  23. package/dist/backend/codex-footer-hints.js +27 -0
  24. package/dist/backend/codex-footer-hints.js.map +1 -0
  25. package/dist/backend/codex-session-lookup.d.ts +35 -0
  26. package/dist/backend/codex-session-lookup.js +93 -0
  27. package/dist/backend/codex-session-lookup.js.map +1 -1
  28. package/dist/backend/codex.d.ts +27 -0
  29. package/dist/backend/codex.js +124 -23
  30. package/dist/backend/codex.js.map +1 -1
  31. package/dist/backend/kiro-identity.d.ts +2 -0
  32. package/dist/backend/kiro-identity.js +13 -6
  33. package/dist/backend/kiro-identity.js.map +1 -1
  34. package/dist/backend/kiro.d.ts +3 -0
  35. package/dist/backend/kiro.js +68 -5
  36. package/dist/backend/kiro.js.map +1 -1
  37. package/dist/backend/muse.js +5 -3
  38. package/dist/backend/muse.js.map +1 -1
  39. package/dist/backend/types.d.ts +6 -1
  40. package/dist/backend/types.js +3 -0
  41. package/dist/backend/types.js.map +1 -1
  42. package/dist/backend-availability.d.ts +6 -0
  43. package/dist/backend-availability.js +13 -5
  44. package/dist/backend-availability.js.map +1 -1
  45. package/dist/cache-ledger.d.ts +85 -0
  46. package/dist/cache-ledger.js +236 -1
  47. package/dist/cache-ledger.js.map +1 -1
  48. package/dist/cache-service.d.ts +5 -0
  49. package/dist/cache-service.js +16 -4
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.js +27 -4
  52. package/dist/channel/adapters/discord.js.map +1 -1
  53. package/dist/channel/adapters/telegram.d.ts +51 -0
  54. package/dist/channel/adapters/telegram.js +238 -18
  55. package/dist/channel/adapters/telegram.js.map +1 -1
  56. package/dist/channel/inbox-temp-name.d.ts +9 -0
  57. package/dist/channel/inbox-temp-name.js +13 -0
  58. package/dist/channel/inbox-temp-name.js.map +1 -0
  59. package/dist/channel/mcp-tools.js +3 -3
  60. package/dist/channel/mcp-tools.js.map +1 -1
  61. package/dist/channel/types.d.ts +2 -0
  62. package/dist/chat-log-ids.d.ts +48 -0
  63. package/dist/chat-log-ids.js +182 -0
  64. package/dist/chat-log-ids.js.map +1 -0
  65. package/dist/classic-channel-manager.d.ts +8 -1
  66. package/dist/classic-channel-manager.js +24 -2
  67. package/dist/classic-channel-manager.js.map +1 -1
  68. package/dist/cli.js +4 -6
  69. package/dist/cli.js.map +1 -1
  70. package/dist/cross-instance-notice.d.ts +10 -0
  71. package/dist/cross-instance-notice.js +67 -5
  72. package/dist/cross-instance-notice.js.map +1 -1
  73. package/dist/daemon.d.ts +52 -0
  74. package/dist/daemon.js +530 -34
  75. package/dist/daemon.js.map +1 -1
  76. package/dist/delivery-consumed-watch.d.ts +23 -1
  77. package/dist/delivery-consumed-watch.js +30 -3
  78. package/dist/delivery-consumed-watch.js.map +1 -1
  79. package/dist/delivery-reconciliation.d.ts +7 -0
  80. package/dist/delivery-reconciliation.js +9 -1
  81. package/dist/delivery-reconciliation.js.map +1 -1
  82. package/dist/dialog-screen-text.d.ts +16 -0
  83. package/dist/dialog-screen-text.js +51 -0
  84. package/dist/dialog-screen-text.js.map +1 -0
  85. package/dist/export-import.d.ts +7 -0
  86. package/dist/export-import.js +44 -10
  87. package/dist/export-import.js.map +1 -1
  88. package/dist/fleet-context.d.ts +2 -0
  89. package/dist/fleet-manager.d.ts +147 -11
  90. package/dist/fleet-manager.js +747 -118
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-yaml-patch.d.ts +3 -0
  93. package/dist/fleet-yaml-patch.js +103 -0
  94. package/dist/fleet-yaml-patch.js.map +1 -0
  95. package/dist/general-knowledge/skills/concepts/SKILL.md +393 -0
  96. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
  97. package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -1
  98. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +41 -4
  99. package/dist/general-knowledge/skills/model-discovery/SKILL.md +2 -0
  100. package/dist/general-knowledge/skills/web-appearance/SKILL.md +1 -1
  101. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +10 -4
  102. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  103. package/dist/install-stream.d.ts +53 -0
  104. package/dist/install-stream.js +134 -0
  105. package/dist/install-stream.js.map +1 -0
  106. package/dist/instance-lifecycle.d.ts +20 -2
  107. package/dist/instance-lifecycle.js +80 -12
  108. package/dist/instance-lifecycle.js.map +1 -1
  109. package/dist/instance-topic-delete.d.ts +77 -0
  110. package/dist/instance-topic-delete.js +123 -0
  111. package/dist/instance-topic-delete.js.map +1 -0
  112. package/dist/instructions.d.ts +1 -1
  113. package/dist/instructions.js +1 -1
  114. package/dist/instructions.js.map +1 -1
  115. package/dist/kiro-engine-status.d.ts +11 -0
  116. package/dist/kiro-engine-status.js +6 -0
  117. package/dist/kiro-engine-status.js.map +1 -1
  118. package/dist/kiro-resume-summary.d.ts +37 -0
  119. package/dist/kiro-resume-summary.js +85 -0
  120. package/dist/kiro-resume-summary.js.map +1 -0
  121. package/dist/locale.js +82 -6
  122. package/dist/locale.js.map +1 -1
  123. package/dist/log-service.d.ts +30 -0
  124. package/dist/log-service.js +36 -3
  125. package/dist/log-service.js.map +1 -1
  126. package/dist/log-steps.d.ts +4 -3
  127. package/dist/log-steps.js +32 -7
  128. package/dist/log-steps.js.map +1 -1
  129. package/dist/logger.d.ts +12 -0
  130. package/dist/logger.js +43 -1
  131. package/dist/logger.js.map +1 -1
  132. package/dist/login-controller.js +9 -1
  133. package/dist/login-controller.js.map +1 -1
  134. package/dist/login-flows.d.ts +2 -0
  135. package/dist/login-flows.js +5 -2
  136. package/dist/login-flows.js.map +1 -1
  137. package/dist/login-manager.d.ts +6 -0
  138. package/dist/login-manager.js +10 -1
  139. package/dist/login-manager.js.map +1 -1
  140. package/dist/missed-offline.d.ts +25 -0
  141. package/dist/missed-offline.js +79 -0
  142. package/dist/missed-offline.js.map +1 -0
  143. package/dist/needs-you-hub.d.ts +6 -1
  144. package/dist/needs-you-hub.js +31 -8
  145. package/dist/needs-you-hub.js.map +1 -1
  146. package/dist/needs-you.d.ts +22 -2
  147. package/dist/needs-you.js +17 -3
  148. package/dist/needs-you.js.map +1 -1
  149. package/dist/office-share-page.d.ts +1 -1
  150. package/dist/office-share-page.js +6 -2
  151. package/dist/office-share-page.js.map +1 -1
  152. package/dist/outbound-handlers.js +59 -3
  153. package/dist/outbound-handlers.js.map +1 -1
  154. package/dist/outbound-schemas.d.ts +1 -0
  155. package/dist/outbound-schemas.js +9 -3
  156. package/dist/outbound-schemas.js.map +1 -1
  157. package/dist/package-preimage.js +27 -2
  158. package/dist/package-preimage.js.map +1 -1
  159. package/dist/queued-handoffs.d.ts +33 -0
  160. package/dist/queued-handoffs.js +75 -0
  161. package/dist/queued-handoffs.js.map +1 -0
  162. package/dist/reply-buttons-controller.js +10 -0
  163. package/dist/reply-buttons-controller.js.map +1 -1
  164. package/dist/scheduler/db.d.ts +2 -1
  165. package/dist/scheduler/db.js +23 -9
  166. package/dist/scheduler/db.js.map +1 -1
  167. package/dist/selection-card.d.ts +2 -3
  168. package/dist/selection-card.js +99 -26
  169. package/dist/selection-card.js.map +1 -1
  170. package/dist/settings-api.d.ts +13 -0
  171. package/dist/settings-api.js +12 -1
  172. package/dist/settings-api.js.map +1 -1
  173. package/dist/stall-reporter.d.ts +21 -0
  174. package/dist/stall-reporter.js +82 -0
  175. package/dist/stall-reporter.js.map +1 -0
  176. package/dist/tmux-control.d.ts +2 -2
  177. package/dist/tmux-control.js +4 -2
  178. package/dist/tmux-control.js.map +1 -1
  179. package/dist/tmux-exec-helper.d.ts +6 -3
  180. package/dist/tmux-exec-helper.js +115 -53
  181. package/dist/tmux-exec-helper.js.map +1 -1
  182. package/dist/tmux-manager.d.ts +6 -0
  183. package/dist/tmux-manager.js +17 -0
  184. package/dist/tmux-manager.js.map +1 -1
  185. package/dist/tool-permissions.d.ts +5 -2
  186. package/dist/tool-permissions.js +6 -5
  187. package/dist/tool-permissions.js.map +1 -1
  188. package/dist/tool-progress.d.ts +2 -0
  189. package/dist/tool-progress.js +19 -3
  190. package/dist/tool-progress.js.map +1 -1
  191. package/dist/topic-commands.js +4 -0
  192. package/dist/topic-commands.js.map +1 -1
  193. package/dist/tunnel/cloudflared-install.d.ts +4 -0
  194. package/dist/tunnel/cloudflared-install.js +20 -1
  195. package/dist/tunnel/cloudflared-install.js.map +1 -1
  196. package/dist/tunnel/cloudflared.d.ts +8 -1
  197. package/dist/tunnel/cloudflared.js +34 -6
  198. package/dist/tunnel/cloudflared.js.map +1 -1
  199. package/dist/tunnel/lease.d.ts +3 -3
  200. package/dist/tunnel/lease.js +23 -19
  201. package/dist/tunnel/lease.js.map +1 -1
  202. package/dist/tunnel/manager.js +6 -7
  203. package/dist/tunnel/manager.js.map +1 -1
  204. package/dist/ui/chat-store.js +20 -3
  205. package/dist/ui/chat-strings.js +4 -4
  206. package/dist/ui/chat-thread.js +52 -10
  207. package/dist/ui/fleet-canvas.js +4 -3
  208. package/dist/ui/fleet-office-model.js +55 -22
  209. package/dist/ui/fleet-office-overview.js +6 -38
  210. package/dist/ui/fleet-office-pack.js +4 -3
  211. package/dist/ui/fleet-office-skin.js +114 -21
  212. package/dist/ui/fleet-office-store.js +5 -0
  213. package/dist/ui/fleet-office-zones.js +2 -2
  214. package/dist/ui/fleet-office.js +41 -9
  215. package/dist/ui/fleet-usage.js +90 -27
  216. package/dist/ui/panel-chat.js +2 -0
  217. package/dist/ui/panel-log.js +7 -1
  218. package/dist/ui/panel-needs.js +17 -8
  219. package/dist/ui/settings-strings.js +2 -2
  220. package/dist/ui/shared/app-i18n.js +4 -4
  221. package/dist/ui/shared/app-needs.js +1 -1
  222. package/dist/ui/shared/app-shell.js +4 -2
  223. package/dist/ui/shared/app-stream.js +26 -0
  224. package/dist/ui/shared/app.css +51 -3
  225. package/dist/ui/shared/office/guild.png +0 -0
  226. package/dist/ui/shared/office/school.png +0 -0
  227. package/dist/ui/shared/panel-view.js +339 -17
  228. package/dist/ui/shared/usage-dialog.js +18 -1
  229. package/dist/ui/shared/view-history.css +17 -0
  230. package/dist/ui/shared/view-history.js +438 -0
  231. package/dist/ui/shared/view-strings.js +42 -0
  232. package/dist/unattended-install.d.ts +6 -0
  233. package/dist/unattended-install.js +79 -0
  234. package/dist/unattended-install.js.map +1 -0
  235. package/dist/usage/capacity.d.ts +69 -0
  236. package/dist/usage/capacity.js +119 -0
  237. package/dist/usage/capacity.js.map +1 -0
  238. package/dist/usage/i18n-keys.d.ts +1 -1
  239. package/dist/usage/i18n-keys.js +3 -0
  240. package/dist/usage/i18n-keys.js.map +1 -1
  241. package/dist/usage/profile-timeline.d.ts +16 -0
  242. package/dist/usage/profile-timeline.js +58 -0
  243. package/dist/usage/profile-timeline.js.map +1 -0
  244. package/dist/usage/quota-history.d.ts +43 -0
  245. package/dist/usage/quota-history.js +168 -0
  246. package/dist/usage/quota-history.js.map +1 -0
  247. package/dist/usage/usage-api.d.ts +31 -0
  248. package/dist/usage/usage-api.js +80 -1
  249. package/dist/usage/usage-api.js.map +1 -1
  250. package/dist/usage-report.d.ts +25 -1
  251. package/dist/usage-report.js +55 -4
  252. package/dist/usage-report.js.map +1 -1
  253. package/dist/usage-rollup.d.ts +4 -1
  254. package/dist/usage-rollup.js +12 -3
  255. package/dist/usage-rollup.js.map +1 -1
  256. package/dist/view-api.d.ts +31 -0
  257. package/dist/view-api.js +186 -41
  258. package/dist/view-api.js.map +1 -1
  259. package/dist/view-history.d.ts +84 -0
  260. package/dist/view-history.js +116 -0
  261. package/dist/view-history.js.map +1 -0
  262. package/dist/web-api.d.ts +6 -0
  263. package/dist/web-api.js +15 -4
  264. package/dist/web-api.js.map +1 -1
  265. package/dist/web-chat-history.d.ts +20 -0
  266. package/dist/web-chat-history.js +21 -0
  267. package/dist/web-chat-history.js.map +1 -1
  268. package/dist/web-guest-access.js +5 -1
  269. package/dist/web-guest-access.js.map +1 -1
  270. package/dist/web-login.d.ts +4 -3
  271. package/dist/web-login.js +23 -14
  272. package/dist/web-login.js.map +1 -1
  273. package/dist/web-office-pack-tools.d.ts +1 -1
  274. package/dist/web-office-pack-tools.js +1 -1
  275. package/dist/web-office-pack-tools.js.map +1 -1
  276. package/dist/web-session-authorization.d.ts +13 -2
  277. package/dist/web-session-authorization.js +10 -3
  278. package/dist/web-session-authorization.js.map +1 -1
  279. package/dist/web-session.d.ts +5 -0
  280. package/dist/web-session.js +20 -8
  281. package/dist/web-session.js.map +1 -1
  282. package/launcher/agend-install.cjs +16 -2
  283. package/launcher/private-home.cjs +1 -1
  284. package/package.json +1 -1
@@ -79,7 +79,7 @@ lists every page (**Pages (N)**), shows a page at phone or tablet width, and goe
79
79
  - `/web revoke` (in chat) signs every browser out, withdraws an unused code and closes the public link.
80
80
 
81
81
  **`/view` without signing in.** `web.view_access`: `open` (default) or `session`. With `open`, anyone who can reach
82
- the local address sees every instance's live terminal and the roster (status, context %, model, effort, backend,
82
+ the local address sees every instance's live terminal (for Claude Code with its earlier conversation, as below) and the roster (status, context %, model, effort, backend,
83
83
  tags, name, description, avatars, cards) and the usage meters — read only; editing says "Sign in to edit". Over a
84
84
  public link `/view` always needs sign-in.
85
85
 
@@ -101,7 +101,7 @@ They can chat there unless `web.guests.chat` is false, run only the commands the
101
101
  agent's channel (on the web: `/ctx`, `/steer`, `/btw`, `/cancel`; anything else is refused), open the usage panel, and the
102
102
  agent sees them as `via web (guest)`. A guest's app has no Fleet, Settings or Needs you. Each person is **Chat** (the
103
103
  default) or **Trusted** (#2855, `level: trusted` on the grant; Chat → Trusted waits for a fleet admin): a Trusted guest
104
- also gets a read-only **View** of those agents (no typing; 2 reads a second, 2 agents at once) and **search** of the chat
104
+ also gets a read-only **View** of those agents (no typing; 2 reads a second, 2 agents at once; for Claude Code with its earlier conversation) and **search** of the chat
105
105
  messages it can already see (6 a minute, 20 results; no messages between agents, tasks or settings). To invite someone: turn it
106
106
  on, grant them their agents in Settings → Access & security → Guests, and press **Send sign-in code** (or tell them to
107
107
  send `/web`). The Guests section is not in Settings over the public link.
@@ -291,8 +291,9 @@ buttons in General); every change is logged and told in General. **Style** (per
291
291
  **Studio** (one scene — Work, Lounge, Help desk, Nap corner — where each instance walks to the area for its state),
292
292
  **Cozy** (#2631: the same in a warm pixel-art office drawn as one picture; its notice board says a skin pack can
293
293
  restyle it; the default for a browser that has not picked a style and for a new office — a saved office keeps its
294
- own) or an uploaded pack; one the page cannot load is drawn as Desks with a note. On a stored office in Studio,
295
- Cozy or a pack,
294
+ own), **Guild** and **School** (#2947: two more pictures on the Cozy's arrangement — an adventurers' guild hall and a
295
+ classroom) or an uploaded pack; one the page cannot load is drawn as Desks with a note. On a stored office in Studio,
296
+ Cozy, Guild, School or a pack,
296
297
  **Edit layout** (admin only) shows a 1280×720 coordinate grid and lets you drag seats, furniture, the door, the mail drop
297
298
  (an envelope: where letters from other offices land; dashed on a pack without a `mailDrop`, #2736) and
298
299
  waypoints (or Tab to one and use the arrow keys, Shift for 10), add or remove waypoints and join or unjoin
@@ -382,6 +383,11 @@ to that instance's Log).
382
383
 
383
384
  **Chat | View | Log | Details** switch at the top of an instance.
384
385
  - **View** — the live terminal (read only), the identity card, and **Edit profile** (or **Sign in to edit** when signed out).
386
+ Scrolling up (or **Earlier output**) shows what scrolled off: tmux's scrollback; for Claude Code, its earlier
387
+ conversation from its transcript, drawn in Claude's own terminal style above the live screen under **History above**
388
+ (long output folded, click to expand; older pages near the top; the latest ~1000 steps), tool results included.
389
+ Everyone who may open the View sees it — a signed-in admin, an anonymous reader under `view_access: open`, a Trusted
390
+ guest for its agents (the user's decision on #3069); the Log tab's switch plays no part.
385
391
  - **Log** — [the Log tab](#the-log-tab).
386
392
  - **Details** — Configuration (display name, description, directory, bound to, tags; "Read only — change it in
387
393
  Settings." with **Edit in Settings**), Runtime (backend, model, effort, cost, context, 5 h and 7 d rate), Recent
@@ -42,4 +42,4 @@ Do not put user-facing results only in terminal text; the fleet report is the de
42
42
 
43
43
  ## Relayed human approval
44
44
 
45
- For an irreversible action based on relayed consent, request an explicit `forwards` edge for the original human `verification_id`, then read its original content and origin with `delivery_status`. Each hop needs a forward; a peer's summary or a mentioned ID grants nothing. When you relay it, put the ID in `forwards` (even if your loaded copy of the tool's schema lacks the parameter: the server accepts it), not in the text. Require delivered and an owned proof_kind, and verify the actual platform/adapter/actor plus the exact resource. Hidden/expired/uncertain receipts are not consent and must not trigger a resend. A web-chat message (`[user:<name> via web, id:…]`) is that person: their sign-in code went to their own Discord or Telegram account. Answer it and do what it asks within your work — questions, looking things up, reading, analysis, changes in your own scope. Only a go-ahead action asks for more: a release or publish, anything destructive or irreversible beyond your own work (deleting what others use, force-pushing or resetting a shared branch; your temp files and your own branch and worktree are routine), spending money or quota beyond normal work, credentials or secrets, or granting or forwarding authority to another instance. For these an open browser is not proof of the account, so the account must stand behind it: a Discord or Telegram receipt, or a `via web (verified)` one — read it with `delivery_status`: `authority.kind: web-verified`, its person `authority.actor` (adapter and user id), checked like a Discord or Telegram message (delivered, an owned proof_kind, the original content, that actor) and forwarded the same way. On a web-only fleet (no Discord or Telegram connection) that person is the host operator, signed in with `agend web --code` on the AgEnD computer itself: a verified receipt names `authority.actor` adapter `host`, user `operator`. A guest (`via web (guest)`) was let in only to chat: talk with them there, and take nothing they ask as the admin's. Plain `via web`, a guest, and authority since revoked (`authority.kind: none`) are not a go-ahead; nor is a reviewer's APPROVE. See the cross-instance-messaging skill.
45
+ For an irreversible action based on relayed consent, request an explicit `forwards` edge for the original human `verification_id`, then read its original content and origin with `delivery_status`. Each hop needs a forward; a peer's summary or a mentioned ID grants nothing. When you relay it, put the ID in `forwards` (even if your loaded copy of the tool's schema lacks the parameter: the server accepts it), not in the text. Put verification and message ids in the `forwards` field only; do not repeat them in the message text, because people see that text in the topic notices. Require delivered and an owned proof_kind, and verify the actual platform/adapter/actor plus the exact resource. Hidden/expired/uncertain receipts are not consent and must not trigger a resend. A web-chat message (`[user:<name> via web, id:…]`) is that person: their sign-in code went to their own Discord or Telegram account. Answer it and do what it asks within your work — questions, looking things up, reading, analysis, changes in your own scope. Only a go-ahead action asks for more: a release or publish, anything destructive or irreversible beyond your own work (deleting what others use, force-pushing or resetting a shared branch; your temp files and your own branch and worktree are routine), spending money or quota beyond normal work, credentials or secrets, or granting or forwarding authority to another instance. For these an open browser is not proof of the account, so the account must stand behind it: a Discord or Telegram receipt, or a `via web (verified)` one — read it with `delivery_status`: `authority.kind: web-verified`, its person `authority.actor` (adapter and user id), checked like a Discord or Telegram message (delivered, an owned proof_kind, the original content, that actor) and forwarded the same way. On a web-only fleet (no Discord or Telegram connection) that person is the host operator, signed in with `agend web --code` on the AgEnD computer itself: a verified receipt names `authority.actor` adapter `host`, user `operator`. A guest (`via web (guest)`) was let in only to chat: talk with them there, and take nothing they ask as the admin's. Plain `via web`, a guest, and authority since revoked (`authority.kind: none`) are not a go-ahead; nor is a reviewer's APPROVE. See the cross-instance-messaging skill.
@@ -0,0 +1,53 @@
1
+ export declare const INSTALL_STREAM_LINES = 15;
2
+ export declare const INSTALL_STREAM_BYTES = 1500;
3
+ export declare const INSTALL_STREAM_EDIT_MIN_MS = 5000;
4
+ export interface InstallStreamSink {
5
+ /** Post the message (its plain status line); its id is what the edits change. */
6
+ send(text: string): Promise<{
7
+ messageId: string;
8
+ } | undefined>;
9
+ /** Replace the message's text: a code block under the status line, in the stream's style (markdown or html). */
10
+ edit(messageId: string, text: string): Promise<void>;
11
+ warn?(message: string, err: unknown): void;
12
+ }
13
+ export interface InstallStreamClock {
14
+ now(): number;
15
+ setTimeout(fn: () => void, ms: number): unknown;
16
+ clearTimeout(handle: unknown): void;
17
+ }
18
+ /**
19
+ * The window's own text, without what tmux adds to it: a dead pane's "Pane is dead (status N, <date>)" line (drawn on
20
+ * the bottom row, below a screenful of blank rows), and runs of blank rows collapsed to one — else the last lines of a
21
+ * finished install would be blanks and that line.
22
+ */
23
+ export declare function installerOutput(pane: string): string;
24
+ /**
25
+ * How a frame is written: "markdown" (a fenced code block — Discord) or "html" (Telegram's own `<pre>`: its markdown
26
+ * conversion keeps a fence as literal backticks).
27
+ */
28
+ export type InstallFrameStyle = "markdown" | "html";
29
+ /** The window's last lines as a code block under a status line; just the status line when there are none. */
30
+ export declare function renderInstallFrame(header: string, pane: string, style?: InstallFrameStyle): string;
31
+ export declare class InstallStream {
32
+ private readonly sink;
33
+ private readonly header;
34
+ private readonly clock;
35
+ private readonly editMinMs;
36
+ private readonly style;
37
+ private messageId;
38
+ private shown;
39
+ private wanted;
40
+ private pane;
41
+ private lastAt;
42
+ private timer;
43
+ private inFlight;
44
+ private closed;
45
+ constructor(sink: InstallStreamSink, header: string, clock?: InstallStreamClock, editMinMs?: number, style?: InstallFrameStyle);
46
+ /** A new capture of the window. Never awaits; nothing is posted until the window shows something. */
47
+ update(pane: string): void;
48
+ /** Stop editing and write the final frame (the last capture, unless a later one is given) over the message. */
49
+ finish(header: string, pane?: string): Promise<void>;
50
+ private schedule;
51
+ /** `header`: the frame's status line as plain text — what a new message is first posted as. */
52
+ private write;
53
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * M-2281 (#2980): the installer's output, live, in the chat that asked `/login` to install a CLI. No page shows the
3
+ * `agend-login-<backend>` window (it is not an instance), so the chat is where its progress is seen.
4
+ *
5
+ * One message, edited in place: the last INSTALL_STREAM_LINES lines of the window, ANSI stripped and secrets masked
6
+ * (dialogScreenText — the whole capture is masked before its bottom is kept), under INSTALL_STREAM_BYTES so it fits
7
+ * a Discord (2000) or Telegram (4096) message. At most one edit per INSTALL_STREAM_EDIT_MIN_MS, one in flight, none
8
+ * for an unchanged frame. The frames come from the session's own poll capture — nothing here reads the pane.
9
+ *
10
+ * Fire-and-forget from the poll: update() only records the frame and arms a timer. finish() stops the edits and
11
+ * writes the final frame (success / failed / timed out / cancelled) over the same message.
12
+ */
13
+ import { dialogScreenText } from "./dialog-screen-text.js";
14
+ export const INSTALL_STREAM_LINES = 15;
15
+ export const INSTALL_STREAM_BYTES = 1_500;
16
+ export const INSTALL_STREAM_EDIT_MIN_MS = 5_000;
17
+ const realClock = {
18
+ now: () => performance.now(),
19
+ setTimeout: (fn, ms) => { const t = setTimeout(fn, ms); t.unref?.(); return t; },
20
+ clearTimeout: handle => clearTimeout(handle),
21
+ };
22
+ /**
23
+ * The window's own text, without what tmux adds to it: a dead pane's "Pane is dead (status N, <date>)" line (drawn on
24
+ * the bottom row, below a screenful of blank rows), and runs of blank rows collapsed to one — else the last lines of a
25
+ * finished install would be blanks and that line.
26
+ */
27
+ export function installerOutput(pane) {
28
+ return pane.replace(/^Pane is dead\b.*$/gm, "").replace(/\n(?:[ \t]*\n){2,}/g, "\n\n");
29
+ }
30
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31
+ /** The window's last lines as a code block under a status line; just the status line when there are none. */
32
+ export function renderInstallFrame(header, pane, style = "markdown") {
33
+ const body = dialogScreenText(installerOutput(pane), { lines: INSTALL_STREAM_LINES, bytes: INSTALL_STREAM_BYTES });
34
+ if (!body)
35
+ return style === "html" ? escapeHtml(header) : header;
36
+ // HTML: everything escaped, the only tag is ours. Markdown: a fence inside the output would close the block early,
37
+ // so its backticks become look-alikes.
38
+ return style === "html"
39
+ ? `${escapeHtml(header)}\n<pre>${escapeHtml(body)}</pre>`
40
+ : `${header}\n\`\`\`\n${body.replace(/`/g, "ˋ")}\n\`\`\``;
41
+ }
42
+ export class InstallStream {
43
+ sink;
44
+ header;
45
+ clock;
46
+ editMinMs;
47
+ style;
48
+ messageId;
49
+ shown = "";
50
+ wanted = "";
51
+ pane = "";
52
+ lastAt = Number.NEGATIVE_INFINITY;
53
+ timer = null;
54
+ inFlight = null;
55
+ closed = false;
56
+ constructor(sink, header, clock = realClock, editMinMs = INSTALL_STREAM_EDIT_MIN_MS, style = "markdown") {
57
+ this.sink = sink;
58
+ this.header = header;
59
+ this.clock = clock;
60
+ this.editMinMs = editMinMs;
61
+ this.style = style;
62
+ }
63
+ /** A new capture of the window. Never awaits; nothing is posted until the window shows something. */
64
+ update(pane) {
65
+ if (this.closed)
66
+ return;
67
+ this.pane = pane;
68
+ const frame = renderInstallFrame(this.header, pane, this.style);
69
+ if (frame === renderInstallFrame(this.header, "", this.style) && this.messageId === undefined)
70
+ return;
71
+ this.wanted = frame;
72
+ this.schedule();
73
+ }
74
+ /** Stop editing and write the final frame (the last capture, unless a later one is given) over the message. */
75
+ async finish(header, pane) {
76
+ this.closed = true;
77
+ if (this.timer !== null) {
78
+ this.clock.clearTimeout(this.timer);
79
+ this.timer = null;
80
+ }
81
+ await this.inFlight;
82
+ const frame = renderInstallFrame(header, pane ?? this.pane, this.style);
83
+ // Nothing was ever shown: the result message says it all.
84
+ if (this.messageId === undefined && frame === renderInstallFrame(header, "", this.style))
85
+ return;
86
+ // The final edit keeps the rate too: it waits out what is left of the interval rather than skip it.
87
+ const wait = this.lastAt + this.editMinMs - this.clock.now();
88
+ if (wait > 0)
89
+ await new Promise(resolve => this.clock.setTimeout(resolve, wait));
90
+ await this.write(frame, header);
91
+ }
92
+ schedule() {
93
+ if (this.timer !== null || this.inFlight || this.wanted === this.shown)
94
+ return;
95
+ const delay = Math.max(0, this.lastAt + this.editMinMs - this.clock.now());
96
+ this.timer = this.clock.setTimeout(() => {
97
+ this.timer = null;
98
+ if (this.closed)
99
+ return;
100
+ const frame = this.wanted;
101
+ this.inFlight = this.write(frame, this.header).finally(() => {
102
+ this.inFlight = null;
103
+ if (!this.closed)
104
+ this.schedule();
105
+ });
106
+ }, delay);
107
+ }
108
+ /** `header`: the frame's status line as plain text — what a new message is first posted as. */
109
+ async write(frame, header) {
110
+ if (frame === this.shown)
111
+ return;
112
+ this.lastAt = this.clock.now();
113
+ try {
114
+ if (this.messageId === undefined) {
115
+ // Posted as its plain status line, and every frame goes in by an edit: the message is then one kind from start
116
+ // to end (Telegram posts a text with a code block as a rich message, but edits text with HTML).
117
+ const sent = await this.sink.send(header);
118
+ if (!sent?.messageId)
119
+ return;
120
+ this.messageId = sent.messageId;
121
+ await this.sink.edit(this.messageId, frame);
122
+ }
123
+ else {
124
+ await this.sink.edit(this.messageId, frame);
125
+ }
126
+ this.shown = frame;
127
+ }
128
+ catch (err) {
129
+ // A refused edit (rate limit, a reconnect) is tried again with the next frame, after the interval.
130
+ this.sink.warn?.("install output edit failed", err);
131
+ }
132
+ }
133
+ }
134
+ //# sourceMappingURL=install-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-stream.js","sourceRoot":"","sources":["../src/install-stream.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { SettingsExecution } from "./settings-transaction.js";
2
2
  import type { InstanceConfig, FleetConfig } from "./types.js";
3
+ import type { TopicDeleteOutcome } from "./instance-topic-delete.js";
3
4
  import { RoutingEngine } from "./routing-engine.js";
4
5
  import type { Logger } from "./logger.js";
5
6
  import type { IpcClient } from "./channel/ipc-bridge.js";
@@ -41,6 +42,11 @@ export interface BackendInstallationInfo {
41
42
  export declare const BACKEND_INSTALLATION_INFO: Readonly<Record<string, BackendInstallationInfo>>;
42
43
  /** Check one executable using the same PATH visible to the fleet process. */
43
44
  export declare function checkBinaryInstalled(binary: string): boolean;
45
+ /**
46
+ * M-2281 (#2980): the same check without holding the fleet loop — for paths a chat click runs (`/login`'s install
47
+ * routing), where a slow PATH (WSL's /mnt/c entries) would otherwise stall every adapter for up to the timeout.
48
+ */
49
+ export declare function checkBinaryInstalledAsync(binary: string): Promise<boolean>;
44
50
  /**
45
51
  * Return why an instance daemon PID must not receive SIGTERM, or null when the
46
52
  * stale-process cleanup may proceed. daemon.pid historically stores the shared
@@ -52,6 +58,7 @@ export declare function getUnsafeInstanceDaemonPidReason(pid: number, dataDir: s
52
58
  * FleetManager implements this.
53
59
  */
54
60
  export interface LifecycleContext {
61
+ readonly agentCredentials?: Pick<import("./agent-credentials.js").AgentCredentials, "issue" | "invalidate">;
55
62
  readonly fleetConfig: FleetConfig | null;
56
63
  readonly logger: Logger;
57
64
  readonly dataDir: string;
@@ -88,8 +95,10 @@ export interface LifecycleContext {
88
95
  createForumTopic(topicName: string, adapterId?: string): Promise<number | string>;
89
96
  /** #2910: no Discord or Telegram connection at all — a new agent gets no topic (it is reached from the dashboard). */
90
97
  isWebOnlyFleet?(): boolean;
91
- /** adapterId selects the world, mirroring createForumTopic (rollback must delete where it created). */
92
- deleteForumTopic(topicId: number | string, adapterId?: string): Promise<void>;
98
+ /** delete_instance: the removal, then (unless `deleteTopic` is false) the agent's own topic, through its own bot. */
99
+ removeInstanceAndTopic(name: string, authorization: ExplicitInstanceRemoval, opts: {
100
+ deleteTopic: boolean;
101
+ }): Promise<TopicDeleteOutcome>;
93
102
  /**
94
103
  * A bound delete for the world `adapterId` selects, captured by the caller
95
104
  * BEFORE awaiting createForumTopic. Rollback must use exactly this — never
@@ -130,6 +139,8 @@ export interface LifecycleContext {
130
139
  /** Notify a clean CLI exit and offer an admin-only restart action in General. */
131
140
  notifyNormalExit(name: string): Promise<void>;
132
141
  notifySessionOomStop?(name: string, unclear?: boolean): Promise<void>;
142
+ /** A pre-launch refusal is still a visible wait when no daemon was published. */
143
+ setKiroCompatibilityHold?(name: string, message: string | null): void;
133
144
  /** Tell every General, as an agent, that kiro-cli can no longer run this instance as configured. */
134
145
  queueKiroIncompatNotice?(name: string, reason: string): void;
135
146
  /** True for a dynamic ClassicBot channel instance (not a fleet topic). */
@@ -366,6 +377,13 @@ export declare class InstanceLifecycle {
366
377
  /** Max retries before giving up and pausing. */
367
378
  private static readonly CAPACITY_BACKOFF_MAX;
368
379
  constructor(ctx: LifecycleContext);
380
+ /** M-2269: the last dialog text logged per instance — the same screen seen again on later scans is not logged again. */
381
+ private readonly lastLoggedScreen;
382
+ /**
383
+ * M-2269: `{ screen }` for a dialog/prompt event's Log row (the daemon already stripped, masked and capped it), or
384
+ * nothing when there is none or it is the same text this instance's Log already has. Bookkeeping only.
385
+ */
386
+ private screenForLog;
369
387
  /** Seed only the Codex daemon replacing a scheduled capacity retry. */
370
388
  private applyCapacityBackoffBaseline;
371
389
  /**
@@ -5,13 +5,14 @@ import { existsSync, readFileSync, mkdirSync, realpathSync, writeFileSync, statS
5
5
  import { ensureInstanceDir } from "./private-dir.js";
6
6
  import { assertNoSessionOomStop } from "./session-oom-stop.js";
7
7
  import { execFileSync } from "node:child_process";
8
+ import { execFile } from "./child-process.js";
8
9
  import { join, basename, dirname, resolve, sep as pathSep } from "node:path";
9
10
  import { access } from "node:fs/promises";
10
11
  import { getAgendHome, ensureWorkspaceGit } from "./paths.js";
11
12
  import { DEFAULT_INSTANCE_CONFIG, getEffectiveInstanceDefaults, getTmuxSession, newInstanceKiroUi } from "./config.js";
12
13
  import { readStatuslineModel, uniqueInstanceName } from "./topic-commands.js";
13
14
  import { sanitizeInstanceName } from "./instance-names.js";
14
- import { isModelCompatible } from "./backend/types.js";
15
+ import { isModelCompatible, noteResolvedBinary } from "./backend/types.js";
15
16
  import { safeHandler } from "./safe-async.js";
16
17
  import { t } from "./locale.js";
17
18
  import { interactionCategory } from "./interaction-observation.js";
@@ -65,6 +66,20 @@ export const BACKEND_INSTALLATION_INFO = {
65
66
  export function checkBinaryInstalled(binary) {
66
67
  return measureSyncWork("lifecycle.checkBinaryInstalled", () => checkBinaryInstalledSync(binary));
67
68
  }
69
+ /**
70
+ * M-2281 (#2980): the same check without holding the fleet loop — for paths a chat click runs (`/login`'s install
71
+ * routing), where a slow PATH (WSL's /mnt/c entries) would otherwise stall every adapter for up to the timeout.
72
+ */
73
+ export function checkBinaryInstalledAsync(binary) {
74
+ return new Promise(resolve => {
75
+ try {
76
+ execFile("which", [binary], { timeout: 2000 }, err => resolve(!err));
77
+ }
78
+ catch {
79
+ resolve(false);
80
+ }
81
+ });
82
+ }
68
83
  function checkBinaryInstalledSync(binary) {
69
84
  try {
70
85
  execFileSync("which", [binary], { stdio: "pipe", timeout: 2000 });
@@ -322,6 +337,20 @@ export class InstanceLifecycle {
322
337
  constructor(ctx) {
323
338
  this.ctx = ctx;
324
339
  }
340
+ /** M-2269: the last dialog text logged per instance — the same screen seen again on later scans is not logged again. */
341
+ lastLoggedScreen = new Map();
342
+ /**
343
+ * M-2269: `{ screen }` for a dialog/prompt event's Log row (the daemon already stripped, masked and capped it), or
344
+ * nothing when there is none or it is the same text this instance's Log already has. Bookkeeping only.
345
+ */
346
+ screenForLog(name, screen) {
347
+ if (typeof screen !== "string" || !screen)
348
+ return {};
349
+ if (this.lastLoggedScreen.get(name) === screen)
350
+ return {};
351
+ this.lastLoggedScreen.set(name, screen);
352
+ return { screen };
353
+ }
325
354
  /** Seed only the Codex daemon replacing a scheduled capacity retry. */
326
355
  applyCapacityBackoffBaseline(name, backendName, daemon) {
327
356
  if (backendName !== "codex")
@@ -652,6 +681,24 @@ export class InstanceLifecycle {
652
681
  }
653
682
  this.ctx.notifyInstanceTopic(name, t("delivery.cancelled_unsent", messages.length, name));
654
683
  }, this.ctx.logger, `daemon.human_messages_cancelled[${name}]`));
684
+ // #3030: messages handed to the busy CLI's own queue that it never took (it went idle without them), or that a
685
+ // restart left unverifiable (its transcript could not be read, so they were not sent again). Each is marked ❌ and
686
+ // the person is told once — never a silent ✅.
687
+ daemon.on("human_handoffs_unsettled", safeHandler((data) => {
688
+ const messages = Array.isArray(data?.messages) ? data.messages : [];
689
+ if (!messages.length)
690
+ return;
691
+ const reason = data.reason === "unverifiable" ? "unverifiable" : "not-taken";
692
+ this.ctx.eventLog?.insert(name, "handoffs_unsettled", { reason, count: messages.length });
693
+ this.ctx.logger.warn({ name, reason, count: messages.length }, "Queued messages not taken by the CLI — telling the person");
694
+ for (const m of messages) {
695
+ if (this.ctx.finishDeliveryStatus)
696
+ this.ctx.finishDeliveryStatus(name, m.chatId, m.messageId, "failed", m.threadId);
697
+ else
698
+ this.ctx.reactMessageStatus(name, m.chatId, m.messageId, "failed", m.threadId);
699
+ }
700
+ this.ctx.notifyInstanceTopic(name, t(reason === "unverifiable" ? "delivery.handoff_unverified_after_restart" : "delivery.handoff_not_taken", messages.length, name));
701
+ }, this.ctx.logger, `daemon.human_handoffs_unsettled[${name}]`));
655
702
  }
656
703
  attachIncidentHandlers(name, daemon) {
657
704
  daemon.on("capacity_nudge_cancelled", safeHandler((data) => {
@@ -937,7 +984,7 @@ export class InstanceLifecycle {
937
984
  // position, so the human must LOOK and pick the option explicitly — no
938
985
  // fixed key sequence; a known dialog whose auto-dismiss did not take gets
939
986
  // the keys we would have pressed.
940
- this.ctx.eventLog?.insert(name, "dialog_parked", { description: data.description, holdOnly: data.holdOnly });
987
+ this.ctx.eventLog?.insert(name, "dialog_parked", { description: data.description, holdOnly: data.holdOnly, ...this.screenForLog(name, data.screen) });
941
988
  this.ctx.logger.warn({ name, description: data.description, holdOnly: data.holdOnly }, "Instance is parked on a CLI dialog — not auto-answering");
942
989
  if (this.ctx.isPlannedRestart())
943
990
  return;
@@ -983,7 +1030,7 @@ export class InstanceLifecycle {
983
1030
  // #2822: a usage-limit hold began or ended — Needs you lists it while it lasts.
984
1031
  daemon.on("usage_hold", () => this.ctx.onAttentionChanged?.(name));
985
1032
  daemon.on("interactive_prompt", safeHandler(async (data) => {
986
- this.ctx.eventLog?.insert(name, "interactive_prompt", { kind: data.kind });
1033
+ this.ctx.eventLog?.insert(name, "interactive_prompt", { kind: data.kind, ...this.screenForLog(name, data.screen) });
987
1034
  this.ctx.logger.warn({ name, kind: data.kind, prompt: data.prompt }, "Instance is waiting for interactive terminal input");
988
1035
  if (this.ctx.isPlannedRestart()) {
989
1036
  this.ctx.logger.info({ name }, "Interactive prompt notification suppressed — planned restart in progress");
@@ -1338,6 +1385,7 @@ export class InstanceLifecycle {
1338
1385
  }
1339
1386
  /** Invalidate every start/wake already running for `name` (they will not publish). */
1340
1387
  invalidate(name) {
1388
+ this.ctx.agentCredentials?.invalidate(name);
1341
1389
  this.lifecycleEpochs.set(name, this.epochOf(name) + 1);
1342
1390
  }
1343
1391
  /** Whether `transition` is `name`'s transition and is the one running now. */
@@ -1431,10 +1479,18 @@ export class InstanceLifecycle {
1431
1479
  this.ctx.notifyInstanceTopic(name, t("inst.backend_removed", name, backendName, REMOVED_BACKENDS[backendName].replacement));
1432
1480
  return;
1433
1481
  }
1434
- // Verify backend binary is in PATH before spawning
1482
+ // Verify backend binary is in PATH before spawning. Off-loop resolve pins a
1483
+ // hint so createBackend's resolveBinary does not run synchronous `which`
1484
+ // (#1490 row 67). Missing still only logs; start continues.
1435
1485
  const installation = BACKEND_INSTALLATION_INFO[backendName];
1436
- if (installation && !checkBinaryInstalled(installation.binary)) {
1437
- this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1486
+ if (installation) {
1487
+ const { resolveBackendBinary } = await import("./backend-availability.js");
1488
+ const path = await resolveBackendBinary(backendName);
1489
+ noteResolvedBinary(installation.binary, path ?? installation.binary);
1490
+ check();
1491
+ if (!path) {
1492
+ this.ctx.logger.error({ binary: installation.binary, backend: backendName, instance: name }, `Backend binary "${installation.binary}" not found in PATH`);
1493
+ }
1438
1494
  }
1439
1495
  // claude-code refuses --dangerously-skip-permissions as root — fail fast
1440
1496
  // Unless IS_SANDBOX=1 is set (recognized sandbox environment)
@@ -1450,6 +1506,18 @@ export class InstanceLifecycle {
1450
1506
  backend: backendName,
1451
1507
  model: config.model ?? "default",
1452
1508
  }, this.ctx.spawnGate, this.ctx.stormWindow, this.ctx.backendOutage);
1509
+ // Register before start: a pre-launch refusal has no published daemon or later incident handlers.
1510
+ daemon.on("kiro_compatibility_hold", (message) => {
1511
+ const owner = this.daemons.get(name);
1512
+ if (owner ? owner !== daemon : this.epochOf(name) !== epoch)
1513
+ return;
1514
+ this.ctx.setKiroCompatibilityHold?.(name, message);
1515
+ });
1516
+ daemon.setAgentCredentialIssuer?.(token => {
1517
+ if (this.epochOf(name) !== epoch)
1518
+ throw new SupersededStartError(name);
1519
+ this.ctx.agentCredentials?.issue(name, token);
1520
+ });
1453
1521
  daemon.setDeliveryOutboxPort(this.ctx.deliveryOutbox ?? undefined);
1454
1522
  daemon.setWorkLeaseCheck(() => this.hasWorkLease(name));
1455
1523
  if (seedNow)
@@ -1543,6 +1611,7 @@ export class InstanceLifecycle {
1543
1611
  this.ctx.setTopicIcon(name, "green");
1544
1612
  this.ctx.touchActivity(name);
1545
1613
  });
1614
+ this.ctx.setKiroCompatibilityHold?.(name, null);
1546
1615
  this.attachIncidentHandlers(name, daemon);
1547
1616
  // #2897: a new daemon starts with no dialog held; a notice the last one left (a crash, a respawn) is out of date.
1548
1617
  this.ctx.resolveParkedNotice?.(name);
@@ -2304,7 +2373,8 @@ export class InstanceLifecycle {
2304
2373
  /** Handle delete_instance tool call from a daemon. */
2305
2374
  async handleDelete(args, respond) {
2306
2375
  const instanceName = args.name;
2307
- const deleteTopic = args.delete_topic ?? false;
2376
+ // The user's decision (beta.9): deleting an agent deletes its topic, unless `delete_topic: false` keeps it.
2377
+ const deleteTopic = args.delete_topic ?? true;
2308
2378
  const instanceConfig = this.ctx.fleetConfig?.instances[instanceName];
2309
2379
  if (!instanceConfig) {
2310
2380
  respond(null, `Instance not found: ${instanceName}`);
@@ -2314,11 +2384,9 @@ export class InstanceLifecycle {
2314
2384
  respond(null, "Cannot delete the General instance");
2315
2385
  return;
2316
2386
  }
2317
- if (deleteTopic && instanceConfig.topic_id) {
2318
- await this.ctx.deleteForumTopic(instanceConfig.topic_id);
2319
- }
2320
- await this.ctx.removeInstance(instanceName, authorizeExplicitInstanceRemoval("delete-instance-tool"));
2321
- respond({ success: true, name: instanceName, topic_deleted: deleteTopic });
2387
+ // topic_deleted is the platform's answer, never the request; when the topic stays, topic_reason says why.
2388
+ const topic = await this.ctx.removeInstanceAndTopic(instanceName, authorizeExplicitInstanceRemoval("delete-instance-tool"), { deleteTopic });
2389
+ respond({ success: true, name: instanceName, topic_deleted: topic.deleted, ...(topic.deleted ? {} : { topic_reason: topic.reason }) });
2322
2390
  }
2323
2391
  has(name) {
2324
2392
  return this.daemons.has(name);