@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/web-api.js CHANGED
@@ -53,6 +53,7 @@ import { resolveTz } from "./tz-utils.js";
53
53
  import { filesPlayInChat } from "./web-effect.js";
54
54
  import { QUERY_MAX_CHARS, RESULT_MAX, SEARCH_SOURCES } from "./search/search-store.js";
55
55
  import { xmsgShown } from "./search/search-docs.js";
56
+ import { WebSendDedupe } from "./web-send-dedupe.js";
56
57
  // ── Strict public-facing schemas ────────────────────────────────────────────
57
58
  // web-api endpoints must reject unknown fields so the dashboard cannot inject
58
59
  // internal-only flags that would reach handleCreate/scheduler/config writers.
@@ -169,6 +170,9 @@ const SendMessageSchema = z.object({
169
170
  attachments: z.array(z.string().regex(/^[0-9a-f]{32}$/)).max(UPLOAD_LIMITS.maxFiles).optional(),
170
171
  /** #2272: the message of this chat this one replies to — its row only; the server reads what it quotes. */
171
172
  replyTo: z.object({ boot: z.string().regex(/^[0-9a-f]{1,32}$/), id: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER) }).strict().optional(),
173
+ /** The sending page's own id for its pending bubble: echoed on the live event only (never recorded), so that page
174
+ * swaps its "sending" bubble for the real one even when the stream beats this answer. */
175
+ clientId: z.string().regex(/^[A-Za-z0-9_-]{8,64}$/).optional(),
172
176
  }).strict().refine(v => v.message.trim().length > 0 || (v.attachments?.length ?? 0) > 0, { message: "a message or at least one file", path: ["message"] });
173
177
  /** An instance name that is safe as one path segment (no separator, no NUL, not a dot name). */
174
178
  const safeInstanceSegment = (name) => /^[^\\/\x00]+$/.test(name) && name !== "." && name !== "..";
@@ -1304,7 +1308,14 @@ function webAuthorityOf(req, url, ctx, instance) {
1304
1308
  return { authority: noAuthority(0), refusal: "not_session" };
1305
1309
  const v = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
1306
1310
  return ctx.webAuthority({ session: v.kind === "allow" && v.via === "session" ? v.session : null, gateway: !!gatewayRequestContext(req),
1307
- peer: classifyWebPeer(req), ...(instance !== undefined ? { instance } : {}) });
1311
+ peer: webPeerOf(req), ...(instance !== undefined ? { instance } : {}) });
1312
+ }
1313
+ /**
1314
+ * #1939 §3.4 / #2453 §0.2: where this request came from — the admin's named link by the gateway's own provenance (its
1315
+ * exact hostname), else what the TCP peer and Host say (a quick tunnel's request is `lan`, and refused as public_link).
1316
+ */
1317
+ export function webPeerOf(req) {
1318
+ return gatewayRequestContext(req)?.named === true ? "named_link" : classifyWebPeer(req);
1308
1319
  }
