@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
@@ -0,0 +1,504 @@
1
+ // #1408 step 1: the chat panel. Behind the session gate (/ui/js/), loaded by the app with a dynamic import in "full"
2
+ // mode only. boot() runs once per page (the chat store must hear the stream from the first frame); <ChatPanel> mounts
3
+ // for /ui and /ui/chat/<name>.
4
+ //
5
+ // The look follows the design (§2): the conversation in a centred column, the person's messages as bubbles on the
6
+ // right, the agent's at full width, and one rounded composer pinned below with attach, Stop reply and Send inside it.
7
+ // The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
8
+ // that apply to its state are offered (#1408 rough edges 4 and 7).
9
+ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
10
+ import { t } from "/assets/app-i18n.js";
11
+ import { appStore, useStore } from "/assets/app-store.js";
12
+ import { useLease } from "/assets/app-ctx.js";
13
+ import { PanelHeader, onPanelKey, setTitle, addFooterItem, statusClass, statusLabel, openDrawer } from "/assets/app-shell.js";
14
+ import { navigate } from "/assets/app-nav.js";
15
+ import { Menu } from "/assets/ui-menu.js";
16
+ import { Dialog } from "/assets/ui-dialog.js";
17
+ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
18
+ import { Icon } from "/assets/ui-icons.js";
19
+ import { toast } from "/assets/ui-toast.js";
20
+ import { confirmDialog } from "/assets/ui-confirm.js";
21
+ import "./chat-strings.js";
22
+ import { confirmedWrite } from "./settings-confirm.js";
23
+ import { createChatStore } from "./chat-store.js";
24
+ import { createThread, msgKey } from "./chat-thread.js";
25
+ import { PreviewPanel, openPanel, panelStore, shownKey } from "./preview-panel.js";
26
+ import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
27
+ import { installTour, refreshTourSpot, startTour } from "./chat-tour.js";
28
+
29
+ const R = () => globalThis.AgendChatRender;
30
+ const P = () => globalThis.AgendPreview;
31
+
32
+ /** The page's chat store (boot creates it). Exported for the tests. */
33
+ export let store = null;
34
+
35
+ // ── Announcements: one polite, visually hidden line for the coarse events only (a reply arrived, the agent started,
36
+ // finished, or waits on you). Messages themselves are not read out as they arrive; the list is a log to browse.
37
+ let announceTimer = null;
38
+ function announce(text) {
39
+ let el = document.getElementById("announcer");
40
+ if (!el) { el = document.createElement("div"); el.id = "announcer"; el.className = "sr-only"; el.setAttribute("role", "status"); el.setAttribute("aria-live", "polite"); el.setAttribute("aria-atomic", "true"); document.body.appendChild(el); }
41
+ if (!text) return;
42
+ el.textContent = "";
43
+ clearTimeout(announceTimer);
44
+ announceTimer = setTimeout(() => { el.textContent = text; }, 60);
45
+ }
46
+
47
+ /**
48
+ * This device's preview opt-in: asks once (the app's dialog) and says what it means; turning it off stops every
49
+ * running preview. Resolves to the choice in force.
50
+ */
51
+ export async function setPreviewOptIn(on) {
52
+ if (on && !P().optedIn() && !(await confirmDialog({ message: t("chat.pvConfirm"), confirmLabel: t("chat.pvAllowButton") }))) on = false;
53
+ P().setOptIn(on);
54
+ return P().optedIn();
55
+ }
56
+
57
+ /** Once per page, in "full" mode: the store on the app's stream, previews (#1306), the sidebar's chat controls. */
58
+ export function boot({ stream, boot: bootData, deps = {} }) {
59
+ if (store) return store;
60
+ store = createChatStore({
61
+ fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog, ...deps,
62
+ });
63
+ store.attach(stream);
64
+ // #1306: what the server said about previews for this load. The page-wide message and storage listeners this adds
65
+ // belong to the app, once (they are never per mount).
66
+ if (P() && bootData) P().init(bootData);
67
+ addFooterItem("previews", PreviewOptIn);
68
+ addFooterItem("tour", TourButton);
69
+ installTour();
70
+ return store;
71
+ }
72
+
73
+ function PreviewOptIn() {
74
+ const [on, setOn] = useState(P() ? P().optedIn() : false);
75
+ useEffect(() => (P() ? P().onChange(() => setOn(P().optedIn())) : undefined), []);
76
+ return html`<label class="side-row toggle"><input type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} />
77
+ <span>${t("chat.previews")}</span></label>`;
78
+ }
79
+ function TourButton() {
80
+ return html`<button type="button" id="tourBtn" class="side-row" title=${t("chat.tourBtnTitle")} onClick=${startTour}><${Icon} name="info" /><span>${t("chat.tour")}</span></button>`;
81
+ }
82
+
83
+ // ── Code wrap: this browser's choice (agend_code_wrap). On a phone, code wraps whatever it is (app.css).
84
+ const stored = (k) => { try { return localStorage.getItem(k); } catch { return null; } };
85
+ let codeWrap = stored("agend_code_wrap") === "1";
86
+ const wrapListeners = new Set();
87
+ function toggleWrap() {
88
+ codeWrap = !codeWrap;
89
+ try { localStorage.setItem("agend_code_wrap", codeWrap ? "1" : "0"); } catch { /* this page only */ }
90
+ for (const fn of wrapListeners) fn(codeWrap);
91
+ }
92
+
93
+ async function copyText(text) {
94
+ try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } } catch { /* fall back */ }
95
+ const ta = document.createElement("textarea"); ta.value = text; ta.setAttribute("readonly", ""); ta.className = "offscreen";
96
+ document.body.appendChild(ta); ta.select();
97
+ let ok = false; try { ok = document.execCommand("copy"); } catch { ok = false; }
98
+ ta.remove();
99
+ return ok;
100
+ }
101
+ /** The HTML as a file: a blob of the source as application/octet-stream, saved as reply.html — never opened. */
102
+ function downloadHtml(code) {
103
+ const url = URL.createObjectURL(new Blob([code], { type: "application/octet-stream" }));
104
+ const a = document.createElement("a"); a.href = url; a.download = "reply.html"; a.className = "offscreen";
105
+ document.body.appendChild(a); a.click(); a.remove();
106
+ URL.revokeObjectURL(url);
107
+ }
108
+
109
+ /** Re-render when the store changes for `name` (or for everyone). `kind` "msgs" is the thread's alone. */
110
+ function useChatTick(name, kinds) {
111
+ const [, tick] = useState(0);
112
+ useEffect(() => store.subscribe((instance, kind) => {
113
+ if ((instance === null || instance === name) && (!kinds || kinds.includes(kind || "state"))) tick(n => n + 1);
114
+ }), [name]);
115
+ }
116
+
117
+ // ── The panel ──
118
+
119
+ export function ChatPanel({ route, navKey }) {
120
+ const lease = useLease(navKey);
121
+ const app = useStore(appStore);
122
+ const name = route.instance;
123
+ const inst = name ? app.instances.find(i => i.name === name) : null;
124
+ useEffect(() => {
125
+ store.setCurrent(name && inst ? name : null);
126
+ return () => store.setCurrent(null);
127
+ }, [name, !!inst]);
128
+ useEffect(() => { setTitle(name || t("app.chat")); }, [name, navKey]);
129
+ if (!name) {
130
+ return html`<div class="panel p-chat"><${PanelHeader} title=${t("app.chat")} />
131
+ <div class="panel-body center"><${Empty} icon="chat" title=${t("chat.pick")} hint=${t("chat.pickHint")}
132
+ action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} /></div></div>`;
133
+ }
134
+ if (!app.ready) return html`<div class="panel p-chat"><${PanelHeader} title=${name} /><div class="panel-body"><${Skeleton} lines=${5} /></div></div>`;
135
+ if (!inst) return html`<${NotFound} name=${name} />`;
136
+ return html`<${ChatView} key=${name} name=${name} inst=${inst} lease=${lease} exec=${app.exec[name]}
137
+ awaiting=${Object.prototype.hasOwnProperty.call(app.awaiting, name) ? app.awaiting[name] : null} />`;
138
+ }
139
+
140
+ function NotFound({ name }) {
141
+ // A remembered chat that no longer exists is forgotten, so /ui does not come back here.
142
+ useEffect(() => { try { if (localStorage.getItem("agend_last_instance") === name) localStorage.removeItem("agend_last_instance"); } catch { /* none */ } }, [name]);
143
+ return html`<div class="panel p-chat"><${PanelHeader} title=${name} />
144
+ <div class="panel-body center"><${Empty} icon="alert" title=${t("chat.notFound", name)} hint=${t("chat.notFoundHint")}
145
+ action=${html`<button type="button" class="btn only-narrow" onClick=${openDrawer}>${t("chat.backToList")}</button>`} /></div></div>`;
146
+ }
147
+
148
+ function ChatView({ name, inst, lease, exec, awaiting }) {
149
+ const [dialog, setDialog] = useState(null); // "details" | "delete" | null
150
+ const [wrap, setWrap] = useState(codeWrap);
151
+ const view = useRef(null), split = useRef(null), thread = useRef(null);
152
+ useEffect(() => { wrapListeners.add(setWrap); return () => wrapListeners.delete(setWrap); }, []);
153
+ // First visit to this instance's chat on this page: its history (a navigation — it counts as use). Back again: nothing.
154
+ useEffect(() => { store.openHistory(name, lease); }, [name]);
155
+ useEffect(() => { try { localStorage.setItem("agend_last_instance", name); } catch { /* none */ } }, [name]);
156
+ // Files dragged over the chat show where they go; a drop attaches them (counted, so moving over messages does not flicker).
157
+ useEffect(() => {
158
+ const el = view.current; if (!el) return undefined;
159
+ let depth = 0;
160
+ const hasFiles = (e) => !!(e.dataTransfer && [...e.dataTransfer.types].includes("Files"));
161
+ lease.on(el, "dragenter", (e) => { if (hasFiles(e)) { depth++; el.classList.add("dragging"); } });
162
+ lease.on(el, "dragover", (e) => { if (hasFiles(e)) e.preventDefault(); });
163
+ lease.on(el, "dragleave", (e) => { if (hasFiles(e) && --depth <= 0) { depth = 0; el.classList.remove("dragging"); } });
164
+ lease.on(el, "drop", (e) => { depth = 0; el.classList.remove("dragging"); if (e.dataTransfer && e.dataTransfer.files.length) { e.preventDefault(); store.addFiles(name, e.dataTransfer.files); } });
165
+ return undefined;
166
+ }, [lease]);
167
+ // Esc stops the agent's reply (as Esc in its terminal would): from the chat, while it works, and only when nothing
168
+ // else that Esc would close is open (a dialog, a menu, the Session menu).
169
+ useEffect(() => lease.hold(onPanelKey((e) => {
170
+ if (e.key !== "Escape" || e.isComposing || e.defaultPrevented) return;
171
+ if (!R().isBusy(store.state.exec[name]) || store.state.stopping[name] || store.state.cancelling[name]) return;
172
+ if (document.querySelector("dialog[open], .menu-list, .pop")) return;
173
+ const active = document.activeElement;
174
+ if (active && active !== document.body && !(view.current && view.current.contains(active))) return;
175
+ e.preventDefault();
176
+ store.cancelReply(name);
177
+ })), [lease]);
178
+ const running = inst.status === "running";
179
+ const action = async (verb) => {
180
+ let r;
181
+ try { const res = await fetch(`/ui/${verb}/${encodeURIComponent(name)}`, { method: "POST", headers: { "Content-Type": "application/json" } }); r = await res.json(); }
182
+ catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
183
+ if (r && r.error) toast(r.error, false);
184
+ else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
185
+ };
186
+ const items = [
187
+ { key: "details", label: t("chat.details"), icon: "info", onSelect: () => setDialog("details") },
188
+ running ? null : { key: "start", label: t("chat.start"), icon: "play", onSelect: () => action("start") },
189
+ running ? { key: "restart", label: t("chat.restart"), icon: "restart", onSelect: () => action("restart") } : null,
190
+ running ? { key: "stop", label: t("chat.stopInstance"), icon: "stop", onSelect: () => action("stop") } : null,
191
+ { key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
192
+ ];
193
+ const cls = statusClass(inst, exec, awaiting);
194
+ // #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
195
+ const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
196
+ const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
197
+ ${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst.model)} aria-label=${t("chat.chipModel", inst.model)} onClick=${() => pick("model")}>${inst.model}</button>` : null}
198
+ ${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
199
+ return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
200
+ <${PanelHeader} title=${name} sub=${sub}><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
201
+ <div class="chat-split" ref=${split}>
202
+ <div class="chat-main">
203
+ <${Thread} name=${name} th=${thread} />
204
+ <${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} />
205
+ </div>
206
+ <${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
207
+ keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} />
208
+ </div>
209
+ <div class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
210
+ ${dialog === "details" ? html`<${DetailsDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
211
+ ${dialog === "delete" ? html`<${DeleteDialog} name=${name} onClose=${() => setDialog(null)} />` : null}
212
+ </div>`;
213
+ }
214
+
215
+ /** The thread: Preact owns the elements, the keyed renderer owns what is inside the list (chat-thread.js). */
216
+ function Thread({ name, th }) {
217
+ const scroller = useRef(null), list = useRef(null);
218
+ const [jump, setJump] = useState({ show: false, unseen: 0 });
219
+ const [empty, setEmpty] = useState(false);
220
+ useLayoutEffect(() => {
221
+ const thread = createThread(list.current, scroller.current, {
222
+ t, tf: t, isUser: (x) => store.isUser(x, name), onJump: setJump, onEmpty: setEmpty,
223
+ setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
224
+ panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o) }, // #1481
225
+ });
226
+ th.current = thread;
227
+ thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
228
+ const off = store.subscribe((instance, kind) => {
229
+ if (instance !== name) return;
230
+ if (kind === "msgs") thread.render(store.state.msgs[name] || []);
231
+ else if (kind === "sent") thread.jumpLatest();
232
+ });
233
+ const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
234
+ const offPanel = panelStore.subscribe(() => thread.refreshCards()); // a card shows whether the panel has its block
235
+ const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
236
+ const sc = scroller.current, ls = list.current;
237
+ sc.addEventListener("scroll", onScroll, { passive: true });
238
+ ls.addEventListener("click", thread.onClick);
239
+ return () => {
240
+ off(); offPv(); offPanel();
241
+ sc.removeEventListener("scroll", onScroll);
242
+ ls.removeEventListener("click", thread.onClick);
243
+ thread.dispose(); // the whole thread goes: every preview in it stops first
244
+ th.current = null;
245
+ };
246
+ }, [name]);
247
+ return html`<div class="scroller" ref=${scroller}>
248
+ <div class="thread" ref=${list} role="log" aria-live="off" aria-label=${t("chat.conversationWith", name)}></div>
249
+ ${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} hint=${t("chat.sendFirst")} />` : null}
250
+ ${jump.show ? html`<button type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
251
+ <${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}
252
+ </div>`;
253
+ }
254
+
255
+ /** Everything under the thread: prompts, the working line, a failed send, files waiting, and the composer. */
256
+ function Dock({ name, inst, lease, exec, awaiting }) {
257
+ useChatTick(name, ["state"]);
258
+ const s = store.state;
259
+ return html`<div class="dock"><div class="dock-col">
260
+ <${Prompts} name=${name} />
261
+ <${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} />
262
+ <${CommandCard} name=${name} lease=${lease} />
263
+ <${QuickActions} name=${name} inst=${inst} lease=${lease} />
264
+ ${s.failedSends[name] ? html`<div class="failed-send" role="alert"><span>${t("chat.notSent")}</span><span class="txt">${s.failedSends[name]}</span>
265
+ <button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
266
+ <button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
267
+ <${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
268
+ <p class="hint">${t("chat.composerHint")}</p>
269
+ </div></div>`;
270
+ }
271
+
272
+ /** #1269: what the last chat command answered — its text, why it was refused, or a list to choose from. */
273
+ function CommandCard({ name, lease }) {
274
+ const c = store.state.commands[name];
275
+ if (!c) return null;
276
+ const run = (args) => store.runCommand(name, c.command, args, { alive: () => lease.current() });
277
+ return html`<div class=${`cmd-card${c.error ? " bad" : ""}`} role=${c.error ? "alert" : "status"}>
278
+ <div class="cmd-head"><span class="cmd-name mono">/${c.command}</span>
279
+ ${c.busy ? html`<span class="wait">${t("chat.cmdRunning", c.command)}</span>` : html`<button type="button" class="icon-btn" title=${t("chat.cmdDismiss")}
280
+ aria-label=${t("chat.cmdDismiss")} onClick=${() => store.dismissCommand(name)}><${Icon} name="close" size=${14} /></button>`}</div>
281
+ ${c.error ? html`<p class="cmd-text">${c.error}</p>` : null}
282
+ ${c.text ? html`<pre class="cmd-text">${c.text}</pre>` : null}
283
+ ${c.choices ? html`<div class="cmd-choices" role="group" aria-label=${t("chat.cmdChoose")}>${c.choices.options.map((o) => html`<button key=${o.id} type="button"
284
+ class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined} onClick=${() => run(o.id)}>${o.label}</button>`)}</div>` : null}
285
+ </div>`;
286
+ }
287
+
288
+ /** #1269 quick actions: Compact and Clear… once the instance's context is 70% used (from the status it already has). */
289
+ function QuickActions({ name, inst, lease }) {
290
+ const pct = inst && typeof inst.context_pct === "number" ? Math.round(inst.context_pct) : null;
291
+ if (pct == null || pct < 70) return null;
292
+ const busy = !!(store.state.commands[name] && store.state.commands[name].busy);
293
+ const run = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
294
+ return html`<div class="quick-actions" role="group" aria-label=${t("chat.chipContext", pct)}>
295
+ <span class="qa-ctx">${t("chat.chipContext", pct)}</span>
296
+ <button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => run("compact")}>${t("chat.chipCompact")}</button>
297
+ <button type="button" class="btn btn-sm" disabled=${busy} onClick=${() => run("clear")}>${t("chat.chipClear")}</button>
298
+ </div>`;
299
+ }
300
+
301
+ function Prompts({ name }) {
302
+ const list = store.promptsFor(name);
303
+ if (!list.length) return null;
304
+ return html`<div class="prompts" aria-live="polite">${list.map(p => html`<div key=${p.nonce} class=${`prompt${p.resolved ? " done" : ""}`} role="group" aria-label=${p.text}>
305
+ <div class="txt">${p.resolved ? (p.outcome || t("chat.promptGone")) : p.text}</div>
306
+ ${p.resolved ? null : html`<div class="acts">${p.actions.map(a => html`<button key=${a.id} type="button" class="btn" disabled=${!!p.busy} onClick=${() => store.answerPrompt(p, a.id)}>${a.label}</button>`)}
307
+ ${p.busy ? html`<span class="wait">${t("chat.promptAnswering")}</span>` : null}</div>`}</div>`)}</div>`;
308
+ }
309
+
310
+ /** "<name> is working… 0:42", "looks stuck", "Stopping…", or what it waits on at its terminal. Text only. */
311
+ function WorkBar({ name, lease, exec, awaiting }) {
312
+ const s = store.state;
313
+ const busy = R().isBusy(exec) ? exec : "";
314
+ const kind = awaiting != null ? "awaiting" : s.stopping[name] && busy ? "stopping" : busy;
315
+ const [, tick] = useState(0);
316
+ useEffect(() => {
317
+ if (!kind || kind === "awaiting") return undefined;
318
+ const h = lease.interval(() => tick(n => n + 1), 1000); // ticks only while it shows, cleared with the lease
319
+ return () => lease.clear(h);
320
+ }, [kind, lease]);
321
+ if (!kind) return null;
322
+ const label = t(kind === "stuck" ? "chat.stuck" : kind === "stopping" ? "chat.stopping" : kind === "awaiting" ? "chat.awaiting" : "chat.working", name);
323
+ const since = s.workingSince[name];
324
+ return html`<div class=${`work-bar ${kind}`}><span class="pulse" aria-hidden="true"></span><span class="lbl">${label}</span>
325
+ ${kind === "awaiting" ? html`<span class="note" title=${t("chat.approxNote")}>${awaiting || t("chat.approxNote")}</span>`
326
+ : html`<span class="elapsed" title=${t("chat.elapsedTitle")} aria-hidden="true">${since == null ? "" : R().formatElapsed(performance.now() - since)}</span>`}</div>`;
327
+ }
328
+
329
+ function Composer({ name, lease, busy }) {
330
+ const s = store.state;
331
+ const input = useRef(null), fileIn = useRef(null);
332
+ const [, tick] = useState(0);
333
+ const [sel, setSel] = useState(0);
334
+ const [closedFor, setClosedFor] = useState(null); // the draft the palette was closed on (Esc); it opens again on change
335
+ const value = s.drafts[name] || "";
336
+ const files = s.pendingFiles[name] || [];
337
+ const content = !!value.trim() || files.length > 0;
338
+ // A file waiting: what goes is a message with its file, never a command (no palette, no command line).
339
+ const palette = files.length || closedFor === value ? null : paletteFor(value);
340
+ const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
341
+ // Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300).
342
+ useLayoutEffect(() => { const el = input.current; if (!el) return; el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 240)}px`; }, [value]);
343
+ useEffect(() => { if (input.current) input.current.focus(); }, [name]);
344
+ useEffect(() => { refreshTourSpot(); });
345
+ const setValue = (v) => { store.setDraft(name, v); setSel(0); tick(n => n + 1); };
346
+ // #1269: a command line runs the command (POST /ui/command); anything else — "/" included — is sent as before.
347
+ // The draft is consumed only by a command that starts: one still out keeps the new line in the composer (#1476 review).
348
+ const runLine = (cmd) => {
349
+ if (!store.commandFree(name)) return;
350
+ setValue("");
351
+ store.runCommand(name, cmd.command, cmd.args, { alive: () => lease.current() });
352
+ };
353
+ const complete = (c) => setValue(`/${c.name}${c.arg ? " " : ""}`);
354
+ const submit = () => {
355
+ if (pick) {
356
+ if (value === `/${pick.name}` && !needsArgument(pick.name)) runLine({ command: pick.name, args: "" });
357
+ else complete(pick);
358
+ return;
359
+ }
360
+ const cmd = files.length ? null : parseCommandLine(value);
361
+ if (cmd && needsArgument(cmd.command) && !cmd.args) { setValue(`/${cmd.command} `); return; }
362
+ if (cmd) { runLine(cmd); return; }
363
+ store.send(name);
364
+ };
365
+ const onKeyDown = (e) => {
366
+ if (palette && palette.length && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
367
+ e.preventDefault();
368
+ setSel((n) => (Math.min(n, palette.length - 1) + (e.key === "ArrowDown" ? 1 : palette.length - 1)) % palette.length);
369
+ return;
370
+ }
371
+ if (pick && e.key === "Tab" && !e.shiftKey) { e.preventDefault(); complete(pick); return; }
372
+ if (palette && e.key === "Escape" && !e.isComposing) { e.preventDefault(); setClosedFor(value); return; }
373
+ if (R().composerKey(e) === "send") { e.preventDefault(); submit(); }
374
+ };
375
+ const onInput = (e) => { setValue(e.target.value); };
376
+ // Files in a paste are attached; a very long text paste goes as a text file — only once it is really attached.
377
+ const onPaste = (e) => {
378
+ const list = e.clipboardData && e.clipboardData.files;
379
+ const text = e.clipboardData ? e.clipboardData.getData("text") : "";
380
+ if (list && list.length) { store.addFiles(name, list); if (!text) e.preventDefault(); return; }
381
+ if (R().isLongPaste(text) && store.attachPastedText(name, text)) e.preventDefault();
382
+ };
383
+ const optId = (c) => `cmd-${c.name}`;
384
+ return html`<div class="composer-box">
385
+ ${palette ? html`<ul class="cmd-palette" id="cmdPalette" role="listbox" aria-label=${t("chat.cmdCommands")}>
386
+ ${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
387
+ class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
388
+ <span class="cmd-name mono">/${c.name}${c.arg ? html` <span class="cmd-arg">${c.arg}</span>` : null}</span><span class="cmd-desc">${t(`chat.cmd_${c.name}`)}</span></li>`)
389
+ : html`<li class="cmd-none" role="option" aria-selected="false">${t("chat.cmdNotCommand")}</li>`}
390
+ </ul>` : null}
391
+ ${files.length ? html`<div class="pending-files">${files.map((f, i) => html`<${FileChip} key=${`${f.name}-${i}`} f=${f} i=${i} name=${name} />`)}</div>` : null}
392
+ <div class="composer">
393
+ <button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
394
+ <input ref=${fileIn} type="file" multiple hidden accept="image/png,image/jpeg,image/gif,image/webp,application/pdf,text/*,.md,.csv,.json,.log,.yaml,.yml"
395
+ onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
396
+ <textarea id="msgIn" ref=${input} rows="1" value=${value} placeholder=${t("chat.composerPlaceholder", name)} aria-label=${t("chat.composerPlaceholder", name)}
397
+ autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
398
+ aria-activedescendant=${pick ? optId(pick) : undefined} onInput=${onInput} onKeyDown=${onKeyDown} onPaste=${onPaste}></textarea>
399
+ <button id="stopBtn" type="button" class="btn btn-stop" hidden=${!busy} title=${t("chat.stopReplyTitle")}
400
+ disabled=${!!s.cancelling[name]} onClick=${() => store.cancelReply(name)}><${Icon} name="stop" size=${14} /><span class="stop-label">${t("chat.stopReply")}</span></button>
401
+ <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content || !!s.sending[name]}
402
+ aria-label=${t("chat.send")} title=${t("chat.send")} onClick=${submit}><${Icon} name="send" size=${16} /><span class="send-label">${t("chat.send")}</span></button>
403
+ </div></div>`;
404
+ }
405
+
406
+ function FileChip({ f, i, name }) {
407
+ const img = useRef(null);
408
+ const isImage = /^image\//.test(f.type) && typeof URL.createObjectURL === "function";
409
+ useEffect(() => {
410
+ if (!isImage || !img.current) return undefined;
411
+ const url = URL.createObjectURL(f);
412
+ img.current.src = url;
413
+ return () => URL.revokeObjectURL(url);
414
+ }, [f]);
415
+ return html`<span class="file-chip">
416
+ ${isImage ? html`<img ref=${img} alt="" />` : html`<span class="ic"><${Icon} name="file" size=${16} /></span>`}
417
+ <span class="nm">${f.name}</span><span class="sz">${R().formatSize(f.size)}</span>
418
+ ${store.isPasted(f) ? html`<button type="button" class="as-text" onClick=${() => store.fileBackAsText(name, i)}>${t("chat.asText")}</button>` : null}
419
+ <button type="button" class="chip-x" title=${t("chat.removeFile")} aria-label=${`${t("chat.removeFile")} ${f.name}`} onClick=${() => store.removeFile(name, i)}><${Icon} name="close" size=${14} /></button></span>`;
420
+ }
421
+
422
+ // ── Dialogs ──
423
+
424
+ function DetailsDialog({ name, onClose }) {
425
+ const lease = useLease(`details:${name}`);
426
+ const [d, setD] = useState(null);
427
+ const [failed, setFailed] = useState(false);
428
+ const body = useRef(null);
429
+ useEffect(() => {
430
+ (async () => {
431
+ try {
432
+ const r = await lease.fetch(`/ui/instance/${encodeURIComponent(name)}`, { headers: { "Content-Type": "application/json" } });
433
+ if (!lease.current()) return;
434
+ const j = await r.json();
435
+ if (!lease.current()) return;
436
+ setD(j);
437
+ } catch { if (lease.current()) setFailed(true); }
438
+ })();
439
+ }, [lease]);
440
+ // Bars are sized after they are drawn, through the CSSOM (never a style attribute).
441
+ useLayoutEffect(() => { if (body.current) for (const f of body.current.querySelectorAll(".progress-fill[data-pct]")) f.style.width = `${f.dataset.pct}%`; });
442
+ const src = (s) => (s === "instance" ? ` ${t("chat.configured")}` : s === "fleet-default" ? ` ${t("chat.fleetDefault")}` : "");
443
+ const bar = (pct, level) => html`<span class="progress-bar"><span class=${`progress-fill${level ? ` ${level}` : ""}`} data-pct=${Math.max(0, Math.min(Number(pct) || 0, 100))}></span></span>`;
444
+ const row = (k, v) => html`<div class="kv"><span class="k">${k}</span><span class="v">${v}</span></div>`;
445
+ let content;
446
+ if (failed) content = html`<${ErrorState} message=${t("chat.loadFailed")} />`;
447
+ else if (!d) content = html`<${Skeleton} lines=${6} />`;
448
+ else {
449
+ const sl = d.statusline || {}, cost = sl.cost?.total_cost_usd ?? 0, ctx = d.context_pct;
450
+ const rl5 = sl.rate_limits?.five_hour?.used_percentage ?? 0, rl7 = sl.rate_limits?.seven_day?.used_percentage ?? 0;
451
+ content = html`<section class="card"><h3>${t("chat.dInstance")}</h3>
452
+ ${row(t("chat.dName"), d.name)}${row(t("chat.dStatus"), d.status)}${row(t("chat.dDisplay"), d.display_name || "--")}
453
+ ${row(t("chat.dDescription"), d.description || "--")}${row(t("chat.dDirectory"), html`<span class="mono">${d.working_directory}</span>`)}</section>
454
+ <section class="card"><h3>${t("chat.dRuntime")}</h3>
455
+ ${row(t("chat.dBackend"), d.backend || "--")}
456
+ ${row(t("chat.dModel"), `${d.model || sl.model?.display_name || "--"}${d.model_source === "live" ? "" : src(d.model_source)}`)}
457
+ ${d.effort ? row(t("chat.dEffort"), `${d.effort}${src(d.effort_source)}`) : null}
458
+ ${row(t("chat.dCost"), `$${cost.toFixed(2)} ${t("chat.dCostNote")}`)}
459
+ ${row(t("chat.dContext"), html`<span class="pct">${ctx != null ? `${Math.round(ctx)}%` : "--"}</span>${ctx != null ? bar(ctx, ctx > 80 ? "warn" : "") : null}`)}
460
+ ${row(t("chat.dRate5"), html`<span class="pct">${Math.round(rl5)}%</span>${bar(rl5, rl5 > 90 ? "error" : rl5 > 70 ? "warn" : "")}`)}
461
+ ${row(t("chat.dRate7"), html`<span class="pct">${Math.round(rl7)}%</span>${bar(rl7, rl7 > 90 ? "error" : rl7 > 70 ? "warn" : "")}`)}</section>
462
+ ${d.recent_activity?.length ? html`<section class="card"><h3>${t("chat.dActivity")}</h3>${d.recent_activity.map((a, i) => html`<div key=${i} class="activity">
463
+ <span class="mono muted">${a.timestamp ? new Date(`${a.timestamp.replace(" ", "T")}Z`).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""}</span>
464
+ <span class="ev">${a.event}</span> ${a.summary || ""}</div>`)}</section>` : null}`;
465
+ }
466
+ return html`<${Dialog} title=${t("chat.details")} onClose=${onClose} wide=${true}><div ref=${body}>${content}</div></${Dialog}>`;
467
+ }
468
+
469
+ function DeleteDialog({ name, onClose }) {
470
+ // The dialog's own lease: it ends when the dialog goes (closed, or the chat left for another page). The delete
471
+ // may still finish on the server, and says so, but a dialog that is gone never closes or navigates the page the
472
+ // person is on now (#1425 review).
473
+ const lease = useLease(`delete:${name}`);
474
+ const [typed, setTyped] = useState("");
475
+ const [busy, setBusy] = useState(false);
476
+ const want = `delete ${name}`;
477
+ async function go() {
478
+ if (typed !== want || busy) return;
479
+ setBusy(true);
480
+ let r, handedOver = false;
481
+ // Deleting an instance may need a fleet admin's confirmation (#1423): then the dialog goes at once and the shell
482
+ // follows the request. From then on the dialog is done: the decision is said in a toast, and it never closes or
483
+ // navigates anything (the person may be anywhere by then).
484
+ try {
485
+ const res = await confirmedWrite(`/ui/instances/${encodeURIComponent(name)}/delete`, { method: "POST", body: { confirm: typed }, label: t("chat.deleteTitle", name),
486
+ onPending: () => { handedOver = true; toast(t("app.pendingSent")); if (lease.current()) { setBusy(false); onClose(); } } });
487
+ r = res.ok ? res.body || {} : { error: (res.body && res.body.error) || `HTTP ${res.status}` };
488
+ } catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
489
+ if (r && r.error) { toast(r.error, false); if (!handedOver && lease.current()) setBusy(false); return; }
490
+ toast(t("chat.instanceDeleted", name));
491
+ if (handedOver || !lease.current()) return;
492
+ setBusy(false);
493
+ onClose();
494
+ navigate("/ui");
495
+ }
496
+ return html`<${Dialog} title=${t("chat.deleteTitle", name)} onClose=${onClose} busy=${busy}
497
+ actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("chat.cancel")}</button>
498
+ <button type="button" class="btn btn-danger" disabled=${typed !== want || busy} onClick=${go}>${t("chat.deleteGo")}</button>`}>
499
+ <p>${t("chat.deleteHelp", name)}</p>
500
+ <label class="field"><span class="sr-only">${t("chat.deleteHelp", name)}</span>
501
+ <input type="text" value=${typed} autocomplete="off" spellcheck="false" placeholder=${want} onInput=${(e) => setTyped(e.target.value)}
502
+ onKeyDown=${(e) => { if (e.key === "Enter") go(); }} /></label>
503
+ </${Dialog}>`;
504
+ }