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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +43 -7
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +4 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-file-read.d.ts +14 -6
  7. package/dist/agent-file-read.js +207 -19
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/agent-whats-new.d.ts +11 -0
  10. package/dist/agent-whats-new.js +29 -15
  11. package/dist/agent-whats-new.js.map +1 -1
  12. package/dist/auth-api.d.ts +30 -1
  13. package/dist/auth-api.js +45 -13
  14. package/dist/auth-api.js.map +1 -1
  15. package/dist/authority/authority-state.d.ts +38 -0
  16. package/dist/authority/authority-state.js +114 -0
  17. package/dist/authority/authority-state.js.map +1 -0
  18. package/dist/authority/session-authorizations.d.ts +43 -0
  19. package/dist/authority/session-authorizations.js +102 -0
  20. package/dist/authority/session-authorizations.js.map +1 -0
  21. package/dist/authority/web-authority.d.ts +120 -0
  22. package/dist/authority/web-authority.js +157 -0
  23. package/dist/authority/web-authority.js.map +1 -0
  24. package/dist/backend/claude-code.d.ts +2 -2
  25. package/dist/backend/claude-code.js +10 -5
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/types.d.ts +14 -0
  28. package/dist/backend/types.js +25 -0
  29. package/dist/backend/types.js.map +1 -1
  30. package/dist/backend-availability.d.ts +120 -0
  31. package/dist/backend-availability.js +396 -0
  32. package/dist/backend-availability.js.map +1 -0
  33. package/dist/backend-command.d.ts +21 -3
  34. package/dist/backend-command.js +23 -4
  35. package/dist/backend-command.js.map +1 -1
  36. package/dist/bundled-skills.d.ts +14 -0
  37. package/dist/bundled-skills.js +29 -0
  38. package/dist/bundled-skills.js.map +1 -0
  39. package/dist/cache-analysis.d.ts +47 -1
  40. package/dist/cache-analysis.js +68 -8
  41. package/dist/cache-analysis.js.map +1 -1
  42. package/dist/cache-ledger.d.ts +16 -3
  43. package/dist/cache-ledger.js +33 -13
  44. package/dist/cache-ledger.js.map +1 -1
  45. package/dist/cache-report-tool.d.ts +17 -0
  46. package/dist/cache-report-tool.js +40 -0
  47. package/dist/cache-report-tool.js.map +1 -0
  48. package/dist/cache-service.d.ts +70 -3
  49. package/dist/cache-service.js +178 -17
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.d.ts +7 -0
  52. package/dist/channel/adapters/discord.js +35 -0
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/mcp-tools.js +22 -10
  55. package/dist/channel/mcp-tools.js.map +1 -1
  56. package/dist/channel/types.d.ts +6 -0
  57. package/dist/classic-channel-manager.d.ts +10 -5
  58. package/dist/classic-channel-manager.js +12 -9
  59. package/dist/classic-channel-manager.js.map +1 -1
  60. package/dist/cli.js +130 -31
  61. package/dist/cli.js.map +1 -1
  62. package/dist/config-validator.js +103 -4
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/daemon.d.ts +7 -0
  65. package/dist/daemon.js +63 -16
  66. package/dist/daemon.js.map +1 -1
  67. package/dist/decision-list.d.ts +25 -0
  68. package/dist/decision-list.js +30 -0
  69. package/dist/decision-list.js.map +1 -0
  70. package/dist/delivery-outbox.d.ts +10 -0
  71. package/dist/delivery-outbox.js +12 -1
  72. package/dist/delivery-outbox.js.map +1 -1
  73. package/dist/fleet-context.d.ts +10 -0
  74. package/dist/fleet-manager.d.ts +417 -11
  75. package/dist/fleet-manager.js +1387 -109
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/fleet-traffic.d.ts +74 -0
  78. package/dist/fleet-traffic.js +196 -0
  79. package/dist/fleet-traffic.js.map +1 -0
  80. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  81. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  82. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  83. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  84. package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
  85. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +476 -0
  86. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  87. package/dist/human-delivery-receipts.d.ts +24 -1
  88. package/dist/human-delivery-receipts.js +64 -11
  89. package/dist/human-delivery-receipts.js.map +1 -1
  90. package/dist/instance-config-impact.js +3 -1
  91. package/dist/instance-config-impact.js.map +1 -1
  92. package/dist/instance-names.d.ts +4 -0
  93. package/dist/instance-names.js +12 -0
  94. package/dist/instance-names.js.map +1 -0
  95. package/dist/instructions.d.ts +12 -1
  96. package/dist/instructions.js +13 -2
  97. package/dist/instructions.js.map +1 -1
  98. package/dist/locale.js +100 -8
  99. package/dist/locale.js.map +1 -1
  100. package/dist/ls-attach-resolve.d.ts +95 -0
  101. package/dist/ls-attach-resolve.js +174 -0
  102. package/dist/ls-attach-resolve.js.map +1 -0
  103. package/dist/memory-hogs.d.ts +27 -0
  104. package/dist/memory-hogs.js +124 -0
  105. package/dist/memory-hogs.js.map +1 -0
  106. package/dist/outbound-handlers.d.ts +10 -0
  107. package/dist/outbound-handlers.js +122 -2
  108. package/dist/outbound-handlers.js.map +1 -1
  109. package/dist/outbound-schemas.d.ts +164 -4
  110. package/dist/outbound-schemas.js +99 -3
  111. package/dist/outbound-schemas.js.map +1 -1
  112. package/dist/public-web-gateway.js +8 -2
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/public-web-link.d.ts +9 -0
  115. package/dist/public-web-link.js +8 -0
  116. package/dist/public-web-link.js.map +1 -1
  117. package/dist/quickstart-api.d.ts +10 -1
  118. package/dist/quickstart-api.js +12 -4
  119. package/dist/quickstart-api.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +4 -0
  121. package/dist/reply-buttons-controller.js +2 -2
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/restart-progress.d.ts +65 -1
  124. package/dist/restart-progress.js +236 -10
  125. package/dist/restart-progress.js.map +1 -1
  126. package/dist/rollout-index.d.ts +5 -0
  127. package/dist/rollout-index.js +26 -5
  128. package/dist/rollout-index.js.map +1 -1
  129. package/dist/scheduler/scheduler.d.ts +12 -0
  130. package/dist/scheduler/scheduler.js +83 -10
  131. package/dist/scheduler/scheduler.js.map +1 -1
  132. package/dist/service-installer.d.ts +2 -1
  133. package/dist/service-installer.js +18 -3
  134. package/dist/service-installer.js.map +1 -1
  135. package/dist/settings-api.d.ts +19 -0
  136. package/dist/settings-api.js +137 -2
  137. package/dist/settings-api.js.map +1 -1
  138. package/dist/settings-change.js +104 -0
  139. package/dist/settings-change.js.map +1 -1
  140. package/dist/settings-effect.js +17 -1
  141. package/dist/settings-effect.js.map +1 -1
  142. package/dist/tmux-manager.d.ts +5 -0
  143. package/dist/tmux-manager.js +16 -0
  144. package/dist/tmux-manager.js.map +1 -1
  145. package/dist/tool-permissions.d.ts +4 -6
  146. package/dist/tool-permissions.js +53 -5
  147. package/dist/tool-permissions.js.map +1 -1
  148. package/dist/topic-commands.d.ts +7 -1
  149. package/dist/topic-commands.js +18 -4
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/types.d.ts +48 -1
  152. package/dist/ui/app-effects.js +77 -31
  153. package/dist/ui/chat-ambient.js +307 -0
  154. package/dist/ui/chat-effect-posters.js +8 -8
  155. package/dist/ui/chat-effects.js +101 -13
  156. package/dist/ui/chat-render.js +5 -2
  157. package/dist/ui/chat-strings.js +59 -6
  158. package/dist/ui/chat-thread.js +149 -21
  159. package/dist/ui/effect-trial-controller.js +13 -8
  160. package/dist/ui/effect-trial-panel.js +19 -16
  161. package/dist/ui/fleet-cache.js +23 -1
  162. package/dist/ui/fleet-canvas-layout.js +111 -0
  163. package/dist/ui/fleet-canvas.js +525 -0
  164. package/dist/ui/fleet-org.js +27 -12
  165. package/dist/ui/fleet-traffic.js +343 -0
  166. package/dist/ui/fleet-usage.js +401 -0
  167. package/dist/ui/panel-chat.js +127 -49
  168. package/dist/ui/panel-details.js +2 -1
  169. package/dist/ui/panel-fleet.js +41 -16
  170. package/dist/ui/panel-settings.js +26 -8
  171. package/dist/ui/preview.js +3 -2
  172. package/dist/ui/settings-appearance.js +42 -18
  173. package/dist/ui/settings-catalog.js +92 -0
  174. package/dist/ui/settings-dialogs.js +40 -12
  175. package/dist/ui/settings-effects.js +4 -3
  176. package/dist/ui/settings-guests.js +117 -0
  177. package/dist/ui/settings-learn.js +25 -0
  178. package/dist/ui/settings-model.js +15 -6
  179. package/dist/ui/settings-mods.js +23 -9
  180. package/dist/ui/settings-owner-effects.js +9 -9
  181. package/dist/ui/settings-strings.js +120 -42
  182. package/dist/ui/settings-web-authority.js +91 -0
  183. package/dist/ui/shared/agend-auth.js +1 -1
  184. package/dist/ui/shared/app-i18n.js +4 -2
  185. package/dist/ui/shared/app-prefs.js +5 -4
  186. package/dist/ui/shared/app-route.js +1 -1
  187. package/dist/ui/shared/app-session.js +2 -0
  188. package/dist/ui/shared/app-shell.js +75 -9
  189. package/dist/ui/shared/app-store.js +26 -1
  190. package/dist/ui/shared/app-stream.js +3 -1
  191. package/dist/ui/shared/app-themes.js +24 -6
  192. package/dist/ui/shared/app.css +364 -16
  193. package/dist/ui/shared/app.js +21 -14
  194. package/dist/ui/shared/card-strings.js +4 -0
  195. package/dist/ui/shared/effect-app-runtime.js +68 -19
  196. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  197. package/dist/ui/shared/effect-frame.js +26 -7
  198. package/dist/ui/shared/effect-geometry.js +24 -9
  199. package/dist/ui/shared/effect-policy.js +21 -7
  200. package/dist/ui/shared/effect-profiler.js +31 -9
  201. package/dist/ui/shared/effect-registry.js +87 -48
  202. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  203. package/dist/ui/shared/effect-stock-trust.js +62 -10
  204. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  205. package/dist/ui/shared/effect-trial-source.js +1 -1
  206. package/dist/ui/shared/effect-trial-stage.js +54 -31
  207. package/dist/ui/shared/effect-trial.css +40 -0
  208. package/dist/ui/shared/fx-motion.js +67 -0
  209. package/dist/ui/shared/identity-card.js +75 -4
  210. package/dist/ui/shared/identity-color.js +3 -3
  211. package/dist/ui/shared/instance-nav.js +194 -28
  212. package/dist/ui/shared/panel-view.js +24 -4
  213. package/dist/ui/shared/tokens.css +9 -3
  214. package/dist/ui/shared/ui-icons.js +1 -0
  215. package/dist/ui/shared/usage-dialog.js +22 -2
  216. package/dist/ui/shared/view-strings.js +68 -2
  217. package/dist/ui/shared/web-authorize.js +151 -0
  218. package/dist/ui/signin.html +1 -1
  219. package/dist/ui/web-terminal/terminal.css +4 -4
  220. package/dist/usage/providers.d.ts +28 -0
  221. package/dist/usage/providers.js +126 -36
  222. package/dist/usage/providers.js.map +1 -1
  223. package/dist/usage-report.d.ts +158 -0
  224. package/dist/usage-report.js +226 -0
  225. package/dist/usage-report.js.map +1 -0
  226. package/dist/usage-rollup.d.ts +51 -0
  227. package/dist/usage-rollup.js +168 -0
  228. package/dist/usage-rollup.js.map +1 -0
  229. package/dist/view-api.d.ts +39 -0
  230. package/dist/view-api.js +65 -4
  231. package/dist/view-api.js.map +1 -1
  232. package/dist/web-ambient.d.ts +72 -0
  233. package/dist/web-ambient.js +161 -0
  234. package/dist/web-ambient.js.map +1 -0
  235. package/dist/web-api.d.ts +69 -5
  236. package/dist/web-api.js +362 -45
  237. package/dist/web-api.js.map +1 -1
  238. package/dist/web-appearance-policy.d.ts +4 -6
  239. package/dist/web-appearance-policy.js +5 -12
  240. package/dist/web-appearance-policy.js.map +1 -1
  241. package/dist/web-appearance-tools.d.ts +3 -0
  242. package/dist/web-appearance-tools.js +22 -3
  243. package/dist/web-appearance-tools.js.map +1 -1
  244. package/dist/web-appearance-view.d.ts +27 -2
  245. package/dist/web-appearance-view.js +129 -14
  246. package/dist/web-appearance-view.js.map +1 -1
  247. package/dist/web-appearance.d.ts +17 -3
  248. package/dist/web-appearance.js +82 -7
  249. package/dist/web-appearance.js.map +1 -1
  250. package/dist/web-asset-parts.d.ts +5 -10
  251. package/dist/web-asset-parts.js +3 -3
  252. package/dist/web-asset-parts.js.map +1 -1
  253. package/dist/web-auth.js +3 -1
  254. package/dist/web-auth.js.map +1 -1
  255. package/dist/web-card.d.ts +14 -0
  256. package/dist/web-card.js +40 -6
  257. package/dist/web-card.js.map +1 -1
  258. package/dist/web-channel-echo.d.ts +7 -1
  259. package/dist/web-channel-echo.js +6 -2
  260. package/dist/web-channel-echo.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +46 -0
  262. package/dist/web-chat-history.js +80 -3
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-commands.d.ts +1 -0
  265. package/dist/web-effect-bootstrap.d.ts +12 -0
  266. package/dist/web-effect-bootstrap.js +49 -0
  267. package/dist/web-effect-bootstrap.js.map +1 -0
  268. package/dist/web-effect-bundle.js +46 -1
  269. package/dist/web-effect-bundle.js.map +1 -1
  270. package/dist/web-effect-playback.d.ts +30 -0
  271. package/dist/web-effect-playback.js +71 -0
  272. package/dist/web-effect-playback.js.map +1 -0
  273. package/dist/web-effect-profiles.d.ts +4 -4
  274. package/dist/web-effect-service.d.ts +11 -4
  275. package/dist/web-effect-service.js +28 -5
  276. package/dist/web-effect-service.js.map +1 -1
  277. package/dist/web-effect-tools.d.ts +1 -0
  278. package/dist/web-effect-tools.js +8 -5
  279. package/dist/web-effect-tools.js.map +1 -1
  280. package/dist/web-effect-trials.d.ts +4 -4
  281. package/dist/web-effect-trials.js +6 -6
  282. package/dist/web-effect-trials.js.map +1 -1
  283. package/dist/web-effect-upload.d.ts +2 -4
  284. package/dist/web-effect-upload.js +3 -8
  285. package/dist/web-effect-upload.js.map +1 -1
  286. package/dist/web-effect-worker.d.ts +1 -1
  287. package/dist/web-effect-worker.js +2 -2
  288. package/dist/web-effect-worker.js.map +1 -1
  289. package/dist/web-effect.d.ts +14 -0
  290. package/dist/web-effect.js +21 -0
  291. package/dist/web-effect.js.map +1 -1
  292. package/dist/web-guest-access.d.ts +125 -0
  293. package/dist/web-guest-access.js +234 -0
  294. package/dist/web-guest-access.js.map +1 -0
  295. package/dist/web-guest-rooms.d.ts +82 -0
  296. package/dist/web-guest-rooms.js +135 -0
  297. package/dist/web-guest-rooms.js.map +1 -0
  298. package/dist/web-identity-color.d.ts +1 -1
  299. package/dist/web-identity-color.js +2 -2
  300. package/dist/web-identity-color.js.map +1 -1
  301. package/dist/web-identity-palette.d.ts +1 -1
  302. package/dist/web-identity-palette.js +2 -2
  303. package/dist/web-identity-palette.js.map +1 -1
  304. package/dist/web-login.d.ts +47 -0
  305. package/dist/web-login.js +115 -0
  306. package/dist/web-login.js.map +1 -1
  307. package/dist/web-mod-agent.d.ts +1 -1
  308. package/dist/web-mod-agent.js +12 -8
  309. package/dist/web-mod-agent.js.map +1 -1
  310. package/dist/web-mod-bundle.d.ts +14 -1
  311. package/dist/web-mod-bundle.js +68 -3
  312. package/dist/web-mod-bundle.js.map +1 -1
  313. package/dist/web-mod-export.d.ts +9 -0
  314. package/dist/web-mod-export.js +21 -0
  315. package/dist/web-mod-export.js.map +1 -1
  316. package/dist/web-mod-store.d.ts +7 -1
  317. package/dist/web-mod-store.js +21 -7
  318. package/dist/web-mod-store.js.map +1 -1
  319. package/dist/web-prefs.d.ts +14 -0
  320. package/dist/web-prefs.js +24 -4
  321. package/dist/web-prefs.js.map +1 -1
  322. package/dist/web-preview.d.ts +6 -2
  323. package/dist/web-preview.js +4 -2
  324. package/dist/web-preview.js.map +1 -1
  325. package/dist/web-returning-device.d.ts +41 -0
  326. package/dist/web-returning-device.js +57 -5
  327. package/dist/web-returning-device.js.map +1 -1
  328. package/dist/web-session-authorization.d.ts +132 -0
  329. package/dist/web-session-authorization.js +193 -0
  330. package/dist/web-session-authorization.js.map +1 -0
  331. package/dist/web-session.d.ts +70 -10
  332. package/dist/web-session.js +137 -22
  333. package/dist/web-session.js.map +1 -1
  334. package/dist/web-settings-patch.d.ts +49 -2
  335. package/dist/web-settings-patch.js +145 -1
  336. package/dist/web-settings-patch.js.map +1 -1
  337. package/dist/web-settings-tools.d.ts +89 -0
  338. package/dist/web-settings-tools.js +270 -0
  339. package/dist/web-settings-tools.js.map +1 -0
  340. package/dist/web-shell-routes.d.ts +1 -1
  341. package/dist/web-shell-routes.js +1 -1
  342. package/dist/web-shell-routes.js.map +1 -1
  343. package/dist/web-theme-store.d.ts +39 -0
  344. package/dist/web-theme-store.js +122 -11
  345. package/dist/web-theme-store.js.map +1 -1
  346. package/dist/web-theme.d.ts +2 -0
  347. package/dist/web-theme.js +10 -1
  348. package/dist/web-theme.js.map +1 -1
  349. package/dist/workflow-templates/default.md +1 -1
  350. package/package.json +1 -1
  351. package/templates/systemd.service.ejs +8 -0
