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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/dist/agent-cli.js +13 -112
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/agent-endpoint.d.ts +0 -1
  4. package/dist/agent-endpoint.js +1 -9
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-whats-new.d.ts +3 -6
  7. package/dist/agent-whats-new.js +7 -18
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/auth-api.d.ts +0 -7
  10. package/dist/auth-api.js +1 -15
  11. package/dist/auth-api.js.map +1 -1
  12. package/dist/authority/authority-state.js +1 -1
  13. package/dist/authority/authority-state.js.map +1 -1
  14. package/dist/authority/web-authority.d.ts +9 -4
  15. package/dist/authority/web-authority.js +10 -5
  16. package/dist/authority/web-authority.js.map +1 -1
  17. package/dist/backend/antigravity.js +6 -1
  18. package/dist/backend/antigravity.js.map +1 -1
  19. package/dist/backend/claude-code.d.ts +7 -0
  20. package/dist/backend/claude-code.js +34 -28
  21. package/dist/backend/claude-code.js.map +1 -1
  22. package/dist/backend/claude-transcript-errors.d.ts +18 -0
  23. package/dist/backend/claude-transcript-errors.js +19 -0
  24. package/dist/backend/claude-transcript-errors.js.map +1 -0
  25. package/dist/backend/codex-session-lookup.d.ts +16 -3
  26. package/dist/backend/codex-session-lookup.js +45 -11
  27. package/dist/backend/codex-session-lookup.js.map +1 -1
  28. package/dist/backend/codex.d.ts +16 -0
  29. package/dist/backend/codex.js +184 -35
  30. package/dist/backend/codex.js.map +1 -1
  31. package/dist/backend/grok.js +58 -12
  32. package/dist/backend/grok.js.map +1 -1
  33. package/dist/backend/kiro.js +2 -0
  34. package/dist/backend/kiro.js.map +1 -1
  35. package/dist/backend/muse.js +2 -1
  36. package/dist/backend/muse.js.map +1 -1
  37. package/dist/backend/types.d.ts +46 -0
  38. package/dist/backend/types.js.map +1 -1
  39. package/dist/bundled-skills.d.ts +4 -6
  40. package/dist/bundled-skills.js +10 -14
  41. package/dist/bundled-skills.js.map +1 -1
  42. package/dist/channel/adapters/discord.js +60 -17
  43. package/dist/channel/adapters/discord.js.map +1 -1
  44. package/dist/channel/mcp-server.js +5 -5
  45. package/dist/channel/mcp-server.js.map +1 -1
  46. package/dist/channel/mcp-tools.d.ts +1 -3
  47. package/dist/channel/mcp-tools.js +4 -58
  48. package/dist/channel/mcp-tools.js.map +1 -1
  49. package/dist/classic-channel-manager.d.ts +4 -1
  50. package/dist/classic-channel-manager.js +21 -7
  51. package/dist/classic-channel-manager.js.map +1 -1
  52. package/dist/cli.js +21 -4
  53. package/dist/cli.js.map +1 -1
  54. package/dist/config-history.d.ts +50 -0
  55. package/dist/config-history.js +263 -0
  56. package/dist/config-history.js.map +1 -0
  57. package/dist/config-validator.js +7 -7
  58. package/dist/config-validator.js.map +1 -1
  59. package/dist/config.d.ts +3 -0
  60. package/dist/config.js +11 -4
  61. package/dist/config.js.map +1 -1
  62. package/dist/daemon.d.ts +73 -3
  63. package/dist/daemon.js +334 -121
  64. package/dist/daemon.js.map +1 -1
  65. package/dist/daily-summary.d.ts +8 -1
  66. package/dist/daily-summary.js +8 -5
  67. package/dist/daily-summary.js.map +1 -1
  68. package/dist/dashboard-live.d.ts +1 -1
  69. package/dist/dashboard-live.js +3 -3
  70. package/dist/dashboard-live.js.map +1 -1
  71. package/dist/dashboard-permissions.d.ts +1 -1
  72. package/dist/dashboard-permissions.js +1 -3
  73. package/dist/dashboard-permissions.js.map +1 -1
  74. package/dist/delivery-outbox.d.ts +4 -0
  75. package/dist/delivery-outbox.js +12 -2
  76. package/dist/delivery-outbox.js.map +1 -1
  77. package/dist/doctor.d.ts +4 -0
  78. package/dist/doctor.js +16 -1
  79. package/dist/doctor.js.map +1 -1
  80. package/dist/fleet-level-config.js +1 -1
  81. package/dist/fleet-level-config.js.map +1 -1
  82. package/dist/fleet-manager.d.ts +70 -54
  83. package/dist/fleet-manager.js +257 -322
  84. package/dist/fleet-manager.js.map +1 -1
  85. package/dist/fleet-process-handlers.d.ts +5 -0
  86. package/dist/fleet-process-handlers.js +21 -0
  87. package/dist/fleet-process-handlers.js.map +1 -1
  88. package/dist/fleet-service-owner.d.ts +11 -0
  89. package/dist/fleet-service-owner.js +3 -0
  90. package/dist/fleet-service-owner.js.map +1 -0
  91. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
  92. package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
  93. package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
  94. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +29 -67
  95. package/dist/instance-lifecycle.d.ts +19 -0
  96. package/dist/instance-lifecycle.js +52 -10
  97. package/dist/instance-lifecycle.js.map +1 -1
  98. package/dist/locale.js +92 -42
  99. package/dist/locale.js.map +1 -1
  100. package/dist/needs-you-hub.d.ts +1 -0
  101. package/dist/needs-you-hub.js +6 -3
  102. package/dist/needs-you-hub.js.map +1 -1
  103. package/dist/needs-you-live.d.ts +3 -1
  104. package/dist/needs-you-live.js +24 -2
  105. package/dist/needs-you-live.js.map +1 -1
  106. package/dist/needs-you.d.ts +13 -2
  107. package/dist/needs-you.js +11 -1
  108. package/dist/needs-you.js.map +1 -1
  109. package/dist/notify-socket.d.ts +10 -0
  110. package/dist/notify-socket.js +45 -0
  111. package/dist/notify-socket.js.map +1 -0
  112. package/dist/outbound-handlers.js +19 -7
  113. package/dist/outbound-handlers.js.map +1 -1
  114. package/dist/outbound-schemas.d.ts +0 -62
  115. package/dist/outbound-schemas.js +3 -42
  116. package/dist/outbound-schemas.js.map +1 -1
  117. package/dist/parked-notices.d.ts +87 -0
  118. package/dist/parked-notices.js +194 -0
  119. package/dist/parked-notices.js.map +1 -0
  120. package/dist/public-link-progress.d.ts +5 -2
  121. package/dist/public-link-progress.js +4 -3
  122. package/dist/public-link-progress.js.map +1 -1
  123. package/dist/public-web-gateway.js +0 -9
  124. package/dist/public-web-gateway.js.map +1 -1
  125. package/dist/public-web-link.d.ts +12 -0
  126. package/dist/public-web-link.js +29 -5
  127. package/dist/public-web-link.js.map +1 -1
  128. package/dist/quota-notice.d.ts +23 -0
  129. package/dist/quota-notice.js +22 -0
  130. package/dist/quota-notice.js.map +1 -0
  131. package/dist/scheduler/task-board.d.ts +0 -13
  132. package/dist/scheduler/task-board.js +0 -14
  133. package/dist/scheduler/task-board.js.map +1 -1
  134. package/dist/sd-notify.d.ts +10 -1
  135. package/dist/sd-notify.js +27 -2
  136. package/dist/sd-notify.js.map +1 -1
  137. package/dist/search/search-docs.d.ts +1 -1
  138. package/dist/search/search-docs.js +2 -2
  139. package/dist/search/search-docs.js.map +1 -1
  140. package/dist/settings-api.d.ts +16 -0
  141. package/dist/settings-api.js +55 -2
  142. package/dist/settings-api.js.map +1 -1
  143. package/dist/settings-change.js +3 -1
  144. package/dist/settings-change.js.map +1 -1
  145. package/dist/settings-confirmation.d.ts +17 -3
  146. package/dist/settings-confirmation.js +8 -5
  147. package/dist/settings-confirmation.js.map +1 -1
  148. package/dist/settings-http-confirmation.js +31 -1
  149. package/dist/settings-http-confirmation.js.map +1 -1
  150. package/dist/setup-wizard.d.ts +3 -0
  151. package/dist/setup-wizard.js +4 -2
  152. package/dist/setup-wizard.js.map +1 -1
  153. package/dist/task-board-waker.d.ts +4 -1
  154. package/dist/task-board-waker.js +13 -2
  155. package/dist/task-board-waker.js.map +1 -1
  156. package/dist/tool-permissions.js +3 -10
  157. package/dist/tool-permissions.js.map +1 -1
  158. package/dist/topic-commands.d.ts +27 -7
  159. package/dist/topic-commands.js +95 -36
  160. package/dist/topic-commands.js.map +1 -1
  161. package/dist/transcript-monitor.d.ts +7 -0
  162. package/dist/transcript-monitor.js +48 -3
  163. package/dist/transcript-monitor.js.map +1 -1
  164. package/dist/tunnel/cloudflared.d.ts +5 -0
  165. package/dist/tunnel/cloudflared.js +32 -1
  166. package/dist/tunnel/cloudflared.js.map +1 -1
  167. package/dist/tunnel/named-diagnostics.d.ts +2 -1
  168. package/dist/tunnel/named-diagnostics.js +4 -1
  169. package/dist/tunnel/named-diagnostics.js.map +1 -1
  170. package/dist/tunnel/named-link-test.d.ts +90 -0
  171. package/dist/tunnel/named-link-test.js +270 -0
  172. package/dist/tunnel/named-link-test.js.map +1 -0
  173. package/dist/tunnel/purpose-lane.d.ts +3 -1
  174. package/dist/tunnel/purpose-lane.js +2 -0
  175. package/dist/tunnel/purpose-lane.js.map +1 -1
  176. package/dist/types.d.ts +8 -6
  177. package/dist/ui/chat-ambient.js +4 -4
  178. package/dist/ui/chat-commands.js +3 -2
  179. package/dist/ui/chat-store.js +4 -1
  180. package/dist/ui/chat-strings.js +12 -46
  181. package/dist/ui/chat-thread.js +51 -59
  182. package/dist/ui/fleet-office-skin.js +37 -14
  183. package/dist/ui/fleet-office.js +31 -7
  184. package/dist/ui/html-attachment.js +22 -8
  185. package/dist/ui/panel-chat.js +21 -50
  186. package/dist/ui/panel-needs.js +11 -9
  187. package/dist/ui/panel-settings.js +5 -3
  188. package/dist/ui/preview-panel.js +12 -21
  189. package/dist/ui/preview.js +10 -3
  190. package/dist/ui/search-dialog.js +30 -14
  191. package/dist/ui/settings-appearance.js +1 -3
  192. package/dist/ui/settings-confirm.js +18 -0
  193. package/dist/ui/settings-guests.js +13 -4
  194. package/dist/ui/settings-learn.js +1 -1
  195. package/dist/ui/settings-mods.js +5 -12
  196. package/dist/ui/settings-named-test.js +144 -0
  197. package/dist/ui/settings-registry.js +0 -1
  198. package/dist/ui/settings-strings.js +14 -14
  199. package/dist/ui/settings-web-authority.js +4 -3
  200. package/dist/ui/shared/app-i18n.js +15 -4
  201. package/dist/ui/shared/app-needs.js +5 -1
  202. package/dist/ui/shared/app-prefs.js +17 -17
  203. package/dist/ui/shared/app-shell.js +47 -9
  204. package/dist/ui/shared/app-store.js +6 -2
  205. package/dist/ui/shared/app.css +33 -39
  206. package/dist/ui/shared/app.js +5 -4
  207. package/dist/ui/shared/instance-nav.js +9 -9
  208. package/dist/ui/shared/instance-switch.js +8 -2
  209. package/dist/ui/shared/panel-view.js +22 -8
  210. package/dist/ui/shared/view-strings.js +6 -0
  211. package/dist/ui/shared/web-authorize.js +35 -13
  212. package/dist/usage/usage-api.d.ts +3 -0
  213. package/dist/usage/usage-api.js +5 -1
  214. package/dist/usage/usage-api.js.map +1 -1
  215. package/dist/view-api.d.ts +2 -0
  216. package/dist/view-api.js +64 -0
  217. package/dist/view-api.js.map +1 -1
  218. package/dist/web-api.d.ts +9 -17
  219. package/dist/web-api.js +257 -371
  220. package/dist/web-api.js.map +1 -1
  221. package/dist/web-appearance-tools.d.ts +0 -2
  222. package/dist/web-appearance-tools.js +2 -5
  223. package/dist/web-appearance-tools.js.map +1 -1
  224. package/dist/web-appearance-view.d.ts +0 -6
  225. package/dist/web-appearance-view.js +0 -1
  226. package/dist/web-appearance-view.js.map +1 -1
  227. package/dist/web-appearance.d.ts +14 -7
  228. package/dist/web-appearance.js +5 -6
  229. package/dist/web-appearance.js.map +1 -1
  230. package/dist/web-asset-parts.d.ts +1 -17
  231. package/dist/web-asset-parts.js +1 -15
  232. package/dist/web-asset-parts.js.map +1 -1
  233. package/dist/web-auth.js +1 -7
  234. package/dist/web-auth.js.map +1 -1
  235. package/dist/web-chat-history.d.ts +3 -7
  236. package/dist/web-chat-history.js +10 -17
  237. package/dist/web-chat-history.js.map +1 -1
  238. package/dist/web-commands.d.ts +9 -1
  239. package/dist/web-commands.js +13 -0
  240. package/dist/web-commands.js.map +1 -1
  241. package/dist/web-experimental.d.ts +2 -11
  242. package/dist/web-experimental.js +8 -36
  243. package/dist/web-experimental.js.map +1 -1
  244. package/dist/web-guest-access.d.ts +40 -2
  245. package/dist/web-guest-access.js +50 -14
  246. package/dist/web-guest-access.js.map +1 -1
  247. package/dist/web-mod-bundle.d.ts +2 -45
  248. package/dist/web-mod-bundle.js +15 -63
  249. package/dist/web-mod-bundle.js.map +1 -1
  250. package/dist/web-mod-store.d.ts +3 -48
  251. package/dist/web-mod-store.js +30 -97
  252. package/dist/web-mod-store.js.map +1 -1
  253. package/dist/web-prefs.d.ts +13 -0
  254. package/dist/web-prefs.js +38 -0
  255. package/dist/web-prefs.js.map +1 -1
  256. package/dist/web-preview.d.ts +6 -13
  257. package/dist/web-preview.js +12 -23
  258. package/dist/web-preview.js.map +1 -1
  259. package/dist/web-session.d.ts +11 -3
  260. package/dist/web-session.js +22 -11
  261. package/dist/web-session.js.map +1 -1
  262. package/dist/web-settings-patch.d.ts +1 -0
  263. package/dist/web-settings-patch.js +6 -3
  264. package/dist/web-settings-patch.js.map +1 -1
  265. package/dist/web-settings-tools.d.ts +0 -13
  266. package/dist/web-settings-tools.js +12 -84
  267. package/dist/web-settings-tools.js.map +1 -1
  268. package/launcher/agend-install.cjs +7 -1
  269. package/launcher/fleet-service-owner.cjs +150 -0
  270. package/package.json +1 -1
  271. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
  272. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
  273. package/dist/task-board-summary.d.ts +0 -101
  274. package/dist/task-board-summary.js +0 -177
  275. package/dist/task-board-summary.js.map +0 -1
  276. package/dist/ui/app-effects.js +0 -251
  277. package/dist/ui/chat-effect-posters.js +0 -42
  278. package/dist/ui/chat-effects.js +0 -167
  279. package/dist/ui/effect-trial-controller.js +0 -124
  280. package/dist/ui/effect-trial-offer.js +0 -2
  281. package/dist/ui/effect-trial-panel.js +0 -40
  282. package/dist/ui/effects-waker.js +0 -2
  283. package/dist/ui/settings-effects.js +0 -202
  284. package/dist/ui/settings-owner-effects.js +0 -82
  285. package/dist/ui/shared/effect-app-runtime.js +0 -299
  286. package/dist/ui/shared/effect-baseline.js +0 -64
  287. package/dist/ui/shared/effect-chat-runtime.js +0 -162
  288. package/dist/ui/shared/effect-cursor.js +0 -126
  289. package/dist/ui/shared/effect-display-metadata.js +0 -39
  290. package/dist/ui/shared/effect-frame.js +0 -146
  291. package/dist/ui/shared/effect-geometry.js +0 -137
  292. package/dist/ui/shared/effect-host-style.js +0 -10
  293. package/dist/ui/shared/effect-owner-regions.js +0 -74
  294. package/dist/ui/shared/effect-owner-source.js +0 -122
  295. package/dist/ui/shared/effect-policy.js +0 -151
  296. package/dist/ui/shared/effect-poster-reader.js +0 -98
  297. package/dist/ui/shared/effect-posters.js +0 -117
  298. package/dist/ui/shared/effect-profiler.js +0 -184
  299. package/dist/ui/shared/effect-profiles.js +0 -65
  300. package/dist/ui/shared/effect-registry.js +0 -545
  301. package/dist/ui/shared/effect-runtime-loader.js +0 -236
  302. package/dist/ui/shared/effect-safety.js +0 -153
  303. package/dist/ui/shared/effect-stock-snapshot.js +0 -43
  304. package/dist/ui/shared/effect-stock-trust.js +0 -217
  305. package/dist/ui/shared/effect-trial-shadow.js +0 -62
  306. package/dist/ui/shared/effect-trial-source.js +0 -103
  307. package/dist/ui/shared/effect-trial-stage.js +0 -170
  308. package/dist/ui/shared/effect-trial.css +0 -40
  309. package/dist/ui/shared/feature-flags.js +0 -127
  310. package/dist/ui/shared/owner-effect-prefs.js +0 -117
  311. package/dist/web-effect-bootstrap.d.ts +0 -12
  312. package/dist/web-effect-bootstrap.js +0 -49
  313. package/dist/web-effect-bootstrap.js.map +0 -1
  314. package/dist/web-effect-bundle.d.ts +0 -28
  315. package/dist/web-effect-bundle.js +0 -171
  316. package/dist/web-effect-bundle.js.map +0 -1
  317. package/dist/web-effect-canonical.d.ts +0 -8
  318. package/dist/web-effect-canonical.js +0 -27
  319. package/dist/web-effect-canonical.js.map +0 -1
  320. package/dist/web-effect-library.d.ts +0 -114
  321. package/dist/web-effect-library.js +0 -530
  322. package/dist/web-effect-library.js.map +0 -1
  323. package/dist/web-effect-playback.d.ts +0 -30
  324. package/dist/web-effect-playback.js +0 -71
  325. package/dist/web-effect-playback.js.map +0 -1
  326. package/dist/web-effect-poster.d.ts +0 -8
  327. package/dist/web-effect-poster.js +0 -52
  328. package/dist/web-effect-poster.js.map +0 -1
  329. package/dist/web-effect-profiles.d.ts +0 -118
  330. package/dist/web-effect-profiles.js +0 -181
  331. package/dist/web-effect-profiles.js.map +0 -1
  332. package/dist/web-effect-service.d.ts +0 -93
  333. package/dist/web-effect-service.js +0 -335
  334. package/dist/web-effect-service.js.map +0 -1
  335. package/dist/web-effect-tools.d.ts +0 -48
  336. package/dist/web-effect-tools.js +0 -129
  337. package/dist/web-effect-tools.js.map +0 -1
  338. package/dist/web-effect-trials.d.ts +0 -86
  339. package/dist/web-effect-trials.js +0 -204
  340. package/dist/web-effect-trials.js.map +0 -1
  341. package/dist/web-effect-upload.d.ts +0 -27
  342. package/dist/web-effect-upload.js +0 -185
  343. package/dist/web-effect-upload.js.map +0 -1
  344. package/dist/web-effect-worker.d.ts +0 -8
  345. package/dist/web-effect-worker.js +0 -170
  346. package/dist/web-effect-worker.js.map +0 -1
  347. package/dist/web-effect.d.ts +0 -103
  348. package/dist/web-effect.js +0 -153
  349. package/dist/web-effect.js.map +0 -1
  350. package/dist/web-experimental-http.d.ts +0 -2
  351. package/dist/web-experimental-http.js +0 -12
  352. package/dist/web-experimental-http.js.map +0 -1
  353. package/dist/web-mod-agent.d.ts +0 -50
  354. package/dist/web-mod-agent.js +0 -207
  355. package/dist/web-mod-agent.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"purpose-lane.js","sourceRoot":"","sources":["../../src/tunnel/purpose-lane.ts"],"names":[],"mappings":"AAYA,MAAM,QAAQ,GAAG,GAAqB,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAClH,kGAAkG;AAClG,MAAM,OAAO,iBAAiB;IAEC,OAAO;IAD5B,KAAK,GAA2E,IAAI,CAAC;IAC7F,YAA6B,OAA8C;uBAA9C,OAAO;IAA0C,CAAC;IAC/E,OAAO,CAAC,OAA8B,EAAE,EAAU;QAChD,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9G,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,QAAiD,CAAC;QACtD,IAAI,QAA+C,CAAC;QACpD,MAAM,OAAO,GAAG,GAAS,EAAE,GAAG,IAAI,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW;YAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3G,MAAM,WAAW,GAAsB;YACrC,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;gBAC3B,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW;oBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;gBACzG,IAAI,QAAQ;oBAAE,OAAO,QAAQ,CAAC;gBAC9B,OAAO,GAAG,KAAK,CAAC;gBAChB,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;oBAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;wBAAE,OAAO,GAAG,IAAI,CAAC;oBACpD,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC,CAAC;gBACH,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,IAAI,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;gBAC3B,4GAA4G;gBAC5G,6CAA6C;gBAC7C,MAAM,KAAK,GAAG,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACjD,OAAO,QAAQ,KAAK,CAAC,KAAK,IAAI,EAAE;oBAC9B,MAAM,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;oBAChE,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC;oBAC3B,OAAO,EAAE,CAAC;oBACV,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC,EAAE,CAAC;YACP,CAAC;YACD,aAAa,EAAE,OAAO;SACvB,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;QAC1C,OAAO,WAAW,CAAC;IACrB,CAAC;CACF"}
