@songsid/agend 2.1.4-beta.9 → 2.1.4

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 (166) hide show
  1. package/README.md +1 -1
  2. package/README.zh-TW.md +1 -1
  3. package/dist/agent-cli-instructions.md +2 -2
  4. package/dist/agent-endpoint.js +1 -1
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/backend/antigravity.d.ts +12 -8
  7. package/dist/backend/antigravity.js +189 -42
  8. package/dist/backend/antigravity.js.map +1 -1
  9. package/dist/backend/claude-code.d.ts +87 -23
  10. package/dist/backend/claude-code.js +361 -46
  11. package/dist/backend/claude-code.js.map +1 -1
  12. package/dist/backend/codex.d.ts +18 -0
  13. package/dist/backend/codex.js +139 -5
  14. package/dist/backend/codex.js.map +1 -1
  15. package/dist/backend/gemini-cli.js +2 -2
  16. package/dist/backend/gemini-cli.js.map +1 -1
  17. package/dist/backend/grok.js +2 -2
  18. package/dist/backend/grok.js.map +1 -1
  19. package/dist/backend/kiro.d.ts +52 -2
  20. package/dist/backend/kiro.js +267 -12
  21. package/dist/backend/kiro.js.map +1 -1
  22. package/dist/backend/opencode.js +17 -4
  23. package/dist/backend/opencode.js.map +1 -1
  24. package/dist/backend/types.d.ts +122 -3
  25. package/dist/backend/types.js +5 -1
  26. package/dist/backend/types.js.map +1 -1
  27. package/dist/backend-outage.d.ts +61 -0
  28. package/dist/backend-outage.js +71 -0
  29. package/dist/backend-outage.js.map +1 -0
  30. package/dist/channel/adapters/discord.d.ts +44 -2
  31. package/dist/channel/adapters/discord.js +523 -90
  32. package/dist/channel/adapters/discord.js.map +1 -1
  33. package/dist/channel/adapters/telegram.d.ts +30 -0
  34. package/dist/channel/adapters/telegram.js +221 -19
  35. package/dist/channel/adapters/telegram.js.map +1 -1
  36. package/dist/channel/agy-mcp-launcher.d.ts +2 -0
  37. package/dist/channel/agy-mcp-launcher.js +28 -0
  38. package/dist/channel/agy-mcp-launcher.js.map +1 -0
  39. package/dist/channel/ipc-bridge.d.ts +2 -1
  40. package/dist/channel/ipc-bridge.js +33 -4
  41. package/dist/channel/ipc-bridge.js.map +1 -1
  42. package/dist/channel/markdown-chunk.d.ts +42 -0
  43. package/dist/channel/markdown-chunk.js +205 -0
  44. package/dist/channel/markdown-chunk.js.map +1 -0
  45. package/dist/channel/mcp-server.js +34 -5
  46. package/dist/channel/mcp-server.js.map +1 -1
  47. package/dist/channel/mcp-tools.js +5 -3
  48. package/dist/channel/mcp-tools.js.map +1 -1
  49. package/dist/channel/message-queue.js +64 -8
  50. package/dist/channel/message-queue.js.map +1 -1
  51. package/dist/channel/types.d.ts +31 -1
  52. package/dist/classic-channel-manager.d.ts +65 -0
  53. package/dist/classic-channel-manager.js +172 -3
  54. package/dist/classic-channel-manager.js.map +1 -1
  55. package/dist/cli.js +124 -83
  56. package/dist/cli.js.map +1 -1
  57. package/dist/config-validator.js +13 -0
  58. package/dist/config-validator.js.map +1 -1
  59. package/dist/config.d.ts +4 -0
  60. package/dist/config.js +12 -1
  61. package/dist/config.js.map +1 -1
  62. package/dist/cross-instance-envelope.d.ts +6 -0
  63. package/dist/cross-instance-envelope.js +30 -0
  64. package/dist/cross-instance-envelope.js.map +1 -0
  65. package/dist/daemon.d.ts +491 -14
  66. package/dist/daemon.js +2121 -276
  67. package/dist/daemon.js.map +1 -1
  68. package/dist/doctor.d.ts +41 -0
  69. package/dist/doctor.js +267 -0
  70. package/dist/doctor.js.map +1 -0
  71. package/dist/fleet-context.d.ts +63 -0
  72. package/dist/fleet-context.js.map +1 -1
  73. package/dist/fleet-manager.d.ts +402 -11
  74. package/dist/fleet-manager.js +2594 -422
  75. package/dist/fleet-manager.js.map +1 -1
  76. package/dist/fleet-yaml-slim.d.ts +10 -0
  77. package/dist/fleet-yaml-slim.js +59 -0
  78. package/dist/fleet-yaml-slim.js.map +1 -0
  79. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
  80. package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
  81. package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
  82. package/dist/instance-lifecycle.d.ts +114 -1
  83. package/dist/instance-lifecycle.js +404 -14
  84. package/dist/instance-lifecycle.js.map +1 -1
  85. package/dist/instructions.js +42 -4
  86. package/dist/instructions.js.map +1 -1
  87. package/dist/locale.js +1024 -320
  88. package/dist/locale.js.map +1 -1
  89. package/dist/login-flows.d.ts +91 -0
  90. package/dist/login-flows.js +170 -0
  91. package/dist/login-flows.js.map +1 -0
  92. package/dist/login-manager.d.ts +63 -0
  93. package/dist/login-manager.js +134 -0
  94. package/dist/login-manager.js.map +1 -0
  95. package/dist/network-family.d.ts +18 -0
  96. package/dist/network-family.js +20 -0
  97. package/dist/network-family.js.map +1 -0
  98. package/dist/outbound-handlers.d.ts +16 -1
  99. package/dist/outbound-handlers.js +174 -40
  100. package/dist/outbound-handlers.js.map +1 -1
  101. package/dist/outbound-schemas.d.ts +10 -3
  102. package/dist/outbound-schemas.js +10 -0
  103. package/dist/outbound-schemas.js.map +1 -1
  104. package/dist/pane-input-residue.d.ts +52 -0
  105. package/dist/pane-input-residue.js +107 -0
  106. package/dist/pane-input-residue.js.map +1 -0
  107. package/dist/process-memory.d.ts +6 -0
  108. package/dist/process-memory.js +45 -0
  109. package/dist/process-memory.js.map +1 -1
  110. package/dist/reply-dedup.d.ts +7 -8
  111. package/dist/reply-dedup.js +0 -0
  112. package/dist/reply-dedup.js.map +1 -1
  113. package/dist/restart-progress.d.ts +2 -0
  114. package/dist/restart-progress.js +3 -0
  115. package/dist/restart-progress.js.map +1 -1
  116. package/dist/scheduler/db.d.ts +12 -0
  117. package/dist/scheduler/db.js +59 -0
  118. package/dist/scheduler/db.js.map +1 -1
  119. package/dist/service-installer.d.ts +11 -0
  120. package/dist/service-installer.js +84 -18
  121. package/dist/service-installer.js.map +1 -1
  122. package/dist/settings-api.js +1 -1
  123. package/dist/settings-api.js.map +1 -1
  124. package/dist/setup-wizard.js +2 -2
  125. package/dist/setup-wizard.js.map +1 -1
  126. package/dist/spawn-gate.d.ts +30 -0
  127. package/dist/spawn-gate.js +79 -0
  128. package/dist/spawn-gate.js.map +1 -0
  129. package/dist/steer-capability.d.ts +1 -0
  130. package/dist/steer-capability.js +6 -0
  131. package/dist/steer-capability.js.map +1 -0
  132. package/dist/storm-window.d.ts +83 -0
  133. package/dist/storm-window.js +251 -0
  134. package/dist/storm-window.js.map +1 -0
  135. package/dist/tips.d.ts +44 -0
  136. package/dist/tips.js +355 -0
  137. package/dist/tips.js.map +1 -0
  138. package/dist/tmux-manager.d.ts +16 -0
  139. package/dist/tmux-manager.js +110 -25
  140. package/dist/tmux-manager.js.map +1 -1
  141. package/dist/topic-commands.d.ts +31 -7
  142. package/dist/topic-commands.js +342 -70
  143. package/dist/topic-commands.js.map +1 -1
  144. package/dist/transcript-sources.d.ts +35 -10
  145. package/dist/transcript-sources.js +190 -38
  146. package/dist/transcript-sources.js.map +1 -1
  147. package/dist/types.d.ts +6 -1
  148. package/dist/ui/dashboard.html +55 -32
  149. package/dist/ui/settings.html +200 -60
  150. package/dist/ui/view.html +147 -30
  151. package/dist/usage/format-rich.d.ts +1 -1
  152. package/dist/usage/format-rich.js +28 -24
  153. package/dist/usage/format-rich.js.map +1 -1
  154. package/dist/usage/i18n-keys.d.ts +7 -0
  155. package/dist/usage/i18n-keys.js +34 -0
  156. package/dist/usage/i18n-keys.js.map +1 -0
  157. package/dist/usage/i18n.d.ts +5 -0
  158. package/dist/usage/i18n.js +27 -0
  159. package/dist/usage/i18n.js.map +1 -0
  160. package/dist/usage/providers.d.ts +21 -0
  161. package/dist/usage/providers.js +153 -75
  162. package/dist/usage/providers.js.map +1 -1
  163. package/dist/usage/usage-api.d.ts +11 -3
  164. package/dist/usage/usage-api.js +61 -24
  165. package/dist/usage/usage-api.js.map +1 -1
  166. package/package.json +2 -2