@@ -6,11 +6,12 @@
6
6
  // right, the agent's at full width, and one rounded composer pinned below with attach, Stop reply and Send inside it.
7
7
  // The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
8
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";
9
+ import { html, useCallback, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
10
+ import { lang, t } from "/assets/app-i18n.js";
11
+ import { appStore, guestPage, useStore } from "/assets/app-store.js";
12
+ import { AuthorizeChip } from "/assets/web-authorize.js";
12
13
  import { useLease } from "/assets/app-ctx.js";
13
- import { namesOf, namesStore } from "/assets/instance-nav.js";
14
+ import { modelText, namesOf, namesStore } from "/assets/instance-nav.js";
14
15
  import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
15
16
  import { detailsPath, textLinkSegments } from "/assets/app-route.js";
16
17
  import { InstanceSwitch } from "/assets/instance-switch.js";
@@ -23,7 +24,7 @@ import { Icon } from "/assets/ui-icons.js";
23
24
  import { toast } from "/assets/ui-toast.js";
24
25
  import { confirmDialog } from "/assets/ui-confirm.js";
25
26
  import { savePref } from "/assets/app-prefs.js";
26
- import { applyTheme, resolvedMode, themeFor, themeImageUrl, themeMenuItems, themesStore } from "/assets/app-themes.js";
27
+ import { applyTheme, clearAmbient, resolvedMode, themeFor, themeImageUrl, themeMenuItems, themesStore } from "/assets/app-themes.js";
27
28
  import { colorMenuItem } from "/assets/identity-color.js";
28
29
  import { IdentityCard, useCard } from "/assets/identity-card.js";
29
30
  import { AgentWidget, instanceBinds, widgetFor } from "/assets/agent-widget.js";
@@ -56,6 +57,8 @@ import { installTour, refreshTourSpot } from "./chat-tour.js";
56
57
  import { LightboxHost, openLightbox } from "./image-lightbox.js";
57
58
  import { ChatEffectPosters } from "/ui/js/chat-effect-posters.js";
58
59
  import { ChatEffects, chatEffectsMenuAvailable } from "./chat-effects.js";
60
+ import { setFollowsReducedMotion } from "/assets/fx-motion.js";
61
+ import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
59
62
  import { startAppEffects, getAppEffects } from "./app-effects.js";
60
63
 
61
64
  const R = () => globalThis.AgendChatRender;
@@ -116,6 +119,7 @@ export function boot({ stream, boot: bootData, deps = {} }) {
116
119
  }
117
120
  // #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
118
121
  installTour();
122
+ // #2118: viewing current in-scope Chat effects needs no management or personal-slot authority.
119
123
  startAppEffects({ store });
120
124
  return store;
121
125
  }
