@songsid/agend 2.3.0-beta.25 → 2.3.0-beta.27

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 (440) hide show
  1. package/README.md +7 -5
  2. package/README.zh-TW.md +7 -5
  3. package/dist/adapter-world.js.map +1 -1
  4. package/dist/agend-home.d.ts +5 -0
  5. package/dist/agend-home.js +7 -0
  6. package/dist/agend-home.js.map +1 -0
  7. package/dist/agent-cli.js.map +1 -1
  8. package/dist/agent-endpoint.js +8 -0
  9. package/dist/agent-endpoint.js.map +1 -1
  10. package/dist/agent-file-read.js +1 -1
  11. package/dist/agent-file-read.js.map +1 -1
  12. package/dist/agent-whats-new.js +12 -2
  13. package/dist/agent-whats-new.js.map +1 -1
  14. package/dist/apply-job.js.map +1 -1
  15. package/dist/atomic-write.js.map +1 -1
  16. package/dist/auth-api.d.ts +5 -0
  17. package/dist/auth-api.js +21 -0
  18. package/dist/auth-api.js.map +1 -1
  19. package/dist/authority/authority-state.d.ts +4 -0
  20. package/dist/authority/authority-state.js +17 -8
  21. package/dist/authority/authority-state.js.map +1 -1
  22. package/dist/authority/session-authorizations.js.map +1 -1
  23. package/dist/authority/web-authority.d.ts +14 -6
  24. package/dist/authority/web-authority.js +19 -5
  25. package/dist/authority/web-authority.js.map +1 -1
  26. package/dist/backend/antigravity.d.ts +2 -2
  27. package/dist/backend/antigravity.js +2 -1
  28. package/dist/backend/antigravity.js.map +1 -1
  29. package/dist/backend/claude-code.js.map +1 -1
  30. package/dist/backend/codex-package.js.map +1 -1
  31. package/dist/backend/codex-session-lookup.js.map +1 -1
  32. package/dist/backend/codex.js +1 -1
  33. package/dist/backend/codex.js.map +1 -1
  34. package/dist/backend/effort-metadata.js.map +1 -1
  35. package/dist/backend/grok.js.map +1 -1
  36. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  37. package/dist/backend/kiro-kas-store.js.map +1 -1
  38. package/dist/backend/kiro-v2-store.js.map +1 -1
  39. package/dist/backend/kiro-v3-identity.js.map +1 -1
  40. package/dist/backend/kiro.js.map +1 -1
  41. package/dist/backend/launch-effort.js.map +1 -1
  42. package/dist/backend/mock.js.map +1 -1
  43. package/dist/backend/muse.js.map +1 -1
  44. package/dist/backend/opencode-permission.js.map +1 -1
  45. package/dist/backend/opencode.js.map +1 -1
  46. package/dist/backend/session-signals.js.map +1 -1
  47. package/dist/backend-availability.js.map +1 -1
  48. package/dist/backend-command.js.map +1 -1
  49. package/dist/backend-outage.js.map +1 -1
  50. package/dist/binary-probe.js.map +1 -1
  51. package/dist/bug-report.js.map +1 -1
  52. package/dist/bundled-skills.js.map +1 -1
  53. package/dist/cache-service.js +1 -1
  54. package/dist/cache-service.js.map +1 -1
  55. package/dist/channel/adapters/discord.d.ts +2 -0
  56. package/dist/channel/adapters/discord.js +40 -5
  57. package/dist/channel/adapters/discord.js.map +1 -1
  58. package/dist/channel/adapters/telegram.d.ts +6 -0
  59. package/dist/channel/adapters/telegram.js +54 -14
  60. package/dist/channel/adapters/telegram.js.map +1 -1
  61. package/dist/channel/mcp-server.js +10 -1
  62. package/dist/channel/mcp-server.js.map +1 -1
  63. package/dist/channel/mcp-tools.js +2 -2
  64. package/dist/channel/mcp-tools.js.map +1 -1
  65. package/dist/channel/message-queue.js.map +1 -1
  66. package/dist/channel/sticker-download.js.map +1 -1
  67. package/dist/channel/tool-router.js.map +1 -1
  68. package/dist/classic-channel-manager.js +3 -2
  69. package/dist/classic-channel-manager.js.map +1 -1
  70. package/dist/cli-entry.js.map +1 -1
  71. package/dist/cli-flags.js.map +1 -1
  72. package/dist/cli.d.ts +1 -0
  73. package/dist/cli.js +44 -9
  74. package/dist/cli.js.map +1 -1
  75. package/dist/command-table.js.map +1 -1
  76. package/dist/compact-watch.js.map +1 -1
  77. package/dist/completion-install.js.map +1 -1
  78. package/dist/completion.js.map +1 -1
  79. package/dist/config-validator.js +22 -6
  80. package/dist/config-validator.js.map +1 -1
  81. package/dist/config.js +8 -2
  82. package/dist/config.js.map +1 -1
  83. package/dist/connection-secrets.js.map +1 -1
  84. package/dist/context-guardian.js.map +1 -1
  85. package/dist/coredump-filter.js.map +1 -1
  86. package/dist/cpu-profile.js.map +1 -1
  87. package/dist/cross-instance-envelope.js.map +1 -1
  88. package/dist/daemon.d.ts +3 -0
  89. package/dist/daemon.js +76 -4
  90. package/dist/daemon.js.map +1 -1
  91. package/dist/daily-summary.js.map +1 -1
  92. package/dist/darwin-memory.js.map +1 -1
  93. package/dist/dashboard-live.d.ts +8 -0
  94. package/dist/dashboard-live.js +35 -0
  95. package/dist/dashboard-live.js.map +1 -0
  96. package/dist/dashboard-permissions.d.ts +69 -0
  97. package/dist/dashboard-permissions.js +138 -0
  98. package/dist/dashboard-permissions.js.map +1 -0
  99. package/dist/decision-list.js.map +1 -1
  100. package/dist/delivery-consumed-watch.js.map +1 -1
  101. package/dist/delivery-outbox.js.map +1 -1
  102. package/dist/delivery-outbox.test.js.map +1 -1
  103. package/dist/delivery-reconciliation.test.js.map +1 -1
  104. package/dist/diagnostic-redaction.js.map +1 -1
  105. package/dist/doctor.d.ts +3 -0
  106. package/dist/doctor.js +10 -0
  107. package/dist/doctor.js.map +1 -1
  108. package/dist/e2e-isolation.js.map +1 -1
  109. package/dist/event-log.js.map +1 -1
  110. package/dist/export-import.js +3 -2
  111. package/dist/export-import.js.map +1 -1
  112. package/dist/fleet-control-audit.js.map +1 -1
  113. package/dist/fleet-label.js.map +1 -1
  114. package/dist/fleet-lock.js +3 -2
  115. package/dist/fleet-lock.js.map +1 -1
  116. package/dist/fleet-manager.d.ts +84 -7
  117. package/dist/fleet-manager.js +562 -158
  118. package/dist/fleet-manager.js.map +1 -1
  119. package/dist/fleet-process-handlers.js +3 -2
  120. package/dist/fleet-process-handlers.js.map +1 -1
  121. package/dist/fleet-startup.d.ts +14 -0
  122. package/dist/fleet-startup.js +30 -0
  123. package/dist/fleet-startup.js.map +1 -0
  124. package/dist/fleet-traffic.js.map +1 -1
  125. package/dist/forged-envelope-scan.d.ts +0 -20
  126. package/dist/forged-envelope-scan.js +3 -2
  127. package/dist/forged-envelope-scan.js.map +1 -1
  128. package/dist/full-restart.js.map +1 -1
  129. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
  130. package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
  131. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +43 -21
  132. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +52 -23
  133. package/dist/host-memory.js.map +1 -1
  134. package/dist/idle-gate-log.d.ts +19 -0
  135. package/dist/idle-gate-log.js +15 -0
  136. package/dist/idle-gate-log.js.map +1 -0
  137. package/dist/instance-config-impact.js +6 -0
  138. package/dist/instance-config-impact.js.map +1 -1
  139. package/dist/instance-lifecycle.js +1 -1
  140. package/dist/instance-lifecycle.js.map +1 -1
  141. package/dist/instructions.d.ts +2 -0
  142. package/dist/instructions.js +6 -2
  143. package/dist/instructions.js.map +1 -1
  144. package/dist/kiro-compact-pane.js.map +1 -1
  145. package/dist/kiro-db-reader.js.map +1 -1
  146. package/dist/kiro-engine-status.js.map +1 -1
  147. package/dist/kiro-log-gate.js.map +1 -1
  148. package/dist/kiro-transcript-lane.js.map +1 -1
  149. package/dist/locale.js +40 -6
  150. package/dist/locale.js.map +1 -1
  151. package/dist/log-service.js.map +1 -1
  152. package/dist/log-steps.js.map +1 -1
  153. package/dist/logger.js +3 -2
  154. package/dist/logger.js.map +1 -1
  155. package/dist/login-controller.js.map +1 -1
  156. package/dist/login-manager.js.map +1 -1
  157. package/dist/login-shell.js.map +1 -1
  158. package/dist/memory-pressure.js.map +1 -1
  159. package/dist/muse-usage-relay.js.map +1 -1
  160. package/dist/needs-you-hub.js.map +1 -1
  161. package/dist/needs-you-live.js.map +1 -1
  162. package/dist/network-family.js.map +1 -1
  163. package/dist/operator-control-paths.js +3 -4
  164. package/dist/operator-control-paths.js.map +1 -1
  165. package/dist/outbound-handlers.js.map +1 -1
  166. package/dist/outbound-schemas.d.ts +68 -68
  167. package/dist/outbound-schemas.js +1 -1
  168. package/dist/outbound-schemas.js.map +1 -1
  169. package/dist/outbox-open-error.js.map +1 -1
  170. package/dist/pane-context-cache.js.map +1 -1
  171. package/dist/paths.js.map +1 -1
  172. package/dist/private-dir.js.map +1 -1
  173. package/dist/probe-worker-pool.js.map +1 -1
  174. package/dist/profile-control.js.map +1 -1
  175. package/dist/prompt-file-ref.js.map +1 -1
  176. package/dist/provider-probe.js.map +1 -1
  177. package/dist/provider-secret-registry.js.map +1 -1
  178. package/dist/public-link-auto-open.d.ts +32 -0
  179. package/dist/public-link-auto-open.js +86 -0
  180. package/dist/public-link-auto-open.js.map +1 -0
  181. package/dist/public-link-owner.d.ts +10 -0
  182. package/dist/public-link-owner.js +36 -0
  183. package/dist/public-link-owner.js.map +1 -0
  184. package/dist/public-link-progress.js.map +1 -1
  185. package/dist/public-preview.js.map +1 -1
  186. package/dist/public-web-gateway.js +7 -2
  187. package/dist/public-web-gateway.js.map +1 -1
  188. package/dist/public-web-link.d.ts +35 -1
  189. package/dist/public-web-link.js +142 -43
  190. package/dist/public-web-link.js.map +1 -1
  191. package/dist/quickstart-api.js.map +1 -1
  192. package/dist/quickstart.d.ts +5 -0
  193. package/dist/quickstart.js +25 -16
  194. package/dist/quickstart.js.map +1 -1
  195. package/dist/reply-buttons-controller.js.map +1 -1
  196. package/dist/reply-dedup.js.map +1 -1
  197. package/dist/resource-report.js.map +1 -1
  198. package/dist/restart-guard.js.map +1 -1
  199. package/dist/restart-progress.js.map +1 -1
  200. package/dist/rollout-index.js.map +1 -1
  201. package/dist/runtime-cpu-profile.js.map +1 -1
  202. package/dist/scheduler/db.js.map +1 -1
  203. package/dist/scheduler/scheduler.js.map +1 -1
  204. package/dist/scheduler/task-board-text.js +16 -11
  205. package/dist/scheduler/task-board-text.js.map +1 -1
  206. package/dist/scheduler/task-board.d.ts +39 -11
  207. package/dist/scheduler/task-board.js +123 -45
  208. package/dist/scheduler/task-board.js.map +1 -1
  209. package/dist/scheduler/types.d.ts +1 -1
  210. package/dist/sd-notify.js.map +1 -1
  211. package/dist/search/search-index.js.map +1 -1
  212. package/dist/secret-file.js.map +1 -1
  213. package/dist/secret-store.js.map +1 -1
  214. package/dist/service-installer.d.ts +7 -1
  215. package/dist/service-installer.js +13 -1
  216. package/dist/service-installer.js.map +1 -1
  217. package/dist/service-plan.d.ts +33 -4
  218. package/dist/service-plan.js +177 -8
  219. package/dist/service-plan.js.map +1 -1
  220. package/dist/session-oom.d.ts +21 -0
  221. package/dist/session-oom.js +139 -0
  222. package/dist/session-oom.js.map +1 -0
  223. package/dist/settings-api.js +3 -1
  224. package/dist/settings-api.js.map +1 -1
  225. package/dist/settings-baseline.js.map +1 -1
  226. package/dist/settings-change.d.ts +5 -0
  227. package/dist/settings-change.js +71 -1
  228. package/dist/settings-change.js.map +1 -1
  229. package/dist/settings-confirmation.d.ts +4 -0
  230. package/dist/settings-confirmation.js +2 -0
  231. package/dist/settings-confirmation.js.map +1 -1
  232. package/dist/settings-control.js.map +1 -1
  233. package/dist/settings-effect.js +4 -1
  234. package/dist/settings-effect.js.map +1 -1
  235. package/dist/settings-http-confirmation.js +1 -0
  236. package/dist/settings-http-confirmation.js.map +1 -1
  237. package/dist/settings-instance-patch.js +13 -1
  238. package/dist/settings-instance-patch.js.map +1 -1
  239. package/dist/settings-transaction.js.map +1 -1
  240. package/dist/setup-auth.js.map +1 -1
  241. package/dist/setup-host.d.ts +5 -1
  242. package/dist/setup-host.js +8 -1
  243. package/dist/setup-host.js.map +1 -1
  244. package/dist/setup-wizard.js +5 -2
  245. package/dist/setup-wizard.js.map +1 -1
  246. package/dist/spawn-gate.d.ts +4 -0
  247. package/dist/spawn-gate.js +15 -5
  248. package/dist/spawn-gate.js.map +1 -1
  249. package/dist/start-attempt.d.ts +1 -0
  250. package/dist/start-attempt.js +18 -0
  251. package/dist/start-attempt.js.map +1 -0
  252. package/dist/start-failure-notice.d.ts +4 -0
  253. package/dist/start-failure-notice.js +20 -0
  254. package/dist/start-failure-notice.js.map +1 -0
  255. package/dist/start-failure.d.ts +147 -0
  256. package/dist/start-failure.js +281 -0
  257. package/dist/start-failure.js.map +1 -0
  258. package/dist/startup-memory-deadline.d.ts +23 -0
  259. package/dist/startup-memory-deadline.js +55 -0
  260. package/dist/startup-memory-deadline.js.map +1 -0
  261. package/dist/statusline-watcher.js.map +1 -1
  262. package/dist/storm-window.js.map +1 -1
  263. package/dist/systemd-peer-bus.js.map +1 -1
  264. package/dist/target-queue-worker.js.map +1 -1
  265. package/dist/task-board-summary.js.map +1 -1
  266. package/dist/task-board-tool.js +3 -2
  267. package/dist/task-board-tool.js.map +1 -1
  268. package/dist/task-board-waker.js +8 -4
  269. package/dist/task-board-waker.js.map +1 -1
  270. package/dist/tips.js.map +1 -1
  271. package/dist/tmux-control.js.map +1 -1
  272. package/dist/tmux-exec-helper.js.map +1 -1
  273. package/dist/tmux-manager.d.ts +1 -0
  274. package/dist/tmux-manager.js +2 -1
  275. package/dist/tmux-manager.js.map +1 -1
  276. package/dist/tmux-read.js +1 -1
  277. package/dist/tmux-read.js.map +1 -1
  278. package/dist/tool-permissions.js.map +1 -1
  279. package/dist/tool-progress.js.map +1 -1
  280. package/dist/topic-archiver.js.map +1 -1
  281. package/dist/topic-commands.js.map +1 -1
  282. package/dist/topic-link.js.map +1 -1
  283. package/dist/transcript-delta-reader.js.map +1 -1
  284. package/dist/transcript-jsonl.js.map +1 -1
  285. package/dist/transcript-monitor.js.map +1 -1
  286. package/dist/transcript-sources.js.map +1 -1
  287. package/dist/tunnel/cloudflared-install.js.map +1 -1
  288. package/dist/tunnel/cloudflared.js.map +1 -1
  289. package/dist/tunnel/lease.js +3 -2
  290. package/dist/tunnel/lease.js.map +1 -1
  291. package/dist/tunnel/manager.js.map +1 -1
  292. package/dist/tunnel/public-fetch.js.map +1 -1
  293. package/dist/tunnel/purpose-lane.js.map +1 -1
  294. package/dist/tunnel/types.js.map +1 -1
  295. package/dist/types.d.ts +22 -1
  296. package/dist/ui/chat-render.js +7 -17
  297. package/dist/ui/chat-store.js +171 -61
  298. package/dist/ui/chat-strings.js +9 -4
  299. package/dist/ui/chat-thread.js +48 -1
  300. package/dist/ui/fleet-canvas-layout.js +3 -0
  301. package/dist/ui/fleet-canvas.js +23 -20
  302. package/dist/ui/fleet-office-editor.js +149 -0
  303. package/dist/ui/fleet-office-model.js +358 -0
  304. package/dist/ui/fleet-office-pack.js +199 -0
  305. package/dist/ui/fleet-office-skin.js +474 -0
  306. package/dist/ui/fleet-office-store.js +49 -0
  307. package/dist/ui/fleet-office-zones.js +177 -0
  308. package/dist/ui/fleet-office.js +434 -0
  309. package/dist/ui/fleet-org.js +18 -5
  310. package/dist/ui/guest-refused.html +41 -0
  311. package/dist/ui/image-lightbox.js +9 -3
  312. package/dist/ui/panel-chat.js +35 -22
  313. package/dist/ui/panel-settings.js +24 -7
  314. package/dist/ui/settings-dialogs.js +11 -2
  315. package/dist/ui/settings-model.js +20 -0
  316. package/dist/ui/settings-strings.js +36 -0
  317. package/dist/ui/settings-web-authority.js +7 -2
  318. package/dist/ui/shared/agend-auth.js +2 -0
  319. package/dist/ui/shared/app-i18n.js +2 -2
  320. package/dist/ui/shared/app-prefs.js +2 -0
  321. package/dist/ui/shared/app-shell.js +62 -9
  322. package/dist/ui/shared/app-store.js +1 -0
  323. package/dist/ui/shared/app-stream.js +2 -1
  324. package/dist/ui/shared/app.css +164 -15
  325. package/dist/ui/shared/guest-refused.js +35 -0
  326. package/dist/ui/shared/header-tools.js +2 -2
  327. package/dist/ui/shared/instance-nav.js +25 -8
  328. package/dist/ui/shared/tokens.css +4 -0
  329. package/dist/ui/shared/ui-dialog.js +2 -2
  330. package/dist/ui/shared/usage-dialog.js +152 -6
  331. package/dist/ui/shared/view-strings.js +20 -0
  332. package/dist/ui/shared/web-authorize.js +3 -1
  333. package/dist/update-check.js.map +1 -1
  334. package/dist/update-dispatch.js.map +1 -1
  335. package/dist/update-install.js.map +1 -1
  336. package/dist/update-launch.js.map +1 -1
  337. package/dist/update-marker.js.map +1 -1
  338. package/dist/update-preflight.js.map +1 -1
  339. package/dist/update-progress.d.ts +3 -1
  340. package/dist/update-progress.js +3 -1
  341. package/dist/update-progress.js.map +1 -1
  342. package/dist/usage/format-rich.js.map +1 -1
  343. package/dist/usage/providers.d.ts +8 -0
  344. package/dist/usage/providers.js +6 -2
  345. package/dist/usage/providers.js.map +1 -1
  346. package/dist/usage/statusline-usage.js.map +1 -1
  347. package/dist/usage/usage-api.js +2 -1
  348. package/dist/usage/usage-api.js.map +1 -1
  349. package/dist/usage-report.js.map +1 -1
  350. package/dist/usage-rollup.js +1 -1
  351. package/dist/usage-rollup.js.map +1 -1
  352. package/dist/user-systemd.js.map +1 -1
  353. package/dist/view-api.js +1 -1
  354. package/dist/view-api.js.map +1 -1
  355. package/dist/wake-coordinator.js.map +1 -1
  356. package/dist/web-ambient.js.map +1 -1
  357. package/dist/web-api.d.ts +19 -2
  358. package/dist/web-api.js +180 -28
  359. package/dist/web-api.js.map +1 -1
  360. package/dist/web-app-icon.js.map +1 -1
  361. package/dist/web-appearance-policy.js.map +1 -1
  362. package/dist/web-appearance-tools.js.map +1 -1
  363. package/dist/web-appearance-view.js.map +1 -1
  364. package/dist/web-appearance.js.map +1 -1
  365. package/dist/web-auth.js +5 -4
  366. package/dist/web-auth.js.map +1 -1
  367. package/dist/web-backend-login.js.map +1 -1
  368. package/dist/web-card.js.map +1 -1
  369. package/dist/web-channel-echo.js.map +1 -1
  370. package/dist/web-chat-history.d.ts +41 -11
  371. package/dist/web-chat-history.js +93 -22
  372. package/dist/web-chat-history.js.map +1 -1
  373. package/dist/web-commands.js.map +1 -1
  374. package/dist/web-effect-bundle.d.ts +2 -2
  375. package/dist/web-effect-library.js.map +1 -1
  376. package/dist/web-effect-playback.js.map +1 -1
  377. package/dist/web-effect-profiles.d.ts +16 -16
  378. package/dist/web-effect-profiles.js +3 -2
  379. package/dist/web-effect-profiles.js.map +1 -1
  380. package/dist/web-effect-service.js.map +1 -1
  381. package/dist/web-effect-tools.js.map +1 -1
  382. package/dist/web-effect-trials.js.map +1 -1
  383. package/dist/web-effect-upload.js.map +1 -1
  384. package/dist/web-effect.d.ts +7 -7
  385. package/dist/web-effect.js.map +1 -1
  386. package/dist/web-experimental.js.map +1 -1
  387. package/dist/web-guest-access.js +1 -1
  388. package/dist/web-guest-access.js.map +1 -1
  389. package/dist/web-guest-rooms.js.map +1 -1
  390. package/dist/web-host-guard.js.map +1 -1
  391. package/dist/web-icons.js.map +1 -1
  392. package/dist/web-identity-color.js.map +1 -1
  393. package/dist/web-images.js.map +1 -1
  394. package/dist/web-libavoid.js.map +1 -1
  395. package/dist/web-login.js.map +1 -1
  396. package/dist/web-mentions.js.map +1 -1
  397. package/dist/web-mod-agent.js.map +1 -1
  398. package/dist/web-mod-store.js.map +1 -1
  399. package/dist/web-office-store.d.ts +104 -0
  400. package/dist/web-office-store.js +236 -0
  401. package/dist/web-office-store.js.map +1 -0
  402. package/dist/web-owner-css-store.js.map +1 -1
  403. package/dist/web-owner-css.js.map +1 -1
  404. package/dist/web-png.js.map +1 -1
  405. package/dist/web-prefs.js +2 -0
  406. package/dist/web-prefs.js.map +1 -1
  407. package/dist/web-profile.js.map +1 -1
  408. package/dist/web-reactions.d.ts +2 -1
  409. package/dist/web-request-context.d.ts +5 -0
  410. package/dist/web-request-context.js.map +1 -1
  411. package/dist/web-returning-device.js +1 -1
  412. package/dist/web-returning-device.js.map +1 -1
  413. package/dist/web-schedules.js.map +1 -1
  414. package/dist/web-send-dedupe.d.ts +36 -0
  415. package/dist/web-send-dedupe.js +69 -0
  416. package/dist/web-send-dedupe.js.map +1 -0
  417. package/dist/web-session-authorization.js.map +1 -1
  418. package/dist/web-session.js +1 -1
  419. package/dist/web-session.js.map +1 -1
  420. package/dist/web-settings-patch.d.ts +1 -0
  421. package/dist/web-settings-patch.js +6 -4
  422. package/dist/web-settings-patch.js.map +1 -1
  423. package/dist/web-settings-tools.js.map +1 -1
  424. package/dist/web-terminal-http.js.map +1 -1
  425. package/dist/web-terminal.js.map +1 -1
  426. package/dist/web-theme-store.js.map +1 -1
  427. package/dist/web-upload.d.ts +5 -1
  428. package/dist/web-upload.js +6 -4
  429. package/dist/web-upload.js.map +1 -1
  430. package/dist/web-widget.js.map +1 -1
  431. package/dist/webhook-emitter.js.map +1 -1
  432. package/dist/windows-shortcuts.js +1 -1
  433. package/dist/windows-shortcuts.js.map +1 -1
  434. package/dist/workflow-templates/default.md +1 -1
  435. package/dist/ws-server.js.map +1 -1
  436. package/dist/yaml-shared-nodes.js.map +1 -1
  437. package/launcher/agend-install.cjs +281 -6
  438. package/launcher/private-home.cjs +43 -0
  439. package/package.json +3 -2
  440. 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" }));