1309
1320
  /**
1310
1321
  * #2122: what let this request in, asked now — its session (by handle, under the token it was signed in with) or the
@@ -1626,15 +1637,12 @@ export function handleWebRequest(req, res, url, ctx) {
1626
1637
  const after = url.searchParams.get("after");
1627
1638
  const fresh = after === null || after === ""; // a cursor that is not ours in shape still gets nothing
1628
1639
  if (guest) {
1629
- // #2085 review: and what became the channel's after its cursor (an older row, its copy sent since), once each.
1640
+ // #2085 review: with what became the channel's after its cursor (an older row, its copy sent since), once each —
1641
+ // replayFor counts a row where it was seen (seenSeq), as for every reader (#1774).
1630
1642
  json(res, 200, guestPollBody(ctx.getUiStatus(), guest.scope(), Date.now(), guest, after, () => {
1631
1643
  if (!history)
1632
1644
  return [];
1633
- if (fresh)
1634
- return history.after(0);
1635
- const cursor = parseLastEventId(after);
1636
- const rows = [...history.replayFor(cursor), ...history.seenAfter(cursor)];
1637
- return rows.filter((m, i) => rows.findIndex((x) => x.boot === m.boot && x.id === m.id) === i);
1645
+ return fresh ? history.after(0) : history.replayFor(parseLastEventId(after));
1638
1646
  }));
1639
1647
  return true;
1640
1648
  }
@@ -1972,6 +1980,100 @@ export function handleWebRequest(req, res, url, ctx) {
1972
1980
  });
1973
1981
  return true;
1974
1982
  }
1983
+ // ── Fleet → Org chart → Office: the offices people made (web-offices.json, web-office-store.ts) ──
1984
+ // GET /ui/offices: { rev, offices, readable } for whoever may open the org chart (the public link too: it reads the
1985
+ // chart; a guest's routes do not include it). PUT /ui/offices { offices, rev? }: a person's change, as a theme's reset
1986
+ // or lock is — an admin session only, never the CLI token, never the public link (absent from its manifest, and
1987
+ // refused here too); asked again once the body is in. Every page hears `offices` on the stream and reads again.
1988
+ if (method === "GET" && path === "/ui/offices") {
1989
+ const store = ctx.officeStore;
1990
+ if (!store) {
1991
+ json(res, 404, { error: "Offices are not kept on this fleet" });
1992
+ return true;
1993
+ }
1994
+ json(res, 200, store.list());
1995
+ return true;
1996
+ }
1997
+ if (method === "PUT" && path === "/ui/offices") {
1998
+ const store = ctx.officeStore;
1999
+ if (!store) {
2000
+ json(res, 404, { error: "Offices are not kept on this fleet" });
2001
+ return true;
2002
+ }
2003
+ if (gatewayRequestContext(req)) {
2004
+ json(res, 403, { error: "offices_not_over_public_link" });
2005
+ return true;
2006
+ }
2007
+ // The person (an admin session's account), or the status that refuses the request.
2008
+ const who = () => {
2009
+ const verdict = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
2010
+ if (verdict.kind !== "allow")
2011
+ return { status: 401 };
2012
+ return verdict.via === "session" && verdict.session?.tier === "admin" ? { person: verdict.session.owner ? webPersonKey(verdict.session.owner) : "admin-session" } : { status: 403 };
2013
+ };
2014
+ const refuse = (status) => json(res, status, { error: status === 401 ? WEB_TOKEN_INVALID_MESSAGE : "offices_need_an_admin_session" });
2015
+ const first = who();
2016
+ if ("status" in first) {
2017
+ refuse(first.status);
2018
+ return true;
2019
+ }
2020
+ (async () => {
2021
+ let body;
2022
+ try {
2023
+ body = await parseBody(req);
2024
+ }
2025
+ catch {
2026
+ json(res, 400, { error: "Invalid JSON" });
2027
+ return;
2028
+ }
2029
+ if (!permitWebContinuation(req, res, ctx))
2030
+ return;
2031
+ const now = who();
2032
+ if ("status" in now) {
2033
+ refuse(now.status);
2034
+ return;
2035
+ }
2036
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
2037
+ json(res, 400, { error: "offices required" });
2038
+ return;
2039
+ }
2040
+ const extra = Object.keys(body).find(k => k !== "offices" && k !== "rev");
2041
+ if (extra !== undefined) {
2042
+ json(res, 400, { error: "offices_invalid", detail: `unknown key "${extra}"` });
2043
+ return;
2044
+ }
2045
+ // Office review: the rev the page read is required — a save without one would overwrite another person's unseen.
2046
+ if (body.rev === undefined) {
2047
+ json(res, 428, { error: "offices_rev_required", detail: "send the rev you read (GET /ui/offices)" });
2048
+ return;
2049
+ }
2050
+ if (!Number.isSafeInteger(body.rev)) {
2051
+ json(res, 400, { error: "offices_invalid", detail: "rev: an integer" });
2052
+ return;
2053
+ }
2054
+ const limiter = ctx.officeWrites, allowed = limiter ? limiter.check(now.person) : { ok: true };
2055
+ if (!allowed.ok) {
2056
+ res.setHeader("Retry-After", String(allowed.retry_after_s));
2057
+ json(res, 429, { error: "offices_rate_limited", retry_after_s: allowed.retry_after_s });
2058
+ return;
2059
+ }
2060
+ const r = store.replace(body.offices, body.rev);
2061
+ if (!r.ok) {
2062
+ json(res, r.status, { error: r.error, ...(r.detail ? { detail: r.detail } : {}) });
2063
+ return;
2064
+ }
2065
+ limiter?.record(now.person);
2066
+ ctx.emitSseEvent("offices", { rev: r.rev });
2067
+ json(res, 200, { rev: r.rev, offices: r.offices });
2068
+ })().catch(err => {
2069
+ ctx.logger.error({ err: err.message }, "Saving offices failed");
2070
+ try {
2071
+ json(res, 500, { error: "Saving offices failed" });
2072
+ }
2073
+ catch { /* already answered */ }
2074
+ });
2075
+ return true;
2076
+ }
1975
2077
  // ── #1630: avatars and Discord server emoji, same-origin (the page never loads a platform's CDN; img-src stays 'self') ──
