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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/dist/adapter-world.js.map +1 -1
  2. package/dist/agend-home.d.ts +5 -0
  3. package/dist/agend-home.js +7 -0
  4. package/dist/agend-home.js.map +1 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +8 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.js +1 -1
  9. package/dist/agent-file-read.js.map +1 -1
  10. package/dist/agent-whats-new.js.map +1 -1
  11. package/dist/apply-job.js.map +1 -1
  12. package/dist/atomic-write.js.map +1 -1
  13. package/dist/auth-api.d.ts +5 -0
  14. package/dist/auth-api.js +21 -0
  15. package/dist/auth-api.js.map +1 -1
  16. package/dist/authority/authority-state.d.ts +4 -0
  17. package/dist/authority/authority-state.js +17 -8
  18. package/dist/authority/authority-state.js.map +1 -1
  19. package/dist/authority/session-authorizations.js.map +1 -1
  20. package/dist/authority/web-authority.d.ts +14 -6
  21. package/dist/authority/web-authority.js +19 -5
  22. package/dist/authority/web-authority.js.map +1 -1
  23. package/dist/backend/antigravity.d.ts +2 -2
  24. package/dist/backend/antigravity.js +2 -1
  25. package/dist/backend/antigravity.js.map +1 -1
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/codex-package.js.map +1 -1
  28. package/dist/backend/codex-session-lookup.js.map +1 -1
  29. package/dist/backend/codex.js +1 -1
  30. package/dist/backend/codex.js.map +1 -1
  31. package/dist/backend/effort-metadata.js.map +1 -1
  32. package/dist/backend/grok.js.map +1 -1
  33. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  34. package/dist/backend/kiro-kas-store.js.map +1 -1
  35. package/dist/backend/kiro-v2-store.js.map +1 -1
  36. package/dist/backend/kiro-v3-identity.js.map +1 -1
  37. package/dist/backend/kiro.js.map +1 -1
  38. package/dist/backend/launch-effort.js.map +1 -1
  39. package/dist/backend/mock.js.map +1 -1
  40. package/dist/backend/muse.js.map +1 -1
  41. package/dist/backend/opencode-permission.js.map +1 -1
  42. package/dist/backend/opencode.js.map +1 -1
  43. package/dist/backend/session-signals.js.map +1 -1
  44. package/dist/backend-availability.js.map +1 -1
  45. package/dist/backend-command.js.map +1 -1
  46. package/dist/backend-outage.js.map +1 -1
  47. package/dist/binary-probe.js.map +1 -1
  48. package/dist/bug-report.js.map +1 -1
  49. package/dist/bundled-skills.js.map +1 -1
  50. package/dist/cache-service.js +1 -1
  51. package/dist/cache-service.js.map +1 -1
  52. package/dist/channel/adapters/discord.js +29 -4
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/adapters/telegram.d.ts +6 -0
  55. package/dist/channel/adapters/telegram.js +54 -14
  56. package/dist/channel/adapters/telegram.js.map +1 -1
  57. package/dist/channel/mcp-server.js +10 -1
  58. package/dist/channel/mcp-server.js.map +1 -1
  59. package/dist/channel/mcp-tools.js +2 -2
  60. package/dist/channel/mcp-tools.js.map +1 -1
  61. package/dist/channel/message-queue.js.map +1 -1
  62. package/dist/channel/sticker-download.js.map +1 -1
  63. package/dist/channel/tool-router.js.map +1 -1
  64. package/dist/classic-channel-manager.js +3 -2
  65. package/dist/classic-channel-manager.js.map +1 -1
  66. package/dist/cli-entry.js.map +1 -1
  67. package/dist/cli-flags.js.map +1 -1
  68. package/dist/cli.js +15 -9
  69. package/dist/cli.js.map +1 -1
  70. package/dist/command-table.js.map +1 -1
  71. package/dist/compact-watch.js.map +1 -1
  72. package/dist/completion-install.js.map +1 -1
  73. package/dist/completion.js.map +1 -1
  74. package/dist/config-validator.js +20 -6
  75. package/dist/config-validator.js.map +1 -1
  76. package/dist/config.js +11 -3
  77. package/dist/config.js.map +1 -1
  78. package/dist/connection-secrets.js.map +1 -1
  79. package/dist/context-guardian.js.map +1 -1
  80. package/dist/coredump-filter.js.map +1 -1
  81. package/dist/cpu-profile.js.map +1 -1
  82. package/dist/cross-instance-envelope.js.map +1 -1
  83. package/dist/daemon.d.ts +3 -0
  84. package/dist/daemon.js +73 -3
  85. package/dist/daemon.js.map +1 -1
  86. package/dist/daily-summary.js.map +1 -1
  87. package/dist/darwin-memory.js.map +1 -1
  88. package/dist/dashboard-live.d.ts +8 -0
  89. package/dist/dashboard-live.js +35 -0
  90. package/dist/dashboard-live.js.map +1 -0
  91. package/dist/dashboard-permissions.d.ts +69 -0
  92. package/dist/dashboard-permissions.js +138 -0
  93. package/dist/dashboard-permissions.js.map +1 -0
  94. package/dist/decision-list.js.map +1 -1
  95. package/dist/delivery-consumed-watch.js.map +1 -1
  96. package/dist/delivery-outbox.js.map +1 -1
  97. package/dist/delivery-outbox.test.js.map +1 -1
  98. package/dist/delivery-reconciliation.test.js.map +1 -1
  99. package/dist/diagnostic-redaction.js.map +1 -1
  100. package/dist/doctor.js.map +1 -1
  101. package/dist/e2e-isolation.js.map +1 -1
  102. package/dist/event-log.js.map +1 -1
  103. package/dist/export-import.js +3 -2
  104. package/dist/export-import.js.map +1 -1
  105. package/dist/fleet-control-audit.js.map +1 -1
  106. package/dist/fleet-label.js.map +1 -1
  107. package/dist/fleet-lock.js +3 -2
  108. package/dist/fleet-lock.js.map +1 -1
  109. package/dist/fleet-manager.d.ts +77 -6
  110. package/dist/fleet-manager.js +488 -147
  111. package/dist/fleet-manager.js.map +1 -1
  112. package/dist/fleet-process-handlers.js +3 -2
  113. package/dist/fleet-process-handlers.js.map +1 -1
  114. package/dist/fleet-startup.d.ts +14 -0
  115. package/dist/fleet-startup.js +30 -0
  116. package/dist/fleet-startup.js.map +1 -0
  117. package/dist/fleet-traffic.js.map +1 -1
  118. package/dist/forged-envelope-scan.d.ts +0 -20
  119. package/dist/forged-envelope-scan.js +3 -2
  120. package/dist/forged-envelope-scan.js.map +1 -1
  121. package/dist/full-restart.js.map +1 -1
  122. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
  123. package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
  124. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +36 -12
  125. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +45 -14
  126. package/dist/host-memory.js.map +1 -1
  127. package/dist/instance-config-impact.js +6 -0
  128. package/dist/instance-config-impact.js.map +1 -1
  129. package/dist/instance-lifecycle.js +1 -1
  130. package/dist/instance-lifecycle.js.map +1 -1
  131. package/dist/instructions.d.ts +2 -0
  132. package/dist/instructions.js +6 -2
  133. package/dist/instructions.js.map +1 -1
  134. package/dist/kiro-compact-pane.js.map +1 -1
  135. package/dist/kiro-db-reader.js.map +1 -1
  136. package/dist/kiro-engine-status.js.map +1 -1
  137. package/dist/kiro-log-gate.js.map +1 -1
  138. package/dist/kiro-transcript-lane.js.map +1 -1
  139. package/dist/locale.js +22 -0
  140. package/dist/locale.js.map +1 -1
  141. package/dist/log-service.js.map +1 -1
  142. package/dist/log-steps.js.map +1 -1
  143. package/dist/logger.js +3 -2
  144. package/dist/logger.js.map +1 -1
  145. package/dist/login-controller.js.map +1 -1
  146. package/dist/login-manager.js.map +1 -1
  147. package/dist/login-shell.js.map +1 -1
  148. package/dist/memory-pressure.js.map +1 -1
  149. package/dist/muse-usage-relay.js.map +1 -1
  150. package/dist/needs-you-hub.js.map +1 -1
  151. package/dist/needs-you-live.js.map +1 -1
  152. package/dist/network-family.js.map +1 -1
  153. package/dist/operator-control-paths.js +3 -4
  154. package/dist/operator-control-paths.js.map +1 -1
  155. package/dist/outbound-handlers.js.map +1 -1
  156. package/dist/outbound-schemas.d.ts +68 -68
  157. package/dist/outbound-schemas.js +1 -1
  158. package/dist/outbound-schemas.js.map +1 -1
  159. package/dist/outbox-open-error.js.map +1 -1
  160. package/dist/pane-context-cache.js.map +1 -1
  161. package/dist/paths.js.map +1 -1
  162. package/dist/private-dir.js.map +1 -1
  163. package/dist/probe-worker-pool.js.map +1 -1
  164. package/dist/profile-control.js.map +1 -1
  165. package/dist/prompt-file-ref.js.map +1 -1
  166. package/dist/provider-probe.js.map +1 -1
  167. package/dist/provider-secret-registry.js.map +1 -1
  168. package/dist/public-link-auto-open.d.ts +32 -0
  169. package/dist/public-link-auto-open.js +86 -0
  170. package/dist/public-link-auto-open.js.map +1 -0
  171. package/dist/public-link-owner.d.ts +10 -0
  172. package/dist/public-link-owner.js +36 -0
  173. package/dist/public-link-owner.js.map +1 -0
  174. package/dist/public-link-progress.js.map +1 -1
  175. package/dist/public-preview.js.map +1 -1
  176. package/dist/public-web-gateway.js +4 -2
  177. package/dist/public-web-gateway.js.map +1 -1
  178. package/dist/public-web-link.d.ts +35 -1
  179. package/dist/public-web-link.js +142 -43
  180. package/dist/public-web-link.js.map +1 -1
  181. package/dist/quickstart-api.js.map +1 -1
  182. package/dist/quickstart.d.ts +5 -0
  183. package/dist/quickstart.js +25 -16
  184. package/dist/quickstart.js.map +1 -1
  185. package/dist/reply-buttons-controller.js.map +1 -1
  186. package/dist/reply-dedup.js.map +1 -1
  187. package/dist/resource-report.js.map +1 -1
  188. package/dist/restart-guard.js.map +1 -1
  189. package/dist/restart-progress.js.map +1 -1
  190. package/dist/rollout-index.js.map +1 -1
  191. package/dist/runtime-cpu-profile.js.map +1 -1
  192. package/dist/scheduler/db.js.map +1 -1
  193. package/dist/scheduler/scheduler.js.map +1 -1
  194. package/dist/scheduler/task-board-text.js +16 -11
  195. package/dist/scheduler/task-board-text.js.map +1 -1
  196. package/dist/scheduler/task-board.d.ts +50 -10
  197. package/dist/scheduler/task-board.js +138 -44
  198. package/dist/scheduler/task-board.js.map +1 -1
  199. package/dist/scheduler/types.d.ts +1 -1
  200. package/dist/sd-notify.js.map +1 -1
  201. package/dist/search/search-index.js.map +1 -1
  202. package/dist/secret-file.js.map +1 -1
  203. package/dist/secret-store.js.map +1 -1
  204. package/dist/service-installer.d.ts +7 -1
  205. package/dist/service-installer.js +13 -1
  206. package/dist/service-installer.js.map +1 -1
  207. package/dist/service-plan.d.ts +33 -4
  208. package/dist/service-plan.js +177 -8
  209. package/dist/service-plan.js.map +1 -1
  210. package/dist/session-oom.d.ts +21 -0
  211. package/dist/session-oom.js +139 -0
  212. package/dist/session-oom.js.map +1 -0
  213. package/dist/settings-api.js +6 -1
  214. package/dist/settings-api.js.map +1 -1
  215. package/dist/settings-baseline.js.map +1 -1
  216. package/dist/settings-change.d.ts +5 -0
  217. package/dist/settings-change.js +71 -1
  218. package/dist/settings-change.js.map +1 -1
  219. package/dist/settings-confirmation.d.ts +4 -0
  220. package/dist/settings-confirmation.js +2 -0
  221. package/dist/settings-confirmation.js.map +1 -1
  222. package/dist/settings-control.js.map +1 -1
  223. package/dist/settings-effect.js +6 -1
  224. package/dist/settings-effect.js.map +1 -1
  225. package/dist/settings-http-confirmation.js +1 -0
  226. package/dist/settings-http-confirmation.js.map +1 -1
  227. package/dist/settings-instance-patch.js +13 -1
  228. package/dist/settings-instance-patch.js.map +1 -1
  229. package/dist/settings-transaction.js.map +1 -1
  230. package/dist/setup-auth.js.map +1 -1
  231. package/dist/setup-host.d.ts +5 -1
  232. package/dist/setup-host.js +8 -1
  233. package/dist/setup-host.js.map +1 -1
  234. package/dist/setup-wizard.js +5 -2
  235. package/dist/setup-wizard.js.map +1 -1
  236. package/dist/spawn-gate.d.ts +4 -0
  237. package/dist/spawn-gate.js +15 -5
  238. package/dist/spawn-gate.js.map +1 -1
  239. package/dist/startup-memory-deadline.d.ts +23 -0
  240. package/dist/startup-memory-deadline.js +55 -0
  241. package/dist/startup-memory-deadline.js.map +1 -0
  242. package/dist/statusline-watcher.js.map +1 -1
  243. package/dist/storm-window.js.map +1 -1
  244. package/dist/systemd-peer-bus.js.map +1 -1
  245. package/dist/target-queue-worker.js.map +1 -1
  246. package/dist/task-board-summary.d.ts +4 -4
  247. package/dist/task-board-summary.js +4 -4
  248. package/dist/task-board-summary.js.map +1 -1
  249. package/dist/task-board-tool.js +3 -2
  250. package/dist/task-board-tool.js.map +1 -1
  251. package/dist/task-board-waker.js +8 -4
  252. package/dist/task-board-waker.js.map +1 -1
  253. package/dist/tips.js.map +1 -1
  254. package/dist/tmux-control.js.map +1 -1
  255. package/dist/tmux-exec-helper.js.map +1 -1
  256. package/dist/tmux-manager.d.ts +1 -0
  257. package/dist/tmux-manager.js +2 -1
  258. package/dist/tmux-manager.js.map +1 -1
  259. package/dist/tmux-read.js +1 -1
  260. package/dist/tmux-read.js.map +1 -1
  261. package/dist/tool-permissions.js.map +1 -1
  262. package/dist/tool-progress.js.map +1 -1
  263. package/dist/topic-archiver.js.map +1 -1
  264. package/dist/topic-commands.js.map +1 -1
  265. package/dist/topic-link.js.map +1 -1
  266. package/dist/transcript-delta-reader.js.map +1 -1
  267. package/dist/transcript-jsonl.js.map +1 -1
  268. package/dist/transcript-monitor.js.map +1 -1
  269. package/dist/transcript-sources.js.map +1 -1
  270. package/dist/tunnel/cloudflared-install.js.map +1 -1
  271. package/dist/tunnel/cloudflared.js.map +1 -1
  272. package/dist/tunnel/lease.js +3 -2
  273. package/dist/tunnel/lease.js.map +1 -1
  274. package/dist/tunnel/manager.js.map +1 -1
  275. package/dist/tunnel/public-fetch.js.map +1 -1
  276. package/dist/tunnel/purpose-lane.js.map +1 -1
  277. package/dist/tunnel/types.js.map +1 -1
  278. package/dist/types.d.ts +26 -2
  279. package/dist/ui/chat-render.js +1 -15
  280. package/dist/ui/chat-store.js +171 -61
  281. package/dist/ui/chat-strings.js +7 -2
  282. package/dist/ui/chat-thread.js +48 -1
  283. package/dist/ui/fleet-canvas-layout.js +3 -0
  284. package/dist/ui/fleet-canvas.js +23 -20
  285. package/dist/ui/fleet-office-editor.js +149 -0
  286. package/dist/ui/fleet-office-model.js +358 -0
  287. package/dist/ui/fleet-office-pack.js +199 -0
  288. package/dist/ui/fleet-office-skin.js +474 -0
  289. package/dist/ui/fleet-office-store.js +49 -0
  290. package/dist/ui/fleet-office-zones.js +177 -0
  291. package/dist/ui/fleet-office.js +434 -0
  292. package/dist/ui/fleet-org.js +18 -5
  293. package/dist/ui/guest-refused.html +41 -0
  294. package/dist/ui/image-lightbox.js +9 -3
  295. package/dist/ui/panel-chat.js +14 -13
  296. package/dist/ui/panel-settings.js +30 -12
  297. package/dist/ui/settings-dialogs.js +15 -6
  298. package/dist/ui/settings-model.js +40 -3
  299. package/dist/ui/settings-strings.js +38 -0
  300. package/dist/ui/settings-web-authority.js +7 -2
  301. package/dist/ui/shared/agend-auth.js +2 -0
  302. package/dist/ui/shared/app-i18n.js +2 -2
  303. package/dist/ui/shared/app-prefs.js +2 -0
  304. package/dist/ui/shared/app-shell.js +12 -0
  305. package/dist/ui/shared/app-store.js +1 -0
  306. package/dist/ui/shared/app-stream.js +2 -1
  307. package/dist/ui/shared/app.css +132 -3
  308. package/dist/ui/shared/guest-refused.js +35 -0
  309. package/dist/ui/shared/header-tools.js +2 -2
  310. package/dist/ui/shared/instance-nav.js +25 -8
  311. package/dist/ui/shared/ui-dialog.js +2 -2
  312. package/dist/ui/shared/usage-dialog.js +152 -6
  313. package/dist/ui/shared/view-strings.js +20 -0
  314. package/dist/ui/shared/web-authorize.js +3 -1
  315. package/dist/update-check.js.map +1 -1
  316. package/dist/update-dispatch.js.map +1 -1
  317. package/dist/update-install.js.map +1 -1
  318. package/dist/update-launch.js.map +1 -1
  319. package/dist/update-marker.js.map +1 -1
  320. package/dist/update-preflight.js.map +1 -1
  321. package/dist/usage/format-rich.js.map +1 -1
  322. package/dist/usage/providers.d.ts +8 -0
  323. package/dist/usage/providers.js +6 -2
  324. package/dist/usage/providers.js.map +1 -1
  325. package/dist/usage/statusline-usage.js.map +1 -1
  326. package/dist/usage/usage-api.js +2 -1
  327. package/dist/usage/usage-api.js.map +1 -1
  328. package/dist/usage-report.js.map +1 -1
  329. package/dist/usage-rollup.js +1 -1
  330. package/dist/usage-rollup.js.map +1 -1
  331. package/dist/user-systemd.js.map +1 -1
  332. package/dist/view-api.js +1 -1
  333. package/dist/view-api.js.map +1 -1
  334. package/dist/wake-coordinator.js.map +1 -1
  335. package/dist/web-ambient.js.map +1 -1
  336. package/dist/web-api.d.ts +15 -2
  337. package/dist/web-api.js +174 -25
  338. package/dist/web-api.js.map +1 -1
  339. package/dist/web-app-icon.js.map +1 -1
  340. package/dist/web-appearance-policy.js.map +1 -1
  341. package/dist/web-appearance-tools.js.map +1 -1
  342. package/dist/web-appearance-view.js.map +1 -1
  343. package/dist/web-appearance.js.map +1 -1
  344. package/dist/web-auth.js +5 -4
  345. package/dist/web-auth.js.map +1 -1
  346. package/dist/web-backend-login.js.map +1 -1
  347. package/dist/web-card.js.map +1 -1
  348. package/dist/web-channel-echo.js.map +1 -1
  349. package/dist/web-chat-history.d.ts +41 -11
  350. package/dist/web-chat-history.js +93 -22
  351. package/dist/web-chat-history.js.map +1 -1
  352. package/dist/web-commands.js.map +1 -1
  353. package/dist/web-effect-bundle.d.ts +2 -2
  354. package/dist/web-effect-library.js.map +1 -1
  355. package/dist/web-effect-playback.js.map +1 -1
  356. package/dist/web-effect-profiles.d.ts +16 -16
  357. package/dist/web-effect-profiles.js +3 -2
  358. package/dist/web-effect-profiles.js.map +1 -1
  359. package/dist/web-effect-service.js.map +1 -1
  360. package/dist/web-effect-tools.js.map +1 -1
  361. package/dist/web-effect-trials.js.map +1 -1
  362. package/dist/web-effect-upload.js.map +1 -1
  363. package/dist/web-effect.d.ts +7 -7
  364. package/dist/web-effect.js.map +1 -1
  365. package/dist/web-experimental.js.map +1 -1
  366. package/dist/web-guest-access.js +1 -1
  367. package/dist/web-guest-access.js.map +1 -1
  368. package/dist/web-guest-rooms.js.map +1 -1
  369. package/dist/web-host-guard.js.map +1 -1
  370. package/dist/web-icons.js.map +1 -1
  371. package/dist/web-identity-color.js.map +1 -1
  372. package/dist/web-images.js.map +1 -1
  373. package/dist/web-libavoid.js.map +1 -1
  374. package/dist/web-login.js.map +1 -1
  375. package/dist/web-mentions.js.map +1 -1
  376. package/dist/web-mod-agent.js.map +1 -1
  377. package/dist/web-mod-store.js.map +1 -1
  378. package/dist/web-office-store.d.ts +104 -0
  379. package/dist/web-office-store.js +236 -0
  380. package/dist/web-office-store.js.map +1 -0
  381. package/dist/web-owner-css-store.js.map +1 -1
  382. package/dist/web-owner-css.js.map +1 -1
  383. package/dist/web-png.js.map +1 -1
  384. package/dist/web-prefs.js +2 -0
  385. package/dist/web-prefs.js.map +1 -1
  386. package/dist/web-profile.js.map +1 -1
  387. package/dist/web-reactions.d.ts +2 -1
  388. package/dist/web-request-context.d.ts +5 -0
  389. package/dist/web-request-context.js.map +1 -1
  390. package/dist/web-returning-device.js +1 -1
  391. package/dist/web-returning-device.js.map +1 -1
  392. package/dist/web-schedules.js.map +1 -1
  393. package/dist/web-send-dedupe.d.ts +36 -0
  394. package/dist/web-send-dedupe.js +69 -0
  395. package/dist/web-send-dedupe.js.map +1 -0
  396. package/dist/web-session-authorization.js.map +1 -1
  397. package/dist/web-session.js +1 -1
  398. package/dist/web-session.js.map +1 -1
  399. package/dist/web-settings-patch.d.ts +1 -0
  400. package/dist/web-settings-patch.js +6 -4
  401. package/dist/web-settings-patch.js.map +1 -1
  402. package/dist/web-settings-tools.js.map +1 -1
  403. package/dist/web-terminal-http.js.map +1 -1
  404. package/dist/web-terminal.js.map +1 -1
  405. package/dist/web-theme-store.js.map +1 -1
  406. package/dist/web-upload.d.ts +5 -1
  407. package/dist/web-upload.js +6 -4
  408. package/dist/web-upload.js.map +1 -1
  409. package/dist/web-widget.js.map +1 -1
  410. package/dist/webhook-emitter.js.map +1 -1
  411. package/dist/windows-shortcuts.js +1 -1
  412. package/dist/windows-shortcuts.js.map +1 -1
  413. package/dist/workflow-templates/default.md +1 -1
  414. package/dist/ws-server.js.map +1 -1
  415. package/dist/yaml-shared-nodes.js.map +1 -1
  416. package/launcher/agend-install.cjs +281 -6
  417. package/launcher/private-home.cjs +43 -0
  418. package/package.json +3 -2
  419. package/templates/windows-shortcuts.ps1 +1 -1
