@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.3

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 (174) hide show
  1. package/dist/agent-cli.js +5 -5
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/agent-file-read.d.ts +11 -6
  4. package/dist/agent-file-read.js +32 -17
  5. package/dist/agent-file-read.js.map +1 -1
  6. package/dist/agent-whats-new.js +10 -13
  7. package/dist/agent-whats-new.js.map +1 -1
  8. package/dist/auth-api.js +10 -5
  9. package/dist/auth-api.js.map +1 -1
  10. package/dist/backend/claude-code.d.ts +2 -2
  11. package/dist/backend/claude-code.js +10 -5
  12. package/dist/backend/claude-code.js.map +1 -1
  13. package/dist/backend/types.d.ts +13 -0
  14. package/dist/backend/types.js +10 -0
  15. package/dist/backend/types.js.map +1 -1
  16. package/dist/cache-analysis.d.ts +47 -1
  17. package/dist/cache-analysis.js +68 -8
  18. package/dist/cache-analysis.js.map +1 -1
  19. package/dist/cache-report-tool.d.ts +17 -0
  20. package/dist/cache-report-tool.js +40 -0
  21. package/dist/cache-report-tool.js.map +1 -0
  22. package/dist/cache-service.d.ts +15 -0
  23. package/dist/cache-service.js +26 -1
  24. package/dist/cache-service.js.map +1 -1
  25. package/dist/channel/mcp-tools.js +9 -3
  26. package/dist/channel/mcp-tools.js.map +1 -1
  27. package/dist/classic-channel-manager.d.ts +8 -3
  28. package/dist/classic-channel-manager.js +10 -3
  29. package/dist/classic-channel-manager.js.map +1 -1
  30. package/dist/cli.js +7 -3
  31. package/dist/cli.js.map +1 -1
  32. package/dist/config-validator.js +5 -1
  33. package/dist/config-validator.js.map +1 -1
  34. package/dist/daemon.js +5 -5
  35. package/dist/daemon.js.map +1 -1
  36. package/dist/delivery-outbox.js +5 -0
  37. package/dist/delivery-outbox.js.map +1 -1
  38. package/dist/fleet-manager.d.ts +71 -3
  39. package/dist/fleet-manager.js +256 -45
  40. package/dist/fleet-manager.js.map +1 -1
  41. package/dist/fleet-traffic.d.ts +38 -0
  42. package/dist/fleet-traffic.js +96 -0
  43. package/dist/fleet-traffic.js.map +1 -0
  44. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  45. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  46. package/dist/general-knowledge/skills/web-appearance/SKILL.md +149 -5
  47. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +398 -0
  48. package/dist/locale.js +6 -4
  49. package/dist/locale.js.map +1 -1
  50. package/dist/memory-hogs.d.ts +27 -0
  51. package/dist/memory-hogs.js +124 -0
  52. package/dist/memory-hogs.js.map +1 -0
  53. package/dist/outbound-handlers.d.ts +4 -0
  54. package/dist/outbound-handlers.js +74 -1
  55. package/dist/outbound-handlers.js.map +1 -1
  56. package/dist/outbound-schemas.d.ts +98 -0
  57. package/dist/outbound-schemas.js +56 -0
  58. package/dist/outbound-schemas.js.map +1 -1
  59. package/dist/public-web-gateway.js +1 -1
  60. package/dist/public-web-gateway.js.map +1 -1
  61. package/dist/scheduler/scheduler.d.ts +12 -0
  62. package/dist/scheduler/scheduler.js +83 -10
  63. package/dist/scheduler/scheduler.js.map +1 -1
  64. package/dist/service-installer.d.ts +2 -1
  65. package/dist/service-installer.js +18 -3
  66. package/dist/service-installer.js.map +1 -1
  67. package/dist/tmux-manager.d.ts +5 -0
  68. package/dist/tmux-manager.js +16 -0
  69. package/dist/tmux-manager.js.map +1 -1
  70. package/dist/tool-permissions.d.ts +5 -0
  71. package/dist/tool-permissions.js +15 -2
  72. package/dist/tool-permissions.js.map +1 -1
  73. package/dist/types.d.ts +1 -1
  74. package/dist/ui/app-effects.js +33 -9
  75. package/dist/ui/chat-effects.js +62 -4
  76. package/dist/ui/chat-strings.js +40 -4
  77. package/dist/ui/chat-thread.js +13 -5
  78. package/dist/ui/effect-trial-panel.js +19 -16
  79. package/dist/ui/fleet-cache.js +23 -1
  80. package/dist/ui/fleet-canvas-layout.js +111 -0
  81. package/dist/ui/fleet-canvas.js +489 -0
  82. package/dist/ui/fleet-org.js +27 -12
  83. package/dist/ui/fleet-traffic.js +224 -0
  84. package/dist/ui/panel-chat.js +8 -2
  85. package/dist/ui/panel-details.js +2 -1
  86. package/dist/ui/panel-fleet.js +17 -5
  87. package/dist/ui/panel-settings.js +3 -2
  88. package/dist/ui/preview.js +3 -2
  89. package/dist/ui/settings-appearance.js +34 -18
  90. package/dist/ui/settings-dialogs.js +3 -6
  91. package/dist/ui/settings-effects.js +4 -3
  92. package/dist/ui/settings-learn.js +25 -0
  93. package/dist/ui/settings-model.js +6 -2
  94. package/dist/ui/settings-mods.js +4 -3
  95. package/dist/ui/settings-owner-effects.js +8 -8
  96. package/dist/ui/settings-strings.js +34 -38
  97. package/dist/ui/shared/app-i18n.js +2 -2
  98. package/dist/ui/shared/app-prefs.js +1 -1
  99. package/dist/ui/shared/app-shell.js +23 -1
  100. package/dist/ui/shared/app-store.js +12 -1
  101. package/dist/ui/shared/app-stream.js +2 -1
  102. package/dist/ui/shared/app.css +163 -12
  103. package/dist/ui/shared/effect-app-runtime.js +1 -1
  104. package/dist/ui/shared/effect-frame.js +10 -5
  105. package/dist/ui/shared/effect-geometry.js +24 -9
  106. package/dist/ui/shared/effect-policy.js +2 -1
  107. package/dist/ui/shared/effect-registry.js +71 -17
  108. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  109. package/dist/ui/shared/effect-trial-stage.js +15 -9
  110. package/dist/ui/shared/effect-trial.css +40 -0
  111. package/dist/ui/shared/identity-card.js +5 -2
  112. package/dist/ui/shared/identity-color.js +3 -3
  113. package/dist/ui/shared/instance-nav.js +35 -9
  114. package/dist/ui/shared/panel-view.js +2 -2
  115. package/dist/ui/shared/usage-dialog.js +14 -2
  116. package/dist/ui/shared/view-strings.js +22 -2
  117. package/dist/ui/signin.html +1 -1
  118. package/dist/usage/providers.d.ts +3 -0
  119. package/dist/usage/providers.js +4 -3
  120. package/dist/usage/providers.js.map +1 -1
  121. package/dist/view-api.d.ts +21 -0
  122. package/dist/view-api.js +28 -0
  123. package/dist/view-api.js.map +1 -1
  124. package/dist/web-api.d.ts +5 -6
  125. package/dist/web-api.js +60 -11
  126. package/dist/web-api.js.map +1 -1
  127. package/dist/web-appearance-view.d.ts +10 -0
  128. package/dist/web-appearance-view.js +77 -8
  129. package/dist/web-appearance-view.js.map +1 -1
  130. package/dist/web-asset-parts.d.ts +3 -8
  131. package/dist/web-asset-parts.js.map +1 -1
  132. package/dist/web-auth.js +1 -1
  133. package/dist/web-auth.js.map +1 -1
  134. package/dist/web-effect-service.js +3 -1
  135. package/dist/web-effect-service.js.map +1 -1
  136. package/dist/web-effect-tools.js +4 -1
  137. package/dist/web-effect-tools.js.map +1 -1
  138. package/dist/web-effect-trials.d.ts +4 -4
  139. package/dist/web-effect-trials.js +6 -6
  140. package/dist/web-effect-trials.js.map +1 -1
  141. package/dist/web-effect-upload.d.ts +2 -4
  142. package/dist/web-effect-upload.js +3 -8
  143. package/dist/web-effect-upload.js.map +1 -1
  144. package/dist/web-effect.d.ts +14 -0
  145. package/dist/web-effect.js +21 -0
  146. package/dist/web-effect.js.map +1 -1
  147. package/dist/web-guest-access.d.ts +67 -0
  148. package/dist/web-guest-access.js +126 -0
  149. package/dist/web-guest-access.js.map +1 -0
  150. package/dist/web-identity-palette.d.ts +1 -1
  151. package/dist/web-identity-palette.js +2 -2
  152. package/dist/web-identity-palette.js.map +1 -1
  153. package/dist/web-mod-agent.js +5 -3
  154. package/dist/web-mod-agent.js.map +1 -1
  155. package/dist/web-mod-bundle.d.ts +5 -0
  156. package/dist/web-mod-bundle.js +6 -0
  157. package/dist/web-mod-bundle.js.map +1 -1
  158. package/dist/web-mod-store.d.ts +5 -0
  159. package/dist/web-mod-store.js +8 -1
  160. package/dist/web-mod-store.js.map +1 -1
  161. package/dist/web-prefs.d.ts +8 -0
  162. package/dist/web-prefs.js +9 -0
  163. package/dist/web-prefs.js.map +1 -1
  164. package/dist/web-preview.d.ts +6 -2
  165. package/dist/web-preview.js +4 -2
  166. package/dist/web-preview.js.map +1 -1
  167. package/dist/web-session.d.ts +15 -7
  168. package/dist/web-session.js +25 -8
  169. package/dist/web-session.js.map +1 -1
  170. package/dist/web-settings-tools.d.ts +69 -0
  171. package/dist/web-settings-tools.js +247 -0
  172. package/dist/web-settings-tools.js.map +1 -0
  173. package/package.json +1 -1
  174. package/templates/systemd.service.ejs +8 -0
