@songsid/agend 2.1.12 → 2.2.0-alpha.2

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 (436) hide show
  1. package/README.md +4 -2
  2. package/README.zh-TW.md +4 -2
  3. package/dist/agent-cli.js +6 -1
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +68 -12
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-token-header.d.ts +13 -0
  8. package/dist/agent-token-header.js +16 -0
  9. package/dist/agent-token-header.js.map +1 -0
  10. package/dist/auth/one-time-code.d.ts +19 -0
  11. package/dist/auth/one-time-code.js +49 -0
  12. package/dist/auth/one-time-code.js.map +1 -0
  13. package/dist/auth-api.d.ts +52 -0
  14. package/dist/auth-api.js +346 -0
  15. package/dist/auth-api.js.map +1 -0
  16. package/dist/backend/antigravity.js +4 -3
  17. package/dist/backend/antigravity.js.map +1 -1
  18. package/dist/backend/claude-code.d.ts +4 -0
  19. package/dist/backend/claude-code.js +97 -0
  20. package/dist/backend/claude-code.js.map +1 -1
  21. package/dist/backend/codex.js +59 -30
  22. package/dist/backend/codex.js.map +1 -1
  23. package/dist/backend/grok.js +1 -1
  24. package/dist/backend/grok.js.map +1 -1
  25. package/dist/backend/kiro-agent.d.ts +66 -0
  26. package/dist/backend/kiro-agent.js +302 -0
  27. package/dist/backend/kiro-agent.js.map +1 -0
  28. package/dist/backend/kiro-identity.d.ts +80 -0
  29. package/dist/backend/kiro-identity.js +537 -0
  30. package/dist/backend/kiro-identity.js.map +1 -0
  31. package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
  32. package/dist/backend/kiro-v2-store-worker.js +87 -0
  33. package/dist/backend/kiro-v2-store-worker.js.map +1 -0
  34. package/dist/backend/kiro-v2-store.d.ts +54 -0
  35. package/dist/backend/kiro-v2-store.js +131 -0
  36. package/dist/backend/kiro-v2-store.js.map +1 -0
  37. package/dist/backend/kiro-v3-identity.d.ts +5 -0
  38. package/dist/backend/kiro-v3-identity.js +22 -0
  39. package/dist/backend/kiro-v3-identity.js.map +1 -1
  40. package/dist/backend/kiro.d.ts +90 -7
  41. package/dist/backend/kiro.js +500 -124
  42. package/dist/backend/kiro.js.map +1 -1
  43. package/dist/backend/muse.js +1 -1
  44. package/dist/backend/muse.js.map +1 -1
  45. package/dist/backend/types.d.ts +51 -3
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/cache-analysis.d.ts +92 -0
  48. package/dist/cache-analysis.js +147 -0
  49. package/dist/cache-analysis.js.map +1 -0
  50. package/dist/cache-ledger.d.ts +108 -0
  51. package/dist/cache-ledger.js +230 -0
  52. package/dist/cache-ledger.js.map +1 -0
  53. package/dist/cache-prices.d.ts +32 -0
  54. package/dist/cache-prices.js +73 -0
  55. package/dist/cache-prices.js.map +1 -0
  56. package/dist/cache-scan.d.ts +39 -0
  57. package/dist/cache-scan.js +115 -0
  58. package/dist/cache-scan.js.map +1 -0
  59. package/dist/cache-service.d.ts +115 -0
  60. package/dist/cache-service.js +417 -0
  61. package/dist/cache-service.js.map +1 -0
  62. package/dist/channel/adapters/discord.d.ts +1 -0
  63. package/dist/channel/adapters/discord.js +29 -10
  64. package/dist/channel/adapters/discord.js.map +1 -1
  65. package/dist/channel/adapters/telegram.d.ts +1 -0
  66. package/dist/channel/adapters/telegram.js +13 -1
  67. package/dist/channel/adapters/telegram.js.map +1 -1
  68. package/dist/channel/types.d.ts +9 -1
  69. package/dist/classic-channel-manager.d.ts +18 -10
  70. package/dist/classic-channel-manager.js +45 -18
  71. package/dist/classic-channel-manager.js.map +1 -1
  72. package/dist/cli-entry.d.ts +19 -0
  73. package/dist/cli-entry.js +36 -0
  74. package/dist/cli-entry.js.map +1 -0
  75. package/dist/cli.js +471 -146
  76. package/dist/cli.js.map +1 -1
  77. package/dist/command-table.d.ts +5 -3
  78. package/dist/command-table.js +29 -27
  79. package/dist/command-table.js.map +1 -1
  80. package/dist/config-validator.js +46 -0
  81. package/dist/config-validator.js.map +1 -1
  82. package/dist/config.js +12 -0
  83. package/dist/config.js.map +1 -1
  84. package/dist/connection-secrets.d.ts +6 -0
  85. package/dist/connection-secrets.js +4 -0
  86. package/dist/connection-secrets.js.map +1 -1
  87. package/dist/daemon.d.ts +95 -4
  88. package/dist/daemon.js +748 -111
  89. package/dist/daemon.js.map +1 -1
  90. package/dist/darwin-memory.d.ts +6 -1
  91. package/dist/darwin-memory.js +17 -6
  92. package/dist/darwin-memory.js.map +1 -1
  93. package/dist/delivery-consumed-watch.d.ts +85 -0
  94. package/dist/delivery-consumed-watch.js +244 -0
  95. package/dist/delivery-consumed-watch.js.map +1 -0
  96. package/dist/delivery-outbox.d.ts +61 -0
  97. package/dist/delivery-outbox.js +120 -2
  98. package/dist/delivery-outbox.js.map +1 -1
  99. package/dist/delivery-reconciliation.d.ts +14 -5
  100. package/dist/delivery-reconciliation.js +27 -4
  101. package/dist/delivery-reconciliation.js.map +1 -1
  102. package/dist/delivery-reconciliation.test.js +35 -1
  103. package/dist/delivery-reconciliation.test.js.map +1 -1
  104. package/dist/event-log.d.ts +4 -1
  105. package/dist/event-log.js +6 -3
  106. package/dist/event-log.js.map +1 -1
  107. package/dist/event-loop-watch.d.ts +28 -0
  108. package/dist/event-loop-watch.js +75 -3
  109. package/dist/event-loop-watch.js.map +1 -1
  110. package/dist/fleet-context.d.ts +22 -2
  111. package/dist/fleet-control-audit.d.ts +5 -0
  112. package/dist/fleet-control-audit.js +7 -0
  113. package/dist/fleet-control-audit.js.map +1 -1
  114. package/dist/fleet-level-config.d.ts +2 -2
  115. package/dist/fleet-level-config.js +28 -1
  116. package/dist/fleet-level-config.js.map +1 -1
  117. package/dist/fleet-manager.d.ts +339 -12
  118. package/dist/fleet-manager.js +2623 -461
  119. package/dist/fleet-manager.js.map +1 -1
  120. package/dist/full-restart.js +2 -3
  121. package/dist/full-restart.js.map +1 -1
  122. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
  123. package/dist/host-memory.d.ts +4 -0
  124. package/dist/host-memory.js.map +1 -1
  125. package/dist/install-lock.d.ts +49 -0
  126. package/dist/install-lock.js +200 -0
  127. package/dist/install-lock.js.map +1 -0
  128. package/dist/instance-config-impact.js +8 -2
  129. package/dist/instance-config-impact.js.map +1 -1
  130. package/dist/instance-lifecycle.d.ts +10 -5
  131. package/dist/instance-lifecycle.js +162 -53
  132. package/dist/instance-lifecycle.js.map +1 -1
  133. package/dist/locale.js +164 -16
  134. package/dist/locale.js.map +1 -1
  135. package/dist/login-controller.d.ts +2 -0
  136. package/dist/login-controller.js +17 -5
  137. package/dist/login-controller.js.map +1 -1
  138. package/dist/memory-pressure.d.ts +7 -8
  139. package/dist/memory-pressure.js +76 -15
  140. package/dist/memory-pressure.js.map +1 -1
  141. package/dist/monotonic-budget.d.ts +2 -0
  142. package/dist/monotonic-budget.js +28 -0
  143. package/dist/monotonic-budget.js.map +1 -0
  144. package/dist/needs-you-hub.d.ts +114 -0
  145. package/dist/needs-you-hub.js +370 -0
  146. package/dist/needs-you-hub.js.map +1 -0
  147. package/dist/needs-you-live.d.ts +116 -0
  148. package/dist/needs-you-live.js +228 -0
  149. package/dist/needs-you-live.js.map +1 -0
  150. package/dist/needs-you.d.ts +132 -0
  151. package/dist/needs-you.js +164 -0
  152. package/dist/needs-you.js.map +1 -0
  153. package/dist/node-version-guard.d.ts +30 -0
  154. package/dist/node-version-guard.js +50 -0
  155. package/dist/node-version-guard.js.map +1 -0
  156. package/dist/operator-control-paths.d.ts +4 -0
  157. package/dist/operator-control-paths.js +24 -0
  158. package/dist/operator-control-paths.js.map +1 -0
  159. package/dist/outbound-handlers.d.ts +2 -0
  160. package/dist/outbound-handlers.js +9 -2
  161. package/dist/outbound-handlers.js.map +1 -1
  162. package/dist/outbound-schemas.d.ts +5 -5
  163. package/dist/outbound-schemas.js +1 -1
  164. package/dist/outbound-schemas.js.map +1 -1
  165. package/dist/package-preimage.d.ts +26 -0
  166. package/dist/package-preimage.js +96 -0
  167. package/dist/package-preimage.js.map +1 -0
  168. package/dist/profile-control.js +4 -20
  169. package/dist/profile-control.js.map +1 -1
  170. package/dist/public-web-gateway.d.ts +18 -0
  171. package/dist/public-web-gateway.js +141 -0
  172. package/dist/public-web-gateway.js.map +1 -0
  173. package/dist/public-web-link.d.ts +53 -0
  174. package/dist/public-web-link.js +175 -0
  175. package/dist/public-web-link.js.map +1 -0
  176. package/dist/quickstart-api.d.ts +8 -1
  177. package/dist/quickstart-api.js +86 -48
  178. package/dist/quickstart-api.js.map +1 -1
  179. package/dist/quickstart.d.ts +1 -0
  180. package/dist/quickstart.js +16 -6
  181. package/dist/quickstart.js.map +1 -1
  182. package/dist/restart-guard.d.ts +52 -0
  183. package/dist/restart-guard.js +109 -0
  184. package/dist/restart-guard.js.map +1 -0
  185. package/dist/scheduler/db.d.ts +7 -1
  186. package/dist/scheduler/db.js +44 -2
  187. package/dist/scheduler/db.js.map +1 -1
  188. package/dist/scheduler/index.d.ts +1 -1
  189. package/dist/scheduler/retry-label.d.ts +11 -0
  190. package/dist/scheduler/retry-label.js +24 -0
  191. package/dist/scheduler/retry-label.js.map +1 -0
  192. package/dist/scheduler/scheduler.d.ts +55 -2
  193. package/dist/scheduler/scheduler.js +220 -8
  194. package/dist/scheduler/scheduler.js.map +1 -1
  195. package/dist/scheduler/types.d.ts +22 -0
  196. package/dist/scheduler/types.js.map +1 -1
  197. package/dist/secret-store.d.ts +5 -0
  198. package/dist/secret-store.js +20 -0
  199. package/dist/secret-store.js.map +1 -1
  200. package/dist/service-activation.d.ts +152 -0
  201. package/dist/service-activation.js +407 -0
  202. package/dist/service-activation.js.map +1 -0
  203. package/dist/service-installer.d.ts +63 -1
  204. package/dist/service-installer.js +141 -16
  205. package/dist/service-installer.js.map +1 -1
  206. package/dist/service-plan.d.ts +65 -0
  207. package/dist/service-plan.js +108 -0
  208. package/dist/service-plan.js.map +1 -0
  209. package/dist/service-restart-selection.d.ts +6 -5
  210. package/dist/service-restart-selection.js +6 -5
  211. package/dist/service-restart-selection.js.map +1 -1
  212. package/dist/settings-api.d.ts +10 -2
  213. package/dist/settings-api.js +216 -72
  214. package/dist/settings-api.js.map +1 -1
  215. package/dist/settings-baseline.d.ts +20 -0
  216. package/dist/settings-baseline.js +104 -0
  217. package/dist/settings-baseline.js.map +1 -0
  218. package/dist/settings-change.d.ts +21 -0
  219. package/dist/settings-change.js +161 -0
  220. package/dist/settings-change.js.map +1 -0
  221. package/dist/settings-confirmation.d.ts +105 -0
  222. package/dist/settings-confirmation.js +232 -0
  223. package/dist/settings-confirmation.js.map +1 -0
  224. package/dist/settings-control.d.ts +41 -0
  225. package/dist/settings-control.js +294 -0
  226. package/dist/settings-control.js.map +1 -0
  227. package/dist/settings-effect.d.ts +24 -0
  228. package/dist/settings-effect.js +169 -0
  229. package/dist/settings-effect.js.map +1 -0
  230. package/dist/settings-http-confirmation.d.ts +29 -0
  231. package/dist/settings-http-confirmation.js +123 -0
  232. package/dist/settings-http-confirmation.js.map +1 -0
  233. package/dist/settings-instance-patch.d.ts +4 -0
  234. package/dist/settings-instance-patch.js +26 -0
  235. package/dist/settings-instance-patch.js.map +1 -0
  236. package/dist/settings-request-capability.d.ts +16 -0
  237. package/dist/settings-request-capability.js +32 -0
  238. package/dist/settings-request-capability.js.map +1 -0
  239. package/dist/settings-transaction.d.ts +65 -0
  240. package/dist/settings-transaction.js +223 -0
  241. package/dist/settings-transaction.js.map +1 -0
  242. package/dist/setup-auth.d.ts +10 -18
  243. package/dist/setup-auth.js +14 -38
  244. package/dist/setup-auth.js.map +1 -1
  245. package/dist/setup-form.d.ts +1 -1
  246. package/dist/setup-form.js +75 -18
  247. package/dist/setup-form.js.map +1 -1
  248. package/dist/setup-guide.d.ts +13 -0
  249. package/dist/setup-guide.js +25 -0
  250. package/dist/setup-guide.js.map +1 -0
  251. package/dist/setup-host.d.ts +16 -0
  252. package/dist/setup-host.js +199 -35
  253. package/dist/setup-host.js.map +1 -1
  254. package/dist/setup-wizard.js +5 -1
  255. package/dist/setup-wizard.js.map +1 -1
  256. package/dist/slash-authz.d.ts +17 -3
  257. package/dist/slash-authz.js +5 -0
  258. package/dist/slash-authz.js.map +1 -1
  259. package/dist/spawn-gate.js +3 -3
  260. package/dist/spawn-gate.js.map +1 -1
  261. package/dist/sqlite-open-errors.d.ts +12 -0
  262. package/dist/sqlite-open-errors.js +13 -0
  263. package/dist/sqlite-open-errors.js.map +1 -0
  264. package/dist/statusline-watcher.d.ts +5 -0
  265. package/dist/statusline-watcher.js +2 -0
  266. package/dist/statusline-watcher.js.map +1 -1
  267. package/dist/steer-capability.d.ts +6 -0
  268. package/dist/steer-capability.js +8 -0
  269. package/dist/steer-capability.js.map +1 -1
  270. package/dist/sync-work-attribution.d.ts +14 -0
  271. package/dist/sync-work-attribution.js +43 -8
  272. package/dist/sync-work-attribution.js.map +1 -1
  273. package/dist/tips.js +10 -5
  274. package/dist/tips.js.map +1 -1
  275. package/dist/tmux-control.d.ts +57 -5
  276. package/dist/tmux-control.js +379 -61
  277. package/dist/tmux-control.js.map +1 -1
  278. package/dist/tmux-manager.d.ts +20 -6
  279. package/dist/tmux-manager.js +90 -47
  280. package/dist/tmux-manager.js.map +1 -1
  281. package/dist/tmux-read.d.ts +59 -0
  282. package/dist/tmux-read.js +213 -0
  283. package/dist/tmux-read.js.map +1 -0
  284. package/dist/topic-commands.d.ts +21 -10
  285. package/dist/topic-commands.js +205 -63
  286. package/dist/topic-commands.js.map +1 -1
  287. package/dist/transcript-delta-reader.js +2 -1
  288. package/dist/transcript-delta-reader.js.map +1 -1
  289. package/dist/tunnel/cloudflared-install.d.ts +2 -0
  290. package/dist/tunnel/cloudflared-install.js +1 -1
  291. package/dist/tunnel/cloudflared-install.js.map +1 -1
  292. package/dist/tunnel/purpose-lane.d.ts +14 -0
  293. package/dist/tunnel/purpose-lane.js +43 -0
  294. package/dist/tunnel/purpose-lane.js.map +1 -0
  295. package/dist/turn-reply-guard.d.ts +2 -0
  296. package/dist/turn-reply-guard.js +10 -1
  297. package/dist/turn-reply-guard.js.map +1 -1
  298. package/dist/types.d.ts +33 -0
  299. package/dist/ui/app.html +18 -0
  300. package/dist/ui/chat-commands.js +43 -0
  301. package/dist/ui/chat-render.js +478 -0
  302. package/dist/ui/chat-store.js +318 -0
  303. package/dist/ui/chat-strings.js +89 -0
  304. package/dist/ui/chat-thread.js +246 -0
  305. package/dist/ui/chat-tour.js +116 -0
  306. package/dist/ui/fleet-cache.js +168 -0
  307. package/dist/ui/fleet-org.js +142 -0
  308. package/dist/ui/panel-chat.js +504 -0
  309. package/dist/ui/panel-fleet.js +427 -0
  310. package/dist/ui/panel-needs.js +202 -0
  311. package/dist/ui/panel-settings.js +754 -0
  312. package/dist/ui/preview-panel.js +202 -0
  313. package/dist/ui/preview.js +270 -0
  314. package/dist/ui/settings-apply.js +239 -0
  315. package/dist/ui/settings-confirm.js +186 -0
  316. package/dist/ui/settings-dialogs.js +546 -0
  317. package/dist/ui/settings-model.js +277 -0
  318. package/dist/ui/settings-strings.js +654 -0
  319. package/dist/ui/settings-wizard.js +140 -0
  320. package/dist/ui/shared/agend-auth.js +93 -0
  321. package/dist/ui/shared/app-ctx.js +80 -0
  322. package/dist/ui/shared/app-html.js +9 -0
  323. package/dist/ui/shared/app-i18n.js +95 -0
  324. package/dist/ui/shared/app-nav.js +73 -0
  325. package/dist/ui/shared/app-needs.js +114 -0
  326. package/dist/ui/shared/app-route.js +73 -0
  327. package/dist/ui/shared/app-session.js +66 -0
  328. package/dist/ui/shared/app-shell.js +346 -0
  329. package/dist/ui/shared/app-store.js +51 -0
  330. package/dist/ui/shared/app-stream.js +201 -0
  331. package/dist/ui/shared/app.css +820 -0
  332. package/dist/ui/shared/app.js +88 -0
  333. package/dist/ui/shared/fonts/OFL.txt +92 -0
  334. package/dist/ui/shared/fonts/README.md +21 -0
  335. package/dist/ui/shared/fonts/inter.woff2 +0 -0
  336. package/dist/ui/shared/htm.module.js +1 -0
  337. package/dist/ui/shared/panel-view.js +655 -0
  338. package/dist/ui/shared/preact-hooks.module.js +2 -0
  339. package/dist/ui/shared/preact.module.js +2 -0
  340. package/dist/ui/shared/signin.js +142 -0
  341. package/dist/ui/shared/theme.js +29 -0
  342. package/dist/ui/shared/tokens.css +153 -0
  343. package/dist/ui/shared/ui-confirm.js +43 -0
  344. package/dist/ui/shared/ui-dialog.js +45 -0
  345. package/dist/ui/shared/ui-icons.js +63 -0
  346. package/dist/ui/shared/ui-menu.js +39 -0
  347. package/dist/ui/shared/ui-states.js +20 -0
  348. package/dist/ui/shared/ui-toast.js +18 -0
  349. package/dist/ui/shared/vendor/htm.LICENSE +202 -0
  350. package/dist/ui/shared/vendor/preact.LICENSE +21 -0
  351. package/dist/ui/shared/vendor/vendor.json +39 -0
  352. package/dist/ui/shared/view-strings.js +309 -0
  353. package/dist/ui/signin.html +52 -0
  354. package/dist/update-check.d.ts +6 -2
  355. package/dist/update-check.js +12 -3
  356. package/dist/update-check.js.map +1 -1
  357. package/dist/update-dispatch.d.ts +28 -0
  358. package/dist/update-dispatch.js +73 -0
  359. package/dist/update-dispatch.js.map +1 -0
  360. package/dist/update-install.d.ts +129 -0
  361. package/dist/update-install.js +262 -0
  362. package/dist/update-install.js.map +1 -0
  363. package/dist/upgrade-notices.d.ts +17 -0
  364. package/dist/upgrade-notices.js +93 -0
  365. package/dist/upgrade-notices.js.map +1 -0
  366. package/dist/usage/statusline-usage.d.ts +2 -0
  367. package/dist/usage/statusline-usage.js +14 -10
  368. package/dist/usage/statusline-usage.js.map +1 -1
  369. package/dist/view-api.d.ts +6 -1
  370. package/dist/view-api.js +62 -51
  371. package/dist/view-api.js.map +1 -1
  372. package/dist/web-api.d.ts +85 -14
  373. package/dist/web-api.js +557 -77
  374. package/dist/web-api.js.map +1 -1
  375. package/dist/web-auth.d.ts +71 -27
  376. package/dist/web-auth.js +169 -69
  377. package/dist/web-auth.js.map +1 -1
  378. package/dist/web-body.d.ts +3 -0
  379. package/dist/web-body.js +55 -0
  380. package/dist/web-body.js.map +1 -0
  381. package/dist/web-channel-echo.d.ts +9 -0
  382. package/dist/web-channel-echo.js +55 -0
  383. package/dist/web-channel-echo.js.map +1 -0
  384. package/dist/web-chat-history.d.ts +125 -0
  385. package/dist/web-chat-history.js +184 -0
  386. package/dist/web-chat-history.js.map +1 -0
  387. package/dist/web-commands.d.ts +77 -0
  388. package/dist/web-commands.js +81 -0
  389. package/dist/web-commands.js.map +1 -0
  390. package/dist/web-continuation.d.ts +7 -0
  391. package/dist/web-continuation.js +22 -0
  392. package/dist/web-continuation.js.map +1 -0
  393. package/dist/web-host-guard.d.ts +34 -0
  394. package/dist/web-host-guard.js +57 -1
  395. package/dist/web-host-guard.js.map +1 -1
  396. package/dist/web-login.d.ts +72 -0
  397. package/dist/web-login.js +132 -0
  398. package/dist/web-login.js.map +1 -0
  399. package/dist/web-org.d.ts +55 -0
  400. package/dist/web-org.js +67 -0
  401. package/dist/web-org.js.map +1 -0
  402. package/dist/web-preview.d.ts +77 -0
  403. package/dist/web-preview.js +240 -0
  404. package/dist/web-preview.js.map +1 -0
  405. package/dist/web-request-context.d.ts +10 -0
  406. package/dist/web-request-context.js +13 -0
  407. package/dist/web-request-context.js.map +1 -0
  408. package/dist/web-session.d.ts +167 -0
  409. package/dist/web-session.js +389 -0
  410. package/dist/web-session.js.map +1 -0
  411. package/dist/web-shell-routes.d.ts +59 -0
  412. package/dist/web-shell-routes.js +96 -0
  413. package/dist/web-shell-routes.js.map +1 -0
  414. package/dist/web-upload.d.ts +183 -0
  415. package/dist/web-upload.js +468 -0
  416. package/dist/web-upload.js.map +1 -0
  417. package/dist/website-tips-renderer.d.ts +2 -2
  418. package/dist/website-tips-renderer.js +3 -3
  419. package/dist/website-tips-renderer.js.map +1 -1
  420. package/launcher/agend +110 -0
  421. package/launcher/agend-agent +110 -0
  422. package/launcher/agend-agent.cjs +4 -0
  423. package/launcher/agend.cjs +4 -0
  424. package/launcher/install-admission.cjs +105 -0
  425. package/launcher/launch.cjs +64 -0
  426. package/launcher/postinstall.cjs +114 -0
  427. package/launcher/run-in-process.cjs +13 -0
  428. package/launcher/runtime-platform.cjs +86 -0
  429. package/launcher/runtime-select.cjs +217 -0
  430. package/package.json +18 -7
  431. package/scripts/preinstall-guard.cjs +55 -0
  432. package/templates/launchd.plist.ejs +2 -2
  433. package/templates/systemd.service.ejs +1 -1
  434. package/dist/ui/dashboard.html +0 -774
  435. package/dist/ui/settings.html +0 -2165
  436. package/dist/ui/view.html +0 -1139