@@ -222,6 +226,9 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
222
226
  const themeKey = JSON.stringify(theme);
223
227
  const card = useCard(name, inst.card_v);
224
228
  const [dialog, setDialog] = useState(null); // "delete" | null
229
+ const [canvasPlayback, setCanvasPlayback] = useState(null);
230
+ const publishCanvasPlayback = useCallback(value => setCanvasPlayback(previous =>
231
+ !previous && !value.playing || previous?.name === value.name && previous.playing === value.playing ? previous : value), []);
225
232
  const [wrap, setWrap] = useState(codeWrap);
226
233
  const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
227
234
  useLayoutEffect(() => { applyTheme(view.current, theme, mode); }, [themeKey, mode]);
@@ -274,24 +281,32 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
274
281
  colorMenuItem(name, inst),
275
282
  // #1629: a person's control over this agent's theme — not over the public link (it only views themes).
276
283
  ...themeMenuItems(name),
284
+ // #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
285
+ ...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
277
286
  chatEffectsMenuAvailable() ? { key: "effects", label: t("chatFx.title"), onSelect: () => setDialog("effects") } : null,
278
287
  { key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
279
288
  ];
280
289
  const cls = statusClass(inst, exec, awaiting);
290
+ // #1920 phase 3: a guest has this chat only — no Details, View or Log to switch to, no lifecycle, colour, theme,
291
+ // effect management or delete (a guest's status frame carries no model or effort either, so those chips do not show).
292
+ const guest = guestPage();
281
293
  // #1269 quick actions: the model and effort it runs, each a way to /model or /effort (the same pick list).
