@songsid/agend 2.2.0-beta.9 → 2.2.0

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 (226) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-endpoint.js +3 -0
  4. package/dist/agent-endpoint.js.map +1 -1
  5. package/dist/agent-whats-new.js +7 -1
  6. package/dist/agent-whats-new.js.map +1 -1
  7. package/dist/auth-api.d.ts +3 -1
  8. package/dist/auth-api.js +15 -1
  9. package/dist/auth-api.js.map +1 -1
  10. package/dist/backend/antigravity.d.ts +33 -0
  11. package/dist/backend/antigravity.js +126 -18
  12. package/dist/backend/antigravity.js.map +1 -1
  13. package/dist/backend/claude-code.d.ts +3 -0
  14. package/dist/backend/claude-code.js +9 -0
  15. package/dist/backend/claude-code.js.map +1 -1
  16. package/dist/backend/codex-metadata.d.ts +33 -0
  17. package/dist/backend/codex-metadata.js +122 -18
  18. package/dist/backend/codex-metadata.js.map +1 -1
  19. package/dist/backend/codex.d.ts +3 -0
  20. package/dist/backend/codex.js +17 -1
  21. package/dist/backend/codex.js.map +1 -1
  22. package/dist/backend/effort-metadata.d.ts +37 -1
  23. package/dist/backend/effort-metadata.js +86 -15
  24. package/dist/backend/effort-metadata.js.map +1 -1
  25. package/dist/backend/grok.d.ts +3 -0
  26. package/dist/backend/grok.js +9 -0
  27. package/dist/backend/grok.js.map +1 -1
  28. package/dist/backend/kiro.d.ts +3 -1
  29. package/dist/backend/kiro.js +7 -3
  30. package/dist/backend/kiro.js.map +1 -1
  31. package/dist/backend/launch-effort.d.ts +61 -0
  32. package/dist/backend/launch-effort.js +170 -0
  33. package/dist/backend/launch-effort.js.map +1 -0
  34. package/dist/backend/types.d.ts +24 -0
  35. package/dist/backend/types.js +12 -1
  36. package/dist/backend/types.js.map +1 -1
  37. package/dist/backend-command.d.ts +91 -0
  38. package/dist/backend-command.js +122 -0
  39. package/dist/backend-command.js.map +1 -0
  40. package/dist/channel/adapters/discord.d.ts +12 -0
  41. package/dist/channel/adapters/discord.js +49 -2
  42. package/dist/channel/adapters/discord.js.map +1 -1
  43. package/dist/channel/adapters/telegram.d.ts +18 -0
  44. package/dist/channel/adapters/telegram.js +24 -3
  45. package/dist/channel/adapters/telegram.js.map +1 -1
  46. package/dist/channel/mcp-tools.js +2 -0
  47. package/dist/channel/mcp-tools.js.map +1 -1
  48. package/dist/channel/tool-router.d.ts +2 -0
  49. package/dist/channel/tool-router.js +1 -1
  50. package/dist/channel/tool-router.js.map +1 -1
  51. package/dist/channel/types.d.ts +16 -0
  52. package/dist/cli.js +124 -24
  53. package/dist/cli.js.map +1 -1
  54. package/dist/command-table.d.ts +2 -2
  55. package/dist/command-table.js +5 -2
  56. package/dist/command-table.js.map +1 -1
  57. package/dist/compact-watch.d.ts +86 -0
  58. package/dist/compact-watch.js +288 -0
  59. package/dist/compact-watch.js.map +1 -0
  60. package/dist/config-validator.js +39 -0
  61. package/dist/config-validator.js.map +1 -1
  62. package/dist/config.d.ts +1 -0
  63. package/dist/config.js +2 -1
  64. package/dist/config.js.map +1 -1
  65. package/dist/daemon.d.ts +9 -0
  66. package/dist/daemon.js +150 -9
  67. package/dist/daemon.js.map +1 -1
  68. package/dist/delivery-outbox.d.ts +8 -1
  69. package/dist/delivery-outbox.js +6 -2
  70. package/dist/delivery-outbox.js.map +1 -1
  71. package/dist/doctor.js +7 -0
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/fleet-context.d.ts +7 -0
  74. package/dist/fleet-manager.d.ts +158 -9
  75. package/dist/fleet-manager.js +827 -118
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/forged-envelope-scan.d.ts +4 -3
  78. package/dist/forged-envelope-scan.js +4 -3
  79. package/dist/forged-envelope-scan.js.map +1 -1
  80. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  81. package/dist/install-lock.d.ts +18 -1
  82. package/dist/install-lock.js +30 -3
  83. package/dist/install-lock.js.map +1 -1
  84. package/dist/instance-avatar.d.ts +5 -4
  85. package/dist/instance-avatar.js +12 -8
  86. package/dist/instance-avatar.js.map +1 -1
  87. package/dist/instructions.js +2 -2
  88. package/dist/instructions.js.map +1 -1
  89. package/dist/kiro-compact-pane.d.ts +38 -0
  90. package/dist/kiro-compact-pane.js +57 -0
  91. package/dist/kiro-compact-pane.js.map +1 -0
  92. package/dist/launchd-handoff.d.ts +43 -0
  93. package/dist/launchd-handoff.js +56 -0
  94. package/dist/launchd-handoff.js.map +1 -0
  95. package/dist/locale.js +202 -30
  96. package/dist/locale.js.map +1 -1
  97. package/dist/log-service.js +10 -2
  98. package/dist/log-service.js.map +1 -1
  99. package/dist/login-shell.d.ts +21 -0
  100. package/dist/login-shell.js +98 -0
  101. package/dist/login-shell.js.map +1 -0
  102. package/dist/outbound-handlers.d.ts +27 -0
  103. package/dist/outbound-handlers.js +120 -36
  104. package/dist/outbound-handlers.js.map +1 -1
  105. package/dist/outbound-schemas.d.ts +12 -0
  106. package/dist/outbound-schemas.js +15 -4
  107. package/dist/outbound-schemas.js.map +1 -1
  108. package/dist/provider-probe.d.ts +29 -0
  109. package/dist/provider-probe.js +198 -2
  110. package/dist/provider-probe.js.map +1 -1
  111. package/dist/public-web-gateway.d.ts +2 -0
  112. package/dist/public-web-gateway.js +11 -1
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/quickstart-api.d.ts +1 -1
  115. package/dist/quickstart-api.js +8 -6
  116. package/dist/quickstart-api.js.map +1 -1
  117. package/dist/quickstart.d.ts +12 -0
  118. package/dist/quickstart.js +36 -6
  119. package/dist/quickstart.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +86 -5
  121. package/dist/reply-buttons-controller.js +271 -7
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/reply-buttons.d.ts +48 -3
  124. package/dist/reply-buttons.js +105 -10
  125. package/dist/reply-buttons.js.map +1 -1
  126. package/dist/reply-polls.d.ts +85 -0
  127. package/dist/reply-polls.js +154 -0
  128. package/dist/reply-polls.js.map +1 -0
  129. package/dist/restart-guard.d.ts +6 -3
  130. package/dist/restart-guard.js +6 -3
  131. package/dist/restart-guard.js.map +1 -1
  132. package/dist/restart-progress.d.ts +0 -2
  133. package/dist/restart-progress.js +0 -2
  134. package/dist/restart-progress.js.map +1 -1
  135. package/dist/sd-notify.d.ts +22 -1
  136. package/dist/sd-notify.js +63 -10
  137. package/dist/sd-notify.js.map +1 -1
  138. package/dist/service-activation.d.ts +15 -7
  139. package/dist/service-activation.js +42 -26
  140. package/dist/service-activation.js.map +1 -1
  141. package/dist/service-installer.d.ts +24 -0
  142. package/dist/service-installer.js +49 -1
  143. package/dist/service-installer.js.map +1 -1
  144. package/dist/service-plan.d.ts +11 -0
  145. package/dist/service-plan.js +23 -0
  146. package/dist/service-plan.js.map +1 -1
  147. package/dist/systemd-peer-bus.d.ts +95 -0
  148. package/dist/systemd-peer-bus.js +430 -0
  149. package/dist/systemd-peer-bus.js.map +1 -0
  150. package/dist/systemd-runtime.d.ts +27 -1
  151. package/dist/systemd-runtime.js +69 -7
  152. package/dist/systemd-runtime.js.map +1 -1
  153. package/dist/tool-permissions.js +3 -3
  154. package/dist/tool-permissions.js.map +1 -1
  155. package/dist/topic-commands.d.ts +68 -2
  156. package/dist/topic-commands.js +141 -27
  157. package/dist/topic-commands.js.map +1 -1
  158. package/dist/transcript-jsonl.d.ts +7 -2
  159. package/dist/transcript-jsonl.js +13 -7
  160. package/dist/transcript-jsonl.js.map +1 -1
  161. package/dist/transcript-sources.js +6 -1
  162. package/dist/transcript-sources.js.map +1 -1
  163. package/dist/types.d.ts +8 -0
  164. package/dist/ui/chat-commands.js +1 -0
  165. package/dist/ui/chat-render.js +55 -10
  166. package/dist/ui/chat-store.js +69 -5
  167. package/dist/ui/chat-strings.js +28 -16
  168. package/dist/ui/chat-thread.js +132 -14
  169. package/dist/ui/chat-tour.js +17 -14
  170. package/dist/ui/panel-chat.js +16 -10
  171. package/dist/ui/panel-fleet.js +15 -2
  172. package/dist/ui/panel-log.js +4 -4
  173. package/dist/ui/panel-needs.js +10 -7
  174. package/dist/ui/panel-settings.js +58 -7
  175. package/dist/ui/settings-dialogs.js +11 -6
  176. package/dist/ui/settings-model.js +3 -1
  177. package/dist/ui/settings-strings.js +24 -10
  178. package/dist/ui/settings-token.js +7 -0
  179. package/dist/ui/settings-wizard.js +5 -3
  180. package/dist/ui/shared/app-i18n.js +1 -1
  181. package/dist/ui/shared/app-stream.js +4 -1
  182. package/dist/ui/shared/app.css +52 -10
  183. package/dist/ui/shared/header-tools.js +34 -14
  184. package/dist/ui/shared/instance-switch.js +5 -2
  185. package/dist/ui/shared/panel-view.js +15 -4
  186. package/dist/ui/shared/signin.js +15 -9
  187. package/dist/ui/shared/ui-icons.js +1 -0
  188. package/dist/ui/shared/view-strings.js +10 -2
  189. package/dist/ui/signin.html +8 -3
  190. package/dist/update-check.d.ts +31 -0
  191. package/dist/update-check.js +47 -0
  192. package/dist/update-check.js.map +1 -1
  193. package/dist/update-launch.d.ts +48 -1
  194. package/dist/update-launch.js +159 -5
  195. package/dist/update-launch.js.map +1 -1
  196. package/dist/usage/providers.d.ts +28 -8
  197. package/dist/usage/providers.js +96 -41
  198. package/dist/usage/providers.js.map +1 -1
  199. package/dist/view-api.js +26 -9
  200. package/dist/view-api.js.map +1 -1
  201. package/dist/web-api.d.ts +44 -2
  202. package/dist/web-api.js +265 -57
  203. package/dist/web-api.js.map +1 -1
  204. package/dist/web-chat-history.d.ts +26 -1
  205. package/dist/web-chat-history.js +47 -8
  206. package/dist/web-chat-history.js.map +1 -1
  207. package/dist/web-commands.d.ts +39 -3
  208. package/dist/web-commands.js +22 -5
  209. package/dist/web-commands.js.map +1 -1
  210. package/dist/web-login.d.ts +14 -0
  211. package/dist/web-login.js +4 -2
  212. package/dist/web-login.js.map +1 -1
  213. package/dist/web-prefs.d.ts +18 -0
  214. package/dist/web-prefs.js +36 -4
  215. package/dist/web-prefs.js.map +1 -1
  216. package/dist/web-profile.d.ts +32 -0
  217. package/dist/web-profile.js +132 -0
  218. package/dist/web-profile.js.map +1 -0
  219. package/dist/web-session.d.ts +14 -0
  220. package/dist/web-session.js +10 -0
  221. package/dist/web-session.js.map +1 -1
  222. package/dist/web-upload.d.ts +119 -4
  223. package/dist/web-upload.js +252 -39
  224. package/dist/web-upload.js.map +1 -1
  225. package/launcher/install-admission.cjs +61 -6
  226. package/package.json +3 -3
