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

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 (48) hide show
  1. package/dist/agent-whats-new.js +14 -9
  2. package/dist/agent-whats-new.js.map +1 -1
  3. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  4. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  5. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  6. package/dist/channel/adapters/discord.d.ts +2 -0
  7. package/dist/channel/adapters/discord.js +14 -4
  8. package/dist/channel/adapters/discord.js.map +1 -1
  9. package/dist/cli.js +12 -79
  10. package/dist/cli.js.map +1 -1
  11. package/dist/daemon.d.ts +3 -0
  12. package/dist/daemon.js +25 -0
  13. package/dist/daemon.js.map +1 -1
  14. package/dist/fleet-manager.d.ts +8 -1
  15. package/dist/fleet-manager.js +101 -33
  16. package/dist/fleet-manager.js.map +1 -1
  17. package/dist/fleet-process-handlers.d.ts +18 -0
  18. package/dist/fleet-process-handlers.js +69 -0
  19. package/dist/fleet-process-handlers.js.map +1 -0
  20. package/dist/general-knowledge/skills/web-appearance/SKILL.md +1 -1
  21. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +4 -0
  22. package/dist/instance-lifecycle.d.ts +2 -0
  23. package/dist/instance-lifecycle.js +11 -6
  24. package/dist/instance-lifecycle.js.map +1 -1
  25. package/dist/outbound-schemas.js +1 -1
  26. package/dist/outbound-schemas.js.map +1 -1
  27. package/dist/settings-api.d.ts +4 -0
  28. package/dist/settings-api.js +23 -0
  29. package/dist/settings-api.js.map +1 -1
  30. package/dist/ui/app-effects.js +13 -1
  31. package/dist/ui/chat-store.js +32 -10
  32. package/dist/ui/chat-strings.js +16 -0
  33. package/dist/ui/chat-thread.js +5 -2
  34. package/dist/ui/panel-chat.js +24 -3
  35. package/dist/ui/panel-settings.js +24 -0
  36. package/dist/ui/settings-strings.js +2 -0
  37. package/dist/ui/shared/effect-owner-source.js +8 -1
  38. package/dist/ui/shared/effect-registry.js +10 -2
  39. package/dist/ui/shared/owner-css.js +30 -1
  40. package/dist/web-api.d.ts +6 -2
  41. package/dist/web-api.js +50 -14
  42. package/dist/web-api.js.map +1 -1
  43. package/dist/web-owner-css-store.d.ts +43 -1
  44. package/dist/web-owner-css-store.js +65 -9
  45. package/dist/web-owner-css-store.js.map +1 -1
  46. package/dist/web-settings-tools.js +7 -4
  47. package/dist/web-settings-tools.js.map +1 -1
  48. package/package.json +1 -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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"}
@@ -43,7 +43,7 @@ of this skill).
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
44
  | `agent_colour` | one agent's colour (`preset:<name>` or `#rrggbb`) and places; null resets each | `{ what: "agent_colour", instance: "atlas", colour: "preset:teal" }` |
45
45
  | `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 |
46
+ | `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` (the trial; 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` 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
47
  | `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
48
  | `effect` | `apply` (instance, id, params?) or `remove` (instance, id?) an effect on a chat | |
49
49
  | `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: ["🌸","❀","✿"] } }` |
@@ -98,6 +98,8 @@ Agents then see that browser's messages as `via web (verified)` and check their
98
98
 
99
99
  ## Chat
100
100
 
101
+ 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.
102
+
101
103
  **Sending.** Type and press Enter. A message sent while the agent works waits its turn; `/steer <text>` goes into
102
104
  the current turn, `/btw <question>` asks a side question. **Stop reply** (or Esc) interrupts the reply and drops
103
105
  queued messages. Text is capped at 16,384 characters; a paste over 4,000 characters becomes a `pasted-….txt`
@@ -285,6 +287,8 @@ running; kiro shows fleet events only; other backends say they have no readable
285
287
 
286
288
  ## Settings
287
289
 
290
+ **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.
291
+
288
292
  Tabs (`/settings/<tab>`): **Preferences**, **Web appearance**, **Agents**, **Connections**, **ClassicBot**,
289
293
  **General**, **Web**, **Developer**. Header: version, **Setup wizard**, **Help**.
290
294
 
@@ -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);