@@ -6,7 +6,7 @@ import { EventLog } from "./event-log.js";
6
6
  import { AdapterWorld } from "./adapter-world.js";
7
7
  import { CostGuard } from "./cost-guard.js";
8
8
  import { IpcClient } from "./channel/ipc-bridge.js";
9
- import type { ChannelAdapter } from "./channel/types.js";
9
+ import type { AdapterHealthSnapshot, ChannelAdapter } from "./channel/types.js";
10
10
  import { type Logger } from "./logger.js";
11
11
  import { Scheduler } from "./scheduler/index.js";
12
12
  import type { FleetContext } from "./fleet-context.js";
@@ -20,6 +20,9 @@ import { type RawConfigPatch } from "./settings-api.js";
20
20
  import { type AgentEndpointContext } from "./agent-endpoint.js";
21
21
  import { ClassicChannelManager } from "./classic-channel-manager.js";
22
22
  import type { InstanceState } from "./backend/types.js";
23
+ import { StormWindow } from "./storm-window.js";
24
+ import { SpawnGate } from "./spawn-gate.js";
25
+ import { BackendOutageTracker } from "./backend-outage.js";
23
26
  export declare function resolveReplyThreadId(argsThreadId: unknown, instanceConfig?: InstanceConfig): string | undefined;