1
+ {"version":3,"file":"purpose-lane.js","sourceRoot":"","sources":["../../src/tunnel/purpose-lane.ts"],"names":[],"mappings":"AAYA,MAAM,QAAQ,GAAG,GAAqB,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAClH,kGAAkG;AAClG,MAAM,OAAO,iBAAiB;IAEC,OAAO;IAD5B,KAAK,GAA2E,IAAI,CAAC;IAC7F,YAA6B,OAA8C;uBAA9C,OAAO;IAA0C,CAAC;IAC/E,+FAA+F;IAC/F,MAAM,KAAoB,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,OAA6C,EAAE,EAAU;QAC/D,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9G,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,QAAiD,CAAC;QACtD,IAAI,QAA+C,CAAC;QACpD,MAAM,OAAO,GAAG,GAAS,EAAE,GAAG,IAAI,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW;YAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3G,MAAM,WAAW,GAAsB;YACrC,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;gBAC3B,IAAI,IAAI,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW;oBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;gBACzG,IAAI,QAAQ;oBAAE,OAAO,QAAQ,CAAC;gBAC9B,OAAO,GAAG,KAAK,CAAC;gBAChB,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;oBAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;wBAAE,OAAO,GAAG,IAAI,CAAC;oBACpD,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC,CAAC;gBACH,OAAO,QAAQ,CAAC;YAClB,CAAC;YACD,IAAI,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;gBAC3B,4GAA4G;gBAC5G,6CAA6C;gBAC7C,MAAM,KAAK,GAAG,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACjD,OAAO,QAAQ,KAAK,CAAC,KAAK,IAAI,EAAE;oBAC9B,MAAM,QAAQ,EAAE,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBAChC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;oBAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;oBAChE,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC;oBAC3B,OAAO,EAAE,CAAC;oBACV,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC,EAAE,CAAC;YACP,CAAC;YACD,aAAa,EAAE,OAAO;SACvB,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;QAC1C,OAAO,WAAW,CAAC;IACrB,CAAC;CACF"}
package/dist/types.d.ts CHANGED
@@ -296,8 +296,8 @@ export interface FleetDefaults extends Partial<InstanceConfig> {
296
296
  tips?: boolean | "dm";
297
297
  /** #2246 step 3: the task board's fleet-level settings. */
298
298
  task_board?: {
299
- /** The daily summary's time in the fleet's zone ("HH:MM", 24 h), or "off". Default "09:00". */
300
- summary_at?: string;
299
+ /** #2804: the task board's daily summary is gone; a value written before is accepted and ignored. */
300
+ summary_at?: unknown;
301
301
  };
302
302
  webhooks?: WebhookConfig[];
303
303
  }
