claudish 12.0.1 → 12.1.1

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 (2) hide show
  1. package/dist/index.js +1649 -422
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -901,7 +901,7 @@ var init_onepassword_config = __esm(() => {
901
901
  });
902
902
 
903
903
  // src/version.ts
904
- var VERSION = "12.0.1";
904
+ var VERSION = "12.1.1";
905
905
 
906
906
  // src/logger.ts
907
907
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -45606,7 +45606,8 @@ var init_contract = __esm(() => {
45606
45606
  "cancel",
45607
45607
  "capture",
45608
45608
  "capture_since_seq",
45609
- "capture_spans"
45609
+ "capture_spans",
45610
+ "resize"
45610
45611
  ];
45611
45612
  SLOT_STATES = [
45612
45613
  "STARTING",
@@ -63874,6 +63875,20 @@ function resolveClaudishSpawn(env = process.env) {
63874
63875
  }
63875
63876
  var CLAUDISH_BIN_ENV = "CLAUDISH_BIN";
63876
63877
 
63878
+ // src/pane/pane-size.ts
63879
+ function clamp(n, lo, hi) {
63880
+ return Math.min(hi, Math.max(lo, Math.trunc(n)));
63881
+ }
63882
+ function clampPaneSize(cols, rows) {
63883
+ const c = clamp(cols, MIN_PANE_COLS, MAX_PANE_COLS);
63884
+ const r = clamp(rows, MIN_PANE_ROWS, MAX_PANE_ROWS);
63885
+ return { cols: c, rows: Math.min(r, Math.floor(MAX_PANE_CELLS / c)) };
63886
+ }
63887
+ function samePaneSize(a, b) {
63888
+ return !!a && !!b && a.cols === b.cols && a.rows === b.rows;
63889
+ }
63890
+ var DEFAULT_PANE_COLS = 160, DEFAULT_PANE_ROWS = 50, MIN_PANE_COLS = 80, MAX_PANE_COLS = 9999, MIN_PANE_ROWS = 24, MAX_PANE_ROWS = 9999, MAX_PANE_CELLS = 4000000, RESIZE_QUIET_MS = 2000;
63891
+
63877
63892
  // src/pane/process-identity.ts
63878
63893
  import { execFile, execFileSync as execFileSync2 } from "child_process";
63879
63894
  import { lstatSync } from "fs";
@@ -64217,12 +64232,6 @@ function mintPaneId(kind, label) {
64217
64232
  throw new Error(`minted an invalid pane id: ${id}`);
64218
64233
  return id;
64219
64234
  }
64220
- function sockPathFor(root, id) {
64221
- const p = sockPathOf(root, id);
64222
- if (Buffer.byteLength(p) >= 100)
64223
- throw new PaneRootError(`pane socket path ${p} is ${Buffer.byteLength(p)} bytes; it must be < 100`);
64224
- return p;
64225
- }
64226
64235
  function isStrippedFromChildEnv(key) {
64227
64236
  return STRIPPED_CHILD_VARS.includes(key) || key.startsWith("MAGMUX_") || TERMINAL_IDENTITY_VARS.includes(key) || TERMINAL_IDENTITY_PREFIXES.some((p) => key.startsWith(p)) || HOST_IDENTITY_VARS.includes(key);
64228
64237
  }
@@ -64233,8 +64242,8 @@ function buildPaneEnv(input) {
64233
64242
  continue;
64234
64243
  env[k] = v;
64235
64244
  }
64236
- env.COLUMNS = "160";
64237
- env.LINES = "50";
64245
+ env.COLUMNS = String(input.cols ?? DEFAULT_PANE_COLS);
64246
+ env.LINES = String(input.rows ?? DEFAULT_PANE_ROWS);
64238
64247
  if (input.parentEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined)
64239
64248
  env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
64240
64249
  Object.assign(env, input.slotEnv);
@@ -64360,24 +64369,46 @@ function versionAtLeast(v, min) {
64360
64369
  return true;
64361
64370
  }
64362
64371
  async function assertMagmuxAvailable(binary) {
64363
- const cached = magmuxCache.get(binary ?? "");
64372
+ const key = binary ?? "";
64373
+ const cached = magmuxCache.get(key);
64364
64374
  if (cached)
64365
64375
  return cached;
64376
+ const inFlight = magmuxProbes.get(key);
64377
+ if (inFlight)
64378
+ return inFlight;
64379
+ const probe = (async () => {
64380
+ try {
64381
+ const result = await probeMagmux(binary);
64382
+ magmuxCache.set(key, result);
64383
+ return result;
64384
+ } finally {
64385
+ magmuxProbes.delete(key);
64386
+ }
64387
+ })();
64388
+ magmuxProbes.set(key, probe);
64389
+ return probe;
64390
+ }
64391
+ async function probeMagmux(binary) {
64366
64392
  const found = binary ?? findMagmuxBinaryOrNull();
64367
64393
  if (!found)
64368
64394
  throw new MagmuxUnavailableError("magmux_unavailable: magmux not found (bundled package or PATH); install it: brew install MadAppGang/tap/magmux");
64395
+ let version = await readMagmuxVersion(found);
64396
+ if (!version)
64397
+ version = await readMagmuxVersion(found);
64398
+ if (!version || !versionAtLeast(version, MIN_MAGMUX_VERSION))
64399
+ throw new MagmuxUnavailableError(`magmux_unavailable: ${found} is version ${version || "unknown"}; panes need >= ${MIN_MAGMUX_VERSION} (a socket at any path depth, and the Claude Code input gate bound to a named transcript); upgrade: brew upgrade MadAppGang/tap/magmux`);
64400
+ return { binary: found, version };
64401
+ }
64402
+ async function readMagmuxVersion(found) {
64369
64403
  let out = "";
64370
64404
  try {
64371
- out = (await execFileAsync(found, ["--version"], { encoding: "utf8", timeout: 5000 })).stdout;
64405
+ const r = await execFileAsync(found, ["--version"], { encoding: "utf8", timeout: 5000 });
64406
+ out = `${r.stdout}
64407
+ ${r.stderr}`;
64372
64408
  } catch (e) {
64373
64409
  throw new MagmuxUnavailableError(`magmux_unavailable: ${found} --version failed: ${e instanceof Error ? e.message : String(e)}`);
64374
64410
  }
64375
- const version = out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "";
64376
- if (!version || !versionAtLeast(version, MIN_MAGMUX_VERSION))
64377
- throw new MagmuxUnavailableError(`magmux_unavailable: ${found} is version ${version || "unknown"}; panes need >= ${MIN_MAGMUX_VERSION}`);
64378
- const result = { binary: found, version };
64379
- magmuxCache.set(binary ?? "", result);
64380
- return result;
64411
+ return out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "";
64381
64412
  }
64382
64413
  function resolveClaudishLaunch(parentEnv) {
64383
64414
  const t = resolveClaudishSpawn(parentEnv);
@@ -64426,21 +64457,35 @@ function spawnPaneWatcher(args) {
64426
64457
  proc.stdin?.unref?.();
64427
64458
  return proc;
64428
64459
  }
64429
- function spawnPaneMagmux(input) {
64430
- const proc = spawn(input.binary, [
64460
+ function paneMagmuxArgv(input) {
64461
+ return [
64431
64462
  "--headless",
64432
64463
  "--no-status",
64464
+ "--controller",
64465
+ "claude-code",
64466
+ "--session-id",
64467
+ input.sessionUuid,
64468
+ "--transcript",
64469
+ resolve3(input.transcriptPath),
64433
64470
  "--id",
64434
64471
  input.paneId,
64435
64472
  "--sock-dir",
64436
64473
  input.sockRoot,
64437
64474
  "-e",
64438
64475
  paneCommand(input.ctlDir)
64439
- ], { cwd: input.cwd, env: input.env, stdio: ["ignore", "ignore", "pipe"], detached: true });
64476
+ ];
64477
+ }
64478
+ function spawnPaneMagmux(input) {
64479
+ const proc = spawn(input.binary, paneMagmuxArgv(input), {
64480
+ cwd: input.cwd,
64481
+ env: input.env,
64482
+ stdio: ["ignore", "ignore", "pipe"],
64483
+ detached: true
64484
+ });
64440
64485
  proc.on("error", () => {});
64441
64486
  return proc;
64442
64487
  }
64443
- var SOCK_ROOT, PaneRootError, OWNER_START36, TERMINAL_IDENTITY_VARS, TERMINAL_IDENTITY_PREFIXES, HOST_IDENTITY_VARS, LINUX_ENV_STRING_LIMIT = 131000, PaneEnvTooLargeError, OWN_ARGV, TRANSPORT_BREAKERS, IDENTITY_BREAKERS, PRINT_ONLY_FLAGS, MODE_FLAGS, SUBCOMMAND_WORDS, CLAUDE_CODE_VALUE_FLAGS, KNOWN_CLAUDISH_FLAGS, MIN_MAGMUX_VERSION = "0.14.0", MagmuxUnavailableError, magmuxCache, execFileAsync, ClaudishNotFoundError, SHELL_SHIM = `#!/bin/sh
64488
+ var SOCK_ROOT, PaneRootError, OWNER_START36, TERMINAL_IDENTITY_VARS, TERMINAL_IDENTITY_PREFIXES, HOST_IDENTITY_VARS, LINUX_ENV_STRING_LIMIT = 131000, PaneEnvTooLargeError, OWN_ARGV, TRANSPORT_BREAKERS, IDENTITY_BREAKERS, PRINT_ONLY_FLAGS, MODE_FLAGS, SUBCOMMAND_WORDS, CLAUDE_CODE_VALUE_FLAGS, KNOWN_CLAUDISH_FLAGS, MIN_MAGMUX_VERSION = "0.18.0", MagmuxUnavailableError, magmuxCache, magmuxProbes, execFileAsync, ClaudishNotFoundError, SHELL_SHIM = `#!/bin/sh
64444
64489
  [ "$1" = -l ] && shift
64445
64490
  exec /bin/sh "$@"
64446
64491
  `, FATAL_MAGMUX_STDERR;
@@ -64614,15 +64659,30 @@ var init_pane_launch = __esm(() => {
64614
64659
  code = "magmux_unavailable";
64615
64660
  };
64616
64661
  magmuxCache = new Map;
64662
+ magmuxProbes = new Map;
64617
64663
  execFileAsync = promisify5(execFile2);
64618
64664
  ClaudishNotFoundError = class ClaudishNotFoundError extends Error {
64619
64665
  code = "pane_lost";
64620
64666
  };
64621
- FATAL_MAGMUX_STDERR = /ignoring --id|ignoring --sock-dir|magmux: socket/;
64667
+ FATAL_MAGMUX_STDERR = /ignoring --id|magmux: (?:--sock-dir|MAGMUX_SOCK_DIR) |magmux: socket/;
64622
64668
  });
64623
64669
 
64624
64670
  // src/pane/magmux-client.ts
64625
64671
  import { Socket } from "net";
64672
+ function failureOf(m) {
64673
+ const out = {
64674
+ ok: false,
64675
+ code: String(m.code ?? "error"),
64676
+ error: String(m.error ?? "")
64677
+ };
64678
+ if (typeof m.reason === "string" && m.reason)
64679
+ out.reason = m.reason;
64680
+ if (typeof m.typed === "boolean")
64681
+ out.typed = m.typed;
64682
+ if (typeof m.maybeSubmitted === "boolean")
64683
+ out.maybeSubmitted = m.maybeSubmitted;
64684
+ return out;
64685
+ }
64626
64686
  function dialOnce(sockPath) {
64627
64687
  return new Promise((resolve, reject) => {
64628
64688
  const s = new Socket;
@@ -64749,7 +64809,7 @@ class MagmuxClient {
64749
64809
  return;
64750
64810
  clearTimeout(p.timer);
64751
64811
  this.pending.delete(m.id);
64752
- p.resolve(m.ok === true ? { ok: true, result: m.result ?? {} } : { ok: false, code: String(m.code ?? "error"), error: String(m.error ?? "") });
64812
+ p.resolve(m.ok === true ? { ok: true, result: m.result ?? {} } : failureOf(m));
64753
64813
  return;
64754
64814
  }
64755
64815
  if (m.type === "shutdown")
@@ -65247,6 +65307,352 @@ var init_process_tree = __esm(() => {
65247
65307
  KILL_PROCESS_GROUP = process.platform !== "win32";
65248
65308
  });
65249
65309
 
65310
+ // src/pane/box-locate.ts
65311
+ function isRuleRow(line) {
65312
+ if (!line || line.length < MIN_RULE_CELLS)
65313
+ return false;
65314
+ return /^\u2500+(?: [^\u2500]+ \u2500+)?$/.test(line);
65315
+ }
65316
+ function nearestRule(lines, from, step) {
65317
+ for (let i = 1;i <= RULE_SEARCH_ROWS; i++) {
65318
+ const r = from + i * step;
65319
+ if (r < 0 || r >= lines.length)
65320
+ return -1;
65321
+ if (isRuleRow(lines[r]))
65322
+ return r;
65323
+ }
65324
+ return -1;
65325
+ }
65326
+ function inspectBox(s) {
65327
+ const lines = s.lines;
65328
+ const y = s.cursor.y;
65329
+ if (!Number.isInteger(y) || y < 0 || y >= s.rows || y >= lines.length) {
65330
+ return { kind: "unknown", why: "cursor_off_screen" };
65331
+ }
65332
+ if (SELECTED_OPTION.test(lines[y] ?? ""))
65333
+ return { kind: "dialog", why: "selected_option" };
65334
+ const top = nearestRule(lines, y, -1);
65335
+ if (top < 0)
65336
+ return { kind: "unknown", why: "no_rule_above" };
65337
+ const bottom = nearestRule(lines, y, 1);
65338
+ if (bottom < 0)
65339
+ return { kind: "unknown", why: "no_rule_below" };
65340
+ const first = lines[top + 1] ?? "";
65341
+ if (SELECTED_OPTION.test(first))
65342
+ return { kind: "dialog", why: "selected_option" };
65343
+ if (lines.slice(bottom + 1).some((l) => SELECT_FOOTER.test(l))) {
65344
+ return { kind: "dialog", why: "select_footer" };
65345
+ }
65346
+ if (!PROMPT_ROW.test(first))
65347
+ return { kind: "unknown", why: "no_prompt_row" };
65348
+ return { kind: "box", box: { top, bottom, caretRow: y } };
65349
+ }
65350
+ function locateBox(s) {
65351
+ const at = inspectBox(s);
65352
+ return at.kind === "box" ? at.box : null;
65353
+ }
65354
+ function visibleText(line, spans) {
65355
+ if (!spans?.length)
65356
+ return line;
65357
+ const chars = Array.from(line);
65358
+ for (const [col, len, , , attr] of spans) {
65359
+ if ((attr & ATTR_FAINT) === 0)
65360
+ continue;
65361
+ for (let c = col;c < col + len && c < chars.length; c++)
65362
+ chars[c] = " ";
65363
+ }
65364
+ return chars.join("");
65365
+ }
65366
+ function locatedBoxText(s, box) {
65367
+ const parts = [];
65368
+ for (let i = box.top + 1;i < box.bottom; i++) {
65369
+ const raw = visibleText(s.lines[i] ?? "", s.spans[i]);
65370
+ const body = i === box.top + 1 ? raw.replace(/^[\u276F!]\s?/, "") : raw.replace(/^ {2}/, "");
65371
+ parts.push(body.replace(/\s+$/, ""));
65372
+ }
65373
+ return parts.join(`
65374
+ `).trim();
65375
+ }
65376
+ var RULE_SEARCH_ROWS = 40, MIN_RULE_CELLS = 20, PROMPT_ROW, SELECTED_OPTION, SELECT_FOOTER, ATTR_FAINT = 2;
65377
+ var init_box_locate = __esm(() => {
65378
+ PROMPT_ROW = /^(\u276F(\u00A0|$)|!)/;
65379
+ SELECTED_OPTION = /^ *\u276F \d+\.\s+\S/;
65380
+ SELECT_FOOTER = /Enter to select \u00B7 \u2191\/\u2193 to navigate/;
65381
+ });
65382
+
65383
+ // src/pane/input-gate.ts
65384
+ function dialogKindOf(reason) {
65385
+ if (!reason?.startsWith(DIALOG_PREFIX))
65386
+ return null;
65387
+ const kind = reason.slice(DIALOG_PREFIX.length);
65388
+ return DIALOG_KINDS.has(kind) ? kind : null;
65389
+ }
65390
+ function str3(v) {
65391
+ return typeof v === "string" && v !== "" ? v : null;
65392
+ }
65393
+ function record4(v) {
65394
+ return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
65395
+ }
65396
+ function paneZero(listResult) {
65397
+ const panes = record4(listResult)?.panes;
65398
+ if (!Array.isArray(panes))
65399
+ return null;
65400
+ for (const p of panes) {
65401
+ const row = record4(p);
65402
+ if (row?.pane === 0)
65403
+ return row;
65404
+ }
65405
+ return null;
65406
+ }
65407
+ function parseGateInput(listResult, now) {
65408
+ const input = record4(paneZero(listResult)?.input);
65409
+ if (!input || typeof input.ready !== "boolean")
65410
+ return null;
65411
+ const reason = input.ready ? null : str3(input.reason);
65412
+ return {
65413
+ ready: input.ready,
65414
+ reason,
65415
+ dialog: dialogKindOf(reason),
65416
+ detail: str3(input.detail),
65417
+ receivedAt: now
65418
+ };
65419
+ }
65420
+ function gateBasisOf(listResult) {
65421
+ const basis = paneZero(listResult)?.gateBasis;
65422
+ return basis === "direct" || basis === "declared" ? basis : null;
65423
+ }
65424
+ function parseGateBinding(listResult) {
65425
+ const row = paneZero(listResult);
65426
+ if (!row)
65427
+ return null;
65428
+ const boundRaw = str3(row.bound);
65429
+ const sessionId = str3(row.sessionId);
65430
+ const transcriptPath = str3(row.transcriptPath);
65431
+ let bound = "unbound";
65432
+ if (sessionId && transcriptPath) {
65433
+ if (boundRaw === "named" || boundRaw === "prompt" || boundRaw === "mtime")
65434
+ bound = boundRaw;
65435
+ }
65436
+ return { bound, boundRaw, sessionId, transcriptPath };
65437
+ }
65438
+ function assertNever(x) {
65439
+ throw new Error(`unreachable: ${JSON.stringify(x)}`);
65440
+ }
65441
+ function retypeOf(result) {
65442
+ if (result.retyped !== true)
65443
+ return { retyped: false, retypeCause: null };
65444
+ const cause = result.retypeCause;
65445
+ return {
65446
+ retyped: true,
65447
+ retypeCause: cause === "terminal_reset" || cause === "never_drawn" ? cause : "unknown"
65448
+ };
65449
+ }
65450
+ function okOutcome(result) {
65451
+ const { retyped, retypeCause } = retypeOf(result);
65452
+ const waitedMs = typeof result.waitedMs === "number" ? result.waitedMs : 0;
65453
+ const verified = str3(result.verified);
65454
+ const accept = result.accept;
65455
+ if (accept === "in_box")
65456
+ return { kind: "in_box", retyped, retypeCause, waitedMs, verified };
65457
+ if (accept === undefined) {
65458
+ return {
65459
+ kind: "unexpected",
65460
+ code: "ungated_reply",
65461
+ error: "ok with no accept: the pane is not gated"
65462
+ };
65463
+ }
65464
+ if (accept !== "confirmed" && accept !== "resent_enter" && accept !== "submitted_by_other") {
65465
+ return {
65466
+ kind: "unexpected",
65467
+ code: "unknown_accept",
65468
+ error: `accept ${JSON.stringify(accept)}`
65469
+ };
65470
+ }
65471
+ const evidence = result.evidence;
65472
+ if (evidence !== "history" && evidence !== "transcript") {
65473
+ return {
65474
+ kind: "unexpected",
65475
+ code: "unknown_evidence",
65476
+ error: `accept ${accept} with evidence ${JSON.stringify(evidence)}`
65477
+ };
65478
+ }
65479
+ const dismissed = Array.isArray(result.dismissed) ? result.dismissed.filter((d) => typeof d === "string") : [];
65480
+ return {
65481
+ kind: "accepted",
65482
+ accept,
65483
+ evidence,
65484
+ enterByMagmux: result.enter === true,
65485
+ retyped,
65486
+ retypeCause,
65487
+ dismissed,
65488
+ waitedMs,
65489
+ verified
65490
+ };
65491
+ }
65492
+ function notAcceptedOutcome(r) {
65493
+ const typed = r.typed === true;
65494
+ switch (r.reason) {
65495
+ case "text_mismatch":
65496
+ return {
65497
+ kind: "not_accepted",
65498
+ reason: "text_mismatch",
65499
+ maybeSubmitted: r.maybeSubmitted === true || r.error.includes(MAYBE_SUBMITTED_MARKER),
65500
+ typed,
65501
+ detail: r.error
65502
+ };
65503
+ case "empty_box":
65504
+ case "still_in_box":
65505
+ case "unconfirmed":
65506
+ return {
65507
+ kind: "not_accepted",
65508
+ reason: r.reason,
65509
+ maybeSubmitted: false,
65510
+ typed,
65511
+ detail: r.error
65512
+ };
65513
+ default:
65514
+ return {
65515
+ kind: "unexpected",
65516
+ code: `not_accepted:${r.reason ?? ""}`,
65517
+ error: r.error
65518
+ };
65519
+ }
65520
+ }
65521
+ function classifySendReply(r) {
65522
+ if (r.ok)
65523
+ return okOutcome(record4(r.result) ?? {});
65524
+ switch (r.code) {
65525
+ case "busy": {
65526
+ const reason = r.reason ?? "";
65527
+ return { kind: "busy", reason, dialog: dialogKindOf(reason), typed: r.typed === true };
65528
+ }
65529
+ case "not_accepted":
65530
+ return notAcceptedOutcome(r);
65531
+ case "bad_request":
65532
+ return { kind: "bad_request", error: r.error };
65533
+ case "client_timeout":
65534
+ case "client_lost":
65535
+ case "timeout":
65536
+ return { kind: "reply_lost", code: r.code };
65537
+ case "client_closed":
65538
+ case "no_client":
65539
+ return { kind: "unsent", code: r.code };
65540
+ case "pane_dead":
65541
+ case "no_such_pane":
65542
+ return { kind: "pane_gone", code: r.code, typed: r.typed === true };
65543
+ default:
65544
+ return { kind: "unexpected", code: r.code, error: r.error };
65545
+ }
65546
+ }
65547
+ function readyWaitMs(readyTimeoutMs) {
65548
+ if (readyTimeoutMs <= 0)
65549
+ return SEND_READY_WAIT_DEFAULT_MS;
65550
+ return Math.min(readyTimeoutMs, SEND_READY_WAIT_MAX_MS);
65551
+ }
65552
+ function typedWriteCount(text) {
65553
+ const rs = Array.from(text);
65554
+ let n = 0;
65555
+ let run = 0;
65556
+ for (let i = 0;i < rs.length; i++) {
65557
+ const r = rs[i];
65558
+ if (r === "\r" || r === `
65559
+ `) {
65560
+ if (r === "\r" && rs[i + 1] === `
65561
+ `)
65562
+ i++;
65563
+ if (run > 0) {
65564
+ n++;
65565
+ run = 0;
65566
+ }
65567
+ n++;
65568
+ } else {
65569
+ run++;
65570
+ if (run === SEND_TYPED_CHUNK_RUNES) {
65571
+ n++;
65572
+ run = 0;
65573
+ }
65574
+ }
65575
+ }
65576
+ return run > 0 ? n + 1 : n;
65577
+ }
65578
+ function sendBudgetMs(s) {
65579
+ let attempt = 0;
65580
+ if (s.typed ?? true)
65581
+ attempt += typedWriteCount(s.text) * (SEND_TYPED_CHUNK_GAP_MS + SEND_GATE_CHECK_COST_MS);
65582
+ attempt += s.keys * (SEND_KEY_GAP_MS + SEND_GATE_CHECK_COST_MS);
65583
+ attempt += SEND_SETTLE_MS + SEND_VERIFY_MIN_MS + SEND_VERIFY_STEP_MS;
65584
+ const accept = s.submit ? SEND_ACCEPT_WINDOWS * SEND_ACCEPT_WINDOW_MS : 0;
65585
+ return readyWaitMs(s.readyTimeoutMs) + 2 * attempt + accept + SEND_BUDGET_SLACK_MS;
65586
+ }
65587
+ var DIALOG_KINDS, BOOT_DIALOG_KINDS, DIALOG_PREFIX = "dialog:", MAYBE_SUBMITTED_MARKER = "check the conversation before sending", SEND_READY_WAIT_DEFAULT_MS = 1e4, SEND_READY_WAIT_MAX_MS = 60000, SEND_TYPED_CHUNK_RUNES = 32, SEND_TYPED_CHUNK_GAP_MS = 8, SEND_KEY_GAP_MS = 20, SEND_SETTLE_MS = 150, SEND_VERIFY_MIN_MS = 1200, SEND_VERIFY_STEP_MS = 40, SEND_GATE_CHECK_COST_MS = 2, SEND_ACCEPT_WINDOW_MS = 2000, SEND_ACCEPT_WINDOWS = 3, SEND_BUDGET_SLACK_MS = 2000;
65588
+ var init_input_gate = __esm(() => {
65589
+ DIALOG_KINDS = new Set([
65590
+ "permission",
65591
+ "question",
65592
+ "plan",
65593
+ "select",
65594
+ "trust",
65595
+ "bypass",
65596
+ "api_key",
65597
+ "onboarding"
65598
+ ]);
65599
+ BOOT_DIALOG_KINDS = new Set([
65600
+ "trust",
65601
+ "bypass",
65602
+ "api_key",
65603
+ "onboarding"
65604
+ ]);
65605
+ });
65606
+
65607
+ // src/pane/gate-binding.ts
65608
+ import { realpathSync as realpathSync2 } from "fs";
65609
+ import { basename as basename2, dirname as dirname14, join as join45, resolve as resolve4 } from "path";
65610
+ function normaliseTranscriptPath(p) {
65611
+ const abs = resolve4(p);
65612
+ const tail = [];
65613
+ let cur = abs;
65614
+ for (;; ) {
65615
+ try {
65616
+ return tail.length ? join45(realpathSync2(cur), ...tail.reverse()) : realpathSync2(cur);
65617
+ } catch {
65618
+ const parent = dirname14(cur);
65619
+ if (parent === cur)
65620
+ return abs;
65621
+ tail.push(basename2(cur));
65622
+ cur = parent;
65623
+ }
65624
+ }
65625
+ }
65626
+ function checkGateBinding(listResult, expected) {
65627
+ const v = expected.magmuxVersion || "?";
65628
+ const basis = gateBasisOf(listResult);
65629
+ if (basis !== "declared" || !parseGateInput(listResult, 0)) {
65630
+ return {
65631
+ ok: false,
65632
+ detail: `input_gate_unavailable: magmux ${v} did not gate pane 0 (gateBasis ${basis ?? "none"})`
65633
+ };
65634
+ }
65635
+ const b = parseGateBinding(listResult);
65636
+ const ours = normaliseTranscriptPath(expected.transcriptPath);
65637
+ const theirs = b?.transcriptPath ? normaliseTranscriptPath(b.transcriptPath) : null;
65638
+ const differs = [];
65639
+ if (b?.bound !== "named" && b?.boundRaw !== "named")
65640
+ differs.push(`bound ${b?.boundRaw ?? "none"}`);
65641
+ if (b?.sessionId !== expected.sessionUuid)
65642
+ differs.push(`sessionId ${b?.sessionId ?? "none"}, not ${expected.sessionUuid}`);
65643
+ if (theirs !== ours)
65644
+ differs.push(`transcriptPath ${theirs ?? "none"}, not ${ours}`);
65645
+ if (b?.bound === "named" && differs.length === 0)
65646
+ return { ok: true };
65647
+ return {
65648
+ ok: false,
65649
+ detail: `input_gate_unbound: magmux ${v} bound pane 0 to ${theirs ?? "none"} (${b?.boundRaw ?? "none"}), not ${ours}: ${differs.join("; ")}`
65650
+ };
65651
+ }
65652
+ var init_gate_binding = __esm(() => {
65653
+ init_input_gate();
65654
+ });
65655
+
65250
65656
  // src/pane/screen-model.ts
65251
65657
  function emptyScreen(cols, rows) {
65252
65658
  return {
@@ -65264,7 +65670,7 @@ function emptyScreen(cols, rows) {
65264
65670
  function isBoxRule(line, cols) {
65265
65671
  if (!line || !/^\u2500+(?: [^\u2500]+ \u2500+)?$/.test(line))
65266
65672
  return false;
65267
- return line.length >= Math.max(20, Math.min(cols, 160) - 1);
65673
+ return line.length >= Math.max(20, cols - 1);
65268
65674
  }
65269
65675
  function boxTopRow(lines, cols) {
65270
65676
  for (let i = lines.length - 2;i >= 0; i--) {
@@ -65288,22 +65694,26 @@ function withSize(s, rows) {
65288
65694
  spans.push([]);
65289
65695
  return { lines, spans };
65290
65696
  }
65291
- function aboveBoxText(lines, cols) {
65292
- const top = boxTopRow(lines, cols);
65293
- return (top >= 0 ? lines.slice(0, top) : lines).join(`
65294
- `);
65697
+ function aboveBoxText(s) {
65698
+ const box = locateBox(s);
65699
+ return box ? s.lines.slice(0, box.top).join(`
65700
+ `) : null;
65295
65701
  }
65296
65702
  function finish(prev, next, now) {
65297
65703
  const visible = prev.seq === 0 || prev.cols !== next.cols || prev.rows !== next.rows || prev.alt !== next.alt || prev.cursor.x !== next.cursor.x || prev.cursor.y !== next.cursor.y || next.lines.some((l, i) => l !== prev.lines[i]) || next.spans.some((r, i) => !sameSpans(r, prev.spans[i]));
65298
65704
  if (!visible)
65299
65705
  return prev;
65300
- const aboveChanged = prev.seq === 0 || aboveBoxText(prev.lines, prev.cols) !== aboveBoxText(next.lines, next.cols);
65301
- return {
65706
+ const out = {
65302
65707
  ...next,
65303
65708
  seq: prev.seq + 1,
65304
65709
  changedAt: now,
65305
- aboveBoxChangedAt: aboveChanged ? now : prev.aboveBoxChangedAt
65710
+ aboveBoxChangedAt: prev.aboveBoxChangedAt
65306
65711
  };
65712
+ const before = prev.seq === 0 ? null : aboveBoxText(prev);
65713
+ const after = aboveBoxText(out);
65714
+ if (before === null || after === null || before !== after)
65715
+ out.aboveBoxChangedAt = now;
65716
+ return out;
65307
65717
  }
65308
65718
  function applyFrame(s, frame, now = Date.now()) {
65309
65719
  const rows = frame.rows;
@@ -65328,6 +65738,9 @@ function applyFrame(s, frame, now = Date.now()) {
65328
65738
  alt: frame.alt ?? s.alt
65329
65739
  }, now);
65330
65740
  }
65741
+ var init_screen_model = __esm(() => {
65742
+ init_box_locate();
65743
+ });
65331
65744
 
65332
65745
  // src/pane/screen-classifier.ts
65333
65746
  function screenText(s) {
@@ -65337,18 +65750,44 @@ function screenText(s) {
65337
65750
  return lines.join(`
65338
65751
  `);
65339
65752
  }
65340
- function visibleText(line, spans) {
65753
+ function flatText(lines) {
65754
+ return lines.join(" ").replace(/\s+/g, " ").trim();
65755
+ }
65756
+ function squash(s) {
65757
+ return s.replace(/\s+/g, "");
65758
+ }
65759
+ function echoHeadLength(cols) {
65760
+ return Math.max(1, Math.min(40, cols - 4));
65761
+ }
65762
+ function echoHead(line, cols) {
65763
+ return line.slice(0, echoHeadLength(cols));
65764
+ }
65765
+ function visibleText2(line, spans) {
65341
65766
  if (!spans?.length)
65342
65767
  return line;
65343
65768
  const chars = Array.from(line);
65344
65769
  for (const [col, len, , , attr] of spans) {
65345
- if ((attr & ATTR_FAINT) === 0)
65770
+ if ((attr & ATTR_FAINT2) === 0)
65346
65771
  continue;
65347
65772
  for (let c = col;c < col + len && c < chars.length; c++)
65348
65773
  chars[c] = " ";
65349
65774
  }
65350
65775
  return chars.join("");
65351
65776
  }
65777
+ function echoAt(lines, i, head) {
65778
+ const first = lines[i] ?? "";
65779
+ if (!first.startsWith("\u276F "))
65780
+ return false;
65781
+ const want = squash(head);
65782
+ let got = squash(first.slice(2));
65783
+ for (let j = i + 1;got.length < want.length && j < lines.length; j++) {
65784
+ const row = lines[j] ?? "";
65785
+ if (!/^ {2}\S/.test(row))
65786
+ break;
65787
+ got += squash(row);
65788
+ }
65789
+ return got.startsWith(want);
65790
+ }
65352
65791
  function inputBox(s) {
65353
65792
  const top = boxTopRow(s.lines, s.cols);
65354
65793
  if (top < 0)
@@ -65367,7 +65806,7 @@ function boxBottomRow(s, top) {
65367
65806
  function boxText(s, top, bottom) {
65368
65807
  const parts = [];
65369
65808
  for (let i = top + 1;i < bottom; i++) {
65370
- const raw = visibleText(s.lines[i] ?? "", s.spans[i]);
65809
+ const raw = visibleText2(s.lines[i] ?? "", s.spans[i]);
65371
65810
  const body = i === top + 1 ? raw.replace(/^\u276F\s?/, "") : raw.replace(/^ {2}/, "");
65372
65811
  parts.push(body.replace(/\s+$/, ""));
65373
65812
  }
@@ -65400,7 +65839,8 @@ function choiceDialog(s) {
65400
65839
  const lines = s.lines;
65401
65840
  const start = lastBulletRow(lines) + 1;
65402
65841
  const below = lines.slice(start);
65403
- const kind = CHOICE_DIALOGS.find((d) => below.some((l) => d.header.test(l)))?.kind;
65842
+ const flat = flatText(below);
65843
+ const kind = CHOICE_DIALOGS.find((d) => d.header.test(flat))?.kind;
65404
65844
  if (!kind)
65405
65845
  return null;
65406
65846
  if (!below.some((l) => FIRST_OPTION.test(l)))
@@ -65408,14 +65848,19 @@ function choiceDialog(s) {
65408
65848
  return { kind, text: below.filter((l) => l.trim()).join(`
65409
65849
  `) };
65410
65850
  }
65411
- function hasChoiceDialog(s) {
65412
- return choiceDialog(s) !== null;
65851
+ function dialogText(s) {
65852
+ return s.lines.slice(lastBulletRow(s.lines) + 1).filter((l) => l.trim()).join(`
65853
+ `);
65413
65854
  }
65414
65855
  function isWorking(s) {
65415
65856
  const top = boxTopRow(s.lines, s.cols);
65416
65857
  const region = top >= 0 ? s.lines.slice(0, top) : s.lines;
65417
65858
  return region.some((l) => WORKING_ROW.test(l));
65418
65859
  }
65860
+ function workingAboveBox(s) {
65861
+ const box = locateBox(s);
65862
+ return box ? s.lines.slice(0, box.top).some((l) => WORKING_ROW.test(l)) : null;
65863
+ }
65419
65864
  function stopHookRunning(s) {
65420
65865
  return s.lines.some((l) => /\(running Stop hook\b/.test(l));
65421
65866
  }
@@ -65447,7 +65892,7 @@ function screenAnswer(s, echo) {
65447
65892
  const lines = s.lines;
65448
65893
  const top = boxTopRow(lines, s.cols);
65449
65894
  const end = top >= 0 ? top : lines.length;
65450
- const start = echoRow(lines, end, `\u276F ${echo.slice(0, 40)}`) + 1;
65895
+ const start = echoRow(lines, end, echoHead(echo, s.cols)) + 1;
65451
65896
  if (start <= 0)
65452
65897
  return "";
65453
65898
  const out = [];
@@ -65462,8 +65907,9 @@ function screenAnswer(s, echo) {
65462
65907
  `).trim();
65463
65908
  }
65464
65909
  function echoRow(lines, end, head) {
65910
+ const above = lines.slice(0, end);
65465
65911
  for (let i = end - 1;i >= 0; i--)
65466
- if ((lines[i] ?? "").startsWith(head))
65912
+ if (echoAt(above, i, head))
65467
65913
  return i;
65468
65914
  return -1;
65469
65915
  }
@@ -65485,9 +65931,9 @@ function agentRejectedLine(text) {
65485
65931
  const m = text.match(/--agent '([^']+)' not found\.(?: Available agents:[^\n]*)?/);
65486
65932
  return m ? m[0] : null;
65487
65933
  }
65488
- function namedDialog(text) {
65934
+ function namedDialog(flat) {
65489
65935
  for (const d of NAMED_DIALOGS)
65490
- if (text.includes(d.marker))
65936
+ if (flat.includes(d.marker))
65491
65937
  return d.name;
65492
65938
  return null;
65493
65939
  }
@@ -65496,22 +65942,20 @@ function isUnnamedChoice(lines) {
65496
65942
  const yesNo = lines.some((l) => /^\s*(\u276F\s*)?(\d+\.\s+)?(Yes|No)\b|\(esc\)/.test(l));
65497
65943
  if (numbered && yesNo)
65498
65944
  return true;
65499
- return lines.some((l) => /Press Enter to continue/.test(l) || SELECT_FOOTER.test(l));
65500
- }
65501
- function readBoot(s) {
65502
- const text = screenText(s);
65503
- const box = inputBox(s);
65504
- const named = namedDialog(text);
65505
- if (box && !named)
65506
- return box.text === "" ? { kind: "repl" } : { kind: "booting" };
65945
+ return lines.some((l) => /Press Enter to continue/.test(l) || SELECT_FOOTER2.test(l));
65946
+ }
65947
+ function nameBootScreen(s) {
65948
+ const named = namedDialog(flatText(s.lines));
65507
65949
  if (named)
65508
- return { kind: "dialog", name: named, text };
65950
+ return { kind: "dialog", name: named, text: screenText(s) };
65509
65951
  if (isUnnamedChoice(s.lines))
65510
- return { kind: "choice", text };
65952
+ return { kind: "choice", text: screenText(s) };
65511
65953
  return { kind: "booting" };
65512
65954
  }
65513
- var NAMED_DIALOGS, CHOICE_DIALOGS, NUMBERED_OPTION, FIRST_OPTION, SELECT_FOOTER, WORKING_ROW, BULLET, ERROR_ROW, ATTR_FAINT = 2, MENU_ROW, NON_ANSWER_ROW;
65955
+ var NAMED_DIALOGS, CHOICE_DIALOGS, NUMBERED_OPTION, FIRST_OPTION, SELECT_FOOTER2, WORKING_ROW, BULLET, ERROR_ROW, ATTR_FAINT2 = 2, MENU_ROW, NON_ANSWER_ROW;
65514
65956
  var init_screen_classifier = __esm(() => {
65957
+ init_box_locate();
65958
+ init_screen_model();
65515
65959
  NAMED_DIALOGS = [
65516
65960
  { name: "onboarding", marker: "Choose the text style" },
65517
65961
  { name: "trust", marker: "Is this a project you created or one you trust" },
@@ -65522,15 +65966,15 @@ var init_screen_classifier = __esm(() => {
65522
65966
  { kind: "permission", header: /\bDo you want to proceed\?/ },
65523
65967
  {
65524
65968
  kind: "permission",
65525
- header: /\bDo you want to (create|make this edit to|overwrite|delete) .+\?/
65969
+ header: /\bDo you want to (create|make this edit to|overwrite|delete) .{1,300}?\?/
65526
65970
  },
65527
65971
  { kind: "plan", header: /\bWould you like to proceed\?/ },
65528
65972
  { kind: "question", header: /Enter to select \u00B7 \u2191\/\u2193 to navigate/ }
65529
65973
  ];
65530
65974
  NUMBERED_OPTION = /^\s*(\u276F\s*)?\d+\.\s+\S/;
65531
65975
  FIRST_OPTION = /^\s*(\u276F\s*)?1\.\s+\S/;
65532
- SELECT_FOOTER = /Enter to (confirm|select)\b.*Esc to cancel/;
65533
- WORKING_ROW = /^\s*[\u00B7\u2722\u2733\u2736\u273B\u273D*\u25CF\u2217]\s+[A-Z][\w'-]*\u2026(\s+\(.*\))?\s*$/;
65976
+ SELECT_FOOTER2 = /Enter to (confirm|select)\b.*Esc to cancel/;
65977
+ WORKING_ROW = /^\s*[\u00B7\u2722\u2733\u2736\u273B\u273D*\u25CF\u2217]\s+[A-Z][\w'-]*\u2026(\s+\((?:.*\)|[^)]*))?\s*$/;
65534
65978
  BULLET = /^\u23FA(\s|$)/;
65535
65979
  ERROR_ROW = /^\u23FA\s.*(API Error\b|Please run \/login|Not logged in|You've hit your|session limit)|^\s*\u23BF\s+API Error\b/;
65536
65980
  MENU_ROW = /^\s{2}\u276F \/\S|^\s{4}\/\S|^\s{4}[*+] \S/;
@@ -65539,7 +65983,7 @@ var init_screen_classifier = __esm(() => {
65539
65983
 
65540
65984
  // src/pane/session-registry.ts
65541
65985
  import { readFileSync as readFileSync29, readdirSync as readdirSync6 } from "fs";
65542
- import { join as join45 } from "path";
65986
+ import { join as join46 } from "path";
65543
65987
  function parseRegistryRecord(raw) {
65544
65988
  let parsed;
65545
65989
  try {
@@ -65584,7 +66028,7 @@ class SessionRegistry {
65584
66028
  isAlive;
65585
66029
  constructor(o) {
65586
66030
  this.o = o;
65587
- this.dir = join45(o.configDir, "sessions");
66031
+ this.dir = join46(o.configDir, "sessions");
65588
66032
  this.now = o.now ?? Date.now;
65589
66033
  this.isAlive = o.isAlive ?? pidAlive;
65590
66034
  }
@@ -65620,7 +66064,7 @@ class SessionRegistry {
65620
66064
  readPid(pid) {
65621
66065
  let raw;
65622
66066
  try {
65623
- raw = readFileSync29(join45(this.dir, `${pid}.json`), "utf8");
66067
+ raw = readFileSync29(join46(this.dir, `${pid}.json`), "utf8");
65624
66068
  } catch {
65625
66069
  return null;
65626
66070
  }
@@ -65665,14 +66109,28 @@ function activityOf(t) {
65665
66109
  return "finishing";
65666
66110
  return "thinking";
65667
66111
  }
66112
+ function unconfirmedBy(f) {
66113
+ const g = f.gate;
66114
+ if (g === null)
66115
+ return "gate_unknown";
66116
+ if (!g.ready || g.dialog !== null)
66117
+ return (g.reason ?? "not_ready").slice(0, 48);
66118
+ if (f.working === true)
66119
+ return "working";
66120
+ return null;
66121
+ }
65668
66122
  function corroborated(input) {
65669
66123
  const quiet = input.corroborationMs ?? CORROBORATION_MS;
65670
66124
  if (input.chatQuietMs < quiet)
65671
66125
  return { ok: false };
65672
- if (!input.screen)
66126
+ if (!input.facts)
65673
66127
  return { ok: true, anomaly: "screen_unverified" };
65674
- const s = input.screen;
65675
- return { ok: s.boxEmpty && !s.choiceDialog && !s.working };
66128
+ const why = unconfirmedBy(input.facts);
66129
+ if (why === null)
66130
+ return { ok: true };
66131
+ if (input.chatQuietMs >= (input.corroborationGraceMs ?? CORROBORATION_GRACE_MS))
66132
+ return { ok: true, anomaly: `settle_gate_unconfirmed:${why}` };
66133
+ return { ok: false };
65676
66134
  }
65677
66135
  function settledAs(by, stopReason, c, extra = []) {
65678
66136
  return {
@@ -65705,10 +66163,13 @@ function pathPrimary(t, input) {
65705
66163
  function pathSecondary(t, input) {
65706
66164
  if (!endingStop(t) || !stopHooksFinished(t, input.session))
65707
66165
  return null;
65708
- if (!input.screen)
66166
+ if (!input.facts)
65709
66167
  return null;
65710
66168
  const window2 = input.secondaryQuietMs ?? SECONDARY_QUIET_MS;
65711
- if (input.transcriptQuietMs < window2 || input.screen.aboveBoxQuietMs < window2)
66169
+ if (input.transcriptQuietMs < window2)
66170
+ return null;
66171
+ const above = input.facts.aboveBoxQuietMs;
66172
+ if (above !== null && above < window2)
65712
66173
  return null;
65713
66174
  const c = corroborated(input);
65714
66175
  return c.ok ? settledAs("quiet", t.lastAssistant?.stopReason ?? null, c, ["settled_without_turn_duration"]) : null;
@@ -65739,9 +66200,10 @@ function decideSettle(input) {
65739
66200
  return endingStop(t) ? settledAs("exit", t.lastAssistant.stopReason, {}) : notYet;
65740
66201
  return pathSecondary(t, input) ?? notYet;
65741
66202
  }
65742
- var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING;
66203
+ var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING, CORROBORATION_GRACE_MS;
65743
66204
  var init_settle_rule = __esm(() => {
65744
66205
  CONTINUING = new Set(["tool_use", "pause_turn"]);
66206
+ CORROBORATION_GRACE_MS = SECONDARY_QUIET_MS;
65745
66207
  });
65746
66208
 
65747
66209
  // src/pane/slot-state.ts
@@ -65882,10 +66344,10 @@ import {
65882
66344
  openSync as openSync8,
65883
66345
  readSync as readSync3,
65884
66346
  readdirSync as readdirSync7,
65885
- realpathSync as realpathSync2,
66347
+ realpathSync as realpathSync3,
65886
66348
  statSync as statSync9
65887
66349
  } from "fs";
65888
- import { join as join46 } from "path";
66350
+ import { join as join47 } from "path";
65889
66351
  function initialFollowerState(resolvePath = safeRealpath) {
65890
66352
  return {
65891
66353
  turns: [],
@@ -65900,7 +66362,7 @@ function initialFollowerState(resolvePath = safeRealpath) {
65900
66362
  }
65901
66363
  function safeRealpath(p) {
65902
66364
  try {
65903
- return realpathSync2(p);
66365
+ return realpathSync3(p);
65904
66366
  } catch {
65905
66367
  return p;
65906
66368
  }
@@ -66413,7 +66875,7 @@ class TranscriptFollower {
66413
66875
  for (const f of this.subagentFiles()) {
66414
66876
  let tail = this.subs.get(f);
66415
66877
  if (!tail) {
66416
- tail = new Tail(join46(this.subagentsDir, f));
66878
+ tail = new Tail(join47(this.subagentsDir, f));
66417
66879
  this.subs.set(f, tail);
66418
66880
  }
66419
66881
  for (const { line } of tail.read()) {
@@ -66496,8 +66958,8 @@ var init_transcript_follower = __esm(() => {
66496
66958
  });
66497
66959
 
66498
66960
  // src/pane/pane-session.ts
66499
- import { readFileSync as readFileSync30, realpathSync as realpathSync3, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
66500
- import { join as join47 } from "path";
66961
+ import { readFileSync as readFileSync30, realpathSync as realpathSync4, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
66962
+ import { join as join48 } from "path";
66501
66963
  function iso(ms) {
66502
66964
  return new Date(ms).toISOString();
66503
66965
  }
@@ -66534,7 +66996,7 @@ class PaneSessionImpl {
66534
66996
  initialPending;
66535
66997
  shape;
66536
66998
  queue = [];
66537
- screen = emptyScreen(160, 50);
66999
+ screen;
66538
67000
  final = false;
66539
67001
  turnIndex = 0;
66540
67002
  admission = null;
@@ -66570,8 +67032,25 @@ class PaneSessionImpl {
66570
67032
  disagreeCounted = new Set;
66571
67033
  exitScrollback = "";
66572
67034
  exitLastLine = "";
66573
- bootKind = null;
66574
- bootKindSince = 0;
67035
+ desiredSize;
67036
+ appliedSize;
67037
+ supportsResize = false;
67038
+ resizeWaiters = [];
67039
+ resizeDraining = false;
67040
+ lastSentSize = null;
67041
+ resizeQuietFrom = 0;
67042
+ resizeQuietUntil = 0;
67043
+ activityChangedAt = 0;
67044
+ gate = null;
67045
+ gateTrusted = false;
67046
+ gateInFlight = false;
67047
+ gateTimer = null;
67048
+ gateDeadSince = 0;
67049
+ lastSend = null;
67050
+ turnAcceptedAt = null;
67051
+ turnErrorLogSize = 0;
67052
+ witnessMissingCounted = false;
67053
+ abandonedIndex = null;
66575
67054
  magmux = null;
66576
67055
  watcher = null;
66577
67056
  client = null;
@@ -66602,12 +67081,16 @@ class PaneSessionImpl {
66602
67081
  this.turnDir = ids.turnDir;
66603
67082
  this.identity = { paneId: ids.paneId, sessionUuid: o.sessionUuid, ctlDir: ids.ctlDir };
66604
67083
  this.shape = o.shape;
67084
+ const size = launchSize(o);
67085
+ this.desiredSize = size;
67086
+ this.appliedSize = { ...size };
67087
+ this.screen = emptyScreen(size.cols, size.rows);
66605
67088
  this.initialPending = !!o.initialPrompt;
66606
67089
  if (o.initialPrompt)
66607
67090
  this.queue.push(o.initialPrompt);
66608
67091
  this.secondaryQuietMs = o.timings?.secondaryQuietMs ?? SECONDARY_QUIET_MS;
66609
67092
  this.corroborationMs = o.timings?.corroborationMs ?? CORROBORATION_MS;
66610
- this.follower = new TranscriptFollower(o.transcriptPath, join47(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
67093
+ this.follower = new TranscriptFollower(o.transcriptPath, join48(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
66611
67094
  this.registry = new SessionRegistry({
66612
67095
  configDir: claudeConfigDir(o.parentEnv ?? process.env),
66613
67096
  sessionUuid: o.sessionUuid
@@ -66643,7 +67126,9 @@ class PaneSessionImpl {
66643
67126
  sockRoot: this.root,
66644
67127
  ctlDir: this.ctlDir,
66645
67128
  cwd: input.cwd,
66646
- env: input.env
67129
+ env: input.env,
67130
+ sessionUuid: this.o.sessionUuid,
67131
+ transcriptPath: this.o.transcriptPath
66647
67132
  });
66648
67133
  } catch (e) {
66649
67134
  return this.spawnFailed(e);
@@ -66652,7 +67137,7 @@ class PaneSessionImpl {
66652
67137
  return this.spawnFailed(new Error("magmux did not start"));
66653
67138
  this.magmux = proc;
66654
67139
  try {
66655
- writeFileSync21(join47(this.ctlDir, "magmux.pid"), `${proc.pid}
67140
+ writeFileSync21(join48(this.ctlDir, "magmux.pid"), `${proc.pid}
66656
67141
  `, { mode: 384 });
66657
67142
  } catch {}
66658
67143
  updateRecord(this.root, this.paneId, { magmuxPid: proc.pid });
@@ -66660,6 +67145,7 @@ class PaneSessionImpl {
66660
67145
  proc.stderr?.on("data", (d) => this.onStderr(d));
66661
67146
  proc.on("exit", (code, sig) => this.onMagmuxExit(code, sig));
66662
67147
  this.ticker = setInterval(() => this.step(() => this.evaluate()), TICK_MS);
67148
+ this.gateTimer = setInterval(() => this.pollGate(), GATE_POLL_MS);
66663
67149
  if (this.o.timeoutMs !== undefined)
66664
67150
  this.timeoutTimer = setTimeout(() => this.step(() => this.fire("timeout")), Math.max(0, this.o.timeoutMs));
66665
67151
  await this.connectFirst();
@@ -66689,7 +67175,6 @@ class PaneSessionImpl {
66689
67175
  client.close();
66690
67176
  return;
66691
67177
  }
66692
- this.client = client;
66693
67178
  client.on("snapshot", (e) => this.onSnapshot(e));
66694
67179
  client.on("frame", (f) => this.onFrame(f));
66695
67180
  client.on("exit", (e) => void this.onPaneExit(e));
@@ -66701,8 +67186,15 @@ class PaneSessionImpl {
66701
67186
  const caps = await client.request({
66702
67187
  type: "capabilities"
66703
67188
  });
67189
+ this.supportsResize = caps.ok && Array.isArray(caps.result.verbs) && caps.result.verbs.includes("resize");
67190
+ if (client.isClosed)
67191
+ return;
67192
+ this.client = client;
67193
+ this.gateTrusted = false;
67194
+ this.gate = null;
67195
+ const magmuxVersion = caps.ok ? String(caps.result.version ?? "") : "";
66704
67196
  if (caps.ok) {
66705
- const v = String(caps.result.version ?? "");
67197
+ const v = magmuxVersion;
66706
67198
  if (caps.result.protocol !== 1 || !versionAtLeast(v, MIN_MAGMUX_VERSION)) {
66707
67199
  this.step(() => this.fire("pane_lost", {
66708
67200
  detail: `magmux_unavailable: magmux ${v || "?"} protocol ${caps.result.protocol ?? "?"}; panes need >= ${MIN_MAGMUX_VERSION}, protocol 1`
@@ -66710,6 +67202,9 @@ class PaneSessionImpl {
66710
67202
  return;
66711
67203
  }
66712
67204
  }
67205
+ this.lastSentSize = null;
67206
+ if (this.supportsResize && !samePaneSize(this.appliedSize, this.desiredSize))
67207
+ this.kickResize();
66713
67208
  await client.request({ type: "watch", pane: 0, mode: "frames", fps: WATCH_FPS });
66714
67209
  const list = await client.request({ type: "list" });
66715
67210
  const p0 = list.ok ? list.result.panes?.find((p) => p.pane === 0) : undefined;
@@ -66719,7 +67214,76 @@ class PaneSessionImpl {
66719
67214
  if (!reconnect)
66720
67215
  await this.nextFrame(500);
66721
67216
  this.onPaneExit({ exitCode: p0.exitCode ?? null, lastLine: "" });
67217
+ return;
67218
+ }
67219
+ if (!this.verifyGateBinding(client, list, magmuxVersion) || !list.ok)
67220
+ return;
67221
+ this.gateTrusted = true;
67222
+ this.onGateList(client, list.result);
67223
+ }
67224
+ verifyGateBinding(client, list, magmuxVersion) {
67225
+ if (client.isClosed || isTerminalPhase(this.phase))
67226
+ return false;
67227
+ if (!list.ok) {
67228
+ this.step(() => this.fire("pane_lost", {
67229
+ detail: `input_gate_unbound: magmux ${magmuxVersion || "?"} list failed (${list.code}: ${list.error}), so the binding of pane 0 is unknown`
67230
+ }));
67231
+ return false;
66722
67232
  }
67233
+ if (!list.result.panes?.some((p) => p.pane === 0))
67234
+ return false;
67235
+ const check = checkGateBinding(list.result, {
67236
+ sessionUuid: this.o.sessionUuid,
67237
+ transcriptPath: this.o.transcriptPath,
67238
+ magmuxVersion
67239
+ });
67240
+ if (!check.ok) {
67241
+ this.step(() => this.fire("pane_lost", { detail: check.detail }));
67242
+ return false;
67243
+ }
67244
+ return true;
67245
+ }
67246
+ pollGate() {
67247
+ const c = this.client;
67248
+ if (!c || !this.gateTrusted || this.gateInFlight)
67249
+ return;
67250
+ if (this.reaping || isTerminalPhase(this.phase))
67251
+ return;
67252
+ this.gateInFlight = true;
67253
+ c.request({ type: "list" }, GATE_REPLY_MS).then((r) => {
67254
+ this.gateInFlight = false;
67255
+ if (r.ok)
67256
+ this.onGateList(c, r.result);
67257
+ });
67258
+ }
67259
+ onGateList(c, result) {
67260
+ if (c !== this.client || !this.gateTrusted || isTerminalPhase(this.phase))
67261
+ return;
67262
+ const now = Date.now();
67263
+ const p0 = result.panes?.find((p) => p.pane === 0);
67264
+ if (typeof p0?.pid === "number" && !this.panePidValue)
67265
+ this.setPanePid(p0.pid);
67266
+ if (p0?.dead === true && !this.paneExited) {
67267
+ if (this.gateDeadSince === 0)
67268
+ this.gateDeadSince = now;
67269
+ else if (now - this.gateDeadSince >= GATE_DEAD_BACKSTOP_MS) {
67270
+ this.anomaly("exit_event_missing");
67271
+ this.onPaneExit({ exitCode: p0.exitCode ?? null, lastLine: "" });
67272
+ return;
67273
+ }
67274
+ } else
67275
+ this.gateDeadSince = 0;
67276
+ const prev = this.gate;
67277
+ this.gate = parseGateInput(result, now);
67278
+ const g = this.gate;
67279
+ if (prev?.ready !== g?.ready || prev?.reason !== g?.reason)
67280
+ this.step(() => this.evaluate());
67281
+ }
67282
+ gateNow() {
67283
+ const g = this.gate;
67284
+ if (!g || !this.client || !this.gateTrusted)
67285
+ return null;
67286
+ return Date.now() - g.receivedAt <= GATE_STALE_MS ? g : null;
66723
67287
  }
66724
67288
  onStderr(d) {
66725
67289
  if (this.stderrHead.length < 8192)
@@ -66742,7 +67306,10 @@ class PaneSessionImpl {
66742
67306
  onFrame(f) {
66743
67307
  if (f.pane !== undefined && f.pane !== 0)
66744
67308
  return;
67309
+ const seq = this.screen.seq;
66745
67310
  this.screen = applyFrame(this.screen, f);
67311
+ if (this.screen.seq !== seq && !this.inResizeQuiet(this.screen.changedAt))
67312
+ this.activityChangedAt = this.screen.changedAt;
66746
67313
  this.framePending = true;
66747
67314
  if (!this.claudeCodeVersion) {
66748
67315
  const m = this.screen.lines.join(`
@@ -66753,8 +67320,11 @@ class PaneSessionImpl {
66753
67320
  this.step(() => this.evaluate());
66754
67321
  }
66755
67322
  onDisconnected(client) {
66756
- if (this.client === client)
67323
+ if (this.client === client) {
66757
67324
  this.client = null;
67325
+ this.gate = null;
67326
+ this.gateTrusted = false;
67327
+ }
66758
67328
  if (this.reaping || !alive(this.magmux))
66759
67329
  return;
66760
67330
  if (isTerminalPhase(this.phase))
@@ -66946,11 +67516,15 @@ class PaneSessionImpl {
66946
67516
  if (!this.client)
66947
67517
  return;
66948
67518
  const idle = status === "idle";
66949
- const dialog = hasChoiceDialog(this.screen);
66950
- this.disagree("registry_idle_screen_booting", this.phase === "BOOTING" && idle && readBoot(this.screen).kind !== "repl", now);
66951
67519
  this.disagree("registry_idle_screen_working", this.phase === "RUNNING" && idle && isWorking(this.screen), now);
66952
- this.disagree("registry_waiting_no_dialog", status === "waiting" && !dialog, now);
66953
- this.disagree("screen_dialog_registry_not_waiting", dialog && (status === "idle" || status === "busy"), now);
67520
+ const g = this.gateNow();
67521
+ if (!g)
67522
+ return;
67523
+ this.disagree("registry_idle_gate_starting", this.phase === "BOOTING" && idle && (g.reason === "starting" || g.reason === "no_input_box"), now, REGISTRY_GATE_BOOT_DISAGREE_MS);
67524
+ this.disagree("registry_waiting_gate_no_dialog", status === "waiting" && g.ready, now);
67525
+ this.disagree("gate_dialog_registry_not_waiting", g.dialog !== null && RUNTIME_DIALOG_KINDS.has(g.dialog) && (idle || status === "busy"), now);
67526
+ this.disagree("gate_ready_box_not_located", g.ready && locateBox(this.screen) === null, now);
67527
+ this.disagree("gate_dialog_screen_unrecognised", (g.dialog === "permission" || g.dialog === "plan" || g.dialog === "question") && choiceDialog(this.screen) === null, now);
66954
67528
  }
66955
67529
  disagree(key, holds, now, forMs = REGISTRY_DISAGREE_MS) {
66956
67530
  if (!holds) {
@@ -66986,33 +67560,42 @@ class PaneSessionImpl {
66986
67560
  }
66987
67561
  evalBoot() {
66988
67562
  const now = Date.now();
66989
- const r = readBoot(this.screen);
66990
- if (r.kind !== this.bootKind) {
66991
- this.bootKind = r.kind;
66992
- this.bootKindSince = now;
66993
- }
66994
- const staticFor = this.screen.seq === 0 ? 0 : now - this.screen.changedAt;
66995
- const staticMs = this.t("bootStaticMs", BOOT_STATIC_MS);
66996
- if (r.kind === "repl" && now - this.bootKindSince >= this.t("replStableMs", REPL_STABLE_MS)) {
67563
+ const g = this.gateNow();
67564
+ if (g?.ready) {
66997
67565
  this.bootReady();
66998
67566
  return;
66999
67567
  }
67000
- if (r.kind === "dialog" && staticFor >= staticMs) {
67001
- this.fire("boot_dialog", { detail: r.text });
67002
- return;
67003
- }
67004
- if (r.kind === "choice" && staticFor >= staticMs) {
67005
- this.fire("boot_blocked", { detail: r.text });
67568
+ const staticFor = this.screen.seq === 0 ? 0 : now - this.screen.changedAt;
67569
+ const settled = staticFor >= this.t("bootStaticMs", BOOT_STATIC_MS);
67570
+ const block = g && settled ? this.bootBlock(g) : null;
67571
+ if (block) {
67572
+ this.fire(block.ev, { detail: block.detail });
67006
67573
  return;
67007
67574
  }
67008
67575
  if (now - this.startedAtMs >= (this.o.bootTimeoutMs ?? BOOT_TIMEOUT_MS)) {
67009
- const blocked = this.screen.seq > 0 && staticFor >= staticMs && r.kind !== "booting";
67576
+ const blocked = this.screen.seq > 0 && settled && (g?.reason?.startsWith("dialog:") === true || nameBootScreen(this.screen).kind !== "booting");
67577
+ const gate = g ? `gate: ${g.reason ?? "ready"}${g.detail ? ` (${g.detail})` : ""}` : "gate: unknown";
67010
67578
  this.fire("boot_deadline", {
67011
67579
  reason: blocked ? "boot_blocked" : "boot_timeout",
67012
- detail: screenTailOf(this.screen)
67580
+ detail: `${screenTailOf(this.screen)}
67581
+ ${gate}`
67013
67582
  });
67014
67583
  }
67015
67584
  }
67585
+ bootBlock(g) {
67586
+ if (g.dialog && BOOT_DIALOG_KINDS.has(g.dialog))
67587
+ return { ev: "boot_dialog", detail: dialogText(this.screen) };
67588
+ const named = nameBootScreen(this.screen);
67589
+ if (named.kind === "dialog")
67590
+ return { ev: "boot_dialog", detail: named.text };
67591
+ if (g.reason === "vim_mode")
67592
+ return { ev: "boot_blocked", detail: "vim editing mode is on (-- INSERT -- under the box)" };
67593
+ if (g.reason?.startsWith("dialog:"))
67594
+ return { ev: "boot_blocked", detail: screenText(this.screen) };
67595
+ if (named.kind === "choice" && g.reason !== null && GATE_KINDLESS_REASONS.has(g.reason))
67596
+ return { ev: "boot_blocked", detail: named.text };
67597
+ return null;
67598
+ }
67016
67599
  bootReady() {
67017
67600
  this.bootReadyAtMs = Date.now();
67018
67601
  if (this.queue.length === 0) {
@@ -67038,7 +67621,7 @@ class PaneSessionImpl {
67038
67621
  const head = this.queue.shift();
67039
67622
  if (isControl(head)) {
67040
67623
  this.exitRequested = true;
67041
- this.typeLine(head.trim(), true);
67624
+ this.submitControl(head.trim());
67042
67625
  return;
67043
67626
  }
67044
67627
  this.fire("admit");
@@ -67062,6 +67645,9 @@ class PaneSessionImpl {
67062
67645
  this.screenTurn = null;
67063
67646
  this.rewakeOfSettled = false;
67064
67647
  this.settledChatOffset = null;
67648
+ this.turnAcceptedAt = null;
67649
+ this.witnessMissingCounted = false;
67650
+ this.abandonedIndex = null;
67065
67651
  if (plan.mode === "refused" || plan.mode === "control") {
67066
67652
  this.anomaly(`delivery_refused:${plan.mode === "refused" ? plan.reason : "control"}`);
67067
67653
  this.admission = null;
@@ -67077,79 +67663,385 @@ class PaneSessionImpl {
67077
67663
  }
67078
67664
  const delivery = (plan.mode === "file" || plan.mode === "command") && plan.file && plan.fileContent !== undefined ? { file: plan.file, text: plan.fileContent } : null;
67079
67665
  this.follower.openTurn({ index, offset, witness: plan.witness, delivery });
67080
- this.admission = {
67081
- index,
67082
- text,
67083
- plan,
67084
- initial,
67085
- startedAt: Date.now(),
67086
- deliveredAt: null,
67087
- resent: false,
67088
- panelEscaped: false,
67089
- degraded: false
67090
- };
67091
- this.deliver(this.admission);
67666
+ const bound = initial ? this.o.admitTimeoutMs ?? ADMIT_TIMEOUT_MS : ADMIT_TIMEOUT_MS;
67667
+ const adm = newAdmission({ index, text, plan, initial, bound });
67668
+ this.admission = adm;
67669
+ this.runAdmission(adm);
67092
67670
  }
67093
- async deliver(adm) {
67094
- if (await this.typeAndSubmit(adm.plan))
67095
- adm.deliveredAt = Date.now();
67671
+ admitting(adm) {
67672
+ return this.admission === adm && this.phase === "ADMITTING";
67096
67673
  }
67097
- async typeAndSubmit(plan) {
67098
- if (plan.mode !== "typed" && plan.mode !== "file" && plan.mode !== "command")
67674
+ async runAdmission(adm) {
67675
+ try {
67676
+ while (this.admitting(adm) && adm.submittedUnproven === null && !adm.stopped) {
67677
+ if (Date.now() >= adm.deadline)
67678
+ return;
67679
+ if (adm.panel) {
67680
+ await this.pause(adm, TICK_MS);
67681
+ continue;
67682
+ }
67683
+ if (!this.client || !this.gateTrusted) {
67684
+ await this.awaitTrustedClient(adm);
67685
+ continue;
67686
+ }
67687
+ if (await this.beforeSend(adm) !== "send")
67688
+ continue;
67689
+ if (!this.admitting(adm) || !await this.attemptOnce(adm))
67690
+ return;
67691
+ }
67692
+ } finally {
67693
+ adm.inFlight = false;
67694
+ }
67695
+ }
67696
+ awaitTrustedClient(adm) {
67697
+ return this.waitFor(() => !this.admitting(adm) || !!this.client && this.gateTrusted, Math.max(0, adm.deadline - Date.now()));
67698
+ }
67699
+ async attemptOnce(adm) {
67700
+ const c = this.client;
67701
+ if (!c)
67702
+ return true;
67703
+ const step = adm.step;
67704
+ const shape = sendShape(step, adm.line);
67705
+ const rt = readyTimeoutFor(step, adm.line, adm.deadline - Date.now());
67706
+ if (rt === null)
67099
67707
  return false;
67100
- if (plan.mode === "command") {
67101
- await this.typeLine(plan.line, false);
67102
- await this.nextFrame(500);
67103
- const box = inputBox(this.screen);
67104
- const name = plan.line.match(/^\/\S+/)?.[0] ?? "";
67105
- if (box?.menuOpen && box.menuItem !== name)
67106
- await this.key("escape");
67107
- await this.key("enter");
67108
- } else {
67109
- await this.typeLine(plan.line, true);
67708
+ const timeout = sendBudgetMs({ ...shape.budget, readyTimeoutMs: rt }) + SEND_CLIENT_SLACK_MS;
67709
+ const sentAt = Date.now();
67710
+ adm.inFlight = true;
67711
+ adm.attempts++;
67712
+ let reply;
67713
+ try {
67714
+ reply = await c.request({ ...shape.msg, readyTimeoutMs: rt }, timeout);
67715
+ } finally {
67716
+ adm.inFlight = false;
67110
67717
  }
67718
+ const out = classifySendReply(reply);
67719
+ adm.last = out;
67720
+ this.noteLastSend(out, adm.attempts);
67721
+ if (!this.admitting(adm))
67722
+ return false;
67723
+ await this.onSendOutcome(adm, out, step, { client: c, until: sentAt + timeout });
67111
67724
  return true;
67112
67725
  }
67113
- async typeLine(text, enter) {
67114
- let why = "disconnected";
67115
- for (let attempt = 0;attempt < 2; attempt++) {
67116
- const c = this.client;
67117
- if (c) {
67118
- const r = await c.request({ type: "send", pane: 0, text, typed: true, enter }, 1e4);
67119
- if (r.ok)
67120
- return true;
67121
- why = r.code;
67122
- if (r.code === "client_timeout" || r.code === "client_lost") {
67123
- if (await this.landedAfterLostReply(text, enter))
67124
- return true;
67726
+ async onSendOutcome(adm, out, step, sent) {
67727
+ switch (out.kind) {
67728
+ case "accepted":
67729
+ this.onGateAccepted(adm, out);
67730
+ return;
67731
+ case "in_box":
67732
+ if (step !== "type") {
67733
+ this.anomaly("send_unexpected:in_box");
67734
+ this.stopAdmission(adm, "magmux answered in_box to a send that carried an Enter");
67735
+ return;
67125
67736
  }
67737
+ if (out.retyped)
67738
+ this.anomaly(`gate_retyped:${out.retypeCause ?? "unknown"}`);
67739
+ await this.menuCheck(adm);
67740
+ adm.step = "enter";
67741
+ return;
67742
+ case "busy":
67743
+ await this.onBusy(adm, out, step);
67744
+ return;
67745
+ case "not_accepted":
67746
+ await this.onNotAccepted(adm, out, step);
67747
+ return;
67748
+ case "bad_request":
67749
+ this.anomaly("send_bad_request");
67750
+ this.stopAdmission(adm, `send_bad_request: ${out.error}`);
67751
+ return;
67752
+ case "reply_lost":
67753
+ this.anomaly("send_reply_lost");
67754
+ if (step === "type") {
67755
+ adm.lostType = sent;
67756
+ markClearFirst(adm);
67757
+ return;
67758
+ }
67759
+ adm.submittedUnproven = "reply_lost";
67760
+ return;
67761
+ case "unsent":
67762
+ await this.pause(adm, BUSY_RETRY_MS);
67763
+ return;
67764
+ case "pane_gone":
67765
+ adm.stopped = true;
67766
+ return;
67767
+ case "unexpected":
67768
+ this.anomaly(`send_unexpected:${out.code}`);
67769
+ this.stopAdmission(adm, `send_unexpected: ${out.code}${out.error ? `: ${out.error}` : ""}`);
67770
+ return;
67771
+ default:
67772
+ assertNever(out);
67773
+ }
67774
+ }
67775
+ onGateAccepted(adm, out) {
67776
+ if (out.accept === "resent_enter")
67777
+ this.anomaly("gate_resent_enter");
67778
+ if (out.accept === "submitted_by_other")
67779
+ this.anomaly("gate_submitted_by_other");
67780
+ if (out.retyped)
67781
+ this.anomaly(`gate_retyped:${out.retypeCause ?? "unknown"}`);
67782
+ if (out.dismissed.length > 0)
67783
+ this.anomaly("gate_dismissed_unexpected");
67784
+ if (out.evidence === "transcript") {
67785
+ this.anomaly("gate_transcript_evidence");
67786
+ if (!this.witnessNow()) {
67787
+ this.anomaly("gate_transcript_evidence_unwitnessed");
67788
+ adm.submittedUnproven = "unconfirmed";
67789
+ return;
67126
67790
  }
67127
- await Bun.sleep(500);
67128
67791
  }
67129
- this.anomaly(`delivery_failed:${why}`);
67792
+ this.acceptAdmission(adm);
67793
+ }
67794
+ async onBusy(adm, out, step) {
67795
+ const reason = out.reason || "unknown";
67796
+ if (this.stopOnGateBlock(adm, reason, out.dialog))
67797
+ return;
67798
+ if (out.dialog === "select" && step === "enter") {
67799
+ this.noteOnce(adm, "gate_dialog_at_admission:select");
67800
+ adm.panel = { showingSince: null, escaped: false, entered: false };
67801
+ return;
67802
+ }
67803
+ if (out.dialog) {
67804
+ this.noteOnce(adm, `gate_dialog_at_admission:${out.dialog}`);
67805
+ } else if (out.typed) {
67806
+ this.anomaly("gate_typed_withheld");
67807
+ markClearFirst(adm);
67808
+ } else if (reason === "box_not_empty") {
67809
+ this.anomaly("gate_box_not_empty");
67810
+ markClearFirst(adm);
67811
+ } else {
67812
+ this.noteOnce(adm, `gate_busy:${reason}`);
67813
+ }
67814
+ await this.pause(adm, BUSY_RETRY_MS);
67815
+ }
67816
+ stopOnGateBlock(adm, reason, dialog) {
67817
+ if (dialog && BOOT_DIALOG_KINDS.has(dialog)) {
67818
+ this.anomaly(`gate_boot_dialog:${dialog}`);
67819
+ this.stopAdmission(adm, dialogText(this.screen) || `busy ${reason}`, "first_run_dialog");
67820
+ return true;
67821
+ }
67822
+ if (reason === "vim_mode") {
67823
+ this.anomaly("gate_vim_mode");
67824
+ this.stopAdmission(adm, "busy vim_mode: vim editing mode is on (-- INSERT -- under the box)");
67825
+ return true;
67826
+ }
67130
67827
  return false;
67131
67828
  }
67132
- async landedAfterLostReply(text, enter) {
67133
- await this.nextFrame(1000);
67134
- const head = text.slice(0, 40);
67135
- const box = inputBox(this.screen);
67136
- let landed = !!box && box.text.startsWith(head);
67137
- if (landed && enter)
67138
- await this.key("enter");
67139
- if (!landed && enter) {
67140
- this.follower.poll();
67141
- const cur = this.follower.view().current;
67142
- landed = !!cur && cur.index === this.turnIndex && cur.acceptedAt !== null || this.screen.lines.some((l) => l.startsWith(`\u276F ${head}`));
67829
+ async precheckGate(adm) {
67830
+ const g = this.gateNow();
67831
+ if (!g || g.ready)
67832
+ return "send";
67833
+ if (this.stopOnGateBlock(adm, g.reason ?? "unknown", g.dialog))
67834
+ return "stopped";
67835
+ const kind = g.dialog;
67836
+ if (kind !== "permission" && kind !== "question" && kind !== "plan")
67837
+ return "send";
67838
+ this.noteOnce(adm, `gate_dialog_at_admission:${kind}`);
67839
+ await this.waitFor(() => !this.admitting(adm) || this.gateNow()?.dialog !== kind, Math.max(0, adm.deadline - Date.now()));
67840
+ return "waited";
67841
+ }
67842
+ async beforeSend(adm) {
67843
+ if (adm.step === "enter")
67844
+ return "send";
67845
+ await this.clearLeftover(adm);
67846
+ if (!this.admitting(adm))
67847
+ return "stopped";
67848
+ return this.precheckGate(adm);
67849
+ }
67850
+ async onNotAccepted(adm, out, step) {
67851
+ switch (out.reason) {
67852
+ case "text_mismatch":
67853
+ if (out.maybeSubmitted) {
67854
+ this.anomaly("gate_maybe_submitted_by_other");
67855
+ adm.submittedUnproven = "maybe_submitted_by_other";
67856
+ return;
67857
+ }
67858
+ this.anomaly("gate_text_mismatch");
67859
+ markClearFirst(adm);
67860
+ if (adm.plan.mode === "command")
67861
+ adm.step = "type";
67862
+ await this.pause(adm, MISMATCH_SETTLE_MS);
67863
+ return;
67864
+ case "empty_box":
67865
+ if (step === "enter") {
67866
+ this.anomaly("gate_command_left_box");
67867
+ adm.submittedUnproven = "maybe_submitted_by_other";
67868
+ return;
67869
+ }
67870
+ this.anomaly("gate_empty_box");
67871
+ await this.pause(adm, BUSY_RETRY_MS);
67872
+ return;
67873
+ case "still_in_box":
67874
+ if (this.witnessNow()) {
67875
+ this.acceptAdmission(adm);
67876
+ return;
67877
+ }
67878
+ this.anomaly("gate_still_in_box");
67879
+ if (adm.stuckRetried) {
67880
+ adm.submittedUnproven = "still_in_box_twice";
67881
+ return;
67882
+ }
67883
+ adm.stuckRetried = true;
67884
+ await this.clearBox(adm);
67885
+ if (adm.plan.mode === "command")
67886
+ adm.step = "type";
67887
+ return;
67888
+ case "unconfirmed":
67889
+ this.anomaly("gate_unconfirmed");
67890
+ adm.submittedUnproven = "unconfirmed";
67891
+ if (step === "enter")
67892
+ adm.panel = { showingSince: null, escaped: false, entered: true };
67893
+ return;
67894
+ default:
67895
+ assertNever(out.reason);
67896
+ }
67897
+ }
67898
+ acceptAdmission(adm) {
67899
+ this.step(() => {
67900
+ if (this.admitting(adm))
67901
+ this.accept();
67902
+ });
67903
+ }
67904
+ stopAdmission(adm, detail, reason) {
67905
+ adm.stopped = true;
67906
+ this.step(() => {
67907
+ if (!this.admitting(adm))
67908
+ return;
67909
+ this.admission = null;
67910
+ if (adm.initial) {
67911
+ this.fire("admit_deadline", { reason, detail: `${detail}
67912
+ ${screenTailOf(this.screen)}` });
67913
+ return;
67914
+ }
67915
+ this.anomaly("send_not_accepted");
67916
+ this.abandonedIndex = adm.index;
67917
+ this.fire("admit_abandoned");
67918
+ });
67919
+ }
67920
+ witnessNow() {
67921
+ return this.witnessed(this.pollFollower(true).current);
67922
+ }
67923
+ noteOnce(adm, key) {
67924
+ if (adm.noted.has(key))
67925
+ return;
67926
+ adm.noted.add(key);
67927
+ this.anomaly(key);
67928
+ }
67929
+ pause(adm, ms) {
67930
+ return this.waitFor(() => !this.admitting(adm), ms);
67931
+ }
67932
+ freshReady(after) {
67933
+ const g = this.gateNow();
67934
+ return !!g && g.receivedAt > after && g.ready;
67935
+ }
67936
+ async clearBox(adm) {
67937
+ const at = Date.now();
67938
+ await this.key("ctrl-u");
67939
+ await this.waitFor(() => !this.admitting(adm) || this.freshReady(at), CLEAR_WAIT_MS);
67940
+ }
67941
+ async clearLeftover(adm) {
67942
+ const lost = adm.lostType;
67943
+ if (lost) {
67944
+ adm.lostType = null;
67945
+ if (lost.client !== this.client || lost.client.isClosed)
67946
+ await this.pause(adm, Math.max(0, lost.until - Date.now()));
67947
+ if (!this.admitting(adm))
67948
+ return;
67949
+ }
67950
+ if (adm.clearFirst)
67951
+ await this.clearOwnLeftover(adm);
67952
+ else if (this.gateNow()?.reason === "box_not_empty")
67953
+ await this.clearBox(adm);
67954
+ }
67955
+ async clearOwnLeftover(adm) {
67956
+ const after = adm.clearAfter + GATE_POLL_MS;
67957
+ const newer = () => {
67958
+ const g = this.gateNow();
67959
+ return g && g.receivedAt > after ? g : null;
67960
+ };
67961
+ await this.waitFor(() => !this.admitting(adm) || newer() !== null, CLEAR_WAIT_MS + GATE_POLL_MS);
67962
+ if (!this.admitting(adm))
67963
+ return;
67964
+ const g = newer();
67965
+ if (g?.ready) {
67966
+ adm.clearFirst = false;
67967
+ return;
67968
+ }
67969
+ if (g && (g.dialog !== null || g.reason === "vim_mode"))
67970
+ return;
67971
+ adm.clearFirst = false;
67972
+ await this.clearBox(adm);
67973
+ }
67974
+ async menuCheck(adm) {
67975
+ const name = adm.line.match(/^\/\S+/)?.[0] ?? "";
67976
+ await this.waitFor(() => {
67977
+ if (!this.admitting(adm))
67978
+ return true;
67979
+ const box = locateBox(this.screen);
67980
+ return !!box && locatedBoxText(this.screen, box).startsWith(name);
67981
+ }, 2 * TICK_MS);
67982
+ if (!this.admitting(adm))
67983
+ return;
67984
+ const box = locateBox(this.screen);
67985
+ if (!box) {
67986
+ this.anomaly("menu_unverified");
67987
+ return;
67988
+ }
67989
+ const menu = menuAbove(this.screen.lines, box.top);
67990
+ if (menu.menuOpen && menu.menuItem !== name)
67991
+ await this.key("escape");
67992
+ }
67993
+ noteLastSend(out, attempts) {
67994
+ const s = { kind: out.kind, attempts };
67995
+ switch (out.kind) {
67996
+ case "accepted":
67997
+ s.accept = out.accept;
67998
+ s.evidence = out.evidence;
67999
+ if (out.retypeCause)
68000
+ s.retypeCause = out.retypeCause;
68001
+ break;
68002
+ case "in_box":
68003
+ s.accept = "in_box";
68004
+ if (out.retypeCause)
68005
+ s.retypeCause = out.retypeCause;
68006
+ break;
68007
+ case "busy":
68008
+ case "not_accepted":
68009
+ s.reason = out.reason;
68010
+ break;
68011
+ case "bad_request":
68012
+ s.reason = "bad_request";
68013
+ break;
68014
+ case "reply_lost":
68015
+ case "unsent":
68016
+ case "pane_gone":
68017
+ case "unexpected":
68018
+ s.reason = out.code;
68019
+ break;
68020
+ default:
68021
+ assertNever(out);
68022
+ }
68023
+ this.lastSend = s;
68024
+ return s;
68025
+ }
68026
+ async submitControl(line) {
68027
+ const c = this.client;
68028
+ if (!c) {
68029
+ this.anomaly("control_send:unsent");
68030
+ return;
67143
68031
  }
67144
- if (landed)
67145
- this.anomaly("send_reply_lost");
67146
- return landed;
68032
+ const rt = SEND_READY_WAIT_DEFAULT_MS;
68033
+ const shape = sendShape("submit", line);
68034
+ const r = await c.request({ ...shape.msg, readyTimeoutMs: rt }, sendBudgetMs({ ...shape.budget, readyTimeoutMs: rt }) + SEND_CLIENT_SLACK_MS);
68035
+ const out = classifySendReply(r);
68036
+ const s = this.noteLastSend(out, 1);
68037
+ if (out.kind !== "accepted")
68038
+ this.anomaly(`control_send:${out.kind}${s.reason ? `:${s.reason}` : ""}`);
67147
68039
  }
67148
68040
  async key(k) {
67149
68041
  const c = this.client;
67150
68042
  if (!c)
67151
68043
  return false;
67152
- const r = await c.request({ type: "send", pane: 0, keys: [k], enter: false }, 1e4);
68044
+ const r = await c.request({ type: "send", pane: 0, keys: [k], enter: false, noGate: true }, 1e4);
67153
68045
  return r.ok;
67154
68046
  }
67155
68047
  nextFrame(ms) {
@@ -67169,139 +68061,134 @@ class PaneSessionImpl {
67169
68061
  const adm = this.admission;
67170
68062
  if (!adm)
67171
68063
  return;
67172
- const cur = view.current;
67173
- if (cur && cur.index === adm.index && cur.acceptedAt !== null) {
68064
+ if (this.witnessed(view.current)) {
67174
68065
  this.accept();
67175
68066
  return;
67176
68067
  }
67177
- const now = Date.now();
67178
- const since = adm.deliveredAt ? now - adm.deliveredAt : 0;
67179
- if (adm.deliveredAt && adm.plan.mode === "command" && this.panelCommand(adm, since))
67180
- return;
67181
- if (adm.deliveredAt && since >= this.t("resendAfterMs", RESEND_AFTER_MS))
67182
- this.nudge(adm);
67183
- if (adm.deliveredAt && (view.availability === "off" || since >= this.t("degradedEntryMs", DEGRADED_ENTRY_MS)))
67184
- this.maybeDegradedAccept(adm, view);
67185
- if (this.phase !== "ADMITTING")
68068
+ if (adm.panel)
68069
+ this.panelStep(adm);
68070
+ if (!this.admitting(adm))
67186
68071
  return;
67187
- if (now - adm.startedAt >= (this.o.admitTimeoutMs ?? ADMIT_TIMEOUT_MS))
68072
+ if (Date.now() >= adm.deadline && !adm.inFlight)
67188
68073
  this.admitExpired(adm);
67189
68074
  }
67190
- panelOpen() {
67191
- return !inputBox(this.screen) && !hasChoiceDialog(this.screen) && /Esc to cancel/.test(screenText(this.screen));
68075
+ panelShowing(g) {
68076
+ return !!g && (g.reason === "no_input_box" || g.dialog === "select") && /Esc to cancel/.test(flatText(this.screen.lines));
67192
68077
  }
67193
- panelCommand(adm, since) {
67194
- if (!adm.panelEscaped) {
67195
- if (since < PANEL_SETTLE_MS || !this.panelOpen())
67196
- return false;
67197
- adm.panelEscaped = true;
67198
- this.anomaly("panel_dismissed");
67199
- this.key("escape");
67200
- return true;
67201
- }
67202
- const box = inputBox(this.screen);
67203
- if (!box || box.text !== "" || this.panelOpen())
67204
- return false;
67205
- this.admission = null;
67206
- this.anomaly("panel_command");
67207
- if (adm.initial)
67208
- this.fire("admit_deadline", {
67209
- detail: `${adm.text.trim()} opened a panel and wrote no transcript record`
67210
- });
67211
- else
67212
- this.fire("admit_abandoned");
67213
- return true;
67214
- }
67215
- async awaitClearedBox(adm, head) {
67216
- const end = Date.now() + this.t("nudgeClearDeadlineMs", NUDGE_CLEAR_DEADLINE_MS);
67217
- for (;; ) {
67218
- if (this.admission !== adm || this.phase !== "ADMITTING")
67219
- return "gone";
67220
- const box = inputBox(this.screen);
67221
- if (box && box.text === "")
67222
- return "empty";
67223
- if (box && !box.text.startsWith(head))
67224
- return "other";
67225
- const left = end - Date.now();
67226
- if (left <= 0)
67227
- return box ? "line" : "other";
67228
- await this.nextFrame(left);
67229
- }
67230
- }
67231
- nudge(adm) {
67232
- const box = inputBox(this.screen);
67233
- const line = adm.plan.mode === "control" || adm.plan.mode === "refused" ? "" : adm.plan.line;
67234
- const head = line.slice(0, 40);
67235
- if (!adm.resent && box && line && box.text.startsWith(head)) {
67236
- adm.resent = true;
67237
- this.anomaly("resent_enter");
67238
- (async () => {
67239
- if (box.menuOpen)
67240
- await this.key("escape");
67241
- await this.key("ctrl-u");
67242
- const after = await this.awaitClearedBox(adm, head);
67243
- if (after === "gone")
67244
- return;
67245
- if (after === "empty") {
67246
- this.anomaly("retyped_prompt");
67247
- await this.typeAndSubmit(adm.plan);
67248
- } else if (after === "line") {
67249
- await this.key("enter");
67250
- } else {
67251
- this.anomaly("clear_failed");
67252
- }
67253
- })();
68078
+ panelStep(adm) {
68079
+ const p = adm.panel;
68080
+ if (!p)
67254
68081
  return;
67255
- }
67256
- if (!adm.panelEscaped && !box && !hasChoiceDialog(this.screen) && /Esc to cancel/.test(screenText(this.screen))) {
67257
- adm.panelEscaped = true;
67258
- this.anomaly("panel_dismissed");
68082
+ const g = this.gateNow();
68083
+ if (!p.escaped) {
68084
+ if (!this.panelShowing(g)) {
68085
+ p.showingSince = null;
68086
+ return;
68087
+ }
68088
+ const now = Date.now();
68089
+ p.showingSince ??= now;
68090
+ if (now - p.showingSince < PANEL_SETTLE_MS)
68091
+ return;
68092
+ p.escaped = true;
68093
+ this.anomaly(g?.dialog === "select" ? "picker_dismissed" : "panel_dismissed");
67259
68094
  this.key("escape");
67260
- }
67261
- }
67262
- maybeDegradedAccept(adm, view) {
67263
- if (view.availability === "ok")
67264
68095
  return;
67265
- const box = inputBox(this.screen);
67266
- if (!box || box.text !== "")
68096
+ }
68097
+ if (!g)
67267
68098
  return;
67268
- const line = adm.plan.mode === "control" || adm.plan.mode === "refused" ? "" : adm.plan.line;
67269
- const echo = this.screen.lines.some((l) => l.startsWith(`\u276F ${line.slice(0, 40)}`));
67270
- const changed = adm.deliveredAt !== null && this.screen.aboveBoxChangedAt > adm.deliveredAt;
67271
- if (!echo && !changed)
68099
+ if (g.ready) {
68100
+ this.admission = null;
68101
+ this.anomaly("panel_command");
68102
+ if (adm.initial)
68103
+ this.fire("admit_deadline", {
68104
+ detail: `${adm.text.trim()} opened a panel and wrote no transcript record`
68105
+ });
68106
+ else
68107
+ this.fire("admit_abandoned");
67272
68108
  return;
67273
- this.turnSource = "screen";
67274
- this.anomaly("degraded_mode");
67275
- this.screenTurn = { echo: line, acceptedAt: Date.now(), errorLogSize: this.errorLogSize() };
67276
- this.accept();
68109
+ }
68110
+ if (!p.entered && g.reason === "box_not_empty")
68111
+ adm.panel = null;
67277
68112
  }
67278
68113
  accept() {
67279
68114
  this.admission = null;
67280
68115
  this.initialDelivery = false;
67281
- this.lastChatSeenAt = Date.now();
68116
+ const now = Date.now();
68117
+ this.lastChatSeenAt = now;
68118
+ this.turnAcceptedAt = now;
68119
+ this.turnErrorLogSize = this.errorLogSize();
67282
68120
  this.fire("prompt_accepted");
67283
68121
  }
67284
68122
  admitExpired(adm) {
67285
68123
  this.admission = null;
67286
68124
  if (adm.initial) {
67287
- this.fire("admit_deadline", { detail: screenTailOf(this.screen) });
68125
+ this.fire("admit_deadline", {
68126
+ detail: `${this.notAcceptedLine(adm)}
68127
+ ${screenTailOf(this.screen)}`
68128
+ });
67288
68129
  return;
67289
68130
  }
67290
68131
  this.anomaly("send_not_accepted");
68132
+ this.abandonedIndex = adm.index;
67291
68133
  this.fire("admit_abandoned");
67292
68134
  }
67293
- screenFacts() {
68135
+ notAcceptedLine(adm) {
68136
+ const secs = Math.round((Date.now() - adm.startedAt) / 1000);
68137
+ switch (adm.submittedUnproven) {
68138
+ case "maybe_submitted_by_other":
68139
+ return "magmux could not tell whether the prompt was submitted (the box emptied without its Enter) and no transcript record of it appeared";
68140
+ case "unconfirmed":
68141
+ return `magmux not_accepted unconfirmed: Enter was pressed with no evidence it was submitted, and no transcript record appeared in ${secs} s`;
68142
+ case "reply_lost": {
68143
+ const code = adm.last?.kind === "reply_lost" ? adm.last.code : "client_lost";
68144
+ return `the magmux send reply was lost (${code}) and no transcript record of the prompt appeared in ${secs} s`;
68145
+ }
68146
+ case "still_in_box_twice":
68147
+ return "magmux not_accepted still_in_box: the prompt stayed in the input box after Enter, twice";
68148
+ case null:
68149
+ return `prompt not accepted in ${secs} s; ${describeLastReply(adm, this.gateNow())}`;
68150
+ default:
68151
+ return assertNever(adm.submittedUnproven);
68152
+ }
68153
+ }
68154
+ corroborationFacts() {
67294
68155
  if (!this.client)
67295
68156
  return null;
67296
- const box = inputBox(this.screen);
68157
+ const g = this.gateNow();
67297
68158
  return {
67298
- boxEmpty: !!box && box.text === "",
67299
- choiceDialog: hasChoiceDialog(this.screen),
67300
- working: isWorking(this.screen),
67301
- aboveBoxQuietMs: Date.now() - this.screen.aboveBoxChangedAt
68159
+ gate: g ? { ready: g.ready, dialog: g.dialog, reason: g.reason } : null,
68160
+ working: workingAboveBox(this.screen),
68161
+ aboveBoxQuietMs: this.aboveBoxQuietMs()
67302
68162
  };
67303
68163
  }
68164
+ aboveBoxQuietMs() {
68165
+ return locateBox(this.screen) ? Date.now() - this.screen.aboveBoxChangedAt : null;
68166
+ }
68167
+ checkUnwitnessed(view) {
68168
+ const at = this.turnAcceptedAt;
68169
+ if (at === null || this.rewakeOfSettled || this.witnessed(view.current))
68170
+ return;
68171
+ const since = Date.now() - at;
68172
+ const missing = view.availability === "absent" || view.availability === "empty";
68173
+ if (view.availability === "off" || missing && since >= this.t("degradedEntryMs", DEGRADED_ENTRY_MS)) {
68174
+ const d = this.currentDelivery;
68175
+ this.turnSource = "screen";
68176
+ this.anomaly("degraded_mode");
68177
+ this.screenTurn = {
68178
+ echo: d && d.mode !== "refused" ? d.line : "",
68179
+ acceptedAt: at,
68180
+ errorLogSize: this.turnErrorLogSize
68181
+ };
68182
+ return;
68183
+ }
68184
+ if (view.availability === "ok" && since >= ADMIT_TIMEOUT_MS && !this.witnessMissingCounted) {
68185
+ this.witnessMissingCounted = true;
68186
+ this.anomaly("witness_missing");
68187
+ }
68188
+ }
67304
68189
  evalRunning(view) {
68190
+ if (this.turnSource === "transcript")
68191
+ this.checkUnwitnessed(view);
67305
68192
  if (this.turnSource === "screen") {
67306
68193
  this.evalScreenTurn(view);
67307
68194
  return;
@@ -67321,22 +68208,24 @@ class PaneSessionImpl {
67321
68208
  if (this.detectBlocked(t))
67322
68209
  return;
67323
68210
  const now = Date.now();
68211
+ const transcriptQuietMs = now - (view.lastAppendAt ?? this.startedAtMs);
67324
68212
  const decision = decideSettle({
67325
68213
  turn: t,
67326
68214
  session: view.session,
67327
- transcriptQuietMs: now - (view.lastAppendAt ?? this.startedAtMs),
68215
+ transcriptQuietMs,
67328
68216
  chatQuietMs: now - this.lastChatSeenAt,
67329
- screen: this.screenFacts(),
68217
+ facts: this.corroborationFacts(),
67330
68218
  paneExited: false,
67331
68219
  secondaryQuietMs: this.secondaryQuietMs,
67332
- corroborationMs: this.corroborationMs
68220
+ corroborationMs: this.corroborationMs,
68221
+ corroborationGraceMs: this.t("gateGraceMs", GATE_GRACE_MS)
67333
68222
  });
67334
68223
  if (decision.settled) {
67335
68224
  this.settle(t, decision, null);
67336
68225
  return;
67337
68226
  }
67338
68227
  this.activity = decision.activity;
67339
- this.checkTurnEndMissing();
68228
+ this.checkTurnEndMissing(t, transcriptQuietMs);
67340
68229
  this.checkAgentCountMismatch(t);
67341
68230
  }
67342
68231
  checkEscape() {
@@ -67347,14 +68236,14 @@ class PaneSessionImpl {
67347
68236
  this.anomaly("escape_not_effective");
67348
68237
  this.escapeSentAt = null;
67349
68238
  }
67350
- checkTurnEndMissing() {
67351
- if (this.activity !== "finishing") {
68239
+ checkTurnEndMissing(t, transcriptQuietMs) {
68240
+ if (this.activity !== "finishing" || t.turnDurationAfterLast || t.interruptAfterLast) {
67352
68241
  this.turnEndMissing = false;
67353
68242
  return;
67354
68243
  }
67355
68244
  if (!this.client)
67356
68245
  return;
67357
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68246
+ const quiet = this.aboveBoxQuietMs() ?? transcriptQuietMs;
67358
68247
  const missing = quiet >= TURN_END_MISSING_FACTOR * this.secondaryQuietMs && !stopHookRunning(this.screen);
67359
68248
  if (missing && !this.turnEndMissing)
67360
68249
  this.anomaly("turn_end_record_missing");
@@ -67374,14 +68263,19 @@ class PaneSessionImpl {
67374
68263
  let kind = null;
67375
68264
  if (pending.name === "AskUserQuestion")
67376
68265
  kind = "question";
67377
- else if (this.client && hasChoiceDialog(this.screen))
67378
- kind = "permission";
68266
+ else {
68267
+ const dialog = this.gateNow()?.dialog ?? null;
68268
+ if (dialog === "permission" || dialog === "plan")
68269
+ kind = "permission";
68270
+ else if (dialog === "question")
68271
+ kind = "question";
68272
+ }
67379
68273
  if (!kind)
67380
68274
  return false;
67381
68275
  this.blockedToolId = pending.id;
67382
68276
  this.fire(kind === "question" ? "blocked_question" : "blocked_permission");
67383
68277
  this.activity = pending.name;
67384
- const text = blockText3(pending, choiceDialog(this.screen)?.text ?? null);
68278
+ const text = blockText3(pending, this.screen.seq > 0 ? dialogText(this.screen) || null : null);
67385
68279
  let v;
67386
68280
  try {
67387
68281
  v = this.o.onBlocked({ kind, tool: pending.name, text });
@@ -67394,6 +68288,16 @@ class PaneSessionImpl {
67394
68288
  }
67395
68289
  evalIdle(view) {
67396
68290
  const t = view.current;
68291
+ if (t && this.abandonedIndex !== null && t.index === this.abandonedIndex && t.acceptedAt !== null && !this.exitRequested) {
68292
+ this.abandonedIndex = null;
68293
+ this.anomaly("late_witness");
68294
+ this.turnAcceptedAt = Date.now();
68295
+ this.turnErrorLogSize = this.errorLogSize();
68296
+ this.lastChatSeenAt = Date.now();
68297
+ this.fire("rewake");
68298
+ this.activity = "thinking";
68299
+ return;
68300
+ }
67397
68301
  if (!t || this.settledChatOffset === null || this.exitRequested)
67398
68302
  return;
67399
68303
  if (t.lastChatOffset !== null && t.lastChatOffset > this.settledChatOffset) {
@@ -67472,10 +68376,10 @@ class PaneSessionImpl {
67472
68376
  return !!t && t.index === this.turnIndex && t.acceptedAt !== null;
67473
68377
  }
67474
68378
  screenSettleReady(view, answer, apiError) {
67475
- const box = inputBox(this.screen);
67476
- if (!box || box.text !== "" || hasChoiceDialog(this.screen) || isWorking(this.screen))
68379
+ const g = this.gateNow();
68380
+ if (!g || !g.ready || g.dialog !== null || workingAboveBox(this.screen) === true)
67477
68381
  return false;
67478
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68382
+ const quiet = this.aboveBoxQuietMs() ?? Date.now() - this.screen.changedAt;
67479
68383
  if (view.availability === "off" && quiet >= this.secondaryQuietMs)
67480
68384
  return true;
67481
68385
  return (!!answer || !!apiError) && quiet >= 2 * this.t("screenSettleQuietMs", SCREEN_SETTLE_QUIET_MS);
@@ -67554,7 +68458,7 @@ class PaneSessionImpl {
67554
68458
  session: view.session,
67555
68459
  transcriptQuietMs: Number.MAX_SAFE_INTEGER,
67556
68460
  chatQuietMs: Number.MAX_SAFE_INTEGER,
67557
- screen: null,
68461
+ facts: null,
67558
68462
  paneExited: true,
67559
68463
  secondaryQuietMs: this.secondaryQuietMs,
67560
68464
  corroborationMs: 0
@@ -67604,7 +68508,7 @@ ${screenTailOf(this.screen)}`
67604
68508
  snapshot() {
67605
68509
  const view = this.follower.view();
67606
68510
  const terminal = isTerminalPhase(this.phase);
67607
- const lastAct = Math.max(this.screen.changedAt, view.lastAppendAt ?? 0);
68511
+ const lastAct = Math.max(this.activityChangedAt, view.lastAppendAt ?? 0);
67608
68512
  const cur = view.current;
67609
68513
  const hasTranscript = view.availability === "ok" && view.usage.tokensIn + view.usage.tokensOut > 0;
67610
68514
  return {
@@ -67689,6 +68593,115 @@ ${screenTailOf(this.screen)}`
67689
68593
  return { ok: false, reason: "terminal" };
67690
68594
  return { ok: true, queued: this.queue.length };
67691
68595
  }
68596
+ async resize(cols, rows) {
68597
+ const size = clampPaneSize(cols, rows);
68598
+ if (isTerminalPhase(this.phase))
68599
+ return this.lastSizeOutcome();
68600
+ if (!this.supportsResize)
68601
+ throw new ContractErrorException("resize_unsupported", 'this magmux has no resize op (capabilities.verbs lacks "resize"); a newer magmux is needed');
68602
+ if (!this.client)
68603
+ return this.pendingOutcome(size);
68604
+ if (samePaneSize(this.appliedSize, size) && samePaneSize(this.desiredSize, size) && !this.resizeDraining)
68605
+ return { ...size, changed: false, seq: this.screen.seq };
68606
+ this.desiredSize = size;
68607
+ return new Promise((resolve, reject) => {
68608
+ this.resizeWaiters.push({ resolve, reject });
68609
+ this.kickResize();
68610
+ });
68611
+ }
68612
+ pendingOutcome(size) {
68613
+ this.desiredSize = { ...size };
68614
+ return { ...size, changed: true, seq: this.screen.seq, pending: true };
68615
+ }
68616
+ lastSizeOutcome() {
68617
+ return { cols: this.screen.cols, rows: this.screen.rows, changed: false, seq: this.screen.seq };
68618
+ }
68619
+ inResizeQuiet(at) {
68620
+ return this.resizeQuietFrom > 0 && at >= this.resizeQuietFrom && at < this.resizeQuietUntil;
68621
+ }
68622
+ extendResizeQuiet() {
68623
+ const now = Date.now();
68624
+ if (now >= this.resizeQuietUntil)
68625
+ this.resizeQuietFrom = now;
68626
+ this.resizeQuietUntil = Math.max(this.resizeQuietUntil, now + RESIZE_QUIET_MS);
68627
+ }
68628
+ resizeOwed() {
68629
+ return !!this.client && this.supportsResize && !isTerminalPhase(this.phase) && !samePaneSize(this.appliedSize, this.desiredSize) && !samePaneSize(this.lastSentSize, this.desiredSize);
68630
+ }
68631
+ kickResize() {
68632
+ if (this.resizeDraining)
68633
+ return;
68634
+ this.resizeDraining = true;
68635
+ this.drainResize().finally(() => {
68636
+ this.resizeDraining = false;
68637
+ if (this.resizeWaiters.length > 0 || this.resizeOwed())
68638
+ this.kickResize();
68639
+ });
68640
+ }
68641
+ async drainResize() {
68642
+ let first = true;
68643
+ while (first || this.resizeWaiters.length > 0 || this.resizeOwed()) {
68644
+ first = false;
68645
+ const batch = this.resizeWaiters.splice(0);
68646
+ const answer = (r) => {
68647
+ for (const w of batch)
68648
+ w.resolve(r);
68649
+ };
68650
+ const fail = (e) => {
68651
+ for (const w of batch)
68652
+ w.reject(e);
68653
+ };
68654
+ if (isTerminalPhase(this.phase)) {
68655
+ answer(this.lastSizeOutcome());
68656
+ continue;
68657
+ }
68658
+ const want = { ...this.desiredSize };
68659
+ const c = this.client;
68660
+ if (!c) {
68661
+ answer(this.pendingOutcome(want));
68662
+ continue;
68663
+ }
68664
+ if (!this.supportsResize) {
68665
+ fail(new ContractErrorException("resize_unsupported", "this magmux has no resize op"));
68666
+ continue;
68667
+ }
68668
+ this.lastSentSize = want;
68669
+ this.extendResizeQuiet();
68670
+ const r = await c.request({ type: "resize", pane: 0, cols: want.cols, rows: want.rows }, RESIZE_REPLY_MS);
68671
+ if (isTerminalPhase(this.phase)) {
68672
+ answer(this.lastSizeOutcome());
68673
+ continue;
68674
+ }
68675
+ if (!r.ok) {
68676
+ if (r.code === "client_lost" || r.code === "client_timeout") {
68677
+ this.appliedSize = null;
68678
+ this.extendResizeQuiet();
68679
+ }
68680
+ if (r.code === "client_lost" || r.code === "client_closed") {
68681
+ answer(this.pendingOutcome(this.desiredSize));
68682
+ continue;
68683
+ }
68684
+ fail(new ContractErrorException("delivery_unavailable", `magmux did not resize the pane: ${r.code}${r.error ? `: ${r.error}` : ""}`));
68685
+ continue;
68686
+ }
68687
+ const set = {
68688
+ cols: typeof r.result.cols === "number" ? r.result.cols : want.cols,
68689
+ rows: typeof r.result.rows === "number" ? r.result.rows : want.rows
68690
+ };
68691
+ this.appliedSize = set;
68692
+ if (r.result.changed !== true) {
68693
+ answer({ ...set, changed: false, seq: this.screen.seq });
68694
+ continue;
68695
+ }
68696
+ this.extendResizeQuiet();
68697
+ await this.waitFor(() => isTerminalPhase(this.phase) || this.screen.cols === set.cols && this.screen.rows === set.rows, RESIZE_SCREEN_MS);
68698
+ if (isTerminalPhase(this.phase)) {
68699
+ answer(this.lastSizeOutcome());
68700
+ continue;
68701
+ }
68702
+ answer({ ...set, changed: true, seq: this.screen.seq });
68703
+ }
68704
+ }
67692
68705
  turnAnswer(index) {
67693
68706
  const done = this.answers.get(index);
67694
68707
  if (done !== undefined)
@@ -67704,6 +68717,7 @@ ${screenTailOf(this.screen)}`
67704
68717
  diagnostics() {
67705
68718
  const cur = this.follower.view().current;
67706
68719
  const rec = this.registryRec;
68720
+ const g = this.gate;
67707
68721
  return {
67708
68722
  magmuxStderr: this.stderrHead,
67709
68723
  registry: rec ? { status: rec.status, waitingFor: rec.waitingFor, version: rec.version } : null,
@@ -67713,7 +68727,9 @@ ${screenTailOf(this.screen)}`
67713
68727
  linesReturned: cur.readCoverage.linesReturned,
67714
68728
  complete: cur.readCoverage.complete,
67715
68729
  reads: cur.readCoverage.reads
67716
- } : null
68730
+ } : null,
68731
+ gate: g && this.client ? { ready: g.ready, reason: g.reason, detail: g.detail, ageMs: Date.now() - g.receivedAt } : null,
68732
+ lastSend: this.lastSend ? { ...this.lastSend } : null
67717
68733
  };
67718
68734
  }
67719
68735
  cancel() {
@@ -67770,6 +68786,9 @@ ${screenTailOf(this.screen)}`
67770
68786
  if (this.ticker)
67771
68787
  clearInterval(this.ticker);
67772
68788
  this.ticker = null;
68789
+ if (this.gateTimer)
68790
+ clearInterval(this.gateTimer);
68791
+ this.gateTimer = null;
67773
68792
  await this.captureGroupBeforeReap();
67774
68793
  const c = this.client;
67775
68794
  if (c && !c.isClosed && !this.paneClosed) {
@@ -67881,6 +68900,94 @@ function signalQuietly(target, sig) {
67881
68900
  process.kill(target, sig);
67882
68901
  } catch {}
67883
68902
  }
68903
+ function launchSize(o) {
68904
+ return o.initialSize ? clampPaneSize(o.initialSize.cols, o.initialSize.rows) : { cols: DEFAULT_PANE_COLS, rows: DEFAULT_PANE_ROWS };
68905
+ }
68906
+ function newAdmission(a) {
68907
+ const now = Date.now();
68908
+ return {
68909
+ index: a.index,
68910
+ text: a.text,
68911
+ plan: a.plan,
68912
+ line: a.plan.line,
68913
+ initial: a.initial,
68914
+ startedAt: now,
68915
+ deadline: now + a.bound,
68916
+ step: a.plan.mode === "command" ? "type" : "submit",
68917
+ inFlight: false,
68918
+ attempts: 0,
68919
+ submittedUnproven: null,
68920
+ stuckRetried: false,
68921
+ panel: null,
68922
+ stopped: false,
68923
+ clearFirst: false,
68924
+ clearAfter: 0,
68925
+ lostType: null,
68926
+ last: null,
68927
+ noted: new Set
68928
+ };
68929
+ }
68930
+ function markClearFirst(adm) {
68931
+ adm.clearFirst = true;
68932
+ adm.clearAfter = Date.now();
68933
+ }
68934
+ function sendShape(step, line) {
68935
+ switch (step) {
68936
+ case "submit":
68937
+ return {
68938
+ msg: { type: "send", pane: 0, text: line, typed: true, enter: true },
68939
+ budget: { text: line, keys: 1, submit: true }
68940
+ };
68941
+ case "type":
68942
+ return {
68943
+ msg: { type: "send", pane: 0, text: line, typed: true, enter: false },
68944
+ budget: { text: line, keys: 0, submit: false }
68945
+ };
68946
+ case "enter":
68947
+ return {
68948
+ msg: { type: "send", pane: 0, keys: ["enter"], enter: false },
68949
+ budget: { text: "", keys: 1, submit: true }
68950
+ };
68951
+ default:
68952
+ return assertNever(step);
68953
+ }
68954
+ }
68955
+ function fixedSendMs(step, line) {
68956
+ const rt = SEND_READY_WAIT_DEFAULT_MS;
68957
+ return sendBudgetMs({ ...sendShape(step, line).budget, readyTimeoutMs: rt }) - rt;
68958
+ }
68959
+ function readyTimeoutFor(step, line, leftMs) {
68960
+ const reserve = step === "type" ? fixedSendMs("enter", line) + SEND_CLIENT_SLACK_MS + MIN_READY_TIMEOUT_MS : 0;
68961
+ const rt = Math.min(SEND_READY_WAIT_DEFAULT_MS, leftMs - fixedSendMs(step, line) - SEND_CLIENT_SLACK_MS - reserve);
68962
+ return rt < MIN_READY_TIMEOUT_MS ? null : Math.floor(rt);
68963
+ }
68964
+ function describeLastReply(adm, gate) {
68965
+ const o = adm.last;
68966
+ if (!o) {
68967
+ const g = gate ? gate.ready ? "ready" : gate.reason ?? "not ready" : "unknown";
68968
+ return adm.attempts === 0 ? `no gated send fitted before the deadline (gate: ${g})` : `no magmux reply (gate: ${g})`;
68969
+ }
68970
+ switch (o.kind) {
68971
+ case "busy":
68972
+ return `last magmux reply: busy ${o.reason || "unknown"}${o.typed ? " (typed, Enter withheld)" : ""}${gate?.detail ? ` (${gate.detail})` : ""}`;
68973
+ case "not_accepted":
68974
+ return `last magmux reply: not_accepted ${o.reason}`;
68975
+ case "accepted":
68976
+ return `last magmux reply: ok ${o.accept} (${o.evidence})`;
68977
+ case "in_box":
68978
+ return "last magmux reply: in_box (the command was typed, its Enter never sent)";
68979
+ case "bad_request":
68980
+ return `last magmux reply: bad_request ${o.error}`;
68981
+ case "reply_lost":
68982
+ case "unsent":
68983
+ case "pane_gone":
68984
+ return `last magmux reply: ${o.code}`;
68985
+ case "unexpected":
68986
+ return `last magmux reply: ${o.code}`;
68987
+ default:
68988
+ return assertNever(o);
68989
+ }
68990
+ }
67884
68991
  function isControl(text) {
67885
68992
  const t = text.trim();
67886
68993
  return t === "/exit" || t === "/quit";
@@ -67899,13 +69006,19 @@ function preparePane(o, root, parentEnv) {
67899
69006
  let dirs = null;
67900
69007
  try {
67901
69008
  const paneId = mintPaneId(o.kind, o.label);
67902
- const sockPath = sockPathFor(root, paneId);
67903
- const cwd = realpathSync3(o.cwd);
69009
+ const sockPath = sockPathOf(root, paneId);
69010
+ const cwd = realpathSync4(o.cwd);
67904
69011
  const launch = o.claudishSpawn ?? resolveClaudishLaunch(parentEnv);
67905
69012
  dirs = createLaunchDirs(root);
67906
69013
  const argv = buildClaudishPaneArgv(o.spawnModel, o.sessionUuid, dirs.turnDir, o.callerFlags);
67907
69014
  writeLauncherFiles(dirs.ctlDir, launcherScript(cwd, launch, argv));
67908
- const { magmuxEnv } = buildPaneEnv({ parentEnv, slotEnv: o.slotEnv, cwd, ctlDir: dirs.ctlDir });
69015
+ const { magmuxEnv } = buildPaneEnv({
69016
+ parentEnv,
69017
+ slotEnv: o.slotEnv,
69018
+ cwd,
69019
+ ctlDir: dirs.ctlDir,
69020
+ ...launchSize(o)
69021
+ });
67909
69022
  return { paneId, sockPath, cwd, ...dirs, magmuxEnv };
67910
69023
  } catch (e) {
67911
69024
  for (const d of dirs ? [dirs.ctlDir, dirs.turnDir] : [])
@@ -67974,21 +69087,38 @@ async function startPaneSession(o) {
67974
69087
  await session.start({ magmuxBinary: magmux.binary, env: p.magmuxEnv, cwd: p.cwd });
67975
69088
  return session;
67976
69089
  }
67977
- var BOOT_TIMEOUT_MS = 90000, ADMIT_TIMEOUT_MS = 30000, RESEND_AFTER_MS = 1e4, DEGRADED_ENTRY_MS = 1e4, SCREEN_SETTLE_QUIET_MS = 3000, PANEL_SETTLE_MS = 500, REPL_STABLE_MS = 3000, NUDGE_CLEAR_DEADLINE_MS = 3000, BOOT_STATIC_MS = 5000, TICK_MS = 250, WATCH_FPS = 4, POLL_BACKSTOP_MS = 1000, TURN_END_MISSING_FACTOR = 3, REGISTRY_READ_MS = 250, REGISTRY_DISAGREE_MS = 2000, REGISTRY_MISSING_MS = 5000, MAX_ANOMALY_KEYS = 64, KEPT_ANSWERS = 4, EVENT_REASON, VERDICT_EVENT, PaneStartError;
69090
+ var BOOT_TIMEOUT_MS = 90000, ADMIT_TIMEOUT_MS = 30000, DEGRADED_ENTRY_MS = 1e4, SCREEN_SETTLE_QUIET_MS = 3000, PANEL_SETTLE_MS = 500, SEND_CLIENT_SLACK_MS = 2000, MIN_READY_TIMEOUT_MS = 1000, BUSY_RETRY_MS = 250, MISMATCH_SETTLE_MS = 1000, CLEAR_WAIT_MS = 1000, BOOT_STATIC_MS = 5000, TICK_MS = 250, WATCH_FPS = 4, POLL_BACKSTOP_MS = 1000, TURN_END_MISSING_FACTOR = 3, RESIZE_REPLY_MS = 1e4, RESIZE_SCREEN_MS = 1000, GATE_POLL_MS = 250, GATE_REPLY_MS = 1000, GATE_STALE_MS = 1000, GATE_GRACE_MS, GATE_DEAD_BACKSTOP_MS = 1000, GATE_KINDLESS_REASONS, RUNTIME_DIALOG_KINDS, REGISTRY_READ_MS = 250, REGISTRY_DISAGREE_MS = 2000, REGISTRY_GATE_BOOT_DISAGREE_MS = 5000, REGISTRY_MISSING_MS = 5000, MAX_ANOMALY_KEYS = 64, KEPT_ANSWERS = 4, EVENT_REASON, VERDICT_EVENT, PaneStartError;
67978
69091
  var init_pane_session = __esm(() => {
67979
69092
  init_parent_session();
67980
69093
  init_process_tree();
67981
69094
  init_redact();
69095
+ init_box_locate();
69096
+ init_contract();
69097
+ init_gate_binding();
69098
+ init_input_gate();
67982
69099
  init_magmux_client();
67983
69100
  init_pane_launch();
67984
69101
  init_pane_registry();
67985
69102
  init_process_identity();
67986
69103
  init_prompt_delivery();
67987
69104
  init_screen_classifier();
69105
+ init_screen_model();
67988
69106
  init_session_registry();
67989
69107
  init_settle_rule();
67990
69108
  init_slot_state();
67991
69109
  init_transcript_follower();
69110
+ GATE_GRACE_MS = SECONDARY_QUIET_MS;
69111
+ GATE_KINDLESS_REASONS = new Set([
69112
+ "starting",
69113
+ "terminal_not_ready",
69114
+ "no_input_box"
69115
+ ]);
69116
+ RUNTIME_DIALOG_KINDS = new Set([
69117
+ "permission",
69118
+ "question",
69119
+ "plan",
69120
+ "select"
69121
+ ]);
67992
69122
  EVENT_REASON = {
67993
69123
  boot_dialog: "first_run_dialog",
67994
69124
  boot_blocked: "boot_blocked",
@@ -68040,10 +69170,10 @@ __export(exports_session_discovery, {
68040
69170
  transcriptPathFor: () => transcriptPathFor
68041
69171
  });
68042
69172
  import { execFile as execFile3, execFileSync as execFileSync3 } from "child_process";
68043
- import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, readdirSync as readdirSync8, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
68044
- import { basename as basename2, join as join48 } from "path";
69173
+ import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, readdirSync as readdirSync8, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
69174
+ import { basename as basename3, join as join49 } from "path";
68045
69175
  function projectsDir(env = process.env) {
68046
- return join48(claudeConfigDir(env), "projects");
69176
+ return join49(claudeConfigDir(env), "projects");
68047
69177
  }
68048
69178
  function slugForPath(absPath) {
68049
69179
  return projectDirNameFor(absPath);
@@ -68051,9 +69181,9 @@ function slugForPath(absPath) {
68051
69181
  function transcriptPathFor(cwd, sessionUuid, projectsRoot = projectsDir()) {
68052
69182
  let real = cwd;
68053
69183
  try {
68054
- real = realpathSync4(cwd);
69184
+ real = realpathSync5(cwd);
68055
69185
  } catch {}
68056
- return join48(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
69186
+ return join49(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
68057
69187
  }
68058
69188
  function isAgentSession(row) {
68059
69189
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -68100,7 +69230,7 @@ function projectDirs() {
68100
69230
  }
68101
69231
  }
68102
69232
  function sessionsIn(dirName) {
68103
- const dir = join48(projectsDir(), dirName);
69233
+ const dir = join49(projectsDir(), dirName);
68104
69234
  let names;
68105
69235
  try {
68106
69236
  names = readdirSync8(dir).filter((n) => n.endsWith(".jsonl"));
@@ -68109,13 +69239,13 @@ function sessionsIn(dirName) {
68109
69239
  }
68110
69240
  const rows = [];
68111
69241
  for (const n of names) {
68112
- const file = join48(dir, n);
69242
+ const file = join49(dir, n);
68113
69243
  try {
68114
69244
  const st = statSync11(file);
68115
69245
  if (st.size === 0)
68116
69246
  continue;
68117
69247
  const row = {
68118
- id: basename2(n, ".jsonl"),
69248
+ id: basename3(n, ".jsonl"),
68119
69249
  file,
68120
69250
  mtimeMs: st.mtimeMs,
68121
69251
  birthtimeMs: st.birthtimeMs,
@@ -68217,7 +69347,7 @@ function discoverWorktreeGroups(repo) {
68217
69347
  if (hit) {
68218
69348
  path = hit.path;
68219
69349
  live = true;
68220
- name = hit.path === repo.root ? "(root)" : basename2(hit.path);
69350
+ name = hit.path === repo.root ? "(root)" : basename3(hit.path);
68221
69351
  } else if (at !== -1 && dir.startsWith(rootSlug)) {
68222
69352
  path = null;
68223
69353
  live = false;
@@ -68487,7 +69617,7 @@ var init_session_discovery = __esm(() => {
68487
69617
  // src/team-orchestrator.ts
68488
69618
  import { randomBytes as randomBytes8, randomUUID as randomUUID5 } from "crypto";
68489
69619
  import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync31, readdirSync as readdirSync9, statSync as statSync12, writeFileSync as writeFileSync22 } from "fs";
68490
- import { basename as basename3, join as join49, resolve as resolve4 } from "path";
69620
+ import { basename as basename4, join as join50, resolve as resolve5 } from "path";
68491
69621
  function slotTerminal(e) {
68492
69622
  if (!e.session)
68493
69623
  return true;
@@ -68524,13 +69654,13 @@ function addressRun(path, runId) {
68524
69654
  pruneRuns();
68525
69655
  if (runId) {
68526
69656
  const run = teamRuns.get(runId) ?? null;
68527
- if (run && path !== undefined && resolve4(path) !== run.path)
69657
+ if (run && path !== undefined && resolve5(path) !== run.path)
68528
69658
  return null;
68529
69659
  return run;
68530
69660
  }
68531
69661
  if (path === undefined)
68532
69662
  return null;
68533
- const id = newestRunByPath.get(resolve4(path));
69663
+ const id = newestRunByPath.get(resolve5(path));
68534
69664
  return id ? teamRuns.get(id) ?? null : null;
68535
69665
  }
68536
69666
  function activeRunAt(path) {
@@ -68556,7 +69686,7 @@ function notableAnomalies(snap) {
68556
69686
  return snap.anomalies.filter((a) => a.startsWith("background_shell_open") || a.startsWith("turn_end_record_missing") || a.startsWith("agent_count_mismatch") || a.startsWith("prompt_not_read"));
68557
69687
  }
68558
69688
  function upstreamLogPath(path, id) {
68559
- return join49(path, "errors", `${id}-upstream.jsonl`);
69689
+ return join50(path, "errors", `${id}-upstream.jsonl`);
68560
69690
  }
68561
69691
  function neverSpawnedRow(e, m) {
68562
69692
  return {
@@ -68649,9 +69779,9 @@ function teamRunRowFromDisk(path, status) {
68649
69779
  const settled = slots.length > 0 && slots.every((s) => isTerminalState(s.state));
68650
69780
  const completions = Object.values(status.models ?? {}).map((m) => m?.completedAt ? Date.parse(m.completedAt) : Number.NaN).filter(Number.isFinite);
68651
69781
  return {
68652
- run_id: status.runId ?? basename3(path),
69782
+ run_id: status.runId ?? basename4(path),
68653
69783
  path,
68654
- kind: status.kind ?? (basename3(path) === "judging" ? "judge" : "run"),
69784
+ kind: status.kind ?? (basename4(path) === "judging" ? "judge" : "run"),
68655
69785
  started_at: status.startedAt,
68656
69786
  finished_at: settled ? new Date(completions.length ? Math.max(...completions) : statusMtimeMs(path) ?? Date.parse(status.startedAt)).toISOString() : null,
68657
69787
  state: settled ? "SETTLED" : "ACTIVE",
@@ -68661,14 +69791,14 @@ function teamRunRowFromDisk(path, status) {
68661
69791
  }
68662
69792
  function statusMtimeMs(path) {
68663
69793
  try {
68664
- return statSync12(join49(path, "status.json")).mtimeMs;
69794
+ return statSync12(join50(path, "status.json")).mtimeMs;
68665
69795
  } catch {
68666
69796
  return null;
68667
69797
  }
68668
69798
  }
68669
69799
  function readManifestOrNull(path) {
68670
69800
  try {
68671
- return JSON.parse(readFileSync31(join49(path, "manifest.json"), "utf-8"));
69801
+ return JSON.parse(readFileSync31(join50(path, "manifest.json"), "utf-8"));
68672
69802
  } catch {
68673
69803
  return null;
68674
69804
  }
@@ -68702,10 +69832,10 @@ async function cancelTeamRun(path, slot, runId) {
68702
69832
  function neverSpawnedCapture() {
68703
69833
  return {
68704
69834
  seq: 0,
68705
- cols: 160,
68706
- rows: 50,
69835
+ cols: DEFAULT_PANE_COLS,
69836
+ rows: DEFAULT_PANE_ROWS,
68707
69837
  cursor: { x: 0, y: 0 },
68708
- lines: Array.from({ length: 50 }, () => ""),
69838
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
68709
69839
  final: true
68710
69840
  };
68711
69841
  }
@@ -68722,6 +69852,18 @@ function captureTeamSlot(path, slot, sinceSeq, spans, runId) {
68722
69852
  }
68723
69853
  return e.session.capture(sinceSeq, { spans: spans === true });
68724
69854
  }
69855
+ async function resizeTeamSlot(path, slot, cols, rows, runId) {
69856
+ const run = addressRun(path, runId);
69857
+ if (!run)
69858
+ throw unknownRun(path, runId);
69859
+ const e = run.slots.get(slot);
69860
+ if (!e)
69861
+ throw new ContractErrorException("unknown_slot", `run ${run.runId} has no slot ${JSON.stringify(slot)}`);
69862
+ const where = { run_id: run.runId, path: run.path, slot };
69863
+ if (!e.session)
69864
+ return { ...where, cols: DEFAULT_PANE_COLS, rows: DEFAULT_PANE_ROWS, changed: false, seq: 0 };
69865
+ return { ...where, ...await e.session.resize(cols, rows) };
69866
+ }
68725
69867
  function teamLiveMaps(path, runId) {
68726
69868
  const run = addressRun(path, runId);
68727
69869
  if (!run)
@@ -68860,7 +70002,7 @@ function persistErrorLog(errorLogPath, header, sections) {
68860
70002
  } catch {}
68861
70003
  }
68862
70004
  function validateSessionPath(sessionPath) {
68863
- const resolved = resolve4(sessionPath);
70005
+ const resolved = resolve5(sessionPath);
68864
70006
  const cwd = process.cwd();
68865
70007
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68866
70008
  throw new Error(`Session path must be within current directory: ${sessionPath}`);
@@ -68868,7 +70010,7 @@ function validateSessionPath(sessionPath) {
68868
70010
  return resolved;
68869
70011
  }
68870
70012
  function readTeamInputFile(inputPath) {
68871
- const resolved = resolve4(inputPath);
70013
+ const resolved = resolve5(inputPath);
68872
70014
  const cwd = process.cwd();
68873
70015
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68874
70016
  throw new Error(`Input file must be within current directory: ${inputPath}`);
@@ -68886,14 +70028,14 @@ function setupSession(sessionPath, models, input) {
68886
70028
  if (models.length === 0) {
68887
70029
  throw new Error("At least one model is required");
68888
70030
  }
68889
- if (existsSync31(join49(sessionPath, "manifest.json"))) {
70031
+ if (existsSync31(join50(sessionPath, "manifest.json"))) {
68890
70032
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
68891
70033
  }
68892
- mkdirSync21(join49(sessionPath, "work"), { recursive: true });
68893
- mkdirSync21(join49(sessionPath, "errors"), { recursive: true });
70034
+ mkdirSync21(join50(sessionPath, "work"), { recursive: true });
70035
+ mkdirSync21(join50(sessionPath, "errors"), { recursive: true });
68894
70036
  if (input !== undefined) {
68895
- writeFileSync22(join49(sessionPath, "input.md"), input, "utf-8");
68896
- } else if (!existsSync31(join49(sessionPath, "input.md"))) {
70037
+ writeFileSync22(join50(sessionPath, "input.md"), input, "utf-8");
70038
+ } else if (!existsSync31(join50(sessionPath, "input.md"))) {
68897
70039
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
68898
70040
  }
68899
70041
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -68910,9 +70052,9 @@ function setupSession(sessionPath, models, input) {
68910
70052
  model: models[i],
68911
70053
  assignedAt: now
68912
70054
  };
68913
- mkdirSync21(join49(sessionPath, "work", anonId), { recursive: true });
70055
+ mkdirSync21(join50(sessionPath, "work", anonId), { recursive: true });
68914
70056
  }
68915
- writeFileSync22(join49(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
70057
+ writeFileSync22(join50(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
68916
70058
  const status = {
68917
70059
  startedAt: now,
68918
70060
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -68927,7 +70069,7 @@ function setupSession(sessionPath, models, input) {
68927
70069
  }
68928
70070
  ]))
68929
70071
  };
68930
- writeFileSync22(join49(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
70072
+ writeFileSync22(join50(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
68931
70073
  return manifest;
68932
70074
  }
68933
70075
  function assertValidRequirePattern(pattern) {
@@ -68955,14 +70097,14 @@ async function preflightTeamRun(opts) {
68955
70097
  throw new Error(`invalid_args: ${refusal}`);
68956
70098
  }
68957
70099
  await assertMagmuxAvailable();
68958
- assertNoActiveRun(resolve4(opts.path));
70100
+ assertNoActiveRun(resolve5(opts.path));
68959
70101
  const root = sockRootFor(opts.parentEnv ?? process.env);
68960
70102
  const live = livePaneCount(root);
68961
70103
  if (live + opts.slots > MAX_LIVE_PANES)
68962
70104
  throw new Error(`pane_limit: ${live} live panes for this user, ${opts.slots} more would exceed ${MAX_LIVE_PANES}`);
68963
70105
  }
68964
70106
  function mintRunId(path, startMs) {
68965
- return `${basename3(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
70107
+ return `${basename4(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
68966
70108
  }
68967
70109
  function startModels(sessionPath, opts = {}) {
68968
70110
  const flight = { aborted: false };
@@ -68986,10 +70128,10 @@ async function startModelsIn(flight, sessionPath, opts) {
68986
70128
  const flagCheck = checkChildFlags(claudeFlags);
68987
70129
  if (!flagCheck.ok)
68988
70130
  throw new Error(`invalid_args: ${flagCheck.message}`);
68989
- const path = resolve4(sessionPath);
68990
- const manifest = JSON.parse(readFileSync31(join49(path, "manifest.json"), "utf-8"));
68991
- const statusPath = join49(path, "status.json");
68992
- const inputContent = readFileSync31(join49(path, "input.md"), "utf-8");
70131
+ const path = resolve5(sessionPath);
70132
+ const manifest = JSON.parse(readFileSync31(join50(path, "manifest.json"), "utf-8"));
70133
+ const statusPath = join50(path, "status.json");
70134
+ const inputContent = readFileSync31(join50(path, "input.md"), "utf-8");
68993
70135
  const readAvailable = !flagsRemoveRead(claudeFlags);
68994
70136
  const refusal = deliveryRefusal(inputContent, readAvailable);
68995
70137
  if (refusal)
@@ -69027,13 +70169,13 @@ async function startModelsIn(flight, sessionPath, opts) {
69027
70169
  mkdirSync21(statsDir(path), { recursive: true });
69028
70170
  const entries = new Map;
69029
70171
  let run = null;
69030
- const errorLogPathOf = (id) => join49(path, "errors", `${id}.log`);
70172
+ const errorLogPathOf = (id) => join50(path, "errors", `${id}.log`);
69031
70173
  const upstreamLogOf = (id) => upstreamLogPath(path, id);
69032
70174
  const commandOf = (e) => ["claudish", "-i", "--model", e.spawnModel ?? e.model, "-y", "--quiet", ...claudeFlags].join(" ");
69033
70175
  function writeResponse(e, text) {
69034
70176
  e.answer = text;
69035
70177
  try {
69036
- writeFileSync22(join49(path, `response-${e.id}.md`), text, "utf-8");
70178
+ writeFileSync22(join50(path, `response-${e.id}.md`), text, "utf-8");
69037
70179
  } catch (err) {
69038
70180
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
69039
70181
  }
@@ -69350,7 +70492,7 @@ async function startModelsIn(flight, sessionPath, opts) {
69350
70492
  done.catch(() => {});
69351
70493
  return {
69352
70494
  runId,
69353
- teamSessionId: basename3(path),
70495
+ teamSessionId: basename4(path),
69354
70496
  sessionPath: path,
69355
70497
  slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
69356
70498
  done
@@ -69361,9 +70503,9 @@ async function runModels(sessionPath, opts = {}) {
69361
70503
  return handle.done;
69362
70504
  }
69363
70505
  async function judgeResponses(sessionPath, opts = {}) {
69364
- const active = activeRunAt(resolve4(sessionPath));
70506
+ const active = activeRunAt(resolve5(sessionPath));
69365
70507
  if (active)
69366
- throw new Error(`invalid_args: the team run at ${resolve4(sessionPath)} is still ACTIVE (run_id ${active.runId}); judge it once it has settled`);
70508
+ throw new Error(`invalid_args: the team run at ${resolve5(sessionPath)} is still ACTIVE (run_id ${active.runId}); judge it once it has settled`);
69367
70509
  const responseFiles = readdirSync9(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
69368
70510
  if (responseFiles.length < 2) {
69369
70511
  throw new Error(`Need at least 2 responses to judge, found ${responseFiles.length}`);
@@ -69371,13 +70513,13 @@ async function judgeResponses(sessionPath, opts = {}) {
69371
70513
  const responses = {};
69372
70514
  for (const file of responseFiles) {
69373
70515
  const id = file.replace(/^response-/, "").replace(/\.md$/, "");
69374
- responses[id] = readFileSync31(join49(sessionPath, file), "utf-8");
70516
+ responses[id] = readFileSync31(join50(sessionPath, file), "utf-8");
69375
70517
  }
69376
- const input = readFileSync31(join49(sessionPath, "input.md"), "utf-8");
70518
+ const input = readFileSync31(join50(sessionPath, "input.md"), "utf-8");
69377
70519
  const judgePrompt = buildJudgePrompt(input, responses);
69378
- writeFileSync22(join49(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
70520
+ writeFileSync22(join50(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
69379
70521
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
69380
- const judgePath = join49(sessionPath, "judging");
70522
+ const judgePath = join50(sessionPath, "judging");
69381
70523
  mkdirSync21(judgePath, { recursive: true });
69382
70524
  setupSession(judgePath, judgeModels, judgePrompt);
69383
70525
  await runModels(judgePath, {
@@ -69388,11 +70530,11 @@ async function judgeResponses(sessionPath, opts = {}) {
69388
70530
  });
69389
70531
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
69390
70532
  const verdict = aggregateVerdict(votes, Object.keys(responses));
69391
- writeFileSync22(join49(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
70533
+ writeFileSync22(join50(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
69392
70534
  return verdict;
69393
70535
  }
69394
70536
  function getStatus(sessionPath) {
69395
- return JSON.parse(readFileSync31(join49(sessionPath, "status.json"), "utf-8"));
70537
+ return JSON.parse(readFileSync31(join50(sessionPath, "status.json"), "utf-8"));
69396
70538
  }
69397
70539
  function readTeamStatus(sessionPath) {
69398
70540
  try {
@@ -69428,7 +70570,7 @@ function fisherYatesShuffle(arr) {
69428
70570
  return arr;
69429
70571
  }
69430
70572
  function getDefaultJudgeModels(sessionPath) {
69431
- const manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70573
+ const manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69432
70574
  return Object.values(manifest.models).map((e) => e.model);
69433
70575
  }
69434
70576
  function buildJudgePrompt(input, responses) {
@@ -69491,7 +70633,7 @@ function parseJudgeVotes(judgePath, responseIds) {
69491
70633
  const judgeId = file.replace(/^response-/, "").replace(/\.md$/, "");
69492
70634
  let content;
69493
70635
  try {
69494
- content = readFileSync31(join49(judgePath, file), "utf-8");
70636
+ content = readFileSync31(join50(judgePath, file), "utf-8");
69495
70637
  } catch {
69496
70638
  continue;
69497
70639
  }
@@ -69543,7 +70685,7 @@ function aggregateVerdict(votes, responseIds) {
69543
70685
  function formatVerdict(verdict, sessionPath) {
69544
70686
  let manifest = null;
69545
70687
  try {
69546
- manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70688
+ manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69547
70689
  } catch {}
69548
70690
  let output = `# Team Verdict
69549
70691
 
@@ -69605,7 +70747,7 @@ import {
69605
70747
  statSync as statSync13,
69606
70748
  writeFileSync as writeFileSync23
69607
70749
  } from "fs";
69608
- import { join as join50, resolve as resolve5, sep } from "path";
70750
+ import { join as join51, resolve as resolve6, sep } from "path";
69609
70751
  function normaliseTimeoutSeconds(raw) {
69610
70752
  const n = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : raw;
69611
70753
  if (typeof n !== "number" || !Number.isFinite(n))
@@ -69771,7 +70913,7 @@ function dropLeadingFragment(tail) {
69771
70913
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
69772
70914
  }
69773
70915
  function diskAccounting(sessionDir) {
69774
- const stats = readTokenStatsAt(join50(sessionDir, "tokens.json"));
70916
+ const stats = readTokenStatsAt(join51(sessionDir, "tokens.json"));
69775
70917
  return {
69776
70918
  tokensIn: stats?.billed_input_tokens ?? null,
69777
70919
  tokensOut: stats?.output_tokens ?? null,
@@ -69794,10 +70936,10 @@ function diskProseBytes(tail, fileBytes) {
69794
70936
  function blankFinalCapture() {
69795
70937
  return {
69796
70938
  seq: 0,
69797
- cols: 160,
69798
- rows: 50,
70939
+ cols: DEFAULT_PANE_COLS,
70940
+ rows: DEFAULT_PANE_ROWS,
69799
70941
  cursor: { x: 0, y: 0 },
69800
- lines: Array.from({ length: 50 }, () => ""),
70942
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
69801
70943
  final: true
69802
70944
  };
69803
70945
  }
@@ -69842,7 +70984,7 @@ class SessionManager {
69842
70984
  let dir = "";
69843
70985
  for (let attempt = 0;; attempt++) {
69844
70986
  id = `team-${randomUUID6().replace(/-/g, "").slice(0, 8)}`;
69845
- dir = join50(this.sessionsDir, id);
70987
+ dir = join51(this.sessionsDir, id);
69846
70988
  try {
69847
70989
  mkdirSync22(dir);
69848
70990
  break;
@@ -69857,7 +70999,7 @@ class SessionManager {
69857
70999
  sessionId: id,
69858
71000
  parentClaudeSessionId: opts.parentClaudeSessionId,
69859
71001
  startedAt,
69860
- teamPath: resolve5(opts.teamPath),
71002
+ teamPath: resolve6(opts.teamPath),
69861
71003
  slots: opts.slots
69862
71004
  });
69863
71005
  this.teamRunStarts.set(id, startedAt);
@@ -69874,9 +71016,9 @@ class SessionManager {
69874
71016
  if (dir === null)
69875
71017
  throw new Error("not a record id");
69876
71018
  const completedAt = new Date().toISOString();
69877
- const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join50(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
71019
+ const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join51(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
69878
71020
  const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
69879
- writeJsonAtomic(join50(dir, "meta.json"), {
71021
+ writeJsonAtomic(join51(dir, "meta.json"), {
69880
71022
  kind: "team",
69881
71023
  status: outcome.status,
69882
71024
  startedAt,
@@ -69908,7 +71050,7 @@ class SessionManager {
69908
71050
  startedAt,
69909
71051
  ...rest
69910
71052
  };
69911
- writeJsonAtomic(join50(dir, "spawn.json"), body);
71053
+ writeJsonAtomic(join51(dir, "spawn.json"), body);
69912
71054
  }
69913
71055
  async createSession(opts) {
69914
71056
  if (this.activeSessions + this.starting >= this.maxSessions) {
@@ -69967,10 +71109,10 @@ class SessionManager {
69967
71109
  const claudeSessionId = randomUUID6();
69968
71110
  const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
69969
71111
  const startedAt = new Date().toISOString();
69970
- const sessionDir = opts.sessionDir ?? join50(this.sessionsDir, sessionId);
71112
+ const sessionDir = opts.sessionDir ?? join51(this.sessionsDir, sessionId);
69971
71113
  mkdirSync22(sessionDir, { recursive: true });
69972
71114
  if (opts.prompt)
69973
- writeFileSync23(join50(sessionDir, "prompt.md"), opts.prompt, "utf-8");
71115
+ writeFileSync23(join51(sessionDir, "prompt.md"), opts.prompt, "utf-8");
69974
71116
  this.writeSpawnRecord(sessionDir, {
69975
71117
  kind: "session",
69976
71118
  sessionId,
@@ -69983,7 +71125,7 @@ class SessionManager {
69983
71125
  const cwd = opts.cwd ?? process.cwd();
69984
71126
  const parentEnv = opts.parentEnv ?? this.parentEnv;
69985
71127
  const spawnModel = opts.spawnModel ?? null;
69986
- const tokenFile = opts.tokenFile ?? join50(sessionDir, "tokens.json");
71128
+ const tokenFile = opts.tokenFile ?? join51(sessionDir, "tokens.json");
69987
71129
  return {
69988
71130
  info: {
69989
71131
  sessionId,
@@ -70019,28 +71161,30 @@ class SessionManager {
70019
71161
  },
70020
71162
  session: null,
70021
71163
  scrollback: new ScrollbackBuffer(this.scrollbackCapacity),
70022
- outputLogStream: createWriteStream(join50(sessionDir, "output.log")),
71164
+ outputLogStream: createWriteStream(join51(sessionDir, "output.log")),
70023
71165
  sessionDir,
70024
71166
  cwd,
70025
71167
  tokenFile,
70026
- eventLogPath: join50(sessionDir, "events.jsonl"),
71168
+ eventLogPath: join51(sessionDir, "events.jsonl"),
70027
71169
  eventLogBytes: 0,
70028
- upstreamErrorLogPath: join50(sessionDir, "upstream-errors.jsonl"),
71170
+ upstreamErrorLogPath: join51(sessionDir, "upstream-errors.jsonl"),
70029
71171
  eventRing: [],
70030
71172
  proseBytes: 0,
70031
71173
  answeredTurns: new Map,
70032
- waitLogPath: join50(sessionDir, "waits.jsonl"),
71174
+ waitLogPath: join51(sessionDir, "waits.jsonl"),
70033
71175
  waitLogBytes: 0,
70034
71176
  waitSince: null,
70035
71177
  ended: false,
70036
71178
  evictHandle: null,
70037
71179
  lastLoggedTool: null,
70038
71180
  assistantIdsLogged: 0,
70039
- anomaliesLogged: new Set
71181
+ anomaliesLogged: new Set,
71182
+ requestedSize: null
70040
71183
  };
70041
71184
  }
70042
71185
  async startPane(entry, opts, p) {
70043
71186
  const info = entry.info;
71187
+ const launch = entry.requestedSize;
70044
71188
  const paneOpts = {
70045
71189
  kind: "s",
70046
71190
  label: info.sessionId,
@@ -70055,6 +71199,7 @@ class SessionManager {
70055
71199
  },
70056
71200
  shape: info.shape,
70057
71201
  ...opts.prompt ? { initialPrompt: opts.prompt } : {},
71202
+ ...launch ? { initialSize: launch } : {},
70058
71203
  readAvailable: p.readAvailable,
70059
71204
  decide: (turn) => this.decide(entry, turn),
70060
71205
  onBlocked: (b) => this.onBlocked(entry, b),
@@ -70070,6 +71215,12 @@ class SessionManager {
70070
71215
  this.failNeverSpawned(entry, err);
70071
71216
  throw err;
70072
71217
  }
71218
+ const asked = entry.requestedSize;
71219
+ entry.requestedSize = null;
71220
+ if (asked && (asked.cols !== launch?.cols || asked.rows !== launch?.rows))
71221
+ entry.session.resize(asked.cols, asked.rows).catch(() => {
71222
+ return;
71223
+ });
70073
71224
  if (!entry.ended)
70074
71225
  this.applySnapshot(entry, entry.session.snapshot());
70075
71226
  }
@@ -70163,7 +71314,7 @@ class SessionManager {
70163
71314
  try {
70164
71315
  const text = screenLines.join(`
70165
71316
  `).replace(/\s+$/, "");
70166
- writeFileSync23(join50(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
71317
+ writeFileSync23(join51(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
70167
71318
  `, "utf-8");
70168
71319
  } catch {}
70169
71320
  }
@@ -70177,7 +71328,7 @@ class SessionManager {
70177
71328
  entry.waitSince = null;
70178
71329
  }
70179
71330
  try {
70180
- writeJsonAtomic(join50(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
71331
+ writeJsonAtomic(join51(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
70181
71332
  } catch (err) {
70182
71333
  process.stderr.write(`[claudish] session ${entry.info.sessionId}: could not write meta.json: ` + `${err instanceof Error ? err.message : String(err)}
70183
71334
  `);
@@ -70321,6 +71472,8 @@ class SessionManager {
70321
71472
  registry: null,
70322
71473
  preambleBytes: 0,
70323
71474
  readCoverage: null,
71475
+ gate: null,
71476
+ lastSend: null,
70324
71477
  claudeCodeVersion: null,
70325
71478
  anomalies: entry.info.detail ? [entry.info.detail] : []
70326
71479
  };
@@ -70335,6 +71488,8 @@ class SessionManager {
70335
71488
  registry: pane.registry,
70336
71489
  preambleBytes: pane.preambleBytes,
70337
71490
  readCoverage: pane.readCoverage,
71491
+ gate: pane.gate,
71492
+ lastSend: pane.lastSend,
70338
71493
  claudeCodeVersion: snap.claudeCodeVersion,
70339
71494
  anomalies: snap.anomalies
70340
71495
  };
@@ -70348,6 +71503,26 @@ class SessionManager {
70348
71503
  const r = entry.session.cancel();
70349
71504
  return { session_id: sessionId, state: r.state, changed: r.changed };
70350
71505
  }
71506
+ async resizeSession(sessionId, cols, rows) {
71507
+ const entry = this.liveEntry(sessionId);
71508
+ if (!entry)
71509
+ throw unknownSession(sessionId);
71510
+ if (!entry.session) {
71511
+ if (isTerminalState(entry.info.state))
71512
+ return {
71513
+ session_id: sessionId,
71514
+ cols: DEFAULT_PANE_COLS,
71515
+ rows: DEFAULT_PANE_ROWS,
71516
+ changed: false,
71517
+ seq: 0
71518
+ };
71519
+ const size = clampPaneSize(cols, rows);
71520
+ entry.requestedSize = size;
71521
+ return { session_id: sessionId, ...size, changed: true, seq: 0, pending: true };
71522
+ }
71523
+ const r = await entry.session.resize(cols, rows);
71524
+ return { session_id: sessionId, ...r };
71525
+ }
70351
71526
  captureSession(sessionId, sinceSeq, spans) {
70352
71527
  const entry = this.liveEntry(sessionId);
70353
71528
  if (!entry)
@@ -70411,9 +71586,9 @@ class SessionManager {
70411
71586
  diskSessionDir(sessionId) {
70412
71587
  if (!SESSION_ID_RE.test(sessionId))
70413
71588
  return null;
70414
- const root = resolve5(this.sessionsDir);
70415
- const dir = resolve5(root, sessionId);
70416
- if (dir !== join50(root, sessionId))
71589
+ const root = resolve6(this.sessionsDir);
71590
+ const dir = resolve6(root, sessionId);
71591
+ if (dir !== join51(root, sessionId))
70417
71592
  return null;
70418
71593
  if (!dir.startsWith(root + sep))
70419
71594
  return null;
@@ -70432,10 +71607,10 @@ class SessionManager {
70432
71607
  } catch {
70433
71608
  return null;
70434
71609
  }
70435
- const spawnRecord = readJsonObject(join50(sessionDir, "spawn.json"), META_READ_LIMIT);
71610
+ const spawnRecord = readJsonObject(join51(sessionDir, "spawn.json"), META_READ_LIMIT);
70436
71611
  if (spawnRecord?.kind === "team")
70437
71612
  return null;
70438
- const meta = readJsonObject(join50(sessionDir, "meta.json"), META_READ_LIMIT);
71613
+ const meta = readJsonObject(join51(sessionDir, "meta.json"), META_READ_LIMIT);
70439
71614
  if (meta?.kind === "team")
70440
71615
  return null;
70441
71616
  const partial = meta === null;
@@ -70457,7 +71632,7 @@ class SessionManager {
70457
71632
  return stored;
70458
71633
  }
70459
71634
  diskOutput(record, tailLines) {
70460
- const tail = readTailText(join50(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
71635
+ const tail = readTailText(join51(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
70461
71636
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
70462
71637
  if (tail?.text)
70463
71638
  buffer.append(dropLeadingFragment(tail));
@@ -70477,12 +71652,12 @@ class SessionManager {
70477
71652
  }
70478
71653
  diskDiagnostics(record, limit) {
70479
71654
  const { sessionDir, info } = record;
70480
- const eventLogPath = join50(sessionDir, "events.jsonl");
70481
- const upstreamErrorLogPath = join50(sessionDir, "upstream-errors.jsonl");
70482
- const outputLogPath = join50(sessionDir, "output.log");
71655
+ const eventLogPath = join51(sessionDir, "events.jsonl");
71656
+ const upstreamErrorLogPath = join51(sessionDir, "upstream-errors.jsonl");
71657
+ const outputLogPath = join51(sessionDir, "output.log");
70483
71658
  const events = readTailLines2(eventLogPath, EVENT_TAIL_BYTES);
70484
71659
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
70485
- const screen = readTailText(join50(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
71660
+ const screen = readTailText(join51(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
70486
71661
  return {
70487
71662
  sessionId: info.sessionId,
70488
71663
  state: info.state,
@@ -70515,6 +71690,8 @@ class SessionManager {
70515
71690
  turnSource: info.turnSource,
70516
71691
  preambleBytes: 0,
70517
71692
  readCoverage: null,
71693
+ gate: null,
71694
+ lastSend: null,
70518
71695
  claudeCodeVersion: null,
70519
71696
  anomalies: record.partial ? [
70520
71697
  `no readable meta.json in ${sessionDir} \u2014 this record was reconstructed from ` + "the remaining artifacts, so state, exit code and timings are unknown. The " + "session's process died before it could write a verdict."
@@ -70867,7 +72044,7 @@ var init_progress_heartbeat = __esm(() => {
70867
72044
  import { spawn as spawn2 } from "child_process";
70868
72045
  import { mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync24 } from "fs";
70869
72046
  import { tmpdir as tmpdir2 } from "os";
70870
- import { join as join51 } from "path";
72047
+ import { join as join52 } from "path";
70871
72048
  function claudeCodePrintArgv(model, systemPromptFile) {
70872
72049
  const head = [PRINT_FLAGS[0], "--model", model, ...PRINT_FLAGS.slice(1)];
70873
72050
  return systemPromptFile !== undefined ? [...head, "--system-prompt-file", systemPromptFile] : [...head, "--system-prompt", ""];
@@ -70985,13 +72162,13 @@ async function runPromptViaClaudeCode(model, prompt, systemPrompt, opts = {}) {
70985
72162
  } catch (e) {
70986
72163
  throw new Error(`cannot start claudish (${parentEnv.CLAUDISH_BIN?.trim() || "claudish"}): ${e instanceof Error ? e.message : String(e)}`);
70987
72164
  }
70988
- const root = mkdtempSync3(join51(tmpdir2(), "claudish-run-prompt-"));
72165
+ const root = mkdtempSync3(join52(tmpdir2(), "claudish-run-prompt-"));
70989
72166
  try {
70990
- const cwd = join51(root, "cwd");
72167
+ const cwd = join52(root, "cwd");
70991
72168
  mkdirSync23(cwd, { mode: 448 });
70992
72169
  let systemPromptFile;
70993
72170
  if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
70994
- systemPromptFile = join51(root, "system-prompt.txt");
72171
+ systemPromptFile = join52(root, "system-prompt.txt");
70995
72172
  writeFileSync24(systemPromptFile, systemPrompt, { mode: 384 });
70996
72173
  }
70997
72174
  const argv = claudeCodePrintArgv(model, systemPromptFile);
@@ -71099,7 +72276,7 @@ var init_run_prompt_claude_code = __esm(() => {
71099
72276
  // src/mcp-server.ts
71100
72277
  import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync33, readdirSync as readdirSync10, writeFileSync as writeFileSync25 } from "fs";
71101
72278
  import { homedir as homedir35 } from "os";
71102
- import { dirname as dirname14, join as join52, resolve as resolve6 } from "path";
72279
+ import { dirname as dirname15, join as join53, resolve as resolve7 } from "path";
71103
72280
  import { fileURLToPath as fileURLToPath3 } from "url";
71104
72281
  async function loadAllModels(forceRefresh = false) {
71105
72282
  if (!forceRefresh && existsSync33(OPENROUTER_MODELS_CACHE_PATH)) {
@@ -71394,6 +72571,12 @@ function optionalInteger(raw, name) {
71394
72571
  throw new ContractErrorException("invalid_args", `'${name}' must be an integer`);
71395
72572
  return raw;
71396
72573
  }
72574
+ function requiredInteger(raw, name, mode) {
72575
+ const n = optionalInteger(raw, name);
72576
+ if (n === undefined)
72577
+ throw new ContractErrorException("invalid_args", `'${name}' is required${mode ? ` for mode '${mode}'` : ""}`);
72578
+ return n;
72579
+ }
71397
72580
  function teamStatusAnswer(path, runId) {
71398
72581
  let row = null;
71399
72582
  if (runId) {
@@ -71433,6 +72616,8 @@ async function teamContractVerb(mode, args) {
71433
72616
  const slot = optionalString(args.slot, "slot");
71434
72617
  const since = optionalInteger(args.since_seq, "since_seq");
71435
72618
  const spans = optionalBoolean(args.spans, "spans");
72619
+ const cols = mode === "resize" ? requiredInteger(args.cols, "cols", mode) : undefined;
72620
+ const rows = mode === "resize" ? requiredInteger(args.rows, "rows", mode) : undefined;
71436
72621
  switch (mode) {
71437
72622
  case "status":
71438
72623
  return contractAnswer(teamStatusAnswer(path, runId));
@@ -71443,6 +72628,11 @@ async function teamContractVerb(mode, args) {
71443
72628
  throw new ContractErrorException("invalid_args", "'slot' is required for mode 'capture'");
71444
72629
  return contractAnswer(captureTeamSlot(path, slot, since, spans, runId));
71445
72630
  }
72631
+ case "resize": {
72632
+ if (slot === undefined)
72633
+ throw new ContractErrorException("invalid_args", "'slot' is required for mode 'resize'");
72634
+ return contractAnswer(await resizeTeamSlot(path, slot, cols, rows, runId));
72635
+ }
71446
72636
  }
71447
72637
  } catch (e) {
71448
72638
  return contractErrorAnswer(e);
@@ -71816,18 +73006,18 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71816
73006
  });
71817
73007
  tools.push({
71818
73008
  name: "team",
71819
- description: "Run AI models on a task with anonymized outputs and optional blind judging. Every " + "slot is an interactive Claude Code session in its own headless magmux pane. " + "Modes: 'run' (START the models and return once each has taken its prompt \u2014 it does " + "NOT wait for answers), 'status' (per-slot state; read `run.slots`), 'list' (every " + "run this server holds), 'capture' (one slot's screen), 'cancel' (stop one slot or " + "the whole run), 'judge' (blind-vote on existing outputs), 'run-and-judge' (the " + "blocking pipeline). Poll 'status' (read run.slots) or 'list'. " + "NO SLOT IS EVER KILLED ON A TIMER once its prompt is accepted. A team slot is a full " + "Claude Code session and may work for a long time; a slot inside a build or test " + "suite emits nothing for minutes and is working, not stuck. Judge the silence " + "against the task you set, and use 'cancel' if you decide a slot is wedged. A slot " + "that stops on a question it cannot answer is FAILED 'blocked' (team has no way to " + "answer it). JUDGING A WORKING SLOT: read live_output_bytes_by_slot, " + "idle_seconds_by_slot and activity_by_slot (or each row's idle_seconds and " + "activity). Do NOT use 'outputSize' \u2014 it is the size of the final answer, written " + "only when a slot finishes, so it is 0 for every working slot no matter how much " + "that slot has produced.",
73009
+ description: "Run AI models on a task with anonymized outputs and optional blind judging. Every " + "slot is an interactive Claude Code session in its own headless magmux pane. " + "Modes: 'run' (START the models and return once each has taken its prompt \u2014 it does " + "NOT wait for answers), 'status' (per-slot state; read `run.slots`), 'list' (every " + "run this server holds), 'capture' (one slot's screen), 'resize' (set one slot's " + "terminal size), 'cancel' (stop one slot or the whole run), 'judge'(blind-vote on existing outputs), 'run-and-judge' (the " + "blocking pipeline). Poll 'status' (read run.slots) or 'list'. " + "NO SLOT IS EVER KILLED ON A TIMER once its prompt is accepted. A team slot is a full " + "Claude Code session and may work for a long time; a slot inside a build or test " + "suite emits nothing for minutes and is working, not stuck. Judge the silence " + "against the task you set, and use 'cancel' if you decide a slot is wedged. A slot " + "that stops on a question it cannot answer is FAILED 'blocked' (team has no way to " + "answer it). JUDGING A WORKING SLOT: read live_output_bytes_by_slot, " + "idle_seconds_by_slot and activity_by_slot (or each row's idle_seconds and " + "activity). Do NOT use 'outputSize' \u2014 it is the size of the final answer, written " + "only when a slot finishes, so it is 0 for every working slot no matter how much " + "that slot has produced.",
71820
73010
  inputSchema: {
71821
73011
  type: "object",
71822
73012
  properties: {
71823
73013
  mode: {
71824
73014
  type: "string",
71825
- enum: ["run", "judge", "run-and-judge", "status", "cancel", "list", "capture"],
71826
- description: "Operation mode. 'run' STARTS the models and returns once every slot has taken " + "its prompt (or failed to) \u2014 it does not wait for answers. Poll 'status' (read " + "`run.slots`) or 'list' for progress, then 'judge' once the slots have " + "finished. 'capture' returns one slot's screen. 'run-and-judge' is the blocking " + "pipeline and holds the call open for the whole run. 'cancel' stops one slot or " + "the whole run."
73015
+ enum: ["run", "judge", "run-and-judge", "status", "cancel", "list", "capture", "resize"],
73016
+ description: "Operation mode. 'run' STARTS the models and returns once every slot has taken " + "its prompt (or failed to) \u2014 it does not wait for answers. Poll 'status' (read " + "`run.slots`) or 'list' for progress, then 'judge' once the slots have " + "finished. 'capture' returns one slot's screen; 'resize' sets its terminal size. " + "'run-and-judge' is the blocking pipeline and holds the call open for the whole run. 'cancel' stops one slot or " + "the whole run."
71827
73017
  },
71828
73018
  slot: {
71829
73019
  type: "string",
71830
- description: "For 'cancel' and 'capture': the anonymised slot id (e.g. '02') from the slot " + "map 'run' returned. 'capture' requires it; for 'cancel', omit it to stop every " + "slot in the run."
73020
+ description: "For 'cancel', 'capture' and 'resize': the anonymised slot id (e.g. '02') from the " + "slot map 'run' returned. 'capture' and 'resize' require it; for 'cancel', omit it " + "to stop every slot in the run."
71831
73021
  },
71832
73022
  path: {
71833
73023
  type: "string",
@@ -71835,7 +73025,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71835
73025
  },
71836
73026
  run_id: {
71837
73027
  type: "string",
71838
- description: "For 'status', 'cancel' and 'capture': the run_id a 'run' answer returned. It " + "addresses THAT run while this server retains it, even after a newer run reused " + "the path. Omit it to address the newest run at 'path'."
73028
+ description: "For 'status', 'cancel', 'capture' and 'resize': the run_id a 'run' answer " + "returned. It " + "addresses THAT run while this server retains it, even after a newer run reused " + "the path. Omit it to address the newest run at 'path'."
71839
73029
  },
71840
73030
  since_seq: {
71841
73031
  type: "number",
@@ -71845,6 +73035,14 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71845
73035
  type: "boolean",
71846
73036
  description: "For 'capture': also return the colour/attribute spans of each line."
71847
73037
  },
73038
+ cols: {
73039
+ type: "integer",
73040
+ description: "For 'resize' (required): the terminal width in columns, clamped to 80\u20139999. The " + "answer {run_id, path, slot, cols, rows, changed, seq} reports the size set."
73041
+ },
73042
+ rows: {
73043
+ type: "integer",
73044
+ description: "For 'resize' (required): the terminal height in rows, clamped to 24\u20139999 and to " + "at most 4 000 000 cells (cols \xD7 rows)."
73045
+ },
71848
73046
  models: {
71849
73047
  type: "array",
71850
73048
  items: { type: "string" },
@@ -71886,7 +73084,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71886
73084
  heartbeat: true,
71887
73085
  handler: async (args, ctx) => {
71888
73086
  const mode = args.mode;
71889
- if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture") {
73087
+ if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture" || mode === "resize") {
71890
73088
  return teamContractVerb(mode, args);
71891
73089
  }
71892
73090
  try {
@@ -71918,7 +73116,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71918
73116
  const promptText = () => {
71919
73117
  if (input !== undefined)
71920
73118
  return input;
71921
- const existing = join52(resolved, "input.md");
73119
+ const existing = join53(resolved, "input.md");
71922
73120
  return existsSync33(existing) ? readFileSync33(existing, "utf-8") : undefined;
71923
73121
  };
71924
73122
  switch (mode) {
@@ -72077,16 +73275,16 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72077
73275
  const sp = session_path;
72078
73276
  for (const file of ["status.json", "manifest.json", "input.md"]) {
72079
73277
  try {
72080
- sessionData[file] = readFileSync33(join52(sp, file), "utf-8");
73278
+ sessionData[file] = readFileSync33(join53(sp, file), "utf-8");
72081
73279
  } catch {}
72082
73280
  }
72083
73281
  try {
72084
- const errorDir = join52(sp, "errors");
73282
+ const errorDir = join53(sp, "errors");
72085
73283
  if (existsSync33(errorDir)) {
72086
73284
  for (const f of readdirSync10(errorDir)) {
72087
73285
  if (f.endsWith(".log")) {
72088
73286
  try {
72089
- sessionData[`errors/${f}`] = readFileSync33(join52(errorDir, f), "utf-8");
73287
+ sessionData[`errors/${f}`] = readFileSync33(join53(errorDir, f), "utf-8");
72090
73288
  } catch {}
72091
73289
  }
72092
73290
  }
@@ -72096,7 +73294,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72096
73294
  for (const f of readdirSync10(sp)) {
72097
73295
  if (f.startsWith("response-") && f.endsWith(".md")) {
72098
73296
  try {
72099
- const content = readFileSync33(join52(sp, f), "utf-8");
73297
+ const content = readFileSync33(join53(sp, f), "utf-8");
72100
73298
  sessionData[f] = content.slice(0, 200) + (content.length > 200 ? "... (truncated)" : "");
72101
73299
  } catch {}
72102
73300
  }
@@ -72105,7 +73303,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72105
73303
  }
72106
73304
  let version = "unknown";
72107
73305
  try {
72108
- const pkgPath = join52(__dirname3, "../package.json");
73306
+ const pkgPath = join53(__dirname3, "../package.json");
72109
73307
  if (existsSync33(pkgPath)) {
72110
73308
  version = JSON.parse(readFileSync33(pkgPath, "utf-8")).version;
72111
73309
  }
@@ -72185,7 +73383,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72185
73383
  });
72186
73384
  tools.push({
72187
73385
  name: "create_session",
72188
- description: "Create a new claudish proxy session for an external model: an interactive Claude Code running " + "it in its own headless magmux pane. Returns at once with state STARTING; nothing is " + "pushed while it runs: poll list_sessions (about once every 10 s) for its state, then " + "call get_output once it is COMPLETED. With a prompt it is one-shot (it ends when that turn " + "settles); without one it is interactive and waits for send_input. Needs magmux >= 0.14.0 " + "(darwin and linux).",
73386
+ description: "Create a new claudish proxy session for an external model: an interactive Claude Code running " + "it in its own headless magmux pane. Returns at once with state STARTING; nothing is " + "pushed while it runs: poll list_sessions (about once every 10 s) for its state, then " + "call get_output once it is COMPLETED. With a prompt it is one-shot (it ends when that turn " + `settles); without one it is interactive and waits for send_input. Needs magmux >= ${MIN_MAGMUX_VERSION} ` + "(darwin and linux).",
72189
73387
  inputSchema: {
72190
73388
  type: "object",
72191
73389
  properties: {
@@ -72229,7 +73427,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72229
73427
  const requestedModel = args.model;
72230
73428
  const workDir = args.work_dir;
72231
73429
  const plan = await prehydrateCredentialsForSpawn([requestedModel], {
72232
- pin: workDir === undefined || resolve6(workDir) === process.cwd()
73430
+ pin: workDir === undefined || resolve7(workDir) === process.cwd()
72233
73431
  });
72234
73432
  const parentClaudeSessionId = await parentSessionForCall({
72235
73433
  hostPid: sessionManager.hostPid
@@ -72411,7 +73609,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72411
73609
  });
72412
73610
  tools.push({
72413
73611
  name: "capture_session",
72414
- description: "Read a session's current terminal screen (160\xD750): {seq, cols, rows, cursor, lines, final, " + "spans?}. A memory read, meant for polling about once per second. Pass the last `seq` as " + "since_seq to get {unchanged:true, seq, final} while nothing changed. `final:true` is the " + "last screen of a closed pane. Errors are a JSON ContractError.",
73612
+ description: "Read a session's current terminal screen: {seq, cols, rows, cursor, lines, final, " + "spans?}. A memory read, meant for polling about once per second. Pass the last `seq` as " + "since_seq to get {unchanged:true, seq, final} while nothing changed. `final:true` is the " + "last screen of a closed pane. Errors are a JSON ContractError.",
72415
73613
  inputSchema: {
72416
73614
  type: "object",
72417
73615
  properties: {
@@ -72441,6 +73639,35 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72441
73639
  }
72442
73640
  }
72443
73641
  });
73642
+ tools.push({
73643
+ name: "resize_session",
73644
+ description: "Set a session's terminal size: the child gets a real SIGWINCH and Claude Code reflows. " + "Allowed at any time, mid-turn included; the last call wins. The size is clamped to " + "cols 80\u20139999, rows 24\u20139999 and at most 4 000 000 cells (magmux's limits); the answer " + "{session_id, cols, rows, changed, seq, pending?} reports the size set. `seq` is the screen's seq after the resize: the next capture_session " + "shows the new cols/rows. `changed:false` = already that size. `pending:true` = the pane " + "is starting or its socket is reconnecting; the size is applied then. A finished session " + "answers its last size with changed:false. Errors are a JSON ContractError " + "(`resize_unsupported` when the installed magmux has no resize op).",
73645
+ inputSchema: {
73646
+ type: "object",
73647
+ properties: {
73648
+ session_id: { type: "string", description: "Session ID from create_session" },
73649
+ cols: { type: "integer", description: "Terminal width in columns (clamped to 80\u20139999)" },
73650
+ rows: {
73651
+ type: "integer",
73652
+ description: "Terminal height in rows (clamped to 24\u20139999 and 4 000 000 cells)"
73653
+ }
73654
+ },
73655
+ required: ["session_id", "cols", "rows"]
73656
+ },
73657
+ group: "session",
73658
+ handler: async (args) => {
73659
+ try {
73660
+ const sessionId = optionalString(args.session_id, "session_id");
73661
+ if (sessionId === undefined)
73662
+ throw new ContractErrorException("invalid_args", "'session_id' is required");
73663
+ const cols = requiredInteger(args.cols, "cols");
73664
+ const rows = requiredInteger(args.rows, "rows");
73665
+ return contractAnswer(await sessionManager.resizeSession(sessionId, cols, rows));
73666
+ } catch (e) {
73667
+ return contractErrorAnswer(e);
73668
+ }
73669
+ }
73670
+ });
72444
73671
  return tools;
72445
73672
  }
72446
73673
  function resolveToolGroups(mode) {
@@ -72566,7 +73793,7 @@ Each session is an interactive Claude Code running the model in its own headless
72566
73793
  3. send_input may be called at any time; it is queued until the session is idle.
72567
73794
  4. COMPLETED: call get_output for the answer.
72568
73795
  5. FAILED or TIMEOUT \u2014 or COMPLETED with an empty or surprising answer \u2014 call get_diagnostics. It returns the child's final screen, the upstream error bodies, the recent state records, the resolved model chain and the paths to the full records. It needs no re-run and no debug flag.
72569
- 6. capture_session reads a session's current screen; cancel_session stops it. Nothing stops a session for being idle: a child inside a long build is silent and working.
73796
+ 6. capture_session reads a session's current screen; resize_session sets its terminal size (any time, mid-turn included); cancel_session stops it. Nothing stops a session for being idle: a child inside a long build is silent and working.
72570
73797
 
72571
73798
  The session_id from create_session is the key for every session tool.`, proxyInstance = null, proxyStarting = null, SCORE_TIE_EPSILON = 0.000001, NEXT_STEP, sanitize2, TOOL_MODES;
72572
73799
  var init_mcp_server = __esm(() => {
@@ -72599,9 +73826,9 @@ var init_mcp_server = __esm(() => {
72599
73826
  import_dotenv2 = __toESM(require_main(), 1);
72600
73827
  import_dotenv2.config({ quiet: true });
72601
73828
  __filename3 = fileURLToPath3(import.meta.url);
72602
- __dirname3 = dirname14(__filename3);
72603
- CLAUDISH_CACHE_DIR = join52(homedir35(), ".claudish");
72604
- OPENROUTER_MODELS_CACHE_PATH = join52(CLAUDISH_CACHE_DIR, "openrouter-models.json");
73829
+ __dirname3 = dirname15(__filename3);
73830
+ CLAUDISH_CACHE_DIR = join53(homedir35(), ".claudish");
73831
+ OPENROUTER_MODELS_CACHE_PATH = join53(CLAUDISH_CACHE_DIR, "openrouter-models.json");
72605
73832
  NEXT_STEP = {
72606
73833
  cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
72607
73834
  timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
@@ -72612,7 +73839,7 @@ var init_mcp_server = __esm(() => {
72612
73839
  prompt_not_accepted: "the REPL never took the prompt \u2014 read the evidence screen; retry",
72613
73840
  prompt_not_read: "the child never read all of its task file \u2014 check that its agent or tool flags allow " + "Read and that the model can call tools; `what` says which lines were read",
72614
73841
  child_exited: "read the evidence screen, then retry or drop the model",
72615
- pane_lost: "magmux died \u2014 check `magmux --version` \u2265 0.14.0 and retry",
73842
+ pane_lost: `magmux died \u2014 check \`magmux --version\` \u2265 ${MIN_MAGMUX_VERSION} and retry`,
72616
73843
  blocked: "the child asked a question team cannot answer \u2014 make the prompt self-contained or use " + "create_session",
72617
73844
  api_error: "retry once, or route via a different provider (or@<model>)",
72618
73845
  refused: "the model refused \u2014 rephrase or drop the model",
@@ -72928,7 +74155,7 @@ var init_behavior_command = __esm(() => {
72928
74155
  import { spawn as spawn3 } from "child_process";
72929
74156
  import { existsSync as existsSync36, readFileSync as readFileSync36, writeFileSync as writeFileSync27 } from "fs";
72930
74157
  import { connect as netConnect2 } from "net";
72931
- import { join as join53 } from "path";
74158
+ import { join as join54 } from "path";
72932
74159
  import { setTimeout as wait } from "timers/promises";
72933
74160
  function paneRouteLine(explanation) {
72934
74161
  const origin = describeRouteExplanation(explanation);
@@ -73078,9 +74305,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
73078
74305
  const keep = opts?.keep ?? false;
73079
74306
  const manifest = setupSession(sessionPath, models, input);
73080
74307
  const startedAt = new Date().toISOString();
73081
- const gridfilePath = join53(sessionPath, "gridfile.txt");
73082
- const prompt = readFileSync36(join53(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
73083
- const rawPrompt = readFileSync36(join53(sessionPath, "input.md"), "utf-8");
74308
+ const gridfilePath = join54(sessionPath, "gridfile.txt");
74309
+ const prompt = readFileSync36(join54(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
74310
+ const rawPrompt = readFileSync36(join54(sessionPath, "input.md"), "utf-8");
73084
74311
  const usedBannerColors = new Set;
73085
74312
  const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
73086
74313
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
@@ -73112,7 +74339,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
73112
74339
  });
73113
74340
  const [{ results }] = await Promise.all([subscription, procExit]);
73114
74341
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
73115
- const statusPath = join53(sessionPath, "status.json");
74342
+ const statusPath = join54(sessionPath, "status.json");
73116
74343
  writeFileSync27(statusPath, JSON.stringify(status, null, 2), "utf-8");
73117
74344
  return status;
73118
74345
  }
@@ -73133,7 +74360,7 @@ var init_team_grid = __esm(() => {
73133
74360
 
73134
74361
  // src/team-cli.ts
73135
74362
  import { readFileSync as readFileSync37 } from "fs";
73136
- import { join as join54 } from "path";
74363
+ import { join as join55 } from "path";
73137
74364
  async function runPaneTeam(sessionPath, models, input, opts) {
73138
74365
  installPaneShutdownHooks({ exitAfter: true });
73139
74366
  try {
@@ -73185,7 +74412,7 @@ Options (run / run-and-judge):
73185
74412
  --timeout <secs> Grid modes only: magmux's own per-pane timeout (default: 300).
73186
74413
  json mode has no deadline \u2014 nothing kills a working model.
73187
74414
  json mode runs each model as an interactive pane in a headless
73188
- magmux (>= 0.14.0; darwin and linux).
74415
+ magmux (>= ${MIN_MAGMUX_VERSION}; darwin and linux).
73189
74416
  --grid Show all models in a magmux grid with live output + status bar
73190
74417
  --mode <m> run only: json (default, panes), default (the grid, as --grid)
73191
74418
  or interactive (as --interactive)
@@ -73278,7 +74505,7 @@ async function teamCommand(args) {
73278
74505
  }
73279
74506
  case "judge": {
73280
74507
  await judgeResponses(sessionPath, { judges });
73281
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74508
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73282
74509
  break;
73283
74510
  }
73284
74511
  case "run-and-judge": {
@@ -73294,7 +74521,7 @@ async function teamCommand(args) {
73294
74521
  });
73295
74522
  printStatus(status);
73296
74523
  await judgeResponses(sessionPath, { judges });
73297
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74524
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73298
74525
  break;
73299
74526
  }
73300
74527
  case "status": {
@@ -73883,7 +75110,7 @@ var init_keychain_command = __esm(() => {
73883
75110
  import { spawnSync as spawnSync2 } from "child_process";
73884
75111
  import { existsSync as existsSync37, unlinkSync as unlinkSync9 } from "fs";
73885
75112
  import { homedir as homedir36 } from "os";
73886
- import { join as join55 } from "path";
75113
+ import { join as join56 } from "path";
73887
75114
  async function defaultSuggestModel() {
73888
75115
  try {
73889
75116
  const tok = readSharedAntigravityToken();
@@ -74004,7 +75231,7 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
74004
75231
  async logout(deps) {
74005
75232
  deleteSharedAntigravityToken(deps);
74006
75233
  try {
74007
- const tokenFile = join55(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
75234
+ const tokenFile = join56(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
74008
75235
  if (existsSync37(tokenFile))
74009
75236
  unlinkSync9(tokenFile);
74010
75237
  } catch {}
@@ -82490,7 +83717,7 @@ import {
82490
83717
  writeFileSync as writeFileSync28
82491
83718
  } from "fs";
82492
83719
  import { homedir as homedir37, tmpdir as tmpdir3 } from "os";
82493
- import { dirname as dirname15, join as join56 } from "path";
83720
+ import { dirname as dirname16, join as join57 } from "path";
82494
83721
  import { isatty } from "tty";
82495
83722
  function releaseTerminalIsolation() {
82496
83723
  if (!restoreTerminal)
@@ -82555,7 +83782,7 @@ function hasResolvableAnthropicAuth(deps = {}) {
82555
83782
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
82556
83783
  return true;
82557
83784
  }
82558
- if (fileExists(join56(homedir37(), ".claude", ".credentials.json")))
83785
+ if (fileExists(join57(homedir37(), ".claude", ".credentials.json")))
82559
83786
  return true;
82560
83787
  return keychainProbe();
82561
83788
  }
@@ -82570,7 +83797,7 @@ function isProxyAuthMode(config) {
82570
83797
  }
82571
83798
  function managedSettingsPath() {
82572
83799
  if (isWindows2()) {
82573
- return join56(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
83800
+ return join57(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
82574
83801
  }
82575
83802
  if (process.platform === "darwin") {
82576
83803
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -82591,9 +83818,9 @@ function isWindows2() {
82591
83818
  }
82592
83819
  function createStatusLineScript(tokenFilePath) {
82593
83820
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82594
- const claudishDir = join56(homeDir, ".claudish");
83821
+ const claudishDir = join57(homeDir, ".claudish");
82595
83822
  const timestamp = Date.now();
82596
- const scriptPath = join56(claudishDir, `status-${timestamp}.js`);
83823
+ const scriptPath = join57(claudishDir, `status-${timestamp}.js`);
82597
83824
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
82598
83825
  const light = getThemeMode() === "light";
82599
83826
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -82731,7 +83958,7 @@ process.stdin.on('end', () => {
82731
83958
  }
82732
83959
  function initializeTokenFile(tokenFilePath) {
82733
83960
  try {
82734
- mkdirSync25(dirname15(tokenFilePath), { recursive: true });
83961
+ mkdirSync25(dirname16(tokenFilePath), { recursive: true });
82735
83962
  writeFileSync28(tokenFilePath, JSON.stringify({
82736
83963
  input_tokens: 0,
82737
83964
  output_tokens: 0,
@@ -82763,7 +83990,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
82763
83990
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
82764
83991
  continue;
82765
83992
  scanned++;
82766
- const full = join56(dir, name);
83993
+ const full = join57(dir, name);
82767
83994
  try {
82768
83995
  if (statSync14(full).mtimeMs >= cutoff)
82769
83996
  continue;
@@ -82792,9 +84019,9 @@ function parseSettingsArgSafe(value) {
82792
84019
  }
82793
84020
  function userSettingsFileCandidates(cwd) {
82794
84021
  return [
82795
- join56(homedir37(), ".claude", "settings.json"),
82796
- join56(cwd, ".claude", "settings.json"),
82797
- join56(cwd, ".claude", "settings.local.json")
84022
+ join57(homedir37(), ".claude", "settings.json"),
84023
+ join57(cwd, ".claude", "settings.json"),
84024
+ join57(cwd, ".claude", "settings.local.json")
82798
84025
  ];
82799
84026
  }
82800
84027
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
@@ -82835,12 +84062,12 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
82835
84062
  }
82836
84063
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
82837
84064
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82838
- const claudishDir = join56(homeDir, ".claudish");
84065
+ const claudishDir = join57(homeDir, ".claudish");
82839
84066
  try {
82840
84067
  mkdirSync25(claudishDir, { recursive: true });
82841
84068
  } catch {}
82842
84069
  const timestamp = Date.now();
82843
- const tempPath = join56(claudishDir, `settings-${timestamp}.json`);
84070
+ const tempPath = join57(claudishDir, `settings-${timestamp}.json`);
82844
84071
  const assignedFile = assignedTokenFile(process.env) !== null;
82845
84072
  const tokenFilePath = resolveTokenFilePath(port);
82846
84073
  cleanupStaleTokenFiles(claudishDir);
@@ -83219,7 +84446,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
83219
84446
  console.error(`
83220
84447
  Or set CLAUDE_PATH to your custom installation:`);
83221
84448
  const home = homedir37();
83222
- const localPath = isWindows2() ? join56(home, ".claude", "local", "claude.exe") : join56(home, ".claude", "local", "claude");
84449
+ const localPath = isWindows2() ? join57(home, ".claude", "local", "claude.exe") : join57(home, ".claude", "local", "claude");
83223
84450
  console.error(` export CLAUDE_PATH=${localPath}`);
83224
84451
  process.exit(1);
83225
84452
  }
@@ -83332,15 +84559,15 @@ async function findClaudeBinary() {
83332
84559
  }
83333
84560
  }
83334
84561
  const home = homedir37();
83335
- const localPath = isWindows ? join56(home, ".claude", "local", "claude.exe") : join56(home, ".claude", "local", "claude");
84562
+ const localPath = isWindows ? join57(home, ".claude", "local", "claude.exe") : join57(home, ".claude", "local", "claude");
83336
84563
  if (existsSync39(localPath)) {
83337
84564
  return localPath;
83338
84565
  }
83339
84566
  if (isWindows) {
83340
84567
  const windowsPaths = [
83341
- join56(home, "AppData", "Roaming", "npm", "claude.cmd"),
83342
- join56(home, ".npm-global", "claude.cmd"),
83343
- join56(home, "node_modules", ".bin", "claude.cmd")
84568
+ join57(home, "AppData", "Roaming", "npm", "claude.cmd"),
84569
+ join57(home, ".npm-global", "claude.cmd"),
84570
+ join57(home, "node_modules", ".bin", "claude.cmd")
83344
84571
  ];
83345
84572
  for (const path of windowsPaths) {
83346
84573
  if (existsSync39(path)) {
@@ -83351,11 +84578,11 @@ async function findClaudeBinary() {
83351
84578
  const commonPaths = [
83352
84579
  "/usr/local/bin/claude",
83353
84580
  "/opt/homebrew/bin/claude",
83354
- join56(home, ".npm-global/bin/claude"),
83355
- join56(home, ".local/bin/claude"),
83356
- join56(home, "node_modules/.bin/claude"),
84581
+ join57(home, ".npm-global/bin/claude"),
84582
+ join57(home, ".local/bin/claude"),
84583
+ join57(home, "node_modules/.bin/claude"),
83357
84584
  "/data/data/com.termux/files/usr/bin/claude",
83358
- join56(home, "../usr/bin/claude")
84585
+ join57(home, "../usr/bin/claude")
83359
84586
  ];
83360
84587
  for (const path of commonPaths) {
83361
84588
  if (existsSync39(path)) {
@@ -83440,16 +84667,16 @@ var init_claude_runner = __esm(() => {
83440
84667
  // src/diag-output.ts
83441
84668
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync26, unlinkSync as unlinkSync11, writeFileSync as writeFileSync29 } from "fs";
83442
84669
  import { homedir as homedir38 } from "os";
83443
- import { join as join57 } from "path";
84670
+ import { join as join58 } from "path";
83444
84671
  function getClaudishDir() {
83445
- const dir = join57(homedir38(), ".claudish");
84672
+ const dir = join58(homedir38(), ".claudish");
83446
84673
  try {
83447
84674
  mkdirSync26(dir, { recursive: true });
83448
84675
  } catch {}
83449
84676
  return dir;
83450
84677
  }
83451
84678
  function getDiagLogPath() {
83452
- return join57(getClaudishDir(), `diag-${process.pid}.log`);
84679
+ return join58(getClaudishDir(), `diag-${process.pid}.log`);
83453
84680
  }
83454
84681
 
83455
84682
  class LogFileDiagOutput {
@@ -86064,7 +87291,7 @@ init_startup_trace();
86064
87291
  init_picker_cancelled();
86065
87292
  var import_dotenv3 = __toESM(require_main(), 1);
86066
87293
  import { existsSync as existsSync40, readFileSync as readFileSync40 } from "fs";
86067
- import { join as join58, resolve as resolve7 } from "path";
87294
+ import { join as join59, resolve as resolve8 } from "path";
86068
87295
 
86069
87296
  // src/tui/runtime/should-open-picker.ts
86070
87297
  function noModelConfigured(gate) {
@@ -86176,7 +87403,7 @@ async function applyOpImport() {
86176
87403
  async function applyConfigOverride() {
86177
87404
  await Promise.resolve();
86178
87405
  const plan = planConfigOverride(process.argv.slice(2), process.env, {
86179
- resolve: resolve7,
87406
+ resolve: resolve8,
86180
87407
  exists: existsSync40
86181
87408
  });
86182
87409
  if (plan.kind === "none")
@@ -86380,7 +87607,7 @@ async function runCli() {
86380
87607
  process.exit(1);
86381
87608
  }
86382
87609
  const mode = cliConfig.teamMode ?? "default";
86383
- const sessionPath = join58(process.cwd(), `.claudish-team-${Date.now()}`);
87610
+ const sessionPath = join59(process.cwd(), `.claudish-team-${Date.now()}`);
86384
87611
  if (mode === "json") {
86385
87612
  await Promise.resolve().then(() => init_pane());
86386
87613
  await Promise.resolve().then(() => init_team_orchestrator());
@@ -86400,7 +87627,7 @@ async function runCli() {
86400
87627
  const status = await runModels(sessionPath);
86401
87628
  const result = { ...status, responses: {} };
86402
87629
  for (const anonId of Object.keys(status.models)) {
86403
- const responsePath = join58(sessionPath, `response-${anonId}.md`);
87630
+ const responsePath = join59(sessionPath, `response-${anonId}.md`);
86404
87631
  try {
86405
87632
  const raw = readFileSync40(responsePath, "utf-8").trim();
86406
87633
  try {