24
27
  /**
25
28
  * Pure warm-cap victim selection (extracted for testability). Given the current
@@ -36,6 +39,25 @@ export declare function selectLruEvictions(warm: string[], cap: number, opts: {
36
39
  isIdle: (name: string) => boolean;
37
40
  lastInboundAt: (name: string) => number;
38
41
  }): string[];
42
+ /**
43
+ * Answer shape for `list_models`. `scope` reports where the LIST came from —
44
+ * "instance" only when it was read through that instance's own backend config,
45
+ * "global" for the account/CLI catalog — so a caller can tell an authoritative
46
+ * per-instance list from a best-effort account-wide one.
47
+ */
48
+ export interface ModelCatalog {
49
+ backend: string;
50
+ scope: "instance" | "global";
51
+ /** Set whenever an instance was asked about, even if the list is global. */
52
+ instance?: string;
53
+ current_model: string | null;
54
+ models: import("./backend/types.js").ModelOption[];
55
+ /** "cache" = startup probe cache, "live" = probed now, "fallback" = none available. */
56
+ source: "cache" | "live" | "fallback";
57
+ probed_at?: string;
58
+ /** Caveat the caller should read before trusting the list. */
59
+ note?: string;
60
+ }
39
61
  export interface DeliveryOptions {
40
62
  /** Explicitly identify agent-to-agent delivery when metadata is unavailable. */
41
63
  isCrossInstance?: boolean;
@@ -50,6 +72,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
50
72
  private static sighupHandlerInstalled;
51
73
  private children;
52
74
  readonly lifecycle: InstanceLifecycle;
75
+ readonly stormWindow: StormWindow;
76
+ readonly spawnGate: SpawnGate;
77
+ /** Fleet-level backend reachability memory (fed by pty_error / startup panes). */
78
+ readonly backendOutage: BackendOutageTracker;
53
79
  /** Live view of lifecycle.daemons — used throughout; not deprecated. */
54
80
  get daemons(): Map<string, import("./daemon.js").Daemon>;
55
81
  fleetConfig: FleetConfig | null;
@@ -83,6 +109,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
83
109
  costGuard: CostGuard | null;
84
110
  private statuslineWatcher;
85
111
  private dailySummary;
112
+ private dailyTipScheduler;
86
113
  private webhookEmitter;
87
114
  private topicIcons;
88
115
  private lastActivity;
@@ -94,6 +121,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
94
121
  private warmCapEvicting;
95
122
  /** Per-instance tail keeps cross-instance and scheduled deliveries FIFO. */
96
123
  private idleGatedDeliveryTails;
124
+ /**
125
+ * Per-instance cancellation epoch for deliveries which have not reached the
126
+ * daemon yet. A cancel advances the epoch; queued work captures the old value
127
+ * and becomes a no-op, while messages arriving after the click capture the new
128
+ * value and remain deliverable.
129
+ */
130
+ private deliveryEpochs;
97
131
  /** Non-user work must observe a fresh idle snapshot after the latest delivery. */
98
132
  private lastDeliveryAt;
99
133
  /** State-cache updates wake event-driven idle waiters without busy polling. */
@@ -112,6 +146,28 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
112
146
  private ipcWaitTails;
113
147
  /** instanceName → restart currently executing; concurrent callers join it. */
114
148
  private restartsInFlight;
149
+ /**
150
+ * Delayed automatic retries for instances whose startup failed. Before this a
151
+ * failed start was logged once and the instance stayed `stopped` until an
152
+ * operator noticed (2026-09-03: 4 kiro instances, all victims of the same
153
+ * backend outage during a post-update herd). instanceName → pending retry.
154
+ */
155
+ private startupRetries;
156
+ /** Bumped by every explicit stop (incl. the stop half of a restart); fences the outage hand-off. */
157
+ private explicitStopGeneration;
158
+ /** instanceName → outage hand-off currently executing; a repeat joins it. */
159
+ private handOffsInFlight;
160
+ /** instanceName → explicit stop currently executing; the outage hand-off waits for it. */
161
+ private stopsInFlight;
162
+ /** Aggregation window for the "N instances failed to start" notice. */
163
+ private startupRetryNotices;
164
+ /** Backoff between automatic startup retries; the last step repeats while the backend is down. */
165
+ static readonly STARTUP_RETRY_BACKOFF_MS: number[];
166
+ /** Hard cap on automatic startup retries (3 backoff steps + up to 3 more during a backend outage). */
167
+ static readonly STARTUP_RETRY_MAX_ATTEMPTS = 6;
168
+ /** Re-check interval when a retry is due but the tmux storm window still blocks spawns. */
169
+ static readonly STARTUP_RETRY_STORM_DEFER_MS = 60000;
170
+ static readonly STARTUP_RETRY_NOTICE_AGGREGATE_MS = 1000;
115
171
  private lastInboundMsg;
116
172
  private topicArchiver;
117
173
  controlClient: TmuxControlClient | null;
@@ -142,12 +198,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
142
198
  private updateProgressTimer;
143
199
  private updateProgressEditRunning;
144
200
  private lastUpdateProgressText;
201
+ private updateCompletionTipText;
145
202
  private eventLogPruneTimer;
146
203
  private logRotateTimer;
147
204
  /** Days of event/activity history to keep. */
148
205
  private static readonly EVENT_LOG_RETENTION_DAYS;
149
206
  private watchdogTimer;
150
207
  private startedAt;
208
+ private stormOpenNotifyTimer;
151
209
  private mirrorBuffer;
152
210
  private mirrorTimer;
153
211
  private sseClients;
@@ -155,6 +213,11 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
155
213
  private viewToken;
156
214
  private healthServerListening;
157
215
  constructor(dataDir: string);
216
+ private spawnConcurrency;
217
+ /** Wire the one fleet-wide storm into notification and recovery surfaces. */
218
+ private bindStormWindowEvents;
219
+ private formatStormDelay;
220
+ stormSuppressed(kind: string): boolean;
158
221
  private handleSighup;
159
222
  private scheduleReconcile;
160
223
  /**
@@ -224,6 +287,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
224
287
  private isModelAdmin;
225
288
  private handlePauseWakeSlash;
226
289
  private handleUpdateSlash;
290
+ private handleTipsSlash;
227
291
  /** Admin-only full conversation reset for fleet-topic and Classic instances. */
228
292
  private handleClearSlash;
229
293
  /** Get the adapter bound to an instance, falling back to primary adapter */
@@ -254,6 +318,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
254
318
  bindInstanceAdapter(name: string, adapterId: string, fromInbound?: boolean): void;
255
319
  getInstanceStatus(name: string): "running" | "paused" | "stopped" | "crashed";
256
320
  getInstanceExecutionState(name: string): InstanceState | null;
321
+ /**
322
+ * Subscription providers currently relevant to this fleet. Stopped/crashed
323
+ * rows do not keep a usage card alive, while persisted paused instances do.
324
+ * Classic channels use their effective backend merge chain, not the raw row.
325
+ */
326
+ getActiveUsageProviderIds(): ReadonlySet<string>;
327
+ /** Effective backends with a running or persisted-paused fleet/Classic instance. */
328
+ getActiveBackendIds(): ReadonlySet<string>;
257
329
  isClassicInstance(name: string): boolean;
258
330
  private cacheInstanceExecutionState;
259
331
  private cancelIdleButtonRetirement;
@@ -270,7 +342,15 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
270
342
  */
271
343
  private enforceWarmCap;
272
344
  private waitForInstanceIdle;
345
+ /**
346
+ * Ask the daemon to capture the pane before answering, then wait for any state
347
+ * report produced after this request. The ordinary query is intentionally
348
+ * cache-only (idle gates call it frequently); this opt-in refresh is reserved
349
+ * for lifecycle decisions where a stale state would strand UI.
350
+ */
351
+ private refreshInstanceExecutionState;
273
352
  private deliverWithIdleGate;
353
+ private holdDeliveryForStorm;
274
354
  /**
275
355
  * Hand a payload to an instance's IPC, waiting out a *transient* disconnect.
276
356
  *
@@ -290,6 +370,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
290
370
  private sendWhenConnected;
291
371
  /** Poll for the instance's IPC to come back, then send. Throws if it does not. */
292
372
  private sendAfterIpcReturns;
373
+ /** Whether this target already has an ordinary non-user delivery in its FIFO. */
374
+ hasPendingIdleGatedDelivery(instanceName: string): boolean;
293
375
  /** Single delivery facade: wake paused CLIs and serialize non-user work behind idle. */
294
376
  deliverToInstance(instanceName: string, payload: Record<string, unknown>, options?: DeliveryOptions): Promise<void>;
295
377
  /** Fleet admin is an explicit config allowlist entry, not merely an open/paired user. */
@@ -313,6 +395,65 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
313
395
  * TODO: per-instance startup timeout (existing issue, not introduced here)
314
396
  */
315
397
  private startInstancesWithConcurrency;
398
+ /**
399
+ * Start from an UNATTENDED path (fleet startup, full restart, config
400
+ * reconcile): nobody is watching the result, so a failure is logged and
401
+ * handed to the delayed automatic retry instead of leaving the instance
402
+ * `stopped` forever. Explicit operator/API starts call startInstance directly
403
+ * and keep their synchronous error. Returns whether the instance is up.
404
+ */
405
+ private startInstanceUnattended;
406
+ /**
407
+ * Schedule attempt `attempt` (0-based) of the automatic startup retry for an
408
+ * instance whose start just failed. Backoff 1 → 5 → 15 min; while the
409
+ * instance's backend is known to be unreachable the 15-min step repeats up to
410
+ * STARTUP_RETRY_MAX_ATTEMPTS, after which we give up with one notice. Each
411
+ * retry re-checks the world (still configured, not running, not paused, no
412
+ * tmux storm) and runs through the SpawnGate as "recovery" — so a herd of
413
+ * failed instances comes back at the gate's concurrency, never all at once.
414
+ */
415
+ scheduleStartupRetry(name: string, attempt: number): void;
416
+ /** Drop a pending automatic retry (an operator start/stop/restart supersedes it). */
417
+ cancelStartupRetry(name: string): void;
418
+ /** Pending automatic retry, if any (status display / tests). */
419
+ pendingStartupRetry(name: string): {
420
+ attempt: number;
421
+ } | null;
422
+ /**
423
+ * A daemon's crash-respawn hit the backend outage (startup_backend_unreachable):
424
+ * stop THAT daemon and schedule the delayed retry. Serialized against the
425
+ * operator paths: an in-flight restart is awaited first (it replaces the
426
+ * daemon itself), the stop is identity-checked so a fresh daemon registered
427
+ * meanwhile is never deleted, and an explicit stop/restart that began during
428
+ * the hand-off (generation bump) owns the outcome — no retry is scheduled
429
+ * behind an operator's back.
430
+ */
431
+ handOffToStartupRetry(name: string, daemon: unknown): Promise<void>;
432
+ private runStartupRetry;
433
+ /** Fleet-topic (fleet.yaml) or ClassicBot (classic channel) — both are retryable; anything else is gone. */
434
+ private isConfiguredInstance;
435
+ /**
436
+ * Kind-aware start for the automatic retry: fleet-topic instances come from
437
+ * fleet.yaml, ClassicBot instances exist only in the classic channel manager
438
+ * and must be rebuilt through startClassicInstance (a fleet.yaml lookup alone
439
+ * silently dropped them — the retry timer fired and nothing happened).
440
+ */
441
+ private startConfiguredInstance;
442
+ /**
443
+ * Unattended ClassicBot start (fleet startup batch, full-restart batch, the
444
+ * classicBot.yaml reconcile): same contract as startInstanceUnattended —
445
+ * failures are logged and handed to the delayed automatic retry, whose
446
+ * kind-aware startConfiguredInstance rebuilds the Classic instance. Returns
447
+ * whether the instance is up.
448
+ */
449
+ private startClassicInstanceUnattended;
450
+ private backendNameOf;
451
+ /**
452
+ * One fleet-level notice per burst, not one per instance: a post-update herd
453
+ * fails many instances within the same second. Two notices per incident at
454
+ * most — "N failed, retrying in X" and, if it comes to that, "gave up on N".
455
+ */
456
+ private queueStartupRetryNotice;
316
457
  private runnableStartupCount;
317
458
  private configuredStartupInstanceNames;
318
459
  private restartProgressTarget;
@@ -364,6 +505,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
364
505
  retryCount: number;
365
506
  lastError?: string;
366
507
  }>;