@@ -187,6 +189,10 @@ function useChatTick(name, kinds) {
187
189
 
188
190
  /** #1722: the chat's title — the name that leads, the other on hover. */
189
191
  const headerName = ({ primary, secondary }) => (secondary ? html`<span title=${secondary}>${primary}</span>` : primary);
192
+ /** #2478: who the composer writes to — the display name, else the instance's name (a long id made the box two lines). */
193
+ const composerLabel = (inst, name) => (inst && typeof inst.display_name === "string" && inst.display_name.trim()) || name;
194
+ /** #2478: a touch screen (a coarse pointer) — Return is a new line there and only the Send button sends. Asked each time. */
195
+ const touchScreen = () => typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
190
196
 
191
197
  export function ChatPanel({ route, navKey }) {
192
198
  const lease = useLease(navKey);
@@ -425,6 +431,9 @@ function threadEntry(name, doc) {
425
431
  canTryEffect: x => getAppEffects()?.canTry(x) === true,
426
432
  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
433
  clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
434
+ retryOutgoing: (clientId) => store.retryOutgoing(name, clientId), // a failed send's Retry and Delete
435
+ deleteOutgoing: (clientId) => store.deleteOutgoing(name, clientId),
436
+ putBackOutgoing: (clientId) => { recordEdit(composerField()); store.putBackOutgoing(name, clientId); }, // undoable, as typing
428
437
  // #2272: reply to a message (the bar above the composer), and show the one a reply quotes.
429
438
  startReply: (x) => { store.startReply(name, x); doc.getElementById("msgIn")?.focus(); },
430
439
  findQuoted: (key) => store.findQuoted(name, key),
@@ -446,12 +455,13 @@ function follow(name, thread) {
446
455
  // #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
447
456
  const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
448
457
  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] || [])));