@@ -526,9 +526,10 @@ export interface WebVerifiedAuthorityConfig {
526
526
  */
527
527
  named_link?: boolean;
528
528
  /**
529
- * #1939 §0.3: sessions over the temporary public link (the quick tunnel `/web` opens) may be authorized too — off by
530
- * default (the user's decision, 2026-10-23). Turning it on is confirmed in a General or on the host (never by the
531
- * asking session); off applies at once and every receipt from a quick-link session reads as none.
529
+ * #1939 §0.3: sessions over the temporary public link (the quick tunnel `/web` opens) may be authorized too — on by
530
+ * default (the user's decision, 2026-10-28; off before), and `false` turns it off. Each session still needs its
531
+ * account's Confirm in the DM. Turning it back on is confirmed in a General or on the host (never by the asking
532
+ * session); off applies at once and every receipt from a quick-link session reads as none.
532
533
  */
533
534
  quick_link?: boolean;
534
535
  }
@@ -552,7 +553,8 @@ export interface WebGuestsConfig {
552
553
  */
553
554
  grants?: Record<string, {
554
555
  name?: string;
555
- instances: string[];
556
+ instances: string[]; /** #2855: "trusted" adds a read-only View and search; absent: chat. */
557
+ level?: "chat" | "trusted";
556
558
  }>;
557
559
  /** A guest session's limits: absolute 1–24 h (default 12), idle 15–720 min (default 120). */
558
560
  session?: {
@@ -252,13 +252,13 @@ export function ChatAmbient({ name, surface, part }) {
252
252
  }
253
253
 
254
254
  /**
255
- * The one-time note (the user's direction): the system asks for reduced motion and this chat's ambient or Canvas Worker plays anyway —
255
+ * The one-time note (the user's direction): the system asks for reduced motion and this chat's ambient plays anyway —
256
256
  * say so once on this device, and turn it off right here (this device's switch: the same on the public link, where
257
- * Settings is not). Shown only while an ambient or Canvas Worker is playing.
257
+ * Settings is not). Shown only while an ambient is playing.
258
258
  */
259
- export function AmbientMotionNote({ name, canvasPlaying = false }) {
259
+ export function AmbientMotionNote({ name }) {
260
260
  const block = useAmbientBlock();
261
- const shown = canvasPlaying || !block && !!ambientFor(name);
261
+ const shown = !block && !!ambientFor(name);
262
262
  const noteDue = motionNoteDue();
263
263
  const [open, setOpen] = useState(false);
264
264
  useEffect(() => {
@@ -24,10 +24,11 @@ const BY_NAME = new Map(CHAT_COMMANDS.map((c) => [c.name, c]));
24
24
  * The palette for a draft: null when it is not one (no leading "/", or past the command's name), else the commands
25
25
  * whose name starts with what was typed (possibly none: then it goes as a message).
26
26
  */
27
- export function paletteFor(draft) {
27
+ export function paletteFor(draft, allowed) {
28
28
  const m = /^\/([a-z]*)$/.exec(draft || "");
29
29
  if (!m) return null;
30
- return CHAT_COMMANDS.filter((c) => c.name.startsWith(m[1]));
30
+ // #2855: a guest's palette — only the commands the fleet says it may run on this agent (its status frame's `commands`).
31
+ return CHAT_COMMANDS.filter((c) => c.name.startsWith(m[1]) && (!allowed || allowed.includes(c.name)));
31
32
  }
32
33
 
33
34
  /** `/name args` for a known command → { command, args }; anything else (unknown, not a command) → null. */
@@ -43,7 +43,7 @@ const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
43
43
  export function createChatStore(deps) {
44
44
  const s = {
45
45
  msgs: {}, drafts: {}, replies: {}, pendingFiles: {}, sending: {}, outgoing: {}, instanceStatus: {}, waking: {},
46
- prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), rbBusy: new Set(),
46
+ prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), allHidden: new Set(), rbBusy: new Set(),
47
47
  pollMine: readPollMine(),
48
48
  // #1823 (#1828 review): the server's clock minus this device's. A poll's deadline is the server's; a page whose clock
49
49
  // is a few seconds off would count a 10 s poll wrong. Only the server's explicit `clock` sets it (#1830 review): a
@@ -166,6 +166,8 @@ export function createChatStore(deps) {
166
166
  if (signal?.aborted) { s.historyRead.delete(name); return false; }
167
167
  if (!r || !Array.isArray(r.messages)) { s.historyRead.delete(name); return true; }
168
168
  // Kept even when the person has moved on: the store is page-lived, and the answer belongs to `name`.
169
+ // #2848: a guest's chat whose every message is hidden from it (between agents): its empty chat says so.
170
+ if (r.allHidden === true) s.allHidden.add(name); else s.allHidden.delete(name);
169
171
  for (const m of r.messages) liveReplies.message(m);
170
172
  s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500);
171
173
  settleOutgoing(name);
@@ -575,5 +577,6 @@ export function createChatStore(deps) {
575
577
  uploadFile, send, retryOutgoing, deleteOutgoing, putBackOutgoing, threadRows, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, setDraft,
576
578
  startReply, clearReply, findQuoted,
577
579
  cancelReply, isUser, runCommand, dismissCommand, commandFree,
580
+ allHidden: (name) => s.allHidden.has(name),
578
581
  };
579
582
  }
@@ -17,26 +17,7 @@ register("chat", {
17
17
  reply: "Reply", replyingTo: "Replying to {0}", replyCancel: "Cancel the reply (Esc)", replyHidden: "an earlier message", replyPhoto: "[Photo]", replyFile: "[File]",
18
18
  replyGone: "That message is no longer in this chat.", replyJump: "Show the message this replies to", copyText: "Copy text", copyLink: "Copy link", msgMenu: "Message",
19
19
 
20
- effectTrialReason_checking: "Checking this session…",
21
- effectTrialReason_localAdmin: "Sign in as an admin outside the public link to Try.",
22
- effectTrialReason_chatChanged: "Open the chat that offered this trial.",
23
- effectTrialReason_busy: "Stop the current trial before starting another.",
24
- effectTrialReason_safe: "Turn off safe mode to Try.",
25
- effectTrialReason_effectsOff: "Turn on effects to Try.",
26
- effectTrialReason_reducedMotion: "Effects follow this device’s reduced-motion setting.",
27
- effectTrialReason_saveData: "Your device requests reduced data use.",
28
- effectTrialReason_transport: "The isolated effect player is unavailable. Reload this page.",
29
- effectTrialReason_unsupported: "Canvas Worker playback is unavailable in this browser.",
30
- effectTrialReason_inactive: "Return to this page to Try.",
31
- effectTrialReason_dialog: "Close the other menu or dialog to Try.",
32
- effectTrialReason_expired: "This trial offer has expired.",
33
- effectTrialSignIn: "Sign in here",
34
- effectTrialStopOther: "Stop current trial",
35
- effectTrialSettings: "Open playback settings",
36
- effectTrialPlayMotion: "Play effects anyway",
37
- effectTrial: "Effect trial", effectTrialLocal: "A sample on this page. Nothing is applied or saved.",
38
- effectTrialStopped: "Try stopped", effectTrialStart: "Try", effectTrialExpired: "This trial offer has expired.", effectTrialUnavailable: "This trial is no longer available.",
39
- cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
20
+ cmdConfirm: "Clear", cmdConfirmBackend: "Switch and restart", cmdRunning: "Running /{0}…", cmdNotCommand: "Not a command here — Enter sends it as a message.", cmdNotGuests: "Not one a guest can run here.", cmdDismiss: "Dismiss", cmdCommands: "Commands",
40
21
  compactRunning: "Compacting…", compactDone: "✅ Conversation compacted", compactUnknown: "This backend does not report when compaction ends.",
41
22
  compactTimeout: "No sign that compaction finished within {0} minutes — check the agent's own chat.",
42
23
  compactLost: "Can't follow this compaction: the command did not reach the agent, or the agent stopped or changed backend.",
@@ -57,13 +38,14 @@ register("chat", {
57
38
  stopping: "Stopping {0}…", awaiting: "{0} is waiting for your input", approxNote: "Read from the terminal, so approximate", elapsedTitle: "Time since this page saw it start (approximate)",
58
39
  dropHere: "Drop files to attach them", asText: "As text", pasteAttached: "A long paste ({0} characters) was attached as a text file", pasteKeptAsText: "No room for another attachment: the long paste stays as text",
59
40
  send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerHintTouch: "Return adds a line · tap Send to send", composerMore: "/ for commands · Esc stops the reply", guestMore: "Esc stops the reply",
60
- guestNote: "You're a guest: the agent sees your messages as a guest's (recognition only).", guestNoCommands: "Guests can't run commands — send it without the leading /.", attachTitle: "Attach files (any type — or paste / drop them)",
41
+ guestNote: "You're a guest: the agent sees your messages as a guest's (recognition only).", guestNoCommands: "Guests can't send that — send it without the leading /.", guestCommandNotAllowed: "{0} isn't available to guests. Only this channel's admins can run it.", attachTitle: "Attach files (any type — or paste / drop them)",
61
42
  pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
62
43
  pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
63
44
  pvConfirm: "Allow HTML previews?\n\nThis applies on every device where you are signed in. A preview runs the agent's HTML in an isolated frame. It cannot use your login, but it may be able to send data out, and it can slow or freeze this tab. You still click Preview each time.",
64
45
  pvNever: "Never preview HTML on this device", pvNeverUndo: "Let this device preview HTML again", pvStarting: "Starting the preview…",
65
46
  pvChanged: "Preview stopped — the message changed. Preview again.", pvTruncated: "Truncated — ask the agent to send it as a .html file.",
66
- pvLoading: "Reading the file…", pvAttOver: "This file is over 1 MiB; it is not previewed.",
47
+ pvLoading: "Reading the file…", pvAttOver: "This file is over 50 MB; it is not previewed. Download it to open it.",
48
+ pvLarge: "This file is large ({0}). Preview anyway?", pvAnyway: "Preview anyway",
67
49
  pvAttGone: "This file is no longer available here (it changed, or the fleet restarted). Ask the agent to send it again.",
68
50
  pvAttFailed: "Could not read this file. Try again.",
69
51
  pvBannerAck: "I understand", pvBannerAckTitle: "Hide this notice until you sign in again",
@@ -83,7 +65,7 @@ register("chat", {
83
65
  pvWhyNever: "Previews are switched off on this device for this session.",
84
66
  pvWhyOrigin: "Previews are off: this page is at {0}, but the fleet believes it is at {1} (a proxy that rewrites Host). Pass the external Host through, or set web.preview_origin.",
85
67
  pvUnknownAddress: "an unknown address", pvWhyServer: "Previews are not available here.",
86
- pvHtmlOver: "This HTML is over 1 MiB; it is not previewed.",
68
+ pvHtmlOver: "This HTML is over 50 MB; it is not previewed. Download it to open it.",
87
69
  pvNoAnswer: "Preview unavailable: the preview did not answer within 3 s. If an earlier preview froze, it may still be running — open the dashboard in a new tab. Over SSH, forward the preview port too.",
88
70
  pvStoppedAnswering: "The preview stopped answering, so it was closed.", pvSwitchedOff: "Previews were switched off on this device.",
89
71
  pvAnother: "Another preview started.", pvWrongBoot: "Preview unavailable: something else answered on the preview port.",
@@ -115,6 +97,7 @@ register("chat", {
115
97
  working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
116
98
  file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
117
99
  noMessages: "Type a task below.",
100
+ guestAllHidden: "Nothing here you can see yet", guestAllHiddenHint: "This agent's recent activity is between agents, which guests don't see. Its chat appears here when someone talks in its channel.",
118
101
  pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
119
102
  details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
120
103
  started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
@@ -158,26 +141,7 @@ register("chat", {
158
141
  reply: "回覆", replyingTo: "回覆 {0}", replyCancel: "取消回覆(Esc)", replyHidden: "之前的一則訊息", replyPhoto: "[圖片]", replyFile: "[檔案]",
159
142
  replyGone: "那則訊息已不在這個聊天裡。", replyJump: "顯示被回覆的訊息", copyText: "複製文字", copyLink: "複製連結", msgMenu: "訊息",
160
143
 
161
- effectTrialReason_checking: "正在確認工作階段…",
162
- effectTrialReason_localAdmin: "請在公開連結以外的儀表板以管理員登入後試播。",
163
- effectTrialReason_chatChanged: "請開啟發出試播邀請的聊天。",
164
- effectTrialReason_busy: "請先停止目前的試播。",
165
- effectTrialReason_safe: "請先關閉安全模式。",
166
- effectTrialReason_effectsOff: "請先開啟效果。",
167
- effectTrialReason_reducedMotion: "效果跟隨這台裝置的「減少動態效果」設定。",
168
- effectTrialReason_saveData: "裝置目前要求節省數據。",
169
- effectTrialReason_transport: "隔離效果播放器目前無法使用。請重新載入頁面。",
170
- effectTrialReason_unsupported: "此瀏覽器無法播放 Canvas Worker 效果。",
171
- effectTrialReason_inactive: "請回到本頁後試播。",
172
- effectTrialReason_dialog: "請先關閉其他選單或對話框。",
173
- effectTrialReason_expired: "試播邀請已過期。",
174
- effectTrialSignIn: "在此登入",
175
- effectTrialStopOther: "停止目前試播",
176
- effectTrialSettings: "開啟播放設定",
177
- effectTrialPlayMotion: "仍然播放效果",
178
- effectTrial: "效果試播", effectTrialLocal: "僅在本頁試播,不會套用或儲存。",
179
- effectTrialStopped: "試播已停止", effectTrialStart: "試播", effectTrialExpired: "試播邀請已過期。", effectTrialUnavailable: "此試播已無法使用。",
180
- cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdDismiss: "關閉", cmdCommands: "指令",
144
+ cmdConfirm: "清空", cmdConfirmBackend: "切換並重新啟動", cmdRunning: "執行 /{0} 中…", cmdNotCommand: "這裡沒有這個指令——按 Enter 會當成一般訊息送出。", cmdNotGuests: "訪客在這裡不能使用這個指令。", cmdDismiss: "關閉", cmdCommands: "指令",
181
145
  compactRunning: "壓縮中…", compactDone: "✅ 對話已壓縮", compactUnknown: "這個後端不會回報壓縮何時完成。",
182
146
  compactTimeout: "{0} 分鐘內沒有看到壓縮完成的紀錄,請到代理自己的對話確認。",
183
147
  compactLost: "無法再追蹤這次壓縮:指令沒有送到代理,或代理已停止、換了後端。",
@@ -198,13 +162,14 @@ register("chat", {
198
162
  stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
199
163
  dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
200
164
  send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerHintTouch: "Return 換行 · 按「送出」傳送", composerMore: "/ 叫出指令 · Esc 中止回覆", guestMore: "Esc 中止回覆",
201
- guestNote: "你是訪客:agent 會把你的訊息視為訪客的訊息(僅供辨識)。", guestNoCommands: "訪客不能執行指令,請去掉開頭的 / 再送出。", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
165
+ guestNote: "你是訪客:agent 會把你的訊息視為訪客的訊息(僅供辨識)。", guestNoCommands: "訪客不能這樣送出,請去掉開頭的 / 再送出。", guestCommandNotAllowed: "訪客不能使用 {0},只有這個頻道的管理員可以執行。", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
202
166
  pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
203
167
  pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
204
168
  pvConfirm: "要允許預覽 HTML 嗎?\n\n這會套用到你登入的每一台裝置。預覽會在隔離的框架裡執行 agent 的 HTML。它無法使用你的登入,但可能把資料傳出去,也可能讓這個分頁變慢或卡住。每次仍需要你按「預覽」。",
205
169
  pvNever: "這台裝置永不預覽 HTML", pvNeverUndo: "讓這台裝置再次可以預覽 HTML", pvStarting: "正在啟動預覽…",
206
170
  pvChanged: "預覽已停止——訊息內容變了。請再預覽一次。", pvTruncated: "內容被截斷——請 agent 改用 .html 檔案傳送。",
207
- pvLoading: "正在讀取檔案…", pvAttOver: "這個檔案超過 1 MiB,不提供預覽。",
171
+ pvLoading: "正在讀取檔案…", pvAttOver: "這個檔案超過 50 MB,不提供預覽,請下載後開啟。",
172
+ pvLarge: "這個檔案比較大({0}),仍要預覽嗎?", pvAnyway: "仍要預覽",
208
173
  pvAttGone: "這裡已無法取得這個檔案(檔案已變更,或艦隊已重新啟動)。請 agent 再傳一次。",
209
174
  pvAttFailed: "無法讀取這個檔案,請再試一次。",
210
175
  pvBannerAck: "我知道了", pvBannerAckTitle: "隱藏這則提醒,直到下次登入",
@@ -222,7 +187,7 @@ register("chat", {
222
187
  pvWhyNever: "這台裝置在這個工作階段已關閉預覽。",
223
188
  pvWhyOrigin: "預覽已關閉:這個頁面位於 {0},但艦隊認為它位於 {1}(有會改寫 Host 的代理)。請讓外部 Host 原樣傳入,或設定 web.preview_origin。",
224
189
  pvUnknownAddress: "未知的網址", pvWhyServer: "這裡無法使用預覽。",
225
- pvHtmlOver: "這段 HTML 超過 1 MiB,不提供預覽。",
190
+ pvHtmlOver: "這段 HTML 超過 50 MB,不提供預覽,請下載後開啟。",
226
191
  pvNoAnswer: "無法預覽:預覽在 3 秒內沒有回應。如果先前的預覽卡住了,它可能還在執行——請在新分頁開啟儀表板。透過 SSH 使用時,也要轉送預覽的連接埠。",
227
192
  pvStoppedAnswering: "預覽沒有回應,已將它關閉。", pvSwitchedOff: "這台裝置已關閉預覽。",
228
193
  pvAnother: "另一個預覽已開始。", pvWrongBoot: "無法預覽:預覽連接埠上回應的不是這個艦隊。",
@@ -254,6 +219,7 @@ register("chat", {
254
219
  working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
255
220
  file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
256
221
  noMessages: "直接在下方輸入任務。",
222
+ guestAllHidden: "這裡還沒有你能看到的訊息", guestAllHiddenHint: "這個 agent 最近只和其他 agent 往來,訪客看不到這些。有人在它的頻道說話後,對話會出現在這裡。",
257
223
  pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
258
224
  details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
259
225
  started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
@@ -8,8 +8,7 @@
8
8
  // Unrelated updates (a new message, another message's tick) stop nothing (#1306 §6.2, #1408 §4).
9
9
  import "./chat-render.js";
10
10
  import "./preview.js";
11
- import { canvasWorkerEnabled, onCanvasWorkerChange, onCanvasMetadataChange, readCanvasTrialMessage } from "/assets/feature-flags.js";
12
- import { loadHtmlAttachment } from "./html-attachment.js";
11
+ import { HTML_PREVIEW_MAX, loadHtmlAttachment, sizeLabel } from "./html-attachment.js";
13
12
  import { chatTime, chatTimeFull, onDayChange } from "./chat-time.js";
14
13
  import { textLinkSegments } from "/assets/app-route.js";
15
14
 
@@ -24,6 +23,12 @@ export const TEXT_PREVIEW_LINES = 200;
24
23
  export const TEXT_PREVIEW_NODES = 4000;
25
24
  /** A csv/tsv wider than this is shown as text, as Markdown tables are capped at 20 columns. */
26
25
  export const TEXT_PREVIEW_COLUMNS = 50;
26
+ /**
27
+ * #2863: the most a text card reads (bytes). It stays 1 MiB while an HTML preview goes to 8 MiB (and 50 MiB on
28
+ * "Preview anyway"): a text card reads, parses and formats the file on this page's own thread (Markdown, highlighting,
29
+ * a CSV table; Show all draws every line), where an HTML preview runs in its own frame. A bigger file is a download.
30
+ */
31
+ export const TEXT_CARD_MAX = 1024 * 1024;
27
32
  const P = () => globalThis.AgendPreview;
28
33
  /** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
29
34
  export const STATUS_FOLD_LINES = 6;
@@ -120,7 +125,7 @@ export function createThread(list, scroller, opts) {
120
125
  if (el.textContent !== v) el.textContent = v;
121
126
  }
122
127
  // #1770: "today" ends at the fleet's midnight; the labels are rewritten then (and on every render), while there are any.
123
- let offDay = null, trialTimer = null;
128
+ let offDay = null;
124
129
  function armDay() {
125
130
  if (nodes.size && !offDay) offDay = onDayChange(() => { for (const v of nodes.values()) timeLabel(v.node); });
126
131
  else if (!nodes.size && offDay) { offDay(); offDay = null; }
@@ -133,8 +138,6 @@ export function createThread(list, scroller, opts) {
133
138
  // is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
134
139
  const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
135
140
  if (x.outgoing) return outgoingHtml(x);
136
- const trial = readCanvasTrialMessage(x);
137
- if (trial) return canvasWorkerEnabled() ? trialHtml(trial, time, x) : "";
138
141
  if (x.role === "status") return statusHtml(x, time);
139
142
  const u = opts.isUser(x);
140
143
  // renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
@@ -213,26 +216,7 @@ export function createThread(list, scroller, opts) {
213
216
  ? `<button type="button" class="click-link" data-act="showButtons" data-arg="${escAttr(of)}" title="${escAttr(tr("chat.clickShow"))}">${inner}</button>`
214
217
  : `<span class="click-text">${inner}</span>`}</div>`;
215
218
  }
216
- function trialNow() { try { const t = opts.serverNow ? opts.serverNow() : Date.now(); return Number.isFinite(t) ? t : Infinity; } catch { return Infinity; } }
217
- const trialReasons = new Set(["checking", "localAdmin", "chatChanged", "busy", "safe", "effectsOff", "reducedMotion", "saveData", "unsupported", "transport", "inactive", "dialog", "expired"]);
218
- function trialStatus(message) {
219
- const reason = opts.effectTrialStatus?.(message);
220
- return reason === "ready" || trialReasons.has(reason) ? reason : opts.canTryEffect?.(message) === true ? "ready" : "localAdmin";
221
- }
222
- function trialHtml(offer, time, message) {
223
- const reason = trialNow() >= offer.expiresAt ? "expired" : PUBLIC_LINK() ? "localAdmin" : trialStatus(message);
224
- const rawFailure = opts.effectTrialFailure?.(message), failure = typeof rawFailure === "string" ? rawFailure.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 80) : null;
225
- const available = reason === "ready", fix = !PUBLIC_LINK() && !available && reason !== "expired" && reason !== "checking" && typeof opts.fixEffectTrial === "function";
226
- const label = available ? "chat.effectTrialStart" : reason === "busy" ? "chat.effectTrialStopOther" : reason === "localAdmin" ? "chat.effectTrialSignIn" : reason === "reducedMotion" ? "chat.effectTrialPlayMotion" : "chat.effectTrialSettings";
227
- const button = available || fix ? `<button type="button" class="btn btn-sm effect-trial-start" data-act="effectTrial" data-arg="${escAttr(msgKey(message))}">${escAttr(tr(label))}</button>` : "";
228
- return `<div class="msg status-line"><div class="meta">${time}</div><div class="body"><div class="effect-trial-offer" data-trust="thread-control"><strong>${escAttr(tr("chat.effectTrial"))}: ${escAttr(offer.title)}</strong><div>${escAttr(tr(available ? "chat.effectTrialLocal" : "chat.effectTrialReason_" + reason))}</div>${failure ? `<div class="effect-trial-failure" role="status">${escAttr(tr("chat.effectTrialStopped"))}: ${escAttr(failure)}</div>` : ""}${button}</div></div></div>`;
229
- }
230
- function armTrials() {
231
- if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
232
- if (!canvasWorkerEnabled()) return;
233
- const now = trialNow(), next = msgs.map(x => readCanvasTrialMessage(x)).filter(x => x && x.expiresAt > now).reduce((n, x) => Math.min(n, x.expiresAt), Infinity);
234
- if (Number.isFinite(next)) trialTimer = setTimeout(() => { trialTimer = null; render(msgs); }, Math.max(1, Math.min(2147483647, next - now)));
235
- }
219
+
236
220
  /**
237
221
  * #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
238
222
  * a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
@@ -428,7 +412,7 @@ export function createThread(list, scroller, opts) {
428
412
  if (reading) return;
429
413
  if (text !== null) { body.hidden = !body.hidden; show.textContent = tr(body.hidden ? "chat.tcShow" : "chat.tcHide"); return; }
430
414
  const mine = reading = ++reads; note.textContent = tr("chat.pvLoading");
431
- const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size });
415
+ const r = await loadHtmlAttachment({ id: att.id, name: att.name, size: att.size }, undefined, { ask: TEXT_CARD_MAX, max: TEXT_CARD_MAX });
432
416
  if (reading !== mine) return; // dropped (its chat left meanwhile): a new click reads again
433
417
  reading = 0;
434
418
  if (!ph.isConnected) return; // the card left the page while it was read: nothing to draw
@@ -484,6 +468,8 @@ export function createThread(list, scroller, opts) {
484
468
  const head = el("div", "pv-head"); head.setAttribute("data-trust", "thread-control");
485
469
  const label = el("span", "pv-label", att ? `${tr("chat.pvHtml")} · ${att.name}` : tr("chat.pvHtml"));
486
470
  const run = el("button", "btn btn-sm pv-run", tr("chat.pvPreview")); run.type = "button";
471
+ // #2863: a large file (over 8 MB) is previewed only on this second click, in the card or the panel as first asked.
472
+ const anywayB = el("button", "btn btn-sm pv-anyway", tr("chat.pvAnyway")); anywayB.type = "button"; anywayB.hidden = true;
487
473
  const stopB = el("button", "btn btn-sm pv-stop", tr("chat.pvStop")); stopB.type = "button"; stopB.hidden = true;
488
474
  // An attachment already has its download link (the file itself) right above the card.
489
475
  const dl = att ? null : el("button", "btn btn-sm btn-ghost pv-dl", tr("chat.pvDownload"));
@@ -496,7 +482,7 @@ export function createThread(list, scroller, opts) {
496
482
  const optB = el("button", "chip-btn pv-opt"); optB.type = "button";
497
483
  const neverB = el("button", "chip-btn pv-never"); neverB.type = "button";
498
484
  menu.append(sum, optB, neverB);
499
- head.append(label, run, stopB, ...(toPanel ? [toPanel] : []), ...(dl ? [dl] : []), menu);
485
+ head.append(label, run, anywayB, stopB, ...(toPanel ? [toPanel] : []), ...(dl ? [dl] : []), menu);
500
486
  const note = el("div", "pv-note"); note.setAttribute("data-trust", "thread-control");
501
487
  // #1568: the banner, with its "I understand" (hidden until the next sign-in; the frame's border stays).
502
488
  const banner = el("div", "pv-banner"); banner.setAttribute("data-trust", "thread-control"); banner.hidden = true;
@@ -508,45 +494,64 @@ export function createThread(list, scroller, opts) {
508
494
  // Reading an attachment: one read per card at a time (a second click while it reads does nothing), and a read
509
495
  // whose card was stopped, replaced or left meanwhile is dropped (`reading` moved on, or the card left the page).
510
496
  let reading = 0, reads = 0;
497
+ // #2863: where "Preview anyway" runs it ("card" | "panel") and what the card says meanwhile; null: not asked.
498
+ // `tooBig`: over the 50 MB ceiling (listed, or read) — the card says so and offers only the file's download.
499
+ let large = null, largeNote = "", tooBig = !!att && typeof att.size === "number" && att.size > HTML_PREVIEW_MAX;
511
500
  const refresh = (reason) => {
512
501
  const a = P().availability();
513
502
  const going = state === "starting" || state === "running";
514
503
  const inPanel = !!panel && panel.shown() === key; // #1481: the panel shows this block; the card points there
515
- run.hidden = !a.ok || going || inPanel || !!reading;
516
- if (toPanel) toPanel.hidden = inPanel || !!reading;
504
+ if (going || reading) large = null;
505
+ if (reason == null && large) reason = largeNote;
506
+ if (reason == null && tooBig) reason = tr("chat.pvAttOver");
507
+ run.hidden = !a.ok || going || inPanel || !!reading || !!large || tooBig;
508
+ anywayB.hidden = !a.ok || !large;
509
+ if (toPanel) toPanel.hidden = inPanel || !!reading || tooBig;
517
510
  stopB.hidden = !going && !reading;
518
511
  banner.hidden = !going || P().bannerAcked();
519
512
  note.textContent = reason != null ? reason : reading ? tr("chat.pvLoading") : going ? (state === "starting" ? tr("chat.pvStarting") : "") : inPanel ? tr("chat.pvInPanel") : a.ok ? "" : a.reason;
520
513
  optB.textContent = P().optedIn() ? tr("chat.pvDisallow") : tr("chat.pvAllow");
521
514
  neverB.textContent = P().never() ? tr("chat.pvNeverUndo") : tr("chat.pvNever");
522
515
  };
523
- /** The HTML, then `then(code)`: at once for a fence or an attachment already read; otherwise read it first. */
524
- const withCode = async (then) => {
516
+ /** Over 8 MB: the card asks, and "Preview anyway" runs it where `target` ("card" | "panel") says. */
517
+ const askLarge = (target, reason) => { large = target; largeNote = reason; refresh(reason); };
518
+ /**
519
+ * The HTML, then `then(code)`: at once for a fence or an attachment already read; otherwise read it first — a large
520
+ * one only once `anyway` (its listed size asks before anything is fetched).
521
+ */
522
+ const withCode = async (then, target = "card", anyway = false) => {
525
523
  if (code != null) { then(code); return; }
526
524
  if (reading) return;
527
525
  const mine = reading = ++reads;
528
526
  refresh();
529
- const r = await loadHtmlAttachment(att);
527
+ const r = await loadHtmlAttachment(att, undefined, { anyway });
530
528
  if (reading !== mine || !ph.isConnected) return;
531
529
  reading = 0;
530
+ if (!r.ok && r.reason === "large") { askLarge(target, trf("chat.pvLarge", sizeLabel(r.size))); return; }
531
+ if (!r.ok && r.reason === "over") { tooBig = true; refresh(); return; }
532
532
  if (!r.ok) { refresh(attReason(r.reason)); return; }
533
533
  code = r.code;
534
534
  refresh();
535
535
  then(code);
536
536
  };
537
- const ui = { state: (name, reason) => { state = name === "starting" || name === "running" ? name : "idle"; refresh(reason || null); } };
537
+ const ui = { state: (name, reason) => {
538
+ state = name === "starting" || name === "running" ? name : "idle";
539
+ if (name === "large") { askLarge("card", reason || ""); return; }
540
+ large = null; refresh(reason || null);
541
+ } };
538
542
  // Nothing is read for Preview on a device that does not allow previews: the button is not even shown there.
539
543
  // A Preview click is newer than any Open in panel still reading (#1704): it claims the panel's turn too — and, its
540
544
  // own HTML read, starts only if no newer click claimed meanwhile and no preview was started since (the panel's own
541
545
  // Preview): a late read never undoes a newer choice. A close is neither, so it does not drop this click.
542
- run.onclick = () => {
546
+ const preview = (anyway) => {
543
547
  if (!P().availability().ok || reading) return; // this card already reading: that click stays the one
544
548
  const mine = panel ? (panel.claim(), panel.claims()) : 0, started = P().startCount();
545
549
  withCode((c) => {
546
550
  if ((panel && panel.claims() !== mine) || P().startCount() !== started) { refresh(); return; }
547
- P().start(key, holder, c, ui);
548
- });
551
+ if (anyway) P().start(key, holder, c, ui, { anyway: true }); else P().start(key, holder, c, ui);
552
+ }, "card", anyway);
549
553
  };
554
+ run.onclick = () => preview(false);
550
555
  stopB.onclick = () => { if (reading) { reading = 0; refresh(); } else P().stop(key, "stopped", ""); };
551
556
  // #1704: this click runs it in the panel, as Preview there would — a running preview moves (it stops here and
552
557
  // starts there), an idle one starts there. Whether it can run is the panel's to say: a device that has not allowed
@@ -554,15 +559,18 @@ export function createThread(list, scroller, opts) {
554
559
  // An attachment's HTML is read first. The click claims the panel at once, so a newer click (even one still reading)
555
560
  // wins: if the panel changed (another click, block, close) or any preview started meanwhile, this one is dropped —
556
561
  // a late read never undoes a newer click.
557
- if (toPanel) toPanel.onclick = () => {
562
+ const openInPanel = (anyway) => {
558
563
  if (reading) return; // this card already reading: that click stays the one
559
564
  if (P().running(key)) P().stop(key, "stopped", "");
560
565
  const at = panel.claim(), started = P().startCount();
561
566
  withCode((c) => {
562
567
  if (panel.version() !== at || P().startCount() !== started) { refresh(); return; }
563
- panel.open({ key, instance: x.instance, msgKey: msgKey(x), ...(att ? { att } : { n: source.n }), code: c, sender: x.sender, ts: x.ts }, { run: true });
564
- });
568
+ large = null; refresh();
569
+ panel.open({ key, instance: x.instance, msgKey: msgKey(x), ...(att ? { att } : { n: source.n }), code: c, sender: x.sender, ts: x.ts, ...(anyway ? { anyway: true } : {}) }, { run: true });
570
+ }, "panel", anyway);
565
571
  };
572
+ if (toPanel) toPanel.onclick = () => openInPanel(false);
573
+ anywayB.onclick = () => { const where = large; large = null; if (where === "panel" && toPanel) openInPanel(true); else preview(true); };
566
574
  if (dl) dl.onclick = () => opts.download(code);
567
575
  ack.onclick = () => P().ackBanner(); // every card and the panel hear it (onChange)
568
576
  optB.onclick = () => { menu.open = false; opts.setPreviewOptIn(!P().optedIn()); };
@@ -668,7 +676,6 @@ export function createThread(list, scroller, opts) {
668
676
  function render(next, renderOpts = {}) {
669
677
  const stick = pinned || nearBottom();
670
678
  msgs = next || [];
671
- armTrials();
672
679
  if (!msgs.length) {
673
680
  for (const v of nodes.values()) stopIn(v.node);
674
681
  nodes.clear();
@@ -693,11 +700,7 @@ export function createThread(list, scroller, opts) {
693
700
  }
694
701
  /** Put the messages from startIndex() on in the list (nodes reused when unchanged). Returns how many are new. */
695
702
  function draw(drawOpts) {
696
- const shown = msgs.slice(startIndex()).filter(message => {
697
- // Volatile host offer records are hidden off and while their on-only parser is loading.
698
- const hostOffer = message?.role === "status" && message.sender === "agend" && Object.hasOwn(message, "effectTrial") && message.effectTrial != null;
699
- return !hostOffer || canvasWorkerEnabled() && readCanvasTrialMessage(message) !== undefined;
700
- });
703
+ const shown = msgs.slice(startIndex());
701
704
  const keep = new Set(shown.map(msgKey));
702
705
  for (const [k, v] of nodes) if (!keep.has(k)) { stopIn(v.node); v.node.remove(); nodes.delete(k); }
703
706
  let prev = null, added = 0;
@@ -711,7 +714,7 @@ export function createThread(list, scroller, opts) {
711
714
  const stopped = have ? stopIn(have.node) : [];
712
715
  const node = msgNode(html, x, stopped);
713
716
  if (have) have.node.replaceWith(node); else added++;
714
- have = { html, node, x, trialButton: node.querySelector(".effect-trial-offer > button.effect-trial-start") }; nodes.set(key, have);
717
+ have = { html, node, x }; nodes.set(key, have);
715
718
  } else { have.x = x; timeLabel(have.node); }
716
719
  const want = prev ? prev.nextSibling : list.firstChild;
717
720
  // A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
@@ -811,13 +814,6 @@ export function createThread(list, scroller, opts) {
811
814
  else if (act === "replyButton") {
812
815
  const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
813
816
  if (m && opts.clickReplyButton) opts.clickReplyButton(m[1], Number(m[2]));
814
- } else if (act === "effectTrial") {
815
- // Only the current host-produced keyed card and a real local click can request admission. No token is read from DOM.
816
- const key = el.dataset.arg, row = msgs.find(x => msgKey(x) === key), owner = nodes.get(key), node = owner?.node, offer = readCanvasTrialMessage(row);
817
- if (PUBLIC_LINK() || e.defaultPrevented || e.isTrusted !== true || owner?.trialButton !== el || el.className !== "btn btn-sm effect-trial-start" || !node?.contains(el) || el.closest(".effect-trial-offer")?.getAttribute("data-trust") !== "thread-control"
818
- || !offer || trialNow() >= offer.expiresAt) return;
819
- if (trialStatus(row) === "ready" && opts.canTryEffect?.(row) === true) opts.startEffectTrial?.(row);
820
- else opts.fixEffectTrial?.(row, trialStatus(row));
821
817
  } else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
822
818
  else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
823
819
  else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
@@ -841,7 +837,7 @@ export function createThread(list, scroller, opts) {
841
837
  /** The thread goes (the chat left, or another instance opened): every preview in it stops first. */
842
838
  /**
843
839
  * #2053: kept for a later visit — the chat left as it always did (every preview stops: "leave"), but its drawn nodes
844
- * stay. Nothing of it runs meanwhile: no timer (day labels, polls, effect trials, filling in), no size observer; the
840
+ * stay. Nothing of it runs meanwhile: no timer (day labels, polls, filling in), no size observer; the
845
841
  * caller drops its subscriptions. Shown again: attach, then render — only what changed is drawn.
846
842
  */
847
843
  function park() {
@@ -850,7 +846,6 @@ export function createThread(list, scroller, opts) {
850
846
  if (P()) P().stopAll("leave");
851
847
  for (const node of list.querySelectorAll(".html-card[data-att], .text-card[data-att]")) { const f = cardDrop.get(node); if (f) f(); }
852
848
  if (ro) { ro.disconnect(); ro = null; }
853
- if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
854
849
  if (growFrame !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(growFrame);
855
850
  if (growTimer !== null) clearTimeout(growTimer);
856
851
  growTimer = growFrame = null;
@@ -868,12 +863,9 @@ export function createThread(list, scroller, opts) {
868
863
  if (ro) { ro.observe(list); ro.observe(sc); }
869
864
  }
870
865
  /** `own`: only this thread's previews stop (a thread let go while another chat is in view); else every one, as leaving. */
871
- const redrawFeature = () => { if (!disposed && !parked) render(msgs); };
872
- const featureOff = onCanvasWorkerChange(redrawFeature), metadataOff = onCanvasMetadataChange(redrawFeature);
873
866
  function dispose(own = false) {
874
- disposed = true; featureOff(); metadataOff();
867
+ disposed = true;
875
868
  if (!parked) watchReader(scroller, false);
876
- if (trialTimer !== null) clearTimeout(trialTimer); trialTimer = null;
877
869
  if (ro) ro.disconnect();
878
870
  if (P()) { if (own) P().stopIn(list); else P().stopAll("leave"); }
879
871
  nodes.clear();
@@ -27,7 +27,7 @@
27
27
  // @property {(ctx, item, active, ms, env) => void} [drawFurniture] zones: one furniture item (active: activeWhen holds)
28
28
  // @property {(ctx, L, env) => void} [drawForeground] zones: a pack's foreground over the whole 1280×720 scene, above
29
29
  // the people, furniture and items in the air, below the crowd counts, name tags and bubbles
30
- // @property {(ctx, x, y, look, env) => void} [drawTag] zones: a name tag under feet at (x, y)
30
+ // @property {(ctx, x, y, look, env, compact?) => void} [drawTag] zones: a name tag under feet at (x, y) (compact: #2860)
31
31
  // @property {(ctx, area, n, env) => void} [drawCrowd] zones: an area's "+n" (who found no spot in it)
32
32
  // @property {(ctx, desk, look, ms, env) => void} drawCharacter the occupant in its pose at time `ms` (0: still); in
33
33
  // zones `desk` is the character's box at its current position (its feet at desk.seat); walking, `look.heading` is
@@ -293,21 +293,44 @@ function drawBubble(ctx, x, y, text, env, lift = 0) {
293
293
  /** A zones name tag's widest text (logical units): longer names are cut with an ellipsis. */
294
294
  export const TAG_TEXT_W = 84;
295
295
  const TAG_FONT = "600 10px system-ui, sans-serif";
296
+ /**
297
+ * #2860: the compact tag a crowded area keeps on (it used to show tags on hover only, #2513): a smaller font, a smaller
298
+ * plate, a shorter cut, the same level badge.
299
+ */
300
+ const COMPACT = { font: "500 9px system-ui, sans-serif", textW: 48, pad: 6, top: 2, h: 11 };
301
+ const NORMAL = { font: TAG_FONT, textW: TAG_TEXT_W, pad: 10, top: 3, h: 14 };
296
302
  /** The box a name tag under feet at (x, y) takes (what drawTag draws; bubbles keep clear of it, #2513). */
297
- export function tagBox(ctx, x, y, label) {
303
+ export function tagBox(ctx, x, y, label, compact = false) {
304
+ const k = compact ? COMPACT : NORMAL;
305
+ ctx.save();
306
+ ctx.font = k.font;
307
+ const t = fitText(ctx, label, k.textW), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + k.pad);
308
+ ctx.restore();
309
+ return { x: Math.round(x - w / 2), y: y + k.top, w, h: k.h, text: t };
310
+ }
311
+ /** #2860: what a tag covers with its level badge: on a full tag's top-left corner (half outside); left of a compact one. */
312
+ export function tagFootprint(ctx, box, badge, compact = false) {
313
+ if (!badge) return box;
314
+ if (compact) { const w = badgeWidth(ctx, badge) + 1; return { x: box.x - w, y: box.y, w: box.w + w, h: box.h }; }
315
+ return { x: box.x - 10, y: box.y - 7, w: box.w + 10, h: box.h + 7 };
316
+ }
317
+ function badgeWidth(ctx, badge) {
318
+ if (badge.mark) return 8;
298
319
  ctx.save();
299
- ctx.font = TAG_FONT;
300
- const t = fitText(ctx, label, TAG_TEXT_W), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + 10);
320
+ ctx.font = BADGE_FONT;
321
+ const w = Math.max(12, Math.ceil((ctx.measureText ? ctx.measureText(badge.text).width : badge.text.length * 5) + 6));
301
322
  ctx.restore();
302
- return { x: Math.round(x - w / 2), y: y + 3, w, h: 14, text: t };
323
+ return w;
303
324
  }
304
325
  /**
305
326
  * #2661: a level badge on a name tag's top-left corner (x, y: the tag's corner), overlapping its edge like an app's
306
327
  * badge — it takes none of the tag's width. `badge`: { text, tier } (fleet-office-levels.js badgeOf), or { mark } for a
307
328
  * pinned title that hides the number: a small diamond.
308
329
  */
309
- export function drawBadge(ctx, x, y, badge, pal) {
330
+ export function drawBadge(ctx, x, y, badge, pal, inline = false) {
310
331
  if (!badge) return;
332
+ // #2860 inline (a compact tag): (x, y) is the tag's left edge and middle; the badge sits just left of it, whole.
333
+ if (inline) { const w = badgeWidth(ctx, badge); x = x - 1 - w + (badge.mark ? w / 2 : w / 2 - 3); y = y + (badge.mark ? 0 : 1); }
311
334
  const c = tierColours(badge.tier, pal);
312
335
  ctx.save();
313
336
  ctx.globalAlpha = 1;
@@ -331,18 +354,18 @@ export function drawBadge(ctx, x, y, badge, pal) {
331
354
  }
332
355
  const BADGE_FONT = "700 8px system-ui, sans-serif";
333
356
 
334
- /** Zones: a name tag under someone's feet at (x, y). */
335
- function drawTag(ctx, x, y, look, env) {
357
+ /** Zones: a name tag under someone's feet at (x, y); `compact` (#2860): the smaller one a crowded area keeps on. */
358
+ function drawTag(ctx, x, y, look, env, compact = false) {
336
359
  const { pal } = env;
360
+ const b = tagBox(ctx, x, y, look.label, compact);
337
361
  ctx.save();
338
362
  if (look.dim) ctx.globalAlpha = 0.35;
339
- ctx.font = TAG_FONT;
340
- const t = fitText(ctx, look.label, TAG_TEXT_W), w = Math.ceil((ctx.measureText ? ctx.measureText(t).width : t.length * 6) + 10);
341
- ctx.fillStyle = pal.surface; ctx.fillRect(Math.round(x - w / 2), y + 3, w, 14);
342
- ctx.strokeStyle = look.match ? pal.focus : pal.border; ctx.lineWidth = look.match ? 2 : 1; ctx.strokeRect(Math.round(x - w / 2) + 0.5, y + 3.5, w - 1, 13);
343
- ctx.fillStyle = pal.text; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(t, x, y + 10.5);
363
+ ctx.font = compact ? COMPACT.font : TAG_FONT;
364
+ ctx.fillStyle = pal.surface; ctx.fillRect(b.x, b.y, b.w, b.h);
365
+ ctx.strokeStyle = look.match ? pal.focus : pal.border; ctx.lineWidth = look.match ? 2 : 1; ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w - 1, b.h - 1);
366
+ ctx.fillStyle = pal.text; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(b.text, x, b.y + b.h / 2 + 0.5);
344
367
  ctx.restore();
345
- if (look.badge) drawBadge(ctx, Math.round(x - w / 2), y + 3, look.badge, pal);
368
+ if (look.badge) compact ? drawBadge(ctx, b.x, b.y + b.h / 2, look.badge, pal, true) : drawBadge(ctx, b.x, b.y, look.badge, pal);
346
369
  }
347
370
 
348
371
  /**