508
+ private bindAdapterHealth;
367
509
  /**
368
510
  * Real, checkable fleet health for `/health` and the operator.
369
511
  *
@@ -392,6 +534,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
392
534
  total: number;
393
535
  connected: number;
394
536
  states: Record<string, string>;
537
+ details: Record<string, AdapterHealthSnapshot>;
395
538
  };
396
539
  startupComplete: boolean;
397
540
  /** See process-memory.ts: the fleet process and the whole service cgroup are
@@ -431,6 +574,51 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
431
574
  * message actually went out — a failed delivery keeps them queued.
432
575
  */
433
576
  private pendingReactionsMeta;
577
+ /**
578
+ * Which adapter's access policy governs an inbound message.
579
+ *
580
+ * `authoritative` means the thread resolved to an owning instance, so that
581
+ * adapter's policy is the only one entitled to decide. A resolved owner whose
582
+ * policy cannot be read is NOT the same as having no owner: the owner's world
583
+ * may not exist yet (an adapter that fails its token check returns before its
584
+ * world and AccessManager are created) while sibling bots are already live and
585
+ * receiving copies. Falling back to a sibling's policy — or to the fleet-wide
586
+ * one — would let the owner's rules be decided by another adapter, so callers
587
+ * must refuse instead.
588
+ *
589
+ * Without an owner (classic channels, the no-thread Telegram path, unrouted
590
+ * threads, no adapter identity) the receiving adapter's policy applies, as
591
+ * before.
592
+ */
593
+ private governingAccess;
594
+ /**
595
+ * Why this adapter should leave an inbound message to another adapter, or null
596
+ * to handle it.
597
+ *
598
+ * A fleet topic is served by exactly one instance, and that instance is
599
+ * answered by exactly one adapter. When several bots share a guild they each
600
+ * receive their own copy of every message, so the copy that is acted on must
601
+ * be chosen by ownership rather than by which inbound happened to fire first.
602
+ * Ownership is per-instance — the adapter the instance is bound to, falling
603
+ * back to channels[0] when it is not bound — so an instance answered by a
604
+ * non-default bot still receives its own traffic, and the access policy that
605
+ * applies is that instance's own adapter's.
606
+ *
607
+ * Callers must ask before claiming the shared dedup key: a copy that is going
608
+ * to be left alone must not consume the key, or the owner's copy would be
609
+ * discarded as a duplicate and the message lost.
610
+ *
611
+ * Exempt: classic channels (each bot owns its own agent there and handles its
612
+ * own copy, which is why their dedup key is adapter-scoped), the no-thread
613
+ * Telegram path (no thread to resolve an instance from), and messages with no
614
+ * adapter id (single-adapter fleets).
615
+ */
616
+ private topicOwnerDropReason;
617
+ /**
618
+ * Why this adapter must ignore a bot/webhook message, or null to accept it.
619
+ * Pure: callers rely on being able to ask before claiming the dedup key.
620
+ */
621
+ private botMessageDropReason;
434
622
  private handleInboundMessage;