@@ -69,10 +69,12 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
69
69
  .sidebar {
70
70
  width: var(--sidebar-w); flex-shrink: 0; display: flex; flex-direction: column; background: var(--bg-sidebar);
71
71
  position: relative; /* #1722: the resize handle sits on its right edge (the width above: tokens.css, or this device's) */
72
- padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom) env(safe-area-inset-left);
72
+ padding: 0 0 env(safe-area-inset-bottom) env(safe-area-inset-left); /* the top inset is the head's, as the page header's is */
73
73
  }
74
74
  .sb-collapsed .sidebar { display: none; }
75
- .side-head { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-3) var(--s-2) var(--s-2) var(--s-4); }
75
+ /* The brand row is the page header's row: the same height, padding and centre line (--header-h; .panel-head, .panel-title). */
76
+ .side-head { display: flex; align-items: center; gap: var(--s-1); box-sizing: content-box; min-height: calc(var(--header-h) - 2 * var(--s-1));
77
+ padding: max(var(--s-1), env(safe-area-inset-top)) var(--s-2) var(--s-1) var(--s-4); }
76
78
  .brand { flex: 1; font-size: var(--fs-lg); font-weight: var(--fw-semibold); letter-spacing: -0.01em; }
77
79
  .side-nav { display: flex; flex-direction: column; padding: var(--s-1) var(--s-2); gap: 1px; }
78
80
  .side-row {
@@ -150,6 +152,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
150
152
  .s-device { display: flex; flex-direction: column; gap: var(--s-3); }
151
153
  .s-device .sub { margin: 0; color: var(--text-3); font-size: var(--fs-sm); }
152
154
  .s-device-row { display: flex; gap: var(--s-2); }
155
+ /* #1827: the person's web name — its field takes the card's width; a refusal reads in the danger colour. */
156
+ .s-profile .field { display: flex; flex-direction: column; gap: var(--s-1); }
157
+ .s-profile .field input { max-width: 24rem; }
158
+ .s-profile .err { color: var(--danger); }
153
159
  /* #1620: Settings → Web — the lead, and the read-only list of what is set in fleet.yaml. */
154
160
  .s-web .sub { margin: 0; color: var(--text-3); font-size: var(--fs-sm); }
155
161
  .s-web-fields:disabled select { opacity: 0.6; } /* over the public link: dimmed like the inputs (.field input:disabled) */
@@ -195,14 +201,17 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
195
201
  }
