@songsid/agend 2.1.2-beta.9 → 2.1.2

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 (172) hide show
  1. package/dist/access-path.js +15 -6
  2. package/dist/access-path.js.map +1 -1
  3. package/dist/agent-cli-instructions.md +1 -0
  4. package/dist/agent-cli.js +14 -0
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.d.ts +2 -0
  7. package/dist/agent-endpoint.js +25 -1
  8. package/dist/agent-endpoint.js.map +1 -1
  9. package/dist/backend/antigravity.d.ts +20 -0
  10. package/dist/backend/antigravity.js +51 -4
  11. package/dist/backend/antigravity.js.map +1 -1
  12. package/dist/backend/claude-code.d.ts +59 -0
  13. package/dist/backend/claude-code.js +67 -1
  14. package/dist/backend/claude-code.js.map +1 -1
  15. package/dist/backend/codex.d.ts +26 -0
  16. package/dist/backend/codex.js +100 -0
  17. package/dist/backend/codex.js.map +1 -1
  18. package/dist/backend/grok.d.ts +25 -0
  19. package/dist/backend/grok.js +31 -0
  20. package/dist/backend/grok.js.map +1 -1
  21. package/dist/backend/kiro.d.ts +23 -0
  22. package/dist/backend/kiro.js +120 -2
  23. package/dist/backend/kiro.js.map +1 -1
  24. package/dist/backend/types.d.ts +58 -0
  25. package/dist/backend/types.js +13 -1
  26. package/dist/backend/types.js.map +1 -1
  27. package/dist/channel/adapters/discord.d.ts +15 -0
  28. package/dist/channel/adapters/discord.js +120 -8
  29. package/dist/channel/adapters/discord.js.map +1 -1
  30. package/dist/channel/adapters/telegram.d.ts +21 -0
  31. package/dist/channel/adapters/telegram.js +86 -0
  32. package/dist/channel/adapters/telegram.js.map +1 -1
  33. package/dist/channel/ipc-bridge.d.ts +9 -1
  34. package/dist/channel/ipc-bridge.js +12 -3
  35. package/dist/channel/ipc-bridge.js.map +1 -1
  36. package/dist/channel/ipc-timeouts.d.ts +46 -0
  37. package/dist/channel/ipc-timeouts.js +65 -0
  38. package/dist/channel/ipc-timeouts.js.map +1 -0
  39. package/dist/channel/mcp-server.js +25 -14
  40. package/dist/channel/mcp-server.js.map +1 -1
  41. package/dist/channel/mcp-tools.js +30 -1
  42. package/dist/channel/mcp-tools.js.map +1 -1
  43. package/dist/channel/reconnect-backoff.d.ts +17 -0
  44. package/dist/channel/reconnect-backoff.js +21 -0
  45. package/dist/channel/reconnect-backoff.js.map +1 -0
  46. package/dist/channel/types.d.ts +29 -0
  47. package/dist/classic-channel-manager.js +1 -5
  48. package/dist/classic-channel-manager.js.map +1 -1
  49. package/dist/cli.js +242 -91
  50. package/dist/cli.js.map +1 -1
  51. package/dist/completion.d.ts +27 -0
  52. package/dist/completion.js +121 -0
  53. package/dist/completion.js.map +1 -0
  54. package/dist/config-validator.js +21 -0
  55. package/dist/config-validator.js.map +1 -1
  56. package/dist/config.js +2 -0
  57. package/dist/config.js.map +1 -1
  58. package/dist/cost-guard.d.ts +3 -1
  59. package/dist/cost-guard.js +3 -1
  60. package/dist/cost-guard.js.map +1 -1
  61. package/dist/daemon.d.ts +298 -6
  62. package/dist/daemon.js +1142 -313
  63. package/dist/daemon.js.map +1 -1
  64. package/dist/event-log.d.ts +31 -0
  65. package/dist/event-log.js +96 -0
  66. package/dist/event-log.js.map +1 -1
  67. package/dist/fleet-context.d.ts +14 -0
  68. package/dist/fleet-lock.d.ts +28 -0
  69. package/dist/fleet-lock.js +130 -0
  70. package/dist/fleet-lock.js.map +1 -0
  71. package/dist/fleet-manager.d.ts +348 -6
  72. package/dist/fleet-manager.js +1859 -266
  73. package/dist/fleet-manager.js.map +1 -1
  74. package/dist/general-instance.d.ts +9 -0
  75. package/dist/general-instance.js +11 -0
  76. package/dist/general-instance.js.map +1 -0
  77. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +22 -0
  78. package/dist/general-knowledge/skills/model-discovery/SKILL.md +14 -23
  79. package/dist/general-knowledge/skills/session-management/SKILL.md +15 -34
  80. package/dist/hang-detector.d.ts +19 -13
  81. package/dist/hang-detector.js +19 -49
  82. package/dist/hang-detector.js.map +1 -1
  83. package/dist/instance-lifecycle.d.ts +58 -2
  84. package/dist/instance-lifecycle.js +231 -46
  85. package/dist/instance-lifecycle.js.map +1 -1
  86. package/dist/locale.js +7 -1
  87. package/dist/locale.js.map +1 -1
  88. package/dist/mcp-liveness.d.ts +21 -0
  89. package/dist/mcp-liveness.js +27 -0
  90. package/dist/mcp-liveness.js.map +1 -0
  91. package/dist/outbound-handlers.d.ts +9 -0
  92. package/dist/outbound-handlers.js +194 -31
  93. package/dist/outbound-handlers.js.map +1 -1
  94. package/dist/outbound-schemas.d.ts +11 -3
  95. package/dist/outbound-schemas.js +16 -2
  96. package/dist/outbound-schemas.js.map +1 -1
  97. package/dist/pane-write-lock.d.ts +48 -0
  98. package/dist/pane-write-lock.js +73 -0
  99. package/dist/pane-write-lock.js.map +1 -0
  100. package/dist/process-memory.d.ts +31 -0
  101. package/dist/process-memory.js +79 -0
  102. package/dist/process-memory.js.map +1 -0
  103. package/dist/quickstart.js +17 -16
  104. package/dist/quickstart.js.map +1 -1
  105. package/dist/reply-dedup.d.ts +41 -0
  106. package/dist/reply-dedup.js +0 -0
  107. package/dist/reply-dedup.js.map +1 -0
  108. package/dist/restart-progress.d.ts +41 -0
  109. package/dist/restart-progress.js +97 -0
  110. package/dist/restart-progress.js.map +1 -0
  111. package/dist/scheduler/db.js +3 -0
  112. package/dist/scheduler/db.js.map +1 -1
  113. package/dist/sd-notify.d.ts +27 -0
  114. package/dist/sd-notify.js +33 -1
  115. package/dist/sd-notify.js.map +1 -1
  116. package/dist/secret-file.d.ts +33 -0
  117. package/dist/secret-file.js +36 -0
  118. package/dist/secret-file.js.map +1 -0
  119. package/dist/service-installer.d.ts +28 -0
  120. package/dist/service-installer.js +72 -1
  121. package/dist/service-installer.js.map +1 -1
  122. package/dist/settings-api.js +5 -3
  123. package/dist/settings-api.js.map +1 -1
  124. package/dist/setup-wizard.js +9 -7
  125. package/dist/setup-wizard.js.map +1 -1
  126. package/dist/tmux-control.d.ts +58 -5
  127. package/dist/tmux-control.js +102 -14
  128. package/dist/tmux-control.js.map +1 -1
  129. package/dist/tmux-manager.d.ts +31 -1
  130. package/dist/tmux-manager.js +40 -11
  131. package/dist/tmux-manager.js.map +1 -1
  132. package/dist/topic-commands.d.ts +49 -9
  133. package/dist/topic-commands.js +270 -109
  134. package/dist/topic-commands.js.map +1 -1
  135. package/dist/tui-glyphs.d.ts +5 -1
  136. package/dist/tui-glyphs.js +6 -2
  137. package/dist/tui-glyphs.js.map +1 -1
  138. package/dist/types.d.ts +19 -2
  139. package/dist/ui/settings.html +1 -1
  140. package/dist/ui/view.html +123 -2
  141. package/dist/update-marker.d.ts +15 -0
  142. package/dist/update-marker.js +69 -0
  143. package/dist/update-marker.js.map +1 -0
  144. package/dist/usage/format-rich.d.ts +11 -0
  145. package/dist/usage/format-rich.js +121 -0
  146. package/dist/usage/format-rich.js.map +1 -0
  147. package/dist/usage/provider-alerts.d.ts +33 -0
  148. package/dist/usage/provider-alerts.js +57 -0
  149. package/dist/usage/provider-alerts.js.map +1 -0
  150. package/dist/usage/providers.d.ts +130 -0
  151. package/dist/usage/providers.js +1063 -0
  152. package/dist/usage/providers.js.map +1 -0
  153. package/dist/usage/statusline-usage.d.ts +20 -0
  154. package/dist/usage/statusline-usage.js +120 -0
  155. package/dist/usage/statusline-usage.js.map +1 -0
  156. package/dist/usage/usage-api.d.ts +50 -0
  157. package/dist/usage/usage-api.js +197 -0
  158. package/dist/usage/usage-api.js.map +1 -0
  159. package/dist/view-api.js +8 -3
  160. package/dist/view-api.js.map +1 -1
  161. package/dist/web-api.js +7 -3
  162. package/dist/web-api.js.map +1 -1
  163. package/dist/web-auth.d.ts +6 -0
  164. package/dist/web-auth.js +54 -0
  165. package/dist/web-auth.js.map +1 -0
  166. package/package.json +4 -1
  167. package/dist/channel/tool-tracker.d.ts +0 -13
  168. package/dist/channel/tool-tracker.js +0 -58
  169. package/dist/channel/tool-tracker.js.map +0 -1
  170. package/dist/daemon-entry.d.ts +0 -1
  171. package/dist/daemon-entry.js +0 -30
  172. package/dist/daemon-entry.js.map +0 -1