435
623
  /** Handle outbound tool calls from a daemon instance */
436
624
  /** Warn (but don't block) when rate limits are high. 30-min debounce per instance. */
@@ -447,6 +635,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
447
635
  private handleDecisionCrud;
448
636
  /** Resolve display name for an instance, fallback to instance name. */
449
637
  resolveDisplayName(instanceName: string): string;
638
+ /** Persist identity to the instance's actual config store. Classic instances
639
+ * are registry rows in classicBot.yaml, not fleet.yaml instance entries. */
640
+ private setInstanceDisplayName;
641
+ private setInstanceDescription;
450
642
  private handleSetDisplayName;
451
643
  private handleSetDescription;
452
644
  handleScheduleCrudHttp(instance: string, op: string, args: Record<string, unknown>): Promise<unknown>;
@@ -467,9 +659,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
467
659
  private startTopicCleanupPoller;
468
660
  /**
469
661
  * Patch only values changed in the effective config into the original YAML
470
- * document. Unknown keys, explicit overrides and comments remain untouched.
662
+ * document. Unknown keys and comments remain untouched; redundant
663
+ * non-identity instance leaves are canonicalized to inheritance afterward.
471
664
  */
472
665
  saveFleetConfig(explicitPatches?: RawConfigPatch[]): void;