@@ -1,4 +1,6 @@
1
1
  import { measureSyncWork } from "./sync-work-attribution.js";
2
+ import { LOOPBACK_HOST_NAMES } from "./web-host-guard.js";
3
+ import { WEB_REMOTE_DOCS_URL } from "./upgrade-notices.js";
2
4
  import { readFileSync, existsSync } from "node:fs";
3
5
  import { exec, execFileSync } from "node:child_process";
4
6
  import { promisify } from "node:util";
@@ -11,14 +13,15 @@ import { formatCents } from "./cost-guard.js";
11
13
  import { truncateDisplay, MODEL_DISPLAY_WIDTH_MAX } from "./ls-rows.js";
12
14
  import { getTmuxSocketName, getTmuxSessionName } from "./paths.js";
13
15
  import { t, getLocale } from "./locale.js";
14
- import { telegramMenu } from "./command-table.js";
16
+ import { commandSpec, decideCommand, telegramMenu } from "./command-table.js";
15
17
  import { runVisibilityCommand } from "./cross-instance-notice.js";
16
18
  import { clampContextPercent, parseContextPercent, parseTokenContextRatio, } from "./context-percent.js";
17
19
  import { isGeneralInstance } from "./general-instance.js";
18
- import { backendSupportsSteer } from "./steer-capability.js";
20
+ import { instanceSupportsSteer } from "./steer-capability.js";
19
21
  import { SYSINFO_BACKEND_IDS } from "./backend/types.js";
