@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
@@ -27,6 +27,20 @@
27
27
 
28
28
  const nativeFetch = window.fetch.bind(window);
29
29
  let sessionPending = null;
30
+ // Observation only: no server authority, owner/CSRF data or way to restore this page's episode.
31
+ let ended = null;
32
+ const endListeners = new Set();
33
+ function endSession(reason) {
34
+ if (ended) return;
35
+ ended = Object.freeze({ reason });
36
+ const listeners = [...endListeners]; endListeners.clear();
37
+ for (const fn of listeners) { try { fn(ended); } catch { /* one observer cannot delay another's cleanup */ } }
38
+ }
39
+ function onSessionEnd(fn) {
40
+ if (typeof fn !== "function") return () => {};
41
+ if (ended) { try { fn(ended); } catch { /* late observers fail closed */ } return () => {}; }
42
+ endListeners.add(fn); return () => endListeners.delete(fn);
43
+ }
30
44
 
31
45
  /**
32
46
  * This page's sign-in, read once from GET /auth/session: { csrf, handle } or null (no session, or the read failed —
@@ -36,8 +50,8 @@
36
50
  function session() {
37
51
  if (!sessionPending) {
38
52
  sessionPending = nativeFetch("/auth/session", { credentials: "same-origin", cache: "no-store" })
39
- .then((r) => (r.ok ? r.json() : null))
40
- .then((j) => (j && typeof j.csrf === "string" ? { csrf: j.csrf, handle: typeof j.handle === "string" ? j.handle : null } : null))
53
+ .then((r) => { if (r.status === 401) endSession("unauthorized"); return r.ok ? r.json() : null; })
54
+ .then((j) => (j && typeof j.csrf === "string" ? { csrf: j.csrf, handle: typeof j.handle === "string" ? j.handle : null, canWrite: j.tier === "admin", localAdmin: j.tier === "admin" && j.surface === "local" } : null))
41
55
  .catch(() => null)
42
56
  .then((value) => { if (!value) sessionPending = null; return value; });
43
57
  }
@@ -51,7 +65,9 @@
51
65
  bannerShown = true;
52
66
  const bar = document.createElement("div");
53
67
  bar.setAttribute("role", "alert");
54
- bar.style.cssText = "position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:10px 16px;background:#f85149;color:#fff;font:14px system-ui,sans-serif;text-align:center";
68
+ bar.setAttribute("data-trust", "session-ended");
69
+ bar.className = "agend-session-ended";
70
+ bar.style.cssText = "position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:10px 16px;background:#f85149;color:#fff;font:14px system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\",emoji;text-align:center";
55
71
  const zh = /^zh/i.test(navigator.language || "");
56
72
  bar.append(document.createTextNode(zh ? "登入已結束。" : "Your session has ended. "));
57
73
  const link = document.createElement("a");
@@ -77,15 +93,23 @@
77
93
  }
78
94
  }
79
95
  const response = await nativeFetch(input, init);
80
- if (sameOrigin && response.status === 401 && !target.pathname.startsWith("/auth/")) sessionEndedBanner();
96
+ if (sameOrigin && response.status === 401 && !target.pathname.startsWith("/auth/")) { endSession("unauthorized"); sessionEndedBanner(); }
81
97
  return response;
82
98
  };
83
99
 
84
100
  window.AgendAuth = {
85
101
  csrf,
102
+ /** Host runtimes subscribe before admission. Late subscribers observe the same closed episode immediately. */
103
+ onSessionEnd,
104
+ /** Display hint from the existing session read. Every write still requires server authorization. */
105
+ canWrite() { return session().then(s => !ended && s?.canWrite === true); },
106
+ /** Display hint from the same server session response; Start still reauthorizes. No CSRF is exposed here. */
107
+ trialSession() { return session().then(s => !ended && s?.handle && /^[a-f0-9]{16}$/.test(s.handle) ? Object.freeze({ epoch: s.handle, localAdmin: s.localAdmin === true }) : null); },
108
+ sessionEnded() { return ended !== null; },
86
109
  /** #1568: the session handle of this page's sign-in, or null when there is none. */
87
110
  sessionHandle() { return session().then((s) => (s && s.handle ? s.handle : null)); },