282
294
  const pick = (command) => store.runCommand(name, command, "", { alive: () => lease.current() });
283
295
  const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
284
- ${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}
296
+ ${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
285
297
  ${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}`;
286
298
  return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
287
- <${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} /><${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onClose=${() => setDialog(null)} /><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
299
+ <${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? null : html`<${InstanceSwitch} name=${name} current="chat" />`} notice=${html`<${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onPlayback=${publishCanvasPlayback} onClose=${() => setDialog(null)} />`}><${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
288
300
  <${ChatCard} key=${name} name=${name} card=${card} />
289
301
  <${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
290
302
  <div class="chat-split" ref=${split}>
291
303
  <div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
292
304
  ${theme && theme.background ? html`<img ref=${bg} class=${`th-bg th-bg-${theme.background.fit}`} src=${themeImageUrl(name, theme.background.image)} alt="" aria-hidden="true" decoding="async" />` : null}
305
+ <${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
306
+ <${AmbientMotionNote} name=${name} canvasPlaying=${canvasPlayback?.name === name && canvasPlayback.playing} />
293
307
  <${Thread} name=${name} th=${thread} />
294
308
  <${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
309
+ <${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
295
310
  </div>
296
311
  <${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
297
312
  keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${(commit) => setPreviewOptIn(true, commit)} />
@@ -302,61 +317,118 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
302
317
  </div>`;
303
318
  }
304
319
 
305
- /** The thread: Preact owns the elements, the keyed renderer owns what is inside the list (chat-thread.js). */
320
+ /**
321
+ * #2053: the last THREAD_KEEP chats' threads are kept when their view leaves (the chat view is keyed by instance, so
322
+ * each switch built the thread again: a revisit was no faster than a first visit). A kept thread is its list element
323
+ * and its renderer, and nothing more: it leaves as a chat always did (previews stop), with no subscription or timer
324
+ * left; shown again, one render draws only what changed meanwhile, where the reader was. A new language, another
325
+ * document, or more than THREAD_KEEP lets one go.
326
+ */
327
+ export const THREAD_KEEP = 4;
328
+ const keptThreads = new Map(); // name → { thread, list, sink, lang }
329
+ /** Tests only: let every kept thread go. */
330
+ export function forgetKeptThreads() { for (const e of keptThreads.values()) letGo(e, true); keptThreads.clear(); }
331
+ function letGo(e, own) { e.thread.dispose(own); e.list.remove(); }
332
+
333
+ /** A chat's thread: its own list element and renderer, and a sink for the view that shows it. */
334
+ function threadEntry(name, doc) {
335
+ const list = doc.createElement("div");
336
+ list.className = "thread";
337
+ list.setAttribute("role", "log");
338
+ list.setAttribute("aria-live", "off");
339
+ list.setAttribute("aria-label", t("chat.conversationWith", name));
340
+ const e = { list, sink: null, lang: lang(), thread: null };
341
+ const thread = createThread(list, doc.createElement("div"), {
342
+ t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j), onEmpty: (v) => e.sink?.setEmpty(v),
343
+ setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
344
+ effectTrialStatus: x => getAppEffects()?.trialStatus(x) ?? "localAdmin",
345
+ effectTrialFailure: x => getAppEffects()?.trialFailure(x),
346
+ fixEffectTrial: (x, reason) => {
347
+ if (reason === "reducedMotion") setFollowsReducedMotion(false);
348
+ else if (reason === "busy") getAppEffects()?.stopTrial();
349
+ else if (reason === "transport") location.reload();
350
+ else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
351
+ else navigate("/settings/appearance");
352
+ },
353
+ canTryEffect: x => getAppEffects()?.canTry(x) === true,
354
+ startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
355
+ clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
356
+ replyButtonBusy: (id) => store.state.rbBusy.has(id),
357
+ pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
358
+ serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
359
+ panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
360
+ openImage: openLightbox, // an image, full size, in the page
361
+ emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
362
+ mentions: (kind, id) => (mentionIndex ? mentionIndex.name(kind, id) : null), // #1682: mention names
363
+ });
364
+ e.thread = thread;
365
+ return e;
366
+ }
367
+ /** While a thread is shown: what it follows. Returns what lets it all go. */
368
+ function follow(name, thread) {
369
+ // #1630: the server-emoji list is read once a message shows a tag; when it arrives, those messages are drawn again.
370
+ // #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
371
+ const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
372
+ const offs = [];
373
+ if (emojiIndex) offs.push(emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
374
+ if (mentionIndex) offs.push(mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
375
+ wantEmojis(store.state.msgs[name]);
376
+ offs.push(store.subscribe((instance, kind) => {
377
+ if (instance !== name) return;
378
+ if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
379
+ else if (kind === "sent") thread.jumpLatest();
380
+ }));
381
+ offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
382
+ offs.push(getAppEffects()?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? (() => {}));
383
+ offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
384
+ return () => { for (const off of offs) off(); };
385
+ }
386
+
387
+ /** The thread's view: Preact owns the scroller and the jump pill; the kept list (chat-thread.js) is put inside it. */
306
388
  function Thread({ name, th }) {
307
- const scroller = useRef(null), list = useRef(null);
389
+ const scroller = useRef(null), host = useRef(null);
308
390
  const [jump, setJump] = useState({ show: false, unseen: 0 });
309
391
  const [empty, setEmpty] = useState(false);
310
392
  useLayoutEffect(() => {
311
- let live = true;
312
- const thread = createThread(list.current, scroller.current, {
313
- t, tf: t, isUser: (x) => store.isUser(x, name), onJump: setJump, onEmpty: setEmpty,
314
- setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
315
- canTryEffect: x => getAppEffects()?.canTry(x) === true,
316
- startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && live && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
317
- clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
318
- replyButtonBusy: (id) => store.state.rbBusy.has(id),
319
- pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
320
- serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
321
- panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
322
- openImage: openLightbox, // an image, full size, in the page
323
- emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
324
- mentions: (kind, id) => (mentionIndex ? mentionIndex.name(kind, id) : null), // #1682: mention names
325
- });
393
+ const doc = host.current.ownerDocument;
394
+ let e = keptThreads.get(name);
395
+ // Kept only while it can be shown as it is: the same language, and this page's own document.
396
+ if (e && (e.lang !== lang() || e.list.ownerDocument !== doc)) { letGo(e, true); keptThreads.delete(name); e = undefined; }
397
+ const kept = !!e;
398
+ if (e) keptThreads.delete(name);
399
+ else e = threadEntry(name, doc);
400
+ keptThreads.set(name, e); // the most recent last
401
+ for (const [k, old] of keptThreads) {
402
+ if (keptThreads.size <= THREAD_KEEP) break;
403
+ if (k !== name) { letGo(old, true); keptThreads.delete(k); }
404
+ }
405
+ const thread = e.thread, sc = scroller.current, ls = e.list;
406
+ e.sink = { setJump, setEmpty };
407
+ host.current.append(ls);
408
+ thread.attach(sc);
326
409
  th.current = thread;
327
- // #1630: the server-emoji list is read once a message shows a tag; when it arrives, those messages are drawn again.
328
- // #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
329
- const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
330
- const offEmojis = emojiIndex ? emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
331
- const offMentions = mentionIndex ? mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
410
+ // Up to date, at the reader's place (the bottom unless they had scrolled up); a kept one's cards say what they can do.
332
411
  thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
333
- wantEmojis(store.state.msgs[name]);
334
- const off = store.subscribe((instance, kind) => {
335
- if (instance !== name) return;
336
- if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
337
- else if (kind === "sent") thread.jumpLatest();
338
- });
339
- const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
340
- const offTrials = getAppEffects()?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? (() => {});
341
- const offPanel = panelStore.subscribe(() => thread.refreshCards()); // a card shows whether the panel has its block
412
+ if (kept) thread.refreshCards();
413
+ const unfollow = follow(name, thread);
342
414
  const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
343
- const sc = scroller.current, ls = list.current;
344
415
  sc.addEventListener("scroll", onScroll, { passive: true });
345
416
  ls.addEventListener("click", thread.onClick);
346
417
  return () => {
347
- live = false;
348
- off(); offPv(); offPanel(); offTrials(); offEmojis(); offMentions();
418
+ unfollow();
349
419
  sc.removeEventListener("scroll", onScroll);
350
420
  ls.removeEventListener("click", thread.onClick);
351
- thread.dispose(); // the whole thread goes: every preview in it stops first
421
+ e.sink = null;
352
422
  th.current = null;
423
+ if (keptThreads.get(name) === e) { thread.park(); ls.remove(); } // kept for a later visit: the chat left
424
+ else letGo(e, false); // let go meanwhile (a new language)
353
425
  };
354
426
  }, [name]);
355
427
  return html`<div class="scroller" ref=${scroller}>
356
- <div class="thread" ref=${list} role="log" aria-live="off" aria-label=${t("chat.conversationWith", name)}></div>
428
+ <div class="thread-host" ref=${host}></div>
357
429
  ${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
358
- ${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
359
- <${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}
430
+ <div class="jump-dock">${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
431
+ <${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}</div>
360
432
  </div>`;
361
433
  }
362
434
 
@@ -373,7 +445,8 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
373
445
  <button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
374
446
  <button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
375
447
  <${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
376
- <p class="hint" title=${t("chat.composerMore")}>${t("chat.composerHint")}</p>
448
+ <p class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t("chat.composerHint")}</p>
449
+ ${guestPage() ? html`<p class="guest-note">${t("chat.guestNote")}</p>` : html`<${AuthorizeChip} />`}
377
450
  </div></div>`;
378
451
  }
379
452
 
@@ -393,7 +466,8 @@ function CommandCard({ name, lease }) {
393
466
  : c.compact === "lost" ? t("chat.compactLost") : c.compact === "not_compacted" ? t("chat.compactNotCompacted") : t("chat.compactUnknown")}</p>` : null}
394
467
  ${c.choices && c.choices.prompt ? html`<pre class="cmd-text">${c.choices.prompt}</pre>` : null}
395
468
  ${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"
396
- 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}
469
+ class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined}
470
+ disabled=${!!o.disabled} title=${o.disabled || undefined} onClick=${() => { if (!o.disabled) run(o.id); }}>${o.label}</button>`)}</div>` : null}
397
471
  </div>`;
398
472
  }
399
473
 
@@ -449,8 +523,10 @@ function Composer({ name, lease, busy }) {
449
523
  const value = s.drafts[name] || "";
450
524
  const files = s.pendingFiles[name] || [];
451
525
  const content = !!value.trim() || files.length > 0;
452
- // A file waiting: what goes is a message with its file, never a command (no palette, no command line).
453
- const palette = files.length || closedFor === value ? null : paletteFor(value);
526
+ // A file waiting: what goes is a message with its file, never a command (no palette, no command line). A guest never
527
+ // runs one (#1920: the server refuses its slash text), so it has no palette either.
528
+ const guest = guestPage();
529
+ const palette = guest || files.length || closedFor === value ? null : paletteFor(value);
454
530
  const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
455
531
  // Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300).
456
532
  useLayoutEffect(() => { const el = input.current; if (!el) return; el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 240)}px`; }, [value]);