666
+ /** One-time upgrade migration; invalid YAML is never rewritten. */
667
+ private slimFleetConfigAtStartup;
668
+ private writeFleetConfigBackup;
473
669
  private patchFleetDocument;
474
670
  removeInstance(name: string): Promise<void>;
475
671
  startStatuslineWatcher(name: string): void;
@@ -538,10 +734,30 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
538
734
  * The log line is written by the caller regardless: if every adapter is down,
539
735
  * the only notification path is the one that is broken.
540
736
  */
737
+ /**
738
+ * Surface ids that can never match into the operator's General topic.
739
+ *
740
+ * The load-time log is still silence for anyone not reading daemon.log, and a
741
+ * chat that never gets in with nothing said anywhere is exactly the failure
742
+ * this line of work exists to remove. notifyFleetError throttles by message
743
+ * text for 10 minutes and resolves its target from config, so the 30s reload
744
+ * poll cannot flood the topic and a down General does not swallow it.
745
+ */
746
+ private reportClassicUnrecoverableIds;
541
747
  notifyFleetError(text: string): void;
542
748
  private static readonly FLEET_ERROR_THROTTLE_MS;
543
749
  private fleetErrorNotices;
544
- notifyInstanceTopic(instanceName: string, text: string, extraOpts?: import("./channel/types.js").SendOpts): void;
750
+ /**
751
+ * Post into an instance's topic. Returns whether a send was DISPATCHED — a
752
+ * target was resolved and sendText was called — so a caller that must not
753
+ * lose the message (notifyFleetError, which spends a throttle key) can tell
754
+ * that from silence. Existing callers ignore the result and are unaffected.
755
+ *
756
+ * Not a delivery guarantee: sendText is fire-and-forget, and a platform-side
757
+ * rejection surfaces only as a warn in its .catch. True delivery confirmation
758
+ * would have to make this async and change every caller.
759
+ */
760
+ notifyInstanceTopic(instanceName: string, text: string, extraOpts?: import("./channel/types.js").SendOpts): boolean;
545
761
  /**
546
762
  * Post decision buttons whose callback ids are `<prefix><nonce>:<action>`.
547
763
  * The entry is registered before the send and rolled back if the send
@@ -550,13 +766,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
550
766
  */
551
767
  private postNonceButtonPrompt;
552
768
  /**
553
- * Claim a nonce-armed callback exactly once.
769
+ * Validate a nonce-armed callback and claim it exactly once.
554
770
  *
555
771
  * Returns:
556
772
  * - null — the callback is not for this prefix; try the next handler
557
773
  * - "consumed" — for this prefix but stale/denied/malformed; stop dispatch
558
- * - the entry+action — the click is authorized and now claimed (removed
559
- * from the map before any await, so double clicks cannot act twice)
774
+ * - the entry+action — the click is authorized and claimed before any await
560
775
  *
561
776
  * A stale click (expired nonce, or a pre-upgrade button whose payload no
562
777
  * longer parses) collapses the clicked message so the dead button stops
@@ -565,6 +780,19 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
565
780
  private consumeNonceCallback;
566
781
  /** Collapse a consumed prompt's buttons into a final status line. */
567
782
  private retireNonceButtons;