196
202
  .panel-head .sb-open { display: none; }
197
203
  .sb-collapsed .panel-head .sb-open { display: inline-flex; }
198
- .panel-title { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: var(--s-3); }
204
+ /* One row of --header-h on every page (a phone's instance tabs wrap below it): the title, its status and badges on one centre line. */
205
+ .panel-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s-3); min-height: calc(var(--header-h) - 2 * var(--s-1)); }
199
206
  .panel-title h1 { font-size: var(--fs-lg); line-height: var(--lh-lg); font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; outline: none; }
200
207
  .panel-sub { color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
201
208
  .status { display: inline-flex; align-items: center; gap: 6px; }
202
209
  .panel-actions { display: flex; align-items: center; gap: var(--s-1); }
203
210
  /* #1523 N2: the instance view switch — beside the title on a wide screen; on a phone, a row of tabs under the header. */
204
211
  .inst-switch.seg { padding: 0; margin: 0; width: auto; max-width: none; flex-shrink: 0; }
205
- .inst-switch .seg-item { min-height: 30px; }
212
+ .inst-switch .seg-item { min-height: 30px; flex-direction: column; justify-content: center; gap: 0; }
213
+ /* The label again in the current tab's weight, taking width but no height and never seen: the bar's size does not change with the tab. */
214
+ .inst-switch .seg-item::after { content: attr(data-label); font-weight: var(--fw-semibold); height: 0; overflow: hidden; visibility: hidden; pointer-events: none; user-select: none; }
206
215
  /* #1523 N3 (Q2 = A): the text size, one custom property from the root's data-text-size (header-tools.js). The chat's
207
216
  messages, Details and the Fleet tabs scale the type tokens by it; View's terminal scales itself (VIEW_SCALE). */
208
217
  :root { --text-scale: 1; }
@@ -248,6 +257,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
248
257
  .p-log .log-older, .p-log .log-start { text-align: center; margin: var(--s-2) 0; }
249
258
  .p-log .log-off { padding: var(--s-6) var(--s-4); }
250
259
  .p-details .d-edit { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-2); }
260
+ /* The header's controls are one height, the icon buttons' (36 px): its small buttons, and the instance tabs when they share
261
+ its row (a phone puts them on a row of their own, under it). */
262
+ .panel-head .btn-sm { min-height: 36px; }
263
+ @media (min-width: 900px) { .panel-head .inst-switch .seg-item { min-height: 36px; } }
251
264
  @media (max-width: 899px) {
252
265
  .panel-head:has(.inst-switch) { flex-wrap: wrap; }
253
266
  .inst-switch.seg { order: 3; flex-basis: 100%; display: grid; grid-template-columns: repeat(4, 1fr); padding: 0 0 var(--s-1); }
@@ -264,8 +277,7 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
264
277
  .empty-title { color: var(--text); font-size: var(--fs-lg); font-weight: var(--fw-semibold); }
265
278
  .empty-hint { font-size: var(--fs-sm); }
266
279
  .empty .btn { margin-top: var(--s-3); }
267
- /* #1519 P7: the first-run card (a fleet with no chat connection), above an empty chat or a Settings list. */
268
- .first-run-stack { width: 100%; max-width: var(--col); }
280
+ /* #1519 P7: the first-run card (a fleet with no chat connection), above a Settings list (#1854: no longer on Chat). */
269
281
  .first-run h3 { display: flex; align-items: center; gap: var(--s-2); }
270
282
  .first-run p { color: var(--text-2); font-size: var(--fs-sm); }
271
283
  .error-state { display: flex; flex-direction: column; align-items: center; gap: var(--s-3); padding: var(--s-8) var(--s-4); color: var(--danger); text-align: center; }
@@ -370,6 +382,13 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
370
382
  such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
371
383
  the right under its meta, and a long one still wraps at the message's max-width. */
372
384
  .msg.user .body { background: var(--user-bubble); border-radius: var(--r-xl); padding: 10px var(--s-4); width: fit-content; max-width: 100%; margin-left: auto; }
385
+ /* A person's click on a reply button: the bubble shows 🔘 and the button's label (never the value the agent was given);
386
+ live, the label is a button that shows the reply its buttons were on. */
387
+ .click-chip { display: flex; }
388
+ .click-chip .click-link, .click-chip .click-text { display: inline-flex; align-items: baseline; gap: var(--s-1); padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; overflow-wrap: anywhere; }
389
+ .click-chip .click-link { cursor: pointer; }
390
+ .click-chip .click-link:hover, .click-chip .click-link:focus-visible { text-decoration: underline; }
391
+ .click-mark { flex: none; }
373
392
  .msg.agent { align-self: stretch; }
374
393
  /* #1672: a status line (another instance's message, a daemon notice): muted plain text, no bubble; a long one folds.
375
394
  Its class is `status-line`, never `status`: that is the header's inline-flex badge class, and on a message it turned
@@ -400,10 +419,13 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
400
419
  .text-card { margin-top: var(--s-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--s-2) var(--s-3); }
401
420
  .text-card .tc-head { display: flex; align-items: center; gap: var(--s-2); }
402
421
  .text-card .tc-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-xs); font-family: var(--font-mono); color: var(--text-2); }
403
- .text-card .tc-body { margin-top: var(--s-2); max-height: 480px; overflow: auto; }
422
+ /* #1812: the scroller spans the card's padding box and insets its own content — 12px on both sides, from the border on
423
+ the left and from the scrollbar (or the border, with overlay scrollbars) on the right. A stable gutter keeps a
424
+ classic scrollbar off the content, and the content from shifting when the scrollbar comes and goes. */
425
+ .text-card .tc-body { margin: var(--s-2) calc(-1 * var(--s-3)) 0; padding: 0 var(--s-3); max-height: 480px; overflow: auto; scrollbar-gutter: stable; }
404
426
  .text-card .tc-body[hidden] { display: none; }
405
427
  .text-card .tc-pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-size: var(--fs-xs); }
406
- .text-card .tc-table-wrap { overflow-x: auto; }
428
+ .text-card .tc-table-wrap { overflow-x: auto; padding-bottom: var(--s-3); }
407
429
  .text-card .tc-table { border-collapse: collapse; font-size: var(--fs-xs); }
408
430
  .text-card .tc-table th, .text-card .tc-table td { border: 1px solid var(--border); padding: 2px 6px; text-align: left; vertical-align: top; }
409
431
  .text-card .tc-more { margin-top: var(--s-2); }
@@ -436,10 +458,17 @@ html.lb-open .scroller { overflow: hidden; }
436
458
  .msg .md ul { list-style: disc; } .msg .md ol { list-style: decimal; }
437
459
  .msg .md li > ul, .msg .md li > ol { margin: 2px 0; }
438
460
  .msg .md hr { border: 0; border-top: 1px solid var(--border); margin: var(--s-3) 0; }