458
+ if (emojiIndex) offs.push(emojiIndex.subscribe(() => thread.render(store.threadRows(name))));
459
+ if (mentionIndex) offs.push(mentionIndex.subscribe(() => thread.render(store.threadRows(name))));
451
460
  wantEmojis(store.state.msgs[name]);
452
461
  offs.push(store.subscribe((instance, kind) => {
453
462
  if (instance !== name) return;
454
- if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
463
+ if (kind === "msgs") { thread.render(store.threadRows(name)); wantEmojis(store.state.msgs[name]); }
464
+ else if (kind === "progress") thread.progress(); // an upload moved: its bar, in place
455
465
  else if (kind === "sent") thread.jumpLatest();
456
466
  }));
457
467
  offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
@@ -461,10 +471,10 @@ function follow(name, thread) {
461
471
  const next = getAppEffects();
462
472
  if (next === trialOwner) return;
463
473
  trialOff?.(); trialOff = null; trialOwner = next;
464
- trialOff = next?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? null;
474
+ trialOff = next?.subscribeTrial(() => thread.render(store.threadRows(name))) ?? null;
465
475
  };
466
476
  watchTrial();
467
- offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.state.msgs[name] || []); }));
477
+ offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.threadRows(name)); }));
468
478
  offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
469
479
  offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
470
480
  return () => { for (const off of offs) off(); };
@@ -493,7 +503,7 @@ function Thread({ name, th }) {
493
503
  thread.attach(sc);
494
504
  th.current = thread;
495
505
  // 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 });
506
+ thread.render(store.threadRows(name), { restore: store.state.scrollMemo[name] ?? null });
497
507
  if (kept) thread.refreshCards();
498
508
  const unfollow = follow(name, thread);
499
509
  const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
@@ -546,15 +556,10 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
546
556
  <${WorkBar} name=${name} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${workingLabel} />
547
557
  <${CommandCard} name=${name} lease=${lease} />
548
558
  <${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
559
  ${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
- <${Composer} name=${name} lease=${lease} busy=${R().isBusy(exec)} />
560
+ <${Composer} name=${name} label=${composerLabel(inst, name)} lease=${lease} busy=${R().isBusy(exec)} />
556
561
  <div class="composer-row">${guestPage() ? html`<span class="guest-note" title=${t("chat.guestNote")}>${t("chat.guestNote")}</span>` : html`<${ComposerAuth} />`}<span
557
- class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t("chat.composerHint")}</span></div>
562
+ class="hint" title=${t(guestPage() ? "chat.guestMore" : "chat.composerMore")}>${t(touchScreen() ? "chat.composerHintTouch" : "chat.composerHint")}</span></div>
558
563
  </div></div>`;
559
564
  }
560
565
 
@@ -637,7 +642,7 @@ function replySnippet(r) {
637
642
  return text || (r.media === "photo" ? t("chat.replyPhoto") : r.media === "document" ? t("chat.replyFile") : "");
638
643
  }
639
644
 
640
- function Composer({ name, lease, busy }) {
645
+ function Composer({ name, label = name, lease, busy }) {
641
646
  const s = store.state;
642
647
  const input = useRef(null), fileIn = useRef(null);
643
648
  const [, tick] = useState(0);
@@ -652,8 +657,13 @@ function Composer({ name, lease, busy }) {
652
657
  const guest = guestPage();
653
658
  const palette = guest || files.length || closedFor === value ? null : paletteFor(value);
654
659
  const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
655
- // Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300).
656
- useLayoutEffect(() => { const el = input.current; if (!el) return; el.style.height = "auto"; el.style.height = `${Math.min(el.scrollHeight, 240)}px`; }, [value]);
660
+ // Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300). Empty, it is one line
661
+ // (#2478 — a long name's placeholder made the box two lines tall).
662
+ useLayoutEffect(() => {
663
+ const el = input.current; if (!el) return;
664
+ el.style.height = "auto";
665
+ if (value) el.style.height = `${Math.min(el.scrollHeight, 240)}px`;
666
+ }, [value]);
657
667
  useEffect(() => { if (input.current) input.current.focus(); }, [name]);
658
668
  useEffect(() => { refreshTourSpot(); });
659
669
  const setValue = (v) => { store.setDraft(name, v); setSel(0); tick(n => n + 1); };
@@ -688,7 +698,7 @@ function Composer({ name, lease, busy }) {
688
698
  if (pick && e.key === "Tab" && !e.shiftKey) { e.preventDefault(); complete(pick); return; }
689
699
  if (palette && e.key === "Escape" && !e.isComposing) { e.preventDefault(); setClosedFor(value); return; }
690
700
  if (reply && e.key === "Escape" && !e.isComposing) { e.preventDefault(); store.clearReply(name); return; } // #2272
691
- if (R().composerKey(e) === "send") { e.preventDefault(); submit(); }
701
+ if (R().composerKey(e, { touch: touchScreen() }) === "send") { e.preventDefault(); submit(); }
692
702
  };
693
703
  const onInput = (e) => { setValue(e.target.value); };
694
704
  // Files in a paste are attached; a very long text paste goes as a text file — only once it is really attached.
@@ -716,12 +726,15 @@ function Composer({ name, lease, busy }) {
716
726
  <button id="attachBtn" type="button" class="icon-btn" title=${t("chat.attachTitle")} aria-label=${t("chat.attachTitle")} onClick=${() => fileIn.current && fileIn.current.click()}><${Icon} name="attach" /></button>
717
727
  <input ref=${fileIn} type="file" multiple hidden
718
728
  onChange=${(e) => { store.addFiles(name, e.target.files); e.target.value = ""; }} />
719
- <textarea id="msgIn" ref=${input} rows="1" value=${value} placeholder=${t("chat.composerPlaceholder", name)} aria-label=${t("chat.composerPlaceholder", name)}
720
- autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
721
- aria-activedescendant=${pick ? optId(pick) : undefined} onInput=${onInput} onKeyDown=${onKeyDown} onPaste=${onPaste}></textarea>
729
+ ${/* #2478: the placeholder is drawn beside the box, one line ending in "…" — a textarea's own placeholder is clipped,
730
+ never ellipsized, and a long name wrapped it to two lines. The box keeps the words as its accessible name. */ ""}
731
+ <span class="composer-field"><textarea id="msgIn" ref=${input} rows="1" value=${value} aria-label=${t("chat.composerPlaceholder", label)}
732
+ enterkeyhint=${touchScreen() ? "enter" : undefined} autocomplete="off" aria-controls=${palette ? "cmdPalette" : undefined} aria-expanded=${palette ? "true" : "false"}
733
+ aria-activedescendant=${pick ? optId(pick) : undefined} onInput=${onInput} onKeyDown=${onKeyDown} onPaste=${onPaste}></textarea>${value ? null
734
+ : html`<span class="composer-ph" aria-hidden="true">${t("chat.composerPlaceholder", label)}</span>`}</span>
722
735
  <button id="stopBtn" type="button" class="btn btn-stop" hidden=${!busy} title=${t("chat.stopReplyTitle")}
723
736
  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]}
737
+ <button id="sendBtn" type="button" class="btn btn-primary btn-send" hidden=${busy && !content} disabled=${!content}
725
738
  onMouseDown=${(e) => e.preventDefault()}
726
739
  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
740
  </div></div>`;
@@ -25,8 +25,8 @@ 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
31
  toolSetDefault, visibilityDefault, worstImpact,
32
32
  } from "./settings-model.js";
@@ -678,6 +678,7 @@ function General({ ctx }) {
678
678
  backend: d.backend || "claude-code", model: d.model || "", locale: d.locale || "", auto: String(d.auto_pause_after ?? 0),
679
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",
@@ -701,12 +702,15 @@ function General({ ctx }) {
701
702
  backend: f.backend, model: f.model || undefined, locale: f.locale || null, auto_pause_after: num(f.auto),
702
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
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">
@@ -814,7 +823,7 @@ function WebSection({ ctx, staged }) {
814
823
  echo: web.echo_to_channel ?? true,
815
824
  log: web.log_tab === true, // #1218: off by default
816
825
  usagePub: web.usage_report_on_public_link === true, // #2122: off by default
817
- 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 },
818
827
  session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
819
828
  terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
820
829
  }), []);