88
111
  async signOut() {
112
+ endSession("sign_out");
89
113
  const value = await csrf();
90
114
  const r = await nativeFetch("/auth/logout", { method: "POST", credentials: "same-origin", headers: value ? { "X-Agend-CSRF": value } : {} });
91
115
  // A sign-out the server could not make durable is not quietly reported as done: say so before leaving.
@@ -0,0 +1,248 @@
1
+ // #1629 r4-4 (option A): widgets — an agent's in its own chat header and Details page, a person's in their sidebar —
2
+ // drawn by the page's own components from a closed declarative spec and its values (src/web-widget.ts). Nothing from a
3
+ // mod becomes markup, script, a handler, a URL or a style: text is text nodes, a number fills a <progress> or an SVG
4
+ // polyline's points, and tones are fixed class tokens. Colours are the theme's tokens (and a person's own CSS). The box
5
+ // always carries the page's own ✦ chip, so a widget never reads as AgEnD's chrome, and nothing in it can be clicked.
6
+ //
7
+ // What the host sends is checked again here with the same rules; a widget that fails is not drawn, and a value that
8
+ // fails shows "—". A `bind` is a value the page already has (the instance's own state, context and cost; the fleet's
9
+ // counts in the person's sidebar only) — nothing new is fetched for it.
10
+ import { html } from "./app-html.js";
11
+ import { register, t } from "./app-i18n.js";
12
+ import { statusFacet } from "./instance-nav.js";
13
+ import { themesShown, themesStore } from "./app-themes.js";
14
+ import { onPrefsChanged, prefInForce, savePref } from "./app-prefs.js";
15
+ import { createStore } from "./app-store.js";
16
+ import { safeMode } from "./owner-css.js";
17
+
18
+ register("widget", {
19
+ en: {
20
+ label: "Widget: {0}",
21
+ chip: "A widget — drawn by AgEnD from its owner's data",
22
+ byHuman: "placed by a person",
23
+ none: "—",
24
+ },
25
+ "zh-TW": {
26
+ label: "小工具:{0}",
27
+ chip: "小工具——由 AgEnD 依擁有者的資料繪製",
28
+ byHuman: "由人放置",
29
+ none: "—",
30
+ },
31
+ });
32
+
33
+ export const WIDGET_ITEM_TYPES = ["stat", "progress", "text", "list", "badge", "spark"];
34
+ export const WIDGET_TONES = ["ok", "warn", "bad", "info", "muted"];
35
+ const INSTANCE_BINDS = ["state", "context_pct", "cost_usd"];
36
+ const FLEET_BINDS = ["fleet.idle", "fleet.working", "fleet.needs_you", "fleet.stopped"];
37
+ export const SLOT_RULES = {
38
+ chat_header: { maxItems: 4, types: ["stat", "progress", "text", "badge"], binds: INSTANCE_BINDS },
39
+ details: { maxItems: 8, types: WIDGET_ITEM_TYPES, binds: INSTANCE_BINDS },
40
+ sidebar: { maxItems: 8, types: WIDGET_ITEM_TYPES, binds: FLEET_BINDS },
41
+ };
42
+ const BINDS_FOR = {
43
+ stat: ["context_pct", "cost_usd", ...FLEET_BINDS], progress: ["context_pct"], text: ["state"], badge: ["state"], list: [], spark: [],
44
+ };
45
+ const ITEM_KEYS = {
46
+ stat: ["type", "label", "key", "bind", "unit"], progress: ["type", "label", "key", "bind", "max"], text: ["type", "label", "key", "bind"],
47
+ list: ["type", "label", "key"], badge: ["type", "label", "key", "bind", "tone", "tones"], spark: ["type", "label", "key"],
48
+ };
49
+ const LABEL_REQUIRED = ["stat", "progress", "spark"];
50
+ const KEY = /^[a-z][a-z0-9_]{0,31}$/;
51
+ const NUMBER_MAX = 1e15;
52
+ const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
53
+ const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
54
+
55
+ // One line of text, as the host's canonicalText: NFC, no control, format or separator character but the zero-width
56
+ // joiner, at least one character that draws something, 1..max graphemes.
57
+ const FORBIDDEN = /(?!‍)[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
58
+ const VISIBLE = /[^\p{M}\p{C}\p{Z}ᅟᅠㅤᅠ⠀]/u;
59
+ let graphemes = null;
60
+ export function lineOk(s, max) {
61
+ if (typeof s !== "string" || s.normalize("NFC") !== s || FORBIDDEN.test(s) || s.trim() !== s || !VISIBLE.test(s)) return false;
62
+ try { graphemes ??= new Intl.Segmenter(undefined, { granularity: "grapheme" }); } catch { return false; }
63
+ const n = [...graphemes.segment(s)].length;
64
+ return n >= 1 && n <= max;
65
+ }
66
+ const isNumber = (v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) <= NUMBER_MAX;
67
+
68
+ /** Whether `v` is a value an item of `type` can show (web-widget.ts valueFits). */
69
+ export function valueFits(type, v) {
70
+ switch (type) {
71
+ case "stat": return isNumber(v) || lineOk(v, 24);
72
+ case "progress": return isNumber(v);
73
+ case "text": return lineOk(v, 120);
74
+ case "badge": return lineOk(v, 24);
75
+ case "list": return Array.isArray(v) && v.length >= 1 && v.length <= 6 && v.every((s) => lineOk(s, 60));
76
+ case "spark": return Array.isArray(v) && v.length >= 2 && v.length <= 48 && v.every(isNumber);
77
+ default: return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * A widget `{ spec, data, by? }` for `slot`, checked again: `{ widget }`, or `{ problem }` (where, and what) when its
83
+ * spec fails — then the whole widget is not drawn. A data value that fails is left out (shown as "—").
84
+ */
85
+ export function explainWidget(slot, w) {
86
+ const no = (problem) => ({ problem });
87
+ const rules = own(SLOT_RULES, slot) ? SLOT_RULES[slot] : null;
88
+ if (!rules) return no("slot");
89
+ if (!isObj(w) || !isObj(w.spec)) return no("spec: an object { v: 1, title, items }");
90
+ const s = w.spec;
91
+ const extra = Object.keys(s).find((k) => !["v", "title", "items"].includes(k));
92
+ if (extra) return no(`spec.${extra}: unknown key`);
93
+ if (s.v !== 1) return no("spec.v: must be 1");
94
+ if (!lineOk(s.title, 24)) return no("spec.title: 1..24 characters on one line");
95
+ if (!Array.isArray(s.items) || s.items.length < 1 || s.items.length > rules.maxItems) return no(`spec.items: 1..${rules.maxItems} items`);
96
+ const items = [];
97
+ for (const [i, it] of s.items.entries()) {
98
+ const at = `spec.items.${i}`;
99
+ if (!isObj(it) || !rules.types.includes(it.type)) return no(`${at}.type: ${rules.types.join(", ")}`);
100
+ const unknown = Object.keys(it).find((k) => !ITEM_KEYS[it.type].includes(k));
101
+ if (unknown) return no(`${at}.${unknown}: unknown key`);
102
+ if ((it.label !== undefined || LABEL_REQUIRED.includes(it.type)) && !lineOk(it.label, 24)) return no(`${at}.label: 1..24 characters`);
103
+ if ((it.key === undefined) === (it.bind === undefined)) return no(`${at}: exactly one of key or bind`);
104
+ if (it.key !== undefined && !(typeof it.key === "string" && KEY.test(it.key))) return no(`${at}.key: a-z, 0-9 and _`);
105
+ if (it.bind !== undefined && !(BINDS_FOR[it.type].includes(it.bind) && rules.binds.includes(it.bind))) return no(`${at}.bind: ${BINDS_FOR[it.type].filter((b) => rules.binds.includes(b)).join(", ") || "none here"}`);
106
+ if (it.unit !== undefined && !lineOk(it.unit, 8)) return no(`${at}.unit: 1..8 characters`);
107
+ if (it.max !== undefined && !(isNumber(it.max) && it.max > 0)) return no(`${at}.max: a number > 0`);
108
+ if (it.tone !== undefined && !WIDGET_TONES.includes(it.tone)) return no(`${at}.tone: ${WIDGET_TONES.join(", ")}`);
109
+ let tones;
110
+ if (it.tones !== undefined) {
111
+ if (!isObj(it.tones)) return no(`${at}.tones: { value: tone }`);
112
+ tones = new Map();
113
+ for (const [k, v] of Object.entries(it.tones)) { if (!lineOk(k, 24) || !WIDGET_TONES.includes(v)) return no(`${at}.tones: { value: ${WIDGET_TONES.join(" | ")} }`); tones.set(k, v); }
114
+ if (tones.size < 1 || tones.size > 8) return no(`${at}.tones: 1..8 entries`);
115
+ }
116
+ items.push({ type: it.type, label: it.label, key: it.key, bind: it.bind, unit: it.unit, max: it.max, tone: it.tone, tones });
117
+ }
118
+ const data = new Map();
119
+ if (isObj(w.data)) items.forEach((it, i) => { if (it.key && own(w.data, it.key) && valueFits(it.type, w.data[it.key])) data.set(`${i}`, w.data[it.key]); });
120
+ return { widget: { title: s.title, items, data, by: w.by === "human" ? "human" : "agent" } };
121
+ }
122
+ /** explainWidget's widget, or null. */
123
+ export function checkWidget(slot, w) { return explainWidget(slot, w).widget || null; }
124
+
125
+ /**
126
+ * What the host would refuse in a person's `{ spec, data }` for `slot` (its whole check, web-widget.ts and web-prefs.ts),
127
+ * or null — so the editor can say why before anything is saved.
128
+ */
129
+ export function widgetProblem(slot, v) {
130
+ if (!isObj(v) || Object.keys(v).length !== 2 || !own(v, "spec") || !own(v, "data")) return "{ \"spec\": …, \"data\": … }";
131
+ const r = explainWidget(slot, v);
132
+ if (r.problem) return r.problem;
133
+ if (!isObj(v.data)) return "data: an object { key: value }";
134
+ for (const [k, val] of Object.entries(v.data)) {
135
+ const users = r.widget.items.filter((it) => it.key === k);
136
+ if (!users.length) return `data.${k}: no item shows this key`;
137
+ const bad = users.find((it) => !valueFits(it.type, val));
138
+ if (bad) return `data.${k}: not a value a ${bad.type} shows`;
139
+ }
140
+ const bytes = (x) => new TextEncoder().encode(JSON.stringify(x)).length;
141
+ if (bytes(v.spec) > 2048) return "spec: at most 2048 bytes";
142
+ if (bytes(v.data) > 4096) return "data: at most 4096 bytes";
143
+ return null;
144
+ }
145
+
146
+ /** The values the page has for one instance: its state word (a sidebar facet), context % and cost. */
147
+ export function instanceBinds(inst, exec, awaiting) {
148
+ if (!inst) return {};
149
+ return {
150
+ state: statusFacet(inst, exec, awaiting),
151
+ context_pct: isNumber(inst.context_pct) ? Math.round(Math.max(0, Math.min(100, inst.context_pct))) : undefined,
152
+ cost_usd: isNumber(inst.cost) ? inst.cost : undefined,
153
+ };
154
+ }
155
+ /** The fleet's counts, for the person's sidebar widget only: how many instances are idle, working, need you, stopped. */
156
+ export function fleetBinds(instances, exec, awaiting) {
157
+ const n = { "fleet.idle": 0, "fleet.working": 0, "fleet.needs_you": 0, "fleet.stopped": 0 };
158
+ for (const it of Array.isArray(instances) ? instances : []) {
159
+ const f = statusFacet(it, exec && exec[it.name], awaiting && own(awaiting, it.name) ? awaiting[it.name] : null);
160
+ n[f === "needs" ? "fleet.needs_you" : f === "working" ? "fleet.working" : f === "idle" ? "fleet.idle" : "fleet.stopped"] += 1;
161
+ }
162
+ return n;
163
+ }
164
+
165
+ let numbers = null;
166
+ function fmt(v) {
167
+ try { numbers ??= new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }); return numbers.format(v); } catch { return String(v); }
168
+ }
169
+ /** An item's value: its data, or its bind; undefined when there is none (shown as "—"). */
170
+ function valueOf(item, i, w, binds) {
171
+ if (item.key) return w.data.get(`${i}`);
172
+ const v = binds && own(binds, item.bind) ? binds[item.bind] : undefined;
173
+ return item.bind === "state" ? (lineOk(v, 24) ? v : undefined) : isNumber(v) ? v : undefined;
174
+ }
175
+ function shown(item, v) {
176
+ if (v === undefined) return t("widget.none");
177
+ if (item.bind === "cost_usd") return `$${v.toFixed(2)}`;
178
+ if (item.bind === "context_pct") return `${v}%`;
179
+ return typeof v === "number" ? fmt(v) : v;
180
+ }
181
+ /** The sparkline's points in a 100×24 box (numbers only; nothing else reaches the SVG). */
182
+ export function sparkPoints(values) {
183
+ const lo = Math.min(...values), hi = Math.max(...values), span = hi - lo || 1;
184
+ return values.map((v, i) => `${((i / (values.length - 1)) * 100).toFixed(2)},${(22 - ((v - lo) / span) * 20).toFixed(2)}`).join(" ");
185
+ }
186
+
187
+ function Item({ item, v }) {
188
+ const label = item.label ? html`<span class="aw-label">${item.label}</span>` : null;
189
+ switch (item.type) {
190
+ case "stat":
191
+ return html`<div class="aw-item aw-stat">${label}<span class="aw-value">${shown(item, v)}${v !== undefined && item.unit ? html`<span class="aw-unit">${item.unit}</span>` : null}</span></div>`;
192
+ case "progress": {
193
+ const max = item.max ?? 100;
194
+ const pct = v === undefined ? null : Math.round((Math.max(0, Math.min(max, v)) / max) * 100);
195
+ // No value: no bar at all (a <progress> without one is the browser's animated "busy" bar), only "—".
196
+ return html`<div class="aw-item aw-progress">${label}${pct === null ? null : html`<progress class="aw-bar" max=${max} value=${Math.max(0, Math.min(max, v))} aria-label=${item.label}></progress>`}<span class="aw-value">${pct === null ? t("widget.none") : `${pct}%`}</span></div>`;
197
+ }
198
+ case "text":
199
+ return html`<div class="aw-item aw-text">${label}<span class="aw-value">${shown(item, v)}</span></div>`;
200
+ case "badge": {
201
+ const tone = v !== undefined && item.tones && item.tones.has(v) ? item.tones.get(v) : item.tone || "muted";
202
+ return html`<div class="aw-item aw-badge-item">${label}<span class=${`aw-badge aw-tone-${WIDGET_TONES.includes(tone) ? tone : "muted"}`}>${shown(item, v)}</span></div>`;
203
+ }
204
+ case "list":
205
+ return html`<div class="aw-item aw-list">${label}${v === undefined ? html`<span class="aw-value">${t("widget.none")}</span>` : html`<ul>${v.map((line, i) => html`<li key=${i}>${line}</li>`)}</ul>`}</div>`;
206
+ case "spark":
207
+ return html`<div class="aw-item aw-spark-item">${label}${v === undefined ? html`<span class="aw-value">${t("widget.none")}</span>`
208
+ : html`<svg class="aw-spark" viewBox="0 0 100 24" preserveAspectRatio="none" aria-hidden="true"><polyline points=${sparkPoints(v)} /></svg><span class="aw-value">${fmt(v[v.length - 1])}</span>`}</div>`;
209
+ default: return null;
210
+ }
211
+ }
212
+
213
+ /** One widget, checked (checkWidget's result), in `slot`. `binds`: instanceBinds or fleetBinds. */
214
+ export function AgentWidget({ slot, widget, binds, tools = null }) {
215
+ if (!widget) return null;
216
+ return html`<section data-trust="widget" class=${`aw aw-${slot}`} aria-label=${t("widget.label", widget.title)} data-widget-slot=${slot}>
217
+ <div class="aw-head"><span class="aw-chip" title=${t("widget.chip")}>✦ ${widget.title}</span>
218
+ ${widget.by === "human" ? html`<span class="aw-by">${t("widget.byHuman")}</span>` : null}${tools}</div>
219
+ <div class="aw-items">${widget.items.map((item, i) => html`<${Item} key=${i} item=${item} v=${valueOf(item, i, widget, binds)} />`)}</div>
220
+ </section>`;
221
+ }
222
+
223
+ /** An agent's widget in `slot` ("chat_header" | "details"), checked; null when none, or agents' looks are not shown here. */
224
+ export function widgetFor(name, slot) {
225
+ const s = themesStore.get();
226
+ if (!s.shown || !themesShown() || !own(s.widgets, name)) return null;
227
+ const slots = s.widgets[name];
228
+ return isObj(slots) && own(slots, slot) ? checkWidget(slot, slots[slot]) : null;
229
+ }
230
+
231
+ const SIDEBAR_KEY = "widget.sidebar";
232
+ const publicLink = () => typeof document !== "undefined" && !!document.body && document.body.dataset.publicLink === "1";
233
+ /** The person's own sidebar widget (a synced preference), raw; null when none. Never on the public link or in safe mode. */
234
+ export function sidebarWidgetRaw() {
235
+ if (publicLink() || safeMode()) return null;
236
+ const f = prefInForce(SIDEBAR_KEY);
237
+ let v = f ? f.value : undefined;
238
+ if (!f) { try { v = JSON.parse(localStorage.getItem("agend_widget_sidebar") || "null"); } catch { v = null; } }
239
+ return isObj(v) ? v : null;
240
+ }
241
+ /** The person's sidebar widget, checked; null when none. */
242
+ export function sidebarWidget() { const v = sidebarWidgetRaw(); return v ? checkWidget("sidebar", v) : null; }
243
+ /** Bumped when the person's sidebar widget changes (here, or on another device), so the sidebar redraws. */
244
+ export const sidebarWidgetStore = createStore({ n: 0 });
245
+ const bump = () => sidebarWidgetStore.set((s) => ({ n: s.n + 1 }));
246
+ onPrefsChanged((changed) => { if (changed.has(SIDEBAR_KEY)) bump(); });
247
+ /** Set (or with null, remove) the person's sidebar widget on all their devices. */
248
+ export function setSidebarWidget(value) { savePref(SIDEBAR_KEY, value); bump(); }
@@ -60,7 +60,7 @@ export function t(key, ...values) {
60
60
 
61
61
  register("app", {
62
62
  en: {
63
- brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet overview", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance",
63
+ brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance", newAgent: "New agent",
64
64
  collapse: "Hide the sidebar", expand: "Show the sidebar", openMenu: "Open the sidebar", closeMenu: "Close the sidebar",
65
65
  resizeSidebar: "Sidebar width", resizeSidebarHint: "Drag to resize · double-click to reset",
66
66
  names: "Names", namesNickname: "Nickname first", namesInstance: "Real name first",
@@ -68,11 +68,13 @@ register("app", {
68
68
  previews: "Allow HTML previews", tour: "Tour", tourTitle: "Show the web chat tour again",
69
69
  needsYou: "needs you", approxNote: "Read from the terminal, so approximate", chat: "Chat", details: "Details", log: "Log", instanceViews: "Views of {0}",
70
70
  textSize: "Text size: {0}", textSize_s: "S", textSize_m: "M", textSize_l: "L", textSize_fit: "Fit", usage: "Usage", usageHide: "Hide usage",
71
+ ownerCssKeep: "Keep this custom CSS?", ownerCssKeepLeft: "Reverts in {0} s", ownerCssKeepYes: "Keep", ownerCssKeepNo: "Revert",
71
72
  statusRunning: "running", statusStopped: "stopped", statusPaused: "paused", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
72
73
  connReconnecting: "Can't reach AgEnD — it may be restarting. Reconnecting…", connReconnectingIn: "Can't reach AgEnD — it may be restarting. Trying again in {0} s.",
73
74
  hydrating: "Loading what is open now…", hydrateFailed: "Could not load what is open now (prompts, ticks).",
74
75
  skip: "Skip to the conversation", loadFailed: "This could not be loaded.", retry: "Try again", loading: "Loading…",
75
76
  noInstances: "No instances yet", noInstancesHint: "Create one to start chatting.",
77
+ guestChip: "Guest", guestAs: "Guest · {0}", guestAsOn: "Guest · {0} ({1})", guestHint: "You see only the agents you can already use in chat.",
76
78
  session: "Session", signIn: "Sign in", signedIn: "Signed in", signedOut: "Not signed in", thisDevice: "This device", devicePrefs: "Theme and language",
77
79
  lastActive: "active {0}", justNow: "just now", minAgo: "{0} min ago", hrAgo: "{0} h ago", signOut: "Sign out",
78
80
  signOutAll: "Sign out everywhere", devices: "Signed-in devices", expires: "Ends by {0} at the latest, or after {1} unused",
@@ -91,7 +93,7 @@ register("app", {
91
93
  needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed",
92
94
  },
93
95
  "zh-TW": {
94
- brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance",
96
+ brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
95
97
  collapse: "隱藏側邊欄", expand: "顯示側邊欄", openMenu: "打開側邊欄", closeMenu: "關閉側邊欄",
96
98
  resizeSidebar: "側邊欄寬度", resizeSidebarHint: "拖曳調整寬度 · 按兩下恢復預設",
97
99
  names: "名稱", namesNickname: "暱稱優先", namesInstance: "實際名稱優先",
@@ -99,11 +101,13 @@ register("app", {
99
101
  previews: "允許預覽 HTML", tour: "導覽", tourTitle: "再看一次網頁聊天導覽",
100
102
  needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", log: "紀錄", instanceViews: "{0} 的檢視",
101
103
  textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "填滿", usage: "用量", usageHide: "隱藏用量",
104
+ ownerCssKeep: "保留這份自訂 CSS?", ownerCssKeepLeft: "{0} 秒後還原", ownerCssKeepYes: "保留", ownerCssKeepNo: "還原",
102
105
  statusRunning: "執行中", statusStopped: "已停止", statusPaused: "已暫停", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
103
106
  connReconnecting: "連不上 AgEnD——可能正在重新啟動。正在重新連線…", connReconnectingIn: "連不上 AgEnD——可能正在重新啟動。{0} 秒後再試。",
104
107
  hydrating: "正在載入目前的狀態…", hydrateFailed: "無法載入目前的狀態(提示、送達標記)。",
105
108
  skip: "跳到對話", loadFailed: "無法載入。", retry: "再試一次", loading: "載入中…",
106
109
  noInstances: "還沒有 instance", noInstancesHint: "建立一個就能開始聊天。",
110
+ guestChip: "訪客", guestAs: "訪客 · {0}", guestAsOn: "訪客 · {0}({1})", guestHint: "你只會看到在聊天中本來就能用的 agent。",
107
111
  session: "登入狀態", signIn: "登入", signedIn: "已登入", signedOut: "尚未登入", thisDevice: "這台裝置", devicePrefs: "主題與語言",
108
112
  lastActive: "{0}活動", justNow: "剛剛", minAgo: "{0} 分鐘前", hrAgo: "{0} 小時前", signOut: "登出",
109
113
  signOutAll: "全部登出", devices: "已登入的裝置", expires: "最晚 {0} 結束;閒置 {1} 也會結束",
@@ -18,7 +18,7 @@
18
18
  // - The status frame's prefs_rev: another device changed something; the host's values are read again and applied. A
19
19
  // revision counts as seen only once its read succeeded (a failed one is tried again, at most every few seconds).
20
20
  import { deviceLanguage, lang, setLang } from "./app-i18n.js";
21
- import { appStore } from "./app-store.js";
21
+ import { appStore, guestPage } from "./app-store.js";
22
22
 
23
23
  const SYNCED_FLAG = "agend_prefs_synced";
24
24
  const RETRY_MS = 5000;
@@ -31,15 +31,35 @@ export const SYNCED = {
31
31
  "nav.collapsed": { ls: "agend_instance_collapsed", ...json },
32
32
  "nav.filter": { ls: "agend_instance_filter", ...json },
33
33
  "nav.favorites": { ls: "agend_favorites", ...json }, // #1664
34
+ "nav.sort": { ls: "agend_nav_sort", ...json }, // #2179
34
35
  "preview.optIn": { ls: "agend_html_preview", toLocal: (v) => (v ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
35
36
  "names.primary": { ls: "agend_names_primary", toLocal: (v) => (v === "instance" ? v : null), fromLocal: (s) => (s === "instance" ? s : undefined) }, // #1722
37
+ // #1629 r2 (d): "Show agents' themes and effects" — on unless turned off (only "off" is cached).
38
+ "fx.show": { ls: "agend_fx_show", toLocal: (v) => (v === false ? "off" : null), fromLocal: (s) => (s === "off" ? false : undefined) },
39
+ // #1629 r4.1 Q3 (a): the identity card's size — every card's default (M unless chosen), and the cards set apart.
40
+ "card.size": { ls: "agend_card_size", toLocal: (v) => (v === "s" || v === "l" ? v : null), fromLocal: (s) => (s === "s" || s === "l" ? s : undefined) },
41
+ "card.sizeByInstance": { ls: "agend_card_sizes", ...json },
42
+ // #1629 r4-4: the person's own sidebar widget ({ spec, data }), drawn by the page's own components.
43
+ "widget.sidebar": { ls: "agend_widget_sidebar", ...json },
44
+ // #1958: where identity colours show by default (#2056: name, bar and ring unless chosen), and the host's switch to turn them off.
45
+ "color.show": { ls: "agend_color_show", ...json },
46
+ "color.off": { ls: "agend_color_off", toLocal: (v) => (v === true ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
36
47
  };
48
+ // #1629: fixed personal scope. A separate session-bound owner-effect store synchronizes these, never this host cache.
49
+ export const OWNER_EFFECT_PREF_KEYS = Object.freeze(["fx.sidebar", "fx.header", "fx.page", "fx.box", "fx.cursor"]);
50
+ for (const spec of Object.values(SYNCED)) spec.scope = "host";
51
+ for (const key of OWNER_EFFECT_PREF_KEYS) SYNCED[key] = Object.freeze({ scope: "person", ls: null });
52
+ for (const spec of Object.values(SYNCED)) Object.freeze(spec);
53
+ Object.freeze(SYNCED);
54
+ const HOST_KEYS = Object.keys(SYNCED).filter(key => SYNCED[key].scope === "host");
55
+ const hostKey = key => own(SYNCED, key) && SYNCED[key].scope === "host";
56
+ const hostValues = values => Object.fromEntries(HOST_KEYS.filter(key => own(values, key)).map(key => [key, values[key]]));
37
57
  const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
38
58
  const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
39
59
 
40
60
  function readLocal(key) { try { return localStorage.getItem(key); } catch { return null; } }
41
61
  function writeLocal(key, s) { try { if (s == null) localStorage.removeItem(key); else localStorage.setItem(key, s); } catch { /* this page only */ } }
42
- function cache(key, value) { const k = SYNCED[key]; writeLocal(k.ls, value == null ? null : k.toLocal(value)); }
62
+ function cache(key, value) { if (!hostKey(key)) return; const k = SYNCED[key]; writeLocal(k.ls, value == null ? null : k.toLocal(value)); }
43
63
  const inStep = () => readLocal(SYNCED_FLAG) === "1";
44
64
 
45
65
  function pageHost() {
@@ -51,7 +71,7 @@ function pageHost() {
51
71
 
52
72
  const initial = typeof document !== "undefined" ? pageHost() : null;
53
73
  let rev = initial ? initial.rev : null;
54
- let host = initial ? initial.values : null;
74
+ let host = initial ? hostValues(initial.values) : null;
55
75
  const pending = new Map(); // key → value chosen here, not sent yet
56
76
  const mine = new Map(); // key → value chosen here whose write is not over (pending or out)
57
77
  const inflight = new Map(); // key → writes sent and not answered yet
@@ -63,15 +83,15 @@ const listeners = new Set();
63
83
  // the host does not have: the default, on a device in step with the host; this device's own (to upload) otherwise.
64
84
  if (host) {
65
85
  const step = inStep();
66
- for (const key of Object.keys(SYNCED)) {
86
+ for (const key of HOST_KEYS) {
67
87
  if (own(host, key)) cache(key, host[key]);
68
88
  else if (step) cache(key, null);
69
89
  }
70
90
  }
71
91
 
72
- /** This page may change the host's preferences: the signed-in app, not over the public link. */
92
+ /** This page may change the host's preferences: the signed-in app, not over the public link, not a guest's (#1920). */
73
93
  export function prefsWritable() {
74
- return !!initial && !(typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1");
94
+ return !!initial && !guestPage() && !(typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1");
75
95
  }
76
96
  /**
77
97
  * The value in force for a key, in memory — never only in this browser's storage (a full storage must not bring back
@@ -80,14 +100,14 @@ export function prefsWritable() {
80
100
  * in step) — the caller reads this browser's copy.
81
101
  */
82
102
  export function prefInForce(key) {
83
- if (!own(SYNCED, key)) return null;
103
+ if (!hostKey(key)) return null;
84
104
  if (mine.has(key)) return { value: mine.get(key) };
85
105
  if (host && own(host, key)) return { value: host[key] };
86
106
  if (initial && inStep()) return { value: undefined };
87
107
  return null;
88
108
  }
89
109
  /** The host's value for a key as this page last saw it (undefined: not set there, or not known). */
90
- export function hostPref(key) { return host && own(host, key) ? host[key] : undefined; }
110
+ export function hostPref(key) { return hostKey(key) && host && own(host, key) ? host[key] : undefined; }
91
111
  /** Called with the keys another device changed, after they are cached and applied here. Returns the unsubscribe. */
92
112
  export function onPrefsChanged(fn) { listeners.add(fn); return () => listeners.delete(fn); }
93
113
 
@@ -97,7 +117,7 @@ const isView = (body) => !!body && typeof body.rev === "number" && !!body.values
97
117
  function take(body) {
98
118
  if (!isView(body) || (rev !== null && body.rev < rev)) return false;
99
119
  rev = body.rev;
100
- host = body.values;
120
+ host = hostValues(body.values);
101
121
  return true;
102
122
  }
103
123
 
@@ -106,7 +126,7 @@ function take(body) {
106
126
  * (the public link, the anonymous reader): this browser only, as before.
107
127
  */
108
128
  export function savePref(key, value) {
109
- if (!own(SYNCED, key)) return;
129
+ if (!hostKey(key)) return;
110
130
  cache(key, value);
111
131
  if (!prefsWritable()) return;
112
132
  pending.set(key, value);
@@ -165,8 +185,8 @@ export async function refresh() {
165
185
  body = res.ok ? await res.json() : null;
166
186
  } catch { body = null; }
167
187
  if (!isView(body)) return false;
168
- const before = new Map(Object.keys(SYNCED).map((key) => [key, own(host, key) ? SYNCED[key].toLocal(host[key]) : null]));
169
- if (take(body)) align(Object.keys(SYNCED), { before });
188
+ const before = new Map(HOST_KEYS.map((key) => [key, own(host, key) ? SYNCED[key].toLocal(host[key]) : null]));
189
+ if (take(body)) align(HOST_KEYS, { before });
170
190
  return true;
171
191
  }
172
192
 
@@ -179,6 +199,7 @@ export async function refresh() {
179
199
  function align(keys, opts = {}) {
180
200
  const changed = new Set();
181
201
  for (const key of keys) {
202
+ if (!hostKey(key)) continue;
182
203
  if (pending.has(key) || inflight.has(key)) continue;
183
204
  const k = SYNCED[key];
184
205
  const want = own(host, key) ? k.toLocal(host[key]) : null;
@@ -216,7 +237,7 @@ function apply(changed) {
216
237
  */
217
238
  export async function migratePrefs() {
218
239
  if (!prefsWritable() || inStep()) return false;
219
- for (const key of Object.keys(SYNCED)) {
240
+ for (const key of HOST_KEYS) {
220
241
  if (own(host, key)) continue;
221
242
  const local = SYNCED[key].fromLocal(readLocal(SYNCED[key].ls));
222
243
  if (local === undefined) continue;
@@ -1,8 +1,8 @@
1
1
  // #1408 §3: the app's routes on the client. The same table as the server's classifier (src/web-shell-routes.ts;
2
2
  // a test runs both over one list of cases). Pure: no DOM, no history.
3
3
 
4
- export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
5
- export const SETTINGS_SECTIONS = ["device", "agents", "bots", "classic", "general", "web", "advanced"]; // #1604: device first
4
+ export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "usage", "config"];
5
+ export const SETTINGS_SECTIONS = ["device", "appearance", "agents", "bots", "classic", "general", "web", "advanced"]; // #1604: device first
6
6
  const MAX_INSTANCE_NAME = 128;
7
7
  const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
8
8
 
@@ -50,6 +50,24 @@ export function viewPath(instance) { return instance ? `/view/${encodeURICompone
50
50
  export function detailsPath(instance) { return `/ui/fleet/agent/${encodeURIComponent(instance)}`; }
51
51
  export function logPath(instance) { return `/ui/log/${encodeURIComponent(instance)}`; }
52
52
  export function fleetPath(tab) { return tab && tab !== "tasks" ? `/ui/fleet/${tab}` : "/ui/fleet"; }
53
+ /** #2183: this device's last Fleet tab (Fleet remembers it; the Org chart the first time). */
54
+ export const LAST_FLEET_TAB_KEY = "agend_last_fleet_tab";
55
+ export function lastFleetTab() {
56
+ let tab = null;
57
+ try { tab = localStorage.getItem(LAST_FLEET_TAB_KEY); } catch { /* storage blocked: the default */ }
58
+ return FLEET_TABS.includes(tab) ? tab : "org";
59
+ }
60
+ /**
61
+ * #2183: where Fleet (the sidebar's entry, the phone's tab) goes — on a Fleet tab, that tab (the route, not storage: the
62
+ * panel stores it in an effect after this render, and a storage write re-renders nothing); anywhere else, the last one.
63
+ */
64
+ export function fleetEntryPath(route) {
65
+ return fleetPath(route && route.panel === "fleet" ? route.tab : lastFleetTab()); // parseRoute names only FLEET_TABS
66
+ }
67
+ export function rememberFleetTab(tab) {
68
+ if (!FLEET_TABS.includes(tab)) return;
69
+ try { localStorage.setItem(LAST_FLEET_TAB_KEY, tab); } catch { /* this page only */ }
70
+ }
53
71
  export const NEEDS_PATH = "/ui/needs";
54
72
  export function settingsPath(section) { return section && section !== "agents" ? `/settings/${section}` : "/settings"; }
55
73
  export function routePath(route) {
@@ -62,6 +80,28 @@ export function routePath(route) {
62
80
  if (route.panel === "log") return logPath(route.instance);
63
81
  return route.instance ? chatPath(route.instance) : "/ui";
64
82
  }
83
+ /**
84
+ * #1990: a text with the in-app links the server put on it (ranges of the text: { offset, length, url }) as segments —
85
+ * { text } or { text, href }. Only a route this table parses as an instance's chat or View becomes a link (its href
86
+ * rebuilt here); a range out of order, out of the text, or anything else stays text. No target: the router opens it.
87
+ */
88
+ export function textLinkSegments(text, links) {
89
+ const s = String(text == null ? "" : text);
90
+ const out = [];
91
+ let at = 0;
92
+ for (const l of Array.isArray(links) ? links.slice(0, 16) : []) {
93
+ if (!l || !Number.isInteger(l.offset) || !Number.isInteger(l.length) || typeof l.url !== "string") continue;
94
+ if (l.offset < at || l.length < 1 || l.offset + l.length > s.length || !/^\/[^?#\s]*$/.test(l.url)) continue;
95
+ const route = parseRoute(l.url);
96
+ if (!route || !route.instance || (route.panel !== "chat" && route.panel !== "view")) continue;
97
+ if (l.offset > at) out.push({ text: s.slice(at, l.offset) });
98
+ out.push({ text: s.slice(l.offset, l.offset + l.length), href: routePath(route) });
99
+ at = l.offset + l.length;
100
+ }
101
+ if (at < s.length || !out.length) out.push({ text: s.slice(at) });
102
+ return out;
103
+ }
104
+
65
105
  /** One string per route: a change of it is a new navigation (a new lease for the panel, §4). */
66
106
  export function routeKey(route) {
67
107
  if (!route) return "none";
@@ -4,6 +4,7 @@
4
4
  import { html, useEffect, useRef, useState } from "./app-html.js";
5
5
  import { Icon } from "./ui-icons.js";
6
6
  import { t, lang } from "./app-i18n.js";
7
+ import { AuthorizeChip } from "./web-authorize.js";
7
8
 
8
9
  async function getJson(url) {
9
10
  try {
@@ -49,10 +50,11 @@ export function SessionMenu() {
49
50
  <button ref=${btn} type="button" class="side-row" aria-haspopup="dialog" aria-expanded=${open ? "true" : "false"}
50
51
  onClick=${() => { const next = !open; setOpen(next); if (next) refresh(); }}>
51
52
  <span class=${`dot${me ? " ok" : ""}`} aria-hidden="true"></span><span class="grow">${label}</span>${me ? html`<span class="meta">${me.label}</span>` : null}</button>
52
- ${open ? html`<div ref=${pop} class="pop" role="dialog" aria-label=${t("app.session")} onKeyDown=${onKey}>
53
+ ${open ? html`<div data-trust="session" ref=${pop} class="pop" role="dialog" aria-label=${t("app.session")} onKeyDown=${onKey}>
53
54
  ${!me ? html`<h3>${t("app.signedOut")}</h3><a class="btn" href=${`/signin?next=${encodeURIComponent(location.pathname)}`}>${t("app.signIn")}</a>` : html`
54
55
  <h3>${t("app.signedIn")} · ${me.label}</h3>
55
56
  <p class="sub">${t("app.expires", clock(me.expiresAt), span(me.idleMs))}</p>
57
+ <${AuthorizeChip} />
56
58
  ${rows.length > 1 ? html`<h3>${t("app.devices")}</h3><ul class="devices">${rows.map(s => html`<li key=${s.handle}>
57
59
  <span class="grow"><span class="name">${s.label}${s.current ? ` · ${t("app.thisDevice")}` : ""}</span><span class="when">${t("app.lastActive", ago(s.lastSeen))}</span></span>
58
60
  ${s.current ? null : html`<button type="button" class="btn btn-sm danger" onClick=${() => act("DELETE", `/auth/sessions/${encodeURIComponent(s.handle)}`, refresh)}>${t("app.signOut")}</button>`}</li>`)}</ul>` : null}