20
22
  import { recordInternalRequest, withOrigin } from "./fleet-control-audit.js";
21
- import { UPDATE_COMMAND } from "./update-check.js";
23
+ import { resolveInstalledAgend, updateCommand } from "./update-dispatch.js";
24
+ import { selfCommand } from "./cli-entry.js";
22
25
  export { parseContextPercent, parseTokenContextRatio } from "./context-percent.js";
23
26
  /** Longest one setMyCommands call may take. */
24
27
  const TELEGRAM_COMMANDS_TIMEOUT_MS = 10_000;
@@ -455,12 +458,89 @@ export function resolveInstanceContext(dataDir, instanceName, backend, opts) {
455
458
  refreshPaneContext(instanceName, backend);
456
459
  return hit ? { context: hit.context, tokenRatio: hit.tokenRatio } : { context: null, tokenRatio: null };
457
460
  }
461
+ const PROFILE_RE = /^\/profile(?:@\w+)?(?:\s+([\s\S]*))?$/;
462
+ const RESTART_RE = /^\/restart(?:@[A-Za-z0-9_]*)?(?:\s+(.*))?$/i;
463
+ const LEGACY_INSTALL_RE = /^\/install[-_]cli(?:@\S+)?(?:\s+([\s\S]*))?$/;
464
+ const VISIBILITY_RE = /^\/visibility(?:@\S+)?(?:\s+([\s\S]*))?$/i;
465
+ /** `/name` or `/name@bot`. */
466
+ const bare = (name) => (text) => text === `/${name}` || text.startsWith(`/${name}@`);
467
+ /** `/name`, `/name@bot`, or `/name <args>`. */
468
+ const withArgs = (name) => (text) => bare(name)(text) || text.startsWith(`/${name} `);
469
+ /**
470
+ * The typed forms each dispatcher runs, in its order. The one recognizer for both the command-table gate and the
471
+ * dispatch (#1399 review): a form no handler runs (`/STATUS`, `/status report`, `/pause one two`) is text for the
472
+ * agent, and the table never answers it either.
473
+ */
474
+ const GENERAL_FORMS = [
475
+ ["profile", (text, source) => source === "telegram" && PROFILE_RE.test(text)],
476
+ ["status", bare("status")],
477
+ ["restart", text => RESTART_RE.test(text)],
478
+ ["sysinfo", text => bare("sysinfo")(text) || text === "/sys-info" || text === "/sys_info"],
479
+ ["doctor", bare("doctor")],
480
+ ["usage", bare("usage")],
481
+ ["tips", withArgs("tips")],
482
+ ["login", withArgs("login")],
483
+ ["install-cli", text => LEGACY_INSTALL_RE.test(text)],
484
+ ["update", bare("update")],
485
+ ["dashboard", withArgs("dashboard")],
486
+ ["visibility", text => VISIBILITY_RE.test(text)],
487
+ ];
488
+ const INSTANCE_FORMS = [
489
+ ["tips", withArgs("tips")],
490
+ ["pause", text => parsePauseWakeCommand(text)?.action === "pause"],
491
+ ["wake", text => parsePauseWakeCommand(text)?.action === "wake"],
492
+ ["collab", bare("collab")],
493
+ ["effort", withArgs("effort")],
494
+ ["model", withArgs("model")],
495
+ ["compact", text => parseCompactCommand(text) !== null],
496
+ ["steer", withArgs("steer")],
497
+ ["btw", withArgs("btw")],
498
+ ["clear", bare("clear")],
499
+ ["cancel", bare("cancel")],
500
+ ["save", withArgs("save")],
501
+ ["raw", text => text === "/raw" || text.startsWith("/raw ")],
502
+ ["ctx", bare("ctx")],
503
+ ];
504
+ const typedCommand = (forms, text, source) => forms.find(([, matches]) => matches(text, source))?.[0];
505
+ /** A typed name that is another spelling of a command-table entry. */
506
+ const TELEGRAM_COMMAND_ALIASES = { "install-cli": "login" };
458
507
  export class TopicCommands {
459
508
  ctx;
460
509
  constructor(ctx) {
461
510
  this.ctx = ctx;
462
511
  }
463
512
  /** Get the adapter that should reply to a given inbound message */
513
+ /**
514
+ * #754: who may run a typed Telegram command is decided by the command table — the same rule a Discord slash
515
+ * command goes through (`decideCommand`), here with its Telegram column — before any handler runs. `msg.adapterId`
516
+ * is the topic's owning adapter (ownedCopy), so the admin it asks about is that bot's. A command the table does not
517
+ * know (`/raw`) or does not handle here (a passthrough cell) is left to the handlers below. `command` is what the
518
+ * dispatcher recognized (GENERAL_FORMS / INSTANCE_FORMS), so the table judges exactly what would run.
519
+ *
520
+ * Synchronous on purpose (#1399 review): the decision, the handler's own checks and the command's effect run in one
521
+ * stretch, so the instance cannot be rebound to another bot between the check and the act. The refusal, if any.
522
+ */
523
+ tableRefusal(msg, command, scope) {
524
+ const spec = command ? commandSpec(TELEGRAM_COMMAND_ALIASES[command] ?? command) : undefined;
525
+ if (!spec)
526
+ return null;
527
+ const fleetAdmin = () => this.ctx.isFleetAdmin(msg.userId, msg.adapterId) ? "ok"
528
+ : this.ctx.hasFleetAdmins && !this.ctx.hasFleetAdmins(msg.adapterId) ? "disabled" : "denied";
529
+ const decision = decideCommand(spec, scope, {
530
+ fleetAdmin,
531
+ // In a fleet topic the channel's own admin IS the owning bot's fleet admin.
532
+ channelAdmin: () => fleetAdmin() === "ok",
533
+ classicAdmin: () => false,
534
+ }, "telegram");
535
+ if (decision.allow || "passthrough" in decision)
536
+ return null;
537
+ return decision.reply;
538
+ }
539
+ async sendRefusal(msg, [key, ...args]) {
540
+ const adapter = this.getReplyAdapter(msg);
541
+ if (adapter)
542
+ await adapter.sendText(msg.chatId, t(key, ...args), { threadId: msg.threadId }).catch(() => { });
543
+ }
464
544
  getReplyAdapter(msg) {
465
545
  if (msg.adapterId && this.ctx.adapters) {
466
546
  return this.ctx.adapters.get(msg.adapterId) ?? this.ctx.adapter;
@@ -495,47 +575,50 @@ export class TopicCommands {
495
575
  const text = msg.text?.trim();
496
576
  if (!text)
497
577
  return false;
498
- const profile = msg.source === "telegram" ? text.match(/^\/profile(?:@\w+)?(?:\s+([\s\S]*))?$/) : null;
499
- if (profile) {
500
- await this.ctx.runProfileCommand?.(msg, profile[1]?.trim());
578
+ const command = typedCommand(GENERAL_FORMS, text, msg.source);
579
+ const refusal = this.tableRefusal(msg, command, "general");
580
+ if (refusal) {
581
+ await this.sendRefusal(msg, refusal);
501
582
  return true;
502
583
  }
503
- if (text === "/status" || text === "/status@" || text.startsWith("/status@")) {
584
+ if (command === "profile") {
585
+ await this.ctx.runProfileCommand?.(msg, text.match(PROFILE_RE)[1]?.trim());
586
+ return true;
587
+ }
588
+ if (command === "status") {
504
589
  await this.handleStatusCommand(msg);
505
590
  return true;
506
591
  }
507
- const restart = text.match(/^\/restart(?:@[A-Za-z0-9_]*)?(?:\s+(.*))?$/i);
508
- if (restart) {
509
- const mode = restart[1]?.trim().toLowerCase();
592
+ if (command === "restart") {
593
+ const mode = text.match(RESTART_RE)[1]?.trim().toLowerCase();
510
594
  await this.handleRestartCommand(msg, mode);
511
595
  return true;
512
596
  }
513
- if (text === "/sysinfo" || text === "/sysinfo@" || text.startsWith("/sysinfo@")
514
- || text === "/sys-info" || text === "/sys_info") {
597
+ if (command === "sysinfo") {
515
598
  await this.handleSysInfoCommand(msg);
516
599
  return true;
517
600
  }
518
- if (text === "/doctor" || text.startsWith("/doctor@")) {
601
+ if (command === "doctor") {
519
602
  await this.handleDoctorCommand(msg);
520
603
  return true;
521
604
  }
522
- if (text === "/usage" || text.startsWith("/usage@")) {
605
+ if (command === "usage") {
523
606
  await this.handleUsageCommand(msg);
524
607
  return true;
525
608
  }
526
- if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
609
+ if (command === "tips") {
527
610
  await this.handleTipsCommand(msg);
528
611
  return true;
529
612
  }
530
- if (text === "/login" || text.startsWith("/login ") || text.startsWith("/login@")) {
613
+ if (command === "login") {
531
614
  await this.handleLoginCommand(msg);
532
615
  return true;
533
616
  }
534
617
  // `/install-cli` became part of `/login` (#1131). Typed, it still works for
535
618
  // one release (2.1.10) — with a line saying where it went — and is in no
536
619
  // command menu. Both spellings: Telegram command names cannot contain "-".
537
- const legacyInstall = text.match(/^\/install[-_]cli(?:@\S+)?(?:\s+([\s\S]*))?$/);
538
- if (legacyInstall) {
620
+ if (command === "install-cli") {
621
+ const legacyInstall = text.match(LEGACY_INSTALL_RE);
539
622
  const adapter = this.getReplyAdapter(msg);
540
623
  if (adapter)
541
624
  await adapter.sendText(msg.chatId, t("login.install_cli_moved"), { threadId: msg.threadId }).catch(() => { });
@@ -544,50 +627,48 @@ export class TopicCommands {
544
627
  await this.handleLoginCommand({ ...msg, text: mapped });
545
628
  return true;
546
629
  }
547
- if (text === "/update" || text.startsWith("/update@")) {
630
+ if (command === "update") {
548
631
  await this.handleUpdateCommand(msg);
549
632
  return true;
550
633
  }
551
- if (text === "/dashboard" || text.startsWith("/dashboard@")) {
634
+ if (command === "dashboard") {
552
635
  await this.handleDashboardCommand(msg);
553
636
  return true;
554
637
  }
555
- const visibility = text.match(/^\/visibility(?:@\S+)?(?:\s+([\s\S]*))?$/i);
556
- if (visibility) {
557
- await this.handleVisibilityCommand(msg, visibility[1] ?? "");
638
+ if (command === "visibility") {
639
+ await this.handleVisibilityCommand(msg, text.match(VISIBILITY_RE)[1] ?? "");
558
640
  return true;
559
641
  }
560
642
  return false;
561
643
  }
562
644
  /**
563
- * Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
564
- * URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
565
- * Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
566
- * in a shared topic (the caller must send with format: "html"). /view is
567
- * public, so it's never spoilered. DC uses the plain form (ephemeral reply).
645
+ * Pure, secret-free dashboard/menu copy. Issuance belongs to the private
646
+ * delivery action; rendering or previewing this text cannot retire a code.
568
647
  */
569
- getDashboardText(htmlSpoiler = false) {
648
+ getDashboardText(_htmlSpoiler = false, showRemoteHint = true) {
570
649
  const port = this.ctx.fleetConfig?.health_port ?? 19280;
571
650
  const host = this.ctx.fleetConfig?.hostname || "localhost";
572
651
  const access = this.ctx.getDashboardAccess?.();
573
652
  if (!access?.ready || !access.token)
574
653
  return t("dashboard.starting");
575
- const token = access.token;
576
654
  const base = `http://${host}:${port}`;
577
- const hide = (u) => htmlSpoiler ? `<tg-spoiler>${u}</tg-spoiler>` : u;
578
655
  return [
579
656
  t("dashboard.title"),
580
657
  "",
581
- `• View: ${base}/view`,
582
- `• View (edit): ${hide(`${base}/view?token=${token}`)}`,
583
- `• Settings: ${hide(`${base}/settings?token=${token}`)}`,
584
- `• Web UI: ${hide(`${base}/ui?token=${token}`)}`,
658
+ t("dashboard.signin", base),
659
+ "",
660
+ `• View: ${base}/view`,
661
+ `• Dashboard: ${base}/ui`,
662
+ `• Settings: ${base}/settings`,
663
+ "",
664
+ t("dashboard.code_help"),
665
+ // #1366: a loopback address does not open on a phone — say where the way in is documented.
666
+ ...(showRemoteHint && (LOOPBACK_HOST_NAMES.includes(host.toLowerCase()) || host === "::1") ? ["", t("dashboard.remote_hint", WEB_REMOTE_DOCS_URL)] : []),
585
667
  ].join("\n");
586
668
  }
587
669
  /**
588
- * /dashboard (TG): admin-only. Replies directly in the topic; the token-
589
- * bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
590
- * the clear (the adapter supports plain/HTML, not MarkdownV2's `||…||`).
670
+ * /dashboard (TG): admin-only menu in General; codes are sent privately.
671
+ * `/dashboard revoke` also closes the public link and signs browsers out.
591
672
  */
592
673
  async handleDashboardCommand(msg) {
593
674
  const adapter = this.getReplyAdapter(msg);
@@ -604,7 +685,17 @@ export class TopicCommands {
604
685
  await adapter.sendText(chatId, t("not_authorized"), { threadId });
605
686
  return;
606
687
  }
607
- await adapter.sendText(chatId, this.getDashboardText(true), { threadId, format: "html" });
688
+ const arg = (msg.text ?? "").trim().replace(/^\/dashboard(?:@\S+)?/i, "").trim().toLowerCase();
689
+ if (arg === "revoke") {
690
+ const result = this.ctx.revokeWebSessions?.() ?? { count: 0, durable: true };
691
+ // Not durable: they are signed out now, but a restart may bring them back — say so, never "done".
692
+ await adapter.sendText(chatId, result.durable ? t("dashboard.revoked", result.count) : t("dashboard.revoked_not_durable", result.count), { threadId });
693
+ return;
694
+ }
695
+ if (msg.source === "telegram")
696
+ await this.ctx.dashboardMenu?.(msg);
697
+ else
698
+ await adapter.sendText(chatId, this.getDashboardText(), { threadId });
608
699
  }
609
700
  /** Handle /ctx or /compact in any instance topic — returns true if handled */
610
701
  async handleInstanceCommand(msg, instanceName) {
@@ -615,15 +706,25 @@ export class TopicCommands {
615
706
  const text = msg.text?.trim();
616
707
  if (!text)
617
708
  return false;
709
+ const inGeneral = !!this.ctx.fleetConfig?.instances[instanceName]?.general_topic;
710
+ // A General command typed in an instance topic is recognized too, in its General forms only: the table points it
711
+ // to General (#1148) instead of handing it to the agent. No branch below runs it.
712
+ const command = typedCommand(INSTANCE_FORMS, text, msg.source)
713
+ ?? (inGeneral ? undefined : typedCommand(GENERAL_FORMS, text, msg.source));
714
+ const refusal = this.tableRefusal(msg, command, inGeneral ? "general" : "fleet");
715
+ if (refusal) {
716
+ await this.sendRefusal(msg, refusal);
717
+ return true;
718
+ }
618
719
  // Tips are informational and should appear where requested, including a
619
720
  // worker topic. This also keeps Telegram text commands aligned with
620
721
  // Discord's channel-local slash-command behavior.
621
- if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
722
+ if (command === "tips") {
622
723
  await this.handleTipsCommand(msg);
623
724
  return true;
624
725
  }
625
- const pauseWake = parsePauseWakeCommand(text);
626
- if (pauseWake) {
726
+ if (command === "pause" || command === "wake") {
727
+ const pauseWake = parsePauseWakeCommand(text);
627
728
  const adapter = this.getReplyAdapter(msg);
628
729
  if (!adapter)
629
730
  return false;
@@ -631,7 +732,7 @@ export class TopicCommands {
631
732
  await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
632
733
  return true;
633
734
  }
634
- const isGeneral = !!this.ctx.fleetConfig?.instances[instanceName]?.general_topic;
735
+ const isGeneral = inGeneral;
635
736
  if (isGeneral && !pauseWake.instance) {
636
737
  await adapter.sendText(msg.chatId, t(`${pauseWake.action}.usage`), { threadId: msg.threadId });
637
738
  return true;
@@ -641,6 +742,11 @@ export class TopicCommands {
641
742
  await adapter.sendText(msg.chatId, t("instance.not_found", target), { threadId: msg.threadId });
642
743
  return true;
643
744
  }
745
+ // #754 audit: a General speaks for its own bot's instances only — an admin of this bot is not one of another's.
746
+ if (isGeneral && this.ctx.getInstanceAdapterId && this.ctx.getInstanceAdapterId(target) !== msg.adapterId) {
747
+ await adapter.sendText(msg.chatId, t("instance.other_bot", target), { threadId: msg.threadId });
748
+ return true;
749
+ }
644
750
  if (pauseWake.action === "pause" && isGeneralInstance(this.ctx.fleetConfig, target)) {
645
751
  await adapter.sendText(msg.chatId, t("general.pause_forbidden"), { threadId: msg.threadId });
646
752
  return true;
@@ -648,17 +754,22 @@ export class TopicCommands {
648
754
  await adapter.sendText(msg.chatId, await this.runPauseWake(target, pauseWake.action), { threadId: msg.threadId });
649
755
  return true;
650
756
  }
651
- if (text === "/collab" || text.startsWith("/collab@")) {
757
+ if (command === "collab") {
652
758
  const adapter = this.getReplyAdapter(msg);
653
759
  if (!adapter)
654
760
  return false;
761
+ // Channel-admin, as the Discord slash command (#754 audit): it changes how the instance is reached.
762
+ if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
763
+ await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
764
+ return true;
765
+ }
655
766
  const isCollab = this.ctx.toggleFleetCollab(instanceName);
656
767
  await adapter.sendText(msg.chatId, isCollab
657
768
  ? t("collab.on")
658
769
  : t("collab.off"), { threadId: msg.threadId });
659
770
  return true;
660
771
  }
661
- if (text === "/effort" || text.startsWith("/effort ") || text.startsWith("/effort@")) {
772
+ if (command === "effort") {
662
773
  const adapter = this.getReplyAdapter(msg);
663
774
  if (!adapter)
664
775
  return false;
@@ -674,13 +785,13 @@ export class TopicCommands {
674
785
  }
675
786
  else if (this.ctx.promptEffortMenu) {
676
787
  // No arg → inline keyboard menu (TG), same shape as /model.
677
- const fallback = await this.ctx.promptEffortMenu(instanceName, msg.userId, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId);
788
+ const fallback = await this.ctx.promptEffortMenu(instanceName, msg.userId, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId, msg.adapterId);
678
789
  if (fallback)
679
790
  await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
680
791
  }
681
792
  return true;
682
793
  }
683
- if (text === "/model" || text.startsWith("/model ") || text.startsWith("/model@")) {
794
+ if (command === "model") {
684
795
  const adapter = this.getReplyAdapter(msg);
685
796
  if (!adapter)
686
797
  return false;
@@ -695,7 +806,7 @@ export class TopicCommands {
695
806
  }
696
807
  else if (this.ctx.promptModelMenu) {
697
808
  // No arg → inline keyboard menu (TG)
698
- const fallback = await this.ctx.promptModelMenu(instanceName, msg.userId, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId);
809
+ const fallback = await this.ctx.promptModelMenu(instanceName, msg.userId, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId, msg.adapterId);
699
810
  if (fallback)
700
811
  await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
701
812
  }
@@ -704,16 +815,21 @@ export class TopicCommands {
704
815
  }
705
816
  return true;
706
817
  }
707
- const compact = parseCompactCommand(text);
708
- if (compact) {
818
+ if (command === "compact") {
819
+ const compact = parseCompactCommand(text);
709
820
  const adapter = this.getReplyAdapter(msg);
710
821
  if (!adapter)
711
822
  return false;
823
+ // Channel-admin, as the Discord slash command (#754 audit): it rewrites the instance's context.
824
+ if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
825
+ await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
826
+ return true;
827
+ }
712
828
  const result = await this.sendCompact(instanceName, compact.instructions);
713
829
  await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
714
830
  return true;
715
831
  }
716
- if (text === "/steer" || text.startsWith("/steer ") || text.startsWith("/steer@")) {
832
+ if (command === "steer") {
717
833
  const adapter = this.getReplyAdapter(msg);
718
834
  if (!adapter)
719
835
  return false;
@@ -730,7 +846,7 @@ export class TopicCommands {
730
846
  await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
731
847
  return true;
732
848
  }
733
- if (text === "/btw" || text.startsWith("/btw ") || text.startsWith("/btw@")) {
849
+ if (command === "btw") {
734
850
  const adapter = this.getReplyAdapter(msg);
735
851
  if (!adapter)
736
852
  return false;
@@ -746,7 +862,7 @@ export class TopicCommands {
746
862
  await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
747
863
  return true;
748
864
  }
749
- if (text === "/clear" || text.startsWith("/clear@")) {
865
+ if (command === "clear") {
750
866
  const adapter = this.getReplyAdapter(msg);
751
867
  if (!adapter)
752
868
  return false;
@@ -763,7 +879,7 @@ export class TopicCommands {
763
879
  await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
764
880
  return true;
765
881
  }
766
- if (text === "/cancel" || text.startsWith("/cancel@")) {
882
+ if (command === "cancel") {
767
883
  const adapter = this.getReplyAdapter(msg);
768
884
  if (!adapter)
769
885
  return false;
@@ -771,10 +887,15 @@ export class TopicCommands {
771
887
  await adapter.sendText(msg.chatId, ok ? t("cancel.sent", instanceName) : t("cancel.not_running", instanceName), { threadId: msg.threadId });
772
888
  return true;
773
889
  }
774
- if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
890
+ if (command === "save") {
775
891
  const adapter = this.getReplyAdapter(msg);
776
892
  if (!adapter)
777
893
  return false;
894
+ // Channel-admin, as the Discord slash command (#754 audit): it writes a file in the instance's directory.
895
+ if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
896
+ await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
897
+ return true;
898
+ }
778
899
  const filename = parseSaveFilename(text);
779
900
  if (!filename) {
780
901
  await adapter.sendText(msg.chatId, t("save.usage"), { threadId: msg.threadId });
@@ -788,7 +909,17 @@ export class TopicCommands {
788
909
  await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
789
910
  return true;
790
911
  }
791
- if (text !== "/ctx" && !text.startsWith("/ctx@"))
912
+ // `/raw <text>` is pasted into the CLI as typed, with no [user:] envelope (daemon.ts): it is CLI input, so only
913
+ // the owning bot's fleet admin may send it (#754 audit). An admin's falls through to delivery unchanged.
914
+ if (command === "raw") {
915
+ if (this.ctx.isFleetAdmin(msg.userId, msg.adapterId))
916
+ return false;
917
+ const adapter = this.getReplyAdapter(msg);
918
+ if (adapter)
919
+ await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
920
+ return true;
921
+ }
922
+ if (command !== "ctx")
792
923
  return false;
793
924
  const adapter = this.getReplyAdapter(msg);
794
925
  if (!adapter)
@@ -946,14 +1077,17 @@ export class TopicCommands {
946
1077
  * Backends whose TUI accepts a busy-pane paste as steering input,
947
1078
  * live-verified: claude-code and codex buffer-then-submit at the turn
948
1079
  * boundary, grok accepts it in its input line. kiro's legacy TUI swallows
949
- * the paste outright, and opencode/antigravity are unverified — for those
1080
+ * the paste outright; its TUI front-ends steer, so kiro answers per launch
1081
+ * (#1405). opencode/antigravity are unverified — for those
950
1082
  * the user gets an honest "not supported" instead of a silent queue
951
1083
  * fallback that looks like a steer but behaves like a normal message.
952
1084
  */
953
1085
  sendSteer(instanceName, content, msg) {
954
1086
  const backend = this.effectiveBackend(instanceName);
955
- if (!backendSupportsSteer(backend)) {
956
- return t("steer.unsupported", backend);
1087
+ const launchSupportsSteer = this.ctx.instanceLaunchSupportsSteer?.(instanceName);
1088
+ if (!instanceSupportsSteer(backend, launchSupportsSteer)) {
1089
+ // A backend that answers per launch (kiro, #1405) can steer, just not as this instance runs now.
1090
+ return launchSupportsSteer === false ? t("steer.unsupported_launch", backend) : t("steer.unsupported", backend);
957
1091
  }
958
1092
  const ipc = this.ctx.instanceIpcClients.get(instanceName);
959
1093
  if (!ipc?.connected)
@@ -1402,12 +1536,18 @@ export class TopicCommands {
1402
1536
  }
1403
1537
  const sent = await adapter.sendText(chatId, t("update.progress.preparing", 0), { threadId });
1404
1538
  this.ctx.beginUpdateProgress?.(adapter, chatId, threadId, sent.messageId);
1405
- // The CLI picks the channel from the installed version it replaces; see UPDATE_COMMAND.
1406
- const updateCmd = UPDATE_COMMAND;
1539
+ // The CLI picks the channel from the installed version it replaces; see UPDATE_COMMAND. #1450 C5: the INSTALLED
1540
+ // agend, verified by identity, by absolute path — or a refusal, never whatever `agend` is first on PATH.
1541
+ const installed = await resolveInstalledAgend();
1542
+ if (!installed.ok) {
1543
+ this.ctx.failUpdateProgress?.(`/update cannot verify the installed AgEnD (${installed.reason}). Run \`agend update\` from a shell.`);
1544
+ return;
1545
+ }
1407
1546
  const { spawn } = await import("node:child_process");
1408
1547
  const origin = `command /update by ${msg.adapterId}:${msg.userId}`;
1409
1548
  recordInternalRequest(this.ctx.dataDir, "update", origin);
1410
- const child = spawn("sh", ["-c", `sleep 2 && ${updateCmd}`], {
1549
+ const { command, args } = updateCommand(installed.agend);
1550
+ const child = spawn(command, args, {
1411
1551
  detached: true, stdio: "ignore", env: withOrigin(origin),
1412
1552
  });
1413
1553
  child.once("error", err => this.ctx.failUpdateProgress?.(err.message));
@@ -1447,7 +1587,9 @@ export class TopicCommands {
1447
1587
  try {
1448
1588
  const { execFile } = await import("node:child_process");
1449
1589
  const { promisify } = await import("node:util");
1450
- const { stdout } = await promisify(execFile)("agend", ["backend", "doctor", backend], {
1590
+ // This CLI on this Node, not whichever `agend` is first on PATH (#1450 C5).
1591
+ const doctor = selfCommand(["backend", "doctor", backend]);
1592
+ const { stdout } = await promisify(execFile)(doctor.command, doctor.args, {
1451
1593
  timeout: 30_000,
1452
1594
  encoding: "utf-8",
1453
1595
  });