439
- .msg .md .tbl { overflow-x: auto; margin: var(--s-2) 0; }
461
+ /* #1812: a table too wide for its message scrolls in this wrapper (see the cell rule below); the bottom padding keeps an
462
+ overlay scrollbar off the last row. */
463
+ .msg .md .tbl { overflow-x: auto; margin: var(--s-2) 0 0; padding-bottom: var(--s-3); }
440
464
  .msg .md table { border-collapse: collapse; font-size: var(--fs-sm); }
441
465
  .msg .md th, .msg .md td { border: 1px solid var(--border); padding: 5px 9px; text-align: left; vertical-align: top; }
442
466
  .msg .md th { background: var(--surface); font-weight: var(--fw-semibold); }
467
+ /* #1812: table cells keep their words whole. The message's `overflow-wrap: anywhere` also shrinks a cell's minimum
468
+ width, so auto table layout squeezed the columns and split words ("directo|r"); `break-word` breaks a word only when
469
+ it alone is wider than its line and leaves the minimum at whole words, so a wide table overflows into its scrolling
470
+ wrapper instead. A column keeps at least 5em: CJK text can break between any two characters. */
471
+ .msg .md th, .msg .md td, .text-card .tc-table th, .text-card .tc-table td { overflow-wrap: break-word; word-break: normal; min-width: 5em; }
443
472
  .msg .md .al-c { text-align: center; } .msg .md .al-r { text-align: right; }
444
473
  .msg .md h3, .msg .md h4, .msg .md h5, .msg .md h6 { margin: var(--s-3) 0 var(--s-2); font-size: var(--fs-md); font-weight: var(--fw-semibold); }
445
474
  .msg .md .tk-k { color: var(--tk-k); } .msg .md .tk-s { color: var(--tk-s); } .msg .md .tk-n { color: var(--tk-n); } .msg .md .tk-c { color: var(--tk-c); font-style: italic; }
@@ -516,6 +545,10 @@ html.lb-open .scroller { overflow: hidden; }
516
545
  .rb-btn { max-width: 100%; white-space: normal; text-align: left; }
517
546
  .rb-btn.chosen, .rb-btn.chosen:disabled { border-color: var(--ok); color: var(--text); background: color-mix(in srgb, var(--ok) 14%, transparent); font-weight: var(--fw-semibold); opacity: 1; }
518
547
  .rb-btn:disabled:not(.chosen) { opacity: 0.55; }
548
+ /* #1823: a poll — its closed buttons show the tally, so they stay readable; the time left keeps its width. */
549
+ .rb-row.poll .rb-btn:disabled { opacity: 0.85; }
550
+ .rb-row.poll .poll-left { font-variant-numeric: tabular-nums; }
551
+ .poll-voters { margin: var(--s-1) 0 0; padding-left: var(--s-4); color: var(--text-2); font-size: var(--fs-sm); }
519
552
  .rb-note { font-size: var(--fs-xs); color: var(--text-3); }
520
553
  .pv-menu { position: relative; }
521
554
  .pv-menu > summary { list-style: none; cursor: pointer; padding: 2px var(--s-2); border-radius: var(--r-sm); color: var(--text-3); }
@@ -704,7 +737,15 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
704
737
  .nav-chip { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; color: var(--text); }
705
738
  .nav-facet-reset { font-size: var(--fs-xs); }
706
739
 
707
- .p-view .panel-actions .btn { gap: var(--s-1); }
740
+ /* One gap on every page (View's, #1448): the header's controls are the same size on every tab. */
741
+ .panel-actions .btn { gap: var(--s-1); }
742
+ /* Aa's names share one cell, only the current one shown: the button is as wide as its widest name on every page. */
743
+ .hd-text-names { display: inline-grid; }
744
+ .hd-text-names > span { grid-area: 1 / 1; visibility: hidden; }
745
+ .hd-text-names > .on { visibility: visible; }
746
+ .hd-slot { visibility: hidden; pointer-events: none; }
747
+ /* #1844 Fleet overview → Docs: the arrow says "opens a new tab". */
748
+ .hd-docs .hd-ext { color: var(--text-3); }
708
749
  .v-term { flex: 1; min-height: 0; overflow: auto; scrollbar-gutter: stable; padding: 5px 6px; background: #000000; position: relative; }
709
750
  .v-pre { margin: 0; min-width: 100%; min-height: 100%; font-family: var(--font-mono); line-height: 1.2; white-space: pre; color: #d0d0d0; }
710
751
  .v-pre .ansi[data-b] { font-weight: var(--fw-bold); }
@@ -1041,6 +1082,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1041
1082
  .cmd-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
1042
1083
  .cmd-name { font-weight: var(--fw-semibold); }
1043
1084
  .cmd-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--font-ui); color: var(--text-2); }
1085
+ .cmd-phase { color: var(--text); font-weight: var(--fw-semibold); }
1044
1086
  .cmd-choices { display: flex; flex-wrap: wrap; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
1045
1087
  .cmd-choices .btn.current { border-color: var(--accent); color: var(--accent); }
1046
1088
  .quick-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); margin-bottom: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
@@ -13,6 +13,8 @@ import { UsageBody, UsageDialog } from "./usage-dialog.js";
13
13
 
14
14
  export const TEXT_SIZE_KEY = "agend_text_size";
15
15
  export const VIEW_FIT_KEY = "agend_view_fit";
16
+ /** #1869: set once this device's View Fit was put back (a size chosen on another page used to turn it off). */
17
+ export const VIEW_FIT_RESTORED_KEY = "agend_view_fit_1869";
16
18
  const LEGACY_DENSITY_KEY = "agend_view_density";
17
19
  export const TEXT_SIZES = ["s", "m", "l"];
18
20
  /** View's terminal: its fitted size times this (S / M / L are View's old compact / – / comfortable). */
@@ -24,6 +26,7 @@ const write = (k, v) => { try { localStorage.setItem(k, v); } catch { /* this pa
24
26
  /**
25
27
  * This device's choice: `agend_text_size` (+ View's Fit), else — once — from View's `agend_view_density`:
26
28
  * comfortable → L, compact → S (Fit off); fit, or nothing → M with View on Fit (View's default, as before).
29
+ * #1869: until then any page's S / M / L also turned View's Fit off, so a Fit off from before is put back once.
27
30
  */
28
31
  export function loadTextSize() {
29
32
  let size = read(TEXT_SIZE_KEY), fit = read(VIEW_FIT_KEY);
@@ -31,7 +34,11 @@ export function loadTextSize() {
31
34
  const legacy = read(LEGACY_DENSITY_KEY);
32
35
  size = legacy === "comfortable" ? "l" : legacy === "compact" ? "s" : "m";
33
36
  fit = legacy === "comfortable" || legacy === "compact" ? "0" : "1";
34
- write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit);
37
+ write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, fit); write(VIEW_FIT_RESTORED_KEY, "1");
38
+ }
39
+ if (read(VIEW_FIT_RESTORED_KEY) !== "1") {
40
+ if (fit === "0") { fit = "1"; write(VIEW_FIT_KEY, fit); }
41
+ write(VIEW_FIT_RESTORED_KEY, "1");
35
42
  }
36
43
  return { size, viewFit: fit !== "0" };
37
44
  }