783
+ private tipsEnabled;
784
+ private readTipState;
785
+ private pickAvailableTip;
786
+ private tipText;
787
+ private formatTip;
788
+ /** Persistently enable advanced tips through the admin-only slash command. */
789
+ unlockAdvancedTips(userId: string): boolean;
790
+ /** Post one fresh nonce-armed tip in a known General channel. */
791
+ promptTip(generalName: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<"posted" | "empty" | "unavailable">;
792
+ private promptAdvancedTipUnlock;
793
+ private sendTipToGeneral;
794
+ private handleTipDismiss;
795
+ private handleTipUnlock;
568
796
  /** Post the destructive `/clear` confirmation in the invoking channel. */
569
797
  promptClearConfirmation(instanceName: string, channelId: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
570
798
  /** Consume `/clear` Confirm/Cancel exactly once; only Confirm reaches IPC. */
@@ -619,7 +847,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
619
847
  */
620
848
  private handleCancelClick;
621
849
  private hasCancelButton;
622
- sendCancelButton(instanceName: string, correlationId?: string): Promise<void>;
850
+ sendCancelButton(instanceName: string, correlationId?: string, preserveProgress?: boolean): Promise<void>;
623
851
  /**
624
852
  * The cancel button's text for a given elapsed time.
625
853
  *
@@ -629,6 +857,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
629
857
  * and once the agent had replied once there was no sign it was still going.
630
858
  */
631
859
  static progressText(elapsedMs: number, activity?: string | null, minElapsedMs?: number): string;
860
+ /** Render elapsed time consistently in live and retained progress bubbles. */
861
+ private static formatProgressElapsed;
862
+ /** Final read-only form of a bubble that contains semantic tool history.
863
+ * Neutral wording is intentional: the same retirement primitive is used for
864
+ * normal completion, a mid-turn bubble replacement, and user cancellation,
865
+ * so claiming every retained checkpoint is "completed" would be false. */
866
+ private composeRetiredBubbleText;
632
867
  /**
633
868
  * Make a tool summary safe to paste into a channel message.
634
869
  *
@@ -678,10 +913,14 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
678
913
  private startProgressTicker;
679
914
  /**
680
915
  * After a reply: give the instance REPLY_RETIRE_GRACE_MS to resume working; if
681
- * it has not, retire its button. Re-arming replaces the previous timer, so a
682
- * burst of replies ends with exactly one pending check.
916
+ * it has not, retire its button. The daemon is asked for a fresh pane capture
917
+ * before deciding. Reading only the transition cache stranded the first
918
+ * post-restart bubble when its startup "working" report never got a matching
919
+ * idle edge. Re-arming replaces the previous timer, so a burst of replies ends
920
+ * with exactly one pending check.
683
921
  */
684
922
  private armReplyGrace;
923
+ private finishReplyGrace;
685
924
  /** Retire (delete) every cancel button belonging to an instance. */
686
925
  private retireInstanceButtons;
687
926
  /** Begin retiring one button (delete + bounded retry on failure). Idempotent:
@@ -706,7 +945,9 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
706
945
  private sweepOrphanedCancelButtons;
707
946
  /** Re-attempt a failed button delete up to CANCEL_BTN_MAX_RETRIES times. */
708
947
  private scheduleButtonRetry;
709
- /** Delete one button's message via its own adapter. Resolves on success,
948
+ /** Retire one cancel bubble via its own adapter. A bubble that accumulated
949
+ * tool progress is kept as a read-only history message; a plain bubble keeps
950
+ * the legacy delete/edit-to-checkmark behavior. Resolves on success and
710
951
  * rejects on failure so the caller can retry. */
711
952
  private deleteButtonMessage;
712
953
  /** Retire all cancel buttons for an instance — on reply or cancel. */
@@ -730,6 +971,123 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
730
971
  private reactDone;
731
972
  /** Interrupt an instance's current generation (cancel button / /cancel). */
732
973
  cancelInstance(instanceName: string): boolean;
974
+ getDeliveryEpoch(instanceName: string): number;
975
+ private isDeliveryEpochCurrent;
976
+ /** Invalidate work queued before a user cancel and wake idle-gate waiters. */
977
+ private cancelPendingDeliveries;
978
+ private activeLogin;
979
+ /** Post the backend chooser for a bare `/login`. Caller enforces admin. */
980
+ promptLoginBackends(chat: {
981
+ adapter: ChannelAdapter;
982
+ adapterId: string;
983
+ chatId: string;
984
+ threadId?: string;
985
+ }): Promise<void>;
986
+ /**
987
+ * Ask the General topic to approve a ClassicBot access request.
988
+ *
989
+ * Two affirmative buttons rather than one, when the trigger identified a
990
+ * user. Allowing a group to talk and granting that person ClassicBot admin
991
+ * (start/stop/model on classic channels — NOT fleet admin, which reads
992
+ * fleet.yaml `channel.access.allowed_users`) have different blast radii, and
993
+ * bundling them would force anyone who wants only the cheap one to accept the
994
+ * expensive one or go edit YAML by hand.
995
+ *
996
+ * Built on postNonceButtonPrompt, so it inherits the canonical-address
997
+ * binding (#682) that makes these buttons answer in a Telegram General topic.
998
+ */
999
+ private promptClassicApproval;
1000
+ /**
1001
+ * Apply an approved ClassicBot access request.
1002
+ *
1003
+ * Writes through ClassicChannelManager's typed mutators rather than handing
1004
+ * General a YAML edit: the id is stored via String() so a Discord snowflake
1005
+ * cannot land as a YAML integer and lose precision, the existing save() path
1006
+ * is reused, in-memory state is correct immediately (no waiting on the 30s
1007
+ * reload poll), and a click cannot silently do nothing because General
1008
+ * happened to be paused. General is still told the outcome.
1009
+ */
1010
+ private applyClassicApproval;
1011
+ /** Consume a ClassicBot approval button. */
1012
+ private handleClassicApproval;
1013
+ /**
1014
+ * Backend chooser for a bare `/install-cli`, mirroring promptLoginBackends so
1015
+ * both commands feel the same. Built on postNonceButtonPrompt rather than the
1016
+ * `/model` selection coordinator: that is the mechanism `/login` already uses,
1017
+ * and the one whose canonical-address binding (#682) makes the buttons answer
1018
+ * in a Telegram General topic.
1019
+ *
1020
+ * Unlike the login chooser this does NOT filter to backends the fleet already
1021
+ * runs. Installing is how you get a backend you do not have yet, so filtering
1022
+ * by configured backends would hide the only entry the admin came for.
1023
+ *
1024
+ * gemini-cli is omitted: it is deprecated (see backend/factory.ts). Typing
1025
+ * `/install-cli gemini-cli` still works — this only stops recommending it.
1026
+ */
1027
+ promptInstallBackends(chat: {
1028
+ adapter: ChannelAdapter;
1029
+ adapterId: string;
1030
+ chatId: string;
1031
+ threadId?: string;
1032
+ }): Promise<void>;
1033
+ /** Backend chooser button → start that backend's install session. */
1034
+ private handleInstallBackendSelect;
1035
+ /**
1036
+ * Start a login session for one backend. Caller enforces admin.
1037
+ * Returns a status line to post, or null when a confirmation prompt was
1038
+ * posted instead (auth still valid — see the pre-check below).
1039
+ */
1040
+ startLoginSession(backendArg: string, chat: {
1041
+ adapter: ChannelAdapter;
1042
+ adapterId: string;
1043
+ chatId: string;
1044
+ threadId?: string;
1045
+ }, opts?: {
1046
+ skipAuthCheck?: boolean;
1047
+ }): Promise<string | null>;
1048
+ /** Create the login window and session (pre-check already settled). */
1049
+ private launchLoginSession;
1050
+ /** `/login code <text>` — paste admin-supplied text into the login window. */
1051
+ loginSubmitInput(text: string): Promise<string>;
1052
+ /** `/login cancel` — abort the active session and remove its window. */
1053
+ cancelLoginSession(): Promise<string>;
1054
+ /**
1055
+ * The URL (+ code) is a live credential: whoever completes it binds THEIR
1056
+ * account to this fleet's CLI. Telegram gets an HTML spoiler (same treatment
1057
+ * as the dashboard token); other adapters get plain text with the warning.
1058
+ */
1059
+ private sendLoginSecret;
1060
+ /**
1061
+ * After a successful login: wake the paused instances of that backend AND
1062
+ * restart the running ones — a live CLI holds the old token in memory and
1063
+ * only re-reads credentials on process start (a paused instance's CLI is
1064
+ * already dead, so waking it respawns with the new token for free).
1065
+ */
1066
+ private recoverBackendInstances;
1067
+ /** Backend chooser button → start that backend's login session. */
1068
+ private handleLoginBackendSelect;
1069
+ /** Re-login confirmation (auth pre-check said credentials still work). */
1070
+ private handleLoginConfirm;
1071
+ /** Kiro provider button → drive the CLI's arrow-key selector. */
1072
+ private handleLoginMenuSelect;
1073
+ private activeInstall;
1074
+ /** Start a CLI install session. Caller enforces admin. */
1075
+ startInstallSession(backendArg: string, chat: {
1076
+ adapter: ChannelAdapter;
1077
+ adapterId: string;
1078
+ chatId: string;
1079
+ threadId?: string;
1080
+ }): Promise<string>;
1081
+ /** `/install-cli cancel` — abort the active install and remove its window. */
1082
+ cancelInstallSession(): Promise<string>;
1083
+ /** `command -v` on a login shell, so PATH additions from rc files count. */
1084
+ private verifyBinaryOnLoginShell;
1085
+ /** "Sign in now?" button after a successful install. */
1086
+ private handleInstallLoginConfirm;
1087
+ /** Discord native `/login` slash — shared by every adapter dispatch block. */
1088
+ private handleLoginSlash;
1089
+ /** Discord native `/install-cli` slash — shared by every dispatch block. */
1090
+ private handleInstallCliSlash;
733
1091
  queueMirrorMessage(text: string): void;
734
1092
  /** Push an SSE event to all connected Web UI clients. */
735
1093
  emitSseEvent(event: string, data: unknown): void;
@@ -831,6 +1189,34 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
831
1189
  private probeCliEnvs;
832
1190
  /** Best-effort model list for `/model`: cached CLI env first, else live probe. Never throws. */
833
1191
  private getModelOptions;
1192
+ /**
1193
+ * Model catalog behind the `list_models` tool.
1194
+ *
1195
+ * The two scopes are not cosmetic. "global" is the account/CLI catalog served
1196
+ * from the startup probe cache; "instance" is resolved through that instance's
1197
+ * OWN backend config, and for a Codex instance on a custom provider that is a
1198
+ * different catalog entirely — `listModels()` reads models_cache.json out of
1199
+ * the instance's private CODEX_HOME. Answering such an instance with the
1200
+ * account list would name models its CLI rejects, which is exactly the
1201
+ * mistake this tool exists to prevent.
1202
+ *
1203
+ * `scope` always describes where the returned LIST came from, not what was
1204
+ * asked for: an instance query that falls back to the account catalog reports
1205
+ * scope "global" and says so in `note`, rather than implying instance-level
1206
+ * accuracy it does not have.
1207
+ *
1208
+ * Never throws — a model listing is an aid, and failing it must not fail a turn.
1209
+ */
1210
+ listModelCatalog(opts?: {
1211
+ backend?: string;
1212
+ instanceName?: string;
1213
+ }): Promise<ModelCatalog>;
1214
+ /** The custom provider an instance overrides its backend with, if any. */
1215
+ private customProviderFor;
1216
+ /** Ask a backend for its catalog using ONE instance's real config. Never throws. */
1217
+ private instanceScopedModels;
1218
+ /** Account-wide catalog: probe cache first, live probe on miss. */
1219
+ private globalModelCatalog;
834
1220
  /** `/model` slash handler (admin only). No arg → DC menu; `/model <name>` → apply directly. */
835
1221
  /** Label an effort choice, marking the one currently configured. */
836
1222
  private effortChoiceLabel;
@@ -848,6 +1234,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
848
1234
  * Returns null on success (menu shown), or a fallback string to send.
849
1235
  */
850
1236
  promptModelMenu(instanceName: string, userId: string, channelId: string, adapter: ChannelAdapter, chatId: string, threadId?: string): Promise<string | null>;
1237
+ /** Cached-or-live account catalog behind "/model → more models" (claude). */
1238
+ private claudeApiModelOptions;
1239
+ /** Replace a consumed "/model" menu with the full account catalog tier. */
1240
+ private expandClaudeModelMenu;
851
1241
  /** Consume a `/model` selection callback. Returns true for all model-select ids (incl. stale). */
852
1242
  private handleModelSelection;
853
1243
  /** Apply a model to an instance: runtime paste (claude-code) or persist + restart (others). */
@@ -935,7 +1325,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
935
1325
  /**
936
1326
  * Hot-reload: re-read fleet.yaml and reconcile running instances.
937
1327
  * Starts new, stops removed, restarts modified instances.
938
- * Fleet-level config (access, cost_guard, etc.) requires /restart to take effect.
1328
+ * Whitelisted runtime fields are pushed into live daemons; all other instance
1329
+ * fields, plus cold fleet-level settings, retain restart semantics.
939
1330
  */
940
1331
  private reconcileInstances;
941
1332
  restartInstances(): Promise<void>;