@@ -829,7 +838,7 @@ function WebSection({ ctx, staged }) {
829
838
  return {
830
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),
831
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),
832
- 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),
833
842
  abs: text(pick(session, "absolute_hours", original.session.absolute_hours)), idle: text(pick(session, "idle_hours", original.session.idle_hours)),
834
843
  termOn: pick(terminal, "enabled", original.terminal.enabled) !== false, termTtl: text(pick(terminal, "ttl_minutes", original.terminal.ttl_minutes)),
835
844
  };
@@ -842,10 +851,15 @@ function WebSection({ ctx, staged }) {
842
851
  // The defaults (#1670: 48 h / 48 h, DEFAULT_SESSION_POLICY); an idle time left blank follows a shorter session, as
843
852
  // the store and the validator apply it — so only an idle time that is typed can exceed the session length.
844
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() !== "";
845
856
  const errors = {
846
857
  hostname: f.hostname && !/^(?:https:\/\/)?[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+\/?$/.test(f.hostname) ? "namedHostnameInvalid" : "",
847
858
  originPort: !Number.isInteger(Number(f.originPort)) || Number(f.originPort) < 1024 || Number(f.originPort) > 65535 ? "namedPortInvalid" : "",
848
- 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" : "",
849
863
  abs: hours(f.abs),
850
864
  idle: hours(f.idle) || (!hours(f.abs) && idle > abs ? "sessionIdleOver" : ""),
851
865
  termTtl: value(f.termTtl) !== null && (!Number.isInteger(value(f.termTtl)) || value(f.termTtl) < 1 || value(f.termTtl) > 20) ? "webTerminalTtlRange" : "",
@@ -858,7 +872,7 @@ function WebSection({ ctx, staged }) {
858
872
  // reference adopts the standard env key; mounting or editing an unrelated field never rewrites credentials.
859
873
  const tokenEnv = f.hostname === original.pub.hostname ? original.pub.tunnel_token_env
860
874
  : f.hostname ? original.pub.tunnel_token_env || "AGEND_CLOUDFLARE_TUNNEL_TOKEN" : "";
861
- 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);
862
876
  const sessionPatch = changedFields({ absolute_hours: value(f.abs), idle_hours: value(f.idle) }, original.session);
863
877
  // The checkbox shows the default (on) for an unset key: only a real toggle is written; on again = back to the default.
864
878
  const terminalPatch = changedFields({ enabled: f.termOn === (original.terminal.enabled !== false) ? original.terminal.enabled : f.termOn ? null : false,
@@ -932,9 +946,12 @@ function WebSection({ ctx, staged }) {
932
946
  </div>
933
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>
934
948
  <div class="grid2">
935
- <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>
936
950
  <div class="field"><label for="publicWebProtocol">${tn("publicProtocol")}</label><${Select} id="publicWebProtocol" value=${f.protocol} onChange=${set("protocol")} options=${["http2", "quic", "auto"]} /></div>
937
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}
938
955
  </section>
939
956
  </fieldset>
940
957
  ${overPublicLink ? null : html`<${PublicTunnelToken} /><${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />`}
@@ -17,9 +17,9 @@ 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,
22
+ chLabel, dashboardDefault, hasOwn, impactOf, newConnectionImpact, nonNegative, nonNegativeInteger, positive, replyGuardSupported, sameValue, visibilityDefault,
23
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. */
@@ -287,6 +287,15 @@ export function AgentDialog({ name, inst, ctx, onClose }) {
287
287
  <${Select} id=${`ag-${k}`} value=${shown(k, f[k], dflt)} disabled=${f.inherit[k]} onChange=${set(k)}
288
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)}
@@ -38,6 +38,23 @@ export function changedFields(next, baseline) {
38
38
  return Object.fromEntries(Object.entries(next).filter(([key, value]) => !sameValue(value, baseline[key])));
39
39
  }
40
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
+
41
58
  /** #1302: cross-instance notices. Unset, or a value the fleet does not know, means full. */
42
59
  export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaults && defaults.cross_instance_visibility) ? defaults.cross_instance_visibility : "full");
43
60
 
@@ -155,6 +172,7 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
155
172
  model_failover: form.inherit.model_failover ? null : (form.model_failover.trim() ? [form.model_failover.trim()] : []),
156
173
  display_name: form.inherit.display_name ? null : form.display_name.trim(),
157
174
  status_emojis: form.status_emojis,
175
+ dashboard: dashboardPatchValue(form.dashboard),
158
176
  };
159
177
  return changedFields(next, {
160
178
  backend: inst.backend || defaults.backend || "claude-code",
@@ -177,6 +195,7 @@ export function agentPatch(form, inst, defaults, visibilityBaseline, statusEmoji
177
195
  model_failover: hasOwn(inst, "model_failover") ? inst.model_failover : null,
178
196
  display_name: hasOwn(inst, "display_name") ? inst.display_name : null,
179
197
  status_emojis: statusEmojisBaseline,
198
+ dashboard: dashboardPatchValue(dashboardForm(inst)),
180
199
  });
181
200
  }
182
201
 
@@ -207,6 +226,7 @@ export function agentForm(inst, defaults, name = "") {
207
226
  model_failover: Array.isArray(inst.model_failover) ? (inst.model_failover[0] || "") : ((defaults.model_failover && defaults.model_failover[0]) || ""),
208
227
  display_name: inst.display_name ?? defaults.display_name ?? "",
209
228
  status_emojis: inst.status_emojis || null,
229
+ dashboard: dashboardForm(inst),
210
230
  inherit: {
211
231
  auto_pause_after: inherit("auto_pause_after"), hang: !hasOwn(hang, "timeout_minutes"), agent_mode: inherit("agent_mode"),
212
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",
@@ -91,6 +98,15 @@ register("settings", {
91
98
  crossInstanceVisibility: "Bot-to-bot messages in topics",
92
99
  crossInstanceVisibilityHint: "full: the whole message · summary: one line · hidden: nothing. Messages are always delivered; the Mirror Topic still shows them.",
93
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.",
94
110
  replyGuardSupported: "Available for Claude Code, Codex, Muse and Kiro legacy/TUI in MCP mode.",
95
111
  replyGuardUnsupported: "Stored but inactive: {0} in {1} mode does not support reply-drop recovery.",
96
112
  logLevel: "Log level",
@@ -506,6 +522,8 @@ register("settings", {
506
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.",
507
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.",
508
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",
509
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.",
510
528
  vaHours: "An authorization lasts (hours, {0}–{1})", vaHoursRange: "Must be a number from 1 to 48",
511
529
  vaSessionsTitle: "Authorized sessions", vaSessionsNone: "No session is authorized now.", vaSessionUntil: "Authorized {0} · until {1}",
@@ -629,6 +647,13 @@ register("settings", {
629
647
  publicLink: "提供臨時公開網頁連結",
630
648
  publicLinkHint: "每次都需管理員明確啟用。Cloudflare 終止 TLS;登入者具有完整網頁管理權限。HTML 預覽會另開一條臨時通道,除非設定 web.preview_on_public_link: false。",
631
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: "需要固定網域——請取消勾選,或保留網域。",
632
657
  publicProtocol: "Tunnel 協定",
633
658
  namedHostname: "固定公開網域(選用)",
634
659
  namedOriginPort: "Named tunnel 本機連接埠",
@@ -683,6 +708,15 @@ register("settings", {
683
708
  crossInstanceVisibility: "Agent 之間的訊息在主題中",
684
709
  crossInstanceVisibilityHint: "full:完整訊息 · summary:一行摘要 · hidden:不顯示。訊息一律照常送達,Mirror Topic 也照常顯示。",
685
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 裡它的對話框另外設定。取消勾選立即生效;勾選要先確認。",
686
720
  replyGuardSupported: "Claude Code、Codex、Muse 與 Kiro legacy/TUI 的 MCP 模式可用。",
687
721
  replyGuardUnsupported: "設定會保留,但不會生效:{0} 的 {1} 模式不支援回覆遺失補救。",
688
722
  logLevel: "Log 等級",
@@ -1097,6 +1131,8 @@ register("settings", {
1097
1131
  vaEnabled: "網頁訊息帶有授權", vaEnabledHint: "預設開啟:session 要等它的帳號在 Discord 或 Telegram 上確認後才帶有授權。關閉:網頁訊息都不帶授權,已給出的授權立即視為無;關閉前給出的授權不會恢復。",
1098
1132
  vaStuckOff: "網頁授權目前是關閉的:它的狀態無法存到這台電腦上(請看 fleet 紀錄)。可以存入後會自動恢復。",
1099
1133
  vaLoopbackOnly: "只有這台電腦可以授權", vaLoopbackOnlyHint: "開啟:只有這台電腦上的瀏覽器符合,網路或 tailnet 上的不算,之前從其他地方給出的授權視為無。再關掉也不會讓那些授權恢復。",
1134
+ vaNamedLink: "也允許透過我的固定網域({0})", vaNamedLinkHint: "開啟:透過你自己的 named tunnel 固定網域登入的瀏覽器也能被授權(一樣要在私訊按「確認」)。它可從網際網路連到,Cloudflare 也看得到流量:開啟需在 General 或這台電腦上確認。關閉立即生效,透過它給出的授權都視為無。Quick tunnel 一律不行。",
1135
+ vaSessionNamed: "透過你的固定網域",
1100
1136
  vaLanWarning: "dashboard 也接受 {0} 的連線(web.allowed_hosts)。任何人只要從那裡連上、又拿到被盜的登入,就能以你的名義給出授權。除非你信任所有能連到那裡的人,否則請開啟「只有這台電腦可以授權」。",
1101
1137
  vaHours: "一次授權的有效期(小時,{0}–{1})", vaHoursRange: "必須是 1 到 48 之間的數字",
1102
1138
  vaSessionsTitle: "已授權的 session", vaSessionsNone: "目前沒有已授權的 session。", vaSessionUntil: "{0} 授權 · 到 {1}",
@@ -22,6 +22,8 @@ export function verifiedAuthorityBody(original, f) {
22
22
  if (f.enabled !== original.enabled) out.enabled = f.enabled ? null : false; // §0.1: on is the default
23
23
  if (f.loopbackOnly !== original.loopbackOnly) out.loopback_only = f.loopbackOnly ? true : null;
24
24
  if (f.hours !== original.hours) out.max_session_hours = f.hours === null ? null : f.hours;
25
+ // #2453 (§0.2): off by default; turning it on is confirmed in a General or on the host.
26
+ if (f.namedLink !== original.namedLink) out.named_link = f.namedLink ? true : null;
25
27
  return Object.keys(out).length ? out : null;
26
28
  }
27
29
 
@@ -39,7 +41,8 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
39
41
  const o = r && r.ok ? r.body : null;
40
42
  if (!o || typeof o !== "object" || !o.state || !o.hours || !Array.isArray(o.lanHosts) || !Array.isArray(o.authorized)) { setFailed(true); return; }
41
43
  const c = o.configured || {};
42
- const orig = { enabled: c.enabled !== false, loopbackOnly: c.loopback_only === true, hours: typeof c.max_session_hours === "number" ? c.max_session_hours : null };
44
+ const orig = { enabled: c.enabled !== false, loopbackOnly: c.loopback_only === true, hours: typeof c.max_session_hours === "number" ? c.max_session_hours : null,
45
+ namedLink: c.named_link === true };
43
46
  setOverview(o); setFailed(false);
44
47
  setOriginal((prev) => prev || orig);
45
48
  setF((prev) => prev || { ...orig });
@@ -78,6 +81,8 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
78
81
  <label class="check"><input id="vaLoopbackOnly" type="checkbox" checked=${f.loopbackOnly} onChange=${(e) => set({ loopbackOnly: e.target.checked })} /><span>${tn("vaLoopbackOnly")}</span></label>
79
82
  <p class="note">${tn("vaLoopbackOnlyHint")}</p>
80
83
  ${warnHosts ? html`<p class="note s-va-warn" role="status" id="vaLanWarning">${tn("vaLanWarning", overview.lanHosts.join(", "))}</p>` : null}
84
+ ${overview.namedHost || original.namedLink ? html`<label class="check"><input id="vaNamedLink" type="checkbox" checked=${f.namedLink} onChange=${(e) => set({ namedLink: e.target.checked })} /><span>${tn("vaNamedLink", overview.namedHost || "—")}</span></label>
85
+ <p class="note">${tn("vaNamedLinkHint")}</p>` : null}
81
86
  <div class="field"><label for="vaHours">${tn("vaHours", overview.hours.min, overview.hours.max)}</label>
82
87
  <input id="vaHours" type="number" min=${overview.hours.min} max=${overview.hours.max} step="any" placeholder=${tn("defaultHours", overview.hours.default)}
83
88
  class=${hoursError ? "invalid" : ""} value=${hoursText} onInput=${(e) => set({ hours: onHoursText(e.target.value) })} />
@@ -85,7 +90,7 @@ export function WebAuthorityCard({ ctx, staged, disabled = false }) {
85
90
  <h4>${tn("vaSessionsTitle")}</h4>
86
91
  ${overview.authorized.length
87
92
  ? html`<ul class="s-va-sessions" id="vaSessions">${overview.authorized.map((a) => html`<li key=${a.handle} data-handle=${a.handle}>
88
- <strong>${a.label}</strong> <span class="note">${a.account}</span><br /><span class="note">${tn("vaSessionUntil", when(a.authorizedAt), when(a.until))}</span></li>`)}</ul>`
93
+ <strong>${a.label}</strong> <span class="note">${a.account}</span>${a.named ? html` <span class="note s-va-named">${tn("vaSessionNamed")}</span>` : null}<br /><span class="note">${tn("vaSessionUntil", when(a.authorizedAt), when(a.until))}</span></li>`)}</ul>`
89
94
  : html`<p class="note" id="vaSessionsNone">${tn("vaSessionsNone")}</p>`}
90
95
  </fieldset>
91
96
  <div class="save-row"><button type="button" class="btn btn-primary" id="vaReview" disabled=${disabled || !!hoursError} onClick=${review}>${tn("vaReview")}</button>
@@ -106,6 +106,8 @@
106
106
  /** Display hint from the same server session response; Start still reauthorizes. No CSRF is exposed here. */
107
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
108
  sessionEnded() { return ended !== null; },
109
+ /** A same-origin request made outside fetch (an upload's XHR, for its progress) was answered 401: the same end. */
110
+ unauthorized() { endSession("unauthorized"); sessionEndedBanner(); },
109
111
  /** #1568: the session handle of this page's sign-in, or null when there is none. */
110
112
  sessionHandle() { return session().then((s) => (s && s.handle ? s.handle : null)); },
111
113
  async signOut() {