@songsid/agend 2.3.0-beta.24 → 2.3.0-beta.26

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 (419) hide show
  1. package/dist/adapter-world.js.map +1 -1
  2. package/dist/agend-home.d.ts +5 -0
  3. package/dist/agend-home.js +7 -0
  4. package/dist/agend-home.js.map +1 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +8 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.js +1 -1
  9. package/dist/agent-file-read.js.map +1 -1
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/apply-job.js.map +1 -1
  12. package/dist/atomic-write.js.map +1 -1
  13. package/dist/auth-api.d.ts +5 -0
  14. package/dist/auth-api.js +21 -0
  15. package/dist/auth-api.js.map +1 -1
  16. package/dist/authority/authority-state.d.ts +4 -0
  17. package/dist/authority/authority-state.js +17 -8
  18. package/dist/authority/authority-state.js.map +1 -1
  19. package/dist/authority/session-authorizations.js.map +1 -1
  20. package/dist/authority/web-authority.d.ts +14 -6
  21. package/dist/authority/web-authority.js +19 -5
  22. package/dist/authority/web-authority.js.map +1 -1
  23. package/dist/backend/antigravity.d.ts +2 -2
  24. package/dist/backend/antigravity.js +2 -1
  25. package/dist/backend/antigravity.js.map +1 -1
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/codex-package.js.map +1 -1
  28. package/dist/backend/codex-session-lookup.js.map +1 -1
  29. package/dist/backend/codex.js +1 -1
  30. package/dist/backend/codex.js.map +1 -1
  31. package/dist/backend/effort-metadata.js.map +1 -1
  32. package/dist/backend/grok.js.map +1 -1
  33. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  34. package/dist/backend/kiro-kas-store.js.map +1 -1
  35. package/dist/backend/kiro-v2-store.js.map +1 -1
  36. package/dist/backend/kiro-v3-identity.js.map +1 -1
  37. package/dist/backend/kiro.js.map +1 -1
  38. package/dist/backend/launch-effort.js.map +1 -1
  39. package/dist/backend/mock.js.map +1 -1
  40. package/dist/backend/muse.js.map +1 -1
  41. package/dist/backend/opencode-permission.js.map +1 -1
  42. package/dist/backend/opencode.js.map +1 -1
  43. package/dist/backend/session-signals.js.map +1 -1
  44. package/dist/backend-availability.js.map +1 -1
  45. package/dist/backend-command.js.map +1 -1
  46. package/dist/backend-outage.js.map +1 -1
  47. package/dist/binary-probe.js.map +1 -1
  48. package/dist/bug-report.js.map +1 -1
  49. package/dist/bundled-skills.js.map +1 -1
  50. package/dist/cache-service.js +1 -1
  51. package/dist/cache-service.js.map +1 -1
  52. package/dist/channel/adapters/discord.js +29 -4
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/adapters/telegram.d.ts +6 -0
  55. package/dist/channel/adapters/telegram.js +54 -14
  56. package/dist/channel/adapters/telegram.js.map +1 -1
  57. package/dist/channel/mcp-server.js +10 -1
  58. package/dist/channel/mcp-server.js.map +1 -1
  59. package/dist/channel/mcp-tools.js +2 -2
  60. package/dist/channel/mcp-tools.js.map +1 -1
  61. package/dist/channel/message-queue.js.map +1 -1
  62. package/dist/channel/sticker-download.js.map +1 -1
  63. package/dist/channel/tool-router.js.map +1 -1
  64. package/dist/classic-channel-manager.js +3 -2
  65. package/dist/classic-channel-manager.js.map +1 -1
  66. package/dist/cli-entry.js.map +1 -1
  67. package/dist/cli-flags.js.map +1 -1
  68. package/dist/cli.js +15 -9
  69. package/dist/cli.js.map +1 -1
  70. package/dist/command-table.js.map +1 -1
  71. package/dist/compact-watch.js.map +1 -1
  72. package/dist/completion-install.js.map +1 -1
  73. package/dist/completion.js.map +1 -1
  74. package/dist/config-validator.js +20 -6
  75. package/dist/config-validator.js.map +1 -1
  76. package/dist/config.js +11 -3
  77. package/dist/config.js.map +1 -1
  78. package/dist/connection-secrets.js.map +1 -1
  79. package/dist/context-guardian.js.map +1 -1
  80. package/dist/coredump-filter.js.map +1 -1
  81. package/dist/cpu-profile.js.map +1 -1
  82. package/dist/cross-instance-envelope.js.map +1 -1
  83. package/dist/daemon.d.ts +3 -0
  84. package/dist/daemon.js +73 -3
  85. package/dist/daemon.js.map +1 -1
  86. package/dist/daily-summary.js.map +1 -1
  87. package/dist/darwin-memory.js.map +1 -1
  88. package/dist/dashboard-live.d.ts +8 -0
  89. package/dist/dashboard-live.js +35 -0
  90. package/dist/dashboard-live.js.map +1 -0
  91. package/dist/dashboard-permissions.d.ts +69 -0
  92. package/dist/dashboard-permissions.js +138 -0
  93. package/dist/dashboard-permissions.js.map +1 -0
  94. package/dist/decision-list.js.map +1 -1
  95. package/dist/delivery-consumed-watch.js.map +1 -1
  96. package/dist/delivery-outbox.js.map +1 -1
  97. package/dist/delivery-outbox.test.js.map +1 -1
  98. package/dist/delivery-reconciliation.test.js.map +1 -1
  99. package/dist/diagnostic-redaction.js.map +1 -1
  100. package/dist/doctor.js.map +1 -1
  101. package/dist/e2e-isolation.js.map +1 -1
  102. package/dist/event-log.js.map +1 -1
  103. package/dist/export-import.js +3 -2
  104. package/dist/export-import.js.map +1 -1
  105. package/dist/fleet-control-audit.js.map +1 -1
  106. package/dist/fleet-label.js.map +1 -1
  107. package/dist/fleet-lock.js +3 -2
  108. package/dist/fleet-lock.js.map +1 -1
  109. package/dist/fleet-manager.d.ts +77 -6
  110. package/dist/fleet-manager.js +488 -147
  111. package/dist/fleet-manager.js.map +1 -1
  112. package/dist/fleet-process-handlers.js +3 -2
  113. package/dist/fleet-process-handlers.js.map +1 -1
  114. package/dist/fleet-startup.d.ts +14 -0
  115. package/dist/fleet-startup.js +30 -0
  116. package/dist/fleet-startup.js.map +1 -0
  117. package/dist/fleet-traffic.js.map +1 -1
  118. package/dist/forged-envelope-scan.d.ts +0 -20
  119. package/dist/forged-envelope-scan.js +3 -2
  120. package/dist/forged-envelope-scan.js.map +1 -1
  121. package/dist/full-restart.js.map +1 -1
  122. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
  123. package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
  124. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +36 -12
  125. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +45 -14
  126. package/dist/host-memory.js.map +1 -1
  127. package/dist/instance-config-impact.js +6 -0
  128. package/dist/instance-config-impact.js.map +1 -1
  129. package/dist/instance-lifecycle.js +1 -1
  130. package/dist/instance-lifecycle.js.map +1 -1
  131. package/dist/instructions.d.ts +2 -0
  132. package/dist/instructions.js +6 -2
  133. package/dist/instructions.js.map +1 -1
  134. package/dist/kiro-compact-pane.js.map +1 -1
  135. package/dist/kiro-db-reader.js.map +1 -1
  136. package/dist/kiro-engine-status.js.map +1 -1
  137. package/dist/kiro-log-gate.js.map +1 -1
  138. package/dist/kiro-transcript-lane.js.map +1 -1
  139. package/dist/locale.js +22 -0
  140. package/dist/locale.js.map +1 -1
  141. package/dist/log-service.js.map +1 -1
  142. package/dist/log-steps.js.map +1 -1
  143. package/dist/logger.js +3 -2
  144. package/dist/logger.js.map +1 -1
  145. package/dist/login-controller.js.map +1 -1
  146. package/dist/login-manager.js.map +1 -1
  147. package/dist/login-shell.js.map +1 -1
  148. package/dist/memory-pressure.js.map +1 -1
  149. package/dist/muse-usage-relay.js.map +1 -1
  150. package/dist/needs-you-hub.js.map +1 -1
  151. package/dist/needs-you-live.js.map +1 -1
  152. package/dist/network-family.js.map +1 -1
  153. package/dist/operator-control-paths.js +3 -4
  154. package/dist/operator-control-paths.js.map +1 -1
  155. package/dist/outbound-handlers.js.map +1 -1
  156. package/dist/outbound-schemas.d.ts +68 -68
  157. package/dist/outbound-schemas.js +1 -1
  158. package/dist/outbound-schemas.js.map +1 -1
  159. package/dist/outbox-open-error.js.map +1 -1
  160. package/dist/pane-context-cache.js.map +1 -1
  161. package/dist/paths.js.map +1 -1
  162. package/dist/private-dir.js.map +1 -1
  163. package/dist/probe-worker-pool.js.map +1 -1
  164. package/dist/profile-control.js.map +1 -1
  165. package/dist/prompt-file-ref.js.map +1 -1
  166. package/dist/provider-probe.js.map +1 -1
  167. package/dist/provider-secret-registry.js.map +1 -1
  168. package/dist/public-link-auto-open.d.ts +32 -0
  169. package/dist/public-link-auto-open.js +86 -0
  170. package/dist/public-link-auto-open.js.map +1 -0
  171. package/dist/public-link-owner.d.ts +10 -0
  172. package/dist/public-link-owner.js +36 -0
  173. package/dist/public-link-owner.js.map +1 -0
  174. package/dist/public-link-progress.js.map +1 -1
  175. package/dist/public-preview.js.map +1 -1
  176. package/dist/public-web-gateway.js +4 -2
  177. package/dist/public-web-gateway.js.map +1 -1
  178. package/dist/public-web-link.d.ts +35 -1
  179. package/dist/public-web-link.js +142 -43
  180. package/dist/public-web-link.js.map +1 -1
  181. package/dist/quickstart-api.js.map +1 -1
  182. package/dist/quickstart.d.ts +5 -0
  183. package/dist/quickstart.js +25 -16
  184. package/dist/quickstart.js.map +1 -1
  185. package/dist/reply-buttons-controller.js.map +1 -1
  186. package/dist/reply-dedup.js.map +1 -1
  187. package/dist/resource-report.js.map +1 -1
  188. package/dist/restart-guard.js.map +1 -1
  189. package/dist/restart-progress.js.map +1 -1
  190. package/dist/rollout-index.js.map +1 -1
  191. package/dist/runtime-cpu-profile.js.map +1 -1
  192. package/dist/scheduler/db.js.map +1 -1
  193. package/dist/scheduler/scheduler.js.map +1 -1
  194. package/dist/scheduler/task-board-text.js +16 -11
  195. package/dist/scheduler/task-board-text.js.map +1 -1
  196. package/dist/scheduler/task-board.d.ts +50 -10
  197. package/dist/scheduler/task-board.js +138 -44
  198. package/dist/scheduler/task-board.js.map +1 -1
  199. package/dist/scheduler/types.d.ts +1 -1
  200. package/dist/sd-notify.js.map +1 -1
  201. package/dist/search/search-index.js.map +1 -1
  202. package/dist/secret-file.js.map +1 -1
  203. package/dist/secret-store.js.map +1 -1
  204. package/dist/service-installer.d.ts +7 -1
  205. package/dist/service-installer.js +13 -1
  206. package/dist/service-installer.js.map +1 -1
  207. package/dist/service-plan.d.ts +33 -4
  208. package/dist/service-plan.js +177 -8
  209. package/dist/service-plan.js.map +1 -1
  210. package/dist/session-oom.d.ts +21 -0
  211. package/dist/session-oom.js +139 -0
  212. package/dist/session-oom.js.map +1 -0
  213. package/dist/settings-api.js +6 -1
  214. package/dist/settings-api.js.map +1 -1
  215. package/dist/settings-baseline.js.map +1 -1
  216. package/dist/settings-change.d.ts +5 -0
  217. package/dist/settings-change.js +71 -1
  218. package/dist/settings-change.js.map +1 -1
  219. package/dist/settings-confirmation.d.ts +4 -0
  220. package/dist/settings-confirmation.js +2 -0
  221. package/dist/settings-confirmation.js.map +1 -1
  222. package/dist/settings-control.js.map +1 -1
  223. package/dist/settings-effect.js +6 -1
  224. package/dist/settings-effect.js.map +1 -1
  225. package/dist/settings-http-confirmation.js +1 -0
  226. package/dist/settings-http-confirmation.js.map +1 -1
  227. package/dist/settings-instance-patch.js +13 -1
  228. package/dist/settings-instance-patch.js.map +1 -1
  229. package/dist/settings-transaction.js.map +1 -1
  230. package/dist/setup-auth.js.map +1 -1
  231. package/dist/setup-host.d.ts +5 -1
  232. package/dist/setup-host.js +8 -1
  233. package/dist/setup-host.js.map +1 -1
  234. package/dist/setup-wizard.js +5 -2
  235. package/dist/setup-wizard.js.map +1 -1
  236. package/dist/spawn-gate.d.ts +4 -0
  237. package/dist/spawn-gate.js +15 -5
  238. package/dist/spawn-gate.js.map +1 -1
  239. package/dist/startup-memory-deadline.d.ts +23 -0
  240. package/dist/startup-memory-deadline.js +55 -0
  241. package/dist/startup-memory-deadline.js.map +1 -0
  242. package/dist/statusline-watcher.js.map +1 -1
  243. package/dist/storm-window.js.map +1 -1
  244. package/dist/systemd-peer-bus.js.map +1 -1
  245. package/dist/target-queue-worker.js.map +1 -1
  246. package/dist/task-board-summary.d.ts +4 -4
  247. package/dist/task-board-summary.js +4 -4
  248. package/dist/task-board-summary.js.map +1 -1
  249. package/dist/task-board-tool.js +3 -2
  250. package/dist/task-board-tool.js.map +1 -1
  251. package/dist/task-board-waker.js +8 -4
  252. package/dist/task-board-waker.js.map +1 -1
  253. package/dist/tips.js.map +1 -1
  254. package/dist/tmux-control.js.map +1 -1
  255. package/dist/tmux-exec-helper.js.map +1 -1
  256. package/dist/tmux-manager.d.ts +1 -0
  257. package/dist/tmux-manager.js +2 -1
  258. package/dist/tmux-manager.js.map +1 -1
  259. package/dist/tmux-read.js +1 -1
  260. package/dist/tmux-read.js.map +1 -1
  261. package/dist/tool-permissions.js.map +1 -1
  262. package/dist/tool-progress.js.map +1 -1
  263. package/dist/topic-archiver.js.map +1 -1
  264. package/dist/topic-commands.js.map +1 -1
  265. package/dist/topic-link.js.map +1 -1
  266. package/dist/transcript-delta-reader.js.map +1 -1
  267. package/dist/transcript-jsonl.js.map +1 -1
  268. package/dist/transcript-monitor.js.map +1 -1
  269. package/dist/transcript-sources.js.map +1 -1
  270. package/dist/tunnel/cloudflared-install.js.map +1 -1
  271. package/dist/tunnel/cloudflared.js.map +1 -1
  272. package/dist/tunnel/lease.js +3 -2
  273. package/dist/tunnel/lease.js.map +1 -1
  274. package/dist/tunnel/manager.js.map +1 -1
  275. package/dist/tunnel/public-fetch.js.map +1 -1
  276. package/dist/tunnel/purpose-lane.js.map +1 -1
  277. package/dist/tunnel/types.js.map +1 -1
  278. package/dist/types.d.ts +26 -2
  279. package/dist/ui/chat-render.js +1 -15
  280. package/dist/ui/chat-store.js +171 -61
  281. package/dist/ui/chat-strings.js +7 -2
  282. package/dist/ui/chat-thread.js +48 -1
  283. package/dist/ui/fleet-canvas-layout.js +3 -0
  284. package/dist/ui/fleet-canvas.js +23 -20
  285. package/dist/ui/fleet-office-editor.js +149 -0
  286. package/dist/ui/fleet-office-model.js +358 -0
  287. package/dist/ui/fleet-office-pack.js +199 -0
  288. package/dist/ui/fleet-office-skin.js +474 -0
  289. package/dist/ui/fleet-office-store.js +49 -0
  290. package/dist/ui/fleet-office-zones.js +177 -0
  291. package/dist/ui/fleet-office.js +434 -0
  292. package/dist/ui/fleet-org.js +18 -5
  293. package/dist/ui/guest-refused.html +41 -0
  294. package/dist/ui/image-lightbox.js +9 -3
  295. package/dist/ui/panel-chat.js +14 -13
  296. package/dist/ui/panel-settings.js +30 -12
  297. package/dist/ui/settings-dialogs.js +15 -6
  298. package/dist/ui/settings-model.js +40 -3
  299. package/dist/ui/settings-strings.js +38 -0
  300. package/dist/ui/settings-web-authority.js +7 -2
  301. package/dist/ui/shared/agend-auth.js +2 -0
  302. package/dist/ui/shared/app-i18n.js +2 -2
  303. package/dist/ui/shared/app-prefs.js +2 -0
  304. package/dist/ui/shared/app-shell.js +12 -0
  305. package/dist/ui/shared/app-store.js +1 -0
  306. package/dist/ui/shared/app-stream.js +2 -1
  307. package/dist/ui/shared/app.css +132 -3
  308. package/dist/ui/shared/guest-refused.js +35 -0
  309. package/dist/ui/shared/header-tools.js +2 -2
  310. package/dist/ui/shared/instance-nav.js +25 -8
  311. package/dist/ui/shared/ui-dialog.js +2 -2
  312. package/dist/ui/shared/usage-dialog.js +152 -6
  313. package/dist/ui/shared/view-strings.js +20 -0
  314. package/dist/ui/shared/web-authorize.js +3 -1
  315. package/dist/update-check.js.map +1 -1
  316. package/dist/update-dispatch.js.map +1 -1
  317. package/dist/update-install.js.map +1 -1
  318. package/dist/update-launch.js.map +1 -1
  319. package/dist/update-marker.js.map +1 -1
  320. package/dist/update-preflight.js.map +1 -1
  321. package/dist/usage/format-rich.js.map +1 -1
  322. package/dist/usage/providers.d.ts +8 -0
  323. package/dist/usage/providers.js +6 -2
  324. package/dist/usage/providers.js.map +1 -1
  325. package/dist/usage/statusline-usage.js.map +1 -1
  326. package/dist/usage/usage-api.js +2 -1
  327. package/dist/usage/usage-api.js.map +1 -1
  328. package/dist/usage-report.js.map +1 -1
  329. package/dist/usage-rollup.js +1 -1
  330. package/dist/usage-rollup.js.map +1 -1
  331. package/dist/user-systemd.js.map +1 -1
  332. package/dist/view-api.js +1 -1
  333. package/dist/view-api.js.map +1 -1
  334. package/dist/wake-coordinator.js.map +1 -1
  335. package/dist/web-ambient.js.map +1 -1
  336. package/dist/web-api.d.ts +15 -2
  337. package/dist/web-api.js +174 -25
  338. package/dist/web-api.js.map +1 -1
  339. package/dist/web-app-icon.js.map +1 -1
  340. package/dist/web-appearance-policy.js.map +1 -1
  341. package/dist/web-appearance-tools.js.map +1 -1
  342. package/dist/web-appearance-view.js.map +1 -1
  343. package/dist/web-appearance.js.map +1 -1
  344. package/dist/web-auth.js +5 -4
  345. package/dist/web-auth.js.map +1 -1
  346. package/dist/web-backend-login.js.map +1 -1
  347. package/dist/web-card.js.map +1 -1
  348. package/dist/web-channel-echo.js.map +1 -1
  349. package/dist/web-chat-history.d.ts +41 -11
  350. package/dist/web-chat-history.js +93 -22
  351. package/dist/web-chat-history.js.map +1 -1
  352. package/dist/web-commands.js.map +1 -1
  353. package/dist/web-effect-bundle.d.ts +2 -2
  354. package/dist/web-effect-library.js.map +1 -1
  355. package/dist/web-effect-playback.js.map +1 -1
  356. package/dist/web-effect-profiles.d.ts +16 -16
  357. package/dist/web-effect-profiles.js +3 -2
  358. package/dist/web-effect-profiles.js.map +1 -1
  359. package/dist/web-effect-service.js.map +1 -1
  360. package/dist/web-effect-tools.js.map +1 -1
  361. package/dist/web-effect-trials.js.map +1 -1
  362. package/dist/web-effect-upload.js.map +1 -1
  363. package/dist/web-effect.d.ts +7 -7
  364. package/dist/web-effect.js.map +1 -1
  365. package/dist/web-experimental.js.map +1 -1
  366. package/dist/web-guest-access.js +1 -1
  367. package/dist/web-guest-access.js.map +1 -1
  368. package/dist/web-guest-rooms.js.map +1 -1
  369. package/dist/web-host-guard.js.map +1 -1
  370. package/dist/web-icons.js.map +1 -1
  371. package/dist/web-identity-color.js.map +1 -1
  372. package/dist/web-images.js.map +1 -1
  373. package/dist/web-libavoid.js.map +1 -1
  374. package/dist/web-login.js.map +1 -1
  375. package/dist/web-mentions.js.map +1 -1
  376. package/dist/web-mod-agent.js.map +1 -1
  377. package/dist/web-mod-store.js.map +1 -1
  378. package/dist/web-office-store.d.ts +104 -0
  379. package/dist/web-office-store.js +236 -0
  380. package/dist/web-office-store.js.map +1 -0
  381. package/dist/web-owner-css-store.js.map +1 -1
  382. package/dist/web-owner-css.js.map +1 -1
  383. package/dist/web-png.js.map +1 -1
  384. package/dist/web-prefs.js +2 -0
  385. package/dist/web-prefs.js.map +1 -1
  386. package/dist/web-profile.js.map +1 -1
  387. package/dist/web-reactions.d.ts +2 -1
  388. package/dist/web-request-context.d.ts +5 -0
  389. package/dist/web-request-context.js.map +1 -1
  390. package/dist/web-returning-device.js +1 -1
  391. package/dist/web-returning-device.js.map +1 -1
  392. package/dist/web-schedules.js.map +1 -1
  393. package/dist/web-send-dedupe.d.ts +36 -0
  394. package/dist/web-send-dedupe.js +69 -0
  395. package/dist/web-send-dedupe.js.map +1 -0
  396. package/dist/web-session-authorization.js.map +1 -1
  397. package/dist/web-session.js +1 -1
  398. package/dist/web-session.js.map +1 -1
  399. package/dist/web-settings-patch.d.ts +1 -0
  400. package/dist/web-settings-patch.js +6 -4
  401. package/dist/web-settings-patch.js.map +1 -1
  402. package/dist/web-settings-tools.js.map +1 -1
  403. package/dist/web-terminal-http.js.map +1 -1
  404. package/dist/web-terminal.js.map +1 -1
  405. package/dist/web-theme-store.js.map +1 -1
  406. package/dist/web-upload.d.ts +5 -1
  407. package/dist/web-upload.js +6 -4
  408. package/dist/web-upload.js.map +1 -1
  409. package/dist/web-widget.js.map +1 -1
  410. package/dist/webhook-emitter.js.map +1 -1
  411. package/dist/windows-shortcuts.js +1 -1
  412. package/dist/windows-shortcuts.js.map +1 -1
  413. package/dist/workflow-templates/default.md +1 -1
  414. package/dist/ws-server.js.map +1 -1
  415. package/dist/yaml-shared-nodes.js.map +1 -1
  416. package/launcher/agend-install.cjs +281 -6
  417. package/launcher/private-home.cjs +43 -0
  418. package/package.json +3 -2
  419. package/templates/windows-shortcuts.ps1 +1 -1
package/dist/types.d.ts CHANGED
@@ -157,7 +157,10 @@ export interface InstanceConfig {
157
157
  * default UI; "v3" opts into the experimental next-generation agent.
158
158
  */
159
159
  kiro_ui?: "legacy" | "tui" | "v3";
160
- /** MCP tool profile: "full" (20 tools), "standard" (8), "minimal" (3). Default: "full" */
160
+ /**
161
+ * MCP tool profile (src/tool-permissions.ts): "worker", "coordinator", "standard", "minimal" or "full". Unset: the
162
+ * fleet default's, else "general" for a General, else "worker".
163
+ */
161
164
  tool_set?: string;
162
165
  /**
163
166
  * Tool-progress detail shown in the channel processing bubble.
@@ -178,6 +181,14 @@ export interface InstanceConfig {
178
181
  * reads it at each claim; the daemon only keeps it current.
179
182
  */
180
183
  board_cards_at_once?: number;
184
+ /**
185
+ * Which dashboard features this instance may use, on top of its `tool_set` (dashboard-permissions.ts): `appearance`
186
+ * (its own look: theme, pictures, widgets, ambient, effects, mod proposals), `task_board` (the `task` tool) and
187
+ * `web_settings` (Settings → Web appearance for the person). `false` switches one off; unset allows. `defaults.dashboard`
188
+ * sets it for every instance and merges key by key with an instance's own. The fleet reads it at each tool call; the
189
+ * daemon keeps it current, and an agent's tool menu follows from its next start.
190
+ */
191
+ dashboard?: import("./dashboard-permissions.js").DashboardAccess;
181
192
  /**
182
193
  * Detect a human-channel turn that ended without a delivered reply and ask
183
194
  * the agent to send one bounded recovery reply. Only capable backends use
@@ -350,7 +361,11 @@ export interface WebConfig {
350
361
  experimental?: {
351
362
  canvas_worker?: boolean;
352
363
  };
353
- /** Only menu availability; every exposure requires explicit admin consent. */
364
+ /**
365
+ * Only menu availability; every exposure requires explicit admin consent. `ttl_minutes`: 1..480 for a quick tunnel;
366
+ * with a fixed `hostname` (named tunnel) 1..10080, or 0 = no expiry. `auto_open` (named only, default false): reopen
367
+ * the link after the fleet starts, for the admin who last opened it — no sign-in code is sent by that.
368
+ */
354
369
  public_link?: {
355
370
  allow_public?: boolean;
356
371
  ttl_minutes?: number;
@@ -358,6 +373,7 @@ export interface WebConfig {
358
373
  hostname?: string;
359
374
  tunnel_token_env?: string;
360
375
  origin_port?: number;
376
+ auto_open?: boolean;
361
377
  };
362
378
  /** Echo web chat to the owning fleet topic (default true); never ClassicBot rooms. */
363
379
  echo_to_channel?: boolean;
@@ -472,6 +488,12 @@ export interface WebVerifiedAuthorityConfig {
472
488
  loopback_only?: boolean;
473
489
  /** A session's authorization lasts this many hours after it was given, 1–48 (default 48). */
474
490
  max_session_hours?: number;
491
+ /**
492
+ * #2453 (§0.2): sessions over the admin's own named tunnel (`web.public_link.hostname`) may be authorized too — off by
493
+ * default. Turning it on is confirmed in a General or on the host (never by the asking session); off applies at once
494
+ * and every receipt from a named-link session reads as none. A quick tunnel never qualifies.
495
+ */
496
+ named_link?: boolean;
475
497
  }
476
498
  /** #1920: fleet.yaml `web.guests`. */
477
499
  export interface WebGuestsConfig {
@@ -541,9 +563,11 @@ export interface FleetConfig {
541
563
  health_port?: number;
542
564
  web?: WebConfig;
543
565
  /** #1386 "Needs you": the live list in each world's General (default on) and DMs to that world's admins (default off). */
566
+ /** `task_board` (default false, #2425): the task board's cards that wait for the person are Needs-you items too. */
544
567
  needs_you?: {
545
568
  live_message?: boolean;
546
569
  dm?: boolean;
570
+ task_board?: boolean;
547
571
  };
548
572
  /** Host name used when building URLs handed to users (/dashboard, web terminal). Default "localhost". */
549
573
  hostname?: string;
@@ -516,20 +516,6 @@
516
516
  return "send";
517
517
  }
518
518
 
519
- /**
520
- * A send that failed: where does its text go? Back into the composer only if that composer is the same
521
- * target's, still on screen and empty; otherwise it is kept aside for that target (never put into another
522
- * target's composer, never over a draft someone is typing) and offered back when that chat is open.
523
- */
524
- function settleFailedSend(target, current, composerPresent, composerValue) {
525
- return target === current && composerPresent && !composerValue ? "restore" : "keep";
526
- }
527
-
528
- /** "Put back" for a kept message: in front of whatever is being typed now, never replacing it. */
529
- function putBack(failedText, draft) {
530
- return draft ? failedText + "\n" + draft : failedText;
531
- }
532
-
533
519
  /** The defaults of the upload limits the server enforces (web-upload.ts UPLOAD_LIMITS), for a page that was given none. */
534
520
  var FILE_LIMITS = { maxFileBytes: 50 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 100 * 1024 * 1024 };
535
521
  /**
@@ -666,7 +652,7 @@
666
652
  return {
667
653
  renderMarkdown: renderMarkdown, escapeHtml: escapeHtml, mergeMessages: mergeMessages, applyReplyButtons: applyReplyButtons, composerKey: composerKey,
668
654
  pollRemaining: pollRemaining,
669
- settleFailedSend: settleFailedSend, putBack: putBack,
655
+
670
656
  FILE_LIMITS: FILE_LIMITS, fileLimits: fileLimits, fileRejection: fileRejection, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
671
657
  nextDeliveryState: nextDeliveryState, applyDelivery: applyDelivery, deliveryHtml: deliveryHtml, isBusy: isBusy,
672
658
  applyReactions: applyReactions, reactionsHtml: reactionsHtml,
@@ -42,7 +42,7 @@ const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
42
42
  /** Build a store. `deps` (fetch, toast, announce, t, now) are injected so tests can drive every path. */
43
43
  export function createChatStore(deps) {
44
44
  const s = {
45
- msgs: {}, drafts: {}, replies: {}, failedSends: {}, pendingFiles: {}, sending: {}, inFlightFiles: {}, sendFailures: {}, instanceStatus: {}, waking: {},
45
+ msgs: {}, drafts: {}, replies: {}, pendingFiles: {}, sending: {}, outgoing: {}, instanceStatus: {}, waking: {},
46
46
  prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), rbBusy: new Set(),
47
47
  pollMine: readPollMine(),
48
48
  // #1823 (#1828 review): the server's clock minus this device's. A poll's deadline is the server's; a page whose clock
@@ -108,6 +108,8 @@ export function createChatStore(deps) {
108
108
  liveReplies.message(m, extra);
109
109
  const before = s.msgs[m.instance];
110
110
  s.msgs[m.instance] = R().mergeMessages(before, [m], 500);
111
+ // The live copy of a message this page sent carries its clientId: its pending bubble goes in this same redraw.
112
+ if (settleOutgoing(m.instance, m.role === "user" ? m.clientId : undefined) && s.msgs[m.instance] === before) changed(m.instance, "msgs");
111
113
  if (m.instance === s.current && s.msgs[m.instance] !== before && m.role !== "status" && !isUser(m, m.instance) && (s.msgs[m.instance] || []).length > (before || []).length) deps.announce(tf("chat.a11yReplied", m.sender));
112
114
  if (s.msgs[m.instance] !== before) changed(m.instance, "msgs");
113
115
  }
@@ -166,6 +168,7 @@ export function createChatStore(deps) {
166
168
  // Kept even when the person has moved on: the store is page-lived, and the answer belongs to `name`.
167
169
  for (const m of r.messages) liveReplies.message(m);
168
170
  s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500);
171
+ settleOutgoing(name);
169
172
  changed(name, "msgs");
170
173
  } catch { s.historyRead.delete(name); /* the live stream still works; the next visit tries again */ }
171
174
  void lease;
@@ -242,71 +245,186 @@ export function createChatStore(deps) {
242
245
  }
243
246
  const promptsFor = (instance) => Object.values(s.prompts).filter(p => p.instance === instance);
244
247
 
245
- // ── Sending. Never lose anything: a failed send's files go back to that chat's list and its text back into its own
246
- // composer when that is empty, otherwise it is kept for its chat and offered back (Put back).
247
- async function uploadFile(target, file) {
248
- const r = await deps.fetch(`/ui/upload?instance=${encodeURIComponent(target)}`, {
249
- method: "POST",
250
- headers: { "Content-Type": file.type || "application/octet-stream", "X-Agend-Filename": encodeURIComponent(file.name || "file") },
251
- body: file,
248
+ // ── Sending (Discord-like, the user's request): a message shows at once as its own bubble, dimmed while it goes; its
249
+ // files show their upload progress. The server's copy replaces it when it arrives — matched by the page's clientId
250
+ // (on the live copy) or the messageId the send answered with — so it is never shown twice. A send that fails stays,
251
+ // marked, with Retry and Delete: nothing is lost, and nothing is put back into the composer behind the person's back.
252
+ // Sends of one chat go out in order, one at a time; a later one waits behind an earlier one still going.
253
+ let outSeq = 0;
254
+ const objectUrl = (f) => { try { return globalThis.URL && typeof URL.createObjectURL === "function" && /^image\/(png|jpeg|gif|webp)$/.test(f.type || "") ? URL.createObjectURL(f) : null; } catch { return null; } };
255
+ const freeUrl = (u) => { try { if (u) URL.revokeObjectURL(u); } catch { /* gone */ } };
256
+ const newClientId = () => {
257
+ const bytes = new Uint8Array(12);
258
+ try { globalThis.crypto.getRandomValues(bytes); } catch { for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256); }
259
+ return "c" + [...bytes].map((x) => x.toString(16).padStart(2, "0")).join("");
260
+ };
261
+ /** Pending bubbles the server's copy now stands for go (their previews freed). Returns whether any went. */
262
+ function settleOutgoing(name, clientId) {
263
+ const list = s.outgoing[name];
264
+ if (!list || !list.length) return false;
265
+ const ids = new Set((s.msgs[name] || []).map((m) => m.messageId).filter(Boolean)), clients = new Set(clientId ? [clientId] : []);
266
+ const kept = list.filter((e) => !((e.messageId && ids.has(e.messageId)) || clients.has(e.clientId)));
267
+ if (kept.length === list.length) return false;
268
+ for (const e of list) if (!kept.includes(e)) for (const f of e.files) freeUrl(f.url);
269
+ s.outgoing[name] = kept;
270
+ return true;
271
+ }
272
+ /** The thread as drawn: the server's messages, then this page's pending ones. */
273
+ function threadRows(name) {
274
+ const out = s.outgoing[name];
275
+ return out && out.length ? (s.msgs[name] || []).concat(out) : (s.msgs[name] || []);
276
+ }
277
+ /** Upload one file, its progress told as it goes (XHR: fetch cannot report an upload's progress). */
278
+ async function uploadFile(target, file, onProgress) {
279
+ const url = `/ui/upload?instance=${encodeURIComponent(target)}`;
280
+ const X = deps.XMLHttpRequest;
281
+ if (!X) { // no XHR given (a test): the whole file at once
282
+ const r = await deps.fetch(url, { method: "POST", headers: { "Content-Type": file.type || "application/octet-stream", "X-Agend-Filename": encodeURIComponent(file.name || "file") }, body: file });
283
+ let body = {};
284
+ try { body = await r.json(); } catch { /* not JSON */ }
285
+ if (!r.ok || !body || typeof body.id !== "string") throw new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${r.status})`);
286
+ onProgress?.(file.size || 0, file.size || 0);
287
+ return body;
288
+ }
289
+ // The CSRF header the page's fetch adds (agend-auth.js), added here too; a 401 ends the session as fetch's would.
290
+ const csrf = globalThis.AgendAuth && typeof globalThis.AgendAuth.csrf === "function" ? await globalThis.AgendAuth.csrf().catch(() => null) : null;
291
+ return new Promise((resolve, reject) => {
292
+ const xhr = new X();
293
+ xhr.open("POST", url);
294
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
295
+ xhr.setRequestHeader("X-Agend-Filename", encodeURIComponent(file.name || "file"));
296
+ if (csrf) xhr.setRequestHeader("X-Agend-CSRF", csrf);
297
+ if (xhr.upload && onProgress) xhr.upload.onprogress = (ev) => { if (ev.lengthComputable) onProgress(ev.loaded, ev.total); };
298
+ xhr.onload = () => {
299
+ let body = {};
300
+ try { body = JSON.parse(xhr.responseText || "{}"); } catch { /* not JSON */ }
301
+ if (xhr.status === 401) globalThis.AgendAuth?.unauthorized?.();
302
+ if (xhr.status >= 200 && xhr.status < 300 && body && typeof body.id === "string") resolve(body);
303
+ else reject(new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${xhr.status})`));
304
+ };
305
+ xhr.onerror = () => reject(new Error(`${file.name}: ${t("chat.uploadNoConnection")}`));
306
+ xhr.onabort = () => reject(new Error(`${file.name}: ${t("chat.uploadNoConnection")}`));
307
+ xhr.send(file);
252
308
  });
253
- let body = {};
254
- try { body = await r.json(); } catch { /* not JSON */ }
255
- if (!r.ok || !body || typeof body.id !== "string") throw new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${r.status})`);
256
- return body;
257
309
  }
258
- async function send(target) {
310
+ /** Send what the composer holds: its bubble at once, then out in this chat's order. */
311
+ function send(target) {
259
312
  const txt = (s.drafts[target] || "").trim();
260
- const quote = s.replies[target]; // #2272: the message this one replies to (its row; the server reads the rest)
261
313
  const files = (s.pendingFiles[target] || []).slice();
262
- if ((!txt && !files.length) || !target || s.sending[target]) return;
263
- s.sending[target] = true;
264
- s.waking[target] = s.instanceStatus[target] === "paused";
265
- delete s.sendFailures[target];
314
+ if ((!txt && !files.length) || !target) return;
315
+ const quote = s.replies[target];
316
+ const e = {
317
+ // "You" until the server's copy (with the person's name) replaces it: no request is made for a name.
318
+ boot: "out", id: ++outSeq, instance: target, role: "user", sender: t("chat.you"), text: txt,
319
+ ts: new Date().toISOString(), clientId: newClientId(), replyTo: quote ? { boot: quote.boot, id: quote.id, sender: quote.sender, text: quote.text, media: quote.media } : undefined,
320
+ quote, outgoing: { state: "queued" },
321
+ files: files.map((f) => ({ file: f, name: f.name || "file", size: f.size || 0, type: f.type || "", url: objectUrl(f), loaded: 0, total: f.size || 0, state: "waiting", id: null })),
322
+ };
323
+ (s.outgoing[target] = s.outgoing[target] || []).push(e);
266
324
  s.drafts[target] = "";
267
- s.pendingFiles[target] = []; s.inFlightFiles[target] = files;
325
+ s.pendingFiles[target] = [];
326
+ if (quote) delete s.replies[target];
327
+ changed(target, "msgs");
268
328
  changed(target);
269
329
  changed(target, "sent"); // your own message: the thread goes to the bottom
330
+ return pump(target);
331
+ }
332
+ /** Take this chat's waiting sends, one after another. */
333
+ async function pump(target) {
334
+ if (s.sending[target]) return;
335
+ s.sending[target] = true;
336
+ try {
337
+ for (let e = nextQueued(target); e; e = nextQueued(target)) await deliver(target, e);
338
+ } finally { s.sending[target] = false; delete s.waking[target]; changed(target); }
339
+ }
340
+ const nextQueued = (target) => (s.outgoing[target] || []).find((x) => x.outgoing.state === "queued");
341
+ /** One send: its files (each with its own progress), then the message. */
342
+ async function deliver(target, e) {
343
+ e.outgoing = { state: "sending" };
344
+ s.waking[target] = s.instanceStatus[target] === "paused";
345
+ changed(target, "msgs"); changed(target);
270
346
  let r;
271
347
  try {
272
- const ids = [];
273
- for (const f of files) ids.push((await uploadFile(target, f)).id);
274
- const body = ids.length ? { instance: target, message: txt, attachments: ids } : { instance: target, message: txt };
275
- if (quote) body.replyTo = { boot: quote.boot, id: quote.id };
348
+ for (const f of e.files) {
349
+ if (f.id) continue; // uploaded by an earlier try
350
+ f.state = "uploading"; f.loaded = 0; changed(target, "progress");
351
+ let last = 0;
352
+ const body = await uploadFile(target, f.file, (loaded, total) => {
353
+ f.loaded = loaded; f.total = total || f.total;
354
+ const at = deps.now ? deps.now() : Date.now();
355
+ if (at - last > 60 || loaded >= f.total) { last = at; changed(target, "progress"); }
356
+ });
357
+ f.id = body.id; f.state = "done"; f.loaded = f.total; changed(target, "progress");
358
+ }
359
+ const body = { instance: target, message: e.text, clientId: e.clientId };
360
+ const ids = e.files.map((f) => f.id);
361
+ if (ids.length) body.attachments = ids;
362
+ if (e.quote) body.replyTo = { boot: e.quote.boot, id: e.quote.id };
276
363
  r = await api("POST", "/ui/send", body);
277
- } catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
278
- s.sending[target] = false;
279
- delete s.waking[target];
280
- delete s.inFlightFiles[target];
281
- if (r && !r.error) { if (s.replies[target] === quote) delete s.replies[target]; changed(target); return; }
282
- // #2272: the quoted message is gone: the reply goes, the text and files stay (sent again without it, or re-aimed).
283
- if (r && r.reason === "reply_gone" && s.replies[target] === quote) delete s.replies[target];
284
- if (files.length) s.pendingFiles[target] = R().checkFiles(files, s.pendingFiles[target] || []).kept;
285
- if (txt) {
286
- const shown = s.current === target;
287
- if (R().settleFailedSend(target, s.current, shown, shown ? (s.drafts[target] || "") : "") === "restore") s.drafts[target] = txt;
288
- else s.failedSends[target] = s.failedSends[target] ? `${s.failedSends[target]}\n${txt}` : txt;
364
+ } catch (err) {
365
+ const f = e.files.find((x) => x.state === "uploading");
366
+ if (f) f.state = "failed";
367
+ // The browser's own words for a request that never reached the server (Chrome, Firefox, Safari): the page's.
368
+ const offline = err && err.name === "TypeError" && /failed to fetch|networkerror|load failed/i.test(err.message || "");
369
+ r = { error: offline ? t("chat.sendNoConnection") : err && err.message ? err.message : t("chat.disconnected") };
289
370
  }
371
+ if (!(s.outgoing[target] || []).includes(e)) return; // deleted meanwhile
372
+ if (r && !r.error && r.sent !== false) {
373
+ e.outgoing = { state: "sent" };
374
+ if (typeof r.messageId === "string") e.messageId = r.messageId;
375
+ settleOutgoing(target);
376
+ changed(target, "msgs"); changed(target);
377
+ return;
378
+ }
379
+ // Files the server no longer holds (they are released when a delivery fails) are uploaded again on Retry.
380
+ if (r && typeof r.error === "string" && /files? (are|is) not available|no such file|already sent/i.test(r.error)) for (const f of e.files) { f.id = null; f.state = "waiting"; }
381
+ // #2272: the quoted message is gone: Retry sends it without the quote.
382
+ if (r && r.reason === "reply_gone") { e.quote = undefined; e.replyTo = undefined; }
290
383
  const why = r && r.reason;
291
384
  const key = typeof why === "string" ? `chat.sendFailure_${why}` : "";
292
385
  const localized = key ? t(key) : "";
293
- const error = localized && localized !== key ? localized : (r && r.error ? r.error : t("chat.disconnected"));
294
- s.sendFailures[target] = { error, text: txt, files };
295
- changed(target);
296
- deps.toast(error, false);
386
+ e.outgoing = { state: "failed", error: localized && localized !== key ? localized : (r && r.error ? r.error : t("chat.disconnected")) };
387
+ changed(target, "msgs"); changed(target);
388
+ deps.toast(e.outgoing.error, false);
389
+ }
390
+ /** A failed send, sent again (the same bubble; files already uploaded are not uploaded again). */
391
+ function retryOutgoing(target, clientId) {
392
+ const e = (s.outgoing[target] || []).find((x) => x.clientId === clientId);
393
+ if (!e || e.outgoing.state !== "failed") return;
394
+ e.outgoing = { state: "queued" };
395
+ for (const f of e.files) if (f.state === "failed") f.state = "waiting";
396
+ changed(target, "msgs");
397
+ return pump(target);
398
+ }
399
+ /** A failed send, let go (its previews freed). */
400
+ function deleteOutgoing(target, clientId) {
401
+ const list = s.outgoing[target] || [];
402
+ const e = list.find((x) => x.clientId === clientId);
403
+ if (!e || e.outgoing.state !== "failed") return;
404
+ for (const f of e.files) freeUrl(f.url);
405
+ s.outgoing[target] = list.filter((x) => x !== e);
406
+ changed(target, "msgs"); changed(target);
297
407
  }
298
- function canRetrySend(target) {
299
- const failed = s.sendFailures[target], files = s.pendingFiles[target] || [];
300
- return !!failed && !s.sending[target] && !s.failedSends[target]
301
- && (s.drafts[target] || "").trim() === failed.text
302
- && files.length === failed.files.length && files.every((f, i) => f === failed.files[i]);
408
+ /**
409
+ * A failed send, back in the composer to change it: its text before what is typed there now, its files (within the
410
+ * limits) and its reply, if none is started; the bubble goes. The page records the composer's undo step first.
411
+ */
412
+ function putBackOutgoing(target, clientId) {
413
+ const list = s.outgoing[target] || [];
414
+ const e = list.find((x) => x.clientId === clientId);
415
+ if (!e || e.outgoing.state !== "failed") return;
416
+ const draft = s.drafts[target] || "";
417
+ s.drafts[target] = e.text ? (draft ? e.text + "\n" + draft : e.text) : draft;
418
+ for (const f of e.files) freeUrl(f.url);
419
+ s.outgoing[target] = list.filter((x) => x !== e);
420
+ if (e.quote && !s.replies[target]) s.replies[target] = e.quote;
421
+ changed(target, "msgs");
422
+ if (e.files.length) addFiles(target, e.files.map((f) => f.file)); else changed(target);
303
423
  }
304
- function retrySend(target) { if (canRetrySend(target)) return send(target); }
305
424
  function addFiles(target, list) {
306
425
  if (!target) return;
307
- const inFlight = s.inFlightFiles[target] || [];
308
- const res = R().checkFiles(inFlight.concat(s.pendingFiles[target] || []), [...(list || [])]);
309
- s.pendingFiles[target] = res.kept.slice(inFlight.length);
426
+ const res = R().checkFiles(s.pendingFiles[target] || [], [...(list || [])]);
427
+ s.pendingFiles[target] = res.kept;
310
428
  for (const x of res.rejected) { const why = R().fileRejection(x.reason); deps.toast(`${x.name}: ${t(why.key, ...why.values)}`, false); }
311
429
  changed(target);
312
430
  }
@@ -316,7 +434,7 @@ export function createChatStore(deps) {
316
434
  if (!target) return false;
317
435
  const stamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "");
318
436
  const f = new File([text], `pasted-${stamp}.txt`, { type: "text/plain" });
319
- const taken = (s.inFlightFiles[target] || []).concat(s.pendingFiles[target] || []);
437
+ const taken = s.pendingFiles[target] || [];
320
438
  if (R().checkFiles(taken, [f]).rejected.length) { deps.toast(t("chat.pasteKeptAsText")); return false; }
321
439
  pastedTexts.set(f, text);
322
440
  addFiles(target, [f]);
@@ -333,16 +451,8 @@ export function createChatStore(deps) {
333
451
  changed(target);
334
452
  }
335
453
  const isPasted = (f) => pastedTexts.has(f);
336
- function putBack(target) {
337
- const kept = s.failedSends[target];
338
- if (!kept) return;
339
- s.drafts[target] = R().putBack(kept, s.drafts[target] || "");
340
- delete s.failedSends[target];
341
- changed(target);
342
- }
343
- function discardFailed(target) { delete s.failedSends[target]; changed(target); }
344
- /** Typing stays local, except a failed send's Retry button must follow edits too. */
345
- function setDraft(target, value) { s.drafts[target] = value; if (s.sendFailures[target]) changed(target); }
454
+ /** Typing stays local. */
455
+ function setDraft(target, value) { s.drafts[target] = value; }
346
456
  /**
347
457
  * #2272: reply to a message of this chat — what the bar above the composer shows (its sender and first words); only
348
458
  * its row is sent. One at a time per chat: a new one replaces it.
@@ -363,7 +473,7 @@ export function createChatStore(deps) {
363
473
  if (has()) return true;
364
474
  try {
365
475
  const r = await api("GET", `/ui/history?instance=${encodeURIComponent(name)}&limit=500`);
366
- if (r && Array.isArray(r.messages)) { s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500); changed(name, "msgs"); }
476
+ if (r && Array.isArray(r.messages)) { s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500); settleOutgoing(name); changed(name, "msgs"); }
367
477
  } catch { /* the jump says it is not there */ }
368
478
  return has();
369
479
  }
@@ -461,7 +571,7 @@ export function createChatStore(deps) {
461
571
  attach, applyStatus, applyActivity, ingest, applyDeliveries, openHistory,
462
572
  onPrompt, applyPrompts, resolvePrompt, answerPrompt, answerByNonce, holdPrompt, releasePrompt, promptsFor,
463
573
  applyReplyButtons, clickReplyButton,
464
- uploadFile, send, canRetrySend, retrySend, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, putBack, discardFailed, setDraft,
574
+ uploadFile, send, retryOutgoing, deleteOutgoing, putBackOutgoing, threadRows, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, setDraft,
465
575
  startReply, clearReply, findQuoted,
466
576
  cancelReply, isUser, runCommand, dismissCommand, commandFree,
467
577
  };
@@ -4,6 +4,9 @@ import { register } from "/assets/app-i18n.js";
4
4
  register("chat", {
5
5
  en: {
6
6
  waking: "Waking {0}…", retrySend: "Retry send",
7
+ // A message being sent (its own bubble at once) and its files' uploads.
8
+ you: "You", sending: "Sending…", uploadingFile: "Uploading {0}", uploadWaiting: "waiting", uploadFailed: "failed",
9
+ uploadNoConnection: "the upload was cut off — check the connection", sendNoConnection: "it did not reach AgEnD — check the connection", notSentBecause: "Not sent: {0}", deleteUnsent: "Delete",
7
10
  sendFailure_wake_capacity: "No slot is free to wake this instance. Retry when another instance is idle.",
8
11
  sendFailure_wake_credentials: "The instance could not sign in. Check its account on the host, then retry.",
9
12
  sendFailure_wake_timeout: "The instance did not become ready in time. Retry, or check its status on the host.",
@@ -110,7 +113,7 @@ register("chat", {
110
113
  disconnected: "disconnected", tick_queued: "Waiting behind another message", tick_processing: "Handed to the agent", tick_delivered: "The agent has it", tick_failed: "Not delivered", tick_cancelled: "Dropped by Stop", reactedBy: "{0} reacted",
111
114
  promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
112
115
  working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
113
- file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", notSent: "Not sent:", putBack: "Put back", discard: "Discard",
116
+ file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
114
117
  noMessages: "Type a task below.",
115
118
  pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
116
119
  details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
@@ -143,6 +146,8 @@ register("chat", {
143
146
  },
144
147
  "zh-TW": {
145
148
  waking: "正在喚醒 {0}…", retrySend: "重試傳送",
149
+ you: "你", sending: "傳送中…", uploadingFile: "正在上傳 {0}", uploadWaiting: "等待中", uploadFailed: "失敗",
150
+ uploadNoConnection: "上傳中斷了,請檢查網路連線", sendNoConnection: "沒有送到 AgEnD,請檢查網路連線", notSentBecause: "沒有送出:{0}", deleteUnsent: "刪除",
146
151
  sendFailure_wake_capacity: "目前沒有空位可喚醒這個實例。請等其他實例閒置後重試。",
147
152
  sendFailure_wake_credentials: "實例無法登入。請先在主機檢查它的帳號,再重試。",
148
153
  sendFailure_wake_timeout: "實例未能及時就緒。請重試,或在主機檢查它的狀態。",
@@ -247,7 +252,7 @@ register("chat", {
247
252
  disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消", reactedBy: "{0} 的回應",
248
253
  promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
249
254
  working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
250
- file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", notSent: "未送出:", putBack: "放回輸入框", discard: "捨棄",
255
+ file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
251
256
  noMessages: "直接在下方輸入任務。",
252
257
  pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
253
258
  details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
@@ -132,6 +132,7 @@ export function createThread(list, scroller, opts) {
132
132
  // #1770: the label ("17:38", "Yesterday 17:38", "10/11 17:38") depends on today, so it is not part of the html a node
133
133
  // is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
134
134
  const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
135
+ if (x.outgoing) return outgoingHtml(x);
135
136
  const trial = readCanvasTrialMessage(x);
136
137
  if (trial) return canvasWorkerEnabled() ? trialHtml(trial, time, x) : "";
137
138
  if (x.role === "status") return statusHtml(x, time);
@@ -147,6 +148,48 @@ export function createThread(list, scroller, opts) {
147
148
  const act = opts.canReply?.() === false ? "" : `<div data-trust="thread-control" class="msg-actions"><button type="button" class="chip-btn icon-only" data-act="reply" data-arg="${escAttr(key)}" title="${escAttr(tr("chat.reply"))}" aria-label="${escAttr(tr("chat.reply"))}">${REPLY}</button></div>`;
148
149
  return `<div class="msg ${u ? "user" : "agent"}" data-key="${escAttr(key)}" tabindex="-1">${act}<div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div>${quoteHtml(x.replyTo)}<div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${renderedMarkdown(x.text, x.role === "agent", opts.emojis, opts.mentions)}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${R().reactionsHtml(x.reactions, tr("chat.reactedBy", x.instance || ""))}${tools}</div>`;
149
150
  }
151
+ /**
152
+ * A message this page is sending (chat-store.js): its own bubble at once, dimmed with "Sending…" until the server
153
+ * takes it, then as sent until the server's copy replaces it; failed: marked, with Retry and Delete. Each file shows
154
+ * its upload — a picture as itself (a local preview), anything else by name — with its own bar, moved in place by
155
+ * progress() (the html does not hold the numbers, so a moving bar never redraws the bubble). No data-key: it is no
156
+ * message yet (nothing replies to it, links to it or copies it).
157
+ */
158
+ function outgoingHtml(x) {
159
+ const st = x.outgoing.state, going = st === "queued" || st === "sending";
160
+ const files = (x.files || []).map((f, i) => {
161
+ const pic = f.url ? `<img class="out-pic" src="${escAttr(f.url)}" alt="${escAttr(f.name)}">` : `<span class="out-doc" aria-hidden="true"></span>`;
162
+ return `<div class="out-file" data-i="${escAttr(i)}">${pic}<div class="out-file-info"><span class="out-name">${escAttr(f.name)}</span>`
163
+ + `<span class="out-bar" role="progressbar" aria-label="${escAttr(trf("chat.uploadingFile", f.name))}" aria-valuemin="0" aria-valuemax="100"><span class="out-fill"></span></span>`
164
+ + `<span class="out-pct"></span></div></div>`;
165
+ }).join("");
166
+ const failed = st === "failed" ? `<div data-trust="thread-control" class="out-note lbar" role="alert"><span class="out-error">${escAttr(trf("chat.notSentBecause", x.outgoing.error || ""))}</span>`
167
+ + `<span class="out-actions"><button type="button" class="btn btn-sm btn-primary" data-act="retryOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.retrySend"))}</button>`
168
+ + `<button type="button" class="btn btn-sm" data-act="putBackOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.putBack"))}</button>`
169
+ + `<button type="button" class="btn btn-sm" data-act="deleteOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.deleteUnsent"))}</button></span></div>` : "";
170
+ const state = going ? `<span class="out-state" role="status"><span class="out-spin" aria-hidden="true"></span>${escAttr(tr("chat.sending"))}</span>` : "";
171
+ return `<div class="msg user out out-${st}" data-out="${escAttr(x.clientId)}"${going ? ' aria-busy="true"' : ""} tabindex="-1"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${state}</div>`
172
+ + `${quoteHtml(x.replyTo)}<div class="body">${x.text ? `<div class="md">${renderedMarkdown(x.text, false, opts.emojis, opts.mentions)}</div>` : ""}`
173
+ + `${files ? `<div class="out-files">${files}</div>` : ""}</div>${failed}</div>`;
174
+ }
175
+ /** The pending bubbles' upload bars and percentages, as their files stand now (in place: CSSOM, no redraw). */
176
+ function progress() {
177
+ for (const v of nodes.values()) {
178
+ const x = v.x;
179
+ if (!x || !x.outgoing || !x.files || !x.files.length) continue;
180
+ for (const el of v.node.querySelectorAll(".out-file[data-i]")) {
181
+ const f = x.files[Number(el.dataset.i)];
182
+ if (!f) continue;
183
+ const pct = f.state === "done" ? 100 : f.total > 0 ? Math.min(100, Math.floor((f.loaded / f.total) * 100)) : 0;
184
+ el.dataset.state = f.state;
185
+ const bar = el.querySelector(".out-bar"), fill = el.querySelector(".out-fill"), label = el.querySelector(".out-pct");
186
+ if (bar) bar.setAttribute("aria-valuenow", String(pct));
187
+ if (fill) fill.style.transform = `scaleX(${pct / 100})`;
188
+ if (label) { const text = f.state === "failed" ? tr("chat.uploadFailed") : f.state === "waiting" ? tr("chat.uploadWaiting") : `${pct}%`; if (label.textContent !== text) label.textContent = text; }
189
+ }
190
+ }
191
+ }
192
+
150
193
  /**
151
194
  * #2272: what a reply quotes, above its body — the quoted sender and first words (or that it had files), as the server
152
195
  * read them when it was sent; a button that shows that message. A guest that may not see it gets only "an earlier
@@ -678,6 +721,7 @@ export function createThread(list, scroller, opts) {
678
721
  armDay();
679
722
  armPolls();
680
723
  rove();
724
+ progress();
681
725
  return added;
682
726
  }
683
727
  /**
@@ -777,6 +821,9 @@ export function createThread(list, scroller, opts) {
777
821
  } else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
778
822
  else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
779
823
  else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
824
+ else if (act === "retryOut") opts.retryOutgoing?.(el.dataset.arg); // a failed send, again
825
+ else if (act === "putBackOut") opts.putBackOutgoing?.(el.dataset.arg); // back in the composer, to change it
826
+ else if (act === "deleteOut") opts.deleteOutgoing?.(el.dataset.arg);
780
827
  }
781
828
  /**
782
829
  * #2272: show the message a reply quotes, flashed — drawn now, or after the chat reads back the longest history the
@@ -834,5 +881,5 @@ export function createThread(list, scroller, opts) {
834
881
  armPolls();
835
882
  }
836
883
 
837
- return { render, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
884
+ return { render, progress, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
838
885
  }
@@ -10,6 +10,9 @@
10
10
  */
11
11
  export const OC = { NW: 184, NH: 56, GAP: 28, PAD: 32, GAP_NARROW: 18, PAD_NARROW: 24, HEAD: 34, TEAM_GAP: 72, ROW_GAP: 96, ROW_MAX: 1400, ROW_MAX_NARROW: 460, LW: 232, LH: 68, TOP_Y: 20, BUS: 44, FOLDED: 64, TRUNK: -30 };
12
12
  const colsFor = (n, narrow) => (n <= 1 ? 1 : n <= 4 || narrow ? 2 : 3);
13
+ /** A search's needle, and whether a node matches it (its name or nickname): the canvas's Find, the Office view's too. */
14
+ export const needle = (q) => String(q || "").trim().toLowerCase();
15
+ export const hits = (node, q) => !!q && (node.name.toLowerCase().includes(q) || (!!node.i && typeof node.i.display_name === "string" && node.i.display_name.toLowerCase().includes(q)));
13
16
  /** The key a team folds by (per device): its name; the instances in no team have one of their own no team name can be. */
14
17
  export const REST_KEY = "\u0000rest";
15
18