@@ -222,8 +222,12 @@ export function accessConfirmations(previousAccess, nextMode, nextUsers) {
222
222
  return out;
223
223
  }
224
224
 
225
- /** #1320: only a short positive numeric id is a Telegram private chat; Discord snowflakes and negative ids are groups. */
226
- export const webEchoIsGroup = (channelIdValue) => !/^\d{1,12}$/.test(String(channelIdValue || ""));
225
+ /**
226
+ * #2054: whether a ClassicBot room copies web chat, as the fleet decides it (ClassicChannelManager's load +
227
+ * classicWebEchoOn): an absent `web_echo` follows the fleet-wide `web.echo_to_channel` (default on); any other value
228
+ * echoes only when it is `true` (a `null`, `0` or string reads as off, as the manager reads it).
229
+ */
230
+ export const classicWebEchoEffective = (room, fleet) => (room?.web_echo === undefined ? fleet?.web?.echo_to_channel !== false : room.web_echo === true);
227
231
 
228
232
  // ── Developer YAML: the config subset's writer and reader, exactly as settings.html had them (round-trip verified) ──
229
233
  export function toYaml(value, indent = 0) {
@@ -12,6 +12,7 @@ import { toast } from "/assets/ui-toast.js";
12
12
  import { confirmDialog } from "/assets/ui-confirm.js";
13
13
  import { startOwnerCssTrial } from "/assets/owner-css.js";
14
14
  import { AgentWidget, checkWidget, fleetBinds, instanceBinds } from "/assets/agent-widget.js";
15
+ import { CardTitle } from "./settings-learn.js";
15
16
  import "./settings-strings.js";
16
17
 
17
18
  const tn = (k, ...v) => t(`settings.${k}`, ...v);
@@ -204,8 +205,8 @@ export function ModsCard() {
204
205
  setState(b && Array.isArray(b.mods) ? { mods: b.mods.filter((m) => m && typeof m.id === "string"), staged, proposals } : { failed: true });
205
206
  };
206
207
  useEffect(() => { load(); }, []);
207
- if (!state) return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3><p class="sub">…</p></section>`;
208
- if (state.failed) return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3><p class="err">${tn("modsLoadFailed")}</p></section>`;
208
+ if (!state) return html`<section class="card s-appearance s-mods"><${CardTitle} title=${tn("modsTitle")} topic="mods" /><p class="sub">…</p></section>`;
209
+ if (state.failed) return html`<section class="card s-appearance s-mods"><${CardTitle} title=${tn("modsTitle")} topic="mods" /><p class="err">${tn("modsLoadFailed")}</p></section>`;
209
210
  const stage = async () => {
210
211
  const f = file.current && file.current.files && file.current.files[0];
211
212
  if (!f) return;
@@ -238,7 +239,7 @@ export function ModsCard() {
238
239
  if (!r || !r.ok) { toast(tn("modFailed", r ? r.status : "network"), false); return; }
239
240
  download(await r.text(), `${m.name}-${m.version}.agendmod.json`);
240
241
  };
241
- return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3>
242
+ return html`<section class="card s-appearance s-mods"><${CardTitle} title=${tn("modsTitle")} topic="mods" />
242
243
  <p class="sub">${tn("modsLead")}</p>
243
244
  ${state.staged ? html`<${Plan} key=${state.staged.stage} staged=${state.staged} onDone=${load} />` : html`<div class="s-mod-import">
244
245
  <label class="btn"><${Icon} name="attach" size=${16} /><span>${tn("modChoose")}</span>
@@ -7,24 +7,24 @@ import { getAppEffects } from "./app-effects.js";
7
7
  import { ownerEffectsStore, watchOwnerEffects, refreshOwnerEffects, saveOwnerEffects } from "/assets/owner-effect-prefs.js";
8
8
  register("ownerEffects", {
9
9
  en: {
10
- title: "Your effect choices", operatorTitle: "Host-wide effect choices", person: "These choices follow this platform sign-in. Discord and Telegram accounts stay separate.",
11
- operator: "Host-wide choices: everyone signed in as the host operator shares these slots.",
10
+ title: "Your effect choices", operatorTitle: "Host-wide effect choices", person: "Follows your sign-in; Discord and Telegram are separate.",
11
+ operator: "Shared by everyone signed in as the host.",
12
12
  sidebar: "Sidebar", header: "Header", page: "Page", box: "Box", cursor: "Cursor", none: "None", savedChoice: "Saved choice",
13
13
  unknown: "Choices are unavailable. Reload to try again.", changed: "This page can no longer access these choices. Reload and sign in again.",
14
14
  unavailable: "No longer available", clear: "Clear", reload: "Reload choices", saved: "Saved.", failed: "Couldn't save. Reload choices and try again.",
15
15
  savedRefresh: "Saved, but the choices couldn't be refreshed. Reload to check.", readOnly: "Sign in as an admin to change these choices.",
16
- playback: "Playback requires Effects and preview consent. The cursor follows the same consent and falls back to stock cursors when unavailable.",
17
- pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
16
+ playback: "Plays once Effects and previews are allowed.",
17
+ pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to keep the page responsive.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
18
18
  },
19
19
  "zh-TW": {
20
- title: "你的效果選擇", operatorTitle: "主機共用效果選擇", person: "選擇會跟隨這個平台的登入身分;Discord 與 Telegram 帳號各自獨立。",
21
- operator: "主機共用選擇:所有以主機操作者身分登入的人,都會共用這些欄位。",
20
+ title: "你的效果選擇", operatorTitle: "主機共用效果選擇", person: "跟隨你的登入身分;Discord 與 Telegram 各自獨立。",
21
+ operator: "所有以主機身分登入的人共用。",
22
22
  sidebar: "側邊欄", header: "頁首", page: "頁面", box: "區塊", cursor: "游標", none: "無", savedChoice: "已儲存的選擇",
23
23
  unknown: "目前無法取得選擇,請重新載入再試。", changed: "這個頁面已無法存取選擇,請重新載入並登入。",
24
24
  unavailable: "已無法使用", clear: "清除", reload: "重新載入選擇", saved: "已儲存。", failed: "無法儲存,請重新載入選擇再試。",
25
25
  savedRefresh: "已儲存,但無法更新畫面;請重新載入確認。", readOnly: "請以管理員身分登入才能修改選擇。",
26
- playback: "開啟效果並允許預覽後才會播放;游標也遵循相同的同意設定;無法使用時會還原一般游標。",
27
- pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
26
+ playback: "開啟效果並允許預覽後才會播放。",
27
+ pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
28
28
  },
29
29
  });
30
30
  const text = key => t(`ownerEffects.${key}`);
@@ -14,7 +14,7 @@ register("settings", {
14
14
  publicTtl: "Public link minutes (1–480)",
15
15
  publicProtocol: "Tunnel protocol",
16
16
  webEcho: "Web echo",
17
- webEchoHint: "Copies web messages to the bound Telegram or Discord topic. ClassicBot rooms are excluded.",
17
+ webEchoHint: "Copies web messages to the bound Telegram or Discord topic. ClassicBot rooms follow it unless a room sets its own.",
18
18
  webLogTab: "Log tab: each agent's steps", webLogTabHint: "Shows each instance's prompts, replies, tool calls and results, and fleet events in its Log tab, read from its own transcript only while the tab is open. Secret values are masked. Also over the public link. Turning it on waits for a fleet admin's confirmation.",
19
19
  general: "General Settings",
20
20
  advanced: "Advanced",
@@ -88,7 +88,6 @@ register("settings", {
88
88
  wake: "Wake",
89
89
  accessMode: "Access mode",
90
90
  affected: "Affected instances",
91
- webEchoGroupConfirm: "This looks like a group channel: enabling posts every web message to this instance where all channel members can read it. Enable web echo?",
92
91
  language: "Language (bot replies)",
93
92
  defaultBackend: "Default backend",
94
93
  defaultModel: "Default model",
@@ -290,17 +289,18 @@ register("settings", {
290
289
  sec_web: "Web",
291
290
  // #1629 r4 §3: Settings → Appearance (owner CSS).
292
291
  sec_appearance: "Web appearance",
293
- colorTitle: "Identity colours", colorLead: "Each agent's colour in the sidebar: a person's pick, the agent's own, its bot's Discord role colour, or an automatic soft colour. Change one agent's from its ⋯ menu → Colour….",
294
- colorPlaces: "Where colours show, unless an agent's own setting says otherwise", colorOff: "Turn off colours",
295
- colorOffLead: "Hides every identity colour on this host. The colours people picked are kept, and come back when you turn colours on again.",
296
- restoreTitle: "Restore defaults", restoreLead: "Puts this host's appearance settings back to their defaults. It asks first and says what it resets.",
297
- restoreConfirm: "This resets, for this host: showing agents' themes and effects, the card size and each card's own size, your sidebar widget, where colours show, and whether colours are off. It also turns your custom CSS off (its versions are kept) and its switch on this device. The colours people picked for agents, installed mods, uploaded effects and the card limits stay as they are.",
292
+ groupLook: "Look", groupLayout: "Your layout", groupCss: "Styles you write", groupMods: "Add-ons", groupAdvanced: "Advanced", learnMore: "Learn more: {0}",
293
+ colorTitle: "Identity colours", colorLead: "One agent's colour: its ⋯ menu → Colour…",
294
+ colorPlaces: "Where colours show", colorSample: "Sample agent", colorOff: "Turn off colours",
295
+ colorOffLead: "Colours people picked are kept.",
296
+ restoreTitle: "Restore defaults", restoreLead: "Puts this host's look back to the defaults. Asks first.",
297
+ restoreConfirm: "Resets, for this host: showing agents' themes and effects, the card size and each card's own size, your sidebar widget, where colours show, and colours off. Also turns your custom CSS off (its versions are kept) and its switch on this device. Kept: the colours people picked for agents, installed mods, uploaded effects and the card limits.",
298
298
  restoreGo: "Restore defaults", restoreDone: "Appearance settings are back to their defaults.", restoreCssFailed: "The settings are back to their defaults, but your custom CSS could not be turned off: {0}",
299
299
  cardSizeTitle: "Identity cards",
300
- cardSizeLead: "How large identity cards are drawn on all your devices. A card's own S / M / L sets that card apart.",
300
+ cardSizeLead: "A card's own S / M / L overrides this.",
301
301
  cardLimitsTitle: "Identity card upload limits",
302
- cardLimitsLead: "The largest portrait and banner anyone may upload, and how many pictures each agent may keep. Empty = the default (shown greyed). A lower limit applies at once; a higher one waits for a fleet admin's confirmation. Pictures already stored stay.",
303
- cardLimitsUnavailable: "Only a signed-in admin can see and change these here (or edit web.appearance in fleet.yaml).",
302
+ cardLimitsLead: "Empty = the default. Raising a limit needs an admin's OK.",
303
+ cardLimitsUnavailable: "Only a signed-in admin can change these.",
304
304
  cardLimit_portrait_max_width: "Portrait width (px)", cardLimit_portrait_max_height: "Portrait height (px)", cardLimit_portrait_max_kib: "Portrait size (KiB)",
305
305
  cardLimit_banner_max_width: "Banner width (px)", cardLimit_banner_max_height: "Banner height (px)", cardLimit_banner_max_kib: "Banner size (KiB)",
306
306
  cardLimit_instance_quota_files: "Pictures per agent", cardLimit_instance_quota_mib: "Picture space per agent (MiB)",
@@ -312,10 +312,10 @@ register("settings", {
312
312
  cardLimitsFailed: "Not saved: {0}",
313
313
  themesTitle: "Agents' themes",
314
314
  themesShow: "Show agents' themes, effects and widgets",
315
- themesShowLead: "On all your devices. When it's off, every chat looks the same, and agents' pictures and ring colours are their bots' again.",
315
+ themesShowLead: "When off, every chat looks the same.",
316
316
  themesSafeMode: "This page was opened with ?safe=1: agents' themes are not shown on it.",
317
317
  widgetTitle: "Your sidebar widget",
318
- widgetLead: "A small panel under the instance list, on all your devices: the dashboard draws it from a spec and values you write here as JSON — no HTML, script or CSS. Its live values are your fleet's counts (fleet.working, fleet.needs_you, fleet.idle, fleet.stopped). Agents can't change it.",
318
+ widgetLead: "A small panel under your agent list, written as JSON.",
319
319
  widgetNotJson: "Not JSON.",
320
320
  widgetSave: "Save widget",
321
321
  widgetSaved: "Sidebar widget saved.",
@@ -323,7 +323,7 @@ register("settings", {
323
323
  widgetRemove: "Remove widget",
324
324
  widgetRemoved: "Sidebar widget removed.",
325
325
  modsTitle: "Mods",
326
- modsLead: "A mod is one file (.agendmod.json) that carries a look: an agent's theme and pictures, its card text, widgets, effects, and custom CSS. Importing shows everything in it first; only what you tick is applied, all at once. Uninstalling removes the mod from this list; what it applied stays until you reset it.",
326
+ modsLead: "Only the parts you tick are applied.",
327
327
  modsLoadFailed: "Couldn't load the installed mods. Reload the page to try again.",
328
328
  modsNone: "No mods installed.",
329
329
  modChoose: "Import a mod file…",
@@ -360,7 +360,7 @@ register("settings", {
360
360
  modUninstallConfirm: "Uninstall {0}? Its effects leave your library unless something still uses them. What it applied stays.",
361
361
  modUninstalled: "Uninstalled {0}.",
362
362
  modExport: "Export a mod",
363
- modExportLead: "Make a mod file from what is in force now: an agent's theme, card text and widgets, your sidebar widget, your custom CSS.",
363
+ modExportLead: "Save what's in force now as a mod file.",
364
364
  modExportInstance: "Agent",
365
365
  modExportName: "Name (a-z, 0-9, -)",
366
366
  modExportVersion: "Version",
@@ -372,7 +372,7 @@ register("settings", {
372
372
  modReview: "Review",
373
373
  modDismiss: "Dismiss",
374
374
  cssTitle: "Custom CSS (yours only)",
375
- cssLead: "Your own stylesheet, applied to your screens only: every device where you are signed in, never anyone else's, and never on Settings. It can't load anything from the internet. Put pictures and fonts under Assets below and name them url(\"asset:<id>\").",
375
+ cssLead: "Applies to your screens only.",
376
376
  cssLoadFailed: "Couldn't load your custom CSS. Reload the page to try again.",
377
377
  cssPublicLink: "Custom CSS isn't available on a temporary public link.",
378
378
  cssInForce: "In force: {0}",
@@ -385,15 +385,14 @@ register("settings", {
385
385
  cssCurrent: "in force",
386
386
  cssRestore: "Use this",
387
387
  cssAssets: "Assets",
388
- cssAssetsLead: "PNG, WebP, JPEG, GIF or WOFF2. {0} of {1} files, {2} in total (at most 1 MiB). Metadata such as GPS is removed.",
388
+ cssAssetsLead: "{0} of {1} files, {2} in total (at most 1 MiB in all).",
389
389
  cssAssetAdd: "Add a picture or font",
390
390
  cssAssetCopy: "Copy reference",
391
391
  cssCopied: "Copied {0}",
392
392
  cssAssetRemove: "Remove",
393
393
  cssDeviceTitle: "On this device",
394
394
  cssDeviceOff: "Turn off custom CSS on this device",
395
- cssDeviceOffLead: "Only this browser. Your other devices keep it.",
396
- cssSafe: "Is something hidden or broken? Add ?safe=1 to the end of any page's address and it loads without custom CSS.",
395
+ cssSafe: "Something hidden? Add ?safe=1 to the address.",
397
396
  cssError_at_rule: "Not saved: {0} isn't allowed.",
398
397
  cssError_url: "Not saved: the only url() allowed is url(\"asset:<id>\") for a file in Assets.",
399
398
  cssError_unknown_asset: "Not saved: there's no asset {0}. Add it under Assets first.",
@@ -491,7 +490,6 @@ register("settings", {
491
490
  discordUserPlaceholder: "your Discord user id",
492
491
  telegramUserPlaceholder: "your Telegram user id",
493
492
  bindingApplyButton: "Rebind",
494
- webEchoEnable: "Turn on web echo",
495
493
  help1: "Agents: each agent's settings, Start/Stop, Pause/Wake, and New agent.",
496
494
  help2: "Connections: who may use each bot, its binding, its status emojis, a new token; and provider API keys.",
497
495
  help3: "ClassicBot: each room's backend, model and options.",
@@ -535,7 +533,7 @@ register("settings", {
535
533
  publicTtl: "公開連結分鐘數(1–480)",
536
534
  publicProtocol: "Tunnel 協定",
537
535
  webEcho: "Web 同步",
538
- webEchoHint: "將網頁訊息複製到綁定的 Telegram 或 Discord 主題,不包含 ClassicBot 群組。",
536
+ webEchoHint: "將網頁訊息複製到綁定的 Telegram 或 Discord 主題。ClassicBot 聊天室也照這個設定,除非該聊天室另外設定。",
539
537
  webLogTab: "紀錄分頁:每個 agent 的步驟", webLogTabHint: "在每個 instance 的「紀錄」分頁顯示提示、回覆、工具呼叫與結果,以及 fleet 事件;只在分頁開著時從它自己的對話紀錄讀取。機密值會遮蔽。公開連結也看得到。開啟需 fleet admin 確認。",
540
538
  general: "一般設定",
541
539
  advanced: "進階",
@@ -584,7 +582,6 @@ register("settings", {
584
582
  wake: "喚醒",
585
583
  accessMode: "存取模式",
586
584
  affected: "受影響 instances",
587
- webEchoGroupConfirm: "這看起來是群組頻道:開啟後,這個 instance 的每則 web 訊息都會貼到所有頻道成員看得到的地方。確定要開啟 web echo?",
588
585
  language: "語言(bot 回覆)",
589
586
  defaultBackend: "預設後端",
590
587
  defaultModel: "預設模型",
@@ -785,17 +782,18 @@ register("settings", {
785
782
  webChat: "網頁聊天",
786
783
  sec_web: "網頁",
787
784
  sec_appearance: "網頁外觀",
788
- colorTitle: "識別色", colorLead: "每個 agent 在側欄的顏色:有人挑選的、agent 自己的、它的 bot 在 Discord 身分組的顏色,或自動配的柔和色。要改某個 agent 的顏色,從它的 ⋯ 選單 → 顏色…。",
789
- colorPlaces: "顏色顯示的位置(agent 自己另有設定時除外)", colorOff: "關閉顏色",
790
- colorOffLead: "在這台主機隱藏所有識別色。有人挑過的顏色會保留,重新開啟顏色後就會回來。",
791
- restoreTitle: "還原預設值", restoreLead: "把這台主機的外觀設定改回預設值。執行前會先詢問,並說明會重設哪些項目。",
792
- restoreConfirm: "會重設這台主機的:是否顯示 agent 的主題與效果、卡片大小與各張卡片自己的大小、你的側欄小工具、顏色顯示的位置,以及是否關閉顏色;也會關閉你的自訂 CSS(版本會保留)以及這台裝置上的 CSS 開關。有人替 agent 挑的顏色、已安裝的 mod、上傳的效果與卡片上限都不會變。",
785
+ groupLook: "外觀", groupLayout: "你的版面", groupCss: "自己寫的樣式", groupMods: "擴充", groupAdvanced: "進階", learnMore: "了解更多:{0}",
786
+ colorTitle: "識別色", colorLead: "單一 agent 的顏色:⋯ 選單 → 顏色…",
787
+ colorPlaces: "顏色顯示的位置", colorSample: "範例 agent", colorOff: "關閉顏色",
788
+ colorOffLead: "已挑選的顏色會保留。",
789
+ restoreTitle: "還原預設值", restoreLead: "把這台主機的外觀改回預設值,會先詢問。",
790
+ restoreConfirm: "會重設這台主機的:是否顯示 agent 的主題與效果、卡片大小與各張卡片自己的大小、你的側欄小工具、顏色顯示的位置、是否關閉顏色;也會關閉你的自訂 CSS(版本會保留)與這台裝置上的 CSS 開關。不變:有人替 agent 挑的顏色、已安裝的 mod、上傳的效果與卡片上限。",
793
791
  restoreGo: "還原預設值", restoreDone: "外觀設定已改回預設值。", restoreCssFailed: "設定已改回預設值,但無法關閉你的自訂 CSS:{0}",
794
792
  cardSizeTitle: "身分卡",
795
- cardSizeLead: "身分卡在你所有裝置上的顯示大小。卡片上自己的 小/中/大 可以單獨設定那張卡片。",
793
+ cardSizeLead: "卡片上的 小/中/大 可單獨設定。",
796
794
  cardLimitsTitle: "身分卡上傳限制",
797
- cardLimitsLead: "任何人可上傳的肖像與橫幅上限,以及每個 agent 可保留的圖片數量。留空=預設值(以灰字顯示)。調低立即生效;調高需 fleet admin 確認。已存的圖片不受影響。",
798
- cardLimitsUnavailable: "只有已登入的管理者能在這裡查看與修改(或在 fleet.yaml 編輯 web.appearance)。",
795
+ cardLimitsLead: "留空=預設值。調高上限需管理員確認。",
796
+ cardLimitsUnavailable: "只有已登入的管理者能修改。",
799
797
  cardLimit_portrait_max_width: "肖像寬度(px)", cardLimit_portrait_max_height: "肖像高度(px)", cardLimit_portrait_max_kib: "肖像大小(KiB)",
800
798
  cardLimit_banner_max_width: "橫幅寬度(px)", cardLimit_banner_max_height: "橫幅高度(px)", cardLimit_banner_max_kib: "橫幅大小(KiB)",
801
799
  cardLimit_instance_quota_files: "每個 agent 的圖片數", cardLimit_instance_quota_mib: "每個 agent 的圖片空間(MiB)",
@@ -807,10 +805,10 @@ register("settings", {
807
805
  cardLimitsFailed: "未儲存:{0}",
808
806
  themesTitle: "Agent 的主題",
809
807
  themesShow: "顯示 agent 的主題、效果與小工具",
810
- themesShowLead: "套用到你所有的裝置。關閉時每個聊天看起來都一樣,agent 的圖片與外框顏色也會換回各自 bot 的。",
808
+ themesShowLead: "關閉時每個聊天看起來都一樣。",
811
809
  themesSafeMode: "這個頁面是用 ?safe=1 開啟的:不顯示 agent 的主題。",
812
810
  widgetTitle: "你的側邊欄小工具",
813
- widgetLead: "實例清單下方的一個小面板,套用到你所有的裝置:儀表板依你在這裡用 JSON 寫的規格與數值繪製——沒有 HTML、腳本或 CSS。即時數值是你 fleet 的計數(fleet.working、fleet.needs_you、fleet.idle、fleet.stopped)。agent 無法變更它。",
811
+ widgetLead: "agent 清單下方的小面板,用 JSON 撰寫。",
814
812
  widgetNotJson: "不是 JSON。",
815
813
  widgetSave: "儲存小工具",
816
814
  widgetSaved: "已儲存側邊欄小工具。",
@@ -818,7 +816,7 @@ register("settings", {
818
816
  widgetRemove: "移除小工具",
819
817
  widgetRemoved: "已移除側邊欄小工具。",
820
818
  modsTitle: "Mod",
821
- modsLead: "Mod 是一個檔案(.agendmod.json),帶著一套外觀:agent 的主題與圖片、身分卡文字、小工具、效果,以及自訂 CSS。匯入時會先完整列出內容,只有你勾選的部分會一次套用。解除安裝會把 mod 從這個清單移除;它已套用的外觀會保留,直到你重設。",
819
+ modsLead: "只套用你勾選的部分。",
822
820
  modsLoadFailed: "無法載入已安裝的 mod。請重新載入頁面再試一次。",
823
821
  modsNone: "尚未安裝任何 mod。",
824
822
  modChoose: "匯入 mod 檔案…",
@@ -855,7 +853,7 @@ register("settings", {
855
853
  modUninstallConfirm: "要解除安裝 {0} 嗎?它的效果會離開你的效果庫,除非還有東西在使用。它已套用的外觀會保留。",
856
854
  modUninstalled: "已解除安裝 {0}。",
857
855
  modExport: "匯出 mod",
858
- modExportLead: "用目前生效的外觀做成 mod 檔案:某個 agent 的主題、身分卡文字與小工具、你的側邊欄小工具、你的自訂 CSS。",
856
+ modExportLead: "把目前生效的外觀存成 mod 檔。",
859
857
  modExportInstance: "Agent",
860
858
  modExportName: "名稱(a-z、0-9、-)",
861
859
  modExportVersion: "版本",
@@ -867,7 +865,7 @@ register("settings", {
867
865
  modReview: "檢視",
868
866
  modDismiss: "略過",
869
867
  cssTitle: "自訂 CSS(只套用在你自己)",
870
- cssLead: "你自己的樣式表,只套用在你自己的畫面:你登入的每一台裝置都會套用,不會影響別人,也不會套用在設定頁。它無法從網路載入任何東西;圖片和字型請放在下方的「資源」,再用 url(\"asset:<id>\") 引用。",
868
+ cssLead: "只套用在你的畫面。",
871
869
  cssLoadFailed: "無法載入你的自訂 CSS。請重新整理頁面再試一次。",
872
870
  cssPublicLink: "臨時公開連結無法使用自訂 CSS。",
873
871
  cssInForce: "使用中:{0}",
@@ -880,15 +878,14 @@ register("settings", {
880
878
  cssCurrent: "使用中",
881
879
  cssRestore: "改用這版",
882
880
  cssAssets: "資源",
883
- cssAssetsLead: "PNG、WebP、JPEG、GIF 或 WOFF2。目前 {0} / {1} 個檔案,共 {2}(上限 1 MiB)。會移除 GPS 等中繼資料。",
881
+ cssAssetsLead: "{0} / {1} 個檔案,合計 {2}(全部合計最多 1 MiB)。",
884
882
  cssAssetAdd: "加入圖片或字型",
885
883
  cssAssetCopy: "複製引用",
886
884
  cssCopied: "已複製 {0}",
887
885
  cssAssetRemove: "移除",
888
886
  cssDeviceTitle: "這台裝置",
889
887
  cssDeviceOff: "在這台裝置關閉自訂 CSS",
890
- cssDeviceOffLead: "只影響這個瀏覽器,你的其他裝置照常套用。",
891
- cssSafe: "畫面被藏起來或壞掉了嗎?在任何頁面網址的最後加上 ?safe=1,就會不套用自訂 CSS 地載入。",
888
+ cssSafe: "畫面被藏起來?在網址後加上 ?safe=1。",
892
889
  cssError_at_rule: "未儲存:不允許使用 {0}。",
893
890
  cssError_url: "未儲存:url() 只能用 url(\"asset:<id>\") 引用「資源」裡的檔案。",
894
891
  cssError_unknown_asset: "未儲存:找不到資源 {0}。請先在「資源」加入它。",
@@ -986,7 +983,6 @@ register("settings", {
986
983
  discordUserPlaceholder: "你的 Discord 使用者 id",
987
984
  telegramUserPlaceholder: "你的 Telegram 使用者 id",
988
985
  bindingApplyButton: "改綁",
989
- webEchoEnable: "開啟網頁轉發",
990
986
  help1: "Agent:每個 agent 的設定、啟動/停止、暫停/喚醒,以及新增 agent。",
991
987
  help2: "連線:誰可以使用各個 bot、它的綁定、狀態表情、新的 token;以及供應商 API key。",
992
988
  help3: "ClassicBot:每個房間的 backend、model 與選項。",
@@ -60,7 +60,7 @@ export function t(key, ...values) {
60
60
 
61
61
  register("app", {
62
62
  en: {
63
- brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet overview", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance",
63
+ brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet overview", view: "View", settings: "Settings", instances: "Instances", newInstance: "New instance", newAgent: "New agent",
64
64
  collapse: "Hide the sidebar", expand: "Show the sidebar", openMenu: "Open the sidebar", closeMenu: "Close the sidebar",
65
65
  resizeSidebar: "Sidebar width", resizeSidebarHint: "Drag to resize · double-click to reset",
66
66
  names: "Names", namesNickname: "Nickname first", namesInstance: "Real name first",
@@ -92,7 +92,7 @@ register("app", {
92
92
  needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed",
93
93
  },
94
94
  "zh-TW": {
95
- brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance",
95
+ brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
96
96
  collapse: "隱藏側邊欄", expand: "顯示側邊欄", openMenu: "打開側邊欄", closeMenu: "關閉側邊欄",
97
97
  resizeSidebar: "側邊欄寬度", resizeSidebarHint: "拖曳調整寬度 · 按兩下恢復預設",
98
98
  names: "名稱", namesNickname: "暱稱優先", namesInstance: "實際名稱優先",
@@ -40,7 +40,7 @@ export const SYNCED = {
40
40
  "card.sizeByInstance": { ls: "agend_card_sizes", ...json },
41
41
  // #1629 r4-4: the person's own sidebar widget ({ spec, data }), drawn by the page's own components.
42
42
  "widget.sidebar": { ls: "agend_widget_sidebar", ...json },
43
- // #1958: where identity colours show by default (ring + bar unless chosen), and the host's switch to turn them off.
43
+ // #1958: where identity colours show by default (#2056: name, bar and ring unless chosen), and the host's switch to turn them off.
44
44
  "color.show": { ls: "agend_color_show", ...json },
45
45
  "color.off": { ls: "agend_color_off", toLocal: (v) => (v === true ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
46
46
  };
@@ -39,6 +39,25 @@ let newInstanceOpener = null;
39
39
  export function setNewInstanceOpener(fn) { newInstanceOpener = fn; }
40
40
  export function requestNewInstance() { if (newInstanceOpener) newInstanceOpener(); }
41
41
 
42
+ /**
43
+ * #2074: whether this page's session may make an agent — an admin's (/auth/session's tier); a guest's create is refused.
44
+ * Asked once per page; a session that ends takes the buttons away. A display hint only: the create route decides.
45
+ */
46
+ export const canCreateStore = createStore({ value: false });
47
+ let canCreateOf = null; // the AgendAuth asked (one per page)
48
+ export function useCanCreate() {
49
+ const s = useStore(canCreateStore);
50
+ useEffect(() => {
51
+ const auth = typeof window !== "undefined" ? window.AgendAuth : undefined;
52
+ if (!auth || typeof auth.canWrite !== "function" || canCreateOf === auth) return;
53
+ canCreateOf = auth;
54
+ canCreateStore.set({ value: false });
55
+ Promise.resolve(auth.canWrite()).then((v) => { if (canCreateOf === auth && !auth.sessionEnded?.()) canCreateStore.set({ value: v === true }); }, () => {});
56
+ auth.onSessionEnd?.(() => { if (canCreateOf === auth) canCreateStore.set({ value: false }); });
57
+ }, []);
58
+ return s.value;
59
+ }
60
+
42
61
  /** An app-level dialog (New instance): rendered by the shell until closed, whatever panel is showing. */
43
62
  export function showDialog(Component, props = {}) { shellStore.set({ dialog: { Component, props } }); }
44
63
  export function closeDialog() { shellStore.set({ dialog: null }); }
@@ -247,6 +266,7 @@ export function signInHref() {
247
266
  function Sidebar({ route, onNewInstance, viewOnly }) {
248
267
  const app = useStore(appStore);
249
268
  const shell = useStore(shellStore);
269
+ const canCreate = useCanCreate();
250
270
  // #1675: the Fleet panel is "Fleet overview"'s (one current row); the "Fleet" row is current on an instance's Details.
251
271
  const on = (panel) => !!route && (panel === "fleet" ? route.panel === "details" : route.panel === panel);
252
272
  const openInstance = openInstanceOf(route);
@@ -280,13 +300,13 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
280
300
  <div class="side-head" data-trust="navigation">
281
301
  <a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
282
302
  <button type="button" class="icon-btn side-collapse" onClick=${toggleSidebar} aria-label=${narrow() ? t("app.closeMenu") : t("app.collapse")} title=${narrow() ? t("app.closeMenu") : t("app.collapse")} aria-controls="sidebar"><${Icon} name="sidebar" /></button>
283
- <button type="button" class="icon-btn" onClick=${onNewInstance} aria-label=${t("app.newInstance")} title=${t("app.newInstance")}><${Icon} name="plus" /></button>
284
303
  </div>
285
304
  <nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>
286
305
  ${overview}
287
306
  ${navLink("needs", NEEDS_PATH, "inbox", t("app.needsNav"), needsCount)}
288
307
  ${navLink("fleet", openInstance ? detailsPath(openInstance) : "/ui/fleet", "fleet", t("app.fleet"))}
289
308
  ${navLink("view", "/view", "view", t("app.view"))}
309
+ ${canCreate && onNewInstance ? html`<button type="button" class="side-row side-new" onClick=${onNewInstance}><${Icon} name="plus" /><span class="grow">${t("app.newAgent")}</span></button>` : null}
290
310
  </nav>
291
311
  ${shell.side ? html`<${shell.side.Component} />` : html`<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false}
292
312
  exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />`}
@@ -349,9 +369,11 @@ function PrefsPopover() {
349
369
  */
350
370
  export function PanelHeader({ title, sub, children, headingRef, nav }) {
351
371
  const shell = useStore(shellStore);
372
+ const canCreate = useCanCreate() && !!newInstanceOpener;
352
373
  return html`<header data-trust="header" class=${`panel-head${shell.collapsed ? " collapsed" : ""}`}>
353
374
  <button type="button" id="sbOpen" class="icon-btn sb-open" onClick=${() => (narrow() ? openDrawer() : toggleSidebar())}
354
375
  aria-label=${narrow() ? t("app.openMenu") : t("app.expand")} title=${narrow() ? t("app.openMenu") : t("app.expand")} aria-controls="sidebar" aria-expanded=${shell.drawer ? "true" : "false"}><${Icon} name="menu" /></button>
376
+ ${canCreate && shell.collapsed ? html`<button type="button" class="icon-btn sb-new" onClick=${requestNewInstance} aria-label=${t("app.newAgent")} title=${t("app.newAgent")}><${Icon} name="plus" /></button>` : null}
355
377
  <div class="panel-title"><h1 ref=${headingRef} tabindex="-1">${title}</h1>${sub ? html`<div class="panel-sub">${sub}</div>` : null}</div>
356
378
  ${nav || null}
357
379
  <div class="panel-actions">${children}</div>
@@ -42,7 +42,18 @@ export function applyStatus(d) {
42
42
  // #1629: the agents' themes' revision (app-themes.js re-reads them when it moves).
43
43
  themesRev: typeof d.themes_rev === "number" ? d.themes_rev : null,
44
44
  // #1685: host memory pressure slowing or holding new agents ("elevated" | "critical"), else null.
45
- hostMemory: d.host_memory === "elevated" || d.host_memory === "critical" ? d.host_memory : null });
45
+ hostMemory: d.host_memory === "elevated" || d.host_memory === "critical" ? d.host_memory : null,
46
+ // #2015: while it lasts, the largest processes in the fleet and whose they are (at most 3; anything else dropped).
47
+ hostMemoryTop: hostMemoryTop(d.host_memory_top) });
48
+ }
49
+
50
+ /** #2015: the status frame's host_memory_top, as the dashboard keeps it: well-formed entries only, at most 3. */
51
+ export function hostMemoryTop(raw) {
52
+ if (!Array.isArray(raw)) return [];
53
+ return raw.slice(0, 3).flatMap(p => p && typeof p.name === "string" && Number.isSafeInteger(p.pid) && Number.isSafeInteger(p.rssBytes) && p.rssBytes >= 0
54
+ && ["fleet", "tmux", "instance", "other"].includes(p.owner)
55
+ ? [{ name: p.name.slice(0, 64), pid: p.pid, rssBytes: p.rssBytes, owner: p.owner, ...(p.owner === "instance" && typeof p.instance === "string" ? { instance: p.instance.slice(0, 128) } : {}) }]
56
+ : []);
46
57
  }
47
58
 
48
59
  /** One "activity" event: an instance started or stopped working. */
@@ -30,7 +30,8 @@
30
30
  // - suspend()/resume(): the page is leaving (pagehide) / came back from the back-forward cache.
31
31
 
32
32
  // #1218: steps_tick — an instance's transcript moved while a Log watches it (SSE only: a poll page asks the Log itself).
33
- const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock"];
33
+ // #1918: traffic — a message between instances moved (SSE only: the org canvas reads /ui/traffic when it has no stream).
34
+ const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock", "traffic"];
34
35
  const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
35
36
  const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
36
37
  const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed