@songsid/agend 2.3.0-beta.11 → 2.3.0-beta.13

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 (111) hide show
  1. package/README.md +6 -0
  2. package/README.zh-TW.md +6 -0
  3. package/dist/agent-whats-new.js +14 -9
  4. package/dist/agent-whats-new.js.map +1 -1
  5. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  6. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  7. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  8. package/dist/channel/adapters/discord.d.ts +2 -0
  9. package/dist/channel/adapters/discord.js +14 -4
  10. package/dist/channel/adapters/discord.js.map +1 -1
  11. package/dist/cli.js +12 -79
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +12 -0
  14. package/dist/config.js +13 -4
  15. package/dist/config.js.map +1 -1
  16. package/dist/daemon.d.ts +3 -0
  17. package/dist/daemon.js +25 -0
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/fleet-manager.d.ts +44 -2
  20. package/dist/fleet-manager.js +229 -63
  21. package/dist/fleet-manager.js.map +1 -1
  22. package/dist/fleet-process-handlers.d.ts +18 -0
  23. package/dist/fleet-process-handlers.js +69 -0
  24. package/dist/fleet-process-handlers.js.map +1 -0
  25. package/dist/general-knowledge/skills/fleet-config/SKILL.md +2 -0
  26. package/dist/general-knowledge/skills/web-appearance/SKILL.md +20 -3
  27. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +27 -5
  28. package/dist/instance-lifecycle.d.ts +2 -0
  29. package/dist/instance-lifecycle.js +11 -6
  30. package/dist/instance-lifecycle.js.map +1 -1
  31. package/dist/outbound-schemas.d.ts +14 -1
  32. package/dist/outbound-schemas.js +8 -2
  33. package/dist/outbound-schemas.js.map +1 -1
  34. package/dist/public-web-gateway.js +4 -0
  35. package/dist/public-web-gateway.js.map +1 -1
  36. package/dist/restart-progress.d.ts +16 -1
  37. package/dist/restart-progress.js +74 -15
  38. package/dist/restart-progress.js.map +1 -1
  39. package/dist/settings-api.d.ts +4 -0
  40. package/dist/settings-api.js +27 -1
  41. package/dist/settings-api.js.map +1 -1
  42. package/dist/settings-confirmation.d.ts +3 -1
  43. package/dist/settings-confirmation.js +2 -0
  44. package/dist/settings-confirmation.js.map +1 -1
  45. package/dist/settings-effect.js +2 -1
  46. package/dist/settings-effect.js.map +1 -1
  47. package/dist/settings-http-confirmation.d.ts +11 -0
  48. package/dist/settings-http-confirmation.js +10 -0
  49. package/dist/settings-http-confirmation.js.map +1 -1
  50. package/dist/ui/app-effects.js +13 -1
  51. package/dist/ui/chat-effects.js +10 -3
  52. package/dist/ui/chat-store.js +32 -10
  53. package/dist/ui/chat-strings.js +18 -0
  54. package/dist/ui/chat-thread.js +5 -2
  55. package/dist/ui/effects-waker.js +70 -0
  56. package/dist/ui/fleet-canvas-layout.js +10 -4
  57. package/dist/ui/fleet-canvas.js +32 -2
  58. package/dist/ui/fleet-route-scene.js +207 -0
  59. package/dist/ui/fleet-route-worker.js +21 -0
  60. package/dist/ui/fleet-route.js +88 -0
  61. package/dist/ui/fleet-traffic.js +27 -11
  62. package/dist/ui/fleet-usage.js +35 -1
  63. package/dist/ui/panel-chat.js +67 -26
  64. package/dist/ui/panel-fleet.js +3 -3
  65. package/dist/ui/panel-settings.js +24 -0
  66. package/dist/ui/settings-appearance.js +2 -1
  67. package/dist/ui/settings-apply.js +3 -3
  68. package/dist/ui/settings-confirm.js +7 -2
  69. package/dist/ui/settings-owner-effects.js +6 -4
  70. package/dist/ui/settings-strings.js +2 -0
  71. package/dist/ui/shared/app-i18n.js +4 -0
  72. package/dist/ui/shared/app-prefs.js +12 -0
  73. package/dist/ui/shared/app-shell.js +38 -2
  74. package/dist/ui/shared/app-themes.js +116 -1
  75. package/dist/ui/shared/app.css +47 -3
  76. package/dist/ui/shared/card-strings.js +4 -0
  77. package/dist/ui/shared/effect-owner-source.js +8 -1
  78. package/dist/ui/shared/effect-registry.js +10 -2
  79. package/dist/ui/shared/identity-card.js +24 -6
  80. package/dist/ui/shared/identity-color.js +11 -7
  81. package/dist/ui/shared/owner-css.js +30 -1
  82. package/dist/ui/shared/ui-icons.js +2 -0
  83. package/dist/ui/shared/ui-menu.js +9 -3
  84. package/dist/ui/shared/web-authorize.js +47 -0
  85. package/dist/web-api.d.ts +6 -2
  86. package/dist/web-api.js +68 -15
  87. package/dist/web-api.js.map +1 -1
  88. package/dist/web-appearance.js +3 -1
  89. package/dist/web-appearance.js.map +1 -1
  90. package/dist/web-effect.d.ts +1 -1
  91. package/dist/web-host-guard.d.ts +11 -0
  92. package/dist/web-host-guard.js +11 -0
  93. package/dist/web-host-guard.js.map +1 -1
  94. package/dist/web-libavoid.d.ts +14 -0
  95. package/dist/web-libavoid.js +46 -0
  96. package/dist/web-libavoid.js.map +1 -0
  97. package/dist/web-owner-css-store.d.ts +44 -1
  98. package/dist/web-owner-css-store.js +70 -9
  99. package/dist/web-owner-css-store.js.map +1 -1
  100. package/dist/web-prefs.js +13 -0
  101. package/dist/web-prefs.js.map +1 -1
  102. package/dist/web-settings-tools.d.ts +5 -0
  103. package/dist/web-settings-tools.js +38 -7
  104. package/dist/web-settings-tools.js.map +1 -1
  105. package/dist/web-theme.d.ts +29 -0
  106. package/dist/web-theme.js +89 -1
  107. package/dist/web-theme.js.map +1 -1
  108. package/dist/yaml-shared-nodes.d.ts +25 -0
  109. package/dist/yaml-shared-nodes.js +185 -0
  110. package/dist/yaml-shared-nodes.js.map +1 -0
  111. package/package.json +2 -1
@@ -0,0 +1,18 @@
1
+ /** Synchronous evidence before shutdown: neither a network nor logger worker is required. */
2
+ export declare function recordFleetFatal(dataDir: string, error: unknown): void;
3
+ interface FleetProcessOptions {
4
+ dataDir: string;
5
+ manager: {
6
+ stopAll(): Promise<void>;
7
+ notifyFleetError(message: string): void;
8
+ };
9
+ runtime: Pick<NodeJS.Process, "on" | "exit">;
10
+ describeSignal: () => string;
11
+ console?: Pick<Console, "log" | "error">;
12
+ writeFatal?: typeof recordFleetFatal;
13
+ }
14
+ /** Identical process policy for cold start and the legacy in-process reload. */
15
+ export declare function installFleetProcessHandlers(options: FleetProcessOptions): {
16
+ readonly stopping: boolean;
17
+ };
18
+ export {};
@@ -0,0 +1,69 @@
1
+ import { closeSync, fsyncSync, mkdirSync, openSync, writeSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ /** Synchronous evidence before shutdown: neither a network nor logger worker is required. */
4
+ export function recordFleetFatal(dataDir, error) {
5
+ mkdirSync(dataDir, { recursive: true });
6
+ const fd = openSync(join(dataDir, "fleet-fatal.log"), "a", 0o600);
7
+ try {
8
+ writeSync(fd, JSON.stringify({ timestamp: new Date().toISOString(), pid: process.pid,
9
+ argv: process.argv, reason: "uncaughtException", error: String(error),
10
+ stack: error instanceof Error ? error.stack : undefined }) + "\n");
11
+ fsyncSync(fd);
12
+ }
13
+ finally {
14
+ closeSync(fd);
15
+ }
16
+ }
17
+ /** Identical process policy for cold start and the legacy in-process reload. */
18
+ export function installFleetProcessHandlers(options) {
19
+ const { runtime, manager } = options;
20
+ const output = options.console ?? console;
21
+ let stopping = false;
22
+ const shutdown = async () => {
23
+ if (stopping)
24
+ return;
25
+ stopping = true;
26
+ output.log("\nStopping fleet...");
27
+ // stopAll fences new controls and drains CPU profiles before disposal.
28
+ await manager.stopAll().catch(error => output.error("Shutdown error:", error));
29
+ runtime.exit(0);
30
+ };
31
+ runtime.on("SIGINT", shutdown);
32
+ runtime.on("SIGTERM", () => {
33
+ output.log(options.describeSignal());
34
+ return shutdown();
35
+ });
36
+ runtime.on("uncaughtException", async (error) => {
37
+ output.error("Uncaught exception:", error);
38
+ try {
39
+ (options.writeFatal ?? recordFleetFatal)(options.dataDir, error);
40
+ }
41
+ catch (writeError) {
42
+ output.error("Failed to persist fleet fatal reason:", writeError);
43
+ }
44
+ if (stopping)
45
+ return;
46
+ stopping = true;
47
+ // Arbitrary uncaught exceptions still leave unknown state and remain fatal.
48
+ await manager.stopAll().catch(() => { });
49
+ runtime.exit(1);
50
+ });
51
+ runtime.on("unhandledRejection", error => {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ if (message.includes("409") && message.includes("getUpdates")) {
54
+ output.error("Bot polling conflict (409) — retrying...");
55
+ return;
56
+ }
57
+ output.error("Unhandled rejection (fleet continues):", error);
58
+ if (error instanceof Error && error.stack)
59
+ output.error(error.stack);
60
+ try {
61
+ manager.notifyFleetError(`⚠️ Fleet caught an unhandled error and kept running:\n\`${message}\`\nSee fleet.log for the stack trace.`);
62
+ }
63
+ catch (notifyError) {
64
+ output.error("Failed to report unhandled rejection:", notifyError);
65
+ }
66
+ });
67
+ return { get stopping() { return stopping; } };
68
+ }
69
+ //# sourceMappingURL=fleet-process-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-process-handlers.js","sourceRoot":"","sources":["../src/fleet-process-handlers.ts"],"names":[],"mappings":"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"}
@@ -84,6 +84,8 @@ Fix all errors before `agend reload`; warnings are advisory.
84
84
  - `topic_id` as string vs number (both work, but be consistent)
85
85
  - `backend` typo (valid: `claude-code`, `codex`, `opencode`, `kiro-cli`, `antigravity`, `grok`, `muse`); `gemini-cli` was removed in 2.1.12 — use `antigravity`
86
86
  - `model` using wrong format for the backend
87
+ - YAML anchors/aliases (`&x` … `*x`) are fine, but a Settings or tool edit of one place gives that place its own
88
+ value; the others keep theirs (#2208). To change every place, edit fleet.yaml itself.
87
89
 
88
90
  **Common classicBot.yaml mistakes:**
89
91
  - `allowed_guilds` values should be quoted strings: numbers are accepted, but a Discord ID above 2^53 loses digits as a YAML integer
@@ -14,6 +14,7 @@ can shape how you look there — **only your own look, and only on the surfaces
14
14
  | Your name and role line | display name, description | `set_display_name`, `set_description` |
15
15
  | Your status stamp | persona emoji | `set_persona_emoji` (see the `persona-emoji` skill) |
16
16
  | Your chat | theme: colours, accent, bubbles, font, background picture, working label | `set_web_theme`, `set_web_theme_asset` |
17
+ | The whole page, while your chat is open | your theme's colours on the sidebar, the header, the usage rail (a phone: the drawer, the header, the tabs) — colours only | `set_web_theme` (`page`) |
17
18
  | Your face and ring | theme picture and accent | `set_web_theme`, `set_web_theme_asset` |
18
19
  | Your identity card | portrait, banner, tagline, bio | `set_web_theme` (`card.*`), `set_web_theme_asset` |
19
20
  | Your identity colour | the colour people find you by in the sidebar, and where it shows (name, bar, ring) | `set_web_theme` (`identity`) |
@@ -23,7 +24,7 @@ can shape how you look there — **only your own look, and only on the surfaces
23
24
  | Effects in your chat | Canvas Worker animations, and Try cards an admin outside the public link may start | `upload_web_effect`, `apply_web_effect`, `preview_web_effect`, … (see Effects) |
24
25
 
25
26
  None of these tools takes a target: they always act on you. You can't change another instance, the dashboard's own
26
- chrome, a person's sidebar widget, or a person's custom CSS.
27
+ chrome (beyond your colours on it while your chat is open), a person's sidebar widget, or a person's custom CSS.
27
28
 
28
29
  ## For the user: Settings → Web appearance (General, coordinators, full)
29
30
 
@@ -40,10 +41,11 @@ of this skill).
40
41
 
41
42
  | `what` | Changes | Example |
42
43
  |---|---|---|
43
- | `host` | `themes_shown`, `card_size` (s/m/l), `card_sizes` ({instance: s/m/l}), `colour_places` (name/bar/ring), `colours_off`, `sidebar_widget` ({spec, data}) — null puts one back to its default | `{ what: "host", set: { colour_places: ["name","ring"] } }` |
44
+ | `host` | `themes_shown`, `card_size` (s/m/l), `card_sizes` ({instance: s/m/l}), `page_colours` ({instance: page/chat}), `colour_places` (name/bar/ring), `colours_off`, `sidebar_widget` ({spec, data}) — null puts one back to its default | `{ what: "host", set: { colour_places: ["name","ring"] } }` |
44
45
  | `agent_colour` | one agent's colour (`preset:<name>` or `#rrggbb`) and places; null resets each | `{ what: "agent_colour", instance: "atlas", colour: "preset:teal" }` |
46
+ | `page_colours` | where one agent's theme colours go: `page` (the whole page), `chat` (only its chat) or `agent` (its own `page.on`) — as ⋯ → Page colours sets it | `{ what: "page_colours", instance: "atlas", choice: "chat" }` |
45
47
  | `banner_focus` | which part of one agent's cropped card banner stays in view: `focus` 0 (top) … 100 (bottom), 50 the centre; it wins over the agent's own `card.bannerFocus`; null follows the agent's again | `{ what: "banner_focus", instance: "atlas", focus: 20 }` |
46
- | `custom_css` | `save` (with `text`): goes **on trial** on their screens (it goes back by itself unless kept); `keep` (the trial), `revert` (the trial), `use` (with `version`: an earlier saved one, at once), `off` | `{ what: "custom_css", action: "save", text: "…" }`; ask how it looks, then `keep` only when they say so |
48
+ | `custom_css` | `save` (with `text`): goes **on trial**. It waits until they open the dashboard (up to 10 minutes; never opened, it goes back unseen), then goes back by itself 20 s after it is on their screen unless kept (#2198). `keep` (with `trial`: the id `save` returned — a different trial on now, say one they saved on their page since, is refused with `trial_mismatch`, never kept instead; or, kept after that 20 s ran out, it is put back in force as `use` of it), `revert` (the trial), `use` (with `version`: an earlier saved one, at once), `off` | `{ what: "custom_css", action: "save", text: "…" }`; tell them to open the dashboard and look, then `keep` (with that `trial`) only when they say so — a `keep` that comes too late (`not_on_trial`) means use `use` with the version from `get_web_settings` |
47
49
  | `mod` | an agent's proposal: `review` (instance → `stage`, `plan`, `plan_hash`), then `install` (stage, plan_hash, `include`: the parts, `target`: the agent); `cancel`, `dismiss`, `uninstall` (id) | review, show the user the plan, install only what they asked for — never custom CSS unasked. A person has one review open at a time: while theirs is open (`mod_review_open`) ask them to finish it; a mod they imported from a file is theirs to install (`mod_not_from_an_agent`) |
48
50
  | `effect` | `apply` (instance, id, params?) or `remove` (instance, id?) an effect on a chat | |
49
51
  | `ambient` | one agent's ambient effect, set for the user: `ambient` is the same spec as `set_web_ambient`'s (it replaces the one of its kind, and the agent can't change it after); `ambient: null` clears (`which`: `loop` or `click`, default both) | `{ what: "ambient", instance: "atlas", ambient: { preset: "fall", glyphs: ["🌸","❀","✿"] } }` |
@@ -107,6 +109,17 @@ Typed values only; anything else is refused with `{ errors: [{ path, problem }]
107
109
  - `show` is a list of `name`, `bar` and `ring` (`[]` for nowhere). Left out, the host's default applies (name, bar and ring).
108
110
  - A person's choice for you in the dashboard always wins, field by field, and their Reset brings yours back.
109
111
  `get_web_appearance` says which is in force.
112
+ - `page` (#2196): `{ on?: true | false, sidebar?: { light?, dark? } }` — `on: true` puts your colours (bg, surface, text,
113
+ text2, accent; the sidebar's own colour per mode in `sidebar`, else your `bg`) on **the whole page while your chat,
114
+ View, Log or Details is open**: the sidebar, the header, the usage rail; on a phone the drawer, the header and the
115
+ tabs. Colours only: never fonts, pictures or layout, and **AgEnD's status colours** (the Needs you count, the state
116
+ dots, errors, the focus ring — `get_web_theme`'s `status_colors`) are never yours to change. With `on: true` the
117
+ contrast check adds: `text` and `text2` 4.5:1 and `accent` 3:1 on the sidebar, and every status colour 3:1 on the
118
+ sidebar and on `bg`. A person's choice for you (⋯ → **Page colours**: your choice, the whole page, only your chat)
119
+ wins; viewers with agents' themes off or `?safe=1` see the page's own look.
120
+ - For a person's **custom CSS** (#2199): while an agent's Chat, View, Log or Details is open, `<body>` has
121
+ `data-agent="<instance name>"` (none elsewhere), set in the same render as the page — so a person's stylesheet can
122
+ follow the open agent: `body[data-agent="atlas"] { --bg-sidebar: … }`. It is the person's, not yours to set.
110
123
  - `bubble`: `{ radius: 0..24, border: "none" | "thin" | "accent" }`.
111
124
  - `font`: `"inter"`, `"system"` or `"mono"`.
112
125
  - `background`: `{ image: <asset id>, fit: "cover" | "contain", opacity: 0.05..1 }`.
@@ -157,6 +170,10 @@ screen and layout. `card.bannerFocus` (0 top … 100 bottom; default 50, the cen
157
170
  near the top at `bannerFocus: 0`. A person's focus (Edit profile, or General's `set_web_settings { what: "banner_focus" }`)
158
171
  wins over yours. `preview_web_appearance` draws the banner cropped at its focus, as the page does (an M card).
159
172
 
173
+ **In your chat** the card is hidden until the person shows it (a card button in the chat header). Shown, the banner sits
174
+ right under the header and they can fold your portrait, tagline and bio away, keeping only the banner. Their choice,
175
+ kept on their device; you cannot change it. So a banner that says who you are on its own reads well in your chat too.
176
+
160
177
  ## Your identity colour
161
178
 
162
179
  The colour people find you by in the sidebar, drawn on your name, a bar at your row's left edge and/or the ring around
@@ -84,10 +84,12 @@ View, Settings or Needs you. To invite someone: turn it on, then tell them to se
84
84
  Settings over the public link.
85
85
 
86
86
  **Web authority (#1939, 2.3 beta).** A signed-in fleet admin's browser can speak as their own Discord or Telegram
87
- account. Under the composer (and in the session menu) the line says **Not verified · Authorize this browser**. Pressing it
87
+ account. The one row under the composer (bottom left, before the Enter hint) says **Not verified · Authorize**; the
88
+ session menu says **Not verified · Authorize this browser**. Pressing it
88
89
  sends a card to that account's DM, saying which browser (device, on this computer or the network, when, until when), with
89
90
  **Confirm** and **Not me**. A card left for 5 minutes is marked expired on the card, and the line offers Authorize
90
- again. Confirm within 5 minutes and the line says **Verified · speaks as you until …**. That
91
+ again. Confirm within 5 minutes and the session menu says **Verified · speaks as you until …**; the composer's row says
92
+ just **✓ Verified**, with the until-when in its tooltip, its accessible name and a tap. That
91
93
  browser's messages then carry the account's authority in their delivery receipt for `max_session_hours` (default 48).
92
94
  Not me signs that browser out, also after Confirm, from the same card. At most one card waits per browser, and 3 an hour
93
95
  per account. Never on the public link or for a guest; when a browser can't be authorized, the line says why (with a
@@ -98,6 +100,8 @@ Agents then see that browser's messages as `via web (verified)` and check their
98
100
 
99
101
  ## Chat
100
102
 
103
+ A message to a paused agent wakes it through the same delivery path as Discord/Telegram, including ClassicBot. The composer shows **Waking…**; a refused wake explains why, keeps the draft/files and offers **Retry send**. No automatic resend. Stopped agents still require Start. Upload/history/Cancel do not wake agents. Existing guest/public-link send permissions are unchanged.
104
+
101
105
  **Sending.** Type and press Enter. A message sent while the agent works waits its turn; `/steer <text>` goes into
102
106
  the current turn, `/btw <question>` asks a side question. **Stop reply** (or Esc) interrupts the reply and drops
103
107
  queued messages. Text is capped at 16,384 characters; a paste over 4,000 characters becomes a `pasted-….txt`
@@ -191,7 +195,11 @@ Colour by kind — task (delegate_task) purple, send/query/update teal, report p
191
195
  read. "×N" = several messages share a line; failed ones belong in Needs you. A `delegate_task` also draws the work: a
192
196
  slow-moving purple line, delegator → worker, **in progress** until the worker's `report_result` with the same
193
197
  correlation id comes back (then one pink line returns); none after 4 h fades as **no report yet**. Reloads and the
194
- public link show the open ones. Every line ends on a card or a folded team's box. No message
198
+ public link show the open ones. Every line ends on a card or a folded team's box. #2182: lines are routed card to card
199
+ by libavoid (libavoid-js 0.4.5, LGPL) in a worker — around cards, through gaps in team borders, never along a border or
200
+ across a team's name, lines sharing a stretch drawn apart; zoomed out, or where the router can't run (no WebAssembly,
201
+ an old browser), they follow the gaps between teams as before. The worker alone may run WebAssembly and reaches nothing
202
+ (its own CSP); the dashboard's CSP is unchanged. No message
195
203
  text is ever shown; instances with `cross_instance_visibility: hidden` are left out. On a public link it refreshes
196
204
  every 5 s.
197
205
 
@@ -215,8 +223,13 @@ to that instance's Log).
215
223
  - **Log** — [the Log tab](#the-log-tab).
216
224
  - **Details** — Configuration (display name, description, directory, bound to, tags; "Read only — change it in
217
225
  Settings." with **Edit in Settings**), Runtime (backend, model, effort, cost, context, 5 h and 7 d rate), Recent
218
- activity, its identity card and widget. The ⋯ menu: Start / Restart / Stop instance, Pause / Wake, Colour…, and
226
+ activity, its identity card and widget. The ⋯ menu: Start / Restart / Stop instance, Pause / Wake, Colour…, **Page
227
+ colours** (#2196: the agent's choice / on the whole page / only its chat — host prefs `theme.pageByInstance`), and
219
228
  the theme lock/reset.
229
+ - **Page colours (#2196):** an agent's theme with `page.on` (or a person's "on the whole page") colours the sidebar,
230
+ the header and the usage rail (a phone: drawer, header, tabs) while that agent's Chat/View/Log/Details is open —
231
+ colours only; status colours (Needs you count, state dots, errors, focus ring) never change. The header follows the
232
+ page, not the chat. Off with agents' themes off or `?safe=1`.
220
233
 
221
234
  Phone layout: bottom tabs Chat, Needs you, Fleet, Settings. Chat is current on any of an agent's pages and goes to its
222
235
  chat; View, Log and Details are the switch under the header.
@@ -285,6 +298,8 @@ running; kiro shows fleet events only; other backends say they have no readable
285
298
 
286
299
  ## Settings
287
300
 
301
+ **Connections → Retry now** retries a disconnected connection without a fleet restart; a running login is reused. Startup retries continue with a five-minute cap. Local admin only, unavailable over public links. It changes no token or binding; refresh to read the later result.
302
+
288
303
  Tabs (`/settings/<tab>`): **Preferences**, **Web appearance**, **Agents**, **Connections**, **ClassicBot**,
289
304
  **General**, **Web**, **Developer**. Header: version, **Setup wizard**, **Help**.
290
305
 
@@ -292,7 +307,10 @@ Tabs (`/settings/<tab>`): **Preferences**, **Web appearance**, **Agents**, **Con
292
307
  **Stage change** (General/Web: **Review changes**) adds it to the bar **Changes not applied yet**, which shows the
293
308
  cost — **Immediately**, **Restart this Agent** or **Restart AgEnD** — then **Apply changes** or **Discard**.
294
309
  Confirmations (e.g. opening access) are asked at Apply. A change that loosens access waits for a fleet admin to confirm
295
- in the General chat ("Waiting for confirmation", **Withdraw**). After a failure, unapplied changes are staged again
310
+ in the General chat ("Waiting for confirmation", **Withdraw**) — unless the browser is web-verified (#2170): an
311
+ account that is a fleet admin of every connection the change affects, on their platform, confirms it by its own Apply
312
+ ("Confirmed by your account"; General gets a notice; every check runs again at execution). Not for a guest, the public
313
+ link, an unverified browser or a change that needs the host CLI. After a failure, unapplied changes are staged again
296
314
  (secrets must be re-entered). **Restart AgEnD** restarts every agent. Preferences and Web appearance save directly.
297
315
 
298
316
  **Preferences** — follows you across devices unless marked:
@@ -472,6 +490,10 @@ fleet.yaml (Developer tab, or the file) and `agend reload`.
472
490
 
473
491
  ### Canvas Worker playback diagnostics
474
492
 
493
+ The effect player loads only when there is something to play: the open chat has a placed effect, the person chose a
494
+ personal effect, or a Try card is shown (#2193). Before that, nothing of it is in the page, by design. Ambient effects
495
+ and custom CSS never need it.
496
+
475
497
  If an effect cannot play, the Chat notice and Effects dialog show its local refusal code. A failed Try retains its stop
476
498
  code on the offer card; the browser console also names refusals. Include the code when reporting a problem. Long chat
477
499
  history is checked separately from the bounded set of current paint holes. Moving the pointer away does not stop Try;
@@ -201,6 +201,8 @@ export interface IncidentEventSource {
201
201
  /** Real daemons buffer `pty_error` raised by the startup scan until the lifecycle can handle it. */
202
202
  holdStartupIncidents?(): void;
203
203
  releaseStartupIncidents?(): void;
204
+ /** Reconsider a cooldown-suppressed MCP revival through daemon liveness/idle gates. */
205
+ deferMcpRestart?(delayMs: number): void;
204
206
  }
205
207
  /** Arguments accepted by handleCreate — mirrors CreateInstanceArgs in outbound-schemas.ts
206
208
  * plus internal-only fields forwarded by deploy_template (profile-derived). */
@@ -696,8 +696,10 @@ export class InstanceLifecycle {
696
696
  const incident = this.mcpIncident(name);
697
697
  const generation = ++incident.gen;
698
698
  incident.deathNoticeSent = false;
699
- this.ctx.eventLog?.insert(name, "mcp_died", { pid: data.pid });
700
- this.ctx.logger.error({ name, pid: data.pid }, "MCP server died — instance cannot use agend tools");
699
+ this.ctx.eventLog?.insert(name, "mcp_died", { pid: data.pid, ...(data.neverConnected ? { neverConnected: true } : {}) });
700
+ this.ctx.logger.error({ name, pid: data.pid, neverConnected: data.neverConnected === true }, data.neverConnected
701
+ ? "MCP server never connected — instance cannot use agend tools"
702
+ : "MCP server died — instance cannot use agend tools");
701
703
  this.ctx.webhookEmit("mcp_died", name, { pid: data.pid });
702
704
  // Auth outranks MCP: when the CLI lost authentication, "MCP server died"
703
705
  // is a symptom and the fix is /login, not a restart. The daemon flags a
@@ -804,13 +806,16 @@ export class InstanceLifecycle {
804
806
  // The daemon object dies with the restart it asks for, so the loop guard
805
807
  // lives here: if the previous auto-restart was under the cooldown, the new
806
808
  // MCP server evidently died right back (broken install, OOM pressure) and
807
- // another attempt would just cycle the instance. Leave it to the operator —
808
- // the mcp_died notification for this death has already gone out.
809
+ // another immediate attempt would just cycle the instance. Keep a bounded
810
+ // reconsideration on the daemon: a one-shot suppression strands this pid
811
+ // forever because later health ticks dedupe the already reported death.
809
812
  const last = this.mcpAutoRestartAt.get(name) ?? 0;
810
813
  const now = Date.now();
811
814
  if (now - last < InstanceLifecycle.MCP_AUTO_RESTART_COOLDOWN_MS) {
812
- this.ctx.eventLog?.insert(name, "mcp_auto_restart_suppressed", { trigger: data.trigger });
813
- this.ctx.logger.error({ name, trigger: data.trigger }, "MCP auto-restart suppressed — the previous attempt is still inside the cooldown, so the restart is not fixing it");
815
+ const retryAfterMs = InstanceLifecycle.MCP_AUTO_RESTART_COOLDOWN_MS - (now - last);
816
+ this.ctx.eventLog?.insert(name, "mcp_auto_restart_suppressed", { trigger: data.trigger, retryAfterMs });
817
+ this.ctx.logger.warn({ name, trigger: data.trigger, retryAfterMs }, "MCP auto-restart deferred — will reconsider after the cooldown with liveness and idle checks");
818
+ daemon.deferMcpRestart?.(retryAfterMs);
814
819
  return;
815
820
  }
816
821
  this.mcpAutoRestartAt.set(name, now);