1976
2078
  const imageMatch = path.match(/^\/ui\/img\/(bot|avatar|emoji)\/([^/]+)$/);
1977
2079
  if (method === "GET" && imageMatch) {
@@ -2373,7 +2475,7 @@ export function handleWebRequest(req, res, url, ctx) {
2373
2475
  if (path === "/ui/authorization" && (method === "GET" || method === "POST")) {
2374
2476
  const judge = () => {
2375
2477
  const v = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
2376
- return { session: v.kind === "allow" && v.via === "session" ? v.session : null, gateway: !!gatewayRequestContext(req), peer: classifyWebPeer(req) };
2478
+ return { session: v.kind === "allow" && v.via === "session" ? v.session : null, gateway: !!gatewayRequestContext(req), peer: webPeerOf(req) };
2377
2479
  };
2378
2480
  res.setHeader("Cache-Control", "no-store");
2379
2481
  if (method === "GET") {
@@ -3435,6 +3537,14 @@ function webDeliveryFailure(err) {
3435
3537
  return { reason: "delivery_unavailable", error: "The instance could not receive this message. Check its status on the host, then retry." };
3436
3538
  }
3437
3539
  /** Handle POST /ui/send — extracted for readability. */
3540
+ /** Each web server's own memory of the sends it accepted by clientId (web-send-dedupe.ts). */
3541
+ const webSendDedupes = new WeakMap();
3542
+ function webSendDedupeOf(ctx) {
3543
+ let d = webSendDedupes.get(ctx);
3544
+ if (!d)
3545
+ webSendDedupes.set(ctx, d = new WebSendDedupe());
3546
+ return d;
3547
+ }
3438
3548
  function handleSendMessage(req, res, ctx) {
3439
3549
  const arrival = webAuthorityOf(req, new URL(req.url ?? "/", "http://localhost"), ctx); // #1939 §3: again after the body
3440
3550
  let body = "";
@@ -3451,6 +3561,9 @@ function handleSendMessage(req, res, ctx) {
3451
3561
  req.on("end", async () => {
3452
3562
  if (!permitWebContinuation(req, res, ctx))
3453
3563
  return;
3564
+ // A claimed clientId is settled however this ends: with the messageId once accepted, else forgotten.
3565
+ let claim;
3566
+ let acceptedId = null;
3454
3567
  try {
3455
3568
  const raw = JSON.parse(body);
3456
3569
  const parsed = SendMessageSchema.safeParse(raw);
@@ -3459,7 +3572,7 @@ function handleSendMessage(req, res, ctx) {
3459
3572
  json(res, 400, { error: `${issue.path.join(".") || "body"}: ${issue.message}` });
3460
3573
  return;
3461
3574
  }
3462
- const { instance, message: typed, attachments = [], replyTo } = parsed.data;
3575
+ const { instance, message: typed, attachments = [], replyTo, clientId } = parsed.data;
3463
3576
  // #1920 step 2b: a guest writes only to an in-scope instance, while guests may chat, and never a command.
3464
3577
  const guest = guestRequestOf(req);
3465
3578
  const refused = guestChatRefusal(guest, instance);
@@ -3486,6 +3599,33 @@ function handleSendMessage(req, res, ctx) {
3486
3599
  json(res, 404, { error: `Instance not found: ${instance}` });
3487
3600
  return;
3488
3601
  }
3602
+ // #1827: who is writing — the name agents and chats see, and the id they tell people apart by. Asked now that the
3603
+ // body is in: a session revoked meanwhile sends nothing (not as the operator).
3604
+ const person = webPersonOf(req, new URL(req.url ?? "/", "http://localhost"), ctx);
3605
+ if (!person) {
3606
+ json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
3607
+ return;
3608
+ }
3609
+ // #2438 follow-up: the page's Retry of a send that already went (its answer and the live copy lost) is that send:
3610
+ // answered with its messageId, delivered once. One still going is waited for; one that failed is sent anew.
3611
+ if (clientId) {
3612
+ const key = WebSendDedupe.key(person.key, instance, clientId);
3613
+ for (;;) {
3614
+ const seen = webSendDedupeOf(ctx).claim(key);
3615
+ if (seen.kind === "going") {
3616
+ await seen.settled;
3617
+ if (res.destroyed)
3618
+ return;
3619
+ continue;
3620
+ }
3621
+ if (seen.kind === "accepted") {
3622
+ json(res, 200, { sent: true, messageId: seen.messageId, repeated: true });
3623
+ return;
3624
+ }
3625
+ claim = seen;
3626
+ break;
3627
+ }
3628
+ }
3489
3629
  let files = [];
3490
3630
  if (attachments.length > 0) {
3491
3631
  const taken = ctx.webFiles?.takeForMessage(instance, attachments);
@@ -3512,14 +3652,6 @@ function handleSendMessage(req, res, ctx) {
3512
3652
  && (!guest || (guest.current() && !guestChatRefusal(guest, instance)))
3513
3653
  && (registered ? registeredChatInstance(ctx, instance) : ctx.instanceIpcClients.has(instance));
3514
3654
  const message = typed;
3515
- // #1827: who is writing — the name agents and chats see, and the id they tell people apart by. Asked now that the
3516
- // body is in: a session revoked meanwhile sends nothing (not as the operator).
3517
- const person = webPersonOf(req, new URL(req.url ?? "/", "http://localhost"), ctx);
3518
- if (!person) {
3519
- ctx.webFiles?.release(files);
3520
- json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
3521
- return;
3522
- }
3523
3655
  // #1939 S1: what the receipt records about who authorized it (observe only: agents still read web as recognition).
3524
3656
  const { authority } = confirmAfterBody(arrival, webAuthorityOf(req, new URL(req.url ?? "/", "http://localhost"), ctx, instance));
3525
3657
  const ts = new Date().toISOString();
@@ -3537,13 +3669,18 @@ function handleSendMessage(req, res, ctx) {
3537
3669
  // Add no runtime credentials or local attachment paths to the display copy. A reply opens with the line it
3538
3670
  // answers (#2272) — quoted only if that message reached the channel; a web-only one is named, never copied out.
3539
3671
  const quoteLine = quoted ? webReplyEchoLine(quoted) : "";
3540
- const preview = (quoteLine ? `${quoteLine}\n` : "") + webEchoPreview(message, files.map(f => f.name));
3672
+ const plainPreview = webEchoPreview(message, files.map(f => f.name));
3673
+ const preview = (quoteLine ? `${quoteLine}\n` : "") + plainPreview;
3674
+ // #2281: where the quoted message is on a platform. An echo to the same place is a native reply to it there, and
3675
+ // then needs no quoted line; anywhere else (or a message with no known post) keeps the line, as before.
3676
+ const quotedPosts = quoted ? ctx.webChatHistory?.postsOf(quoted) ?? [] : [];
3677
+ const echoText = (p) => formatWebChannelEcho(person.name, p, t("web.echo_full_text"), { guest: !!guest });
3541
3678
  // ClassicBot echo (#1320 part B): same ordering lane, each room's setting (#2054: on unless it says false, or
3542
3679
  // the fleet-wide switch is off for a room that is unset) resolved at send time inside sendClassicWebEcho.
3543
3680
  const canClassicEcho = ctx.isClassicInstance?.(instance) === true && !!ctx.sendClassicWebEcho;
3544
3681
  const settleClassicEcho = canClassicEcho ? ctx.reserveWebChannelEcho?.(instance, async () => {
3545
3682
  const copies = [];
3546
- const posted = await ctx.sendClassicWebEcho(instance, formatWebChannelEcho(person.name, preview, t("web.echo_full_text"), { guest: !!guest }), person.name, copies);
3683
+ const posted = await ctx.sendClassicWebEcho(instance, echoText(preview), person.name, copies, quotedPosts.length ? { posts: quotedPosts, text: echoText(plainPreview) } : undefined);
3547
3684
  if (posted > 0)
3548
3685
  ctx.webMessageInChannel?.(instance, messageId, copies); // #1920 §5.2: the room saw it (#2305: its copies)
3549
3686
  }) : undefined;
@@ -3553,12 +3690,13 @@ function handleSendMessage(req, res, ctx) {
3553
3690
  || (ctx.getGroupIdForInstance?.(instance) ?? String(ctx.fleetConfig?.channel?.group_id ?? "")) !== groupId
3554
3691
  || ctx.fleetConfig?.instances[instance]?.topic_id !== topicId)
3555
3692
  return;
3556
- const sent = await syncAdapter.sendText(String(groupId), formatWebChannelEcho(person.name, preview, t("web.echo_full_text"), { guest: !!guest }), { threadId: String(topicId), format: "text", allowedMentions: { parse: [] } });
3693
+ const native = quotedPosts.find(p => p.adapterId === syncAdapter.id && p.chatId === String(groupId) && (p.threadId ?? "") === String(topicId));
3694
+ const sent = await syncAdapter.sendText(String(groupId), echoText(native ? plainPreview : preview), { threadId: String(topicId), format: "text", allowedMentions: { parse: [] }, ...(native ? { replyTo: native.messageId } : {}) });
3557
3695
  const copyId = sent && typeof sent === "object" && typeof sent.messageId === "string" ? sent.messageId : "";
3558
- // #1920 §5.2: the topic saw it. #2305: and where it is, for a reaction to the web message.
3696
+ // #1920 §5.2: the topic saw it. #2305: and where it is, for a reaction to the web message (#2281: and a reply).
3559
3697
  const reacting = syncAdapter;
3560
- ctx.webMessageInChannel?.(instance, messageId, copyId && typeof reacting.react === "function"
3561
- ? [{ adapter: reacting, chatId: String(groupId), threadId: String(topicId), messageId: copyId }] : []);
3698
+ ctx.webMessageInChannel?.(instance, messageId, copyId
3699
+ ? [{ adapter: reacting, adapterId: syncAdapter.id, chatId: String(groupId), threadId: String(topicId), messageId: copyId }] : []);
3562
3700
  return sent;
3563
3701
  }) : undefined;
3564
3702
  let delivered;
@@ -3604,6 +3742,9 @@ function handleSendMessage(req, res, ctx) {
3604
3742
  json(res, 503, { reason: "delivery_changed", error: "The session or instance changed before this message was sent. Sign in again if needed, then retry." });
3605
3743
  return;
3606
3744
  }
3745
+ // The agent has it: from here on a repeat of this clientId is this send, whatever fails after (the echo, the
3746
+ // stream's copy) — a Retry then gets this messageId, never a second delivery.
3747
+ acceptedId = messageId;
3607
3748
  settleEcho?.(true);
3608
3749
  settleClassicEcho?.(true);
3609
3750
  // #1666: the same cancel/progress bubble a platform inbound starts, in the topic the echo went to — a second turn
@@ -3618,11 +3759,19 @@ function handleSendMessage(req, res, ctx) {
3618
3759
  ctx.eventLog?.logActivity("message", person.name, (message || `[${files.length} file(s)]`).slice(0, 200), instance);
3619
3760
  // #1920: the account that wrote it (a guest sees its own); channel-visible once its copy was sent (above).
3620
3761
  ctx.emitSseEvent("message", { instance, sender: person.name, role: "user", text: message, ts, attachments: files.map(publicAttachment), messageId, ...(person.bound ? { from: person.voterKey } : {}),
3621
- ...(quoted ? { replyTo: webChatReplyRef(quoted) } : {}) });
3762
+ ...(quoted ? { replyTo: webChatReplyRef(quoted) } : {}), ...(clientId ? { clientId } : {}) });
3622
3763
  json(res, 200, { sent: true, messageId });
3623
3764
  }
3624
3765
  catch {
3625
- json(res, 400, { error: "Invalid JSON" });
3766
+ // A failure after the agent has it is no failed send: the page is told it went (a Retry would only find it).
3767
+ if (res.headersSent) { /* answered already: the failure was in or after the answer */ }
3768
+ else if (acceptedId)
3769
+ json(res, 200, { sent: true, messageId: acceptedId });
3770
+ else
3771
+ json(res, 400, { error: "Invalid JSON" });
3772
+ }
3773
+ finally {
3774
+ claim?.settle(acceptedId);
3626
3775
  }
3627
3776
  });
3628
3777
  }