@songsid/agend 2.2.0 → 2.2.5

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 (447) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +284 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.d.ts +2 -0
  5. package/dist/agent-endpoint.js +29 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-file-read.d.ts +38 -0
  8. package/dist/agent-file-read.js +317 -0
  9. package/dist/agent-file-read.js.map +1 -0
  10. package/dist/agent-whats-new.d.ts +11 -0
  11. package/dist/agent-whats-new.js +34 -2
  12. package/dist/agent-whats-new.js.map +1 -1
  13. package/dist/atomic-write.js +9 -1
  14. package/dist/atomic-write.js.map +1 -1
  15. package/dist/auth-api.d.ts +30 -1
  16. package/dist/auth-api.js +61 -12
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/authority/authority-state.d.ts +38 -0
  19. package/dist/authority/authority-state.js +114 -0
  20. package/dist/authority/authority-state.js.map +1 -0
  21. package/dist/authority/session-authorizations.d.ts +43 -0
  22. package/dist/authority/session-authorizations.js +102 -0
  23. package/dist/authority/session-authorizations.js.map +1 -0
  24. package/dist/authority/web-authority.d.ts +120 -0
  25. package/dist/authority/web-authority.js +157 -0
  26. package/dist/authority/web-authority.js.map +1 -0
  27. package/dist/backend/antigravity.js +3 -3
  28. package/dist/backend/antigravity.js.map +1 -1
  29. package/dist/backend/claude-code.d.ts +2 -2
  30. package/dist/backend/claude-code.js +10 -5
  31. package/dist/backend/claude-code.js.map +1 -1
  32. package/dist/backend/launch-effort.js +1 -1
  33. package/dist/backend/launch-effort.js.map +1 -1
  34. package/dist/backend/muse.js +10 -2
  35. package/dist/backend/muse.js.map +1 -1
  36. package/dist/backend/types.d.ts +14 -0
  37. package/dist/backend/types.js +25 -0
  38. package/dist/backend/types.js.map +1 -1
  39. package/dist/backend-availability.d.ts +120 -0
  40. package/dist/backend-availability.js +396 -0
  41. package/dist/backend-availability.js.map +1 -0
  42. package/dist/backend-command.d.ts +21 -3
  43. package/dist/backend-command.js +23 -4
  44. package/dist/backend-command.js.map +1 -1
  45. package/dist/bundled-skills.d.ts +14 -0
  46. package/dist/bundled-skills.js +29 -0
  47. package/dist/bundled-skills.js.map +1 -0
  48. package/dist/cache-analysis.d.ts +47 -1
  49. package/dist/cache-analysis.js +68 -8
  50. package/dist/cache-analysis.js.map +1 -1
  51. package/dist/cache-ledger.d.ts +16 -3
  52. package/dist/cache-ledger.js +33 -13
  53. package/dist/cache-ledger.js.map +1 -1
  54. package/dist/cache-report-tool.d.ts +17 -0
  55. package/dist/cache-report-tool.js +40 -0
  56. package/dist/cache-report-tool.js.map +1 -0
  57. package/dist/cache-service.d.ts +70 -3
  58. package/dist/cache-service.js +178 -17
  59. package/dist/cache-service.js.map +1 -1
  60. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  61. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  62. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  63. package/dist/channel/adapters/discord.d.ts +11 -0
  64. package/dist/channel/adapters/discord.js +66 -6
  65. package/dist/channel/adapters/discord.js.map +1 -1
  66. package/dist/channel/adapters/telegram.d.ts +8 -1
  67. package/dist/channel/adapters/telegram.js +74 -4
  68. package/dist/channel/adapters/telegram.js.map +1 -1
  69. package/dist/channel/mcp-tools.js +48 -2
  70. package/dist/channel/mcp-tools.js.map +1 -1
  71. package/dist/channel/types.d.ts +21 -0
  72. package/dist/classic-channel-manager.d.ts +10 -5
  73. package/dist/classic-channel-manager.js +12 -9
  74. package/dist/classic-channel-manager.js.map +1 -1
  75. package/dist/cli.js +142 -110
  76. package/dist/cli.js.map +1 -1
  77. package/dist/config-validator.js +119 -4
  78. package/dist/config-validator.js.map +1 -1
  79. package/dist/daemon.d.ts +10 -0
  80. package/dist/daemon.js +327 -31
  81. package/dist/daemon.js.map +1 -1
  82. package/dist/decision-list.d.ts +25 -0
  83. package/dist/decision-list.js +30 -0
  84. package/dist/decision-list.js.map +1 -0
  85. package/dist/delivery-outbox.d.ts +10 -0
  86. package/dist/delivery-outbox.js +12 -1
  87. package/dist/delivery-outbox.js.map +1 -1
  88. package/dist/fleet-context.d.ts +15 -0
  89. package/dist/fleet-manager.d.ts +514 -12
  90. package/dist/fleet-manager.js +1885 -163
  91. package/dist/fleet-manager.js.map +1 -1
  92. package/dist/fleet-process-handlers.d.ts +18 -0
  93. package/dist/fleet-process-handlers.js +69 -0
  94. package/dist/fleet-process-handlers.js.map +1 -0
  95. package/dist/fleet-traffic.d.ts +74 -0
  96. package/dist/fleet-traffic.js +196 -0
  97. package/dist/fleet-traffic.js.map +1 -0
  98. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  99. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  100. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  101. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  102. package/dist/general-knowledge/skills/web-appearance/SKILL.md +492 -0
  103. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +482 -0
  104. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  105. package/dist/human-delivery-receipts.d.ts +24 -1
  106. package/dist/human-delivery-receipts.js +64 -11
  107. package/dist/human-delivery-receipts.js.map +1 -1
  108. package/dist/instance-avatar.js +3 -0
  109. package/dist/instance-avatar.js.map +1 -1
  110. package/dist/instance-config-impact.js +6 -1
  111. package/dist/instance-config-impact.js.map +1 -1
  112. package/dist/instance-lifecycle.d.ts +2 -0
  113. package/dist/instance-lifecycle.js +20 -10
  114. package/dist/instance-lifecycle.js.map +1 -1
  115. package/dist/instance-names.d.ts +4 -0
  116. package/dist/instance-names.js +12 -0
  117. package/dist/instance-names.js.map +1 -0
  118. package/dist/instructions.d.ts +12 -1
  119. package/dist/instructions.js +13 -2
  120. package/dist/instructions.js.map +1 -1
  121. package/dist/locale.js +100 -8
  122. package/dist/locale.js.map +1 -1
  123. package/dist/ls-attach-resolve.d.ts +95 -0
  124. package/dist/ls-attach-resolve.js +174 -0
  125. package/dist/ls-attach-resolve.js.map +1 -0
  126. package/dist/memory-hogs.d.ts +27 -0
  127. package/dist/memory-hogs.js +124 -0
  128. package/dist/memory-hogs.js.map +1 -0
  129. package/dist/needs-you-hub.d.ts +4 -0
  130. package/dist/needs-you-hub.js +31 -6
  131. package/dist/needs-you-hub.js.map +1 -1
  132. package/dist/needs-you.d.ts +1 -13
  133. package/dist/needs-you.js +3 -37
  134. package/dist/needs-you.js.map +1 -1
  135. package/dist/outbound-handlers.d.ts +15 -0
  136. package/dist/outbound-handlers.js +152 -11
  137. package/dist/outbound-handlers.js.map +1 -1
  138. package/dist/outbound-schemas.d.ts +228 -4
  139. package/dist/outbound-schemas.js +147 -1
  140. package/dist/outbound-schemas.js.map +1 -1
  141. package/dist/public-web-gateway.js +21 -1
  142. package/dist/public-web-gateway.js.map +1 -1
  143. package/dist/public-web-link.d.ts +9 -0
  144. package/dist/public-web-link.js +8 -0
  145. package/dist/public-web-link.js.map +1 -1
  146. package/dist/quickstart-api.d.ts +10 -1
  147. package/dist/quickstart-api.js +12 -4
  148. package/dist/quickstart-api.js.map +1 -1
  149. package/dist/reply-buttons-controller.d.ts +4 -0
  150. package/dist/reply-buttons-controller.js +2 -2
  151. package/dist/reply-buttons-controller.js.map +1 -1
  152. package/dist/repo-worktree-guard.d.ts +36 -0
  153. package/dist/repo-worktree-guard.js +143 -0
  154. package/dist/repo-worktree-guard.js.map +1 -0
  155. package/dist/restart-progress.d.ts +65 -1
  156. package/dist/restart-progress.js +236 -10
  157. package/dist/restart-progress.js.map +1 -1
  158. package/dist/rollout-index.d.ts +5 -0
  159. package/dist/rollout-index.js +26 -5
  160. package/dist/rollout-index.js.map +1 -1
  161. package/dist/scheduler/scheduler.d.ts +70 -1
  162. package/dist/scheduler/scheduler.js +256 -17
  163. package/dist/scheduler/scheduler.js.map +1 -1
  164. package/dist/service-installer.d.ts +2 -1
  165. package/dist/service-installer.js +18 -3
  166. package/dist/service-installer.js.map +1 -1
  167. package/dist/settings-api.d.ts +23 -0
  168. package/dist/settings-api.js +233 -2
  169. package/dist/settings-api.js.map +1 -1
  170. package/dist/settings-change.js +112 -0
  171. package/dist/settings-change.js.map +1 -1
  172. package/dist/settings-effect.js +32 -2
  173. package/dist/settings-effect.js.map +1 -1
  174. package/dist/settings-http-confirmation.js +5 -0
  175. package/dist/settings-http-confirmation.js.map +1 -1
  176. package/dist/tmux-manager.d.ts +5 -0
  177. package/dist/tmux-manager.js +16 -0
  178. package/dist/tmux-manager.js.map +1 -1
  179. package/dist/tool-permissions.d.ts +4 -6
  180. package/dist/tool-permissions.js +100 -2
  181. package/dist/tool-permissions.js.map +1 -1
  182. package/dist/topic-commands.d.ts +17 -7
  183. package/dist/topic-commands.js +39 -21
  184. package/dist/topic-commands.js.map +1 -1
  185. package/dist/topic-link.d.ts +128 -0
  186. package/dist/topic-link.js +269 -0
  187. package/dist/topic-link.js.map +1 -0
  188. package/dist/types.d.ts +61 -1
  189. package/dist/ui/app-effects.js +245 -0
  190. package/dist/ui/chat-ambient.js +307 -0
  191. package/dist/ui/chat-effect-posters.js +42 -0
  192. package/dist/ui/chat-effects.js +160 -0
  193. package/dist/ui/chat-live-replies.js +58 -0
  194. package/dist/ui/chat-render.js +25 -7
  195. package/dist/ui/chat-store.js +41 -13
  196. package/dist/ui/chat-strings.js +86 -2
  197. package/dist/ui/chat-thread.js +204 -37
  198. package/dist/ui/chat-tour.js +1 -1
  199. package/dist/ui/effect-trial-controller.js +124 -0
  200. package/dist/ui/effect-trial-offer.js +31 -0
  201. package/dist/ui/effect-trial-panel.js +40 -0
  202. package/dist/ui/fleet-cache.js +23 -1
  203. package/dist/ui/fleet-canvas-layout.js +111 -0
  204. package/dist/ui/fleet-canvas.js +525 -0
  205. package/dist/ui/fleet-org.js +27 -12
  206. package/dist/ui/fleet-traffic.js +343 -0
  207. package/dist/ui/fleet-usage.js +401 -0
  208. package/dist/ui/image-lightbox.js +1 -1
  209. package/dist/ui/panel-chat.js +252 -58
  210. package/dist/ui/panel-details.js +20 -6
  211. package/dist/ui/panel-fleet.js +45 -19
  212. package/dist/ui/panel-log.js +6 -6
  213. package/dist/ui/panel-needs.js +3 -3
  214. package/dist/ui/panel-settings.js +58 -13
  215. package/dist/ui/preview-panel.js +25 -9
  216. package/dist/ui/preview.js +3 -2
  217. package/dist/ui/settings-appearance.js +349 -0
  218. package/dist/ui/settings-catalog.js +92 -0
  219. package/dist/ui/settings-dialogs.js +40 -12
  220. package/dist/ui/settings-effects.js +202 -0
  221. package/dist/ui/settings-guests.js +117 -0
  222. package/dist/ui/settings-learn.js +25 -0
  223. package/dist/ui/settings-model.js +15 -6
  224. package/dist/ui/settings-mods.js +271 -0
  225. package/dist/ui/settings-owner-effects.js +80 -0
  226. package/dist/ui/settings-strings.js +335 -10
  227. package/dist/ui/settings-web-authority.js +91 -0
  228. package/dist/ui/shared/agend-auth.js +28 -4
  229. package/dist/ui/shared/agent-widget.js +248 -0
  230. package/dist/ui/shared/app-i18n.js +6 -2
  231. package/dist/ui/shared/app-prefs.js +34 -13
  232. package/dist/ui/shared/app-route.js +42 -2
  233. package/dist/ui/shared/app-session.js +3 -1
  234. package/dist/ui/shared/app-shell.js +119 -41
  235. package/dist/ui/shared/app-store.js +28 -1
  236. package/dist/ui/shared/app-stream.js +14 -7
  237. package/dist/ui/shared/app-themes.js +206 -0
  238. package/dist/ui/shared/app.css +566 -5
  239. package/dist/ui/shared/app.js +23 -10
  240. package/dist/ui/shared/card-strings.js +32 -0
  241. package/dist/ui/shared/effect-app-runtime.js +297 -0
  242. package/dist/ui/shared/effect-baseline.js +64 -0
  243. package/dist/ui/shared/effect-chat-runtime.js +167 -0
  244. package/dist/ui/shared/effect-cursor.js +125 -0
  245. package/dist/ui/shared/effect-frame.js +146 -0
  246. package/dist/ui/shared/effect-geometry.js +137 -0
  247. package/dist/ui/shared/effect-host-style.js +10 -0
  248. package/dist/ui/shared/effect-owner-regions.js +74 -0
  249. package/dist/ui/shared/effect-owner-source.js +122 -0
  250. package/dist/ui/shared/effect-policy.js +151 -0
  251. package/dist/ui/shared/effect-poster-reader.js +98 -0
  252. package/dist/ui/shared/effect-posters.js +117 -0
  253. package/dist/ui/shared/effect-profiler.js +184 -0
  254. package/dist/ui/shared/effect-profiles.js +65 -0
  255. package/dist/ui/shared/effect-registry.js +543 -0
  256. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  257. package/dist/ui/shared/effect-safety.js +153 -0
  258. package/dist/ui/shared/effect-stock-snapshot.js +43 -0
  259. package/dist/ui/shared/effect-stock-trust.js +217 -0
  260. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  261. package/dist/ui/shared/effect-trial-source.js +103 -0
  262. package/dist/ui/shared/effect-trial-stage.js +170 -0
  263. package/dist/ui/shared/effect-trial.css +40 -0
  264. package/dist/ui/shared/fx-motion.js +67 -0
  265. package/dist/ui/shared/header-tools.js +1 -1
  266. package/dist/ui/shared/identity-card.js +273 -0
  267. package/dist/ui/shared/identity-color.js +261 -0
  268. package/dist/ui/shared/instance-nav.js +215 -41
  269. package/dist/ui/shared/owner-css.js +250 -0
  270. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  271. package/dist/ui/shared/panel-view.js +103 -10
  272. package/dist/ui/shared/signin.js +2 -0
  273. package/dist/ui/shared/tokens.css +9 -3
  274. package/dist/ui/shared/ui-dialog.js +1 -1
  275. package/dist/ui/shared/ui-icons.js +3 -0
  276. package/dist/ui/shared/ui-menu.js +1 -1
  277. package/dist/ui/shared/ui-toast.js +1 -1
  278. package/dist/ui/shared/usage-dialog.js +22 -2
  279. package/dist/ui/shared/view-strings.js +98 -2
  280. package/dist/ui/shared/web-authorize.js +151 -0
  281. package/dist/ui/signin.html +1 -0
  282. package/dist/ui/web-terminal/terminal.css +4 -4
  283. package/dist/usage/providers.d.ts +28 -0
  284. package/dist/usage/providers.js +126 -36
  285. package/dist/usage/providers.js.map +1 -1
  286. package/dist/usage-report.d.ts +158 -0
  287. package/dist/usage-report.js +226 -0
  288. package/dist/usage-report.js.map +1 -0
  289. package/dist/usage-rollup.d.ts +51 -0
  290. package/dist/usage-rollup.js +168 -0
  291. package/dist/usage-rollup.js.map +1 -0
  292. package/dist/view-api.d.ts +68 -0
  293. package/dist/view-api.js +249 -8
  294. package/dist/view-api.js.map +1 -1
  295. package/dist/web-ambient.d.ts +72 -0
  296. package/dist/web-ambient.js +161 -0
  297. package/dist/web-ambient.js.map +1 -0
  298. package/dist/web-api.d.ts +117 -7
  299. package/dist/web-api.js +1283 -70
  300. package/dist/web-api.js.map +1 -1
  301. package/dist/web-appearance-limits.d.ts +82 -0
  302. package/dist/web-appearance-limits.js +83 -0
  303. package/dist/web-appearance-limits.js.map +1 -0
  304. package/dist/web-appearance-policy.d.ts +64 -0
  305. package/dist/web-appearance-policy.js +87 -0
  306. package/dist/web-appearance-policy.js.map +1 -0
  307. package/dist/web-appearance-tools.d.ts +53 -0
  308. package/dist/web-appearance-tools.js +124 -0
  309. package/dist/web-appearance-tools.js.map +1 -0
  310. package/dist/web-appearance-view.d.ts +124 -0
  311. package/dist/web-appearance-view.js +278 -0
  312. package/dist/web-appearance-view.js.map +1 -0
  313. package/dist/web-appearance.d.ts +93 -0
  314. package/dist/web-appearance.js +373 -0
  315. package/dist/web-appearance.js.map +1 -0
  316. package/dist/web-asset-parts.d.ts +93 -0
  317. package/dist/web-asset-parts.js +133 -0
  318. package/dist/web-asset-parts.js.map +1 -0
  319. package/dist/web-auth.js +20 -2
  320. package/dist/web-auth.js.map +1 -1
  321. package/dist/web-card.d.ts +110 -0
  322. package/dist/web-card.js +266 -0
  323. package/dist/web-card.js.map +1 -0
  324. package/dist/web-channel-echo.d.ts +7 -1
  325. package/dist/web-channel-echo.js +6 -2
  326. package/dist/web-channel-echo.js.map +1 -1
  327. package/dist/web-chat-history.d.ts +63 -0
  328. package/dist/web-chat-history.js +109 -6
  329. package/dist/web-chat-history.js.map +1 -1
  330. package/dist/web-commands.d.ts +1 -0
  331. package/dist/web-effect-bootstrap.d.ts +12 -0
  332. package/dist/web-effect-bootstrap.js +49 -0
  333. package/dist/web-effect-bootstrap.js.map +1 -0
  334. package/dist/web-effect-bundle.d.ts +28 -0
  335. package/dist/web-effect-bundle.js +171 -0
  336. package/dist/web-effect-bundle.js.map +1 -0
  337. package/dist/web-effect-canonical.d.ts +8 -0
  338. package/dist/web-effect-canonical.js +27 -0
  339. package/dist/web-effect-canonical.js.map +1 -0
  340. package/dist/web-effect-library.d.ts +114 -0
  341. package/dist/web-effect-library.js +530 -0
  342. package/dist/web-effect-library.js.map +1 -0
  343. package/dist/web-effect-playback.d.ts +30 -0
  344. package/dist/web-effect-playback.js +71 -0
  345. package/dist/web-effect-playback.js.map +1 -0
  346. package/dist/web-effect-poster.d.ts +8 -0
  347. package/dist/web-effect-poster.js +52 -0
  348. package/dist/web-effect-poster.js.map +1 -0
  349. package/dist/web-effect-profiles.d.ts +118 -0
  350. package/dist/web-effect-profiles.js +180 -0
  351. package/dist/web-effect-profiles.js.map +1 -0
  352. package/dist/web-effect-service.d.ts +93 -0
  353. package/dist/web-effect-service.js +335 -0
  354. package/dist/web-effect-service.js.map +1 -0
  355. package/dist/web-effect-tools.d.ts +48 -0
  356. package/dist/web-effect-tools.js +129 -0
  357. package/dist/web-effect-tools.js.map +1 -0
  358. package/dist/web-effect-trials.d.ts +86 -0
  359. package/dist/web-effect-trials.js +204 -0
  360. package/dist/web-effect-trials.js.map +1 -0
  361. package/dist/web-effect-upload.d.ts +27 -0
  362. package/dist/web-effect-upload.js +185 -0
  363. package/dist/web-effect-upload.js.map +1 -0
  364. package/dist/web-effect-worker.d.ts +8 -0
  365. package/dist/web-effect-worker.js +170 -0
  366. package/dist/web-effect-worker.js.map +1 -0
  367. package/dist/web-effect.d.ts +103 -0
  368. package/dist/web-effect.js +153 -0
  369. package/dist/web-effect.js.map +1 -0
  370. package/dist/web-guest-access.d.ts +125 -0
  371. package/dist/web-guest-access.js +234 -0
  372. package/dist/web-guest-access.js.map +1 -0
  373. package/dist/web-guest-rooms.d.ts +82 -0
  374. package/dist/web-guest-rooms.js +135 -0
  375. package/dist/web-guest-rooms.js.map +1 -0
  376. package/dist/web-identity-color.d.ts +87 -0
  377. package/dist/web-identity-color.js +209 -0
  378. package/dist/web-identity-color.js.map +1 -0
  379. package/dist/web-identity-palette.d.ts +113 -0
  380. package/dist/web-identity-palette.js +84 -0
  381. package/dist/web-identity-palette.js.map +1 -0
  382. package/dist/web-image-meta.d.ts +38 -0
  383. package/dist/web-image-meta.js +262 -0
  384. package/dist/web-image-meta.js.map +1 -0
  385. package/dist/web-login.d.ts +47 -0
  386. package/dist/web-login.js +115 -0
  387. package/dist/web-login.js.map +1 -1
  388. package/dist/web-mod-agent.d.ts +50 -0
  389. package/dist/web-mod-agent.js +207 -0
  390. package/dist/web-mod-agent.js.map +1 -0
  391. package/dist/web-mod-bundle.d.ts +157 -0
  392. package/dist/web-mod-bundle.js +529 -0
  393. package/dist/web-mod-bundle.js.map +1 -0
  394. package/dist/web-mod-export.d.ts +68 -0
  395. package/dist/web-mod-export.js +100 -0
  396. package/dist/web-mod-export.js.map +1 -0
  397. package/dist/web-mod-store.d.ts +289 -0
  398. package/dist/web-mod-store.js +638 -0
  399. package/dist/web-mod-store.js.map +1 -0
  400. package/dist/web-org.d.ts +3 -3
  401. package/dist/web-org.js +11 -17
  402. package/dist/web-org.js.map +1 -1
  403. package/dist/web-owner-css-store.d.ts +222 -0
  404. package/dist/web-owner-css-store.js +440 -0
  405. package/dist/web-owner-css-store.js.map +1 -0
  406. package/dist/web-owner-css.d.ts +58 -0
  407. package/dist/web-owner-css.js +552 -0
  408. package/dist/web-owner-css.js.map +1 -0
  409. package/dist/web-prefs.d.ts +62 -0
  410. package/dist/web-prefs.js +232 -36
  411. package/dist/web-prefs.js.map +1 -1
  412. package/dist/web-preview.d.ts +15 -3
  413. package/dist/web-preview.js +28 -9
  414. package/dist/web-preview.js.map +1 -1
  415. package/dist/web-returning-device.d.ts +41 -0
  416. package/dist/web-returning-device.js +57 -5
  417. package/dist/web-returning-device.js.map +1 -1
  418. package/dist/web-session-authorization.d.ts +132 -0
  419. package/dist/web-session-authorization.js +193 -0
  420. package/dist/web-session-authorization.js.map +1 -0
  421. package/dist/web-session.d.ts +70 -10
  422. package/dist/web-session.js +137 -22
  423. package/dist/web-session.js.map +1 -1
  424. package/dist/web-settings-patch.d.ts +67 -2
  425. package/dist/web-settings-patch.js +196 -1
  426. package/dist/web-settings-patch.js.map +1 -1
  427. package/dist/web-settings-tools.d.ts +89 -0
  428. package/dist/web-settings-tools.js +273 -0
  429. package/dist/web-settings-tools.js.map +1 -0
  430. package/dist/web-shell-routes.d.ts +2 -2
  431. package/dist/web-shell-routes.js +2 -2
  432. package/dist/web-shell-routes.js.map +1 -1
  433. package/dist/web-theme-store.d.ts +257 -0
  434. package/dist/web-theme-store.js +627 -0
  435. package/dist/web-theme-store.js.map +1 -0
  436. package/dist/web-theme.d.ts +123 -0
  437. package/dist/web-theme.js +416 -0
  438. package/dist/web-theme.js.map +1 -0
  439. package/dist/web-upload.d.ts +13 -0
  440. package/dist/web-upload.js +39 -3
  441. package/dist/web-upload.js.map +1 -1
  442. package/dist/web-widget.d.ts +72 -0
  443. package/dist/web-widget.js +245 -0
  444. package/dist/web-widget.js.map +1 -0
  445. package/dist/workflow-templates/default.md +1 -1
  446. package/package.json +1 -1
  447. package/templates/systemd.service.ejs +8 -0