@@ -50,15 +57,22 @@ export function initTextSize() {
50
57
  window.addEventListener("storage", (e) => { if (!e || e.key === TEXT_SIZE_KEY || e.key === VIEW_FIT_KEY || e.key === null) textSizeStore.set(loadTextSize()); });
51
58
  }
52
59
  }
53
- /** A size chosen anywhere is the size everywhere (View's Fit included: the last choice wins). */
54
- export function setTextSize(size) {
60
+ /**
61
+ * A size chosen anywhere is the size everywhere. View's Fit is left only from View's own Aa (`view`): a size chosen on
62
+ * another page keeps View on Fit (#1869).
63
+ */
64
+ export function setTextSize(size, { view = false } = {}) {
55
65
  if (!TEXT_SIZES.includes(size)) return;
56
- write(TEXT_SIZE_KEY, size); write(VIEW_FIT_KEY, "0");
57
- textSizeStore.set({ size, viewFit: false });
66
+ write(TEXT_SIZE_KEY, size);
67
+ if (view) write(VIEW_FIT_KEY, "0");
68
+ textSizeStore.set((s) => ({ size, viewFit: view ? false : s.viewFit }));
58
69
  }
59
70
  export function setViewFit() { write(VIEW_FIT_KEY, "1"); textSizeStore.set((s) => ({ ...s, viewFit: true })); }
60
71
 
61
- /** Aa: S → M → L → S; on View, Fit → S → M → L → Fit. */
72
+ /**
73
+ * Aa: S → M → L → S; on View, Fit → S → M → L → Fit. Every page's button is as wide as its widest name (Fit's
74
+ * included): switching pages or sizes never moves the controls beside it.
75
+ */
62
76
  export function TextSizeButton({ view = false }) {
63
77
  const st = useStore(textSizeStore);
64
78
  const cur = view && st.viewFit ? "fit" : st.size;
@@ -66,8 +80,9 @@ export function TextSizeButton({ view = false }) {
66
80
  const next = order[(order.indexOf(cur) + 1) % order.length];
67
81
  const name = t(`app.textSize_${cur}`);
68
82
  const label = t("app.textSize", name);
69
- return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next))}
70
- title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true">${name}</span></button>`;
83
+ return html`<button type="button" class="btn btn-ghost btn-sm hd-text" onClick=${() => (next === "fit" ? setViewFit() : setTextSize(next, { view }))}
84
+ title=${label} aria-label=${label}><${Icon} name="type" size=${16} /><span class="hide-narrow" aria-hidden="true"><span class="hd-text-names">${
85
+ ["fit", ...TEXT_SIZES].map(k => html`<span key=${k} class=${k === cur ? "on" : undefined}>${t(`app.textSize_${k}`)}</span>`)}</span></span></button>`;
71
86
  }
72
87
 
73
88
  /** Whether the fleet serves the usage panel: the page says so (data-usage-panel); "0" only when turned off. */
@@ -76,14 +91,15 @@ export function usageOffered() {
76
91
  }
77
92
 
78
93
  // #1673: the rail. Wide enough for it beside the sidebar and the page: RAIL_MEDIA (followed as it changes). Shown or
79
- // hidden is this device's choice (agend_usage_rail; "0" = hidden, else shown) — a layout choice, not synced (#1665).
94
+ // hidden is this device's choice (agend_usage_rail; "1" = shown, "0" = hidden) — a layout choice, not synced (#1665).
95
+ // #1854 (rank 7): a device that never chose starts with it hidden — a quiet first page; ◔ shows it (and remembers).
80
96
  export const RAIL_MEDIA = "(min-width: 1200px)";
81
97
  export const RAIL_KEY = "agend_usage_rail";
82
- /** The pages whose header has ◔ — the rail is theirs. */
83
- export const RAIL_PANELS = ["chat", "view", "details", "fleet"];
84
- export const usageRailStore = createStore({ open: true });
85
- /** A page load: this device's choice. */
86
- export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) !== "0" }); }
98
+ /** The pages whose header has ◔ — the rail is theirs (Log's too: one frame on every tab of an instance). */
99
+ export const RAIL_PANELS = ["chat", "view", "log", "details", "fleet"];
100
+ export const usageRailStore = createStore({ open: false });
101
+ /** A page load: this device's choice; none made yet = hidden. */
102
+ export function initUsageRail() { usageRailStore.set({ open: read(RAIL_KEY) === "1" }); }
87
103
  export function setUsageRail(open) { write(RAIL_KEY, open ? "1" : "0"); usageRailStore.set({ open: !!open }); }
88
104
  const railQuery = () => { try { return typeof matchMedia === "function" ? matchMedia(RAIL_MEDIA) : null; } catch { return null; } };
89
105
  /** Whether the screen is wide enough for the rail, now and as it changes. */
@@ -141,3 +157,7 @@ export function UsageButton({ navKey }) {
141
157
  export function HeaderTools({ view = false, navKey }) {
142
158
  return html`<${TextSizeButton} view=${view} /><${UsageButton} navKey=${navKey} />`;
143
159
  }
160
+ /** Where a page with no control of its own after the shared ones (Log) keeps the room Chat's ⋯ takes: the same box on every tab. */
161
+ export function HeaderSlot() {
162
+ return html`<span class="icon-btn hd-slot" aria-hidden="true"></span>`;
163
+ }
@@ -6,10 +6,13 @@ import { html } from "./app-html.js";
6
6
  import { t } from "./app-i18n.js";
7
7
  import { chatPath, detailsPath, logPath, viewPath } from "./app-route.js";
8
8
 
9
- /** current: "chat" | "view" | "log" | "details". #1218: Log sits between View and Details. */
9
+ /**
10
+ * current: "chat" | "view" | "log" | "details". #1218: Log sits between View and Details. `data-label`: each tab is as
11
+ * wide as its label in the current tab's weight (app.css), so the bar keeps its size whichever tab is current.
12
+ */
10
13
  export function InstanceSwitch({ name, current }) {
11
14
  const seg = (key, href, label) => html`<a key=${key} class=${`seg-item${current === key ? " active" : ""}`} href=${href}
12
- aria-current=${current === key ? "page" : undefined}>${label}</a>`;
15
+ aria-current=${current === key ? "page" : undefined} data-label=${label}>${label}</a>`;
13
16
  return html`<nav class="inst-switch seg" aria-label=${t("app.instanceViews", name)}>
14
17
  ${seg("chat", chatPath(name), t("app.chat"))}${seg("view", viewPath(name), t("app.view"))}${seg("log", logPath(name), t("app.log"))}${seg("details", detailsPath(name), t("app.details"))}
15
18
  </nav>`;
@@ -114,7 +114,9 @@ export { viewStore };
114
114
  // ── The panel ──
115
115
 
116
116
  // readStream: one stream of reads under a lease (read-stream.js; View's pane and roster use it).
117
- const MIN_PX = 12, MAX_PX = 22;
117
+ // S / M / L: the fitted size kept within 12–22 px, then scaled. Fit (#1869): never larger than what fits — only a 6 px
118
+ // floor for legibility (a grid too wide for the box even at 6 px, e.g. 200 columns on a phone, still scrolls).
119
+ const MIN_PX = 12, MAX_PX = 22, FIT_FLOOR_PX = 6;
118
120
 
119
121
  export function ViewPanel({ route, navKey }) {
120
122
  const lease = useLease(navKey);
@@ -219,10 +221,11 @@ function Terminal({ name, lease, density }) {
219
221
  const lh = parseFloat(pcs.lineHeight) / parseFloat(pcs.fontSize);
220
222
  const lineH = Number.isFinite(lh) && lh > 0 ? lh : 1.2;
221
223
  let size = Math.min(innerW / (g.cols * glyphRatio(p)), innerH / (g.rows * lineH)) * 0.995;
222
- size = Math.max(MIN_PX, Math.min(size, MAX_PX)) * (density === "fit" ? 1 : VIEW_SCALE[density] || 1);
223
- size = Math.max(8, Math.min(size, 32));
224
+ if (density === "fit") size = Math.max(FIT_FLOOR_PX, Math.min(size, MAX_PX));
225
+ else size = Math.max(8, Math.min(Math.max(MIN_PX, Math.min(size, MAX_PX)) * (VIEW_SCALE[density] || 1), 32));
224
226
  g.key = key;
225
- if (Math.abs(size - g.applied) < 0.75) return;
227
+ // A small change is skipped only when growing (the size applied still fits); a smaller box always shrinks it.
228
+ if (size >= g.applied && size - g.applied < 0.75) return;
226
229
  g.applied = size;
227
230
  p.style.fontSize = `${size.toFixed(2)}px`; // CSSOM, never a style attribute (#1300)
228
231
  };
@@ -314,6 +317,8 @@ function EditProfile({ it, onClose }) {
314
317
  const [f, setF] = useState({ display: it.display_name || "", role: it.role || "", desc: it.description || "" });
315
318
  const [busy, setBusy] = useState(false);
316
319
  const [msg, setMsg] = useState(null);
320
+ // #1630 follow-up: the uploaded picture is removed on Save (Cancel keeps it); a newly picked file wins over removing.
321
+ const [removePic, setRemovePic] = useState(false);
317
322
  const file = useRef(null);
318
323
  async function save() {
319
324
  if (busy) return;
@@ -323,6 +328,7 @@ function EditProfile({ it, onClose }) {
323
328
  const name = encodeURIComponent(it.instance_name);
324
329
  const draft = { display_name: f.display, role: f.role, description: f.desc };
325
330
  const picked = file.current && file.current.files && file.current.files[0];
331
+ const removing = removePic;
326
332
  try {
327
333
  const r = await fetch(`/api/profile/${name}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft) });
328
334
  if (!lease.current()) return;
@@ -331,6 +337,9 @@ function EditProfile({ it, onClose }) {
331
337
  if (picked) {
332
338
  const ar = await fetch(`/api/avatar/${name}`, { method: "POST", headers: { "Content-Type": picked.type }, body: picked });
333
339
  if (!ar.ok) { const e = await ar.json().catch(() => ({})); throw new Error(tn("avatarFailed", { error: e.error || ar.status })); }
340
+ } else if (removing) { // a newly picked file replaces it instead
341
+ const dr = await fetch(`/api/avatar/${name}`, { method: "DELETE" });
342
+ if (!dr.ok) { const e = await dr.json().catch(() => ({})); throw new Error(tn("avatarRemoveFailed", { error: e.error || dr.status })); }
334
343
  }
335
344
  if (!lease.current()) return;
336
345
  setBusy(false);
@@ -345,6 +354,8 @@ function EditProfile({ it, onClose }) {
345
354
  <label class="field"><span>${tn("role")}</span><input value=${f.role} onInput=${(e) => setF({ ...f, role: e.target.value })} /></label>
346
355
  <label class="field"><span>${tn("description")}</span><textarea rows="3" value=${f.desc} onInput=${(e) => setF({ ...f, desc: e.target.value })}></textarea></label>
347
356
  <label class="field"><span>${tn("avatar")}</span><input ref=${file} type="file" accept="image/png,image/jpeg,image/gif,image/webp" /></label>
357
+ ${it.has_avatar ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removePic ? "true" : "false"}
358
+ onClick=${() => setRemovePic(!removePic)}>${tn(removePic ? "keepPicture" : "removePicture")}</button>${removePic ? html`<span class="muted">${tn("removePictureNote")}</span>` : null}</div>` : null}
348
359
  </fieldset></form>
349
360
  ${msg ? html`<p class="err" role="alert">${msg}${msg === tn("signInToSave") ? html` <a class="link" href=${signInHref()}>${t("app.signIn")}</a>` : null}</p>` : null}
350
361
  </${Dialog}>`;
@@ -11,10 +11,12 @@
11
11
  const $ = (id) => document.getElementById(id);
12
12
  const T = {
13
13
  en: {
14
- checking: "Checking your session…", title: "Sign in to AgEnD", lead: "Enter the one-time code from your chat channel.",
14
+ checking: "Checking your session…", title: "Sign in to AgEnD", lead: "Enter your sign-in code.",
15
15
  label: "One-time code", submit: "Sign in", working: "Signing in…",
16
- hint: "Send /web to your AgEnD bot, or run “agend web --code” on the host. A code works once and expires in 5 minutes.",
17
- refused: "That code did not work — it may be wrong, expired or already used. Ask for a new one with /web.",
16
+ hint: "No code? Send /web in General, then press a sign-in button to get one.",
17
+ nobot: "No chat bot?", nobot_body: "Ask the operator to run agend web --code on the AgEnD computer.",
18
+ refused: "The code is invalid or no longer works. Send /web in General, then press a sign-in button for a new code.",
19
+ unconfirmed: "Sign-in could not be confirmed. Get a new code and try again; if it keeps failing, contact the operator.",
18
20
  paused: "Too many wrong codes — sign-in is paused for a few minutes.",
19
21
  network: "Could not reach AgEnD. Check the connection and try again.",
20
22
  resend: "Send me a new code", resent: "A new code was sent to you privately in chat. Enter it above.",
@@ -22,10 +24,12 @@
22
24
  resend_refused: "A new code could not be sent. Ask for one with /web.",
23
25
  },
24
26
  "zh-TW": {
25
- checking: "正在確認登入狀態…", title: "登入 AgEnD", lead: "請輸入聊天頻道給你的一次性登入碼。",
27
+ checking: "正在確認登入狀態…", title: "登入 AgEnD", lead: "輸入登入碼。",
26
28
  label: "一次性登入碼", submit: "登入", working: "登入中…",
27
- hint: "在 AgEnD bot 傳 /web,或在主機執行「agend web --code」。登入碼只能用一次,5 分鐘內有效。",
28
- refused: "登入碼無效:可能輸入錯誤、已過期或已使用。請用 /web 再要一個。",
29
+ hint: "沒收到?在 General 傳 /web,再按登入按鈕取碼。",
30
+ nobot: "沒有聊天 bot?", nobot_body: "請管理員在 AgEnD 電腦執行 agend web --code。",
31
+ refused: "登入碼無效或已失效。請在 General 傳 /web,再按登入按鈕取新碼。",
32
+ unconfirmed: "登入暫時無法確認。請重新取碼再試;若仍失敗,請聯絡管理員。",
29
33
  paused: "錯誤次數過多,登入暫停幾分鐘。",
30
34
  network: "連不上 AgEnD,請檢查連線後再試。",
31
35
  resend: "傳一組新的登入碼給我", resent: "新的登入碼已私訊給你,請在上方輸入。",
@@ -47,8 +51,8 @@
47
51
  history.replaceState(null, "", here.pathname + here.search + here.hash);
48
52
  }
49
53
  } catch { /* nothing to clean */ }
50
- // The command in the hint is a <code> that never breaks across lines (a break inside "--code" reads as two dashes
51
- // and a word, alpha.2 sweep); the text around it is text.
54
+ // The command in the "No chat bot?" help is a <code> that never breaks across lines (a break inside "--code" reads as
55
+ // two dashes and a word, alpha.2 sweep); the text around it is text.
52
56
  const CMD = "agend web --code";
53
57
  document.querySelectorAll("[data-i18n]").forEach((el) => {
54
58
  const text = t(el.getAttribute("data-i18n"));
@@ -163,7 +167,9 @@
163
167
  body: JSON.stringify({ code: $("code").value }),
164
168
  });
165
169
  if (r.ok) { clearBounce(); location.replace(nextTarget()); return; }
166
- msg.textContent = r.status === 429 ? t("paused") : t("refused");
170
+ // #1854: a 5xx is the server failing to confirm the sign-in (503: a public link's notice did not go out), not a
171
+ // wrong code — say so. The code is spent either way: the way on is a new one.
172
+ msg.textContent = r.status === 429 ? t("paused") : r.status >= 500 ? t("unconfirmed") : t("refused");
167
173
  } catch {
168
174
  msg.textContent = t("network");
169
175
  }
@@ -54,6 +54,7 @@ const P = {
54
54
  panel: () => html`<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>`,
55
55
  back: () => html`<path d="M19 12H5M12 19l-7-7 7-7"/>`,
56
56
  forward: () => html`<path d="M5 12h14M12 5l7 7-7 7"/>`,
57
+ book: () => html`<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"/><path d="M4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/>`,
57
58
  external: () => html`<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>`,
58
59
  };
59
60
 
@@ -152,6 +152,10 @@ register("view", {
152
152
  "paneFailed": "⚠ Could not load pane for {name} (HTTP {status}).",
153
153
  "signInToSave": "Sign in to save — your session may have ended.",
154
154
  "avatarFailed": "Avatar upload failed: {error}",
155
+ "avatarRemoveFailed": "Could not remove the picture: {error}",
156
+ "removePicture": "Remove picture",
157
+ "keepPicture": "Keep picture",
158
+ "removePictureNote": "Removed when you save. The bot's avatar, the persona emoji or the first letter shows instead.",
155
159
  "saved": "Saved",
156
160
  "saveFailed": "Save failed: {error}",
157
161
  "signInToEdit": "Sign in to edit",
@@ -202,8 +206,8 @@ register("view", {
202
206
  "help1": "在清單裡選一個 instance:它的即時終端畫面會顯示在這裡。",
203
207
  "help2": "點群組名稱可以收合或展開。",
204
208
  "help3": "「編輯個人資料」可修改顯示名稱、角色、描述和頭像。",
205
- "help5": "「字級」會在剛好填滿 → 小 → 中 → 大之間切換。小、中、大是所有頁面共用的字級;剛好填滿讓終端畫面符合框的大小。",
206
- "densityFit": "剛好填滿",
209
+ "help5": "「字級」會在填滿 → 小 → 中 → 大之間切換。小、中、大是所有頁面共用的字級;填滿讓終端畫面符合框的大小。",
210
+ "densityFit": "填滿",
207
211
  "densityComfortable": "舒適",
208
212
  "densityCompact": "精簡",
209
213
  "densityLabel": "字級密度",
@@ -328,6 +332,10 @@ register("view", {
328
332
  "paneFailed": "⚠ 無法載入 {name} 的 pane(HTTP {status})。",
329
333
  "signInToSave": "請先登入才能儲存:你的 session 可能已結束。",
330
334
  "avatarFailed": "上傳頭像失敗:{error}",
335
+ "avatarRemoveFailed": "無法移除圖片:{error}",
336
+ "removePicture": "移除圖片",
337
+ "keepPicture": "保留圖片",
338
+ "removePictureNote": "儲存後移除,改顯示 Bot 的頭像、persona emoji 或名稱第一個字。",
331
339
  "saved": "已儲存",
332
340
  "saveFailed": "儲存失敗:{error}",
333
341
  "signInToEdit": "登入後才能編輯",
@@ -32,7 +32,11 @@
32
32
  #msg { min-height: 1.5em; margin-top: 14px; font-size: var(--fs-sm); color: var(--danger); }
33
33
  #msg.ok { color: var(--text-2); }
34
34
  .hint { margin-top: 18px; font-size: var(--fs-xs); color: var(--text-3); }
35
- .hint code { white-space: nowrap; font-family: var(--font-mono); }
35
+ .hint code, .help code { white-space: nowrap; font-family: var(--font-mono); }
36
+ /* #1854: the operator's way in (no chat bot) — closed until asked for. */
37
+ .help { margin-top: 8px; font-size: var(--fs-xs); color: var(--text-3); }
38
+ .help summary { cursor: pointer; width: fit-content; }
39
+ .help p { margin: 6px 0 0; color: var(--text-3); }
36
40
  [hidden] { display: none !important; }
37
41
  </style>
38
42
  </head>
@@ -42,14 +46,15 @@
42
46
  <div id="checking" data-i18n="checking">Checking your session…</div>
43
47
  <form id="form" hidden autocomplete="off">
44
48
  <h1 data-i18n="title">Sign in to AgEnD</h1>
45
- <p data-i18n="lead">Enter the one-time code from your chat channel.</p>
49
+ <p data-i18n="lead">Enter your sign-in code.</p>
46
50
  <label for="code" data-i18n="label">One-time code</label>
47
51
  <input id="code" name="code" inputmode="text" autocomplete="one-time-code" autocapitalize="characters"
48
52
  autocorrect="off" spellcheck="false" maxlength="12" placeholder="XXXX-XXXX" required>
49
53
  <button id="go" type="submit" data-i18n="submit">Sign in</button>
50
54
  <button id="resend" type="button" class="secondary" hidden data-i18n="resend">Send me a new code</button>
51
55
  <div id="msg" role="alert" aria-live="polite"></div>
52
- <div class="hint" data-i18n="hint">Send /web to your AgEnD bot, or run “<code>agend web --code</code>” on the host. A code works once and expires in 5 minutes.</div>
56
+ <div class="hint" data-i18n="hint">No code? Send /web in General, then press a sign-in button to get one.</div>
57
+ <details class="help"><summary data-i18n="nobot">No chat bot?</summary><p data-i18n="nobot_body">Ask the operator to run <code>agend web --code</code> on the AgEnD computer.</p></details>
53
58
  </form>
54
59
  <noscript>This page needs JavaScript to sign you in.</noscript>
55
60
  </main>
@@ -80,6 +80,37 @@ export declare function runningFleetPredatesInstall(opts: {
80
80
  isFleetProcess: (pid: number) => boolean;
81
81
  marginMs?: number;
82
82
  }): boolean;
83
+ /**
84
+ * #1829 review: what fleet.pid says, as evidence — "missing" only when the file is not there (ENOENT), "valid" for a
85
+ * whole positive pid, "unknown" for anything else (unreadable, a directory, empty, malformed). Unknown is never read
86
+ * as a stopped fleet.
87
+ */
88
+ export declare function readFleetPidEvidence(path: string, read?: (path: string) => string): {
89
+ kind: "missing";
90
+ } | {
91
+ kind: "valid";
92
+ pid: number;
93
+ } | {
94
+ kind: "unknown";
95
+ };
96
+ /** The largest pid `process.kill` accepts (a signed 32-bit integer). */
97
+ export declare const MAX_PID = 2147483647;
98
+ /**
99
+ * Whether `pid` may be alive: only ESRCH ("no such process") proves it gone. EPERM is a live process of another
100
+ * user, and any other failure (an argument the call refused, for one) proves nothing — so: alive.
101
+ */
102
+ export declare function pidAlive(pid: number, kill?: (pid: number, signal: 0) => unknown): boolean;
103
+ /**
104
+ * #1829 ⑥: whether the fleet is PROVEN not running — no fleet.pid (a fleet removes it when it stops), a pid that no
105
+ * longer exists, or a live pid whose readable command line is not a fleet's (a recycled pid). A command line that
106
+ * cannot be read proves nothing either way: not stopped.
107
+ */
108
+ export declare function fleetProvenStopped(opts: {
109
+ pid: number | null;
110
+ alive: (pid: number) => boolean;
111
+ commandLine: (pid: number) => string;
112
+ isFleetProcess: (commandLine: string) => boolean;
113
+ }): boolean;
83
114
  /** Start time of a live process, via `ps -o lstart=` (Linux and macOS); null if gone. */
84
115
  export declare function processStartMs(pid: number, run?: (args: string[]) => string): number | null;
85
116
  interface UpdateOutput {
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
2
3
  import { SYSTEMD_RESTART_INDETERMINATE_EXIT_CODE } from "./service-restart-selection.js";
3
4
  /**
4
5
  * What a chat `/update` (Discord slash or Telegram) runs: no channel flag, ever.
@@ -160,6 +161,52 @@ export function runningFleetPredatesInstall(opts) {
160
161
  return false;
161
162
  return started < opts.installedAtMs - (opts.marginMs ?? 2_000);
162
163
  }
164
+ /**
165
+ * #1829 review: what fleet.pid says, as evidence — "missing" only when the file is not there (ENOENT), "valid" for a
166
+ * whole positive pid, "unknown" for anything else (unreadable, a directory, empty, malformed). Unknown is never read
167
+ * as a stopped fleet.
168
+ */
169
+ export function readFleetPidEvidence(path, read = p => readFileSync(p, "utf-8")) {
170
+ let raw;
171
+ try {
172
+ raw = read(path);
173
+ }
174
+ catch (err) {
175
+ return err?.code === "ENOENT" ? { kind: "missing" } : { kind: "unknown" };
176
+ }
177
+ const text = raw.trim();
178
+ const pid = /^[1-9][0-9]{0,9}$/.test(text) ? Number(text) : Number.NaN;
179
+ // process.kill takes a signed 32-bit pid (review): anything above is not a pid it can ask about.
180
+ return pid <= MAX_PID ? { kind: "valid", pid } : { kind: "unknown" };
181
+ }
182
+ /** The largest pid `process.kill` accepts (a signed 32-bit integer). */
183
+ export const MAX_PID = 2_147_483_647;
184
+ /**
185
+ * Whether `pid` may be alive: only ESRCH ("no such process") proves it gone. EPERM is a live process of another
186
+ * user, and any other failure (an argument the call refused, for one) proves nothing — so: alive.
187
+ */
188
+ export function pidAlive(pid, kill = (p, s) => process.kill(p, s)) {
189
+ try {
190
+ kill(pid, 0);
191
+ return true;
192
+ }
193
+ catch (err) {
194
+ return err?.code !== "ESRCH";
195
+ }
196
+ }
197
+ /**
198
+ * #1829 ⑥: whether the fleet is PROVEN not running — no fleet.pid (a fleet removes it when it stops), a pid that no
199
+ * longer exists, or a live pid whose readable command line is not a fleet's (a recycled pid). A command line that
200
+ * cannot be read proves nothing either way: not stopped.
201
+ */
202
+ export function fleetProvenStopped(opts) {
203
+ if (opts.pid === null)
204
+ return true;
205
+ if (!opts.alive(opts.pid))
206
+ return true;
207
+ const line = opts.commandLine(opts.pid);
208
+ return line !== "" && !opts.isFleetProcess(line);
209
+ }
163
210
  /** Start time of a live process, via `ps -o lstart=` (Linux and macOS); null if gone. */
164
211
  export function processStartMs(pid, run = args => execFileSync("ps", args, { encoding: "utf8", env: { ...process.env, LC_ALL: "C", TZ: process.env.TZ }, stdio: ["ignore", "pipe", "ignore"], timeout: 5000 })) {
165
212
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,uCAAuC,EAAE,MAAM,gCAAgC,CAAC;AAEzF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAY7C;;;;;GAKG;AACH,MAAM,GAAG,GAAG,iBAAiB,CAAC;AAC9B,MAAM,MAAM,GAAG,MAAM,GAAG,8BAA8B,CAAC;AACvD,MAAM,QAAQ,GAAG,eAAe,CAAC;AACjC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,MAAM,SAAS,MAAM,cAAc,QAAQ,SAAS,QAAQ,OAAO,CAAC,CAAC;AAIxI,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACxE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;AACvC,CAAC;AAED,oHAAoH;AACpH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,mGAAmG;AACnG,SAAS,cAAc,CAAC,CAAS,EAAE,CAAS;IAC1C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IAChC,CAAC;IACD,IAAI,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAChG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChE,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACtB,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjD,IAAI,EAAE,IAAI,EAAE;YAAE,OAAO,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAClD,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC;IAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACtC,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AAC/C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA0B,EAAE,gBAAwB;IACpF,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IACtC,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAC/B,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC;IAC7B,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IACjC,OAAO,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,aAAqB;IAC3E,OAAO,mBAAmB,CAAC,cAAc,CAAC,IAAI,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,0BAA0B,CAAC;AAC7I,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,gBAAwB,EAAE,aAA4B,EAAE,IAA0B;IACvH,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACtF,MAAM,KAAK,GAAG,aAAa,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IAC7D,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,MAA2B,YAAY;IAEvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,CAChB,KAAK,EACL,CAAC,MAAM,EAAE,kBAAkB,QAAQ,EAAE,EAAE,SAAS,CAAC,EACjD;YACE,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,OAAO,EAAE,MAAM;SAChB,CACF,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,cAAsB,EACtB,aAA4B,EAC5B,KAAK,GAAG,KAAK;IAEb,OAAO,CAAC,KAAK;WACR,aAAa,KAAK,IAAI;WACtB,gBAAgB,CAAC,cAAc,CAAC,KAAK,gBAAgB,CAAC,aAAa,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAW3C;IACC,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5E,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,OAAO,GAAG,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;AACjE,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,MAAkC,IAAI,CAAC,EAAE,CACnF,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAE5J,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9D,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAOD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAqB,EACrB,SAAuB,OAAO;IAE9B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;QACpC,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,IAAI,MAAM,KAAK,uCAAuC,EAAE,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,sGAAsG,CAAC,CAAC;QACnH,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACjE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"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"}