@integrity-labs/agt-cli 0.28.960 → 0.28.962
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.
- package/dist/bin/agt.js +5 -5
- package/dist/{chunk-3ICRKL3Y.js → chunk-37AUMVY3.js} +4 -4
- package/dist/{chunk-GSRUWWT6.js → chunk-D3FGOGI2.js} +5 -1
- package/dist/chunk-D3FGOGI2.js.map +1 -0
- package/dist/{chunk-SQGW4VAW.js → chunk-MNARTDPO.js} +2 -2
- package/dist/{claude-pair-runtime-5Q2WNQVB.js → claude-pair-runtime-6DGCWXRM.js} +105 -22
- package/dist/claude-pair-runtime-6DGCWXRM.js.map +1 -0
- package/dist/lib/manager-worker.js +14 -14
- package/dist/mcp/direct-chat-channel.js +4 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/origami.js +4 -0
- package/dist/mcp/slack-channel.js +4 -0
- package/dist/mcp/telegram-channel.js +4 -0
- package/dist/{persistent-session-OD7M7K5C.js → persistent-session-22PIKH3S.js} +3 -3
- package/dist/{responsiveness-probe-73TIJ6ZV.js → responsiveness-probe-3HPANEK6.js} +3 -3
- package/dist/{session-auth-dead-FGWPJVFS.js → session-auth-dead-FAIJPJUK.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-GSRUWWT6.js.map +0 -1
- package/dist/claude-pair-runtime-5Q2WNQVB.js.map +0 -1
- /package/dist/{chunk-3ICRKL3Y.js.map → chunk-37AUMVY3.js.map} +0 -0
- /package/dist/{chunk-SQGW4VAW.js.map → chunk-MNARTDPO.js.map} +0 -0
- /package/dist/{persistent-session-OD7M7K5C.js.map → persistent-session-22PIKH3S.js.map} +0 -0
- /package/dist/{responsiveness-probe-73TIJ6ZV.js.map → responsiveness-probe-3HPANEK6.js.map} +0 -0
- /package/dist/{session-auth-dead-FGWPJVFS.js.map → session-auth-dead-FAIJPJUK.js.map} +0 -0
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
rotateDailySession,
|
|
21
21
|
sessionFileExists,
|
|
22
22
|
todayLocalIso
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-D3FGOGI2.js";
|
|
24
24
|
import {
|
|
25
25
|
classifyUnanswerablePane,
|
|
26
26
|
findUsageLimitResetHint,
|
|
@@ -5575,4 +5575,4 @@ export {
|
|
|
5575
5575
|
stopAllSessionsAndWait,
|
|
5576
5576
|
getProjectDir
|
|
5577
5577
|
};
|
|
5578
|
-
//# sourceMappingURL=chunk-
|
|
5578
|
+
//# sourceMappingURL=chunk-MNARTDPO.js.map
|
|
@@ -111,60 +111,132 @@ async function sendKeySequence(session, keys) {
|
|
|
111
111
|
await sendKeys(session, keys[i]);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
var DEAD_PANE_STATUS_WAIT_MS = 15e3;
|
|
115
|
+
async function awaitDeadPaneVerdict(io, initial, opts = {}) {
|
|
116
|
+
const waitMs = opts.waitMs ?? DEAD_PANE_STATUS_WAIT_MS;
|
|
117
|
+
const pollMs = opts.pollMs ?? 100;
|
|
118
|
+
const captureEveryMs = opts.captureEveryMs ?? 1e3;
|
|
119
|
+
const numeric = (v) => /^\d+$/.test(v.trim());
|
|
120
|
+
const signalled = (v) => v.trim().length > 0 && !v.includes("#{");
|
|
121
|
+
const started = Date.now();
|
|
122
|
+
const deadline = started + waitMs;
|
|
123
|
+
let reading = initial;
|
|
124
|
+
let pane = "";
|
|
125
|
+
let polls = 0;
|
|
126
|
+
let readFailures = 0;
|
|
127
|
+
let nextCaptureAt = started + captureEveryMs;
|
|
128
|
+
let resolved = "timeout";
|
|
129
|
+
if (numeric(reading.exitStatus)) resolved = "status";
|
|
130
|
+
else if (signalled(reading.signal)) resolved = "signal";
|
|
131
|
+
while (resolved === "timeout" && Date.now() < deadline) {
|
|
132
|
+
await sleep(pollMs);
|
|
133
|
+
polls++;
|
|
134
|
+
try {
|
|
135
|
+
reading = await io.read();
|
|
136
|
+
} catch (err) {
|
|
137
|
+
readFailures++;
|
|
138
|
+
if (classifyTmuxError(err).kind !== "unknown") {
|
|
139
|
+
resolved = "session-gone";
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (numeric(reading.exitStatus)) {
|
|
145
|
+
resolved = "status";
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
if (signalled(reading.signal)) {
|
|
149
|
+
resolved = "signal";
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (Date.now() >= nextCaptureAt) {
|
|
153
|
+
nextCaptureAt = Date.now() + captureEveryMs;
|
|
154
|
+
try {
|
|
155
|
+
pane = await io.capture();
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
if (/^Pane is dead \(/m.test(pane)) {
|
|
159
|
+
resolved = "banner";
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
pane = await io.capture();
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
exitStatus: reading.exitStatus,
|
|
170
|
+
pane,
|
|
171
|
+
resolved,
|
|
172
|
+
waitedMs: Date.now() - started,
|
|
173
|
+
polls,
|
|
174
|
+
readFailures
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function describeClaudeExit(exitStatus, pane, wait) {
|
|
115
178
|
const finalBanner = [...pane.matchAll(/^Pane is dead \(.*$/gm)].at(-1)?.[0] ?? "";
|
|
116
179
|
const bannerStatus = /^Pane is dead \(status (\d+)/.exec(finalBanner)?.[1] ?? "";
|
|
117
180
|
if (!/^\d+$/.test(exitStatus.trim()) && bannerStatus) exitStatus = bannerStatus;
|
|
118
181
|
const lines = pane.split("\n").map((l) => l.trimEnd()).filter((l) => l.length > 0 && !/^Pane is dead\b/.test(l));
|
|
119
182
|
let output = lines.slice(-15).join("\n");
|
|
120
183
|
if (output.length > 800) output = `\u2026${output.slice(-800)}`;
|
|
121
|
-
const
|
|
184
|
+
const known = /^\d+$/.test(exitStatus.trim());
|
|
185
|
+
let unresolved = "";
|
|
186
|
+
if (wait?.resolved === "timeout") {
|
|
187
|
+
unresolved = ` \u2014 tmux had not published one after ${wait.waitedMs}ms (${wait.polls} polls, ${wait.readFailures} read failure(s), ${finalBanner ? "banner present" : "no dead-pane banner"}${wait.tmuxVersion ? `; ${wait.tmuxVersion}` : ""})`;
|
|
188
|
+
} else if (wait?.resolved === "session-gone") {
|
|
189
|
+
unresolved = ` \u2014 the tmux session disappeared after ${wait.waitedMs}ms (${wait.polls} polls) before tmux published one`;
|
|
190
|
+
}
|
|
191
|
+
const status = known ? `exit status ${exitStatus.trim()}` : `exit status unknown${unresolved}`;
|
|
122
192
|
return `Claude Code exited on the host (${status}) before login finished. ` + (output ? `Its last output:
|
|
123
193
|
${output}
|
|
124
194
|
` : "It printed nothing. ") + "Run `claude` on the host as the manager's user to reproduce, then Start over.";
|
|
125
195
|
}
|
|
126
|
-
async function checkPairPaneAlive(session) {
|
|
196
|
+
async function checkPairPaneAlive(session, opts = {}) {
|
|
127
197
|
const read = async () => {
|
|
128
198
|
const { stdout } = await execFileAsync("tmux", [
|
|
129
199
|
"display-message",
|
|
130
200
|
"-p",
|
|
131
201
|
"-t",
|
|
132
202
|
session,
|
|
133
|
-
|
|
203
|
+
// `|`-separated, not space-separated: an unset format expands to the
|
|
204
|
+
// empty string, so splitting three fields on a space would shift them
|
|
205
|
+
// left and read the signal as the status.
|
|
206
|
+
"#{pane_dead}|#{pane_dead_status}|#{pane_dead_signal}"
|
|
134
207
|
]);
|
|
135
|
-
const [dead = "",
|
|
136
|
-
return { dead, exitStatus
|
|
208
|
+
const [dead = "", exitStatus = "", signal = ""] = stdout.trim().split("|");
|
|
209
|
+
return { dead, exitStatus, signal };
|
|
137
210
|
};
|
|
138
|
-
let
|
|
211
|
+
let initial;
|
|
139
212
|
try {
|
|
140
|
-
|
|
213
|
+
initial = await read();
|
|
141
214
|
} catch (err) {
|
|
142
215
|
return classifyTmuxError(err);
|
|
143
216
|
}
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
217
|
+
if (initial.dead !== "1") return null;
|
|
218
|
+
const verdict = await awaitDeadPaneVerdict(
|
|
219
|
+
{ read, capture: () => capturePane(session, { scrollback: -50 }) },
|
|
220
|
+
initial,
|
|
221
|
+
opts
|
|
222
|
+
);
|
|
223
|
+
if (verdict.resolved === "timeout") {
|
|
147
224
|
try {
|
|
148
|
-
|
|
225
|
+
const { stdout } = await execFileAsync("tmux", ["-V"]);
|
|
226
|
+
verdict.tmuxVersion = stdout.trim();
|
|
149
227
|
} catch {
|
|
150
|
-
break;
|
|
151
228
|
}
|
|
152
229
|
}
|
|
153
|
-
|
|
154
|
-
let pane = "";
|
|
155
|
-
try {
|
|
156
|
-
pane = await capturePane(session, { scrollback: -50 });
|
|
157
|
-
} catch {
|
|
158
|
-
}
|
|
159
|
-
return { kind: "claude-exited", message: describeClaudeExit(exitStatus, pane) };
|
|
230
|
+
return { kind: "claude-exited", message: describeClaudeExit(verdict.exitStatus, verdict.pane, verdict) };
|
|
160
231
|
}
|
|
232
|
+
var PAIR_REMAIN_ON_EXIT_FORMAT = "Pane is dead (#{?#{!=:#{pane_dead_status},},status #{pane_dead_status},signal #{pane_dead_signal}})";
|
|
161
233
|
async function spawnPairSession(session) {
|
|
162
234
|
try {
|
|
163
235
|
await execFileAsync("tmux", ["has-session", "-t", session]);
|
|
164
236
|
return { ok: true };
|
|
165
237
|
} catch {
|
|
166
238
|
}
|
|
167
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
239
|
+
const { resolveClaudeBinary } = await import("./persistent-session-22PIKH3S.js");
|
|
168
240
|
const claudeBin = resolveClaudeBinary();
|
|
169
241
|
const pairEnv = {
|
|
170
242
|
...process.env,
|
|
@@ -197,6 +269,14 @@ async function spawnPairSession(session) {
|
|
|
197
269
|
} catch (err) {
|
|
198
270
|
return { ok: false, error: classifyTmuxError(err) };
|
|
199
271
|
}
|
|
272
|
+
try {
|
|
273
|
+
await execFileAsync(
|
|
274
|
+
"tmux",
|
|
275
|
+
["set-option", "-w", "-t", session, "remain-on-exit-format", PAIR_REMAIN_ON_EXIT_FORMAT],
|
|
276
|
+
{ env: pairEnv }
|
|
277
|
+
);
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
200
280
|
await sleep(500);
|
|
201
281
|
const dead = await checkPairPaneAlive(session);
|
|
202
282
|
if (dead?.kind === "claude-exited") return { ok: false, error: dead };
|
|
@@ -464,6 +544,9 @@ async function getClaudePairStatus(session) {
|
|
|
464
544
|
return { kind: "idle" };
|
|
465
545
|
}
|
|
466
546
|
export {
|
|
547
|
+
DEAD_PANE_STATUS_WAIT_MS,
|
|
548
|
+
PAIR_REMAIN_ON_EXIT_FORMAT,
|
|
549
|
+
awaitDeadPaneVerdict,
|
|
467
550
|
describeClaudeExit,
|
|
468
551
|
finalizeClaudePairOnboarding,
|
|
469
552
|
getClaudePairStatus,
|
|
@@ -475,4 +558,4 @@ export {
|
|
|
475
558
|
startClaudePair,
|
|
476
559
|
submitClaudePairCode
|
|
477
560
|
};
|
|
478
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
561
|
+
//# sourceMappingURL=claude-pair-runtime-6DGCWXRM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/claude-pair-runtime.ts","../src/lib/claude-pair-parser.ts"],"sourcesContent":["/**\n * ENG-4580: manager-side runtime for the Claude Code OAuth pairing flow.\n *\n * These functions own the actual tmux dance — sending `/login`,\n * polling the pane until Claude Code prints the OAuth URL, sending\n * the auth code, and detecting success/failure. They are the\n * counterpart to the pure parser in `claude-pair-parser.ts`.\n *\n * The API surface in ENG-4581 will wrap these — they don't include\n * any HTTP / DB code so the unit tests can target the parser layer\n * without spinning up a fake API. Errors are classified into the\n * `SessionError` shape so the API can translate them into structured\n * 4xx responses (e.g. `session_missing`).\n *\n * Architectural note: tmux capture-pane on a session that doesn't\n * exist exits non-zero with `can't find session`. Same goes for tmux\n * not being installed. classifyTmuxError covers both.\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { existsSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir, userInfo } from 'node:os';\n\nimport {\n classifyTmuxError,\n detectAuthOutcome,\n extractOAuthUrl,\n isUrlPromptReady,\n type AuthOutcome,\n type SessionError,\n} from './claude-pair-parser.js';\nimport { consumerTermsOffKeys, selectRowKeys } from './claude-dialogs.js';\n\nconst execFileAsync = promisify(execFile);\n\n// Pair-scoped tmux session name. The pairing flow runs inside a\n// throwaway `claude` instance — never inside an agent's persistent\n// session — so first-time auth on a fresh host works (no chicken-and-\n// egg) and re-auth doesn't disrupt an in-flight agent conversation.\nexport function pairTmuxSession(pairId: string): string {\n return `agt-pair-${pairId.slice(0, 12)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Low-level helpers\n// ---------------------------------------------------------------------------\n\ninterface CapturePaneOpts {\n /** How many lines of scrollback to include (negative = lines back). Default -200. */\n scrollback?: number;\n}\n\nasync function capturePane(session: string, opts: CapturePaneOpts = {}): Promise<string> {\n const scrollback = opts.scrollback ?? -200;\n const { stdout } = await execFileAsync('tmux', [\n 'capture-pane',\n '-t',\n session,\n '-p',\n '-S',\n String(scrollback),\n ]);\n return stdout;\n}\n\nasync function sendKeys(session: string, ...keys: string[]): Promise<void> {\n await execFileAsync('tmux', ['send-keys', '-t', session, ...keys]);\n}\n\nasync function sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * ENG-10619: answer Claude Code's consumer-terms dialog with\n * \"Help improve our AI models: OFF\" if it is focused. Returns true when\n * keys were sent. One send-keys call per key with a beat between them —\n * batching arrow keys into one call risks a bracketed paste (see\n * sendDialogKeys in claude-dialogs.ts).\n */\nasync function answerConsumerTermsDialog(session: string, pane: string): Promise<boolean> {\n const keys = consumerTermsOffKeys(pane);\n if (!keys) return false;\n await sendKeySequence(session, keys);\n return true;\n}\n\nasync function sendKeySequence(session: string, keys: readonly string[]): Promise<void> {\n for (let i = 0; i < keys.length; i++) {\n if (i > 0) await sleep(300);\n await sendKeys(session, keys[i]!);\n }\n}\n\n/**\n * ENG-10653: how long to wait, after a pane goes dead, for tmux to publish its\n * exit status. A wall-clock deadline, deliberately not an iteration count —\n * see the bound's derivation below for why that distinction is not cosmetic.\n *\n * MEASURED (`scripts/measure-tmux-dead-status.mjs`, macOS 15, tmux 3.7b,\n * 2026-09-18, across three load regimes — unloaded, 12-way CPU saturation and\n * a fork storm): **not once** did a poll see the `Pane is dead (...)` banner\n * drawn while `pane_dead_status` was still empty.\n *\n * That is stated as a count of a single-instant observation, deliberately, and\n * not as a timing gap. An earlier draft of this comment claimed \"gap 0 ms at\n * p100\"; that was an artefact of polling — two values read in the same poll\n * bound the gap by one poll width (~9–15 ms here), they do not measure it as\n * zero — and the companion claim that the banner \"followed by 3–24 ms\" was\n * just the cost of the second tmux call in the same iteration. Both are gone.\n *\n * Two consequences, and the second is why ENG-10631 did not close this:\n *\n * 1. The banner is not an independent source of the status. tmux draws it\n * only once it has reaped the child, so the state ENG-10631's fallback\n * exists to exploit — banner present, status absent — is one this tmux\n * never actually entered. The fallback is still right and stays (it\n * recovers a status the format read itself lost), but it could not win the\n * reap race it was written for, which is why ENG-10643's queue run still\n * read \"exit status unknown\" with all three of its fixes in the tree.\n * 2. The banner IS a completion marker. Once drawn, tmux has published\n * everything it is going to, so it ends the wait as definitively as a\n * numeric status does.\n *\n * The BOUND is therefore not derived from that measurement — a 0 ms gap would\n * justify no wait at all. It is derived from the CI tail, the only place the\n * gap has ever opened. ENG-10631 measured >1 s. The ENG-10643 ejection (run\n * 35223527037, job `tests (packages)`) then exhausted the 30 × 100 ms loop\n * that replaced it: the test took 4624 ms, which decomposes to the 500 ms\n * spawn settle + 3000 ms of sleeps + ~33 tmux client spawns at ~35 ms each.\n * That is the point about wall clock — the loop's nominal \"3 s\" was really\n * ~4.1 s, because each iteration also paid to spawn a tmux client, and the\n * number in the code did not say so.\n *\n * 15 s is ~3.7× the bound that was observed losing. It is affordable because\n * this wait is on a TERMINAL path — the pane is dead, the pair flow is over —\n * and it sits inside a single poll of callers budgeting 60 s\n * (`startClaudePair`) and 30 s (`submitClaudePairCode`). An alive pane never\n * reaches it.\n */\nexport const DEAD_PANE_STATUS_WAIT_MS = 15_000;\n\n/** One `#{pane_dead}|#{pane_dead_status}|#{pane_dead_signal}` reading. */\nexport interface DeadPaneReading {\n dead: string;\n exitStatus: string;\n /** Empty string on tmux builds with no `pane_dead_signal` format. */\n signal: string;\n}\n\nexport interface DeadPaneWaitOpts {\n waitMs?: number;\n pollMs?: number;\n /** How often to spend a capture-pane looking for the completion banner. */\n captureEveryMs?: number;\n}\n\nexport interface DeadPaneVerdict {\n exitStatus: string;\n pane: string;\n /**\n * How the wait ended. Four of these are answers; only `timeout` means tmux\n * never told us. `signal` is a genuinely status-less death, not a missing\n * status — the distinction the operator message depends on.\n */\n resolved: 'status' | 'signal' | 'banner' | 'session-gone' | 'timeout';\n waitedMs: number;\n polls: number;\n readFailures: number;\n /** Filled in only on `timeout`, where it is worth knowing. */\n tmuxVersion?: string;\n}\n\n/**\n * ENG-10653: wait for tmux to publish a dead pane's exit status, and report\n * how the wait ended.\n *\n * tmux marks a pane dead when the pty hits EOF but only fills in\n * `pane_dead_status` once it has reaped the child — separate events, so a read\n * in between sees a dead pane with an EMPTY status. Ending the wait on the\n * first of those and reporting \"unknown\" is the defect this closes.\n *\n * The I/O is injected so the wait can be proven against a fake that publishes\n * late or fails transiently. It cannot be proven against real tmux on a\n * developer machine: the gap there is 0 ms (see `DEAD_PANE_STATUS_WAIT_MS`),\n * so the race simply does not occur.\n */\nexport async function awaitDeadPaneVerdict(\n io: { read: () => Promise<DeadPaneReading>; capture: () => Promise<string> },\n initial: DeadPaneReading,\n opts: DeadPaneWaitOpts = {},\n): Promise<DeadPaneVerdict> {\n const waitMs = opts.waitMs ?? DEAD_PANE_STATUS_WAIT_MS;\n const pollMs = opts.pollMs ?? 100;\n const captureEveryMs = opts.captureEveryMs ?? 1_000;\n\n const numeric = (v: string): boolean => /^\\d+$/.test(v.trim());\n // NOT `numeric`: tmux reports the signal by NAME, not number. Measured on\n // tmux 3.7b, `#{pane_dead_signal}` for a SIGTERM death is the string `term`\n // — so a numeric test never matches, and a signal death would sit out the\n // whole bound before reporting the unknown it already knew about. Any\n // non-empty value is the answer; builds without the format expand it to ''.\n //\n // The `#{` rejection is the fail-safe direction. A build that echoed the\n // format back unexpanded would otherwise make EVERY dead pane resolve as a\n // signal on the first read — skipping the status wait entirely and turning\n // this fix into a faster version of the bug it closes.\n const signalled = (v: string): boolean => v.trim().length > 0 && !v.includes('#{');\n const started = Date.now();\n const deadline = started + waitMs;\n\n let reading = initial;\n let pane = '';\n let polls = 0;\n let readFailures = 0;\n let nextCaptureAt = started + captureEveryMs;\n let resolved: DeadPaneVerdict['resolved'] = 'timeout';\n if (numeric(reading.exitStatus)) resolved = 'status';\n else if (signalled(reading.signal)) resolved = 'signal';\n\n while (resolved === 'timeout' && Date.now() < deadline) {\n await sleep(pollMs);\n polls++;\n try {\n reading = await io.read();\n } catch (err) {\n readFailures++;\n // A transient tmux client spawn failure under load must NOT collapse the\n // wait. The previous `catch { break }` here turned one such failure into\n // an immediate \"exit status unknown\", which is the same wrong answer\n // this whole wait exists to avoid — and it left no trace of having\n // happened. Only a session that has genuinely gone away ends the wait,\n // and even then the pane was already dead, so the verdict is still\n // `claude-exited` rather than a bare `no-session`.\n if (classifyTmuxError(err).kind !== 'unknown') { resolved = 'session-gone'; break; }\n continue;\n }\n if (numeric(reading.exitStatus)) { resolved = 'status'; break; }\n if (signalled(reading.signal)) { resolved = 'signal'; break; }\n if (Date.now() >= nextCaptureAt) {\n nextCaptureAt = Date.now() + captureEveryMs;\n try { pane = await io.capture(); } catch { /* best-effort */ }\n // Costs one capture per second, and covers the tmux builds that publish\n // no `pane_dead_signal`: on those, a signal death would otherwise sit\n // here for the full bound before admitting it has no status.\n if (/^Pane is dead \\(/m.test(pane)) { resolved = 'banner'; break; }\n }\n }\n\n // Capture last, so the pane read is the freshest one available — on `status`\n // the banner is 3–24 ms behind and may only now have landed. Attempted even\n // on `session-gone`: a `display-message` that classified as no-session does\n // not prove `capture-pane` fails in the same instant, and the pane content\n // is the last thing recoverable about why claude exited.\n try { pane = await io.capture(); } catch { /* best-effort */ }\n return {\n exitStatus: reading.exitStatus,\n pane,\n resolved,\n waitedMs: Date.now() - started,\n polls,\n readFailures,\n };\n}\n\n/**\n * ENG-10617: turn a dead pair pane into an operator-readable message.\n *\n * Pair sessions run with `remain-on-exit on`, so when claude exits the\n * pane stays behind holding its final screen plus tmux's own\n * `Pane is dead (status N, <date>)` banner. That screen is the only record\n * of why claude exited — without it the operator got a raw\n * `can't find pane` and nothing to act on.\n *\n * Exported for unit testing.\n */\nexport function describeClaudeExit(exitStatus: string, pane: string, wait?: DeadPaneVerdict): string {\n // ENG-10631: `pane_dead_status` can still be empty after the reap wait under\n // heavy load (seen in CI with a 1s wait), but tmux's own banner in the pane\n // carries the same number. Use it rather than report \"exit status unknown\".\n // The LAST banner: claude's own output above it could contain banner-shaped\n // text, and tmux always writes the real one after everything claude printed.\n // Take the final banner of ANY kind first (a signal death writes\n // `Pane is dead (signal N, ...)`), and only then read a status out of it.\n const finalBanner = [...pane.matchAll(/^Pane is dead \\(.*$/gm)].at(-1)?.[0] ?? '';\n const bannerStatus = /^Pane is dead \\(status (\\d+)/.exec(finalBanner)?.[1] ?? '';\n if (!/^\\d+$/.test(exitStatus.trim()) && bannerStatus) exitStatus = bannerStatus;\n const lines = pane\n .split('\\n')\n .map((l) => l.trimEnd())\n .filter((l) => l.length > 0 && !/^Pane is dead\\b/.test(l));\n let output = lines.slice(-15).join('\\n');\n if (output.length > 800) output = `…${output.slice(-800)}`;\n const known = /^\\d+$/.test(exitStatus.trim());\n // ENG-10653: \"we waited and tmux never said\" and \"tmux said there is no exit\n // status\" are different facts, and only the first is a defect. A signal death\n // is a real status-less exit and keeps the plain wording; a wait that ran out\n // says so, and carries what it saw — otherwise the next occurrence of this\n // flake is as undiagnosable as the one that opened this issue.\n let unresolved = '';\n if (wait?.resolved === 'timeout') {\n unresolved = ` — tmux had not published one after ${wait.waitedMs}ms (${wait.polls} polls, ` +\n `${wait.readFailures} read failure(s), ` +\n `${finalBanner ? 'banner present' : 'no dead-pane banner'}` +\n `${wait.tmuxVersion ? `; ${wait.tmuxVersion}` : ''})`;\n } else if (wait?.resolved === 'session-gone') {\n // Also unanswered, just for a different reason, and it needs saying for\n // the same one: without it this reads as a plain status-less exit, which\n // is the bare-diff shape this whole change exists to remove.\n unresolved = ` — the tmux session disappeared after ${wait.waitedMs}ms ` +\n `(${wait.polls} polls) before tmux published one`;\n }\n const status = known ? `exit status ${exitStatus.trim()}` : `exit status unknown${unresolved}`;\n return (\n `Claude Code exited on the host (${status}) before login finished. ` +\n (output ? `Its last output:\\n${output}\\n` : 'It printed nothing. ') +\n 'Run `claude` on the host as the manager\\'s user to reproduce, then Start over.'\n );\n}\n\n/**\n * ENG-10617: null while claude is still running in the pair pane; a\n * `claude-exited` error (with its last output) once it has exited; or the\n * classified tmux error if the session itself is gone.\n */\nasync function checkPairPaneAlive(session: string, opts: DeadPaneWaitOpts = {}): Promise<SessionError | null> {\n const read = async (): Promise<DeadPaneReading> => {\n const { stdout } = await execFileAsync('tmux', [\n 'display-message',\n '-p',\n '-t',\n session,\n // `|`-separated, not space-separated: an unset format expands to the\n // empty string, so splitting three fields on a space would shift them\n // left and read the signal as the status.\n '#{pane_dead}|#{pane_dead_status}|#{pane_dead_signal}',\n ]);\n const [dead = '', exitStatus = '', signal = ''] = stdout.trim().split('|');\n return { dead, exitStatus, signal };\n };\n let initial: DeadPaneReading;\n try {\n initial = await read();\n } catch (err) {\n return classifyTmuxError(err);\n }\n // Fast path: an alive pane costs exactly one tmux call and never captures.\n if (initial.dead !== '1') return null;\n const verdict = await awaitDeadPaneVerdict(\n { read, capture: () => capturePane(session, { scrollback: -50 }) },\n initial,\n opts,\n );\n if (verdict.resolved === 'timeout') {\n try {\n const { stdout } = await execFileAsync('tmux', ['-V']);\n verdict.tmuxVersion = stdout.trim();\n } catch { /* best-effort: the version is a diagnostic, not a requirement */ }\n }\n return { kind: 'claude-exited', message: describeClaudeExit(verdict.exitStatus, verdict.pane, verdict) };\n}\n\n// ---------------------------------------------------------------------------\n// Pair-scoped tmux session lifecycle\n// ---------------------------------------------------------------------------\n\n/**\n * ENG-10653: the dead-pane banner, pinned rather than inherited.\n *\n * The banner text is an OPTION (`remain-on-exit-format`, tmux 3.3+), not a\n * constant, and a pair session is created on whatever tmux server the host\n * already runs — so it inherits the host user's `~/.tmux.conf`. A host that\n * sets its own format silently disables BOTH readers of that banner:\n * ENG-10631's status fallback and ENG-10653's completion marker. Verified:\n * with `remain-on-exit-format \"child gone: #{pane_dead_status}\"` the pane\n * holds `child gone: 3` and `/^Pane is dead \\(/` matches nothing, while the\n * status sits in plain view. No error, no log — the shape this whole issue is\n * about, one level down.\n *\n * So the pair session states the format it intends to parse.\n *\n * The `#{!=:...,}` guard is not decoration, and tmux's own default carries it\n * for the same reason: `#{?pane_dead_status,...}` would test TRUTHINESS, and\n * tmux reads `0` as false — so a clean `exit 0` would render as a signal\n * death. Comparing against the empty string asks the question actually meant,\n * \"did tmux publish a status\".\n */\nexport const PAIR_REMAIN_ON_EXIT_FORMAT =\n 'Pane is dead (#{?#{!=:#{pane_dead_status},},status #{pane_dead_status},signal #{pane_dead_signal}})';\n\n/**\n * Spawn a throwaway tmux session running `claude` for the pairing flow.\n * Idempotent — if the session already exists, returns success silently.\n * Caller is responsible for `killPairSession` once the pair reaches a\n * terminal state.\n *\n * Uses an absolute path to the claude binary so we don't depend on the\n * inherited PATH (the manager runs with cloud-init's minimal env on\n * EC2). resolveClaudeBinary checks CLAUDE_PATH, then `which`, then\n * canonical Linux/macOS Homebrew install dirs.\n */\nexport async function spawnPairSession(session: string): Promise<{ ok: true } | { ok: false; error: SessionError }> {\n try {\n await execFileAsync('tmux', ['has-session', '-t', session]);\n return { ok: true };\n } catch {\n // session doesn't exist yet — fall through to create it\n }\n\n const { resolveClaudeBinary } = await import('./persistent-session.js');\n const claudeBin = resolveClaudeBinary();\n\n // ENG-5070: backfill HOME/USER before spawning the pair tmux.\n // persistent-session.ts already does this for agent sessions\n // (ENG-4632) but the pair flow re-used execFileAsync's inherited\n // env, so the same SSM-launched-manager-strips-HOME trap fired the\n // moment an operator clicked \"Login via browser\" on a fresh host:\n // tmux inherits HOME='', the new claude process can't read or\n // write ~/.claude/, claude exits within milliseconds, and the\n // pairing UI surfaces the generic \"claude exited immediately\"\n // message. Treat empty-string as missing too — HOME=\"\" makes ~\n // resolve to cwd (whatever happens to be the manager's working\n // dir), which is the same broken outcome as no HOME, just better\n // hidden.\n const pairEnv: NodeJS.ProcessEnv = {\n ...process.env,\n HOME: (process.env.HOME?.trim()) || homedir(),\n USER: (process.env.USER?.trim()) || userInfo().username,\n };\n\n try {\n // -x 240 -y 50 — wide enough that Claude Code's OAuth URL (~350\n // chars after PKCE expansion) doesn't soft-wrap. The dewrap pass in\n // extractOAuthUrl handles any leftover wrapping, but giving the URL\n // a single line in the first place is more robust against future\n // claude UI changes.\n // ENG-10617: `remain-on-exit on` is chained into the SAME tmux\n // invocation (`;` is tmux's command separator) so it is applied before\n // the server can reap an early-exiting claude. Without it, claude\n // exiting destroys the session and its last output with it, and every\n // later tmux call fails with a bare `can't find pane`.\n await execFileAsync(\n 'tmux',\n [\n 'new-session',\n '-d',\n '-x',\n '240',\n '-y',\n '50',\n '-s',\n session,\n claudeBin,\n ';',\n 'set-option',\n '-w',\n '-t',\n session,\n 'remain-on-exit',\n 'on',\n ],\n { env: pairEnv },\n );\n } catch (err) {\n return { ok: false, error: classifyTmuxError(err) };\n }\n\n // ENG-10653: pin the banner, BEST-EFFORT AND DELIBERATELY NOT CHAINED.\n //\n // `remain-on-exit-format` is tmux 3.3+, and tmux exits 1 on an unknown\n // option while still creating the session — measured. Chained into the\n // new-session above, that would turn \"this host runs tmux 3.2\" into a failed\n // pair AND an orphaned session, trading a silent degradation for an outage.\n // Separate and swallowed, an old tmux simply keeps the host's banner, which\n // is exactly the behaviour it had before this change.\n //\n // The window this opens — claude dying between the two calls — resolves to\n // that same pre-existing behaviour, not to anything worse.\n try {\n await execFileAsync(\n 'tmux',\n ['set-option', '-w', '-t', session, 'remain-on-exit-format', PAIR_REMAIN_ON_EXIT_FORMAT],\n { env: pairEnv },\n );\n } catch { /* tmux < 3.3, or the option is gone: fall back to the host's banner */ }\n\n // `tmux new-session` returns 0 even if the command exited immediately\n // (binary missing, claude crashed, etc.) — the session is created and\n // then torn down. Without this verification we'd propagate a generic\n // `session_missing` to the operator on the next has-session call,\n // hiding the real failure. Sleep briefly to give claude a beat to\n // crash-or-stay-alive, then check.\n await sleep(500);\n const dead = await checkPairPaneAlive(session);\n if (dead?.kind === 'claude-exited') return { ok: false, error: dead };\n if (dead) {\n return {\n ok: false,\n error: {\n kind: 'unknown',\n message: `claude exited immediately after launch (binary at ${claudeBin}). Run \\`${claudeBin}\\` manually on the host to see why — likely missing TTY, missing HOME, or a startup error.`,\n },\n };\n }\n return { ok: true };\n}\n\n/**\n * ENG-4633: drive Claude Code through its post-OAuth dialogs so it\n * persists `~/.claude.json` (the device-state + onboarding-complete\n * file) before we tear down the pair tmux session.\n *\n * Without this step, the pair flow leaves the host with only\n * `~/.claude/.credentials.json` written. On the next agent launch,\n * Claude Code interactive mode sees no `~/.claude.json` and falls\n * back to the login picker — even though the OAuth tokens are\n * sitting right there. This was the root cause of the 2026-05-01\n * prod scout outage.\n *\n * After successful code submission, claude shows in sequence:\n * 1. \"Logged in as <email> / Login successful. Press Enter to continue…\"\n * 2. \"Security notes / Press Enter to continue…\"\n * 3. (depending on cwd) trust-folder prompt and/or bypass-permissions\n * warning. The pair tmux session has neither a project dir nor\n * `--dangerously-skip-permissions`, so we usually only see the\n * first two.\n *\n * For each iteration: capture the pane, match a known prompt, send\n * Enter, repeat. Bail when `~/.claude.json`'s mtime advances past\n * the snapshot we took at entry — proxy for \"claude has rewritten\n * the file with post-login state\". The actual contents (e.g.\n * `hasCompletedOnboarding: true`) are not parsed here; we trust\n * claude to write a coherent file once it gets the chance, and\n * mtime-only is enough to gate the success log line. Returns\n * `finalized` so the caller can warn if onboarding never flushed —\n * the pair is still considered successful (OAuth tokens are valid\n * regardless), the worst-case fallout is a one-time login picker\n * on the next agent launch.\n */\nexport async function finalizeClaudePairOnboarding(\n session: string,\n log: (msg: string) => void,\n opts: { maxIterations?: number; intervalMs?: number; claudeJsonPath?: string } = {},\n): Promise<{ finalized: boolean; iterations: number }> {\n const maxIterations = opts.maxIterations ?? 10;\n const intervalMs = opts.intervalMs ?? 1500;\n const claudeJsonPath = opts.claudeJsonPath ?? join(homedir(), '.claude.json');\n\n // Snapshot the file's existence + mtime so we can detect the moment\n // claude actually writes it. We can't simply check \"does the file\n // exist\" because a stale file from a prior pair attempt would\n // satisfy that on the first iteration.\n const initialMtime = existsSync(claudeJsonPath)\n ? statSync(claudeJsonPath).mtimeMs\n : 0;\n\n for (let i = 0; i < maxIterations; i++) {\n await sleep(intervalMs);\n const dead = await checkPairPaneAlive(session);\n if (dead) {\n log(`[claude-pair] finalize: pair session no longer running (${dead.kind}); aborting onboarding flow`);\n return { finalized: false, iterations: i };\n }\n let pane: string;\n try {\n pane = await capturePane(session);\n } catch (err) {\n // Session likely dead. Reading it back as failure here is fine —\n // the upstream caller already considered the pair successful, so\n // we report finalized=false and let it decide whether to fail\n // soft.\n const classified = classifyTmuxError(err);\n log(`[claude-pair] finalize: capture-pane failed (${classified.kind}); aborting onboarding flow`);\n return { finalized: false, iterations: i };\n }\n\n // ENG-10619: must run before the generic Enter branch below — a bare\n // Enter on this dialog confirms its default row, which opts the\n // account into model training.\n try {\n if (await answerConsumerTermsDialog(session, pane)) continue;\n } catch {\n log('[claude-pair] finalize: send-keys failed on consumer-terms dialog; aborting onboarding flow');\n return { finalized: false, iterations: i };\n }\n\n // Pattern-match the post-OAuth dialogs claude shows in succession.\n // Keep these matchers loose — claude's wording shifts between\n // versions, and a missed match just means we send Enter on the\n // generic \"Press Enter to continue\" branch below.\n if (\n pane.includes('Login successful') ||\n pane.includes('Logged in as') ||\n /Press Enter to continue/i.test(pane) ||\n pane.includes('Security notes')\n ) {\n try {\n await sendKeys(session, 'C-m');\n } catch {\n // sendKeys failure → session dead. Same fallthrough as above.\n log('[claude-pair] finalize: send-keys failed; aborting onboarding flow');\n return { finalized: false, iterations: i };\n }\n continue;\n }\n\n // Has claude rewritten ~/.claude.json since we entered this\n // function? mtime advance is our proxy for \"post-login state has\n // been flushed to disk\" — we don't crack the JSON open to verify\n // hasCompletedOnboarding because claude's exact write timing is\n // version-dependent and we'd be guessing about which key to look\n // at. Mtime-only is good enough for the smoke contract.\n if (existsSync(claudeJsonPath)) {\n const mtime = statSync(claudeJsonPath).mtimeMs;\n if (mtime > initialMtime) {\n log(`[claude-pair] finalize: ~/.claude.json updated (after ${i + 1} dialog dismissal(s))`);\n return { finalized: true, iterations: i + 1 };\n }\n }\n }\n\n log(`[claude-pair] finalize: reached ${maxIterations} iterations without ~/.claude.json being updated`);\n return { finalized: false, iterations: maxIterations };\n}\n\n/**\n * Returns true if the session is gone afterwards (either kill succeeded\n * or it was already missing). Returns false only if `kill-session`\n * failed for a non-missing-session reason — caller should treat this\n * as \"still tracked, will retry next poll\".\n */\nexport async function killPairSession(session: string): Promise<boolean> {\n try {\n await execFileAsync('tmux', ['kill-session', '-t', session]);\n return true;\n } catch (err) {\n // \"can't find session\" is success-equivalent — the desired end state\n // is \"this session does not exist\" and that's already true.\n if (classifyTmuxError(err).kind === 'no-session') return true;\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Result shapes\n// ---------------------------------------------------------------------------\n\nexport type ClaudePairStartResult =\n | { kind: 'url'; url: string }\n | { kind: 'timeout' }\n | { kind: 'error'; error: SessionError };\n\nexport type ClaudePairSubmitResult =\n | { kind: 'success'; rawMatch: string }\n | { kind: 'failure'; rawMatch: string }\n | { kind: 'timeout' }\n | { kind: 'error'; error: SessionError };\n\nexport type ClaudePairStatusResult =\n | { kind: 'idle' }\n | { kind: 'awaiting-code'; url: string }\n | { kind: 'success' }\n | { kind: 'failure'; rawMatch: string }\n | { kind: 'session-missing' }\n | { kind: 'error'; error: SessionError };\n\n/**\n * Detect Claude Code's workspace-trust dialog in a captured pane.\n *\n * ENG-5375: Claude Code 2.1.146 reworded the prompt from \"Do you trust\n * the files in this folder?\" to \"Quick safety check: Is this a project\n * you created or one you trust?\". Match on the option text instead of\n * the question — same shape as `acceptDialogs` in persistent-session.ts,\n * stable across Anthropic's question rewordings.\n *\n * Exported for unit testing.\n */\nexport function isTrustDialogVisible(pane: string): boolean {\n return /Yes,\\s+I\\s+trust\\s+this\\s+folder/i.test(pane);\n}\n\n/**\n * Detect Claude Code's regular interactive prompt in a captured pane.\n *\n * ENG-5377: Claude Code 2.1.146 replaced the older boxed `│ > ` cursor\n * with `❯ ` (U+276F) and the tip text is variable (\"Try \\\"fix lint\n * errors\\\"\" today — no \" to \"). Detect on the stable 2.1.146 markers:\n * the `❯` cursor and/or the `? for shortcuts` footer. Keep the legacy\n * shapes for back-compat with hosts that haven't picked up the newer\n * Claude Code binary yet.\n *\n * IMPORTANT: callers must check the dialog detectors (theme, trust,\n * login picker) BEFORE this, since `❯` also appears on those screens\n * as the selected-option indicator.\n *\n * Exported for unit testing.\n */\nexport function isInteractivePromptReady(pane: string): boolean {\n return (\n /❯/.test(pane) ||\n /\\?\\s+for\\s+shortcuts/i.test(pane) ||\n /[│|]\\s*>\\s/.test(pane) ||\n /^\\s*>\\s*$/m.test(pane)\n );\n}\n\n// ---------------------------------------------------------------------------\n// start — send `/login`, wait for the OAuth URL prompt\n// ---------------------------------------------------------------------------\n\nexport interface ClaudePairStartOpts {\n /** Pair-scoped tmux session name (see pairTmuxSession). */\n session: string;\n /** Total time to wait for Claude Code to print the URL prompt. Default 60s. */\n timeoutMs?: number;\n /** How often to re-capture and check the pane. Default 500ms. */\n pollIntervalMs?: number;\n}\n\nexport async function startClaudePair(opts: ClaudePairStartOpts): Promise<ClaudePairStartResult> {\n const { session } = opts;\n // Pair sessions cold-start `claude` from scratch — first-run on a fresh\n // host can take 10-30s before the prompt is interactive enough to\n // accept `/login`. Stay generous.\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const pollIntervalMs = opts.pollIntervalMs ?? 500;\n\n // Quick precheck — fail fast if the tmux session doesn't exist\n // rather than blasting `/login` into an unrelated pane.\n try {\n await execFileAsync('tmux', ['has-session', '-t', session]);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n\n // Drive claude through its first-run onboarding to reach a state\n // where the OAuth URL is on screen. Possible paths:\n //\n // • Fresh host: theme picker → \"Trust folder?\" → login method picker\n // (option 1 = Claude subscription, already highlighted) → OAuth URL.\n // No `/login` needed — option 1 IS the login.\n // • Already onboarded: regular prompt → we send `/login` ourselves →\n // login method picker → OAuth URL.\n //\n // Auto-advance every onboarding screen we recognize. Keep going until\n // we either see the URL prompt or hit the deadline.\n const onboardingDeadline = Date.now() + Math.min(45_000, timeoutMs);\n let lastDispatchAt = 0;\n let loginCommandSent = false;\n const dispatchEnter = async (): Promise<void> => {\n if (Date.now() - lastDispatchAt < 1_500) return;\n lastDispatchAt = Date.now();\n try { await sendKeys(session, 'C-m'); } catch { /* keep polling */ }\n };\n\n while (Date.now() < onboardingDeadline) {\n await sleep(pollIntervalMs);\n const dead = await checkPairPaneAlive(session);\n if (dead) return { kind: 'error', error: dead };\n let pane: string;\n try {\n // VIEWPORT ONLY (no scrollback). With scrollback included, the\n // theme picker / login picker text lingers in the buffer after\n // claude advances to the URL-paste prompt. The match would then\n // dispatch Enter on the empty paste field — submitting an empty\n // code and triggering \"OAuth error: Invalid code\". Visible-only\n // capture ensures we only react to what's actually on screen.\n pane = await capturePane(session, { scrollback: 0 });\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n\n // Already at the URL prompt — done driving onboarding, fall through\n // to the URL-extraction loop below.\n if (isUrlPromptReady(pane)) {\n const url = extractOAuthUrl(pane);\n if (url) return { kind: 'url', url };\n }\n\n // Stuck on \"OAuth error: Invalid code. Press Enter to retry\" — usually\n // because claude has cached partial state from a previous failed login\n // attempt on this host. Pressing Enter would just resubmit the bad\n // code and loop forever, so bail with an actionable error.\n //\n // Capture WITH scrollback for this check: claude's TUI sometimes\n // re-renders the splash/banner over the OAuth error so the viewport\n // alone misses it (we'd then time out instead of giving the operator\n // an actionable error). OAuth-retry is a sticky state — claude waits\n // for input — so checking recent scrollback is safe; a false positive\n // from stale scrollback would only fire if a previous attempt actually\n // failed, in which case bailing is the right call.\n let scrollPane = pane;\n try {\n scrollPane = await capturePane(session, { scrollback: -50 });\n } catch { /* fall back to viewport-only check */ }\n const hasOAuthInvalidCode = /OAuth error[\\s\\S]*Invalid code/i.test(scrollPane);\n const hasOAuthRetryPrompt = /OAuth error/i.test(scrollPane) && /Press Enter to retry/i.test(scrollPane);\n if (hasOAuthInvalidCode || hasOAuthRetryPrompt) {\n return {\n kind: 'error',\n error: {\n kind: 'oauth-retry-stuck',\n message:\n 'claude is stuck on a previous failed-login retry prompt. SSH to the host and clear ~/.claude/.credentials.json (and any *.json next to it), then retry pair-via-browser.',\n },\n };\n }\n\n // ENG-10619: the consumer-terms dialog renders a `❯` cursor, so without\n // this the interactive-prompt branch below types `/login` into it.\n // Throttled like dispatchEnter: a relative arrow sequence re-sent before\n // claude re-renders would overshoot the row.\n if (consumerTermsOffKeys(pane)) {\n if (Date.now() - lastDispatchAt >= 1_500) {\n lastDispatchAt = Date.now();\n try {\n await answerConsumerTermsDialog(session, pane);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n }\n continue;\n }\n\n // Login method picker — option 1 (Claude subscription) is already\n // highlighted, Enter selects it.\n if (/Select login method:/i.test(pane)) {\n await dispatchEnter();\n continue;\n }\n // First-run theme picker. Accept the highlighted default; operator\n // can change it later via /theme.\n if (/\\bDark mode\\b/.test(pane) && /\\bLight mode\\b/.test(pane)) {\n await dispatchEnter();\n continue;\n }\n // \"Trust this folder?\" — select \"Yes, I trust this folder\" BY NAME.\n // ENG-10619: this used to be a bare Enter on the assumption the default\n // row is Yes. Since Claude Code 2.1.250 the dialog is an arrow-select\n // list whose cursor defaults to \"No, exit\", so Enter refused trust and\n // claude exited 1 — every re-login on a host whose workspace was not yet\n // trusted failed (surfaced as \"can't find pane\" before ENG-10617, and as\n // the trust dialog in the exit output after). Same defect sweepDialogs()\n // fixed for agents in ENG-9575. An unresolvable row sends nothing.\n if (isTrustDialogVisible(pane)) {\n const keys = selectRowKeys(pane, 'Yes, I trust this folder');\n if (keys && Date.now() - lastDispatchAt >= 1_500) {\n lastDispatchAt = Date.now();\n try {\n await sendKeySequence(session, keys);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n }\n continue;\n }\n // Onboarding \"Press Enter to continue\" splashes.\n if (/press\\s+enter\\s+to\\s+continue/i.test(pane)) {\n await dispatchEnter();\n continue;\n }\n // Regular interactive prompt — this means claude was already\n // onboarded. Send `/login` once to surface the login picker, then\n // the next iteration handles it via the picker branch above.\n // ENG-5377: detect both the 2.1.146 `❯` cursor + `? for shortcuts`\n // footer AND the legacy boxed/bare prompt shapes. This branch must\n // come AFTER the dialog detectors above, since `❯` ALSO renders on\n // theme/trust/login dialogs.\n if (isInteractivePromptReady(pane) && !loginCommandSent) {\n loginCommandSent = true;\n try {\n await sendKeys(session, '/login', 'C-m');\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n continue;\n }\n }\n\n // Onboarding deadline hit without seeing a URL — surface the pane so\n // operators can see what was on screen when we gave up.\n let lastPane = '';\n try { lastPane = await capturePane(session); } catch { /* best-effort */ }\n return {\n kind: 'error',\n error: {\n kind: 'unknown',\n message: `claude never reached OAuth URL prompt within ${timeoutMs}ms. Last pane: ${lastPane.slice(-500)}`,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// submit-code — paste the auth code, wait for outcome\n// ---------------------------------------------------------------------------\n\nexport interface ClaudePairSubmitOpts {\n /** Pair-scoped tmux session name (see pairTmuxSession). */\n session: string;\n code: string;\n /** Total time to wait for the success/failure marker. Default 30s. */\n timeoutMs?: number;\n pollIntervalMs?: number;\n}\n\nexport async function submitClaudePairCode(\n opts: ClaudePairSubmitOpts,\n): Promise<ClaudePairSubmitResult> {\n const { session } = opts;\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const pollIntervalMs = opts.pollIntervalMs ?? 500;\n\n // Validate code shape minimally — Claude Code's auth codes are\n // alphanumeric with dashes, ~40-80 chars. Accept anything within\n // that envelope; reject blank or whitespace-only to avoid\n // accidentally submitting an empty buffer.\n if (!opts.code || !opts.code.trim()) {\n return {\n kind: 'error',\n error: { kind: 'unknown', message: 'empty auth code' },\n };\n }\n if (opts.code.length > 1024) {\n return {\n kind: 'error',\n error: { kind: 'unknown', message: 'auth code suspiciously long' },\n };\n }\n\n // Send the code + Enter. We use the literal value as one send-keys\n // argument; tmux handles spaces fine, but newlines would terminate\n // early so reject those as well.\n if (/[\\r\\n]/.test(opts.code)) {\n return {\n kind: 'error',\n error: { kind: 'unknown', message: 'auth code contains newline' },\n };\n }\n\n // Send the code as LITERAL text (-l) so tmux doesn't try to interpret\n // any chars as key tokens. Then a 250ms breather so claude's\n // ink/React render loop finishes processing the paste before Enter\n // lands. Without that wait the Enter often gets swallowed mid-render\n // and the code stays in the input box unsubmitted.\n try {\n await execFileAsync('tmux', ['send-keys', '-t', session, '-l', opts.code.trim()]);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n await sleep(250);\n try {\n // Use Enter (semantic) plus a fallback C-m on the next iteration if\n // claude still hasn't moved.\n await sendKeys(session, 'Enter');\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n\n const deadline = Date.now() + timeoutMs;\n let enterRetried = false;\n let lastPane = '';\n while (Date.now() < deadline) {\n await sleep(pollIntervalMs);\n try {\n lastPane = await capturePane(session);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n const outcome: AuthOutcome = detectAuthOutcome(lastPane);\n if (outcome.kind === 'success') return { kind: 'success', rawMatch: outcome.rawMatch };\n if (outcome.kind === 'failure') return { kind: 'failure', rawMatch: outcome.rawMatch };\n // ENG-10617: checked after the outcome so a success/failure marker\n // on claude's final screen still wins over \"it exited\".\n const dead = await checkPairPaneAlive(session);\n if (dead) return { kind: 'error', error: dead };\n\n // After ~5s with no outcome, retry Enter once — covers the case\n // where the first Enter landed mid-render and claude swallowed it.\n // The \"Paste code here\" prompt is still on screen if submission\n // didn't take; if it advanced, we'd already have an outcome above.\n if (!enterRetried && Date.now() - (deadline - timeoutMs) > 5_000 && /Paste code here/i.test(lastPane)) {\n enterRetried = true;\n try { await sendKeys(session, 'C-m'); } catch { /* keep polling */ }\n }\n }\n // Include pane snippet in the error path so operators can see what\n // claude was actually showing — outcome detection is brittle and the\n // session is killed immediately after this returns, so this is our\n // only chance to capture state for debugging regex updates.\n return {\n kind: 'error',\n error: {\n kind: 'unknown',\n message: `submit timed out after ${timeoutMs}ms — outcome regex didn't match. Last pane: ${lastPane.slice(-600)}`,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// status — non-mutating peek at the pane state\n// ---------------------------------------------------------------------------\n\nexport async function getClaudePairStatus(session: string): Promise<ClaudePairStatusResult> {\n const dead = await checkPairPaneAlive(session);\n if (dead?.kind === 'no-session') return { kind: 'session-missing' };\n if (dead) return { kind: 'error', error: dead };\n\n let pane: string;\n try {\n pane = await capturePane(session);\n } catch (err) {\n return { kind: 'error', error: classifyTmuxError(err) };\n }\n\n // Outcome takes priority — if the pane already shows success/failure\n // from a recent submission, the API can short-circuit without\n // restarting the flow.\n const outcome = detectAuthOutcome(pane);\n if (outcome.kind === 'success') return { kind: 'success' };\n if (outcome.kind === 'failure') return { kind: 'failure', rawMatch: outcome.rawMatch };\n\n if (isUrlPromptReady(pane)) {\n const url = extractOAuthUrl(pane);\n if (url) return { kind: 'awaiting-code', url };\n }\n return { kind: 'idle' };\n}\n","/**\n * ENG-4580: pane-scrape parser for Claude Code's `/login` OAuth flow.\n *\n * The manager drives the flow by sending `/login` into the agent's\n * persistent tmux session, capturing the pane after a short poll, and\n * extracting the OAuth URL Claude Code prints. After the operator\n * pastes the auth code via the UI, the manager sends it back into the\n * pane and polls for a success / failure marker.\n *\n * Everything in this module is pure — no tmux calls, no fs I/O. The\n * runtime side (apps/cli/src/lib/manager-worker.ts) shells out and\n * feeds the captured pane string through these functions.\n *\n * Why a dedicated module: pane scraping is fragile across Claude Code\n * versions, terminal widths, and locale changes. Centralising the\n * regexes + the ANSI stripper makes them easy to fixture-test and\n * iterate on without touching the runtime path.\n */\n\n// ---------------------------------------------------------------------------\n// ANSI escape sequence stripper\n// ---------------------------------------------------------------------------\n\n/**\n * Strip the ANSI escape sequences a terminal emits for colour, cursor\n * movement, screen clears, and bracketed paste mode. The pattern below\n * covers:\n *\n * - CSI sequences: `ESC [ ... <final byte>` where the final byte is\n * in the 0x40-0x7E range (covers SGR colour, cursor-position,\n * erase-in-line/display, etc.)\n * - OSC sequences: `ESC ] ... BEL` or `ESC ] ... ESC \\` (used for\n * window titles and hyperlinks)\n * - Single-character `ESC <char>` two-byte escapes (e.g. `ESC =`,\n * `ESC >`, the `ESC c` reset)\n *\n * We keep newlines and printable text intact so pane content remains\n * matchable after stripping.\n *\n * The regex uses Unicode-friendly character classes; we explicitly\n * avoid `\\x1b` named escapes in source to keep the file ASCII-safe.\n */\nconst ANSI_ESC = String.fromCharCode(0x1b);\nconst ANSI_BEL = String.fromCharCode(0x07);\n\nconst CSI_RE = new RegExp(`${ANSI_ESC}\\\\[[0-?]*[ -/]*[@-~]`, 'g');\nconst OSC_RE = new RegExp(\n `${ANSI_ESC}\\\\][^${ANSI_BEL}${ANSI_ESC}]*(?:${ANSI_BEL}|${ANSI_ESC}\\\\\\\\)`,\n 'g',\n);\nconst TWO_BYTE_RE = new RegExp(`${ANSI_ESC}[=>cM78]`, 'g');\n\nexport function stripAnsi(text: string): string {\n return text.replace(CSI_RE, '').replace(OSC_RE, '').replace(TWO_BYTE_RE, '');\n}\n\n// ---------------------------------------------------------------------------\n// OAuth URL extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Anchored to Anthropic-owned domains that Claude Code's `/login`\n * actually prints. Adding more hosts is fine — keep them allowlisted\n * rather than matching arbitrary `https://` to avoid pulling random\n * URLs from the user's previous shell output.\n *\n * Note: Claude Code currently emits URLs at `claude.com/cai/oauth/...`\n * (the consumer-facing domain), but `claude.ai` and the console hosts\n * have appeared historically and are kept here for tolerance.\n */\nconst OAUTH_URL_RE =\n /https:\\/\\/(?:claude\\.com|claude\\.ai|platform\\.claude\\.com|console\\.anthropic\\.com|auth\\.anthropic\\.com)\\/[^\\s)\\]]*/;\n\n/**\n * Strip ANSI + reassemble URLs that were soft-wrapped across terminal\n * lines. tmux capture-pane emits hard newlines for wrapped lines, and\n * Claude Code's OAuth URL routinely runs >300 chars — much wider than\n * the manager's default tmux window. Iteratively strip newlines that\n * fall inside what looks like a URL until convergence (a single URL\n * can wrap 5+ times).\n *\n * Shared between extractOAuthUrl and isUrlPromptReady so the readiness\n * check sees the same string the extractor would.\n */\nfunction dewrapPane(rawPane: string): string {\n const stripped = stripAnsi(rawPane);\n let dewrapped = stripped;\n let prev = '';\n while (prev !== dewrapped) {\n prev = dewrapped;\n dewrapped = dewrapped.replace(/(https?:\\/\\/\\S+?)\\n(?=\\S)/, (_m, head: string) => head);\n }\n return dewrapped;\n}\n\nexport function extractOAuthUrl(rawPane: string): string | null {\n const match = OAUTH_URL_RE.exec(dewrapPane(rawPane));\n if (!match) return null;\n // Trim trailing punctuation that often clings to URLs in TUIs.\n return match[0].replace(/[.,;:!?]+$/, '');\n}\n\n// ---------------------------------------------------------------------------\n// Prompt readiness — \"we've printed the URL, now waiting for a code\"\n// ---------------------------------------------------------------------------\n\nconst URL_PROMPT_RE =\n /(?:Paste code here|Paste your code|Enter (?:the )?code|Authorization code)/i;\n\nexport function isUrlPromptReady(rawPane: string): boolean {\n const dewrapped = dewrapPane(rawPane);\n // Both anchors must be present: the URL itself AND the paste-code\n // prompt. The prompt alone could appear during a stale screen redraw;\n // the URL alone could be a stray match in command history. Use the\n // dewrapped pane so a wrapped URL still matches OAUTH_URL_RE.\n return OAUTH_URL_RE.test(dewrapped) && URL_PROMPT_RE.test(dewrapped);\n}\n\n// ---------------------------------------------------------------------------\n// Outcome detection after submitting the code\n// ---------------------------------------------------------------------------\n\n// Claude Code's success/failure copy has drifted across versions\n// (\"Logged in\" → \"Login successful\" → \"Signed in as ...\" → etc.).\n// Keep the alternations broad-but-specific — phrases that only appear\n// after a real auth roundtrip, never in welcome / tutorial text.\n// \"Welcome back\" was tried and rejected: it shows up in onboarding\n// help blurbs and produced false positives.\nconst SUCCESS_RE =\n /(?:Logged in|Login successful|Successfully (?:logged in|authenticated|signed in)|Authentication successful|Sign-?in (?:complete|successful)|You(?:'|’)?re signed in|Signed in as)/i;\nconst FAILURE_RE =\n /(?:Invalid (?:code|authorization code)|OAuth error|Authentication failed|Error (?:logging in|during authentication)|Login failed|Sign-?in failed|Failed to (?:authenticate|sign in|log in))/i;\n\nexport type AuthOutcome =\n | { kind: 'success'; rawMatch: string }\n | { kind: 'failure'; rawMatch: string }\n | { kind: 'pending' };\n\nexport function detectAuthOutcome(rawPane: string): AuthOutcome {\n const stripped = stripAnsi(rawPane);\n // Failure first — Claude Code sometimes prints a stale \"logged in\" from\n // a previous successful session above the new failure banner. The\n // most-recent line wins, so we scan from the end of the pane.\n const failureMatch = lastMatch(stripped, FAILURE_RE);\n const successMatch = lastMatch(stripped, SUCCESS_RE);\n\n if (failureMatch && successMatch) {\n // Whichever is later on the pane is the live state.\n if (failureMatch.index > successMatch.index) {\n return { kind: 'failure', rawMatch: failureMatch.match };\n }\n return { kind: 'success', rawMatch: successMatch.match };\n }\n if (failureMatch) return { kind: 'failure', rawMatch: failureMatch.match };\n if (successMatch) return { kind: 'success', rawMatch: successMatch.match };\n return { kind: 'pending' };\n}\n\nfunction lastMatch(haystack: string, re: RegExp): { match: string; index: number } | null {\n // Construct a sticky/global variant if needed. Most of our REs are\n // anchored to small phrases; iterating with a `g`-flagged RegExp is\n // cheap and correct.\n const globalRe = new RegExp(re.source, re.flags.includes('g') ? re.flags : `${re.flags}g`);\n let last: RegExpExecArray | null = null;\n let m: RegExpExecArray | null;\n while ((m = globalRe.exec(haystack)) !== null) {\n last = m;\n // Prevent zero-length matches from looping.\n if (m.index === globalRe.lastIndex) globalRe.lastIndex++;\n }\n return last ? { match: last[0], index: last.index } : null;\n}\n\n// ---------------------------------------------------------------------------\n// \"Session not running / tmux missing\" — surface as a structured signal\n// ---------------------------------------------------------------------------\n\n/**\n * The runtime path will throw when `tmux capture-pane` fails. This\n * helper classifies the failure for the API layer so the UI can show\n * \"start a session first\" rather than a generic 500.\n */\nexport type SessionError =\n | { kind: 'no-session' }\n | { kind: 'tmux-missing' }\n | { kind: 'pane-empty' }\n /** Claude is sitting on the \"OAuth error: Invalid code. Press Enter to\n * retry.\" prompt from a previous failed login — pressing Enter would\n * resubmit the bad code, so the runtime bails with operator-actionable\n * guidance instead. Distinct from `unknown` so telemetry / UI can\n * surface a specific message rather than a generic 500. */\n | { kind: 'oauth-retry-stuck'; message: string }\n /** ENG-10617: the pair `claude` process exited mid-flow. The pair\n * session runs with `remain-on-exit on`, so the dead pane survives and\n * `message` carries claude's exit status and last output — the only\n * record of WHY it exited. */\n | { kind: 'claude-exited'; message: string }\n | { kind: 'unknown'; message: string };\n\nexport function classifyTmuxError(err: unknown): SessionError {\n const msg = err instanceof Error ? err.message : String(err);\n // ENG-10617: tmux 3.x phrases a `-t` miss by the target type it resolved\n // — `can't find pane` from capture-pane/send-keys, `can't find window` from\n // window-scoped commands — not only `can't find session`. Unmatched, those\n // fell to `unknown` and the raw command line reached the operator.\n if (/can't find (session|pane|window)|no server running/i.test(msg)) return { kind: 'no-session' };\n if (/command not found.*tmux|ENOENT.*tmux/i.test(msg)) return { kind: 'tmux-missing' };\n return { kind: 'unknown', message: msg };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
safeWriteJsonAtomic,
|
|
64
64
|
setConfigHash,
|
|
65
65
|
tripClass
|
|
66
|
-
} from "../chunk-
|
|
66
|
+
} from "../chunk-37AUMVY3.js";
|
|
67
67
|
import {
|
|
68
68
|
getProjectDir as getProjectDir2,
|
|
69
69
|
getReadyTasks,
|
|
@@ -140,7 +140,7 @@ import {
|
|
|
140
140
|
takeZombieDetection,
|
|
141
141
|
toOpencodeModel,
|
|
142
142
|
writeEgressAllowlist
|
|
143
|
-
} from "../chunk-
|
|
143
|
+
} from "../chunk-MNARTDPO.js";
|
|
144
144
|
import {
|
|
145
145
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
146
146
|
AnchorSessionClient,
|
|
@@ -236,7 +236,7 @@ import {
|
|
|
236
236
|
subagentActivityAgeSeconds,
|
|
237
237
|
sumTranscriptUsageInWindow,
|
|
238
238
|
transcriptActivityAgeSeconds
|
|
239
|
-
} from "../chunk-
|
|
239
|
+
} from "../chunk-D3FGOGI2.js";
|
|
240
240
|
import "../chunk-DHWNVVX4.js";
|
|
241
241
|
import {
|
|
242
242
|
reapOrphanChannelMcps
|
|
@@ -13262,7 +13262,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
|
|
|
13262
13262
|
var dayRolloverInboundHold = /* @__PURE__ */ new Map();
|
|
13263
13263
|
var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
|
|
13264
13264
|
async function channelInboundActivityAgeSecondsFor(codeName) {
|
|
13265
|
-
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-
|
|
13265
|
+
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-3HPANEK6.js");
|
|
13266
13266
|
const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
|
|
13267
13267
|
if (newest === null) return null;
|
|
13268
13268
|
return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
|
|
@@ -13947,7 +13947,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
13947
13947
|
var lastVersionCheckAt = 0;
|
|
13948
13948
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
13949
13949
|
var lastResponsivenessProbeAt = 0;
|
|
13950
|
-
var agtCliVersion = true ? "0.28.
|
|
13950
|
+
var agtCliVersion = true ? "0.28.962" : "dev";
|
|
13951
13951
|
function resolveBrewPath(execFileSync2) {
|
|
13952
13952
|
try {
|
|
13953
13953
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -15549,7 +15549,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
15549
15549
|
if (codeNames.length === 0) return;
|
|
15550
15550
|
void (async () => {
|
|
15551
15551
|
try {
|
|
15552
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15552
|
+
const { collectDiagnostics } = await import("../persistent-session-22PIKH3S.js");
|
|
15553
15553
|
await api.post("/host/heartbeat", {
|
|
15554
15554
|
host_id: hostId,
|
|
15555
15555
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -15691,7 +15691,7 @@ async function pollCycleInner() {
|
|
|
15691
15691
|
}
|
|
15692
15692
|
try {
|
|
15693
15693
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
15694
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15694
|
+
const { collectDiagnostics } = await import("../persistent-session-22PIKH3S.js");
|
|
15695
15695
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
15696
15696
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
|
|
15697
15697
|
let tailscaleHostname;
|
|
@@ -15867,7 +15867,7 @@ async function pollCycleInner() {
|
|
|
15867
15867
|
collectPanelessActivityProbes,
|
|
15868
15868
|
getResponsivenessIntervalMs,
|
|
15869
15869
|
occupancyQualificationClassifications
|
|
15870
|
-
} = await import("../responsiveness-probe-
|
|
15870
|
+
} = await import("../responsiveness-probe-3HPANEK6.js");
|
|
15871
15871
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
15872
15872
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
15873
15873
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -15984,7 +15984,7 @@ async function pollCycleInner() {
|
|
|
15984
15984
|
collectResponsivenessProbes,
|
|
15985
15985
|
livePendingInboundOldestAgeSeconds,
|
|
15986
15986
|
parkPendingInbound
|
|
15987
|
-
} = await import("../responsiveness-probe-
|
|
15987
|
+
} = await import("../responsiveness-probe-3HPANEK6.js");
|
|
15988
15988
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
|
|
15989
15989
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
15990
15990
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -16140,7 +16140,7 @@ async function pollCycleInner() {
|
|
|
16140
16140
|
}
|
|
16141
16141
|
try {
|
|
16142
16142
|
const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
|
|
16143
|
-
const { probeSessionAuth } = await import("../session-auth-dead-
|
|
16143
|
+
const { probeSessionAuth } = await import("../session-auth-dead-FAIJPJUK.js");
|
|
16144
16144
|
const observations = [];
|
|
16145
16145
|
const pendingCacheCommits = [];
|
|
16146
16146
|
const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
|
|
@@ -20416,7 +20416,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
20416
20416
|
try {
|
|
20417
20417
|
const freshId = rotateSessionForWedge(codeName);
|
|
20418
20418
|
consecutiveWedgeCycles.delete(codeName);
|
|
20419
|
-
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-
|
|
20419
|
+
const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-3HPANEK6.js");
|
|
20420
20420
|
const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
|
|
20421
20421
|
const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
|
|
20422
20422
|
const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
|
|
@@ -20459,7 +20459,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
20459
20459
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
20460
20460
|
void (async () => {
|
|
20461
20461
|
try {
|
|
20462
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
20462
|
+
const { collectDiagnostics } = await import("../persistent-session-22PIKH3S.js");
|
|
20463
20463
|
await api.post("/host/heartbeat", {
|
|
20464
20464
|
host_id: hostId,
|
|
20465
20465
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -20510,7 +20510,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
20510
20510
|
}
|
|
20511
20511
|
try {
|
|
20512
20512
|
const hostId = await getHostId();
|
|
20513
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
20513
|
+
const { collectDiagnostics } = await import("../persistent-session-22PIKH3S.js");
|
|
20514
20514
|
await api.post("/host/heartbeat", {
|
|
20515
20515
|
host_id: hostId,
|
|
20516
20516
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -21152,7 +21152,7 @@ async function processClaudePairSessions(agents) {
|
|
|
21152
21152
|
killPairSession,
|
|
21153
21153
|
pairTmuxSession,
|
|
21154
21154
|
finalizeClaudePairOnboarding
|
|
21155
|
-
} = await import("../claude-pair-runtime-
|
|
21155
|
+
} = await import("../claude-pair-runtime-6DGCWXRM.js");
|
|
21156
21156
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
21157
21157
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
21158
21158
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -34806,6 +34806,10 @@ var NOTICE_KIND_AUDIENCE = {
|
|
|
34806
34806
|
// HITL tool-call approvals: "Do NOT call this tool again, do NOT claim the
|
|
34807
34807
|
// action happened, and do NOT poll".
|
|
34808
34808
|
integration_invoke_resolved: "agent",
|
|
34809
|
+
// ADR-0104: the batch finished. Addressed to the agent, which reports the
|
|
34810
|
+
// counts onward; the approver is told separately, on the surface they
|
|
34811
|
+
// approved from.
|
|
34812
|
+
integration_batch_resolved: "agent",
|
|
34809
34813
|
integration_invoke_denied: "agent",
|
|
34810
34814
|
// Support/recruit approvals — same shape, agent resumes the provisioning.
|
|
34811
34815
|
recruit_resolved: "agent",
|
package/dist/mcp/index.js
CHANGED
|
@@ -27407,6 +27407,10 @@ var NOTICE_KIND_AUDIENCE = {
|
|
|
27407
27407
|
// HITL tool-call approvals: "Do NOT call this tool again, do NOT claim the
|
|
27408
27408
|
// action happened, and do NOT poll".
|
|
27409
27409
|
integration_invoke_resolved: "agent",
|
|
27410
|
+
// ADR-0104: the batch finished. Addressed to the agent, which reports the
|
|
27411
|
+
// counts onward; the approver is told separately, on the surface they
|
|
27412
|
+
// approved from.
|
|
27413
|
+
integration_batch_resolved: "agent",
|
|
27410
27414
|
integration_invoke_denied: "agent",
|
|
27411
27415
|
// Support/recruit approvals — same shape, agent resumes the provisioning.
|
|
27412
27416
|
recruit_resolved: "agent",
|
package/dist/mcp/origami.js
CHANGED
|
@@ -41558,6 +41558,10 @@ var NOTICE_KIND_AUDIENCE = {
|
|
|
41558
41558
|
// HITL tool-call approvals: "Do NOT call this tool again, do NOT claim the
|
|
41559
41559
|
// action happened, and do NOT poll".
|
|
41560
41560
|
integration_invoke_resolved: "agent",
|
|
41561
|
+
// ADR-0104: the batch finished. Addressed to the agent, which reports the
|
|
41562
|
+
// counts onward; the approver is told separately, on the surface they
|
|
41563
|
+
// approved from.
|
|
41564
|
+
integration_batch_resolved: "agent",
|
|
41561
41565
|
integration_invoke_denied: "agent",
|
|
41562
41566
|
// Support/recruit approvals — same shape, agent resumes the provisioning.
|
|
41563
41567
|
recruit_resolved: "agent",
|
|
@@ -35784,6 +35784,10 @@ var NOTICE_KIND_AUDIENCE = {
|
|
|
35784
35784
|
// HITL tool-call approvals: "Do NOT call this tool again, do NOT claim the
|
|
35785
35785
|
// action happened, and do NOT poll".
|
|
35786
35786
|
integration_invoke_resolved: "agent",
|
|
35787
|
+
// ADR-0104: the batch finished. Addressed to the agent, which reports the
|
|
35788
|
+
// counts onward; the approver is told separately, on the surface they
|
|
35789
|
+
// approved from.
|
|
35790
|
+
integration_batch_resolved: "agent",
|
|
35787
35791
|
integration_invoke_denied: "agent",
|
|
35788
35792
|
// Support/recruit approvals — same shape, agent resumes the provisioning.
|
|
35789
35793
|
recruit_resolved: "agent",
|
|
@@ -35796,6 +35796,10 @@ var NOTICE_KIND_AUDIENCE = {
|
|
|
35796
35796
|
// HITL tool-call approvals: "Do NOT call this tool again, do NOT claim the
|
|
35797
35797
|
// action happened, and do NOT poll".
|
|
35798
35798
|
integration_invoke_resolved: "agent",
|
|
35799
|
+
// ADR-0104: the batch finished. Addressed to the agent, which reports the
|
|
35800
|
+
// counts onward; the approver is told separately, on the surface they
|
|
35801
|
+
// approved from.
|
|
35802
|
+
integration_batch_resolved: "agent",
|
|
35799
35803
|
integration_invoke_denied: "agent",
|
|
35800
35804
|
// Support/recruit approvals — same shape, agent resumes the provisioning.
|
|
35801
35805
|
recruit_resolved: "agent",
|
|
@@ -62,8 +62,8 @@ import {
|
|
|
62
62
|
writeDirectChatSessionState,
|
|
63
63
|
writeEgressAllowlist,
|
|
64
64
|
writePersistentClaudeWrapper
|
|
65
|
-
} from "./chunk-
|
|
66
|
-
import "./chunk-
|
|
65
|
+
} from "./chunk-MNARTDPO.js";
|
|
66
|
+
import "./chunk-D3FGOGI2.js";
|
|
67
67
|
import "./chunk-DHWNVVX4.js";
|
|
68
68
|
import "./chunk-XWVM4KPK.js";
|
|
69
69
|
export {
|
|
@@ -131,4 +131,4 @@ export {
|
|
|
131
131
|
writeEgressAllowlist,
|
|
132
132
|
writePersistentClaudeWrapper
|
|
133
133
|
};
|
|
134
|
-
//# sourceMappingURL=persistent-session-
|
|
134
|
+
//# sourceMappingURL=persistent-session-22PIKH3S.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paneLogPath
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-MNARTDPO.js";
|
|
4
|
+
import "./chunk-D3FGOGI2.js";
|
|
5
5
|
import "./chunk-DHWNVVX4.js";
|
|
6
6
|
import "./chunk-XWVM4KPK.js";
|
|
7
7
|
|
|
@@ -800,4 +800,4 @@ export {
|
|
|
800
800
|
readAndResetSlackReplyBindingClassifications,
|
|
801
801
|
readAndResetSlackReplyTargetClassifications
|
|
802
802
|
};
|
|
803
|
-
//# sourceMappingURL=responsiveness-probe-
|
|
803
|
+
//# sourceMappingURL=responsiveness-probe-3HPANEK6.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
sessionTranscriptDir
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-D3FGOGI2.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/session-auth-dead.ts
|
|
6
6
|
import { closeSync, openSync, readSync, readdirSync, statSync } from "fs";
|
|
@@ -203,4 +203,4 @@ export {
|
|
|
203
203
|
decideSessionAuthState,
|
|
204
204
|
probeSessionAuth
|
|
205
205
|
};
|
|
206
|
-
//# sourceMappingURL=session-auth-dead-
|
|
206
|
+
//# sourceMappingURL=session-auth-dead-FAIJPJUK.js.map
|