@@ -0,0 +1,160 @@
1
+ // #1629: Chat contributes a committed lease to the one app owner; no private pool or server polling.
2
+ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
3
+ import { register, t } from "/assets/app-i18n.js";
4
+ import { appStore, useStore } from "/assets/app-store.js";
5
+ import { ownerCssLifecycle } from "/assets/owner-css.js";
6
+ import { chatEffectState, chatEffectIdentity } from "/assets/effect-chat-runtime.js";
7
+ import { Dialog } from "/assets/ui-dialog.js";
8
+ import { navigate } from "/assets/app-nav.js";
9
+ import { toast } from "/assets/ui-toast.js";
10
+ import { setThemesShown, themesShown, themesStore } from "/assets/app-themes.js";
11
+ import { setFollowsReducedMotion } from "/assets/fx-motion.js";
12
+ import { getAppEffects } from "./app-effects.js";
13
+
14
+ register("chatFx", {
15
+ en: { stock: "Off: the page layout could not be verified ({0}). Reload and report this code if it persists.", transport: "Off: the isolated effect frame could not start. Reload to try again.", enableHere: "Turn effects on here", playMotion: "Play effects anyway", leaveSafe: "Leave safe mode", viewerShow: "Show themes, effects and widgets here", clock: "Off: the page timing clock is unavailable. Reload to try again.", gatesUnknown: "Off: the playback conditions could not be verified.", resource: "Off: this effect could not be loaded or verified. Reload to try again.", frontQueue: "Waiting for a foreground slot.", consent: "This page has older playback information. Reload to use the isolated effect player.", previewMissing: "Off: the isolated effect frame is unavailable. Reload to try again.", unsupported: "Off: this browser lacks required Canvas Worker or layout support.", calibration: "Starting the effect.", baseline: "This page has older playback information. Reload to try again.", safetyUnknown: "Stopped: this device's safety history could not be verified. Reload to check it.", rawReason: "Playback blocked: {0}", missingRow: "Playback state is unavailable (runtime_unavailable).", region: "Off: this effect does not declare the chat region.", blocked: "Placed effects are blocked for this viewer:", deviceSetting: "Open device preferences", webSetting: "Open Web settings", reloadHint: "Reload page",
16
+ title: "Effects", loading: "Checking effects…", empty: "No effects in this chat.", failed: "Effects stopped because their resources could not be verified. Reload the page to try again.", reload: "Reload page", pause: "Pause", resume: "Resume", remove: "Remove from chat", removeFailed: "Could not remove this effect. Reload the page to check its state.", css: "Agent foreground effects are paused while your custom CSS is on or cannot be verified.", setting: "Open Web appearance settings", running: "Running", starting: "Starting", off: "Stopped", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.", queued: "Waiting for a slot", unknown: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", pauseHelp: "Resume keeps this device’s automatic-pause history and three-pause safety limit.", paused: "Paused", low: "Reduced quality",
17
+ typing: "Waiting while you type — it plays when you leave the message box.", covered: "Waiting while a menu or panel covers the chat.",
18
+ dialogOpen: "Paused while this dialog is open.", wasRunning: "Was running; paused while this dialog is open.", modal: "Waiting while a dialog is open.",
19
+ off3: "Turned off on this device after three automatic pauses. Resume cannot restart it here.", interrupted: "Stopped: the page closed while it was playing. Resume to play it again.",
20
+ cssOff: "Off while your custom CSS is on.", reducedMotion: "Off: you chose to follow the system's reduced motion setting.", saveData: "This page has older playback information. Reload to try again.",
21
+ hiddenTab: "Waiting until this tab is visible.", offscreen: "Waiting until the chat is on screen.",
22
+ safeMode: "Off in safe mode.", publicLink: "Off on a public link.", disabled: "Effects are switched off." },
23
+ "zh-TW": { stock: "已關閉:無法驗證頁面版面({0})。請重新載入;若持續發生,請回報此代碼。", transport: "已關閉:無法啟動隔離效果框架。請重新載入再試。", enableHere: "在這裡開啟效果", playMotion: "仍然播放效果", leaveSafe: "離開安全模式", viewerShow: "在這裡顯示主題、效果與小工具", clock: "已關閉:頁面計時無法使用。請重新載入再試。", gatesUnknown: "已關閉:無法確認播放條件。", resource: "已關閉:無法載入或驗證此效果。請重新載入再試。", frontQueue: "等待前景空位。", consent: "這個頁面的播放資訊已過期。請重新載入以使用隔離效果播放器。", previewMissing: "已關閉:隔離效果框架無法使用。請重新載入再試。", unsupported: "已關閉:這個瀏覽器缺少 Canvas Worker 或版面驗證支援。", calibration: "正在啟動效果。", baseline: "這個頁面的播放資訊已過期,請重新載入再試。", safetyUnknown: "已停止:無法確認這台裝置的安全紀錄。請重新載入確認。", rawReason: "播放受阻:{0}", missingRow: "無法取得播放狀態(runtime_unavailable)。", region: "已關閉:此效果未宣告聊天區域。", blocked: "放置的效果目前無法在這個畫面播放:", deviceSetting: "開啟裝置偏好設定", webSetting: "開啟網頁設定", reloadHint: "重新載入頁面",
24
+ title: "效果", loading: "正在確認效果…", empty: "這個聊天沒有放置效果。", failed: "無法確認效果資源,已停止播放。重新載入頁面後再試。", reload: "重新載入頁面", pause: "暫停", resume: "恢復", remove: "從聊天移除", removeFailed: "無法移除效果。請重新載入頁面確認狀態。", css: "自訂 CSS 開啟或無法確認時,代理的前景效果會暫停。", setting: "開啟網頁外觀設定", running: "播放中", starting: "啟動中", off: "已停止", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。", queued: "等待空位", unknown: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", pauseHelp: "恢復仍保留這台裝置的自動暫停紀錄與三次暫停安全上限。", paused: "已暫停", low: "降低畫質",
25
+ typing: "輸入時暫停,離開訊息輸入框就會播放。", covered: "選單或面板蓋住聊天時暫停。",
26
+ dialogOpen: "開著這個對話框時暫停。", wasRunning: "原本在播放;開著這個對話框時暫停。", modal: "有對話框開著時暫停。",
27
+ off3: "已自動暫停三次,這台裝置已關閉此效果,無法在這裡恢復。", interrupted: "播放時頁面被關閉而停止。按「恢復」再播放一次。",
28
+ cssOff: "自訂 CSS 開啟時關閉。", reducedMotion: "已關閉:效果跟隨這台裝置的「減少動態效果」設定。", saveData: "這個頁面的播放資訊已過期,請重新載入再試。",
29
+ hiddenTab: "等這個分頁顯示時再播放。", offscreen: "等聊天顯示在畫面上時再播放。",
30
+ safeMode: "安全模式下關閉。", publicLink: "公開連結上關閉。", disabled: "效果已關閉。" },
31
+ });
32
+
33
+ /** Read/admin dashboard sessions keep management controls; public/guest Chat playback has no owner identity. */
34
+ export function chatEffectsMenuAvailable() { return chatEffectIdentity(window) !== null; }
35
+
36
+ // #2055: what a row's state means, in words — never only "Stopped" when the runtime knows why.
37
+ const RECENT_MS = 5000;
38
+ const REASON_KEY = { __proto__: null, transport_unavailable: "chatFx.transport", owner_css: "chatFx.cssOff", reduced_motion: "chatFx.reducedMotion", save_data: "chatFx.saveData", preview_unavailable: "chatFx.previewMissing", preview_not_consented: "chatFx.consent", unsupported_host: "chatFx.unsupported", registry_unsupported: "chatFx.unsupported",
39
+ baseline_unsupported: "chatFx.baseline", baseline_unavailable: "chatFx.baseline", baseline_timeout: "chatFx.baseline", calibrating: "chatFx.calibration", safety_unknown: "chatFx.safetyUnknown", region_unavailable: "chatFx.region",
40
+ hidden: "chatFx.hiddenTab", offscreen: "chatFx.offscreen", safe_mode: "chatFx.safeMode", public_link: "chatFx.publicLink", disabled: "chatFx.disabled",
41
+ clock_unavailable: "chatFx.clock", gates_unknown: "chatFx.gatesUnknown", runtime_unavailable: "chatFx.missingRow", resource_unavailable: "chatFx.resource", resource_timeout: "chatFx.resource", front_limit: "chatFx.frontQueue",
42
+ safety_off: "chatFx.off3", interrupted: "chatFx.interrupted" };
43
+ /** `ownOpen`: this Effects dialog is open; `wasRunning`: the effect was running when it opened (shown as past, never as current). */
44
+ export function effectRowText(row, { ownOpen = false, wasRunning = false, doc = typeof document === "undefined" ? null : document } = {}) {
45
+ if (!row) return t("chatFx.missingRow");
46
+ if (typeof row.reason === "string" && row.reason !== "stock_overlay" && /^(stock_|trust_|geometry_)/.test(row.reason)) return t("chatFx.stock", row.reason.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80));
47
+ // The sticky history first (it is what Resume answers), then a pause, then what is playing or queued.
48
+ if (row.reason === "safety_off" || row.reason === "interrupted") return t(REASON_KEY[row.reason]);
49
+ if (row.reason === "paused" || row.reason === "host_budget") return t("chatFx.paused");
50
+ if (REASON_KEY[row.reason]) return t(REASON_KEY[row.reason]);
51
+ if (row.reason != null && !["stock_overlay", "modal", "frame_limit"].includes(row.reason))
52
+ return t("chatFx.rawReason", typeof row.reason === "string" ? row.reason.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : "invalid_reason_type");
53
+ if (row.phase === "running") return t("chatFx.running");
54
+ if (row.phase) return t("chatFx.starting");
55
+ if (row.verdict === "queued") return t("chatFx.queued");
56
+ if (row.reason === "stock_overlay" || row.reason === "modal") {
57
+ if (ownOpen) return t(wasRunning ? "chatFx.wasRunning" : "chatFx.dialogOpen");
58
+ if (row.reason === "modal") return t("chatFx.modal");
59
+ return t("chatFx.covered");
60
+ }
61
+ return t("chatFx.rawReason", typeof row.reason === "string" ? row.reason.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : "invalid_reason_type");
62
+ }
63
+
64
+ /** Fixed local actions: no server/Worker-provided destination or raw reason in a URL. */
65
+ export function effectReasonAction(reason) {
66
+ if (reason === "preview_not_consented" || reason === "preview_unavailable") return { reload: true, label: "chatFx.reloadHint" };
67
+ if (["disabled", "owner_css"].includes(reason)) return { href: "/settings/appearance", label: "chatFx.setting" };
68
+ if (["transport_unavailable", "baseline_unsupported", "baseline_unavailable", "baseline_timeout", "runtime_unavailable", "resource_unavailable", "resource_timeout", "clock_unavailable", "safety_unknown"].includes(reason)) return { reload: true, label: "chatFx.reloadHint" };
69
+ return null;
70
+ }
71
+ const localViewer = () => document.body.dataset.publicLink === "1" || document.body.dataset.tier === "guest";
72
+ function ReasonAction({ reason, onClose = () => {} }) {
73
+ if (reason === "safe_mode") return html`<button type="button" class="btn" onClick=${() => { onClose(); const url = new URL(location.href); url.searchParams.delete("safe"); location.assign(url.href); }}>${t("chatFx.leaveSafe")}</button>`;
74
+ if (reason === "reduced_motion") return html`<button type="button" class="btn" onClick=${() => { onClose(); setFollowsReducedMotion(false); }}>${t("chatFx.playMotion")}</button>`;
75
+ if (reason === "disabled" && localViewer()) return html`<button type="button" class="btn" onClick=${() => { onClose(); setThemesShown(true); }}>${t("chatFx.enableHere")}</button>`;
76
+ const action = document.body.dataset.tier === "guest" && reason === "owner_css" ? { reload: true, label: "chatFx.reloadHint" } : effectReasonAction(reason);
77
+ return action ? html`<button type="button" class="btn" onClick=${() => { onClose(); if (action.reload) location.reload(); else navigate(action.href); }}>${t(action.label)}</button>` : null;
78
+ }
79
+
80
+ const NO_PLAYBACK = () => {};
81
+ export function ChatEffects({ name, lease, element, store, open, onClose, onPlayback = NO_PLAYBACK }) {
82
+ const cssLife = useStore(ownerCssLifecycle);
83
+ useStore(themesStore); // Viewer-local off/on is visible immediately, independent of runtime callbacks.
84
+ // Which effects were running just before this dialog opened ("was running" is said in the past, never shown as
85
+ // current). The ⋯ menu that opens it is a barrier too, so "just before" is within RECENT_MS of the observed stop.
86
+ // Tracked where updates arrive (an unchanged snapshot is not republished while an effect keeps running).
87
+ const before = useRef(null), wasOpen = useRef(false), playing = useRef(new Map());
88
+ const runtime = useRef(null), [state, setState] = useState(null), [canWrite, setCanWrite] = useState(false), [busy, setBusy] = useState(false);
89
+ useLayoutEffect(() => {
90
+ let live = true, api = null;
91
+ const releases = [];
92
+ // Effects are optional: a failed setup must release its partial ownership without breaking Chat.
93
+ const cleanup = () => {
94
+ if (!live) return;
95
+ live = false;
96
+ try { api?.dispose?.(); } catch { /* continue releasing partial setup */ }
97
+ for (const off of releases) { try { if (typeof off === "function") off(); } catch { /* release the rest */ } }
98
+ if (runtime.current === api) runtime.current = null;
99
+ };
100
+ lease.hold(cleanup);
101
+ try {
102
+ setBusy(false); onClose(); setCanWrite(false); setState(null);
103
+ const host = window, owner = getAppEffects(host);
104
+ const current = () => live && lease.current();
105
+ if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true, reason: typeof host.OffscreenCanvas !== "function" || typeof host.Worker !== "function" ? "unsupported_host" : "runtime_unavailable" }); return cleanup; }
106
+ releases.push(owner.onEnd(() => { if (current()) setState({ error: "resource_unavailable", rows: [], effects: [], unavailable: true }); }));
107
+ playing.current = new Map(); // evidence belongs to this name and lease only
108
+ const track = rows => {
109
+ const now = performance.now(), seen = new Set();
110
+ for (const r of Array.isArray(rows) ? rows : []) {
111
+ seen.add(r.id);
112
+ if (r.phase === "running") playing.current.set(r.id, null);
113
+ else if (playing.current.get(r.id) === null) playing.current.set(r.id, now);
114
+ }
115
+ for (const [id, at] of playing.current) if (!seen.has(id) && at === null) playing.current.set(id, now);
116
+ };
117
+ const changed = value => {
118
+ if (!current()) return;
119
+ track(value?.rows);
120
+ const g = owner.gates();
121
+ setState({ ...value, unavailable: !g.enabled || !g.show || !g.transportReady || g.safe || g.reducedMotion });
122
+ };
123
+ api = owner.attachChat({ name, element: element.current, current,
124
+ observe: () => ({ state: chatEffectState(appStore.get(), name) }), onChange: changed });
125
+ runtime.current = api;
126
+ if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true, reason: "runtime_unavailable" }); return cleanup; }
127
+ if (chatEffectIdentity(host)) host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
128
+ api.sync();
129
+ return cleanup;
130
+ } catch {
131
+ cleanup();
132
+ if (lease.current()) setState({ error: "runtime_unavailable", rows: [], effects: [] });
133
+ return cleanup;
134
+ }
135
+ }, [name, lease]);
136
+ const rows = state?.rows ?? [], effects = state?.effects ?? [];
137
+ if (open && !wasOpen.current) {
138
+ const now = performance.now();
139
+ before.current = new Map([...playing.current].map(([id, stoppedAt]) => [id, stoppedAt === null || now - stoppedAt < RECENT_MS]));
140
+ }
141
+ if (!open) before.current = null;
142
+ wasOpen.current = open;
143
+ const css = cssLife.state !== "off" && effects.some(p => p.by !== "human" && p.manifest.layer === "front");
144
+ const remove = async id => { if (busy) return; setBusy(true); const api = runtime.current; const ok = await api?.remove?.(id); if (!lease.current() || runtime.current !== api) return; setBusy(false); if (!ok) toast(t("chatFx.removeFailed"), false); };
145
+ const playingNow = !state?.unavailable && !state?.error && rows.some(row => row.phase === "running" && row.reason == null);
146
+ useEffect(() => { onPlayback({ name, playing: playingNow }); return () => onPlayback({ name, playing: false }); }, [name, playingNow, onPlayback]);
147
+ const blocked = effects.flatMap(p => { const row = rows.find(r => r.id === p.id); return row?.phase === "running" && row.reason == null ? [] : [{ p, row }]; });
148
+ return html`${!open && localViewer() && effects.length ? html`<div class="chat-fx-notice"><label><input type="checkbox" checked=${themesShown()} onChange=${e => setThemesShown(e.currentTarget.checked)} /> ${t("chatFx.viewerShow")}</label></div>` : null}${!open && state?.error && !effects.length ? html`<div class="chat-fx-notice" role="status">${effectRowText({ reason: state.error })} <${ReasonAction} reason=${state.error} /></div>` : null}${!open && state?.reason ? html`<div class="chat-fx-notice" role="status">${effectRowText({ reason: state.reason })} <${ReasonAction} reason=${state.reason} /></div>` : null}${!open && blocked.length ? html`<div class="chat-fx-notice" role="status"><strong>${t("chatFx.blocked")}</strong>
149
+ ${blocked.map(({ p, row }) => html`<span key=${p.id}>${p.manifest.title}: ${effectRowText(row)} ${["paused", "host_budget"].includes(row?.reason) ? html`<small>${t(row.reason === "host_budget" ? "chatFx.budget" : "chatFx.pauseHelp")}</small>` : null} <${ReasonAction} reason=${row?.reason} /></span>`)}</div>` : null}${open ? html`<${Dialog} title=${t("chatFx.title")} onClose=${onClose} busy=${busy}>
150
+ ${state?.error ? html`<p>${t("chatFx.failed")}</p><button type="button" class="btn" onClick=${() => location.reload()}>${t("chatFx.reload")}</button>` : null}
151
+ ${css ? html`<p>${t("chatFx.css")}</p><button type="button" class="btn" onClick=${() => { onClose(); navigate("/settings/appearance"); }}>${t("chatFx.setting")}</button>` : null}
152
+ ${!effects.length && !state?.error ? html`<p>${t(state?.unavailable ? "chatFx.unknown" : state?.loading || !state ? "chatFx.loading" : "chatFx.empty")}</p>` : null}
153
+ ${effects.map(p => { const row = rows.find(r => r.id === p.id); return html`<section key=${p.id}><h3>${p.manifest.title}</h3><p>${effectRowText(row, { ownOpen: open, wasRunning: before.current?.get(p.id) === true })}${row?.quality === "low" ? ` · ${t("chatFx.low")}` : ""}</p>
154
+ <${ReasonAction} reason=${row?.reason} onClose=${onClose} />
155
+ ${["paused", "host_budget"].includes(row?.reason) ? html`<p>${t(row.reason === "host_budget" ? "chatFx.budget" : "chatFx.pauseHelp")}</p>` : null}
156
+ <button type="button" class="btn" disabled=${busy} onClick=${() => runtime.current?.pause?.(p.id)}>${t("chatFx.pause")}</button>
157
+ <button type="button" class="btn" disabled=${busy} onClick=${() => { if (!runtime.current?.resume?.(p.id)) toast(t(rows.find(r => r.id === p.id)?.reason === "safety_off" ? "chatFx.off3" : "chatFx.resumeFailed"), false); }}>${t("chatFx.resume")}</button>
158
+ ${canWrite ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => remove(p.id)}>${t("chatFx.remove")}</button>` : null}</section>`; })}
159
+ </${Dialog}>` : null}`;
160
+ }
@@ -0,0 +1,58 @@
1
+ // #1629: transient host notifications, never message content or replay/history authority.
2
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
3
+ function fields(value, keys, closed = false) {
4
+ try {
5
+ if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
6
+ if (closed && Reflect.ownKeys(value).length !== keys.length) return null;
7
+ const out = {};
8
+ for (const k of keys) { const d = Object.getOwnPropertyDescriptor(value, k); if (!d || !own(d, "value")) return null; out[k] = d.value; }
9
+ return out;
10
+ } catch { return null; }
11
+ }
12
+ const identity = x => !!x && typeof x.boot === "string" && /^[0-9a-f]{1,32}$/.test(x.boot)
13
+ && Number.isSafeInteger(x.id) && x.id > 0 && typeof x.instance === "string" && x.instance.length >= 1 && x.instance.length <= 128;
14
+ const MAX_AGE = 5000, MAX_BOOTS = 64, MAX_PENDING = 64;
15
+ export function createLiveAgentReplies(now) {
16
+ const seen = new Map(), pending = new Map(), listeners = new Set();
17
+ let last = null, revoked = false;
18
+ function time() {
19
+ if (revoked) return null;
20
+ let t; try { t = now(); } catch { t = null; }
21
+ if (typeof t !== "number" || !Number.isFinite(t) || t < 0 || last !== null && t < last) { revoked = true; pending.clear(); return null; }
22
+ last = t;
23
+ for (const [key, item] of pending) if (t - item.at > MAX_AGE) pending.delete(key);
24
+ return t;
25
+ }
26
+ function ingress(extra, t) {
27
+ const e = fields(extra, ["source", "receivedAt"], true);
28
+ return e && e.source === "sse" && typeof e.receivedAt === "number" && Number.isFinite(e.receivedAt) && e.receivedAt >= 0 && e.receivedAt <= t && t - e.receivedAt <= MAX_AGE ? e.receivedAt : null;
29
+ }
30
+ return {
31
+ // Record every accepted ingress, including history/poll, before its message-array merge/eviction.
32
+ message(value, extra) {
33
+ const m = fields(value, ["boot", "id", "instance"]); if (!identity(m)) return;
34
+ const role = fields(value, ["role"])?.role;
35
+ const t = time(); if (t === null) return;
36
+ const key = `${m.boot}:${m.id}`, previous = seen.get(m.boot) || 0;
37
+ pending.delete(key);
38
+ if (!seen.has(m.boot) && seen.size >= MAX_BOOTS) { revoked = true; pending.clear(); return; }
39
+ seen.set(m.boot, Math.max(previous, m.id));
40
+ const at = ingress(extra, t);
41
+ if (m.id <= previous || role !== "agent" || at === null) return;
42
+ // Reconnect replay has no receipts: discard old candidates, never the durable-in-page watermark.
43
+ if (pending.size >= MAX_PENDING) pending.delete(pending.keys().next().value);
44
+ pending.set(key, { instance: m.instance, at });
45
+ },
46
+ receipt(value, extra) {
47
+ const r = fields(value, ["v", "boot", "id", "instance"], true); if (!identity(r) || r.v !== 1) return false;
48
+ const t = time(); if (t === null) return false;
49
+ const at = ingress(extra, t); if (at === null) return false;
50
+ const key = `${r.boot}:${r.id}`, candidate = pending.get(key); pending.delete(key);
51
+ if (!candidate || candidate.instance !== r.instance || at < candidate.at || t - candidate.at > MAX_AGE) return false;
52
+ const event = Object.freeze({ v: 1, boot: r.boot, id: r.id, instance: r.instance });
53
+ for (const fn of [...listeners]) { try { fn(event); } catch { /* one host observer cannot suppress another */ } }
54
+ return true;
55
+ },
56
+ subscribe(fn) { if (typeof fn !== "function") return () => {}; listeners.add(fn); return () => listeners.delete(fn); },
57
+ };
58
+ }
@@ -36,7 +36,7 @@
36
36
  }
37
37
 
38
38
  function link(href, label) {
39
- return '<a href="' + href + '" target="_blank" rel="noopener noreferrer">' + label + "</a>";
39
+ return '<a href="' + href + '" target="_blank" rel="noopener noreferrer" data-trust="link">' + label + "</a>";
40
40
  }
41
41
 
42
42
  // Placeholders survive the later passes untouched (they contain no Markdown characters).
@@ -53,11 +53,14 @@
53
53
 
54
54
  // #1630: a Discord server emoji, `<:name:id>` / `<a:name:id>` — escaped by now — drawn as its picture. Only an id the
55
55
  // page knows (the fleet's servers: /ui/emojis) and only from the dashboard's own route; the src is built from the
56
- // matched digits, the name (word characters only) goes into alt/title. An unknown id stays the literal text.
56
+ // matched digits, the name (word characters only) goes into alt/title. Any other — an id the page does not know, or
57
+ // before the list has come — reads `:name:` (never the raw markup); so does a picture that does not load
58
+ // (chat-thread.js swaps it: an <img> sized to an emoji showed an empty box, the user's "tofu").
57
59
  var EMOJI_RE = /&lt;(a?):([A-Za-z0-9_]{2,32}):(\d{15,25})&gt;/g;
58
60
  function emojiImg(id, name) {
59
61
  return '<img class="emoji" src="/ui/img/emoji/' + id + '" alt=":' + name + ':" title=":' + name + ':" decoding="async">';
60
62
  }
63
+ function emojiText(name) { return '<span class="emoji-text" title=":' + name + ':">:' + name + ':</span>'; }
61
64
 
62
65
  // #1682: Discord mentions — `<@id>` / `<@!id>` (a user), `<@&id>` (a role), `<#id>` (a channel) — escaped by now (the
63
66
  // role's & too), drawn as a pill: the name the page knows (store.mentions(kind, id), inserted escaped), else @user /
@@ -82,7 +85,7 @@
82
85
  // path held its whole column wide.
83
86
  s = s.replace(/`([^`\n]+)`/g, function (_m, code) { return stash(store, "<code>" + (store.inCell ? code.replace(/\//g, "/<wbr>") : code) + "</code>"); });
84
87
  var known = store.emojis;
85
- if (known) s = s.replace(EMOJI_RE, function (m, _a, name, id) { return known(id) ? stash(store, emojiImg(id, name)) : m; });
88
+ s = s.replace(EMOJI_RE, function (_m, _a, name, id) { return stash(store, known && known(id) ? emojiImg(id, name) : emojiText(name)); });
86
89
  var names = store.mentions;
87
90
  s = s.replace(MENTION_RE, function (_m, sigil, id) {
88
91
  var kind = MENTION_KIND[sigil];
@@ -577,6 +580,21 @@
577
580
  * With { htmlCards: true } (a server-marked agent reply, as for renderMarkdown) each .html/.htm file also gets an
578
581
  * empty card placeholder <div class="html-card" data-att="<id>"> after the list — the id is 32 hex, nothing else.
579
582
  */
583
+ // The box a photo is drawn in (app.css: .msg .att-img img, max 260 × 220): its own size, scaled down to fit.
584
+ var PHOTO_MAX_W = 260, PHOTO_MAX_H = 220;
585
+ /**
586
+ * #2184: a photo's drawn size as width/height attributes, from the size the server read from its headers: the box is there
587
+ * before the picture loads, so the text around it never moves when it does (a chat opened cold jittered, as each
588
+ * photo grew from nothing). Nothing when the size is not known (drawn as before).
589
+ */
590
+ function photoBox(a) {
591
+ var w = a.width, h = a.height;
592
+ var ok = function (v) { return typeof v === "number" && v >= 1 && v <= 65535 && Math.floor(v) === v; };
593
+ if (!ok(w) || !ok(h)) return "";
594
+ var k = Math.min(1, PHOTO_MAX_W / w, PHOTO_MAX_H / h);
595
+ return ' width="' + Math.max(1, Math.round(w * k)) + '" height="' + Math.max(1, Math.round(h * k)) + '"';
596
+ }
597
+
580
598
  function attachmentsHtml(attachments, opts) {
581
599
  if (!Array.isArray(attachments)) return "";
582
600
  var cards = !!(opts && opts.htmlCards);
@@ -594,17 +612,17 @@
594
612
  if (cards && isHtmlAttachment(a)) after.push('<div class="html-card" data-att="' + a.id + '"></div>');
595
613
  var url = "/ui/file/" + a.id;
596
614
  var kind = inline ? attachmentPreviewType(a) : null;
597
- if (kind === "video") after.push('<div class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
598
- else if (kind === "audio") after.push('<div class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
615
+ if (kind === "video") after.push('<div data-trust="media" class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
616
+ else if (kind === "audio") after.push('<div data-trust="media" class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
599
617
  else if (kind) after.push('<div class="text-card" data-att="' + a.id + '" data-type="' + kind + '"></div>');
600
618
  var name = escapeHtml(a.name || "file");
601
619
  if (a.kind === "photo") {
602
- out.push('<a class="att-img" href="' + url + '" target="_blank" rel="noopener noreferrer"><img src="' + url + '" alt="' + name + '" loading="lazy"></a>');
620
+ out.push('<a class="att-img" href="' + url + '" target="_blank" rel="noopener noreferrer"><img src="' + url + '" alt="' + name + '"' + photoBox(a) + ' loading="lazy"></a>');
603
621
  } else {
604
622
  out.push('<a class="att-file" href="' + url + '" download="' + name + '">📎 ' + name + ' <span class="att-size">' + escapeHtml(formatSize(a.size)) + "</span></a>");
605
623
  }
606
624
  });
607
- return (out.length ? '<div class="atts">' + out.join("") + "</div>" : "") + after.join("");
625
+ return (out.length ? '<div data-trust="attachment" class="atts">' + out.join("") + "</div>" : "") + after.join("");
608
626
  }
609
627
 
610
628
  return {
@@ -8,6 +8,7 @@
8
8
  // Requests and #1374: the history of an instance is read once per page, when a person first opens its chat (a
9
9
  // navigation — it counts as use, as the old click did). Coming back to it reads nothing; the stream keeps it current.
10
10
  import "./chat-render.js";
11
+ import { createLiveAgentReplies } from "./chat-live-replies.js";
11
12
 
12
13
  const R = () => globalThis.AgendChatRender;
13
14
 
@@ -41,7 +42,7 @@ const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
41
42
  /** Build a store. `deps` (fetch, toast, announce, t, now) are injected so tests can drive every path. */
42
43
  export function createChatStore(deps) {
43
44
  const s = {
44
- msgs: {}, drafts: {}, failedSends: {}, pendingFiles: {}, sending: {}, inFlightFiles: {},
45
+ msgs: {}, drafts: {}, failedSends: {}, pendingFiles: {}, sending: {}, inFlightFiles: {}, sendFailures: {}, instanceStatus: {}, waking: {},
45
46
  prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), rbBusy: new Set(),
46
47
  pollMine: readPollMine(),
47
48
  // #1823 (#1828 review): the server's clock minus this device's. A poll's deadline is the server's; a page whose clock
@@ -56,12 +57,14 @@ export function createChatStore(deps) {
56
57
  const pastedTexts = new WeakMap();
57
58
  const now = deps.now || (() => (typeof performance !== "undefined" ? performance.now() : Date.now()));
58
59
  const t = deps.t, tf = deps.t;
60
+ const liveReplies = createLiveAgentReplies(now);
59
61
 
60
62
  /** Tell subscribers what changed: `kind` "msgs" (the thread redraws) or "state" (everything else). */
61
63
  function changed(instance, kind = "state") { for (const fn of [...subs]) fn(instance, kind); }
62
- async function api(method, path, body) {
64
+ async function api(method, path, body, signal) {
63
65
  const o = { method, headers: { "Content-Type": "application/json" } };
64
66
  if (body) o.body = JSON.stringify(body);
67
+ if (signal) o.signal = signal;
65
68
  const r = await deps.fetch(path, o);
66
69
  return r.json();
67
70
  }
@@ -80,6 +83,7 @@ export function createChatStore(deps) {
80
83
  function applyStatus(d) {
81
84
  if (!d || !Array.isArray(d.instances)) return;
82
85
  for (const i of d.instances) {
86
+ s.instanceStatus[i.name] = i.status;
83
87
  noteExec(i.name, (i.execution_state !== undefined ? i.execution_state : i.state) ?? null);
84
88
  const waits = i.state === "awaiting_input" ? (typeof i.interaction_summary === "string" ? i.interaction_summary : "") : null;
85
89
  if (i.name === s.current && waits != null && s.awaiting[i.name] == null) deps.announce(tf("chat.awaiting", i.name));
@@ -99,8 +103,9 @@ export function createChatStore(deps) {
99
103
  function noteServerClock(c) {
100
104
  if (c && typeof c.now === "number" && Number.isFinite(c.now)) s.clockSkew = c.now - Date.now();
101
105
  }
102
- function ingest(m) {
106
+ function ingest(m, extra) {
103
107
  if (!m || typeof m.instance !== "string") return;
108
+ liveReplies.message(m, extra);
104
109
  const before = s.msgs[m.instance];
105
110
  s.msgs[m.instance] = R().mergeMessages(before, [m], 500);
106
111
  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));
@@ -139,14 +144,20 @@ export function createChatStore(deps) {
139
144
  if (!r || !r.answered) deps.toast(r && r.error ? r.error : t("chat.disconnected"), false);
140
145
  }
141
146
 
142
- /** Read an instance's history once per page (the first time a person opens its chat). */
143
- async function openHistory(name, lease) {
144
- if (s.historyRead.has(name)) return false;
147
+ /**
148
+ * Read an instance's history once per page (the first time a person opens its chat). #2188: `signal` — the chat was
149
+ * left before the answer came (clicking down the sidebar): the read is aborted, nothing is merged, and the next visit
150
+ * reads again.
151
+ */
152
+ async function openHistory(name, lease, signal) {
153
+ if (s.historyRead.has(name) || signal?.aborted) return false;
145
154
  s.historyRead.add(name);
146
155
  try {
147
- const r = await api("GET", `/ui/history?instance=${encodeURIComponent(name)}&limit=200`);
156
+ const r = await api("GET", `/ui/history?instance=${encodeURIComponent(name)}&limit=200`, undefined, signal);
157
+ if (signal?.aborted) { s.historyRead.delete(name); return false; }
148
158
  if (!r || !Array.isArray(r.messages)) { s.historyRead.delete(name); return true; }
149
159
  // Kept even when the person has moved on: the store is page-lived, and the answer belongs to `name`.
160
+ for (const m of r.messages) liveReplies.message(m);
150
161
  s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500);
151
162
  changed(name, "msgs");
152
163
  } catch { s.historyRead.delete(name); /* the live stream still works; the next visit tries again */ }
@@ -162,7 +173,7 @@ export function createChatStore(deps) {
162
173
  // A prompt already held (being answered, or claimed from Needs you) keeps its busy/answered state: a repeat of it
163
174
  // only refreshes its text and buttons (#1463 review).
164
175
  if (!had) s.prompts[p.nonce] = { ...p, resolved: false, busy: false };
165
- else if (!had.resolved) Object.assign(had, { text: p.text, actions: p.actions, expiresAt: p.expiresAt });
176
+ else if (!had.resolved) Object.assign(had, { text: p.text, links: p.links, actions: p.actions, expiresAt: p.expiresAt });
166
177
  if (p.instance === s.current) changed(p.instance);
167
178
  else if (fresh) deps.toast(tf("chat.promptFor", p.instance, p.text), false);
168
179
  }
@@ -174,7 +185,7 @@ export function createChatStore(deps) {
174
185
  for (const p of list) {
175
186
  const had = s.prompts[p.nonce];
176
187
  if (!had) s.prompts[p.nonce] = { ...p, resolved: false, busy: false };
177
- else if (!had.resolved) Object.assign(had, { text: p.text, actions: p.actions, expiresAt: p.expiresAt });
188
+ else if (!had.resolved) Object.assign(had, { text: p.text, links: p.links, actions: p.actions, expiresAt: p.expiresAt });
178
189
  }
179
190
  for (const n of Object.keys(s.prompts)) if (!s.prompts[n].resolved && !open.has(n)) s.prompts[n].resolved = true;
180
191
  changed(null);
@@ -242,6 +253,8 @@ export function createChatStore(deps) {
242
253
  const files = (s.pendingFiles[target] || []).slice();
243
254
  if ((!txt && !files.length) || !target || s.sending[target]) return;
244
255
  s.sending[target] = true;
256
+ s.waking[target] = s.instanceStatus[target] === "paused";
257
+ delete s.sendFailures[target];
245
258
  s.drafts[target] = "";
246
259
  s.pendingFiles[target] = []; s.inFlightFiles[target] = files;
247
260
  changed(target);
@@ -253,6 +266,7 @@ export function createChatStore(deps) {
253
266
  r = await api("POST", "/ui/send", ids.length ? { instance: target, message: txt, attachments: ids } : { instance: target, message: txt });
254
267
  } catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
255
268
  s.sending[target] = false;
269
+ delete s.waking[target];
256
270
  delete s.inFlightFiles[target];
257
271
  if (r && !r.error) { changed(target); return; }
258
272
  if (files.length) s.pendingFiles[target] = R().checkFiles(files, s.pendingFiles[target] || []).kept;
@@ -261,9 +275,21 @@ export function createChatStore(deps) {
261
275
  if (R().settleFailedSend(target, s.current, shown, shown ? (s.drafts[target] || "") : "") === "restore") s.drafts[target] = txt;
262
276
  else s.failedSends[target] = s.failedSends[target] ? `${s.failedSends[target]}\n${txt}` : txt;
263
277
  }
278
+ const why = r && r.reason;
279
+ const key = typeof why === "string" ? `chat.sendFailure_${why}` : "";
280
+ const localized = key ? t(key) : "";
281
+ const error = localized && localized !== key ? localized : (r && r.error ? r.error : t("chat.disconnected"));
282
+ s.sendFailures[target] = { error, text: txt, files };
264
283
  changed(target);
265
- deps.toast(r && r.error ? r.error : t("chat.disconnected"), false);
284
+ deps.toast(error, false);
285
+ }
286
+ function canRetrySend(target) {
287
+ const failed = s.sendFailures[target], files = s.pendingFiles[target] || [];
288
+ return !!failed && !s.sending[target] && !s.failedSends[target]
289
+ && (s.drafts[target] || "").trim() === failed.text
290
+ && files.length === failed.files.length && files.every((f, i) => f === failed.files[i]);
266
291
  }
292
+ function retrySend(target) { if (canRetrySend(target)) return send(target); }
267
293
  function addFiles(target, list) {
268
294
  if (!target) return;
269
295
  const inFlight = s.inFlightFiles[target] || [];
@@ -303,8 +329,8 @@ export function createChatStore(deps) {
303
329
  changed(target);
304
330
  }
305
331
  function discardFailed(target) { delete s.failedSends[target]; changed(target); }
306
- /** Typing: kept for its chat, no redraw (the composer already shows it). */
307
- function setDraft(target, value) { s.drafts[target] = value; }
332
+ /** Typing stays local, except a failed send's Retry button must follow edits too. */
333
+ function setDraft(target, value) { s.drafts[target] = value; if (s.sendFailures[target]) changed(target); }
308
334
 
309
335
  // ── Stop the reply under way (the composer's Stop, or Esc). Set before the request goes, so a held Esc or a second
310
336
  // click sends nothing more (#1317); "Stopping…" until the agent goes idle, or a minute passes.
@@ -380,6 +406,7 @@ export function createChatStore(deps) {
380
406
  stream.on("status", applyStatus);
381
407
  stream.on("activity", applyActivity);
382
408
  stream.on("message", ingest);
409
+ stream.on("agent_reply_live", liveReplies.receipt);
383
410
  stream.on("delivery", (d) => applyDeliveries([d]));
384
411
  stream.on("deliveries", applyDeliveries);
385
412
  stream.on("prompt", onPrompt);
@@ -393,10 +420,11 @@ export function createChatStore(deps) {
393
420
  state: s,
394
421
  subscribe(fn) { subs.add(fn); return () => subs.delete(fn); },
395
422
  setCurrent(name) { s.current = name; },
423
+ onAgentReply: liveReplies.subscribe,
396
424
  attach, applyStatus, applyActivity, ingest, applyDeliveries, openHistory,
397
425
  onPrompt, applyPrompts, resolvePrompt, answerPrompt, answerByNonce, holdPrompt, releasePrompt, promptsFor,
398
426
  applyReplyButtons, clickReplyButton,
399
- uploadFile, send, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, putBack, discardFailed, setDraft,
427
+ uploadFile, send, canRetrySend, retrySend, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, putBack, discardFailed, setDraft,
400
428
  cancelReply, isUser, runCommand, dismissCommand, commandFree,
401
429
  };
402
430
  }