@@ -471,6 +547,8 @@ function Composer({ name, lease, busy }) {
471
547
  else complete(pick);
472
548
  return;
473
549
  }
550
+ // #1920: a guest's "/…" would be refused as a command — said here, the draft kept to edit.
551
+ if (guest && /^\s*\//.test(value)) { toast(t("chat.guestNoCommands"), false); return; }
474
552
  const cmd = files.length ? null : parseCommandLine(value);
475
553
  if (cmd && needsArgument(cmd.command) && !cmd.args) { setValue(`/${cmd.command} `); return; }
476
554
  if (cmd) { runLine(cmd); return; }
@@ -14,6 +14,7 @@ import { PanelHeader, setTitle, statusClass, statusLabel, openDrawer } from "/as
14
14
  import { navigate } from "/assets/app-nav.js";
15
15
  import { settingsPath } from "/assets/app-route.js";
16
16
  import { InstanceSwitch } from "/assets/instance-switch.js";
17
+ import { modelText } from "/assets/instance-nav.js";
17
18
  import { HeaderTools } from "/assets/header-tools.js";
18
19
  import { Menu } from "/assets/ui-menu.js";
19
20
  import { themeMenuItems, themesStore } from "/assets/app-themes.js";
@@ -143,7 +144,7 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
143
144
  <button type="button" class="btn btn-sm d-edit-settings" onClick=${editInSettings}>${t("chat.dEditInSettings")}</button></div></section>
144
145
  <section class="card"><h3>${t("chat.dRuntime")}</h3>
145
146
  ${row(t("chat.dBackend"), d.backend || "--")}
146
- ${row(t("chat.dModel"), `${d.model || sl.model?.display_name || "--"}${d.model_source === "live" ? "" : src(d.model_source)}`)}
147
+ ${row(t("chat.dModel"), `${(d.model && modelText(d)) || sl.model?.display_name || "--"}${d.model_source === "live" ? "" : src(d.model_source)}`)}
147
148
  ${d.effort ? row(t("chat.dEffort"), `${d.effort}${src(d.effort_source)}`) : null}
148
149
  ${row(t("chat.dCost"), `$${cost.toFixed(2)} ${t("chat.dCostNote")}`)}
149
150
  ${row(t("chat.dContext"), html`<span class="pct">${ctx != null ? `${Math.round(ctx)}%` : "--"}</span>${ctx != null ? bar(ctx, ctx > 80 ? "warn" : "") : null}`)}
@@ -18,15 +18,26 @@ import { Icon } from "/assets/ui-icons.js";
18
18
  import { toast } from "/assets/ui-toast.js";
19
19
  import { confirmDialog } from "/assets/ui-confirm.js";
20
20
  import { confirmedWrite } from "./settings-confirm.js";
21
- import { OrgChart } from "./fleet-org.js";
21
+ import { OrgCanvas } from "./fleet-canvas.js";
22
22
  import { labelOf } from "/assets/instance-nav.js";
23
23
  import { CacheReport } from "./fleet-cache.js";
24
+ import { UsageTab } from "./fleet-usage.js";
25
+ import { BackendSelect, ModelSelect } from "./settings-dialogs.js";
26
+
27
+ /** #2015: one of the largest processes as text: "python3 18.1 GB (dev-a)". */
28
+ export function memoryHogText(p) {
29
+ const size = p.rssBytes >= 1024 ** 3 ? `${(p.rssBytes / 1024 ** 3).toFixed(1)} GB` : `${Math.round(p.rssBytes / 1024 ** 2)} MB`;
30
+ const owner = p.owner === "instance" && p.instance ? p.instance
31
+ : t(p.owner === "fleet" ? "fleet.hostMemory_ownerFleet" : p.owner === "tmux" ? "fleet.hostMemory_ownerTmux" : "fleet.hostMemory_ownerOther");
32
+ return `${p.name} ${size} (${owner})`;
33
+ }
24
34
 
25
35
  register("fleet", {
26
36
  en: {
27
37
  hostMemory_elevated: "Host memory pressure: elevated — new agents start one at a time, at least 5 s apart",
28
38
  hostMemory_critical: "Host memory pressure: critical — new agents wait for memory to recover",
29
- title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
39
+ hostMemory_top: "Largest: {0}", hostMemory_ownerFleet: "the fleet", hostMemory_ownerTmux: "tmux", hostMemory_ownerOther: "not an instance",
40
+ title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", usage: "Usage", config: "Config",
30
41
  docs: "Docs", docsNewTab: "Docs (opens in a new tab)",
31
42
  newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
32
43
  noTasks: "No tasks yet", noTasksHint: "Create one to coordinate fleet work.", noSchedules: "No schedules yet", noSchedulesHint: "Automate recurring work with cron.",
@@ -52,12 +63,13 @@ register("fleet", {
52
63
  newInstance: "New instance", directory: "Directory", directoryHint: "Optional — a workspace is created if empty", topic: "Name", topicHint: "From the directory name",
53
64
  topicRequired: "Name is required when Directory is empty", description: "Description", descriptionHint: "What this instance does", model: "Model", modelHint: "e.g. sonnet, opus, gpt-5",
54
65
  branch: "Branch (git worktree)", branchHint: "e.g. feature-x", tags: "Tags", tagsHint: "comma-separated, e.g. dev, review", systemPrompt: "System prompt", systemPromptHint: "Custom instructions for this instance",
55
- fleetDefault: "Use the fleet default", notInstalled: "(not installed)", deprecated: "(deprecated)", instanceCreated: "Instance created",
66
+ fleetDefault: "Use the fleet default", notInstalled: "(not installed)", notSignedIn: "(not signed in — use /login)", tooOld: "(needs {0} or newer)", unchecked: "(not checked yet)", deprecated: "(deprecated)", instanceCreated: "Instance created",
56
67
  },
57
68
  "zh-TW": {
58
69
  hostMemory_elevated: "主機記憶體壓力偏高:新的 agent 一次啟動一個,至少間隔 5 秒",
59
70
  hostMemory_critical: "主機記憶體壓力嚴重:新的 agent 會等記憶體恢復再啟動",
60
- title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
71
+ hostMemory_top: "最大:{0}", hostMemory_ownerFleet: "fleet 本身", hostMemory_ownerTmux: "tmux", hostMemory_ownerOther: "不屬於任何 instance",
72
+ title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", usage: "用量", config: "設定",
61
73
  docs: "文件", docsNewTab: "文件(在新分頁開啟)",
62
74
  newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
63
75
  noTasks: "還沒有 task", noTasksHint: "建立一個來協調 fleet 的工作。", noSchedules: "還沒有排程", noSchedulesHint: "用 cron 自動執行例行工作。",
@@ -82,7 +94,7 @@ register("fleet", {
82
94
  newInstance: "新增 instance", directory: "目錄", directoryHint: "可留空——留空會自動建立工作區", topic: "名稱", topicHint: "預設取自目錄名稱",
83
95
  topicRequired: "目錄留空時必須填寫名稱", description: "說明", descriptionHint: "這個 instance 做什麼", model: "模型", modelHint: "例如 sonnet、opus、gpt-5",
84
96
  branch: "分支(git worktree)", branchHint: "例如 feature-x", tags: "標籤", tagsHint: "以逗號分隔,例如 dev, review", systemPrompt: "System prompt", systemPromptHint: "這個 instance 的自訂指示",
85
- fleetDefault: "使用 fleet 預設", notInstalled: "(未安裝)", deprecated: "(已淘汰)", instanceCreated: "Instance 已建立",
97
+ fleetDefault: "使用 fleet 預設", notInstalled: "(未安裝)", notSignedIn: "(未登入,請用 /login)", tooOld: "(需要 {0} 以上版本)", unchecked: "(尚未檢查)", deprecated: "(已淘汰)", instanceCreated: "Instance 已建立",
86
98
  },
87
99
  });
88
100
 
@@ -91,7 +103,7 @@ const DOCS_SITE = "https://songsid.github.io/AgEnD";
91
103
  export function docsUrl(language = lang()) {
92
104
  return `${DOCS_SITE}${language === "zh-TW" ? "/zh-tw" : ""}/docs/web-guide/`;
93
105
  }
94
- const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", config: "sliders" };
106
+ const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", usage: "chart", config: "sliders" };
95
107
  const BACKENDS = ["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"];
96
108
 
97
109
  async function api(method, path, body, lease) {
@@ -130,15 +142,17 @@ export function FleetPanel({ route, navKey }) {
130
142
  const lease = useLease(navKey);
131
143
  const tab = route.tab;
132
144
  useEffect(() => { setTitle(`${t("fleet.title")} · ${t(`fleet.${tab}`)}`); }, [tab, navKey]);
133
- const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, config: Config }[tab] || Tasks;
145
+ const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, usage: UsageTab, config: Config }[tab] || Tasks;
134
146
  // #1685: host memory pressure slowing or holding new agents — shown here, where macOS's routine "elevated" is not a chat notice.
135
- const { hostMemory } = useStore(appStore);
136
- const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}</span>` : null;
147
+ const { hostMemory, hostMemoryTop } = useStore(appStore);
148
+ // #2015: and which processes hold the most, and whose — text only (never chat, #1712).
149
+ const top = hostMemory && hostMemoryTop?.length ? t("fleet.hostMemory_top", hostMemoryTop.map(memoryHogText).join(" · ")) : "";
150
+ const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}${top ? html`<span class="host-mem-top">${top}</span>` : null}</span>` : null;
137
151
  return html`<div data-effect-panel="fleet" class="panel p-fleet">
138
152
  <${PanelHeader} title=${t("fleet.title")} sub=${sub}><${DocsLink} /><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
139
153
  <nav data-trust="panel-navigation" class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
140
154
  aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
141
- <div data-trust="panel-content" class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
155
+ <div data-trust="panel-content" class="panel-body"><div class=${`col${tab === "org" || tab === "cache" || tab === "usage" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
142
156
  </div>`;
143
157
  }
144
158
 
@@ -255,11 +269,11 @@ function Teams({ lease }) {
255
269
  ${editing ? html`<${CreateTeam} team=${editing} onClose=${() => setEditing(null)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
256
270
  }
257
271
 
258
- /** #1389: the org chart — its structure read once per opening, its live state from the app store (fleet-org.js). */
272
+ /** #1389: the org chart — its structure read once per opening, its live state from the app store. #1918: a canvas, or the list (fleet-canvas.js). */
259
273
  function Org({ lease }) {
260
274
  const state = useLoad(lease, "/ui/org", 0);
261
275
  const app = useStore(appStore);
262
- return html`<${Loaded} state=${state}>${(org) => html`<${OrgChart} org=${org} app=${app} />`}</${Loaded}>`;
276
+ return html`<${Loaded} state=${state}>${(org) => html`<${OrgCanvas} org=${org} app=${app} lease=${lease} />`}</${Loaded}>`;
263
277
  }
264
278
 
265
279
  /** #1468: the prompt-cache analysis — one read per opening and per window chosen; Refresh reads again (fleet-cache.js). */
@@ -322,8 +336,7 @@ function Config({ lease }) {
322
336
  <div class="field"><label for="cfg-users">${t("fleet.allowedUsers")}</label><${Secret} id="cfg-users" value=${form.users} onInput=${set("users")} label=${t("fleet.allowedUsers")} />
323
337
  <p class="note">${t("fleet.allowedUsersNote")}</p></div></section>
324
338
  <section class="card"><h3>${t("fleet.defaults")}</h3>
325
- <label class="field"><span>${t("fleet.backend")}</span><select value=${form.backend} onChange=${set("backend")}>
326
- ${[...(BACKENDS.includes(form.backend) ? [] : [form.backend]), ...BACKENDS].map(b => html`<option key=${b} value=${b}>${b}</option>`)}</select></label></section>
339
+ <div class="field"><label for="cfg-be">${t("fleet.backend")}</label><${BackendSelect} id="cfg-be" value=${form.backend} onChange=${(v) => setForm({ ...form, backend: v })} /></div></section>
327
340
  <section class="card"><h3>${t("fleet.roots")}</h3>
328
341
  ${form.roots.map((r, i) => html`<div key=${i} class="field-row"><input value=${r} placeholder="/path/to/projects" aria-label=${t("fleet.roots")}
329
342
  onInput=${(e) => { const roots = [...form.roots]; roots[i] = e.target.value; setForm({ ...form, roots }); }} />
@@ -510,6 +523,8 @@ function CreateTeam({ onClose, onDone, team = null }) {
510
523
  export function CreateInstanceDialog({ onClose }) {
511
524
  const lease = useLease("create-instance");
512
525
  const [backends, setBackends] = useState(null);
526
+ // The backend "fleet default" means (#2098 review P3-3): its models are the ones the model list offers then.
527
+ const [defaultBackend, setDefaultBackend] = useState("claude-code");
513
528
  const [f, setF] = useState({ dir: "", topic: "", desc: "", backend: "", model: "", branch: "", tags: "", prompt: "" });
514
529
  useEffect(() => {
515
530
  (async () => {
@@ -518,6 +533,7 @@ export function CreateInstanceDialog({ onClose }) {
518
533
  if (!lease.current()) return;
519
534
  const list = bd.backends || [];
520
535
  const first = list.find(b => b.installed && !b.deprecated) || list.find(b => b.installed);
536
+ if (typeof bd.defaultBackend === "string" && bd.defaultBackend) setDefaultBackend(bd.defaultBackend);
521
537
  setBackends(list);
522
538
  setF(x => ({ ...x, backend: first ? first.name : "" }));
523
539
  } catch {
@@ -547,14 +563,23 @@ export function CreateInstanceDialog({ onClose }) {
547
563
  ${field(`${t("fleet.topic")}${f.dir.trim() ? "" : " *"}`, html`<input value=${f.topic} onInput=${set("topic")} placeholder=${t("fleet.topicHint")} />`)}
548
564
  ${field(t("fleet.description"), html`<input value=${f.desc} onInput=${set("desc")} placeholder=${t("fleet.descriptionHint")} />`)}
549
565
  ${field(t("fleet.backend"), backends ? html`<select value=${f.backend} onChange=${set("backend")}><option value="">${t("fleet.fleetDefault")}</option>
550
- ${backends.map(b => html`<option key=${b.name} value=${b.name} disabled=${!b.installed}>${b.name}${b.deprecated ? ` ${t("fleet.deprecated")}` : ""}${b.installed ? "" : ` ${t("fleet.notInstalled")}`}</option>`)}</select>` : html`<${Skeleton} lines=${1} />`)}
551
- ${field(t("fleet.model"), html`<input value=${f.model} onInput=${set("model")} placeholder=${t("fleet.modelHint")} />`)}
566
+ ${backends.map(b => html`<option key=${b.name} value=${b.name} disabled=${backendBlocked(b)}>${b.name}${b.deprecated ? ` ${t("fleet.deprecated")}` : ""}${backendNote(b)}</option>`)}</select>` : html`<${Skeleton} lines=${1} />`)}
567
+ ${field(t("fleet.model"), html`<${ModelSelect} value=${f.model} onChange=${(v) => setF(x => ({ ...x, model: v }))} backend=${f.backend || defaultBackend} inherit=${t("fleet.fleetDefault")} />`)}
552
568
  ${field(t("fleet.branch"), html`<input value=${f.branch} onInput=${set("branch")} placeholder=${t("fleet.branchHint")} />`)}
553
569
  ${field(t("fleet.tags"), html`<input value=${f.tags} onInput=${set("tags")} placeholder=${t("fleet.tagsHint")} />`)}
554
570
  ${field(t("fleet.systemPrompt"), html`<textarea rows="3" value=${f.prompt} onInput=${set("prompt")} placeholder=${t("fleet.systemPromptHint")}></textarea>`)}
555
571
  </${FormDialog}>`;
556
572
  }
557
573
 
574
+ // #2072: a backend this host cannot run is shown, disabled, with why; one not checked yet stays selectable.
575
+ const backendState = (b) => b.state ?? (b.installed ? "usable" : b.unknown ? "unknown" : "not_installed");
576
+ function backendBlocked(b) { return ["not_installed", "not_signed_in", "too_old"].includes(backendState(b)); }
577
+ function backendNote(b) {
578
+ const s = backendState(b);
579
+ return s === "not_installed" ? ` ${t("fleet.notInstalled")}` : s === "not_signed_in" ? ` ${t("fleet.notSignedIn")}`
580
+ : s === "too_old" ? ` ${t("fleet.tooOld", b.minVersion ?? "?")}` : s === "unknown" ? ` ${t("fleet.unchecked")}` : "";
581
+ }
582
+
558
583
  // ── Cron in words (#1724: in the page's language) ──
559
584
  /**
560
585
  * A cron expression as a person says it — "Weekdays 18:00", 「每週一到五 18:00」, "Every 15 minutes" — or the expression