@@ -11,11 +11,11 @@ import { t, register } from "/assets/app-i18n.js";
11
11
  import { chatPath } from "/assets/app-route.js";
12
12
  import { Empty, Skeleton } from "/assets/ui-states.js";
13
13
  import { Icon } from "/assets/ui-icons.js";
14
- import { modelText } from "/assets/instance-nav.js";
14
+ import { backendLabel, modelText, namesOf } from "/assets/instance-nav.js";
15
15
 
16
16
  register("org", {
17
17
  en: {
18
- title: "Org chart", general: "General", unteamed: "Not in a team", members: "{0} members", member: "1 member", instances: "{0} instances", instance: "1 instance",
18
+ starting: "Starting", queued: "Queued", title: "Org chart", general: "General", unteamed: "Not in a team", members: "{0} members", member: "1 member", instances: "{0} instances", instance: "1 instance",
19
19
  needs: "Needs you", working: "Working", idle: "Idle", stuck: "Looks stuck", paused: "Paused", stopped: "Stopped", crashed: "Crashed",
20
20
  missing: "Not in the fleet", thread_discord: "Discord thread", thread_telegram: "Telegram topic", openChat: "Open chat",
21
21
  classic: "ClassicBot room", empty: "No instances yet", emptyHint: "Instances and teams show here once the fleet has them.",
@@ -23,7 +23,7 @@ register("org", {
23
23
  sending: "Sending:", queuedNote: "(queued)", workingNote: "(in progress)",
24
24
  },
25
25
  "zh-TW": {
26
- title: "組織圖", general: "General", unteamed: "未加入 team", members: "{0} 名成員", member: "1 名成員", instances: "{0} 個 instance", instance: "1 個 instance",
26
+ starting: "啟動中", queued: "等待啟動", title: "組織圖", general: "General", unteamed: "未加入 team", members: "{0} 名成員", member: "1 名成員", instances: "{0} 個 instance", instance: "1 個 instance",
27
27
  needs: "等你回應", working: "工作中", idle: "閒置", stuck: "似乎卡住", paused: "已暫停", stopped: "已停止", crashed: "已當掉",
28
28
  missing: "不在 fleet 中", thread_discord: "Discord 討論串", thread_telegram: "Telegram topic", openChat: "開啟聊天",
29
29
  classic: "ClassicBot 頻道", empty: "還沒有 instance", emptyHint: "fleet 有 instance 和 team 後會顯示在這裡。",
@@ -35,8 +35,8 @@ const to = (k, ...v) => t(`org.${k}`, ...v);
35
35
  const count = (one, many, n) => (n === 1 ? to(one) : to(many, n));
36
36
 
37
37
  /** The states a node can show, most pressing first (a team's summary lists them in this order). */
38
- export const ORG_STATES = ["needs", "crashed", "stuck", "working", "idle", "paused", "stopped", "missing"];
39
- const DOT = { needs: "warn", crashed: "bad", stuck: "warn", working: "busy", idle: "ok", paused: "off", stopped: "off", missing: "off" };
38
+ export const ORG_STATES = ["needs", "crashed", "stuck", "starting", "queued", "working", "idle", "paused", "stopped", "missing"];
39
+ const DOT = { starting: "busy", queued: "off", needs: "warn", crashed: "bad", stuck: "warn", working: "busy", idle: "ok", paused: "off", stopped: "off", missing: "off" };
40
40
 
41
41
  /**
42
42
  * One instance's live state, from the app store: `i` its status entry (null: the fleet does not list it), `exec` the
@@ -46,6 +46,8 @@ const DOT = { needs: "warn", crashed: "bad", stuck: "warn", working: "busy", idl
46
46
  export function orgState(i, exec, awaiting, needs) {
47
47
  if (!i) return "missing";
48
48
  if (awaiting != null || needs > 0) return "needs";
49
+ if (i.startup_phase === "queued") return "queued";
50
+ if (i.startup_phase === "starting") return "starting";
49
51
  if (i.status === "crashed") return "crashed";
50
52
  if (i.status === "paused") return "paused";
51
53
  if (i.status !== "running") return "stopped";
@@ -54,6 +56,17 @@ export function orgState(i, exec, awaiting, needs) {
54
56
  return "idle";
55
57
  }
56
58
 
59
+ /**
60
+ * What a node's card says (fleet-canvas.js; the Office view's desk too, fleet-office.js): its names, its state and
61
+ * backend (`sub`), and all of it in one line (`full`, the card's title and label).
62
+ */
63
+ export function nodeText(n) {
64
+ const it = n.i;
65
+ const names = it ? namesOf(it) : { primary: n.name, secondary: "" };
66
+ const sub = it ? `${to(n.state)} · ${backendLabel(it.backend)}` : to(n.state);
67
+ return { primary: names.primary, secondary: names.secondary, sub, full: `${names.primary}${names.secondary ? ` (${names.secondary})` : ""} — ${sub}` };
68
+ }
69
+
57
70
  /**
58
71
  * The chart: the structure from /ui/org, the nodes and their states from the app store. General on top; each team with
59
72
  * its members in fleet.yaml's order (an instance in two teams shows in both: that is its place in each); then the
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="referrer" content="no-referrer">
7
+ <meta name="robots" content="noindex">
8
+ <title>AgEnD</title>
9
+ <!--app-icon-->
10
+ <link rel="icon" href="/favicon.ico" sizes="16x16 32x32 64x64">
11
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any">
12
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
13
+ <!--/app-icon-->
14
+ <script src="/assets/theme.js"></script>
15
+ <link rel="stylesheet" href="/assets/tokens.css">
16
+ <style>
17
+ /* What a guest's browser gets for a page it cannot open (#2399 smoke: it was the gate's bare JSON). The sign-in
18
+ page's look: the app's tokens, light and dark (theme.js before paint). */
19
+ * { box-sizing: border-box; }
20
+ body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 16px;
21
+ background: var(--bg); color: var(--text); font: var(--fw-regular) var(--fs-md)/var(--lh-md) var(--font-ui); -webkit-font-smoothing: antialiased; }
22
+ main { width: 100%; max-width: 380px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 28px 24px; }
23
+ .brand { margin: 0 0 18px; font-size: var(--fs-lg); font-weight: var(--fw-bold); }
24
+ h1 { margin: 0 0 6px; font-size: var(--fs-xl); font-weight: var(--fw-semibold); }
25
+ p { margin: 0 0 18px; color: var(--text-2); }
26
+ a.go { display: block; width: 100%; padding: 12px; text-align: center; text-decoration: none; font: var(--fw-semibold) var(--fs-md) var(--font-ui);
27
+ color: var(--on-accent); background: var(--accent-fill); border-radius: var(--r-full); }
28
+ a.go:hover { opacity: .88; }
29
+ a.go:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-dim); }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <main id="refused" data-why="__WHY__">
34
+ <div class="brand">AgEnD</div>
35
+ <h1 id="title">This page is not open to guests</h1>
36
+ <p id="lead">This page is for the fleet's members. As a guest you can use the chats shared with you.</p>
37
+ <a id="go" class="go" href="/ui">Open your chats</a>
38
+ </main>
39
+ <script src="/assets/guest-refused.js"></script>
40
+ </body>
41
+ </html>
@@ -83,12 +83,18 @@ function ImageLightbox({ items, index, opener, onClose }) {
83
83
  zoom.current = z;
84
84
  return () => { z.destroy(); zoom.current = null; };
85
85
  }, []);
86
- // Another image: hidden until it has loaded and been fitted (its load resets the zoom).
86
+ // Another image: hidden until it has loaded and been fitted (its load resets the zoom). A layout effect — in the same
87
+ // commit that set the new src, before any load can arrive: a picture already in the cache (every thumbnail above it
88
+ // has loaded) fires its load before a passive effect would run, and such an effect then hid it for good with the
89
+ // level at "…" (the report on beta.24: Next "flashes once, then the next image is not visible"). A picture that is
90
+ // already complete here (the same file again: no load comes) is fitted at once.
87
91
  const first = useRef(true);
88
- useEffect(() => {
92
+ useLayoutEffect(() => {
89
93
  if (first.current) { first.current = false; return; }
90
- if (img.current) img.current.classList.remove("lb-ready");
94
+ const el = img.current;
95
+ if (el) el.classList.remove("lb-ready");
91
96
  setLevel(null);
97
+ if (el && el.complete && el.naturalWidth > 0 && zoom.current) zoom.current.reset();
92
98
  }, [at]);
93
99
  useEffect(() => {
94
100
  const d = ref.current;
@@ -122,7 +122,9 @@ export async function setPreviewOptIn(on, commit = (apply) => apply()) {
122
122
  export function boot({ stream, boot: bootData, deps = {} }) {
123
123
  if (store) return store;
124
124
  store = createChatStore({
125
- fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog, ...deps,
125
+ fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog,
126
+ XMLHttpRequest: typeof XMLHttpRequest === "function" ? XMLHttpRequest : undefined, // an upload's progress (chat-store.js)
127
+ ...deps,
126
128
  });
127
129
  emojiIndex = createEmojiIndex((url) => (deps.fetch || fetch)(url, { credentials: "same-origin" }));
128
130
  mentionIndex = createMentionIndex((url) => (deps.fetch || fetch)(url, { credentials: "same-origin" }));
@@ -425,6 +427,9 @@ function threadEntry(name, doc) {
425
427
  canTryEffect: x => getAppEffects()?.canTry(x) === true,
426
428
  startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
427
429
  clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
430
+ retryOutgoing: (clientId) => store.retryOutgoing(name, clientId), // a failed send's Retry and Delete
431
+ deleteOutgoing: (clientId) => store.deleteOutgoing(name, clientId),
432
+ putBackOutgoing: (clientId) => { recordEdit(composerField()); store.putBackOutgoing(name, clientId); }, // undoable, as typing
428
433
  // #2272: reply to a message (the bar above the composer), and show the one a reply quotes.
429
434
  startReply: (x) => { store.startReply(name, x); doc.getElementById("msgIn")?.focus(); },
430
435
  findQuoted: (key) => store.findQuoted(name, key),
@@ -446,12 +451,13 @@ function follow(name, thread) {
446
451
  // #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
447
452
  const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
448
453
  const offs = [];
449
- if (emojiIndex) offs.push(emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
450
- if (mentionIndex) offs.push(mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
454
+ if (emojiIndex) offs.push(emojiIndex.subscribe(() => thread.render(store.threadRows(name))));
455
+ if (mentionIndex) offs.push(mentionIndex.subscribe(() => thread.render(store.threadRows(name))));
451
456
  wantEmojis(store.state.msgs[name]);
452
457
  offs.push(store.subscribe((instance, kind) => {
453
458
  if (instance !== name) return;
454
- if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
459
+ if (kind === "msgs") { thread.render(store.threadRows(name)); wantEmojis(store.state.msgs[name]); }
460
+ else if (kind === "progress") thread.progress(); // an upload moved: its bar, in place
455
461
  else if (kind === "sent") thread.jumpLatest();
456
462
  }));
457
463
  offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
@@ -461,10 +467,10 @@ function follow(name, thread) {
461
467
  const next = getAppEffects();
462
468
  if (next === trialOwner) return;
463
469
  trialOff?.(); trialOff = null; trialOwner = next;
464
- trialOff = next?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? null;
470
+ trialOff = next?.subscribeTrial(() => thread.render(store.threadRows(name))) ?? null;
465
471
  };
466
472
  watchTrial();
467
- offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.state.msgs[name] || []); }));
473
+ offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.threadRows(name)); }));
468
474
  offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
469
475
  offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
470
476
  return () => { for (const off of offs) off(); };
@@ -493,7 +499,7 @@ function Thread({ name, th }) {
493
499
  thread.attach(sc);
494
500
  th.current = thread;
495
501
  // Up to date, at the reader's place (the bottom unless they had scrolled up); a kept one's cards say what they can do.
496
- thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
502
+ thread.render(store.threadRows(name), { restore: store.state.scrollMemo[name] ?? null });
497
503
  if (kept) thread.refreshCards();
498
504
  const unfollow = follow(name, thread);
499
505
  const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
@@ -546,12 +552,7 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
546
552
  <${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${workingLabel} />
547
553
  <${CommandCard} name=${name} lease=${lease} />
548
554
  <${QuickActions} name=${name} inst=${inst} lease=${lease} />
549
- ${s.failedSends[name] ? html`<div class="failed-send" role="alert"><span>${t("chat.notSent")}</span><span class="txt">${s.failedSends[name]}</span>
550
- <button type="button" class="btn btn-primary btn-sm" onClick=${() => { recordEdit(composerField()); store.putBack(name); }}>${t("chat.putBack")}</button>
551
- <button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
552
555
  ${s.waking[name] ? html`<p class="send-waking" role="status">${t("chat.waking", name)}</p>` : null}
553
- ${s.sendFailures[name] ? html`<div class="failed-send send-failure" role="alert"><span>${s.sendFailures[name].error}</span>
554
- <button type="button" class="btn btn-sm" disabled=${!store.canRetrySend(name)} onClick=${() => store.retrySend(name)}>${t("chat.retrySend")}</button></div>` : null}
555
556
  <${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
556
557
  <div class="composer-row">${guestPage() ? html`<span class="guest-note" title=${t("chat.guestNote")}>${t("chat.guestNote")}</span>` : html`<${ComposerAuth} />`}<span
557
558
  class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t("chat.composerHint")}</span></div>
@@ -721,7 +722,7 @@ function Composer({ name, lease, busy }) {
721
722
  aria-activedescendant=${pick ? optId(pick) : undefined} onInput=${onInput} onKeyDown=${onKeyDown} onPaste=${onPaste}></textarea>
722
723
  <button id="stopBtn" type="button" class="btn btn-stop" hidden=${!busy} title=${t("chat.stopReplyTitle")}
723
724
  disabled=${!!s.cancelling[name]} onClick=${() => store.cancelReply(name)}><${Icon} name="stop" size=${14} /><span class="stop-label">${t("chat.stopReply")}</span></button>
724
- <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content || !!s.sending[name]}
725
+ <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content}
725
726
  onMouseDown=${(e) => e.preventDefault()}
726
727
  aria-label=${t("chat.send")} title=${t("chat.send")} onClick=${submit}><${Icon} name="send" size=${16} /><span class="send-label">${t("chat.send")}</span></button>
727
728
  </div></div>`;
@@ -25,10 +25,10 @@ import "./settings-strings.js";
25
25
  import { api, attach, confirmedWrite, newKey } from "./settings-confirm.js";
26
26
  import { dismissOperation, operationActive, restartFleet, startOperation, takeLeftover } from "./settings-apply.js";
27
27
  import {
28
- ACCESS_MODES, AGENT_MODES, DEFAULT_SCHEMA, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
29
- changedFields, channelId, channelsOf, chLabel, connectionState, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
28
+ ACCESS_MODES, AGENT_MODES, DASHBOARD_FEATURES, DEFAULT_SCHEMA, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS, VISIBILITY_MODES, batchImpact,
29
+ changedFields, channelId, channelsOf, chLabel, connectionState, dashboardDefault, effectiveSummary, fleetModel, fromYaml, fullModelRequests, groupAgents,
30
30
  hasOwn, impactOf, localeOptions, defaultsImpact, requestImpact, nonNegative, positive, replyGuardSupported, shortName, STATUS_ORDER, toYaml,
31
- visibilityDefault, worstImpact,
31
+ toolSetDefault, visibilityDefault, worstImpact,
32
32
  } from "./settings-model.js";
33
33
  import {
34
34
  AgentDialog, BackendSelect, BotDialog, ChipList, ClassicDialog, Drawer, Feedback, Impact, Label, ModelSelect, NewBotDialog, Override,
@@ -676,8 +676,9 @@ function General({ ctx }) {
676
676
  const original = useMemo(() => ({ c: structuredClone(c) }), []);
677
677
  const [f, setF] = useState(() => ({
678
678
  backend: d.backend || "claude-code", model: d.model || "", locale: d.locale || "", auto: String(d.auto_pause_after ?? 0),
679
- agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
679
+ agent_mode: d.agent_mode || "mcp", tool_set: TOOL_SETS.includes(d.tool_set) ? d.tool_set : "", tool_progress: d.tool_progress ?? "off",
680
680
  visibility: visibilityDefault(d), reply: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
681
+ dash: Object.fromEntries(DASHBOARD_FEATURES.map((k) => [k, dashboardDefault(d, k)])),
681
682
  hangOn: hang.enabled !== false, hang: String(hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES),
682
683
  concurrency: String(startup.concurrency ?? 10), stagger: String(startup.stagger_delay_ms ?? 500),
683
684
  classicBackend: c.backend || d.backend || "claude-code",
@@ -699,14 +700,17 @@ function General({ ctx }) {
699
700
  if (!ready) return;
700
701
  const defaultsPatch = changedFields({
701
702
  backend: f.backend, model: f.model || undefined, locale: f.locale || null, auto_pause_after: num(f.auto),
702
- agent_mode: f.agent_mode, tool_set: f.tool_set, tool_progress: f.tool_progress, cross_instance_visibility: f.visibility,
703
+ agent_mode: f.agent_mode, tool_set: f.tool_set || null, tool_progress: f.tool_progress, cross_instance_visibility: f.visibility,
703
704
  reply_completion_guard: f.reply, log_level: f.log_level,
705
+ // The dashboard features every agent may use unless its own setting says otherwise (all three, as shown).
706
+ dashboard: f.dash,
704
707
  hang_detector: { enabled: f.hangOn, timeout_minutes: num(f.hang) },
705
708
  startup: { concurrency: num(f.concurrency), stagger_delay_ms: num(f.stagger) },
706
709
  }, {
707
710
  backend: d.backend || "claude-code", model: d.model || undefined, locale: d.locale || null, auto_pause_after: d.auto_pause_after ?? 0,
708
- agent_mode: d.agent_mode || "mcp", tool_set: d.tool_set || "full", tool_progress: d.tool_progress ?? "off",
711
+ agent_mode: d.agent_mode || "mcp", tool_set: TOOL_SETS.includes(d.tool_set) ? d.tool_set : null, tool_progress: d.tool_progress ?? "off",
709
712
  cross_instance_visibility: visibilityDefault(d), reply_completion_guard: d.reply_completion_guard ?? true, log_level: d.log_level || "info",
713
+ dashboard: Object.fromEntries(DASHBOARD_FEATURES.map((k) => [k, dashboardDefault(d, k)])),
710
714
  hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? HANG_TIMEOUT_DEFAULT_MINUTES },
711
715
  startup: { concurrency: startup.concurrency ?? 10, stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
712
716
  });
@@ -751,6 +755,11 @@ function General({ ctx }) {
751
755
  <div class="field"><label class="check"><input type="checkbox" checked=${f.reply} onChange=${(e) => set("reply")(e.target.checked)} /><span>${tn("replyCompletionGuard")}</span><${Impact} schema=${schema} field="defaults.reply_completion_guard" /></label>
752
756
  <p class="note">${fleetReply}</p></div>
753
757
  </div>
758
+ <fieldset class="field dash-perms"><legend>${L(tn("dashboardAccess"), "defaults.dashboard")}</legend>
759
+ <p class="note">${tn("dashboardDefaultsHint")}</p>
760
+ ${DASHBOARD_FEATURES.map((k) => html`<label key=${k} class="check"><input type="checkbox" checked=${f.dash[k]}
761
+ onChange=${(e) => setF((x) => ({ ...x, dash: { ...x.dash, [k]: e.target.checked } }))} /><span>${tn(`dashboard_${k}`)}</span></label>`)}
762
+ </fieldset>
754
763
  </section>
755
764
  <section class="card"><h3>ClassicBot</h3>
756
765
  <div class="grid2">
@@ -771,7 +780,8 @@ function General({ ctx }) {
771
780
  </section>
772
781
  <${Drawer} title=${tn("advancedSection")}>
773
782
  <section class="card"><h3>${tn("runtimeResources")}</h3><div class="grid2">
774
- <div class="field">${L(tn("toolSet"), "defaults.tool_set", "g-ts")}<${Select} id="g-ts" value=${f.tool_set} onChange=${set("tool_set")} options=${TOOL_SETS} /></div>
783
+ <div class="field">${L(tn("toolSet"), "defaults.tool_set", "g-ts")}<${Select} id="g-ts" value=${f.tool_set} onChange=${set("tool_set")}
784
+ options=${[{ value: "", label: tn("toolSetUnset", toolSetDefault({}, {}), toolSetDefault({}, { general_topic: true })) }, ...TOOL_SETS]} /></div>
775
785
  <div class="field">${L(tn("logLevel"), "defaults.log_level", "g-ll")}<${Select} id="g-ll" value=${f.log_level} onChange=${set("log_level")} options=${LOG_LEVELS} /></div>
776
786
  <div class="field">${L(tn("agentMode"), "defaults.agent_mode", "g-am")}<${Select} id="g-am" value=${f.agent_mode} onChange=${set("agent_mode")} options=${AGENT_MODES} /></div>
777
787
  </div></section>
@@ -813,7 +823,7 @@ function WebSection({ ctx, staged }) {
813
823
  echo: web.echo_to_channel ?? true,
814
824
  log: web.log_tab === true, // #1218: off by default
815
825
  usagePub: web.usage_report_on_public_link === true, // #2122: off by default
816
- pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2", hostname: (web.public_link && web.public_link.hostname) ?? "", tunnel_token_env: (web.public_link && web.public_link.tunnel_token_env) ?? "", origin_port: (web.public_link && web.public_link.origin_port) ?? 19282 },
826
+ pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2", hostname: (web.public_link && web.public_link.hostname) ?? "", tunnel_token_env: (web.public_link && web.public_link.tunnel_token_env) ?? "", origin_port: (web.public_link && web.public_link.origin_port) ?? 19282, auto_open: (web.public_link && web.public_link.auto_open) ?? false },
817
827
  session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
818
828
  terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
819
829
  }), []);
@@ -828,7 +838,7 @@ function WebSection({ ctx, staged }) {
828
838
  return {
829
839
  echo: pick(pending, "echo_to_channel", original.echo), log: pick(pending, "log_tab", original.log), usagePub: pick(pending, "usage_report_on_public_link", original.usagePub),
830
840
  pub: pick(pub, "allow_public", original.pub.allow_public), ttl: String(pick(pub, "ttl_minutes", original.pub.ttl_minutes)), protocol: pick(pub, "protocol", original.pub.protocol),
831
- hostname: pick(pub, "hostname", original.pub.hostname), originPort: String(pick(pub, "origin_port", original.pub.origin_port)),
841
+ hostname: pick(pub, "hostname", original.pub.hostname), originPort: String(pick(pub, "origin_port", original.pub.origin_port)), autoOpen: pick(pub, "auto_open", original.pub.auto_open),
832
842
  abs: text(pick(session, "absolute_hours", original.session.absolute_hours)), idle: text(pick(session, "idle_hours", original.session.idle_hours)),
833
843
  termOn: pick(terminal, "enabled", original.terminal.enabled) !== false, termTtl: text(pick(terminal, "ttl_minutes", original.terminal.ttl_minutes)),
834
844
  };
@@ -841,10 +851,15 @@ function WebSection({ ctx, staged }) {
841
851
  // The defaults (#1670: 48 h / 48 h, DEFAULT_SESSION_POLICY); an idle time left blank follows a shorter session, as
842
852
  // the store and the validator apply it — so only an idle time that is typed can exceed the session length.
843
853
  const abs = value(f.abs) ?? 48, idle = value(f.idle) ?? Math.min(48, abs);
854
+ // Named mode: a hostname is typed (the server judges the merged result the same way).
855
+ const named = String(f.hostname || "").trim() !== "";
844
856
  const errors = {
845
857
  hostname: f.hostname && !/^(?:https:\/\/)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+\/?$/.test(f.hostname) ? "namedHostnameInvalid" : "",
846
858
  originPort: !Number.isInteger(Number(f.originPort)) || Number(f.originPort) < 1024 || Number(f.originPort) > 65535 ? "namedPortInvalid" : "",
847
- ttl: !Number.isInteger(Number(f.ttl)) || Number(f.ttl) < 1 || Number(f.ttl) > 480 ? "publicTtl" : "",
859
+ // #named-link-ttl: a fixed hostname (named tunnel) allows up to 7 days, or 0 = no expiry; a quick tunnel 1–480.
860
+ ttl: String(f.ttl).trim() === "" || !Number.isInteger(Number(f.ttl)) || Number(f.ttl) < (named ? 0 : 1) || Number(f.ttl) > (named ? 10080 : 480)
861
+ ? (named ? "publicTtlNamedRange" : "publicTtlQuickRange") : "",
862
+ autoOpen: f.autoOpen && !named ? "publicAutoOpenNeedsHostname" : "",
848
863
  abs: hours(f.abs),
849
864
  idle: hours(f.idle) || (!hours(f.abs) && idle > abs ? "sessionIdleOver" : ""),
850
865
  termTtl: value(f.termTtl) !== null && (!Number.isInteger(value(f.termTtl)) || value(f.termTtl) < 1 || value(f.termTtl) > 20) ? "webTerminalTtlRange" : "",
@@ -857,7 +872,7 @@ function WebSection({ ctx, staged }) {
857
872
  // reference adopts the standard env key; mounting or editing an unrelated field never rewrites credentials.
858
873
  const tokenEnv = f.hostname === original.pub.hostname ? original.pub.tunnel_token_env
859
874
  : f.hostname ? original.pub.tunnel_token_env || "AGEND_CLOUDFLARE_TUNNEL_TOKEN" : "";
860
- const publicPatch = changedFields({ allow_public: f.pub, ttl_minutes: Number(f.ttl), protocol: f.protocol, hostname: f.hostname, tunnel_token_env: tokenEnv, origin_port: Number(f.originPort) }, original.pub);
875
+ const publicPatch = changedFields({ allow_public: f.pub, ttl_minutes: Number(f.ttl), protocol: f.protocol, hostname: f.hostname, tunnel_token_env: tokenEnv, origin_port: Number(f.originPort), auto_open: f.autoOpen }, original.pub);
861
876
  const sessionPatch = changedFields({ absolute_hours: value(f.abs), idle_hours: value(f.idle) }, original.session);
862
877
  // The checkbox shows the default (on) for an unset key: only a real toggle is written; on again = back to the default.
863
878
  const terminalPatch = changedFields({ enabled: f.termOn === (original.terminal.enabled !== false) ? original.terminal.enabled : f.termOn ? null : false,
@@ -931,9 +946,12 @@ function WebSection({ ctx, staged }) {
931
946
  </div>
932
947
  <p class="note">${tn("namedTunnelHint")} <a href="https://github.com/songsid/AgEnD/blob/main/docs/named-tunnel.md" target="_blank" rel="noopener noreferrer">${tn("namedTunnelSetup")}</a></p>
933
948
  <div class="grid2">
934
- <div class="field"><label for="publicWebTtl">${tn("publicTtl")}</label><input id="publicWebTtl" type="number" min="1" max="480" class=${errors.ttl ? "invalid" : ""} value=${f.ttl} onInput=${(e) => set("ttl")(e.target.value)} /><${Feedback} error=${errors.ttl} /></div>
949
+ <div class="field"><label for="publicWebTtl">${tn(named ? "publicTtlNamed" : "publicTtl")}</label><input id="publicWebTtl" type="number" min=${named ? "0" : "1"} max=${named ? "10080" : "480"} step="1" class=${errors.ttl ? "invalid" : ""} value=${f.ttl} onInput=${(e) => set("ttl")(e.target.value)} /><${Feedback} error=${errors.ttl} /></div>
935
950
  <div class="field"><label for="publicWebProtocol">${tn("publicProtocol")}</label><${Select} id="publicWebProtocol" value=${f.protocol} onChange=${set("protocol")} options=${["http2", "quic", "auto"]} /></div>
936
951
  </div>
952
+ ${named ? html`<p class="note" id="publicTtlNamedHint">${tn("publicTtlNamedHint")}</p>` : null}
953
+ ${named || f.autoOpen ? html`<label class="check"><input id="publicAutoOpen" type="checkbox" checked=${f.autoOpen} onChange=${(e) => set("autoOpen")(e.target.checked)} /><span>${tn("publicAutoOpen")}</span><${Impact} schema=${schema} field="web.public_link.auto_open" /></label>
954
+ <p class="note">${tn("publicAutoOpenHint")}</p><${Feedback} error=${errors.autoOpen} />` : null}
937
955
  </section>
938
956
  </fieldset>
939
957
  ${overPublicLink ? null : html`<${PublicTunnelToken} /><${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />`}
@@ -17,10 +17,10 @@ import { startOperation } from "./settings-apply.js";
17
17
  import "./settings-strings.js"; // its words, wherever a picker of this file is used (#2072: Fleet's create dialog too)
18
18
  import { backendChoices, modelChoices, OTHER_MODEL, useBackendRows, useModelCatalog } from "./settings-catalog.js";
19
19
  import {
20
- ACCESS_MODES, AGENT_MODES, CH_TYPES, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
20
+ ACCESS_MODES, AGENT_MODES, CH_TYPES, DASHBOARD_CHOICES, DASHBOARD_FEATURES, HANG_TIMEOUT_DEFAULT_MINUTES, LOG_LEVELS, TOOL_PROGRESS, TOOL_SETS,
21
21
  VISIBILITY_MODES, accessConfirmations, agentForm, agentPatch, backendOptions, changedFields, channelId, channelsOf,
22
- chLabel, hasOwn, impactOf, newConnectionImpact, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
23
- classicWebEchoEffective, worstImpact,
22
+ chLabel, dashboardDefault, hasOwn, impactOf, newConnectionImpact, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
23
+ classicWebEchoEffective, toolSetDefault, worstImpact,
24
24
  } from "./settings-model.js";
25
25
  /** #2342: a field by id, for recordEdit before a pick or a reset writes it. */
26
26
  const byId = (id) => (typeof document !== "undefined" ? document.getElementById(id) : null);
@@ -230,7 +230,7 @@ function EmojiPicker({ cat, keyName, platform, channel, onPick, onClose }) {
230
230
  export function AgentDialog({ name, inst, ctx, onClose }) {
231
231
  const { fleet, schema } = ctx;
232
232
  const defaults = fleet.defaults || {};
233
- const [f, setF] = useState(() => agentForm(inst, defaults));
233
+ const [f, setF] = useState(() => agentForm(inst, defaults, name));
234
234
  const [emojis, setEmojis] = useState({ value: inst.status_emojis || null, baseline: inst.status_emojis || null });
235
235
  // #1302: the baseline is what the picker shows before anyone touches it.
236
236
  const visibilityBaseline = useMemo(() => (hasOwn(inst, "cross_instance_visibility") ? (VISIBILITY_MODES.includes(inst.cross_instance_visibility) ? inst.cross_instance_visibility : VISIBILITY_MODES[0]) : null), []);
@@ -278,15 +278,24 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
278
278
  <input id="ag-hang" type="number" min="0.1" step="0.5" class=${errors.hang ? "invalid" : ""} disabled=${f.inherit.hang}
279
279
  value=${shown("hang", f.hang, String((defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? HANG_TIMEOUT_DEFAULT_MINUTES))} onInput=${(e) => set("hang")(e.target.value)} /></${Override}>
280
280
  <${Feedback} error=${errors.hang} /></div>
281
- ${[["agent_mode", "agentMode", AGENT_MODES, defaults.agent_mode || "mcp"], ["tool_set", "toolSet", TOOL_SETS, defaults.tool_set || "full"],
281
+ ${[["agent_mode", "agentMode", AGENT_MODES, defaults.agent_mode || "mcp"], ["tool_set", "toolSet", TOOL_SETS, toolSetDefault(defaults, inst, name)],
282
282
  ["tool_progress", "toolProgress", TOOL_PROGRESS, defaults.tool_progress ?? "off"],
283
283
  ["cross_instance_visibility", "crossInstanceVisibility", VISIBILITY_MODES, visibilityDefault(defaults)],
284
284
  ["log_level", "logLevel", LOG_LEVELS, defaults.log_level || "info"]].map(([k, label, opts, dflt]) => html`<div key=${k} class="field">
285
285
  ${L(tn(label), `instance.${k}`, `ag-${k}`)}
286
286
  <${Override} inherit=${f.inherit[k]} onInherit=${setInherit(k)}>
287
287
  <${Select} id=${`ag-${k}`} value=${shown(k, f[k], dflt)} disabled=${f.inherit[k]} onChange=${set(k)}
288
- options=${opts.includes(f[k]) || k === "cross_instance_visibility" ? opts : [f[k], ...opts]} /></${Override}>
288
+ options=${opts.includes(shown(k, f[k], dflt)) || k === "cross_instance_visibility" ? opts : [shown(k, f[k], dflt), ...opts]} /></${Override}>
289
289
  ${k === "cross_instance_visibility" ? html`<p class="note">${tn("crossInstanceVisibilityHint")}</p>` : null}</div>`)}
290
+ <fieldset class="field dash-perms"><legend>${L(tn("dashboardAccess"), "instance.dashboard")}</legend>
291
+ <p class="note">${tn("dashboardAccessHint")}</p>
292
+ ${DASHBOARD_FEATURES.map((feature) => html`<div key=${feature} class="field">
293
+ <label for=${`ag-dash-${feature}`}>${tn(`dashboard_${feature}`)}</label>
294
+ <select id=${`ag-dash-${feature}`} value=${f.dashboard[feature]} onChange=${(e) => setF((x) => ({ ...x, dashboard: { ...x.dashboard, [feature]: e.target.value } }))}>
295
+ ${DASHBOARD_CHOICES.map((c) => html`<option key=${c} value=${c}>${c === "inherit"
296
+ ? tn("dashboardInherit", tn(dashboardDefault(defaults, feature) ? "dashboardOn" : "dashboardOff")) : tn(c === "on" ? "dashboardOn" : "dashboardOff")}</option>`)}
297
+ </select></div>`)}
298
+ </fieldset>
290
299
  <div class="field">${L(tn("replyCompletionGuard"), "instance.reply_completion_guard")}
291
300
  <${Override} inherit=${f.inherit.reply_completion_guard} onInherit=${setInherit("reply_completion_guard")}>
292
301
  <label class="check"><input type="checkbox" disabled=${f.inherit.reply_completion_guard} checked=${shown("reply_completion_guard", f.reply_completion_guard, defaults.reply_completion_guard ?? true)}
@@ -9,7 +9,22 @@ export const CH_TYPES = ["discord", "telegram"];
9
9
  export const LOCALES = [["en", "English"], ["zh-TW", "繁體中文 (zh-TW)"]];
10
10
  export const VISIBILITY_MODES = ["full", "summary", "hidden"];
11
11
  export const LOG_LEVELS = ["trace", "debug", "info", "warn", "error"];
12
- export const TOOL_SETS = ["full", "standard", "minimal"];
12
+ /**
13
+ * The tool profiles a person may choose (src/tool-permissions.ts; the validator's list). `general` is not one: AgEnD
14
+ * gives it to a General that sets none.
15
+ */
16
+ export const TOOL_SETS = ["worker", "coordinator", "standard", "minimal", "full"];
17
+ /**
18
+ * The profile an agent runs under while it sets none (resolveToolSet, on the config as loaded: the defaults merged
19
+ * under the agent's own): the fleet default's, else `general` for a General — its own `general_topic`, else the
20
+ * default's (#2407 review: `defaults.general_topic: true` makes every agent without its own a General), or the instance
21
+ * named general — else `worker`; never `full`, which the page used to show.
22
+ */
23
+ export function toolSetDefault(defaults, inst, name = "") {
24
+ if (TOOL_SETS.includes(defaults && defaults.tool_set)) return defaults.tool_set;
25
+ const general = hasOwn(inst, "general_topic") ? inst.general_topic === true : !!defaults && defaults.general_topic === true;
26
+ return general || name === "general" ? "general" : "worker";
27
+ }
13
28
  export const TOOL_PROGRESS = ["off", "standard", "verbose"];
14
29
  export const AGENT_MODES = ["mcp", "cli"];
15
30
  export const ACCESS_MODES = ["locked", "open", "pairing"];
@@ -23,6 +38,23 @@ export function changedFields(next, baseline) {
23
38
  return Object.fromEntries(Object.entries(next).filter(([key, value]) => !sameValue(value, baseline[key])));
24
39
  }
25
40
 
41
+ /**
42
+ * The dashboard features an agent may use (dashboard-permissions.ts): each "inherit" (the fleet default: `defaults.dashboard`,
43
+ * else allowed), "on" or "off". The patch names only the features that changed: null follows the default again.
44
+ */
45
+ export const DASHBOARD_FEATURES = ["appearance", "task_board", "web_settings"];
46
+ export const DASHBOARD_CHOICES = ["inherit", "on", "off"];
47
+ const dashboardOf = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : {});
48
+ /** What a feature is for an agent with no setting of its own: the default's, else allowed. */
49
+ export const dashboardDefault = (defaults, feature) => dashboardOf(defaults && defaults.dashboard)[feature] !== false;
50
+ export function dashboardForm(inst) {
51
+ const own = dashboardOf(inst.dashboard);
52
+ return Object.fromEntries(DASHBOARD_FEATURES.map((f) => [f, own[f] === true ? "on" : own[f] === false ? "off" : "inherit"]));
53
+ }
54
+ export function dashboardPatchValue(choices) {
55
+ return Object.fromEntries(DASHBOARD_FEATURES.map((f) => [f, choices[f] === "on" ? true : choices[f] === "off" ? false : null]));
56
+ }
57
+
26
58
  /** #1302: cross-instance notices. Unset, or a value the fleet does not know, means full. */
27
59
  export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaults && defaults.cross_instance_visibility) ? defaults.cross_instance_visibility : "full");
28
60
 
@@ -140,6 +172,7 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
140
172
  model_failover: form.inherit.model_failover ? null : (form.model_failover.trim() ? [form.model_failover.trim()] : []),
141
173
  display_name: form.inherit.display_name ? null : form.display_name.trim(),
142
174
  status_emojis: form.status_emojis,
175
+ dashboard: dashboardPatchValue(form.dashboard),
143
176
  };
144
177
  return changedFields(next, {
145
178
  backend: inst.backend || defaults.backend || "claude-code",
@@ -162,11 +195,12 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
162
195
  model_failover: hasOwn(inst, "model_failover") ? inst.model_failover : null,
163
196
  display_name: hasOwn(inst, "display_name") ? inst.display_name : null,
164
197
  status_emojis: statusEmojisBaseline,
198
+ dashboard: dashboardPatchValue(dashboardForm(inst)),
165
199
  });
166
200
  }
167
201
 
168
202
  /** The agent form's starting values (an override shows the inherited value while "inherit" is on). */
169
- export function agentForm(inst, defaults) {
203
+ export function agentForm(inst, defaults, name = "") {
170
204
  const hang = inst.hang_detector || {};
171
205
  const inherit = (key) => !hasOwn(inst, key);
172
206
  return {
@@ -177,7 +211,9 @@ export function agentForm(inst, defaults) {
177
211
  auto_pause_after: String(inst.auto_pause_after ?? defaults.auto_pause_after ?? 0),
178
212
  hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? HANG_TIMEOUT_DEFAULT_MINUTES),
179
213
  agent_mode: inst.agent_mode || defaults.agent_mode || "mcp",
180
- tool_set: inst.tool_set || defaults.tool_set || "full",
214
+ // What the picker offers when the agent sets its own: a profile a person may choose (a General's inherited
215
+ // `general` is shown while inheriting, but not offered to write back).
216
+ tool_set: TOOL_SETS.includes(inst.tool_set) ? inst.tool_set : TOOL_SETS.includes(toolSetDefault(defaults, inst, name)) ? toolSetDefault(defaults, inst, name) : "worker",
181
217
  tool_progress: inst.tool_progress ?? defaults.tool_progress ?? "off",
182
218
  // #1302: a value the fleet does not know (a typo) is read as unset — the picker shows "full", as the old page did,
183
219
  // and the baseline is that same "full", so an unrelated edit never writes the typo back (the server refuses it).
@@ -190,6 +226,7 @@ export function agentForm(inst, defaults) {
190
226
  model_failover: Array.isArray(inst.model_failover) ? (inst.model_failover[0] || "") : ((defaults.model_failover && defaults.model_failover[0]) || ""),
191
227
  display_name: inst.display_name ?? defaults.display_name ?? "",
192
228
  status_emojis: inst.status_emojis || null,
229
+ dashboard: dashboardForm(inst),
193
230
  inherit: {
194
231
  auto_pause_after: inherit("auto_pause_after"), hang: !hasOwn(hang, "timeout_minutes"), agent_mode: inherit("agent_mode"),
195
232
  tool_set: inherit("tool_set"), tool_progress: inherit("tool_progress"), cross_instance_visibility: inherit("cross_instance_visibility"),
@@ -12,6 +12,13 @@ register("settings", {
12
12
  publicLink: "Offer temporary public web links",
13
13
  publicLinkHint: "Explicit admin consent on every request. Cloudflare handles TLS; link users have full web-admin access. HTML previews get a second temporary tunnel unless web.preview_on_public_link is false.",
14
14
  publicTtl: "Public link minutes (1–480)",
15
+ publicTtlNamed: "Public link minutes (0–10080; 0 = no expiry)",
16
+ publicTtlNamedHint: "With a fixed hostname the link may stay open up to 7 days (10080 minutes), or 0 = no expiry: it stays open until closed or AgEnD stops. Sign-in still needs a code each time. A changed lifetime applies at the next opening; Close the link to end the current one.",
17
+ publicTtlQuickRange: "A quick tunnel allows 1–480 minutes. Lower it, or set a fixed hostname for longer.",
18
+ publicTtlNamedRange: "With a fixed hostname: a whole number from 1 to 10080, or 0 for no expiry.",
19
+ publicAutoOpen: "Reopen the public link when AgEnD starts",
20
+ publicAutoOpenHint: "Fixed hostname only. After a restart the link reopens for the admin who last opened it with /web; nothing is posted. Sign-in still needs a code (from /web, or asked for on the sign-in page by a browser that signed in before). Close or /web revoke stops it until the next /web.",
21
+ publicAutoOpenNeedsHostname: "Needs a fixed hostname — untick it, or keep the hostname.",
15
22
  publicProtocol: "Tunnel protocol",
16
23
  namedHostname: "Fixed public hostname (optional)",
17
24
  namedOriginPort: "Named tunnel origin port",
@@ -86,10 +93,20 @@ register("settings", {
86
93
  hangTimeout: "Stuck timeout",
87
94
  agentMode: "Agent mode",
88
95
  toolSet: "Tool set",
96
+ toolSetUnset: "Not set ({0}; a General: {1})",
89
97
  toolProgress: "Tool progress",
90
98
  crossInstanceVisibility: "Bot-to-bot messages in topics",
91
99
  crossInstanceVisibilityHint: "full: the whole message · summary: one line · hidden: nothing. Messages are always delivered; the Mirror Topic still shows them.",
92
100
  replyCompletionGuard: "Require a delivered reply",
101
+ dashboardAccess: "Dashboard access",
102
+ dashboardAccessHint: "What this agent may use on the dashboard, on top of its tool set. Switching a feature off applies at once (its tools are refused and leave its tool list); switching one on is confirmed first.",
103
+ dashboard_appearance: "Its look (theme, pictures, widgets, effects, mod proposals)",
104
+ dashboard_task_board: "The task board",
105
+ dashboard_web_settings: "Your web appearance settings",
106
+ dashboardInherit: "Fleet default ({0})",
107
+ dashboardOn: "allowed",
108
+ dashboardOff: "off",
109
+ dashboardDefaultsHint: "What every agent may use on the dashboard unless its own setting (its dialog under Agents) says otherwise. Unticking applies at once; ticking is confirmed first.",
93
110
  replyGuardSupported: "Available for Claude Code, Codex, Muse and Kiro legacy/TUI in MCP mode.",
94
111
  replyGuardUnsupported: "Stored but inactive: {0} in {1} mode does not support reply-drop recovery.",
95
112
  logLevel: "Log level",
@@ -505,6 +522,8 @@ register("settings", {
505
522
  vaEnabled: "Web messages carry authority", vaEnabledHint: "On by default: a session carries authority only once its account confirms it from Discord or Telegram. Off: no web message carries authority, and any already given reads as none at once; authority given before it was turned off never comes back.",
506
523
  vaStuckOff: "Web authority is off for now: its state could not be saved on this computer (see the fleet log). It comes back on by itself once it can be.",
507
524
  vaLoopbackOnly: "Only this computer can authorize", vaLoopbackOnlyHint: "On: only a browser on this computer qualifies, not one on your network or tailnet, and authority already given from elsewhere reads as none. Turning it off again doesn't bring that back.",
525
+ vaNamedLink: "Also over my named link ({0})", vaNamedLinkHint: "On: a browser signed in over your own named-tunnel hostname may be authorized too (the same Confirm in your DM). It is reached from the internet, and Cloudflare sees its traffic: turning this on is confirmed in a General or on this computer. Off applies at once, and every authorization given over it reads as none. A quick tunnel never qualifies.",
526
+ vaSessionNamed: "over your named link",
508
527
  vaLanWarning: "The dashboard also answers to {0} (web.allowed_hosts). Anyone who reaches it there with a stolen sign-in could give authority in your name. Turn on \"Only this computer can authorize\" unless you trust everyone who can reach it.",
509
528
  vaHours: "An authorization lasts (hours, {0}–{1})", vaHoursRange: "Must be a number from 1 to 48",
510
529
  vaSessionsTitle: "Authorized sessions", vaSessionsNone: "No session is authorized now.", vaSessionUntil: "Authorized {0} · until {1}",
@@ -628,6 +647,13 @@ register("settings", {
628
647
  publicLink: "提供臨時公開網頁連結",
629
648
  publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS;登入者具有完整網頁管理權限。HTML 預覽會另開一條臨時通道,除非設定 web.preview_on_public_link: false。",
630
649
  publicTtl: "公開連結分鐘數(1–480)",
650
+ publicTtlNamed: "公開連結分鐘數(0–10080;0 = 不到期)",
651
+ publicTtlNamedHint: "設定固定網域時,連結最長可開 7 天(10080 分鐘);設 0 則不會到期,直到關閉或 AgEnD 停止。每次登入仍需登入碼。修改後的期限從下次開啟起生效;要結束目前的連結,請按「關閉」。",
652
+ publicTtlQuickRange: "Quick tunnel 只能設 1–480 分鐘。請調低,或設定固定網域以使用更長時間。",
653
+ publicTtlNamedRange: "設定固定網域時:請輸入 1 到 10080 的整數,或 0 代表不到期。",
654
+ publicAutoOpen: "AgEnD 啟動時重新開啟公開連結",
655
+ publicAutoOpenHint: "僅限固定網域。重新啟動後,會替上次用 /web 開啟連結的管理員重新開啟;不會發任何訊息。登入仍需登入碼(用 /web 取得,或由曾登入過的瀏覽器在登入頁索取)。按「關閉」或 /web revoke 後,直到下次 /web 前都不會自動開啟。",
656
+ publicAutoOpenNeedsHostname: "需要固定網域——請取消勾選,或保留網域。",
631
657
  publicProtocol: "Tunnel 協定",
632
658
  namedHostname: "固定公開網域(選用)",
633
659
  namedOriginPort: "Named tunnel 本機連接埠",
@@ -677,10 +703,20 @@ register("settings", {
677
703
  hangTimeout: "卡住逾時",
678
704
  agentMode: "Agent 模式",
679
705
  toolSet: "工具集",
706
+ toolSetUnset: "未設定({0};General 為 {1})",
680
707
  toolProgress: "工具進度",
681
708
  crossInstanceVisibility: "Agent 之間的訊息在主題中",
682
709
  crossInstanceVisibilityHint: "full:完整訊息 · summary:一行摘要 · hidden:不顯示。訊息一律照常送達,Mirror Topic 也照常顯示。",
683
710
  replyCompletionGuard: "要求送出回覆",
711
+ dashboardAccess: "Dashboard 權限",
712
+ dashboardAccessHint: "這個 agent 能在 dashboard 上用哪些功能(在工具組之外再加一層限制)。關掉立即生效(工具會被拒絕,也從它的工具清單拿掉);打開要先確認。",
713
+ dashboard_appearance: "它的外觀(主題、圖片、小工具、特效、mod 提案)",
714
+ dashboard_task_board: "任務看板",
715
+ dashboard_web_settings: "你的網頁外觀設定",
716
+ dashboardInherit: "跟 fleet 預設({0})",
717
+ dashboardOn: "允許",
718
+ dashboardOff: "關閉",
719
+ dashboardDefaultsHint: "每個 agent 預設能在 dashboard 上用哪些功能;個別 agent 可以在 Agents 裡它的對話框另外設定。取消勾選立即生效;勾選要先確認。",
684
720
  replyGuardSupported: "Claude Code、Codex、Muse 與 Kiro legacy/TUI 的 MCP 模式可用。",
685
721
  replyGuardUnsupported: "設定會保留,但不會生效:{0} 的 {1} 模式不支援回覆遺失補救。",
686
722
  logLevel: "Log 等級",
@@ -1095,6 +1131,8 @@ register("settings", {
1095
1131
  vaEnabled: "網頁訊息帶有授權", vaEnabledHint: "預設開啟:session 要等它的帳號在 Discord 或 Telegram 上確認後才帶有授權。關閉:網頁訊息都不帶授權,已給出的授權立即視為無;關閉前給出的授權不會恢復。",
1096
1132
  vaStuckOff: "網頁授權目前是關閉的:它的狀態無法存到這台電腦上(請看 fleet 紀錄)。可以存入後會自動恢復。",
1097
1133
  vaLoopbackOnly: "只有這台電腦可以授權", vaLoopbackOnlyHint: "開啟:只有這台電腦上的瀏覽器符合,網路或 tailnet 上的不算,之前從其他地方給出的授權視為無。再關掉也不會讓那些授權恢復。",
1134
+ vaNamedLink: "也允許透過我的固定網域({0})", vaNamedLinkHint: "開啟:透過你自己的 named tunnel 固定網域登入的瀏覽器也能被授權(一樣要在私訊按「確認」)。它可從網際網路連到,Cloudflare 也看得到流量:開啟需在 General 或這台電腦上確認。關閉立即生效,透過它給出的授權都視為無。Quick tunnel 一律不行。",
1135
+ vaSessionNamed: "透過你的固定網域",
1098
1136
  vaLanWarning: "dashboard 也接受 {0} 的連線(web.allowed_hosts)。任何人只要從那裡連上、又拿到被盜的登入,就能以你的名義給出授權。除非你信任所有能連到那裡的人,否則請開啟「只有這台電腦可以授權」。",
1099
1137
  vaHours: "一次授權的有效期(小時,{0}–{1})", vaHoursRange: "必須是 1 到 48 之間的數字",
1100
1138
  vaSessionsTitle: "已授權的 session", vaSessionsNone: "目前沒有已授權的 session。", vaSessionUntil: "{0} 授權 · 到 {1}",