package/dist/daemon.d.ts CHANGED
@@ -11,12 +11,55 @@ export declare function buildInstructionReloadNotice(binaryName: string, instanc
11
11
  export declare const DEFAULT_STUCK_TIMEOUT_MS: number;
12
12
  export declare const DEFAULT_STATE_IDLE_DEBOUNCE_MS = 2000;
13
13
  export declare const DEFAULT_STATE_SAFETY_SWEEP_MS = 60000;
14
- /** @deprecated State detection is event-driven; this now aliases the safety sweep. */
15
- export declare const DEFAULT_STATE_POLL_INTERVAL_MS = 60000;
14
+ /**
15
+ * Whether two working directories belong to the same project, so a fleet-scoped
16
+ * decision recorded in one reaches the other. Covers the worktree/checkout
17
+ * layout AgEnD fleets actually use — `AgEnD`, `AgEnD-dev1`, `AgEnD-dev2`,
18
+ * `AgEnD-reviewer`, `AgEnD-main` are one project; `DouPo_Server` is not.
19
+ * Nesting counts too (a subdirectory of a project belongs to it).
20
+ */
21
+ export declare function sameProjectFamily(a: string, b: string): boolean;
22
+ /**
23
+ * Pick the decisions worth injecting into one instance's system prompt.
24
+ *
25
+ * `scope: "fleet"` previously bypassed the project check outright, so every
26
+ * instance carried every other project's playbook — measured at 8 of 14 here,
27
+ * resent on every API call. A fleet decision must now also be relevant: global
28
+ * (no project_root), the same project, or the same project family.
29
+ *
30
+ * `isDispatcher` (a general) opts out of that narrowing on purpose: it routes
31
+ * work across all projects, so cross-project rules ("X 文件操作由 Y 負責") are
32
+ * precisely what it must know. Narrowing a general would cause misrouting.
33
+ */
34
+ export declare function selectRelevantDecisions<T extends {
35
+ scope?: string;
36
+ project_root?: string;
37
+ title: string;
38
+ }>(all: T[], workDir: string, isDispatcher?: boolean): T[];
16
39
  /** Read the last real channel inbound timestamp persisted across daemon restarts. */
17
40
  export declare function readLastInboundAt(instanceDir: string, now?: number): number | null;
18
41
  /** Atomically persist the last real channel inbound timestamp. */
19
42
  export declare function writeLastInboundAt(instanceDir: string, timestamp: number): void;
43
+ /**
44
+ * Render the handoff/routing metadata that rides along with an inbound message
45
+ * into the block actually pasted into the agent's pane.
46
+ *
47
+ * These fields were all populated by the sender (outbound-handlers builds them
48
+ * into `ipcMeta`) and delivered over IPC, but never rendered — so the receiving
49
+ * agent could not see them, and several documented flows could not work:
50
+ *
51
+ * - `report_result` requires a `correlation_id` the agent had no way to know, so
52
+ * the "correlation_id not recognized" warning fired on essentially every call.
53
+ * - Delegation cancel buttons are retired by correlation id, so they never retired.
54
+ * - `react`, `edit_message` and `reply.reply_to` need `message_id`, which the tool
55
+ * descriptions tell the agent to take "from the inbound block".
56
+ * - `download_attachment` needs `attachment_file_id`.
57
+ * - `requires_reply` was invisible, so a delegated task looked like an FYI.
58
+ *
59
+ * Only non-empty fields are emitted, so a plain user message gains at most a
60
+ * message_id line.
61
+ */
62
+ export declare function renderHandoffMetadata(meta: Record<string, string>): string;
20
63
  /** Headless inactivity timer used by the daemon and unit tests. */
21
64
  export declare class AutoPauseController {
22
65
  private readonly thresholdMs;
@@ -37,17 +80,73 @@ export declare class AutoPauseController {
37
80
  * Pane motion wins over a ready match because several backends keep their ready
38
81
  * marker in a persistent header/footer while generating. A stable ready pane is
39
82
  * idle; changing content is working; stable non-ready content eventually sticks.
83
+ *
84
+ * ## Why an optional busy pattern exists
85
+ *
86
+ * "Motion wins" degrades badly when a backend's ready marker is *always* on
87
+ * screen. `stuck` requires `!ready`, so for such a backend the state can only ever
88
+ * be idle or working — the stuck edge, and with it `handleStuckTransition` and the
89
+ * hang notification, is unreachable. A frozen CLI is then reported as idle, which
90
+ * also clears pending work: the message the user sent is quietly booked as done.
91
+ *
92
+ * claude-code was exactly this case (see ClaudeCodeBackend.getReadyPattern). A
93
+ * backend that can point at a marker meaning "generating right now" supplies
94
+ * `getBusyPattern()`, and that match vetoes ready regardless of what the ready
95
+ * pattern says.
96
+ */
97
+ /**
98
+ * A shell command reduced to the part that is safe to show: the program, and a
99
+ * subcommand when there is one.
100
+ *
101
+ * curl -H "Bearer sk-ant-…" https://api.com → curl
102
+ * npm test --env=API_KEY=xxx → npm test
103
+ * git push origin main → git push
104
+ *
105
+ * Arguments are dropped wholesale rather than filtered, because there is no
106
+ * reliable way to tell a secret from an ordinary argument: tokens, bearer
107
+ * headers, connection strings and `--password=` values all look like text. The
108
+ * progress line is posted to a chat channel and, for a public one, so is
109
+ * anything in it — so the rule is "never the arguments", not "not the arguments
110
+ * that look dangerous".
111
+ *
112
+ * A second token is kept only when it is a bare word (`push`, `test`), which is
113
+ * what a subcommand looks like and what a flag, path, URL, assignment or quoted
114
+ * string does not. Everything after it goes, including `git push ORIGIN MAIN`.
40
115
  */
116
+ export declare function shellCommandLabel(command: string): string;
41
117
  export declare class PaneStateMachine {
42
118
  private readonly stuckTimeoutMs;
43
119
  private readonly readyPattern;
120
+ private readonly busyPattern;
44
121
  private lastPaneHash;
45
122
  private lastPaneChangeAt;
46
123
  private lastObservedAt;
47
124
  private stateChangedAt;
48
125
  private currentState;
49
- constructor(readyPattern: RegExp, stuckTimeoutMs?: number, now?: number);
50
- observe(pane: string, now?: number): InstanceStateSnapshot;
126
+ constructor(readyPattern: RegExp, stuckTimeoutMs?: number, now?: number, busyPattern?: RegExp | null);
127
+ /** Ready, unless the backend can positively see that it is still generating. */
128
+ private isReady;
129
+ /**
130
+ * @param now when this observation is being evaluated — drives the idle and
131
+ * stuck decisions.
132
+ * @param opts.changeAt when the content is believed to have changed, if that
133
+ * is earlier than `now` (the tmux output timestamp). Only the elapsed-time
134
+ * clock uses it; defaults to `now`.
135
+ * @param opts.settled the caller knows no output has arrived for the debounce
136
+ * window, so "the content differs from the last capture" says nothing about
137
+ * whether the CLI is still generating — decide on the patterns instead.
138
+ *
139
+ * Captures are event-driven, not periodic: the one taken 2s after output
140
+ * stops is compared against a capture that may be a minute old, so it
141
+ * *always* differs. Without this flag that difference reads as "still
142
+ * working" and a finished turn would not be seen as idle until the next 60s
143
+ * safety sweep. Output timestamps are the honest liveness signal here, and
144
+ * the caller has them.
145
+ */
146
+ observe(pane: string, now?: number, opts?: {
147
+ settled?: boolean;
148
+ changeAt?: number;
149
+ }): InstanceStateSnapshot;
51
150
  /** Record pane motion from tmux control mode without capturing pane content. */
52
151
  recordOutput(now?: number): InstanceStateSnapshot;
53
152
  snapshot(now?: number): InstanceStateSnapshot;
@@ -77,6 +176,27 @@ export declare class FirstDeliveryDelay {
77
176
  }
78
177
  /** Redact likely credentials and control sequences before pane text reaches logs. */
79
178
  export declare function sanitizePaneTail(pane: string, lineCount?: number): string[];
179
+ export type InteractivePromptKind = "sudo_password" | "password" | "confirmation" | "press_enter";
180
+ export interface InteractivePromptDetection {
181
+ kind: InteractivePromptKind;
182
+ prompt: string;
183
+ }
184
+ /**
185
+ * Detect terminal prompts that require a human, but only after the pane tail is
186
+ * unchanged and no control-mode output has arrived for the full grace period.
187
+ * This prevents prose such as "the installer asks [Y/n]" from notifying while
188
+ * an agent is still writing it.
189
+ */
190
+ export declare class InteractivePromptDetector {
191
+ private readonly stableMs;
192
+ private signature;
193
+ private stableSince;
194
+ private lastOutputAt;
195
+ private notifiedSignature;
196
+ constructor(stableMs?: number);
197
+ observe(pane: string, now?: number, outputAt?: number): InteractivePromptDetection | null;
198
+ reset(): void;
199
+ }
80
200
  export declare class Daemon extends EventEmitter {
81
201
  private name;
82
202
  private config;
@@ -91,7 +211,6 @@ export declare class Daemon extends EventEmitter {
91
211
  private ipcServer;
92
212
  private messageBus;
93
213
  private transcriptMonitor;
94
- private toolTracker;
95
214
  private guardian;
96
215
  private adapter;
97
216
  private pendingIpcRequests;
@@ -99,6 +218,8 @@ export declare class Daemon extends EventEmitter {
99
218
  private lastThreadId;
100
219
  private lastAdapterId;
101
220
  private pendingAckMessage;
221
+ /** Last activity published to the fleet manager; null when nothing is running. */
222
+ private currentActivity;
102
223
  private toolStatusMessageId;
103
224
  private toolStatusLines;
104
225
  private toolStatusDebounce;
@@ -112,9 +233,19 @@ export declare class Daemon extends EventEmitter {
112
233
  private lastSpawnAt;
113
234
  private crashTimestamps;
114
235
  private healthCheckPaused;
236
+ private lastHealthErrorNotifyAt;
115
237
  /** CLI pane availability, independent from the daemon process and tri-state. */
116
238
  private processStatus;
117
239
  private spawning;
240
+ /**
241
+ * Resolves when the in-flight spawn finishes; null when none is running.
242
+ *
243
+ * `spawning` alone can only be polled. Delivery needs to *wait*, and a boolean
244
+ * poll would either busy-loop or race the flag being cleared.
245
+ */
246
+ private spawnSettled;
247
+ private resolveSpawnSettled;
248
+ private spawnDepth;
118
249
  private skipResume;
119
250
  private backgroundSessionRecoveryAttempted;
120
251
  /** Whether the last spawn started a fresh session (not resumed). */
@@ -136,10 +267,18 @@ export declare class Daemon extends EventEmitter {
136
267
  private instanceStateIdleDebounceMs;
137
268
  private instanceStateStuckTimeoutMs;
138
269
  private instanceStateReadyPattern;
270
+ private instanceStateBusyPattern;
139
271
  private instanceStateMonitorActive;
140
272
  private statePollInFlight;
141
273
  private autoPauseController;
142
274
  private pauseRequested;
275
+ /**
276
+ * Sticky "pause as soon as possible" (auth failure). Unlike pauseRequested —
277
+ * which the state monitor clears whenever the pane is not idle — this survives
278
+ * busy/stuck states, because an auth error fires mid-turn and a plain pause()
279
+ * would silently no-op exactly when we most need to stop feeding the CLI.
280
+ */
281
+ private pausePending;
143
282
  private pauseWakeState;
144
283
  private pauseWakeTransition;
145
284
  private modelOverride;
@@ -147,7 +286,13 @@ export declare class Daemon extends EventEmitter {
147
286
  private recentEvents;
148
287
  private recentToolActivity;
149
288
  private snapshotConsumed;
289
+ /** Orders inbound channel messages against each other (queue-depth accounting
290
+ * and ⏳/👀/✅ reactions live here). It does NOT cover the other writers that
291
+ * reach the pane — {@link paneWriteLock} does. */
150
292
  private pasteLock;
293
+ /** Mutual exclusion for *every* write into the pane, whichever subsystem it
294
+ * comes from. See PaneWriteLock for why interleaving is destructive. */
295
+ private readonly paneWriteLock;
151
296
  private pendingInstructionsUpdate;
152
297
  private pendingInstructionsNotice;
153
298
  private warmupNeeded;
@@ -155,6 +300,26 @@ export declare class Daemon extends EventEmitter {
155
300
  private pasteQueueDepth;
156
301
  private firstDeliveryDelay;
157
302
  private errorMonitorTimer;
303
+ private readonly interactivePromptDetector;
304
+ /** Same 5-min gate the error monitor uses, so a dead MCP server alerts once. */
305
+ private static readonly MCP_DEATH_COOLDOWN_MS;
306
+ private lastMcpDeathNotifiedAt;
307
+ private mcpDeathNotifiedForPid;
308
+ /**
309
+ * Ceiling on how long a dead MCP server waits for an idle window before the
310
+ * auto-restart fires anyway. A toolless instance cannot reply or report, so
311
+ * whatever a very long turn produces is stranded until the restart happens —
312
+ * past this point interrupting the turn costs less than staying mute.
313
+ */
314
+ private static readonly MCP_RESTART_MAX_WAIT_MS;
315
+ /**
316
+ * Sticky "restart to revive the MCP server as soon as the pane idles" — same
317
+ * shape as pausePending, because the death is usually detected mid-turn when
318
+ * an immediate restart would destroy in-flight work. Cleared when the server
319
+ * turns up alive again (operator restarted, CLI reconnected) before we fire.
320
+ */
321
+ private mcpRestartPending;
322
+ private mcpRestartStaleTimer;
158
323
  /** Prevent in-flight monitor callbacks from re-arming after a pause. */
159
324
  private runtimeMonitorsFrozen;
160
325
  private errorWaitingForRecovery;
@@ -164,6 +329,8 @@ export declare class Daemon extends EventEmitter {
164
329
  /** Whether this instance is in an abnormal error state (auto-pause is normal). */
165
330
  get isErrorState(): boolean;
166
331
  get isPaused(): boolean;
332
+ /** Current CLI process state for status surfaces and recovery commands. */
333
+ getProcessStatus(): "running" | "crashed" | "stopped";
167
334
  get lastPausedAt(): number | null;
168
335
  private getPauseWakeState;
169
336
  /** Whether this instance is in a crash loop (3+ consecutive crashes). */
@@ -181,12 +348,50 @@ export declare class Daemon extends EventEmitter {
181
348
  private clearErrorRecoveryGate;
182
349
  constructor(name: string, config: InstanceConfig, instanceDir: string, topicMode?: boolean, backend?: CliBackend | undefined, controlClient?: TmuxControlClient | undefined, rootLogger?: Logger, runtimeIdentity?: FleetInstructionsParams["runtimeIdentity"]);
183
350
  start(): Promise<void>;
351
+ /**
352
+ * Detect a CRASHED MCP server and report it once.
353
+ *
354
+ * The MCP server writes its pid to channel.mcp.pid at startup and unlinks it on
355
+ * a clean exit, so "file present + pid dead" specifically means it died without
356
+ * cleaning up (crash / OOM / SIGKILL). A missing file is either "not started
357
+ * yet" or "exited cleanly" — neither is an incident, and an orphan exit implies
358
+ * the CLI itself died, which the crash detector already covers.
359
+ *
360
+ * The daemon deliberately does NOT try to respawn it: MCP is a stdio protocol
361
+ * where the CLI spawns the server and owns its pipes, so a daemon-spawned
362
+ * process would have no client reading it. Only the CLI can restore its own
363
+ * tools — hence notify, and let the operator decide about restarting.
364
+ */
365
+ private checkMcpServerAlive;
366
+ /**
367
+ * Arm an automatic instance restart to revive a dead MCP server. Fires on the
368
+ * next busy→idle edge — or immediately when the pane is already idle, which is
369
+ * the common case for a collab/chat instance parked between messages (no edge
370
+ * would ever come). A turn that outlives MCP_RESTART_MAX_WAIT_MS is
371
+ * force-restarted: see that constant for why staying mute costs more.
372
+ */
373
+ private armMcpRestartWhenIdle;
374
+ private fireMcpRestartRequest;
375
+ private clearMcpRestartRequest;
184
376
  private startHealthCheck;
185
377
  /**
186
378
  * Publish pane-process availability separately from idle/working/stuck.
187
379
  * A dead remain-on-exit pane still contains the old ready prompt, so allowing
188
380
  * the pane monitor to capture it would re-create a false idle state.
189
381
  */
382
+ /**
383
+ * Announce that this instance is no longer being supervised.
384
+ *
385
+ * Four health-check exits set `healthCheckPaused = true` and returned without
386
+ * telling anyone: a clean CLI exit, `max_retries <= 0`, crash retries exhausted,
387
+ * and the instance directory being deleted. Only the crash-LOOP case emitted an
388
+ * event, so a fleet could quietly contain a dead instance that still looked fine
389
+ * on the dashboard — while messages routed to it queued or failed with a bare ❌.
390
+ *
391
+ * `crash_loop` already had this treatment; this is the same bridge for the other
392
+ * four, carrying a human-readable cause and the operator's next step.
393
+ */
394
+ private emitSupervisionEnded;
190
395
  private setProcessStatus;
191
396
  /**
192
397
  * Periodically scan PTY output for backend-defined error patterns.
@@ -213,6 +418,12 @@ export declare class Daemon extends EventEmitter {
213
418
  * Interrupt the CLI's current generation (cancel button / `/cancel`).
214
419
  * Direct tmux key event (not a paste) so it registers as the interrupt key.
215
420
  * kiro-cli interrupts on Ctrl+C; the others (claude-code, codex, …) on Escape.
421
+ *
422
+ * Deliberately NOT taken through `paneWriteLock`. Cancel is the user's way out
423
+ * of a pane that is busy or wedged — queueing it behind the very delivery chain
424
+ * it exists to unblock would make the button useless exactly when it is needed.
425
+ * The cost is accepted: an Escape landing between a paste and its Enter discards
426
+ * that message, which is what the user asked for anyway.
216
427
  */
217
428
  sendEscape(): Promise<void>;
218
429
  /** Send the backend-specific graceful quit command/key sequence. */
@@ -222,6 +433,12 @@ export declare class Daemon extends EventEmitter {
222
433
  getInstanceState(): InstanceState | "paused";
223
434
  getInstanceStateSnapshot(): InstanceStateSnapshot;
224
435
  /** Gracefully stop the CLI while keeping its remain-on-exit tmux window. */
436
+ /**
437
+ * Mark the instance to pause as soon as its pane is idle. Used for auth
438
+ * failures: pause() alone no-ops while the CLI is busy/stuck, which is the
439
+ * usual state when the error surfaces. Cleared by a successful pause or wake.
440
+ */
441
+ requestPauseWhenIdle(): void;
225
442
  pause(): Promise<void>;
226
443
  /** Respawn the CLI in the preserved window and block until its prompt is ready. */
227
444
  wake(timeoutMs?: number): Promise<void>;
@@ -241,6 +458,28 @@ export declare class Daemon extends EventEmitter {
241
458
  /** Restore the same monitor objects after wake without adding event listeners. */
242
459
  private resumeRuntimeMonitors;
243
460
  getMessageBus(): MessageBus;
461
+ /**
462
+ * Tell the fleet manager what this instance is doing right now, for the live
463
+ * progress line on the cancel button.
464
+ *
465
+ * Purely cosmetic — nothing decides anything from it, so it is fine that only
466
+ * backends with a transcript feed report at all (claude-code today). For the
467
+ * rest the progress line keeps showing just elapsed time.
468
+ *
469
+ * Repeats are dropped: the ticker only edits the channel message when the text
470
+ * changes, and a stream of identical broadcasts would defeat that.
471
+ */
472
+ private publishActivity;
473
+ /**
474
+ * Options for every system-initiated paste (startup notice, session snapshot,
475
+ * runtime-dialog keys).
476
+ *
477
+ * One place, so the three call sites cannot drift apart on it. Backends with a
478
+ * native input queue do not get the retry Enter: on those, a second bare Enter
479
+ * is not the no-op the retry assumed but a queue mutation — the same reason
480
+ * `deliverMessage` refuses to probe for busy with one.
481
+ */
482
+ private systemPasteOptions;
244
483
  private summarizeTool;
245
484
  private addToolStatus;
246
485
  /** Debounce tool status updates to avoid channel rate limits */
@@ -271,9 +510,37 @@ export declare class Daemon extends EventEmitter {
271
510
  * serial in both cases so separate PTY writes can never overlap.
272
511
  */
273
512
  private deliverMessage;
513
+ /**
514
+ * The write half of a delivery: paste → Enter → confirm, with retries.
515
+ *
516
+ * Always called under {@link paneWriteLock}. Split out from `deliverMessage`
517
+ * precisely so the lock's scope is visible at the call site rather than being
518
+ * an invariant maintained by comments.
519
+ */
520
+ private writeMessageToPane;
521
+ /**
522
+ * True when a busy native-queue paste appears to have landed: Codex shows a
523
+ * `↳` queue marker, or a distinctive slice of the pasted text is on screen.
524
+ * Used only to detect silent paste loss — not as a general ready check.
525
+ */
526
+ private nativeQueuePasteVisible;
274
527
  /** Re-resolve this instance's tmux window by name (stale id after crash/respawn). */
275
528
  private recoverWindow;
276
- /** Poll up to ~2s (200ms × 10) for the pane to emit output after `since`. */
529
+ /**
530
+ * Poll up to ~2s (200ms × 10) for the pane to emit output after `since`.
531
+ *
532
+ * A `false` here is not "no output" — it is read as **"the message was pasted but
533
+ * never submitted"**, which re-sends Enter and then reports ❌ to the user. So a
534
+ * false negative costs a possible double submit and a failure notice for a
535
+ * message that actually arrived.
536
+ *
537
+ * That is exactly what a control-mode reconnect used to produce. `connect()`
538
+ * drops every output timestamp, so `hasOutputSince` answers `false` for a pane
539
+ * that did react — the evidence was thrown away, not absent. Blind time is
540
+ * therefore not counted against the budget: after a reset the poll restarts from
541
+ * the moment observation resumed. A hard wall-clock cap keeps a reconnect loop
542
+ * from extending this forever.
543
+ */
277
544
  private confirmBusyAfterEnter;
278
545
  private getWindowId;
279
546
  /** Find the IPC socket for a given sessionName */
@@ -291,6 +558,31 @@ export declare class Daemon extends EventEmitter {
291
558
  * This replaces the old system-prompt injection approach.
292
559
  */
293
560
  private injectSnapshotMessage;
561
+ /**
562
+ * Depth, not a flag: the wake path marks a spawn and then calls trySpawn, which
563
+ * marks another. With a plain boolean the inner one's completion would clear the
564
+ * latch while the outer spawn was still dismissing dialogs, releasing delivery
565
+ * into exactly the window this exists to close.
566
+ */
567
+ private beginSpawn;
568
+ private endSpawn;
569
+ /**
570
+ * Hold until the CLI has finished starting up.
571
+ *
572
+ * Startup is not a quiet period: `dismissDialogsUntilReady` is clicking through
573
+ * trust prompts and session pickers. A pane showing a modal dialog produces no
574
+ * output, so `waitUntilIdle` reports it idle and a queued message gets pasted
575
+ * *into the dialog* — where the text is discarded and the Enter picks a menu
576
+ * item. The paste and the Enter both "succeed", so this loses the message
577
+ * without even a ❌.
578
+ *
579
+ * The pane write lock (#414) does not cover this: it serialises each key
580
+ * sequence, but it is released between one dialog and the next.
581
+ *
582
+ * Bounded, and called BEFORE the pane lock is taken — waiting on the spawn while
583
+ * holding the lock the spawn itself needs would deadlock.
584
+ */
585
+ private waitForSpawnToSettle;
294
586
  /** Spawn a CLI window. Returns true if --resume was used successfully. */
295
587
  private spawnClaudeWindow;
296
588
  /** Kill the entire process tree of the current tmux pane (CLI + MCP server). */