claudish 12.0.1 → 12.1.0

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 +1647 -414
  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.0";
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";
@@ -64233,8 +64248,8 @@ function buildPaneEnv(input) {
64233
64248
  continue;
64234
64249
  env[k] = v;
64235
64250
  }
64236
- env.COLUMNS = "160";
64237
- env.LINES = "50";
64251
+ env.COLUMNS = String(input.cols ?? DEFAULT_PANE_COLS);
64252
+ env.LINES = String(input.rows ?? DEFAULT_PANE_ROWS);
64238
64253
  if (input.parentEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined)
64239
64254
  env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
64240
64255
  Object.assign(env, input.slotEnv);
@@ -64360,24 +64375,46 @@ function versionAtLeast(v, min) {
64360
64375
  return true;
64361
64376
  }
64362
64377
  async function assertMagmuxAvailable(binary) {
64363
- const cached = magmuxCache.get(binary ?? "");
64378
+ const key = binary ?? "";
64379
+ const cached = magmuxCache.get(key);
64364
64380
  if (cached)
64365
64381
  return cached;
64382
+ const inFlight = magmuxProbes.get(key);
64383
+ if (inFlight)
64384
+ return inFlight;
64385
+ const probe = (async () => {
64386
+ try {
64387
+ const result = await probeMagmux(binary);
64388
+ magmuxCache.set(key, result);
64389
+ return result;
64390
+ } finally {
64391
+ magmuxProbes.delete(key);
64392
+ }
64393
+ })();
64394
+ magmuxProbes.set(key, probe);
64395
+ return probe;
64396
+ }
64397
+ async function probeMagmux(binary) {
64366
64398
  const found = binary ?? findMagmuxBinaryOrNull();
64367
64399
  if (!found)
64368
64400
  throw new MagmuxUnavailableError("magmux_unavailable: magmux not found (bundled package or PATH); install it: brew install MadAppGang/tap/magmux");
64401
+ let version = await readMagmuxVersion(found);
64402
+ if (!version)
64403
+ version = await readMagmuxVersion(found);
64404
+ if (!version || !versionAtLeast(version, MIN_MAGMUX_VERSION))
64405
+ throw new MagmuxUnavailableError(`magmux_unavailable: ${found} is version ${version || "unknown"}; panes need >= ${MIN_MAGMUX_VERSION} (the Claude Code input gate bound to a named transcript); upgrade: brew upgrade MadAppGang/tap/magmux`);
64406
+ return { binary: found, version };
64407
+ }
64408
+ async function readMagmuxVersion(found) {
64369
64409
  let out = "";
64370
64410
  try {
64371
- out = (await execFileAsync(found, ["--version"], { encoding: "utf8", timeout: 5000 })).stdout;
64411
+ const r = await execFileAsync(found, ["--version"], { encoding: "utf8", timeout: 5000 });
64412
+ out = `${r.stdout}
64413
+ ${r.stderr}`;
64372
64414
  } catch (e) {
64373
64415
  throw new MagmuxUnavailableError(`magmux_unavailable: ${found} --version failed: ${e instanceof Error ? e.message : String(e)}`);
64374
64416
  }
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;
64417
+ return out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "";
64381
64418
  }
64382
64419
  function resolveClaudishLaunch(parentEnv) {
64383
64420
  const t = resolveClaudishSpawn(parentEnv);
@@ -64426,21 +64463,35 @@ function spawnPaneWatcher(args) {
64426
64463
  proc.stdin?.unref?.();
64427
64464
  return proc;
64428
64465
  }
64429
- function spawnPaneMagmux(input) {
64430
- const proc = spawn(input.binary, [
64466
+ function paneMagmuxArgv(input) {
64467
+ return [
64431
64468
  "--headless",
64432
64469
  "--no-status",
64470
+ "--controller",
64471
+ "claude-code",
64472
+ "--session-id",
64473
+ input.sessionUuid,
64474
+ "--transcript",
64475
+ resolve3(input.transcriptPath),
64433
64476
  "--id",
64434
64477
  input.paneId,
64435
64478
  "--sock-dir",
64436
64479
  input.sockRoot,
64437
64480
  "-e",
64438
64481
  paneCommand(input.ctlDir)
64439
- ], { cwd: input.cwd, env: input.env, stdio: ["ignore", "ignore", "pipe"], detached: true });
64482
+ ];
64483
+ }
64484
+ function spawnPaneMagmux(input) {
64485
+ const proc = spawn(input.binary, paneMagmuxArgv(input), {
64486
+ cwd: input.cwd,
64487
+ env: input.env,
64488
+ stdio: ["ignore", "ignore", "pipe"],
64489
+ detached: true
64490
+ });
64440
64491
  proc.on("error", () => {});
64441
64492
  return proc;
64442
64493
  }
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
64494
+ 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.17.0", MagmuxUnavailableError, magmuxCache, magmuxProbes, execFileAsync, ClaudishNotFoundError, SHELL_SHIM = `#!/bin/sh
64444
64495
  [ "$1" = -l ] && shift
64445
64496
  exec /bin/sh "$@"
64446
64497
  `, FATAL_MAGMUX_STDERR;
@@ -64614,6 +64665,7 @@ var init_pane_launch = __esm(() => {
64614
64665
  code = "magmux_unavailable";
64615
64666
  };
64616
64667
  magmuxCache = new Map;
64668
+ magmuxProbes = new Map;
64617
64669
  execFileAsync = promisify5(execFile2);
64618
64670
  ClaudishNotFoundError = class ClaudishNotFoundError extends Error {
64619
64671
  code = "pane_lost";
@@ -64623,6 +64675,20 @@ var init_pane_launch = __esm(() => {
64623
64675
 
64624
64676
  // src/pane/magmux-client.ts
64625
64677
  import { Socket } from "net";
64678
+ function failureOf(m) {
64679
+ const out = {
64680
+ ok: false,
64681
+ code: String(m.code ?? "error"),
64682
+ error: String(m.error ?? "")
64683
+ };
64684
+ if (typeof m.reason === "string" && m.reason)
64685
+ out.reason = m.reason;
64686
+ if (typeof m.typed === "boolean")
64687
+ out.typed = m.typed;
64688
+ if (typeof m.maybeSubmitted === "boolean")
64689
+ out.maybeSubmitted = m.maybeSubmitted;
64690
+ return out;
64691
+ }
64626
64692
  function dialOnce(sockPath) {
64627
64693
  return new Promise((resolve, reject) => {
64628
64694
  const s = new Socket;
@@ -64749,7 +64815,7 @@ class MagmuxClient {
64749
64815
  return;
64750
64816
  clearTimeout(p.timer);
64751
64817
  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 ?? "") });
64818
+ p.resolve(m.ok === true ? { ok: true, result: m.result ?? {} } : failureOf(m));
64753
64819
  return;
64754
64820
  }
64755
64821
  if (m.type === "shutdown")
@@ -65247,6 +65313,352 @@ var init_process_tree = __esm(() => {
65247
65313
  KILL_PROCESS_GROUP = process.platform !== "win32";
65248
65314
  });
65249
65315
 
65316
+ // src/pane/box-locate.ts
65317
+ function isRuleRow(line) {
65318
+ if (!line || line.length < MIN_RULE_CELLS)
65319
+ return false;
65320
+ return /^\u2500+(?: [^\u2500]+ \u2500+)?$/.test(line);
65321
+ }
65322
+ function nearestRule(lines, from, step) {
65323
+ for (let i = 1;i <= RULE_SEARCH_ROWS; i++) {
65324
+ const r = from + i * step;
65325
+ if (r < 0 || r >= lines.length)
65326
+ return -1;
65327
+ if (isRuleRow(lines[r]))
65328
+ return r;
65329
+ }
65330
+ return -1;
65331
+ }
65332
+ function inspectBox(s) {
65333
+ const lines = s.lines;
65334
+ const y = s.cursor.y;
65335
+ if (!Number.isInteger(y) || y < 0 || y >= s.rows || y >= lines.length) {
65336
+ return { kind: "unknown", why: "cursor_off_screen" };
65337
+ }
65338
+ if (SELECTED_OPTION.test(lines[y] ?? ""))
65339
+ return { kind: "dialog", why: "selected_option" };
65340
+ const top = nearestRule(lines, y, -1);
65341
+ if (top < 0)
65342
+ return { kind: "unknown", why: "no_rule_above" };
65343
+ const bottom = nearestRule(lines, y, 1);
65344
+ if (bottom < 0)
65345
+ return { kind: "unknown", why: "no_rule_below" };
65346
+ const first = lines[top + 1] ?? "";
65347
+ if (SELECTED_OPTION.test(first))
65348
+ return { kind: "dialog", why: "selected_option" };
65349
+ if (lines.slice(bottom + 1).some((l) => SELECT_FOOTER.test(l))) {
65350
+ return { kind: "dialog", why: "select_footer" };
65351
+ }
65352
+ if (!PROMPT_ROW.test(first))
65353
+ return { kind: "unknown", why: "no_prompt_row" };
65354
+ return { kind: "box", box: { top, bottom, caretRow: y } };
65355
+ }
65356
+ function locateBox(s) {
65357
+ const at = inspectBox(s);
65358
+ return at.kind === "box" ? at.box : null;
65359
+ }
65360
+ function visibleText(line, spans) {
65361
+ if (!spans?.length)
65362
+ return line;
65363
+ const chars = Array.from(line);
65364
+ for (const [col, len, , , attr] of spans) {
65365
+ if ((attr & ATTR_FAINT) === 0)
65366
+ continue;
65367
+ for (let c = col;c < col + len && c < chars.length; c++)
65368
+ chars[c] = " ";
65369
+ }
65370
+ return chars.join("");
65371
+ }
65372
+ function locatedBoxText(s, box) {
65373
+ const parts = [];
65374
+ for (let i = box.top + 1;i < box.bottom; i++) {
65375
+ const raw = visibleText(s.lines[i] ?? "", s.spans[i]);
65376
+ const body = i === box.top + 1 ? raw.replace(/^[\u276F!]\s?/, "") : raw.replace(/^ {2}/, "");
65377
+ parts.push(body.replace(/\s+$/, ""));
65378
+ }
65379
+ return parts.join(`
65380
+ `).trim();
65381
+ }
65382
+ var RULE_SEARCH_ROWS = 40, MIN_RULE_CELLS = 20, PROMPT_ROW, SELECTED_OPTION, SELECT_FOOTER, ATTR_FAINT = 2;
65383
+ var init_box_locate = __esm(() => {
65384
+ PROMPT_ROW = /^(\u276F(\u00A0|$)|!)/;
65385
+ SELECTED_OPTION = /^ *\u276F \d+\.\s+\S/;
65386
+ SELECT_FOOTER = /Enter to select \u00B7 \u2191\/\u2193 to navigate/;
65387
+ });
65388
+
65389
+ // src/pane/input-gate.ts
65390
+ function dialogKindOf(reason) {
65391
+ if (!reason?.startsWith(DIALOG_PREFIX))
65392
+ return null;
65393
+ const kind = reason.slice(DIALOG_PREFIX.length);
65394
+ return DIALOG_KINDS.has(kind) ? kind : null;
65395
+ }
65396
+ function str3(v) {
65397
+ return typeof v === "string" && v !== "" ? v : null;
65398
+ }
65399
+ function record4(v) {
65400
+ return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
65401
+ }
65402
+ function paneZero(listResult) {
65403
+ const panes = record4(listResult)?.panes;
65404
+ if (!Array.isArray(panes))
65405
+ return null;
65406
+ for (const p of panes) {
65407
+ const row = record4(p);
65408
+ if (row?.pane === 0)
65409
+ return row;
65410
+ }
65411
+ return null;
65412
+ }
65413
+ function parseGateInput(listResult, now) {
65414
+ const input = record4(paneZero(listResult)?.input);
65415
+ if (!input || typeof input.ready !== "boolean")
65416
+ return null;
65417
+ const reason = input.ready ? null : str3(input.reason);
65418
+ return {
65419
+ ready: input.ready,
65420
+ reason,
65421
+ dialog: dialogKindOf(reason),
65422
+ detail: str3(input.detail),
65423
+ receivedAt: now
65424
+ };
65425
+ }
65426
+ function gateBasisOf(listResult) {
65427
+ const basis = paneZero(listResult)?.gateBasis;
65428
+ return basis === "direct" || basis === "declared" ? basis : null;
65429
+ }
65430
+ function parseGateBinding(listResult) {
65431
+ const row = paneZero(listResult);
65432
+ if (!row)
65433
+ return null;
65434
+ const boundRaw = str3(row.bound);
65435
+ const sessionId = str3(row.sessionId);
65436
+ const transcriptPath = str3(row.transcriptPath);
65437
+ let bound = "unbound";
65438
+ if (sessionId && transcriptPath) {
65439
+ if (boundRaw === "named" || boundRaw === "prompt" || boundRaw === "mtime")
65440
+ bound = boundRaw;
65441
+ }
65442
+ return { bound, boundRaw, sessionId, transcriptPath };
65443
+ }
65444
+ function assertNever(x) {
65445
+ throw new Error(`unreachable: ${JSON.stringify(x)}`);
65446
+ }
65447
+ function retypeOf(result) {
65448
+ if (result.retyped !== true)
65449
+ return { retyped: false, retypeCause: null };
65450
+ const cause = result.retypeCause;
65451
+ return {
65452
+ retyped: true,
65453
+ retypeCause: cause === "terminal_reset" || cause === "never_drawn" ? cause : "unknown"
65454
+ };
65455
+ }
65456
+ function okOutcome(result) {
65457
+ const { retyped, retypeCause } = retypeOf(result);
65458
+ const waitedMs = typeof result.waitedMs === "number" ? result.waitedMs : 0;
65459
+ const verified = str3(result.verified);
65460
+ const accept = result.accept;
65461
+ if (accept === "in_box")
65462
+ return { kind: "in_box", retyped, retypeCause, waitedMs, verified };
65463
+ if (accept === undefined) {
65464
+ return {
65465
+ kind: "unexpected",
65466
+ code: "ungated_reply",
65467
+ error: "ok with no accept: the pane is not gated"
65468
+ };
65469
+ }
65470
+ if (accept !== "confirmed" && accept !== "resent_enter" && accept !== "submitted_by_other") {
65471
+ return {
65472
+ kind: "unexpected",
65473
+ code: "unknown_accept",
65474
+ error: `accept ${JSON.stringify(accept)}`
65475
+ };
65476
+ }
65477
+ const evidence = result.evidence;
65478
+ if (evidence !== "history" && evidence !== "transcript") {
65479
+ return {
65480
+ kind: "unexpected",
65481
+ code: "unknown_evidence",
65482
+ error: `accept ${accept} with evidence ${JSON.stringify(evidence)}`
65483
+ };
65484
+ }
65485
+ const dismissed = Array.isArray(result.dismissed) ? result.dismissed.filter((d) => typeof d === "string") : [];
65486
+ return {
65487
+ kind: "accepted",
65488
+ accept,
65489
+ evidence,
65490
+ enterByMagmux: result.enter === true,
65491
+ retyped,
65492
+ retypeCause,
65493
+ dismissed,
65494
+ waitedMs,
65495
+ verified
65496
+ };
65497
+ }
65498
+ function notAcceptedOutcome(r) {
65499
+ const typed = r.typed === true;
65500
+ switch (r.reason) {
65501
+ case "text_mismatch":
65502
+ return {
65503
+ kind: "not_accepted",
65504
+ reason: "text_mismatch",
65505
+ maybeSubmitted: r.maybeSubmitted === true || r.error.includes(MAYBE_SUBMITTED_MARKER),
65506
+ typed,
65507
+ detail: r.error
65508
+ };
65509
+ case "empty_box":
65510
+ case "still_in_box":
65511
+ case "unconfirmed":
65512
+ return {
65513
+ kind: "not_accepted",
65514
+ reason: r.reason,
65515
+ maybeSubmitted: false,
65516
+ typed,
65517
+ detail: r.error
65518
+ };
65519
+ default:
65520
+ return {
65521
+ kind: "unexpected",
65522
+ code: `not_accepted:${r.reason ?? ""}`,
65523
+ error: r.error
65524
+ };
65525
+ }
65526
+ }
65527
+ function classifySendReply(r) {
65528
+ if (r.ok)
65529
+ return okOutcome(record4(r.result) ?? {});
65530
+ switch (r.code) {
65531
+ case "busy": {
65532
+ const reason = r.reason ?? "";
65533
+ return { kind: "busy", reason, dialog: dialogKindOf(reason), typed: r.typed === true };
65534
+ }
65535
+ case "not_accepted":
65536
+ return notAcceptedOutcome(r);
65537
+ case "bad_request":
65538
+ return { kind: "bad_request", error: r.error };
65539
+ case "client_timeout":
65540
+ case "client_lost":
65541
+ case "timeout":
65542
+ return { kind: "reply_lost", code: r.code };
65543
+ case "client_closed":
65544
+ case "no_client":
65545
+ return { kind: "unsent", code: r.code };
65546
+ case "pane_dead":
65547
+ case "no_such_pane":
65548
+ return { kind: "pane_gone", code: r.code, typed: r.typed === true };
65549
+ default:
65550
+ return { kind: "unexpected", code: r.code, error: r.error };
65551
+ }
65552
+ }
65553
+ function readyWaitMs(readyTimeoutMs) {
65554
+ if (readyTimeoutMs <= 0)
65555
+ return SEND_READY_WAIT_DEFAULT_MS;
65556
+ return Math.min(readyTimeoutMs, SEND_READY_WAIT_MAX_MS);
65557
+ }
65558
+ function typedWriteCount(text) {
65559
+ const rs = Array.from(text);
65560
+ let n = 0;
65561
+ let run = 0;
65562
+ for (let i = 0;i < rs.length; i++) {
65563
+ const r = rs[i];
65564
+ if (r === "\r" || r === `
65565
+ `) {
65566
+ if (r === "\r" && rs[i + 1] === `
65567
+ `)
65568
+ i++;
65569
+ if (run > 0) {
65570
+ n++;
65571
+ run = 0;
65572
+ }
65573
+ n++;
65574
+ } else {
65575
+ run++;
65576
+ if (run === SEND_TYPED_CHUNK_RUNES) {
65577
+ n++;
65578
+ run = 0;
65579
+ }
65580
+ }
65581
+ }
65582
+ return run > 0 ? n + 1 : n;
65583
+ }
65584
+ function sendBudgetMs(s) {
65585
+ let attempt = 0;
65586
+ if (s.typed ?? true)
65587
+ attempt += typedWriteCount(s.text) * (SEND_TYPED_CHUNK_GAP_MS + SEND_GATE_CHECK_COST_MS);
65588
+ attempt += s.keys * (SEND_KEY_GAP_MS + SEND_GATE_CHECK_COST_MS);
65589
+ attempt += SEND_SETTLE_MS + SEND_VERIFY_MIN_MS + SEND_VERIFY_STEP_MS;
65590
+ const accept = s.submit ? SEND_ACCEPT_WINDOWS * SEND_ACCEPT_WINDOW_MS : 0;
65591
+ return readyWaitMs(s.readyTimeoutMs) + 2 * attempt + accept + SEND_BUDGET_SLACK_MS;
65592
+ }
65593
+ 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;
65594
+ var init_input_gate = __esm(() => {
65595
+ DIALOG_KINDS = new Set([
65596
+ "permission",
65597
+ "question",
65598
+ "plan",
65599
+ "select",
65600
+ "trust",
65601
+ "bypass",
65602
+ "api_key",
65603
+ "onboarding"
65604
+ ]);
65605
+ BOOT_DIALOG_KINDS = new Set([
65606
+ "trust",
65607
+ "bypass",
65608
+ "api_key",
65609
+ "onboarding"
65610
+ ]);
65611
+ });
65612
+
65613
+ // src/pane/gate-binding.ts
65614
+ import { realpathSync as realpathSync2 } from "fs";
65615
+ import { basename as basename2, dirname as dirname14, join as join45, resolve as resolve4 } from "path";
65616
+ function normaliseTranscriptPath(p) {
65617
+ const abs = resolve4(p);
65618
+ const tail = [];
65619
+ let cur = abs;
65620
+ for (;; ) {
65621
+ try {
65622
+ return tail.length ? join45(realpathSync2(cur), ...tail.reverse()) : realpathSync2(cur);
65623
+ } catch {
65624
+ const parent = dirname14(cur);
65625
+ if (parent === cur)
65626
+ return abs;
65627
+ tail.push(basename2(cur));
65628
+ cur = parent;
65629
+ }
65630
+ }
65631
+ }
65632
+ function checkGateBinding(listResult, expected) {
65633
+ const v = expected.magmuxVersion || "?";
65634
+ const basis = gateBasisOf(listResult);
65635
+ if (basis !== "declared" || !parseGateInput(listResult, 0)) {
65636
+ return {
65637
+ ok: false,
65638
+ detail: `input_gate_unavailable: magmux ${v} did not gate pane 0 (gateBasis ${basis ?? "none"})`
65639
+ };
65640
+ }
65641
+ const b = parseGateBinding(listResult);
65642
+ const ours = normaliseTranscriptPath(expected.transcriptPath);
65643
+ const theirs = b?.transcriptPath ? normaliseTranscriptPath(b.transcriptPath) : null;
65644
+ const differs = [];
65645
+ if (b?.bound !== "named" && b?.boundRaw !== "named")
65646
+ differs.push(`bound ${b?.boundRaw ?? "none"}`);
65647
+ if (b?.sessionId !== expected.sessionUuid)
65648
+ differs.push(`sessionId ${b?.sessionId ?? "none"}, not ${expected.sessionUuid}`);
65649
+ if (theirs !== ours)
65650
+ differs.push(`transcriptPath ${theirs ?? "none"}, not ${ours}`);
65651
+ if (b?.bound === "named" && differs.length === 0)
65652
+ return { ok: true };
65653
+ return {
65654
+ ok: false,
65655
+ detail: `input_gate_unbound: magmux ${v} bound pane 0 to ${theirs ?? "none"} (${b?.boundRaw ?? "none"}), not ${ours}: ${differs.join("; ")}`
65656
+ };
65657
+ }
65658
+ var init_gate_binding = __esm(() => {
65659
+ init_input_gate();
65660
+ });
65661
+
65250
65662
  // src/pane/screen-model.ts
65251
65663
  function emptyScreen(cols, rows) {
65252
65664
  return {
@@ -65264,7 +65676,7 @@ function emptyScreen(cols, rows) {
65264
65676
  function isBoxRule(line, cols) {
65265
65677
  if (!line || !/^\u2500+(?: [^\u2500]+ \u2500+)?$/.test(line))
65266
65678
  return false;
65267
- return line.length >= Math.max(20, Math.min(cols, 160) - 1);
65679
+ return line.length >= Math.max(20, cols - 1);
65268
65680
  }
65269
65681
  function boxTopRow(lines, cols) {
65270
65682
  for (let i = lines.length - 2;i >= 0; i--) {
@@ -65288,22 +65700,26 @@ function withSize(s, rows) {
65288
65700
  spans.push([]);
65289
65701
  return { lines, spans };
65290
65702
  }
65291
- function aboveBoxText(lines, cols) {
65292
- const top = boxTopRow(lines, cols);
65293
- return (top >= 0 ? lines.slice(0, top) : lines).join(`
65294
- `);
65703
+ function aboveBoxText(s) {
65704
+ const box = locateBox(s);
65705
+ return box ? s.lines.slice(0, box.top).join(`
65706
+ `) : null;
65295
65707
  }
65296
65708
  function finish(prev, next, now) {
65297
65709
  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
65710
  if (!visible)
65299
65711
  return prev;
65300
- const aboveChanged = prev.seq === 0 || aboveBoxText(prev.lines, prev.cols) !== aboveBoxText(next.lines, next.cols);
65301
- return {
65712
+ const out = {
65302
65713
  ...next,
65303
65714
  seq: prev.seq + 1,
65304
65715
  changedAt: now,
65305
- aboveBoxChangedAt: aboveChanged ? now : prev.aboveBoxChangedAt
65716
+ aboveBoxChangedAt: prev.aboveBoxChangedAt
65306
65717
  };
65718
+ const before = prev.seq === 0 ? null : aboveBoxText(prev);
65719
+ const after = aboveBoxText(out);
65720
+ if (before === null || after === null || before !== after)
65721
+ out.aboveBoxChangedAt = now;
65722
+ return out;
65307
65723
  }
65308
65724
  function applyFrame(s, frame, now = Date.now()) {
65309
65725
  const rows = frame.rows;
@@ -65328,6 +65744,9 @@ function applyFrame(s, frame, now = Date.now()) {
65328
65744
  alt: frame.alt ?? s.alt
65329
65745
  }, now);
65330
65746
  }
65747
+ var init_screen_model = __esm(() => {
65748
+ init_box_locate();
65749
+ });
65331
65750
 
65332
65751
  // src/pane/screen-classifier.ts
65333
65752
  function screenText(s) {
@@ -65337,18 +65756,44 @@ function screenText(s) {
65337
65756
  return lines.join(`
65338
65757
  `);
65339
65758
  }
65340
- function visibleText(line, spans) {
65759
+ function flatText(lines) {
65760
+ return lines.join(" ").replace(/\s+/g, " ").trim();
65761
+ }
65762
+ function squash(s) {
65763
+ return s.replace(/\s+/g, "");
65764
+ }
65765
+ function echoHeadLength(cols) {
65766
+ return Math.max(1, Math.min(40, cols - 4));
65767
+ }
65768
+ function echoHead(line, cols) {
65769
+ return line.slice(0, echoHeadLength(cols));
65770
+ }
65771
+ function visibleText2(line, spans) {
65341
65772
  if (!spans?.length)
65342
65773
  return line;
65343
65774
  const chars = Array.from(line);
65344
65775
  for (const [col, len, , , attr] of spans) {
65345
- if ((attr & ATTR_FAINT) === 0)
65776
+ if ((attr & ATTR_FAINT2) === 0)
65346
65777
  continue;
65347
65778
  for (let c = col;c < col + len && c < chars.length; c++)
65348
65779
  chars[c] = " ";
65349
65780
  }
65350
65781
  return chars.join("");
65351
65782
  }
65783
+ function echoAt(lines, i, head) {
65784
+ const first = lines[i] ?? "";
65785
+ if (!first.startsWith("\u276F "))
65786
+ return false;
65787
+ const want = squash(head);
65788
+ let got = squash(first.slice(2));
65789
+ for (let j = i + 1;got.length < want.length && j < lines.length; j++) {
65790
+ const row = lines[j] ?? "";
65791
+ if (!/^ {2}\S/.test(row))
65792
+ break;
65793
+ got += squash(row);
65794
+ }
65795
+ return got.startsWith(want);
65796
+ }
65352
65797
  function inputBox(s) {
65353
65798
  const top = boxTopRow(s.lines, s.cols);
65354
65799
  if (top < 0)
@@ -65367,7 +65812,7 @@ function boxBottomRow(s, top) {
65367
65812
  function boxText(s, top, bottom) {
65368
65813
  const parts = [];
65369
65814
  for (let i = top + 1;i < bottom; i++) {
65370
- const raw = visibleText(s.lines[i] ?? "", s.spans[i]);
65815
+ const raw = visibleText2(s.lines[i] ?? "", s.spans[i]);
65371
65816
  const body = i === top + 1 ? raw.replace(/^\u276F\s?/, "") : raw.replace(/^ {2}/, "");
65372
65817
  parts.push(body.replace(/\s+$/, ""));
65373
65818
  }
@@ -65400,7 +65845,8 @@ function choiceDialog(s) {
65400
65845
  const lines = s.lines;
65401
65846
  const start = lastBulletRow(lines) + 1;
65402
65847
  const below = lines.slice(start);
65403
- const kind = CHOICE_DIALOGS.find((d) => below.some((l) => d.header.test(l)))?.kind;
65848
+ const flat = flatText(below);
65849
+ const kind = CHOICE_DIALOGS.find((d) => d.header.test(flat))?.kind;
65404
65850
  if (!kind)
65405
65851
  return null;
65406
65852
  if (!below.some((l) => FIRST_OPTION.test(l)))
@@ -65408,14 +65854,19 @@ function choiceDialog(s) {
65408
65854
  return { kind, text: below.filter((l) => l.trim()).join(`
65409
65855
  `) };
65410
65856
  }
65411
- function hasChoiceDialog(s) {
65412
- return choiceDialog(s) !== null;
65857
+ function dialogText(s) {
65858
+ return s.lines.slice(lastBulletRow(s.lines) + 1).filter((l) => l.trim()).join(`
65859
+ `);
65413
65860
  }
65414
65861
  function isWorking(s) {
65415
65862
  const top = boxTopRow(s.lines, s.cols);
65416
65863
  const region = top >= 0 ? s.lines.slice(0, top) : s.lines;
65417
65864
  return region.some((l) => WORKING_ROW.test(l));
65418
65865
  }
65866
+ function workingAboveBox(s) {
65867
+ const box = locateBox(s);
65868
+ return box ? s.lines.slice(0, box.top).some((l) => WORKING_ROW.test(l)) : null;
65869
+ }
65419
65870
  function stopHookRunning(s) {
65420
65871
  return s.lines.some((l) => /\(running Stop hook\b/.test(l));
65421
65872
  }
@@ -65447,7 +65898,7 @@ function screenAnswer(s, echo) {
65447
65898
  const lines = s.lines;
65448
65899
  const top = boxTopRow(lines, s.cols);
65449
65900
  const end = top >= 0 ? top : lines.length;
65450
- const start = echoRow(lines, end, `\u276F ${echo.slice(0, 40)}`) + 1;
65901
+ const start = echoRow(lines, end, echoHead(echo, s.cols)) + 1;
65451
65902
  if (start <= 0)
65452
65903
  return "";
65453
65904
  const out = [];
@@ -65462,8 +65913,9 @@ function screenAnswer(s, echo) {
65462
65913
  `).trim();
65463
65914
  }
65464
65915
  function echoRow(lines, end, head) {
65916
+ const above = lines.slice(0, end);
65465
65917
  for (let i = end - 1;i >= 0; i--)
65466
- if ((lines[i] ?? "").startsWith(head))
65918
+ if (echoAt(above, i, head))
65467
65919
  return i;
65468
65920
  return -1;
65469
65921
  }
@@ -65485,9 +65937,9 @@ function agentRejectedLine(text) {
65485
65937
  const m = text.match(/--agent '([^']+)' not found\.(?: Available agents:[^\n]*)?/);
65486
65938
  return m ? m[0] : null;
65487
65939
  }
65488
- function namedDialog(text) {
65940
+ function namedDialog(flat) {
65489
65941
  for (const d of NAMED_DIALOGS)
65490
- if (text.includes(d.marker))
65942
+ if (flat.includes(d.marker))
65491
65943
  return d.name;
65492
65944
  return null;
65493
65945
  }
@@ -65496,22 +65948,20 @@ function isUnnamedChoice(lines) {
65496
65948
  const yesNo = lines.some((l) => /^\s*(\u276F\s*)?(\d+\.\s+)?(Yes|No)\b|\(esc\)/.test(l));
65497
65949
  if (numbered && yesNo)
65498
65950
  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" };
65951
+ return lines.some((l) => /Press Enter to continue/.test(l) || SELECT_FOOTER2.test(l));
65952
+ }
65953
+ function nameBootScreen(s) {
65954
+ const named = namedDialog(flatText(s.lines));
65507
65955
  if (named)
65508
- return { kind: "dialog", name: named, text };
65956
+ return { kind: "dialog", name: named, text: screenText(s) };
65509
65957
  if (isUnnamedChoice(s.lines))
65510
- return { kind: "choice", text };
65958
+ return { kind: "choice", text: screenText(s) };
65511
65959
  return { kind: "booting" };
65512
65960
  }
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;
65961
+ 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
65962
  var init_screen_classifier = __esm(() => {
65963
+ init_box_locate();
65964
+ init_screen_model();
65515
65965
  NAMED_DIALOGS = [
65516
65966
  { name: "onboarding", marker: "Choose the text style" },
65517
65967
  { name: "trust", marker: "Is this a project you created or one you trust" },
@@ -65522,15 +65972,15 @@ var init_screen_classifier = __esm(() => {
65522
65972
  { kind: "permission", header: /\bDo you want to proceed\?/ },
65523
65973
  {
65524
65974
  kind: "permission",
65525
- header: /\bDo you want to (create|make this edit to|overwrite|delete) .+\?/
65975
+ header: /\bDo you want to (create|make this edit to|overwrite|delete) .{1,300}?\?/
65526
65976
  },
65527
65977
  { kind: "plan", header: /\bWould you like to proceed\?/ },
65528
65978
  { kind: "question", header: /Enter to select \u00B7 \u2191\/\u2193 to navigate/ }
65529
65979
  ];
65530
65980
  NUMBERED_OPTION = /^\s*(\u276F\s*)?\d+\.\s+\S/;
65531
65981
  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*$/;
65982
+ SELECT_FOOTER2 = /Enter to (confirm|select)\b.*Esc to cancel/;
65983
+ WORKING_ROW = /^\s*[\u00B7\u2722\u2733\u2736\u273B\u273D*\u25CF\u2217]\s+[A-Z][\w'-]*\u2026(\s+\((?:.*\)|[^)]*))?\s*$/;
65534
65984
  BULLET = /^\u23FA(\s|$)/;
65535
65985
  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
65986
  MENU_ROW = /^\s{2}\u276F \/\S|^\s{4}\/\S|^\s{4}[*+] \S/;
@@ -65539,7 +65989,7 @@ var init_screen_classifier = __esm(() => {
65539
65989
 
65540
65990
  // src/pane/session-registry.ts
65541
65991
  import { readFileSync as readFileSync29, readdirSync as readdirSync6 } from "fs";
65542
- import { join as join45 } from "path";
65992
+ import { join as join46 } from "path";
65543
65993
  function parseRegistryRecord(raw) {
65544
65994
  let parsed;
65545
65995
  try {
@@ -65584,7 +66034,7 @@ class SessionRegistry {
65584
66034
  isAlive;
65585
66035
  constructor(o) {
65586
66036
  this.o = o;
65587
- this.dir = join45(o.configDir, "sessions");
66037
+ this.dir = join46(o.configDir, "sessions");
65588
66038
  this.now = o.now ?? Date.now;
65589
66039
  this.isAlive = o.isAlive ?? pidAlive;
65590
66040
  }
@@ -65620,7 +66070,7 @@ class SessionRegistry {
65620
66070
  readPid(pid) {
65621
66071
  let raw;
65622
66072
  try {
65623
- raw = readFileSync29(join45(this.dir, `${pid}.json`), "utf8");
66073
+ raw = readFileSync29(join46(this.dir, `${pid}.json`), "utf8");
65624
66074
  } catch {
65625
66075
  return null;
65626
66076
  }
@@ -65665,14 +66115,28 @@ function activityOf(t) {
65665
66115
  return "finishing";
65666
66116
  return "thinking";
65667
66117
  }
66118
+ function unconfirmedBy(f) {
66119
+ const g = f.gate;
66120
+ if (g === null)
66121
+ return "gate_unknown";
66122
+ if (!g.ready || g.dialog !== null)
66123
+ return (g.reason ?? "not_ready").slice(0, 48);
66124
+ if (f.working === true)
66125
+ return "working";
66126
+ return null;
66127
+ }
65668
66128
  function corroborated(input) {
65669
66129
  const quiet = input.corroborationMs ?? CORROBORATION_MS;
65670
66130
  if (input.chatQuietMs < quiet)
65671
66131
  return { ok: false };
65672
- if (!input.screen)
66132
+ if (!input.facts)
65673
66133
  return { ok: true, anomaly: "screen_unverified" };
65674
- const s = input.screen;
65675
- return { ok: s.boxEmpty && !s.choiceDialog && !s.working };
66134
+ const why = unconfirmedBy(input.facts);
66135
+ if (why === null)
66136
+ return { ok: true };
66137
+ if (input.chatQuietMs >= (input.corroborationGraceMs ?? CORROBORATION_GRACE_MS))
66138
+ return { ok: true, anomaly: `settle_gate_unconfirmed:${why}` };
66139
+ return { ok: false };
65676
66140
  }
65677
66141
  function settledAs(by, stopReason, c, extra = []) {
65678
66142
  return {
@@ -65705,10 +66169,13 @@ function pathPrimary(t, input) {
65705
66169
  function pathSecondary(t, input) {
65706
66170
  if (!endingStop(t) || !stopHooksFinished(t, input.session))
65707
66171
  return null;
65708
- if (!input.screen)
66172
+ if (!input.facts)
65709
66173
  return null;
65710
66174
  const window2 = input.secondaryQuietMs ?? SECONDARY_QUIET_MS;
65711
- if (input.transcriptQuietMs < window2 || input.screen.aboveBoxQuietMs < window2)
66175
+ if (input.transcriptQuietMs < window2)
66176
+ return null;
66177
+ const above = input.facts.aboveBoxQuietMs;
66178
+ if (above !== null && above < window2)
65712
66179
  return null;
65713
66180
  const c = corroborated(input);
65714
66181
  return c.ok ? settledAs("quiet", t.lastAssistant?.stopReason ?? null, c, ["settled_without_turn_duration"]) : null;
@@ -65739,9 +66206,10 @@ function decideSettle(input) {
65739
66206
  return endingStop(t) ? settledAs("exit", t.lastAssistant.stopReason, {}) : notYet;
65740
66207
  return pathSecondary(t, input) ?? notYet;
65741
66208
  }
65742
- var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING;
66209
+ var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING, CORROBORATION_GRACE_MS;
65743
66210
  var init_settle_rule = __esm(() => {
65744
66211
  CONTINUING = new Set(["tool_use", "pause_turn"]);
66212
+ CORROBORATION_GRACE_MS = SECONDARY_QUIET_MS;
65745
66213
  });
65746
66214
 
65747
66215
  // src/pane/slot-state.ts
@@ -65882,10 +66350,10 @@ import {
65882
66350
  openSync as openSync8,
65883
66351
  readSync as readSync3,
65884
66352
  readdirSync as readdirSync7,
65885
- realpathSync as realpathSync2,
66353
+ realpathSync as realpathSync3,
65886
66354
  statSync as statSync9
65887
66355
  } from "fs";
65888
- import { join as join46 } from "path";
66356
+ import { join as join47 } from "path";
65889
66357
  function initialFollowerState(resolvePath = safeRealpath) {
65890
66358
  return {
65891
66359
  turns: [],
@@ -65900,7 +66368,7 @@ function initialFollowerState(resolvePath = safeRealpath) {
65900
66368
  }
65901
66369
  function safeRealpath(p) {
65902
66370
  try {
65903
- return realpathSync2(p);
66371
+ return realpathSync3(p);
65904
66372
  } catch {
65905
66373
  return p;
65906
66374
  }
@@ -66413,7 +66881,7 @@ class TranscriptFollower {
66413
66881
  for (const f of this.subagentFiles()) {
66414
66882
  let tail = this.subs.get(f);
66415
66883
  if (!tail) {
66416
- tail = new Tail(join46(this.subagentsDir, f));
66884
+ tail = new Tail(join47(this.subagentsDir, f));
66417
66885
  this.subs.set(f, tail);
66418
66886
  }
66419
66887
  for (const { line } of tail.read()) {
@@ -66496,8 +66964,8 @@ var init_transcript_follower = __esm(() => {
66496
66964
  });
66497
66965
 
66498
66966
  // 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";
66967
+ import { readFileSync as readFileSync30, realpathSync as realpathSync4, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
66968
+ import { join as join48 } from "path";
66501
66969
  function iso(ms) {
66502
66970
  return new Date(ms).toISOString();
66503
66971
  }
@@ -66534,7 +67002,7 @@ class PaneSessionImpl {
66534
67002
  initialPending;
66535
67003
  shape;
66536
67004
  queue = [];
66537
- screen = emptyScreen(160, 50);
67005
+ screen;
66538
67006
  final = false;
66539
67007
  turnIndex = 0;
66540
67008
  admission = null;
@@ -66570,8 +67038,25 @@ class PaneSessionImpl {
66570
67038
  disagreeCounted = new Set;
66571
67039
  exitScrollback = "";
66572
67040
  exitLastLine = "";
66573
- bootKind = null;
66574
- bootKindSince = 0;
67041
+ desiredSize;
67042
+ appliedSize;
67043
+ supportsResize = false;
67044
+ resizeWaiters = [];
67045
+ resizeDraining = false;
67046
+ lastSentSize = null;
67047
+ resizeQuietFrom = 0;
67048
+ resizeQuietUntil = 0;
67049
+ activityChangedAt = 0;
67050
+ gate = null;
67051
+ gateTrusted = false;
67052
+ gateInFlight = false;
67053
+ gateTimer = null;
67054
+ gateDeadSince = 0;
67055
+ lastSend = null;
67056
+ turnAcceptedAt = null;
67057
+ turnErrorLogSize = 0;
67058
+ witnessMissingCounted = false;
67059
+ abandonedIndex = null;
66575
67060
  magmux = null;
66576
67061
  watcher = null;
66577
67062
  client = null;
@@ -66602,12 +67087,16 @@ class PaneSessionImpl {
66602
67087
  this.turnDir = ids.turnDir;
66603
67088
  this.identity = { paneId: ids.paneId, sessionUuid: o.sessionUuid, ctlDir: ids.ctlDir };
66604
67089
  this.shape = o.shape;
67090
+ const size = launchSize(o);
67091
+ this.desiredSize = size;
67092
+ this.appliedSize = { ...size };
67093
+ this.screen = emptyScreen(size.cols, size.rows);
66605
67094
  this.initialPending = !!o.initialPrompt;
66606
67095
  if (o.initialPrompt)
66607
67096
  this.queue.push(o.initialPrompt);
66608
67097
  this.secondaryQuietMs = o.timings?.secondaryQuietMs ?? SECONDARY_QUIET_MS;
66609
67098
  this.corroborationMs = o.timings?.corroborationMs ?? CORROBORATION_MS;
66610
- this.follower = new TranscriptFollower(o.transcriptPath, join47(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
67099
+ this.follower = new TranscriptFollower(o.transcriptPath, join48(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
66611
67100
  this.registry = new SessionRegistry({
66612
67101
  configDir: claudeConfigDir(o.parentEnv ?? process.env),
66613
67102
  sessionUuid: o.sessionUuid
@@ -66643,7 +67132,9 @@ class PaneSessionImpl {
66643
67132
  sockRoot: this.root,
66644
67133
  ctlDir: this.ctlDir,
66645
67134
  cwd: input.cwd,
66646
- env: input.env
67135
+ env: input.env,
67136
+ sessionUuid: this.o.sessionUuid,
67137
+ transcriptPath: this.o.transcriptPath
66647
67138
  });
66648
67139
  } catch (e) {
66649
67140
  return this.spawnFailed(e);
@@ -66652,7 +67143,7 @@ class PaneSessionImpl {
66652
67143
  return this.spawnFailed(new Error("magmux did not start"));
66653
67144
  this.magmux = proc;
66654
67145
  try {
66655
- writeFileSync21(join47(this.ctlDir, "magmux.pid"), `${proc.pid}
67146
+ writeFileSync21(join48(this.ctlDir, "magmux.pid"), `${proc.pid}
66656
67147
  `, { mode: 384 });
66657
67148
  } catch {}
66658
67149
  updateRecord(this.root, this.paneId, { magmuxPid: proc.pid });
@@ -66660,6 +67151,7 @@ class PaneSessionImpl {
66660
67151
  proc.stderr?.on("data", (d) => this.onStderr(d));
66661
67152
  proc.on("exit", (code, sig) => this.onMagmuxExit(code, sig));
66662
67153
  this.ticker = setInterval(() => this.step(() => this.evaluate()), TICK_MS);
67154
+ this.gateTimer = setInterval(() => this.pollGate(), GATE_POLL_MS);
66663
67155
  if (this.o.timeoutMs !== undefined)
66664
67156
  this.timeoutTimer = setTimeout(() => this.step(() => this.fire("timeout")), Math.max(0, this.o.timeoutMs));
66665
67157
  await this.connectFirst();
@@ -66689,7 +67181,6 @@ class PaneSessionImpl {
66689
67181
  client.close();
66690
67182
  return;
66691
67183
  }
66692
- this.client = client;
66693
67184
  client.on("snapshot", (e) => this.onSnapshot(e));
66694
67185
  client.on("frame", (f) => this.onFrame(f));
66695
67186
  client.on("exit", (e) => void this.onPaneExit(e));
@@ -66701,8 +67192,15 @@ class PaneSessionImpl {
66701
67192
  const caps = await client.request({
66702
67193
  type: "capabilities"
66703
67194
  });
67195
+ this.supportsResize = caps.ok && Array.isArray(caps.result.verbs) && caps.result.verbs.includes("resize");
67196
+ if (client.isClosed)
67197
+ return;
67198
+ this.client = client;
67199
+ this.gateTrusted = false;
67200
+ this.gate = null;
67201
+ const magmuxVersion = caps.ok ? String(caps.result.version ?? "") : "";
66704
67202
  if (caps.ok) {
66705
- const v = String(caps.result.version ?? "");
67203
+ const v = magmuxVersion;
66706
67204
  if (caps.result.protocol !== 1 || !versionAtLeast(v, MIN_MAGMUX_VERSION)) {
66707
67205
  this.step(() => this.fire("pane_lost", {
66708
67206
  detail: `magmux_unavailable: magmux ${v || "?"} protocol ${caps.result.protocol ?? "?"}; panes need >= ${MIN_MAGMUX_VERSION}, protocol 1`
@@ -66710,6 +67208,9 @@ class PaneSessionImpl {
66710
67208
  return;
66711
67209
  }
66712
67210
  }
67211
+ this.lastSentSize = null;
67212
+ if (this.supportsResize && !samePaneSize(this.appliedSize, this.desiredSize))
67213
+ this.kickResize();
66713
67214
  await client.request({ type: "watch", pane: 0, mode: "frames", fps: WATCH_FPS });
66714
67215
  const list = await client.request({ type: "list" });
66715
67216
  const p0 = list.ok ? list.result.panes?.find((p) => p.pane === 0) : undefined;
@@ -66719,7 +67220,76 @@ class PaneSessionImpl {
66719
67220
  if (!reconnect)
66720
67221
  await this.nextFrame(500);
66721
67222
  this.onPaneExit({ exitCode: p0.exitCode ?? null, lastLine: "" });
67223
+ return;
67224
+ }
67225
+ if (!this.verifyGateBinding(client, list, magmuxVersion) || !list.ok)
67226
+ return;
67227
+ this.gateTrusted = true;
67228
+ this.onGateList(client, list.result);
67229
+ }
67230
+ verifyGateBinding(client, list, magmuxVersion) {
67231
+ if (client.isClosed || isTerminalPhase(this.phase))
67232
+ return false;
67233
+ if (!list.ok) {
67234
+ this.step(() => this.fire("pane_lost", {
67235
+ detail: `input_gate_unbound: magmux ${magmuxVersion || "?"} list failed (${list.code}: ${list.error}), so the binding of pane 0 is unknown`
67236
+ }));
67237
+ return false;
67238
+ }
67239
+ if (!list.result.panes?.some((p) => p.pane === 0))
67240
+ return false;
67241
+ const check = checkGateBinding(list.result, {
67242
+ sessionUuid: this.o.sessionUuid,
67243
+ transcriptPath: this.o.transcriptPath,
67244
+ magmuxVersion
67245
+ });
67246
+ if (!check.ok) {
67247
+ this.step(() => this.fire("pane_lost", { detail: check.detail }));
67248
+ return false;
66722
67249
  }
67250
+ return true;
67251
+ }
67252
+ pollGate() {
67253
+ const c = this.client;
67254
+ if (!c || !this.gateTrusted || this.gateInFlight)
67255
+ return;
67256
+ if (this.reaping || isTerminalPhase(this.phase))
67257
+ return;
67258
+ this.gateInFlight = true;
67259
+ c.request({ type: "list" }, GATE_REPLY_MS).then((r) => {
67260
+ this.gateInFlight = false;
67261
+ if (r.ok)
67262
+ this.onGateList(c, r.result);
67263
+ });
67264
+ }
67265
+ onGateList(c, result) {
67266
+ if (c !== this.client || !this.gateTrusted || isTerminalPhase(this.phase))
67267
+ return;
67268
+ const now = Date.now();
67269
+ const p0 = result.panes?.find((p) => p.pane === 0);
67270
+ if (typeof p0?.pid === "number" && !this.panePidValue)
67271
+ this.setPanePid(p0.pid);
67272
+ if (p0?.dead === true && !this.paneExited) {
67273
+ if (this.gateDeadSince === 0)
67274
+ this.gateDeadSince = now;
67275
+ else if (now - this.gateDeadSince >= GATE_DEAD_BACKSTOP_MS) {
67276
+ this.anomaly("exit_event_missing");
67277
+ this.onPaneExit({ exitCode: p0.exitCode ?? null, lastLine: "" });
67278
+ return;
67279
+ }
67280
+ } else
67281
+ this.gateDeadSince = 0;
67282
+ const prev = this.gate;
67283
+ this.gate = parseGateInput(result, now);
67284
+ const g = this.gate;
67285
+ if (prev?.ready !== g?.ready || prev?.reason !== g?.reason)
67286
+ this.step(() => this.evaluate());
67287
+ }
67288
+ gateNow() {
67289
+ const g = this.gate;
67290
+ if (!g || !this.client || !this.gateTrusted)
67291
+ return null;
67292
+ return Date.now() - g.receivedAt <= GATE_STALE_MS ? g : null;
66723
67293
  }
66724
67294
  onStderr(d) {
66725
67295
  if (this.stderrHead.length < 8192)
@@ -66742,7 +67312,10 @@ class PaneSessionImpl {
66742
67312
  onFrame(f) {
66743
67313
  if (f.pane !== undefined && f.pane !== 0)
66744
67314
  return;
67315
+ const seq = this.screen.seq;
66745
67316
  this.screen = applyFrame(this.screen, f);
67317
+ if (this.screen.seq !== seq && !this.inResizeQuiet(this.screen.changedAt))
67318
+ this.activityChangedAt = this.screen.changedAt;
66746
67319
  this.framePending = true;
66747
67320
  if (!this.claudeCodeVersion) {
66748
67321
  const m = this.screen.lines.join(`
@@ -66753,8 +67326,11 @@ class PaneSessionImpl {
66753
67326
  this.step(() => this.evaluate());
66754
67327
  }
66755
67328
  onDisconnected(client) {
66756
- if (this.client === client)
67329
+ if (this.client === client) {
66757
67330
  this.client = null;
67331
+ this.gate = null;
67332
+ this.gateTrusted = false;
67333
+ }
66758
67334
  if (this.reaping || !alive(this.magmux))
66759
67335
  return;
66760
67336
  if (isTerminalPhase(this.phase))
@@ -66946,11 +67522,15 @@ class PaneSessionImpl {
66946
67522
  if (!this.client)
66947
67523
  return;
66948
67524
  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
67525
  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);
67526
+ const g = this.gateNow();
67527
+ if (!g)
67528
+ return;
67529
+ this.disagree("registry_idle_gate_starting", this.phase === "BOOTING" && idle && (g.reason === "starting" || g.reason === "no_input_box"), now, REGISTRY_GATE_BOOT_DISAGREE_MS);
67530
+ this.disagree("registry_waiting_gate_no_dialog", status === "waiting" && g.ready, now);
67531
+ this.disagree("gate_dialog_registry_not_waiting", g.dialog !== null && RUNTIME_DIALOG_KINDS.has(g.dialog) && (idle || status === "busy"), now);
67532
+ this.disagree("gate_ready_box_not_located", g.ready && locateBox(this.screen) === null, now);
67533
+ this.disagree("gate_dialog_screen_unrecognised", (g.dialog === "permission" || g.dialog === "plan" || g.dialog === "question") && choiceDialog(this.screen) === null, now);
66954
67534
  }
66955
67535
  disagree(key, holds, now, forMs = REGISTRY_DISAGREE_MS) {
66956
67536
  if (!holds) {
@@ -66986,33 +67566,42 @@ class PaneSessionImpl {
66986
67566
  }
66987
67567
  evalBoot() {
66988
67568
  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)) {
67569
+ const g = this.gateNow();
67570
+ if (g?.ready) {
66997
67571
  this.bootReady();
66998
67572
  return;
66999
67573
  }
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 });
67574
+ const staticFor = this.screen.seq === 0 ? 0 : now - this.screen.changedAt;
67575
+ const settled = staticFor >= this.t("bootStaticMs", BOOT_STATIC_MS);
67576
+ const block = g && settled ? this.bootBlock(g) : null;
67577
+ if (block) {
67578
+ this.fire(block.ev, { detail: block.detail });
67006
67579
  return;
67007
67580
  }
67008
67581
  if (now - this.startedAtMs >= (this.o.bootTimeoutMs ?? BOOT_TIMEOUT_MS)) {
67009
- const blocked = this.screen.seq > 0 && staticFor >= staticMs && r.kind !== "booting";
67582
+ const blocked = this.screen.seq > 0 && settled && (g?.reason?.startsWith("dialog:") === true || nameBootScreen(this.screen).kind !== "booting");
67583
+ const gate = g ? `gate: ${g.reason ?? "ready"}${g.detail ? ` (${g.detail})` : ""}` : "gate: unknown";
67010
67584
  this.fire("boot_deadline", {
67011
67585
  reason: blocked ? "boot_blocked" : "boot_timeout",
67012
- detail: screenTailOf(this.screen)
67586
+ detail: `${screenTailOf(this.screen)}
67587
+ ${gate}`
67013
67588
  });
67014
67589
  }
67015
67590
  }
67591
+ bootBlock(g) {
67592
+ if (g.dialog && BOOT_DIALOG_KINDS.has(g.dialog))
67593
+ return { ev: "boot_dialog", detail: dialogText(this.screen) };
67594
+ const named = nameBootScreen(this.screen);
67595
+ if (named.kind === "dialog")
67596
+ return { ev: "boot_dialog", detail: named.text };
67597
+ if (g.reason === "vim_mode")
67598
+ return { ev: "boot_blocked", detail: "vim editing mode is on (-- INSERT -- under the box)" };
67599
+ if (g.reason?.startsWith("dialog:"))
67600
+ return { ev: "boot_blocked", detail: screenText(this.screen) };
67601
+ if (named.kind === "choice" && g.reason !== null && GATE_KINDLESS_REASONS.has(g.reason))
67602
+ return { ev: "boot_blocked", detail: named.text };
67603
+ return null;
67604
+ }
67016
67605
  bootReady() {
67017
67606
  this.bootReadyAtMs = Date.now();
67018
67607
  if (this.queue.length === 0) {
@@ -67038,7 +67627,7 @@ class PaneSessionImpl {
67038
67627
  const head = this.queue.shift();
67039
67628
  if (isControl(head)) {
67040
67629
  this.exitRequested = true;
67041
- this.typeLine(head.trim(), true);
67630
+ this.submitControl(head.trim());
67042
67631
  return;
67043
67632
  }
67044
67633
  this.fire("admit");
@@ -67062,6 +67651,9 @@ class PaneSessionImpl {
67062
67651
  this.screenTurn = null;
67063
67652
  this.rewakeOfSettled = false;
67064
67653
  this.settledChatOffset = null;
67654
+ this.turnAcceptedAt = null;
67655
+ this.witnessMissingCounted = false;
67656
+ this.abandonedIndex = null;
67065
67657
  if (plan.mode === "refused" || plan.mode === "control") {
67066
67658
  this.anomaly(`delivery_refused:${plan.mode === "refused" ? plan.reason : "control"}`);
67067
67659
  this.admission = null;
@@ -67077,79 +67669,385 @@ class PaneSessionImpl {
67077
67669
  }
67078
67670
  const delivery = (plan.mode === "file" || plan.mode === "command") && plan.file && plan.fileContent !== undefined ? { file: plan.file, text: plan.fileContent } : null;
67079
67671
  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);
67672
+ const bound = initial ? this.o.admitTimeoutMs ?? ADMIT_TIMEOUT_MS : ADMIT_TIMEOUT_MS;
67673
+ const adm = newAdmission({ index, text, plan, initial, bound });
67674
+ this.admission = adm;
67675
+ this.runAdmission(adm);
67676
+ }
67677
+ admitting(adm) {
67678
+ return this.admission === adm && this.phase === "ADMITTING";
67092
67679
  }
67093
- async deliver(adm) {
67094
- if (await this.typeAndSubmit(adm.plan))
67095
- adm.deliveredAt = Date.now();
67680
+ async runAdmission(adm) {
67681
+ try {
67682
+ while (this.admitting(adm) && adm.submittedUnproven === null && !adm.stopped) {
67683
+ if (Date.now() >= adm.deadline)
67684
+ return;
67685
+ if (adm.panel) {
67686
+ await this.pause(adm, TICK_MS);
67687
+ continue;
67688
+ }
67689
+ if (!this.client || !this.gateTrusted) {
67690
+ await this.awaitTrustedClient(adm);
67691
+ continue;
67692
+ }
67693
+ if (await this.beforeSend(adm) !== "send")
67694
+ continue;
67695
+ if (!this.admitting(adm) || !await this.attemptOnce(adm))
67696
+ return;
67697
+ }
67698
+ } finally {
67699
+ adm.inFlight = false;
67700
+ }
67096
67701
  }
67097
- async typeAndSubmit(plan) {
67098
- if (plan.mode !== "typed" && plan.mode !== "file" && plan.mode !== "command")
67702
+ awaitTrustedClient(adm) {
67703
+ return this.waitFor(() => !this.admitting(adm) || !!this.client && this.gateTrusted, Math.max(0, adm.deadline - Date.now()));
67704
+ }
67705
+ async attemptOnce(adm) {
67706
+ const c = this.client;
67707
+ if (!c)
67708
+ return true;
67709
+ const step = adm.step;
67710
+ const shape = sendShape(step, adm.line);
67711
+ const rt = readyTimeoutFor(step, adm.line, adm.deadline - Date.now());
67712
+ if (rt === null)
67099
67713
  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);
67714
+ const timeout = sendBudgetMs({ ...shape.budget, readyTimeoutMs: rt }) + SEND_CLIENT_SLACK_MS;
67715
+ const sentAt = Date.now();
67716
+ adm.inFlight = true;
67717
+ adm.attempts++;
67718
+ let reply;
67719
+ try {
67720
+ reply = await c.request({ ...shape.msg, readyTimeoutMs: rt }, timeout);
67721
+ } finally {
67722
+ adm.inFlight = false;
67110
67723
  }
67724
+ const out = classifySendReply(reply);
67725
+ adm.last = out;
67726
+ this.noteLastSend(out, adm.attempts);
67727
+ if (!this.admitting(adm))
67728
+ return false;
67729
+ await this.onSendOutcome(adm, out, step, { client: c, until: sentAt + timeout });
67111
67730
  return true;
67112
67731
  }
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;
67732
+ async onSendOutcome(adm, out, step, sent) {
67733
+ switch (out.kind) {
67734
+ case "accepted":
67735
+ this.onGateAccepted(adm, out);
67736
+ return;
67737
+ case "in_box":
67738
+ if (step !== "type") {
67739
+ this.anomaly("send_unexpected:in_box");
67740
+ this.stopAdmission(adm, "magmux answered in_box to a send that carried an Enter");
67741
+ return;
67125
67742
  }
67743
+ if (out.retyped)
67744
+ this.anomaly(`gate_retyped:${out.retypeCause ?? "unknown"}`);
67745
+ await this.menuCheck(adm);
67746
+ adm.step = "enter";
67747
+ return;
67748
+ case "busy":
67749
+ await this.onBusy(adm, out, step);
67750
+ return;
67751
+ case "not_accepted":
67752
+ await this.onNotAccepted(adm, out, step);
67753
+ return;
67754
+ case "bad_request":
67755
+ this.anomaly("send_bad_request");
67756
+ this.stopAdmission(adm, `send_bad_request: ${out.error}`);
67757
+ return;
67758
+ case "reply_lost":
67759
+ this.anomaly("send_reply_lost");
67760
+ if (step === "type") {
67761
+ adm.lostType = sent;
67762
+ markClearFirst(adm);
67763
+ return;
67764
+ }
67765
+ adm.submittedUnproven = "reply_lost";
67766
+ return;
67767
+ case "unsent":
67768
+ await this.pause(adm, BUSY_RETRY_MS);
67769
+ return;
67770
+ case "pane_gone":
67771
+ adm.stopped = true;
67772
+ return;
67773
+ case "unexpected":
67774
+ this.anomaly(`send_unexpected:${out.code}`);
67775
+ this.stopAdmission(adm, `send_unexpected: ${out.code}${out.error ? `: ${out.error}` : ""}`);
67776
+ return;
67777
+ default:
67778
+ assertNever(out);
67779
+ }
67780
+ }
67781
+ onGateAccepted(adm, out) {
67782
+ if (out.accept === "resent_enter")
67783
+ this.anomaly("gate_resent_enter");
67784
+ if (out.accept === "submitted_by_other")
67785
+ this.anomaly("gate_submitted_by_other");
67786
+ if (out.retyped)
67787
+ this.anomaly(`gate_retyped:${out.retypeCause ?? "unknown"}`);
67788
+ if (out.dismissed.length > 0)
67789
+ this.anomaly("gate_dismissed_unexpected");
67790
+ if (out.evidence === "transcript") {
67791
+ this.anomaly("gate_transcript_evidence");
67792
+ if (!this.witnessNow()) {
67793
+ this.anomaly("gate_transcript_evidence_unwitnessed");
67794
+ adm.submittedUnproven = "unconfirmed";
67795
+ return;
67126
67796
  }
67127
- await Bun.sleep(500);
67128
67797
  }
67129
- this.anomaly(`delivery_failed:${why}`);
67798
+ this.acceptAdmission(adm);
67799
+ }
67800
+ async onBusy(adm, out, step) {
67801
+ const reason = out.reason || "unknown";
67802
+ if (this.stopOnGateBlock(adm, reason, out.dialog))
67803
+ return;
67804
+ if (out.dialog === "select" && step === "enter") {
67805
+ this.noteOnce(adm, "gate_dialog_at_admission:select");
67806
+ adm.panel = { showingSince: null, escaped: false, entered: false };
67807
+ return;
67808
+ }
67809
+ if (out.dialog) {
67810
+ this.noteOnce(adm, `gate_dialog_at_admission:${out.dialog}`);
67811
+ } else if (out.typed) {
67812
+ this.anomaly("gate_typed_withheld");
67813
+ markClearFirst(adm);
67814
+ } else if (reason === "box_not_empty") {
67815
+ this.anomaly("gate_box_not_empty");
67816
+ markClearFirst(adm);
67817
+ } else {
67818
+ this.noteOnce(adm, `gate_busy:${reason}`);
67819
+ }
67820
+ await this.pause(adm, BUSY_RETRY_MS);
67821
+ }
67822
+ stopOnGateBlock(adm, reason, dialog) {
67823
+ if (dialog && BOOT_DIALOG_KINDS.has(dialog)) {
67824
+ this.anomaly(`gate_boot_dialog:${dialog}`);
67825
+ this.stopAdmission(adm, dialogText(this.screen) || `busy ${reason}`, "first_run_dialog");
67826
+ return true;
67827
+ }
67828
+ if (reason === "vim_mode") {
67829
+ this.anomaly("gate_vim_mode");
67830
+ this.stopAdmission(adm, "busy vim_mode: vim editing mode is on (-- INSERT -- under the box)");
67831
+ return true;
67832
+ }
67130
67833
  return false;
67131
67834
  }
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}`));
67835
+ async precheckGate(adm) {
67836
+ const g = this.gateNow();
67837
+ if (!g || g.ready)
67838
+ return "send";
67839
+ if (this.stopOnGateBlock(adm, g.reason ?? "unknown", g.dialog))
67840
+ return "stopped";
67841
+ const kind = g.dialog;
67842
+ if (kind !== "permission" && kind !== "question" && kind !== "plan")
67843
+ return "send";
67844
+ this.noteOnce(adm, `gate_dialog_at_admission:${kind}`);
67845
+ await this.waitFor(() => !this.admitting(adm) || this.gateNow()?.dialog !== kind, Math.max(0, adm.deadline - Date.now()));
67846
+ return "waited";
67847
+ }
67848
+ async beforeSend(adm) {
67849
+ if (adm.step === "enter")
67850
+ return "send";
67851
+ await this.clearLeftover(adm);
67852
+ if (!this.admitting(adm))
67853
+ return "stopped";
67854
+ return this.precheckGate(adm);
67855
+ }
67856
+ async onNotAccepted(adm, out, step) {
67857
+ switch (out.reason) {
67858
+ case "text_mismatch":
67859
+ if (out.maybeSubmitted) {
67860
+ this.anomaly("gate_maybe_submitted_by_other");
67861
+ adm.submittedUnproven = "maybe_submitted_by_other";
67862
+ return;
67863
+ }
67864
+ this.anomaly("gate_text_mismatch");
67865
+ markClearFirst(adm);
67866
+ if (adm.plan.mode === "command")
67867
+ adm.step = "type";
67868
+ await this.pause(adm, MISMATCH_SETTLE_MS);
67869
+ return;
67870
+ case "empty_box":
67871
+ if (step === "enter") {
67872
+ this.anomaly("gate_command_left_box");
67873
+ adm.submittedUnproven = "maybe_submitted_by_other";
67874
+ return;
67875
+ }
67876
+ this.anomaly("gate_empty_box");
67877
+ await this.pause(adm, BUSY_RETRY_MS);
67878
+ return;
67879
+ case "still_in_box":
67880
+ if (this.witnessNow()) {
67881
+ this.acceptAdmission(adm);
67882
+ return;
67883
+ }
67884
+ this.anomaly("gate_still_in_box");
67885
+ if (adm.stuckRetried) {
67886
+ adm.submittedUnproven = "still_in_box_twice";
67887
+ return;
67888
+ }
67889
+ adm.stuckRetried = true;
67890
+ await this.clearBox(adm);
67891
+ if (adm.plan.mode === "command")
67892
+ adm.step = "type";
67893
+ return;
67894
+ case "unconfirmed":
67895
+ this.anomaly("gate_unconfirmed");
67896
+ adm.submittedUnproven = "unconfirmed";
67897
+ if (step === "enter")
67898
+ adm.panel = { showingSince: null, escaped: false, entered: true };
67899
+ return;
67900
+ default:
67901
+ assertNever(out.reason);
67902
+ }
67903
+ }
67904
+ acceptAdmission(adm) {
67905
+ this.step(() => {
67906
+ if (this.admitting(adm))
67907
+ this.accept();
67908
+ });
67909
+ }
67910
+ stopAdmission(adm, detail, reason) {
67911
+ adm.stopped = true;
67912
+ this.step(() => {
67913
+ if (!this.admitting(adm))
67914
+ return;
67915
+ this.admission = null;
67916
+ if (adm.initial) {
67917
+ this.fire("admit_deadline", { reason, detail: `${detail}
67918
+ ${screenTailOf(this.screen)}` });
67919
+ return;
67920
+ }
67921
+ this.anomaly("send_not_accepted");
67922
+ this.abandonedIndex = adm.index;
67923
+ this.fire("admit_abandoned");
67924
+ });
67925
+ }
67926
+ witnessNow() {
67927
+ return this.witnessed(this.pollFollower(true).current);
67928
+ }
67929
+ noteOnce(adm, key) {
67930
+ if (adm.noted.has(key))
67931
+ return;
67932
+ adm.noted.add(key);
67933
+ this.anomaly(key);
67934
+ }
67935
+ pause(adm, ms) {
67936
+ return this.waitFor(() => !this.admitting(adm), ms);
67937
+ }
67938
+ freshReady(after) {
67939
+ const g = this.gateNow();
67940
+ return !!g && g.receivedAt > after && g.ready;
67941
+ }
67942
+ async clearBox(adm) {
67943
+ const at = Date.now();
67944
+ await this.key("ctrl-u");
67945
+ await this.waitFor(() => !this.admitting(adm) || this.freshReady(at), CLEAR_WAIT_MS);
67946
+ }
67947
+ async clearLeftover(adm) {
67948
+ const lost = adm.lostType;
67949
+ if (lost) {
67950
+ adm.lostType = null;
67951
+ if (lost.client !== this.client || lost.client.isClosed)
67952
+ await this.pause(adm, Math.max(0, lost.until - Date.now()));
67953
+ if (!this.admitting(adm))
67954
+ return;
67955
+ }
67956
+ if (adm.clearFirst)
67957
+ await this.clearOwnLeftover(adm);
67958
+ else if (this.gateNow()?.reason === "box_not_empty")
67959
+ await this.clearBox(adm);
67960
+ }
67961
+ async clearOwnLeftover(adm) {
67962
+ const after = adm.clearAfter + GATE_POLL_MS;
67963
+ const newer = () => {
67964
+ const g = this.gateNow();
67965
+ return g && g.receivedAt > after ? g : null;
67966
+ };
67967
+ await this.waitFor(() => !this.admitting(adm) || newer() !== null, CLEAR_WAIT_MS + GATE_POLL_MS);
67968
+ if (!this.admitting(adm))
67969
+ return;
67970
+ const g = newer();
67971
+ if (g?.ready) {
67972
+ adm.clearFirst = false;
67973
+ return;
67974
+ }
67975
+ if (g && (g.dialog !== null || g.reason === "vim_mode"))
67976
+ return;
67977
+ adm.clearFirst = false;
67978
+ await this.clearBox(adm);
67979
+ }
67980
+ async menuCheck(adm) {
67981
+ const name = adm.line.match(/^\/\S+/)?.[0] ?? "";
67982
+ await this.waitFor(() => {
67983
+ if (!this.admitting(adm))
67984
+ return true;
67985
+ const box = locateBox(this.screen);
67986
+ return !!box && locatedBoxText(this.screen, box).startsWith(name);
67987
+ }, 2 * TICK_MS);
67988
+ if (!this.admitting(adm))
67989
+ return;
67990
+ const box = locateBox(this.screen);
67991
+ if (!box) {
67992
+ this.anomaly("menu_unverified");
67993
+ return;
67994
+ }
67995
+ const menu = menuAbove(this.screen.lines, box.top);
67996
+ if (menu.menuOpen && menu.menuItem !== name)
67997
+ await this.key("escape");
67998
+ }
67999
+ noteLastSend(out, attempts) {
68000
+ const s = { kind: out.kind, attempts };
68001
+ switch (out.kind) {
68002
+ case "accepted":
68003
+ s.accept = out.accept;
68004
+ s.evidence = out.evidence;
68005
+ if (out.retypeCause)
68006
+ s.retypeCause = out.retypeCause;
68007
+ break;
68008
+ case "in_box":
68009
+ s.accept = "in_box";
68010
+ if (out.retypeCause)
68011
+ s.retypeCause = out.retypeCause;
68012
+ break;
68013
+ case "busy":
68014
+ case "not_accepted":
68015
+ s.reason = out.reason;
68016
+ break;
68017
+ case "bad_request":
68018
+ s.reason = "bad_request";
68019
+ break;
68020
+ case "reply_lost":
68021
+ case "unsent":
68022
+ case "pane_gone":
68023
+ case "unexpected":
68024
+ s.reason = out.code;
68025
+ break;
68026
+ default:
68027
+ assertNever(out);
68028
+ }
68029
+ this.lastSend = s;
68030
+ return s;
68031
+ }
68032
+ async submitControl(line) {
68033
+ const c = this.client;
68034
+ if (!c) {
68035
+ this.anomaly("control_send:unsent");
68036
+ return;
67143
68037
  }
67144
- if (landed)
67145
- this.anomaly("send_reply_lost");
67146
- return landed;
68038
+ const rt = SEND_READY_WAIT_DEFAULT_MS;
68039
+ const shape = sendShape("submit", line);
68040
+ const r = await c.request({ ...shape.msg, readyTimeoutMs: rt }, sendBudgetMs({ ...shape.budget, readyTimeoutMs: rt }) + SEND_CLIENT_SLACK_MS);
68041
+ const out = classifySendReply(r);
68042
+ const s = this.noteLastSend(out, 1);
68043
+ if (out.kind !== "accepted")
68044
+ this.anomaly(`control_send:${out.kind}${s.reason ? `:${s.reason}` : ""}`);
67147
68045
  }
67148
68046
  async key(k) {
67149
68047
  const c = this.client;
67150
68048
  if (!c)
67151
68049
  return false;
67152
- const r = await c.request({ type: "send", pane: 0, keys: [k], enter: false }, 1e4);
68050
+ const r = await c.request({ type: "send", pane: 0, keys: [k], enter: false, noGate: true }, 1e4);
67153
68051
  return r.ok;
67154
68052
  }
67155
68053
  nextFrame(ms) {
@@ -67169,139 +68067,134 @@ class PaneSessionImpl {
67169
68067
  const adm = this.admission;
67170
68068
  if (!adm)
67171
68069
  return;
67172
- const cur = view.current;
67173
- if (cur && cur.index === adm.index && cur.acceptedAt !== null) {
68070
+ if (this.witnessed(view.current)) {
67174
68071
  this.accept();
67175
68072
  return;
67176
68073
  }
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))
68074
+ if (adm.panel)
68075
+ this.panelStep(adm);
68076
+ if (!this.admitting(adm))
67180
68077
  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")
67186
- return;
67187
- if (now - adm.startedAt >= (this.o.admitTimeoutMs ?? ADMIT_TIMEOUT_MS))
68078
+ if (Date.now() >= adm.deadline && !adm.inFlight)
67188
68079
  this.admitExpired(adm);
67189
68080
  }
67190
- panelOpen() {
67191
- return !inputBox(this.screen) && !hasChoiceDialog(this.screen) && /Esc to cancel/.test(screenText(this.screen));
68081
+ panelShowing(g) {
68082
+ return !!g && (g.reason === "no_input_box" || g.dialog === "select") && /Esc to cancel/.test(flatText(this.screen.lines));
67192
68083
  }
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
- })();
68084
+ panelStep(adm) {
68085
+ const p = adm.panel;
68086
+ if (!p)
67254
68087
  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");
68088
+ const g = this.gateNow();
68089
+ if (!p.escaped) {
68090
+ if (!this.panelShowing(g)) {
68091
+ p.showingSince = null;
68092
+ return;
68093
+ }
68094
+ const now = Date.now();
68095
+ p.showingSince ??= now;
68096
+ if (now - p.showingSince < PANEL_SETTLE_MS)
68097
+ return;
68098
+ p.escaped = true;
68099
+ this.anomaly(g?.dialog === "select" ? "picker_dismissed" : "panel_dismissed");
67259
68100
  this.key("escape");
67260
- }
67261
- }
67262
- maybeDegradedAccept(adm, view) {
67263
- if (view.availability === "ok")
67264
68101
  return;
67265
- const box = inputBox(this.screen);
67266
- if (!box || box.text !== "")
68102
+ }
68103
+ if (!g)
67267
68104
  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)
68105
+ if (g.ready) {
68106
+ this.admission = null;
68107
+ this.anomaly("panel_command");
68108
+ if (adm.initial)
68109
+ this.fire("admit_deadline", {
68110
+ detail: `${adm.text.trim()} opened a panel and wrote no transcript record`
68111
+ });
68112
+ else
68113
+ this.fire("admit_abandoned");
67272
68114
  return;
67273
- this.turnSource = "screen";
67274
- this.anomaly("degraded_mode");
67275
- this.screenTurn = { echo: line, acceptedAt: Date.now(), errorLogSize: this.errorLogSize() };
67276
- this.accept();
68115
+ }
68116
+ if (!p.entered && g.reason === "box_not_empty")
68117
+ adm.panel = null;
67277
68118
  }
67278
68119
  accept() {
67279
68120
  this.admission = null;
67280
68121
  this.initialDelivery = false;
67281
- this.lastChatSeenAt = Date.now();
68122
+ const now = Date.now();
68123
+ this.lastChatSeenAt = now;
68124
+ this.turnAcceptedAt = now;
68125
+ this.turnErrorLogSize = this.errorLogSize();
67282
68126
  this.fire("prompt_accepted");
67283
68127
  }
67284
68128
  admitExpired(adm) {
67285
68129
  this.admission = null;
67286
68130
  if (adm.initial) {
67287
- this.fire("admit_deadline", { detail: screenTailOf(this.screen) });
68131
+ this.fire("admit_deadline", {
68132
+ detail: `${this.notAcceptedLine(adm)}
68133
+ ${screenTailOf(this.screen)}`
68134
+ });
67288
68135
  return;
67289
68136
  }
67290
68137
  this.anomaly("send_not_accepted");
68138
+ this.abandonedIndex = adm.index;
67291
68139
  this.fire("admit_abandoned");
67292
68140
  }
67293
- screenFacts() {
68141
+ notAcceptedLine(adm) {
68142
+ const secs = Math.round((Date.now() - adm.startedAt) / 1000);
68143
+ switch (adm.submittedUnproven) {
68144
+ case "maybe_submitted_by_other":
68145
+ return "magmux could not tell whether the prompt was submitted (the box emptied without its Enter) and no transcript record of it appeared";
68146
+ case "unconfirmed":
68147
+ return `magmux not_accepted unconfirmed: Enter was pressed with no evidence it was submitted, and no transcript record appeared in ${secs} s`;
68148
+ case "reply_lost": {
68149
+ const code = adm.last?.kind === "reply_lost" ? adm.last.code : "client_lost";
68150
+ return `the magmux send reply was lost (${code}) and no transcript record of the prompt appeared in ${secs} s`;
68151
+ }
68152
+ case "still_in_box_twice":
68153
+ return "magmux not_accepted still_in_box: the prompt stayed in the input box after Enter, twice";
68154
+ case null:
68155
+ return `prompt not accepted in ${secs} s; ${describeLastReply(adm, this.gateNow())}`;
68156
+ default:
68157
+ return assertNever(adm.submittedUnproven);
68158
+ }
68159
+ }
68160
+ corroborationFacts() {
67294
68161
  if (!this.client)
67295
68162
  return null;
67296
- const box = inputBox(this.screen);
68163
+ const g = this.gateNow();
67297
68164
  return {
67298
- boxEmpty: !!box && box.text === "",
67299
- choiceDialog: hasChoiceDialog(this.screen),
67300
- working: isWorking(this.screen),
67301
- aboveBoxQuietMs: Date.now() - this.screen.aboveBoxChangedAt
68165
+ gate: g ? { ready: g.ready, dialog: g.dialog, reason: g.reason } : null,
68166
+ working: workingAboveBox(this.screen),
68167
+ aboveBoxQuietMs: this.aboveBoxQuietMs()
67302
68168
  };
67303
68169
  }
68170
+ aboveBoxQuietMs() {
68171
+ return locateBox(this.screen) ? Date.now() - this.screen.aboveBoxChangedAt : null;
68172
+ }
68173
+ checkUnwitnessed(view) {
68174
+ const at = this.turnAcceptedAt;
68175
+ if (at === null || this.rewakeOfSettled || this.witnessed(view.current))
68176
+ return;
68177
+ const since = Date.now() - at;
68178
+ const missing = view.availability === "absent" || view.availability === "empty";
68179
+ if (view.availability === "off" || missing && since >= this.t("degradedEntryMs", DEGRADED_ENTRY_MS)) {
68180
+ const d = this.currentDelivery;
68181
+ this.turnSource = "screen";
68182
+ this.anomaly("degraded_mode");
68183
+ this.screenTurn = {
68184
+ echo: d && d.mode !== "refused" ? d.line : "",
68185
+ acceptedAt: at,
68186
+ errorLogSize: this.turnErrorLogSize
68187
+ };
68188
+ return;
68189
+ }
68190
+ if (view.availability === "ok" && since >= ADMIT_TIMEOUT_MS && !this.witnessMissingCounted) {
68191
+ this.witnessMissingCounted = true;
68192
+ this.anomaly("witness_missing");
68193
+ }
68194
+ }
67304
68195
  evalRunning(view) {
68196
+ if (this.turnSource === "transcript")
68197
+ this.checkUnwitnessed(view);
67305
68198
  if (this.turnSource === "screen") {
67306
68199
  this.evalScreenTurn(view);
67307
68200
  return;
@@ -67321,22 +68214,24 @@ class PaneSessionImpl {
67321
68214
  if (this.detectBlocked(t))
67322
68215
  return;
67323
68216
  const now = Date.now();
68217
+ const transcriptQuietMs = now - (view.lastAppendAt ?? this.startedAtMs);
67324
68218
  const decision = decideSettle({
67325
68219
  turn: t,
67326
68220
  session: view.session,
67327
- transcriptQuietMs: now - (view.lastAppendAt ?? this.startedAtMs),
68221
+ transcriptQuietMs,
67328
68222
  chatQuietMs: now - this.lastChatSeenAt,
67329
- screen: this.screenFacts(),
68223
+ facts: this.corroborationFacts(),
67330
68224
  paneExited: false,
67331
68225
  secondaryQuietMs: this.secondaryQuietMs,
67332
- corroborationMs: this.corroborationMs
68226
+ corroborationMs: this.corroborationMs,
68227
+ corroborationGraceMs: this.t("gateGraceMs", GATE_GRACE_MS)
67333
68228
  });
67334
68229
  if (decision.settled) {
67335
68230
  this.settle(t, decision, null);
67336
68231
  return;
67337
68232
  }
67338
68233
  this.activity = decision.activity;
67339
- this.checkTurnEndMissing();
68234
+ this.checkTurnEndMissing(t, transcriptQuietMs);
67340
68235
  this.checkAgentCountMismatch(t);
67341
68236
  }
67342
68237
  checkEscape() {
@@ -67347,14 +68242,14 @@ class PaneSessionImpl {
67347
68242
  this.anomaly("escape_not_effective");
67348
68243
  this.escapeSentAt = null;
67349
68244
  }
67350
- checkTurnEndMissing() {
67351
- if (this.activity !== "finishing") {
68245
+ checkTurnEndMissing(t, transcriptQuietMs) {
68246
+ if (this.activity !== "finishing" || t.turnDurationAfterLast || t.interruptAfterLast) {
67352
68247
  this.turnEndMissing = false;
67353
68248
  return;
67354
68249
  }
67355
68250
  if (!this.client)
67356
68251
  return;
67357
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68252
+ const quiet = this.aboveBoxQuietMs() ?? transcriptQuietMs;
67358
68253
  const missing = quiet >= TURN_END_MISSING_FACTOR * this.secondaryQuietMs && !stopHookRunning(this.screen);
67359
68254
  if (missing && !this.turnEndMissing)
67360
68255
  this.anomaly("turn_end_record_missing");
@@ -67374,14 +68269,19 @@ class PaneSessionImpl {
67374
68269
  let kind = null;
67375
68270
  if (pending.name === "AskUserQuestion")
67376
68271
  kind = "question";
67377
- else if (this.client && hasChoiceDialog(this.screen))
67378
- kind = "permission";
68272
+ else {
68273
+ const dialog = this.gateNow()?.dialog ?? null;
68274
+ if (dialog === "permission" || dialog === "plan")
68275
+ kind = "permission";
68276
+ else if (dialog === "question")
68277
+ kind = "question";
68278
+ }
67379
68279
  if (!kind)
67380
68280
  return false;
67381
68281
  this.blockedToolId = pending.id;
67382
68282
  this.fire(kind === "question" ? "blocked_question" : "blocked_permission");
67383
68283
  this.activity = pending.name;
67384
- const text = blockText3(pending, choiceDialog(this.screen)?.text ?? null);
68284
+ const text = blockText3(pending, this.screen.seq > 0 ? dialogText(this.screen) || null : null);
67385
68285
  let v;
67386
68286
  try {
67387
68287
  v = this.o.onBlocked({ kind, tool: pending.name, text });
@@ -67394,6 +68294,16 @@ class PaneSessionImpl {
67394
68294
  }
67395
68295
  evalIdle(view) {
67396
68296
  const t = view.current;
68297
+ if (t && this.abandonedIndex !== null && t.index === this.abandonedIndex && t.acceptedAt !== null && !this.exitRequested) {
68298
+ this.abandonedIndex = null;
68299
+ this.anomaly("late_witness");
68300
+ this.turnAcceptedAt = Date.now();
68301
+ this.turnErrorLogSize = this.errorLogSize();
68302
+ this.lastChatSeenAt = Date.now();
68303
+ this.fire("rewake");
68304
+ this.activity = "thinking";
68305
+ return;
68306
+ }
67397
68307
  if (!t || this.settledChatOffset === null || this.exitRequested)
67398
68308
  return;
67399
68309
  if (t.lastChatOffset !== null && t.lastChatOffset > this.settledChatOffset) {
@@ -67472,10 +68382,10 @@ class PaneSessionImpl {
67472
68382
  return !!t && t.index === this.turnIndex && t.acceptedAt !== null;
67473
68383
  }
67474
68384
  screenSettleReady(view, answer, apiError) {
67475
- const box = inputBox(this.screen);
67476
- if (!box || box.text !== "" || hasChoiceDialog(this.screen) || isWorking(this.screen))
68385
+ const g = this.gateNow();
68386
+ if (!g || !g.ready || g.dialog !== null || workingAboveBox(this.screen) === true)
67477
68387
  return false;
67478
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68388
+ const quiet = this.aboveBoxQuietMs() ?? Date.now() - this.screen.changedAt;
67479
68389
  if (view.availability === "off" && quiet >= this.secondaryQuietMs)
67480
68390
  return true;
67481
68391
  return (!!answer || !!apiError) && quiet >= 2 * this.t("screenSettleQuietMs", SCREEN_SETTLE_QUIET_MS);
@@ -67554,7 +68464,7 @@ class PaneSessionImpl {
67554
68464
  session: view.session,
67555
68465
  transcriptQuietMs: Number.MAX_SAFE_INTEGER,
67556
68466
  chatQuietMs: Number.MAX_SAFE_INTEGER,
67557
- screen: null,
68467
+ facts: null,
67558
68468
  paneExited: true,
67559
68469
  secondaryQuietMs: this.secondaryQuietMs,
67560
68470
  corroborationMs: 0
@@ -67604,7 +68514,7 @@ ${screenTailOf(this.screen)}`
67604
68514
  snapshot() {
67605
68515
  const view = this.follower.view();
67606
68516
  const terminal = isTerminalPhase(this.phase);
67607
- const lastAct = Math.max(this.screen.changedAt, view.lastAppendAt ?? 0);
68517
+ const lastAct = Math.max(this.activityChangedAt, view.lastAppendAt ?? 0);
67608
68518
  const cur = view.current;
67609
68519
  const hasTranscript = view.availability === "ok" && view.usage.tokensIn + view.usage.tokensOut > 0;
67610
68520
  return {
@@ -67689,6 +68599,115 @@ ${screenTailOf(this.screen)}`
67689
68599
  return { ok: false, reason: "terminal" };
67690
68600
  return { ok: true, queued: this.queue.length };
67691
68601
  }
68602
+ async resize(cols, rows) {
68603
+ const size = clampPaneSize(cols, rows);
68604
+ if (isTerminalPhase(this.phase))
68605
+ return this.lastSizeOutcome();
68606
+ if (!this.supportsResize)
68607
+ throw new ContractErrorException("resize_unsupported", 'this magmux has no resize op (capabilities.verbs lacks "resize"); a newer magmux is needed');
68608
+ if (!this.client)
68609
+ return this.pendingOutcome(size);
68610
+ if (samePaneSize(this.appliedSize, size) && samePaneSize(this.desiredSize, size) && !this.resizeDraining)
68611
+ return { ...size, changed: false, seq: this.screen.seq };
68612
+ this.desiredSize = size;
68613
+ return new Promise((resolve, reject) => {
68614
+ this.resizeWaiters.push({ resolve, reject });
68615
+ this.kickResize();
68616
+ });
68617
+ }
68618
+ pendingOutcome(size) {
68619
+ this.desiredSize = { ...size };
68620
+ return { ...size, changed: true, seq: this.screen.seq, pending: true };
68621
+ }
68622
+ lastSizeOutcome() {
68623
+ return { cols: this.screen.cols, rows: this.screen.rows, changed: false, seq: this.screen.seq };
68624
+ }
68625
+ inResizeQuiet(at) {
68626
+ return this.resizeQuietFrom > 0 && at >= this.resizeQuietFrom && at < this.resizeQuietUntil;
68627
+ }
68628
+ extendResizeQuiet() {
68629
+ const now = Date.now();
68630
+ if (now >= this.resizeQuietUntil)
68631
+ this.resizeQuietFrom = now;
68632
+ this.resizeQuietUntil = Math.max(this.resizeQuietUntil, now + RESIZE_QUIET_MS);
68633
+ }
68634
+ resizeOwed() {
68635
+ return !!this.client && this.supportsResize && !isTerminalPhase(this.phase) && !samePaneSize(this.appliedSize, this.desiredSize) && !samePaneSize(this.lastSentSize, this.desiredSize);
68636
+ }
68637
+ kickResize() {
68638
+ if (this.resizeDraining)
68639
+ return;
68640
+ this.resizeDraining = true;
68641
+ this.drainResize().finally(() => {
68642
+ this.resizeDraining = false;
68643
+ if (this.resizeWaiters.length > 0 || this.resizeOwed())
68644
+ this.kickResize();
68645
+ });
68646
+ }
68647
+ async drainResize() {
68648
+ let first = true;
68649
+ while (first || this.resizeWaiters.length > 0 || this.resizeOwed()) {
68650
+ first = false;
68651
+ const batch = this.resizeWaiters.splice(0);
68652
+ const answer = (r) => {
68653
+ for (const w of batch)
68654
+ w.resolve(r);
68655
+ };
68656
+ const fail = (e) => {
68657
+ for (const w of batch)
68658
+ w.reject(e);
68659
+ };
68660
+ if (isTerminalPhase(this.phase)) {
68661
+ answer(this.lastSizeOutcome());
68662
+ continue;
68663
+ }
68664
+ const want = { ...this.desiredSize };
68665
+ const c = this.client;
68666
+ if (!c) {
68667
+ answer(this.pendingOutcome(want));
68668
+ continue;
68669
+ }
68670
+ if (!this.supportsResize) {
68671
+ fail(new ContractErrorException("resize_unsupported", "this magmux has no resize op"));
68672
+ continue;
68673
+ }
68674
+ this.lastSentSize = want;
68675
+ this.extendResizeQuiet();
68676
+ const r = await c.request({ type: "resize", pane: 0, cols: want.cols, rows: want.rows }, RESIZE_REPLY_MS);
68677
+ if (isTerminalPhase(this.phase)) {
68678
+ answer(this.lastSizeOutcome());
68679
+ continue;
68680
+ }
68681
+ if (!r.ok) {
68682
+ if (r.code === "client_lost" || r.code === "client_timeout") {
68683
+ this.appliedSize = null;
68684
+ this.extendResizeQuiet();
68685
+ }
68686
+ if (r.code === "client_lost" || r.code === "client_closed") {
68687
+ answer(this.pendingOutcome(this.desiredSize));
68688
+ continue;
68689
+ }
68690
+ fail(new ContractErrorException("delivery_unavailable", `magmux did not resize the pane: ${r.code}${r.error ? `: ${r.error}` : ""}`));
68691
+ continue;
68692
+ }
68693
+ const set = {
68694
+ cols: typeof r.result.cols === "number" ? r.result.cols : want.cols,
68695
+ rows: typeof r.result.rows === "number" ? r.result.rows : want.rows
68696
+ };
68697
+ this.appliedSize = set;
68698
+ if (r.result.changed !== true) {
68699
+ answer({ ...set, changed: false, seq: this.screen.seq });
68700
+ continue;
68701
+ }
68702
+ this.extendResizeQuiet();
68703
+ await this.waitFor(() => isTerminalPhase(this.phase) || this.screen.cols === set.cols && this.screen.rows === set.rows, RESIZE_SCREEN_MS);
68704
+ if (isTerminalPhase(this.phase)) {
68705
+ answer(this.lastSizeOutcome());
68706
+ continue;
68707
+ }
68708
+ answer({ ...set, changed: true, seq: this.screen.seq });
68709
+ }
68710
+ }
67692
68711
  turnAnswer(index) {
67693
68712
  const done = this.answers.get(index);
67694
68713
  if (done !== undefined)
@@ -67704,6 +68723,7 @@ ${screenTailOf(this.screen)}`
67704
68723
  diagnostics() {
67705
68724
  const cur = this.follower.view().current;
67706
68725
  const rec = this.registryRec;
68726
+ const g = this.gate;
67707
68727
  return {
67708
68728
  magmuxStderr: this.stderrHead,
67709
68729
  registry: rec ? { status: rec.status, waitingFor: rec.waitingFor, version: rec.version } : null,
@@ -67713,7 +68733,9 @@ ${screenTailOf(this.screen)}`
67713
68733
  linesReturned: cur.readCoverage.linesReturned,
67714
68734
  complete: cur.readCoverage.complete,
67715
68735
  reads: cur.readCoverage.reads
67716
- } : null
68736
+ } : null,
68737
+ gate: g && this.client ? { ready: g.ready, reason: g.reason, detail: g.detail, ageMs: Date.now() - g.receivedAt } : null,
68738
+ lastSend: this.lastSend ? { ...this.lastSend } : null
67717
68739
  };
67718
68740
  }
67719
68741
  cancel() {
@@ -67770,6 +68792,9 @@ ${screenTailOf(this.screen)}`
67770
68792
  if (this.ticker)
67771
68793
  clearInterval(this.ticker);
67772
68794
  this.ticker = null;
68795
+ if (this.gateTimer)
68796
+ clearInterval(this.gateTimer);
68797
+ this.gateTimer = null;
67773
68798
  await this.captureGroupBeforeReap();
67774
68799
  const c = this.client;
67775
68800
  if (c && !c.isClosed && !this.paneClosed) {
@@ -67881,6 +68906,94 @@ function signalQuietly(target, sig) {
67881
68906
  process.kill(target, sig);
67882
68907
  } catch {}
67883
68908
  }
68909
+ function launchSize(o) {
68910
+ return o.initialSize ? clampPaneSize(o.initialSize.cols, o.initialSize.rows) : { cols: DEFAULT_PANE_COLS, rows: DEFAULT_PANE_ROWS };
68911
+ }
68912
+ function newAdmission(a) {
68913
+ const now = Date.now();
68914
+ return {
68915
+ index: a.index,
68916
+ text: a.text,
68917
+ plan: a.plan,
68918
+ line: a.plan.line,
68919
+ initial: a.initial,
68920
+ startedAt: now,
68921
+ deadline: now + a.bound,
68922
+ step: a.plan.mode === "command" ? "type" : "submit",
68923
+ inFlight: false,
68924
+ attempts: 0,
68925
+ submittedUnproven: null,
68926
+ stuckRetried: false,
68927
+ panel: null,
68928
+ stopped: false,
68929
+ clearFirst: false,
68930
+ clearAfter: 0,
68931
+ lostType: null,
68932
+ last: null,
68933
+ noted: new Set
68934
+ };
68935
+ }
68936
+ function markClearFirst(adm) {
68937
+ adm.clearFirst = true;
68938
+ adm.clearAfter = Date.now();
68939
+ }
68940
+ function sendShape(step, line) {
68941
+ switch (step) {
68942
+ case "submit":
68943
+ return {
68944
+ msg: { type: "send", pane: 0, text: line, typed: true, enter: true },
68945
+ budget: { text: line, keys: 1, submit: true }
68946
+ };
68947
+ case "type":
68948
+ return {
68949
+ msg: { type: "send", pane: 0, text: line, typed: true, enter: false },
68950
+ budget: { text: line, keys: 0, submit: false }
68951
+ };
68952
+ case "enter":
68953
+ return {
68954
+ msg: { type: "send", pane: 0, keys: ["enter"], enter: false },
68955
+ budget: { text: "", keys: 1, submit: true }
68956
+ };
68957
+ default:
68958
+ return assertNever(step);
68959
+ }
68960
+ }
68961
+ function fixedSendMs(step, line) {
68962
+ const rt = SEND_READY_WAIT_DEFAULT_MS;
68963
+ return sendBudgetMs({ ...sendShape(step, line).budget, readyTimeoutMs: rt }) - rt;
68964
+ }
68965
+ function readyTimeoutFor(step, line, leftMs) {
68966
+ const reserve = step === "type" ? fixedSendMs("enter", line) + SEND_CLIENT_SLACK_MS + MIN_READY_TIMEOUT_MS : 0;
68967
+ const rt = Math.min(SEND_READY_WAIT_DEFAULT_MS, leftMs - fixedSendMs(step, line) - SEND_CLIENT_SLACK_MS - reserve);
68968
+ return rt < MIN_READY_TIMEOUT_MS ? null : Math.floor(rt);
68969
+ }
68970
+ function describeLastReply(adm, gate) {
68971
+ const o = adm.last;
68972
+ if (!o) {
68973
+ const g = gate ? gate.ready ? "ready" : gate.reason ?? "not ready" : "unknown";
68974
+ return adm.attempts === 0 ? `no gated send fitted before the deadline (gate: ${g})` : `no magmux reply (gate: ${g})`;
68975
+ }
68976
+ switch (o.kind) {
68977
+ case "busy":
68978
+ return `last magmux reply: busy ${o.reason || "unknown"}${o.typed ? " (typed, Enter withheld)" : ""}${gate?.detail ? ` (${gate.detail})` : ""}`;
68979
+ case "not_accepted":
68980
+ return `last magmux reply: not_accepted ${o.reason}`;
68981
+ case "accepted":
68982
+ return `last magmux reply: ok ${o.accept} (${o.evidence})`;
68983
+ case "in_box":
68984
+ return "last magmux reply: in_box (the command was typed, its Enter never sent)";
68985
+ case "bad_request":
68986
+ return `last magmux reply: bad_request ${o.error}`;
68987
+ case "reply_lost":
68988
+ case "unsent":
68989
+ case "pane_gone":
68990
+ return `last magmux reply: ${o.code}`;
68991
+ case "unexpected":
68992
+ return `last magmux reply: ${o.code}`;
68993
+ default:
68994
+ return assertNever(o);
68995
+ }
68996
+ }
67884
68997
  function isControl(text) {
67885
68998
  const t = text.trim();
67886
68999
  return t === "/exit" || t === "/quit";
@@ -67900,12 +69013,18 @@ function preparePane(o, root, parentEnv) {
67900
69013
  try {
67901
69014
  const paneId = mintPaneId(o.kind, o.label);
67902
69015
  const sockPath = sockPathFor(root, paneId);
67903
- const cwd = realpathSync3(o.cwd);
69016
+ const cwd = realpathSync4(o.cwd);
67904
69017
  const launch = o.claudishSpawn ?? resolveClaudishLaunch(parentEnv);
67905
69018
  dirs = createLaunchDirs(root);
67906
69019
  const argv = buildClaudishPaneArgv(o.spawnModel, o.sessionUuid, dirs.turnDir, o.callerFlags);
67907
69020
  writeLauncherFiles(dirs.ctlDir, launcherScript(cwd, launch, argv));
67908
- const { magmuxEnv } = buildPaneEnv({ parentEnv, slotEnv: o.slotEnv, cwd, ctlDir: dirs.ctlDir });
69021
+ const { magmuxEnv } = buildPaneEnv({
69022
+ parentEnv,
69023
+ slotEnv: o.slotEnv,
69024
+ cwd,
69025
+ ctlDir: dirs.ctlDir,
69026
+ ...launchSize(o)
69027
+ });
67909
69028
  return { paneId, sockPath, cwd, ...dirs, magmuxEnv };
67910
69029
  } catch (e) {
67911
69030
  for (const d of dirs ? [dirs.ctlDir, dirs.turnDir] : [])
@@ -67974,21 +69093,38 @@ async function startPaneSession(o) {
67974
69093
  await session.start({ magmuxBinary: magmux.binary, env: p.magmuxEnv, cwd: p.cwd });
67975
69094
  return session;
67976
69095
  }
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;
69096
+ 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
69097
  var init_pane_session = __esm(() => {
67979
69098
  init_parent_session();
67980
69099
  init_process_tree();
67981
69100
  init_redact();
69101
+ init_box_locate();
69102
+ init_contract();
69103
+ init_gate_binding();
69104
+ init_input_gate();
67982
69105
  init_magmux_client();
67983
69106
  init_pane_launch();
67984
69107
  init_pane_registry();
67985
69108
  init_process_identity();
67986
69109
  init_prompt_delivery();
67987
69110
  init_screen_classifier();
69111
+ init_screen_model();
67988
69112
  init_session_registry();
67989
69113
  init_settle_rule();
67990
69114
  init_slot_state();
67991
69115
  init_transcript_follower();
69116
+ GATE_GRACE_MS = SECONDARY_QUIET_MS;
69117
+ GATE_KINDLESS_REASONS = new Set([
69118
+ "starting",
69119
+ "terminal_not_ready",
69120
+ "no_input_box"
69121
+ ]);
69122
+ RUNTIME_DIALOG_KINDS = new Set([
69123
+ "permission",
69124
+ "question",
69125
+ "plan",
69126
+ "select"
69127
+ ]);
67992
69128
  EVENT_REASON = {
67993
69129
  boot_dialog: "first_run_dialog",
67994
69130
  boot_blocked: "boot_blocked",
@@ -68040,10 +69176,10 @@ __export(exports_session_discovery, {
68040
69176
  transcriptPathFor: () => transcriptPathFor
68041
69177
  });
68042
69178
  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";
69179
+ import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, readdirSync as readdirSync8, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
69180
+ import { basename as basename3, join as join49 } from "path";
68045
69181
  function projectsDir(env = process.env) {
68046
- return join48(claudeConfigDir(env), "projects");
69182
+ return join49(claudeConfigDir(env), "projects");
68047
69183
  }
68048
69184
  function slugForPath(absPath) {
68049
69185
  return projectDirNameFor(absPath);
@@ -68051,9 +69187,9 @@ function slugForPath(absPath) {
68051
69187
  function transcriptPathFor(cwd, sessionUuid, projectsRoot = projectsDir()) {
68052
69188
  let real = cwd;
68053
69189
  try {
68054
- real = realpathSync4(cwd);
69190
+ real = realpathSync5(cwd);
68055
69191
  } catch {}
68056
- return join48(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
69192
+ return join49(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
68057
69193
  }
68058
69194
  function isAgentSession(row) {
68059
69195
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -68100,7 +69236,7 @@ function projectDirs() {
68100
69236
  }
68101
69237
  }
68102
69238
  function sessionsIn(dirName) {
68103
- const dir = join48(projectsDir(), dirName);
69239
+ const dir = join49(projectsDir(), dirName);
68104
69240
  let names;
68105
69241
  try {
68106
69242
  names = readdirSync8(dir).filter((n) => n.endsWith(".jsonl"));
@@ -68109,13 +69245,13 @@ function sessionsIn(dirName) {
68109
69245
  }
68110
69246
  const rows = [];
68111
69247
  for (const n of names) {
68112
- const file = join48(dir, n);
69248
+ const file = join49(dir, n);
68113
69249
  try {
68114
69250
  const st = statSync11(file);
68115
69251
  if (st.size === 0)
68116
69252
  continue;
68117
69253
  const row = {
68118
- id: basename2(n, ".jsonl"),
69254
+ id: basename3(n, ".jsonl"),
68119
69255
  file,
68120
69256
  mtimeMs: st.mtimeMs,
68121
69257
  birthtimeMs: st.birthtimeMs,
@@ -68217,7 +69353,7 @@ function discoverWorktreeGroups(repo) {
68217
69353
  if (hit) {
68218
69354
  path = hit.path;
68219
69355
  live = true;
68220
- name = hit.path === repo.root ? "(root)" : basename2(hit.path);
69356
+ name = hit.path === repo.root ? "(root)" : basename3(hit.path);
68221
69357
  } else if (at !== -1 && dir.startsWith(rootSlug)) {
68222
69358
  path = null;
68223
69359
  live = false;
@@ -68487,7 +69623,7 @@ var init_session_discovery = __esm(() => {
68487
69623
  // src/team-orchestrator.ts
68488
69624
  import { randomBytes as randomBytes8, randomUUID as randomUUID5 } from "crypto";
68489
69625
  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";
69626
+ import { basename as basename4, join as join50, resolve as resolve5 } from "path";
68491
69627
  function slotTerminal(e) {
68492
69628
  if (!e.session)
68493
69629
  return true;
@@ -68524,13 +69660,13 @@ function addressRun(path, runId) {
68524
69660
  pruneRuns();
68525
69661
  if (runId) {
68526
69662
  const run = teamRuns.get(runId) ?? null;
68527
- if (run && path !== undefined && resolve4(path) !== run.path)
69663
+ if (run && path !== undefined && resolve5(path) !== run.path)
68528
69664
  return null;
68529
69665
  return run;
68530
69666
  }
68531
69667
  if (path === undefined)
68532
69668
  return null;
68533
- const id = newestRunByPath.get(resolve4(path));
69669
+ const id = newestRunByPath.get(resolve5(path));
68534
69670
  return id ? teamRuns.get(id) ?? null : null;
68535
69671
  }
68536
69672
  function activeRunAt(path) {
@@ -68556,7 +69692,7 @@ function notableAnomalies(snap) {
68556
69692
  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
69693
  }
68558
69694
  function upstreamLogPath(path, id) {
68559
- return join49(path, "errors", `${id}-upstream.jsonl`);
69695
+ return join50(path, "errors", `${id}-upstream.jsonl`);
68560
69696
  }
68561
69697
  function neverSpawnedRow(e, m) {
68562
69698
  return {
@@ -68649,9 +69785,9 @@ function teamRunRowFromDisk(path, status) {
68649
69785
  const settled = slots.length > 0 && slots.every((s) => isTerminalState(s.state));
68650
69786
  const completions = Object.values(status.models ?? {}).map((m) => m?.completedAt ? Date.parse(m.completedAt) : Number.NaN).filter(Number.isFinite);
68651
69787
  return {
68652
- run_id: status.runId ?? basename3(path),
69788
+ run_id: status.runId ?? basename4(path),
68653
69789
  path,
68654
- kind: status.kind ?? (basename3(path) === "judging" ? "judge" : "run"),
69790
+ kind: status.kind ?? (basename4(path) === "judging" ? "judge" : "run"),
68655
69791
  started_at: status.startedAt,
68656
69792
  finished_at: settled ? new Date(completions.length ? Math.max(...completions) : statusMtimeMs(path) ?? Date.parse(status.startedAt)).toISOString() : null,
68657
69793
  state: settled ? "SETTLED" : "ACTIVE",
@@ -68661,14 +69797,14 @@ function teamRunRowFromDisk(path, status) {
68661
69797
  }
68662
69798
  function statusMtimeMs(path) {
68663
69799
  try {
68664
- return statSync12(join49(path, "status.json")).mtimeMs;
69800
+ return statSync12(join50(path, "status.json")).mtimeMs;
68665
69801
  } catch {
68666
69802
  return null;
68667
69803
  }
68668
69804
  }
68669
69805
  function readManifestOrNull(path) {
68670
69806
  try {
68671
- return JSON.parse(readFileSync31(join49(path, "manifest.json"), "utf-8"));
69807
+ return JSON.parse(readFileSync31(join50(path, "manifest.json"), "utf-8"));
68672
69808
  } catch {
68673
69809
  return null;
68674
69810
  }
@@ -68702,10 +69838,10 @@ async function cancelTeamRun(path, slot, runId) {
68702
69838
  function neverSpawnedCapture() {
68703
69839
  return {
68704
69840
  seq: 0,
68705
- cols: 160,
68706
- rows: 50,
69841
+ cols: DEFAULT_PANE_COLS,
69842
+ rows: DEFAULT_PANE_ROWS,
68707
69843
  cursor: { x: 0, y: 0 },
68708
- lines: Array.from({ length: 50 }, () => ""),
69844
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
68709
69845
  final: true
68710
69846
  };
68711
69847
  }
@@ -68722,6 +69858,18 @@ function captureTeamSlot(path, slot, sinceSeq, spans, runId) {
68722
69858
  }
68723
69859
  return e.session.capture(sinceSeq, { spans: spans === true });
68724
69860
  }
69861
+ async function resizeTeamSlot(path, slot, cols, rows, runId) {
69862
+ const run = addressRun(path, runId);
69863
+ if (!run)
69864
+ throw unknownRun(path, runId);
69865
+ const e = run.slots.get(slot);
69866
+ if (!e)
69867
+ throw new ContractErrorException("unknown_slot", `run ${run.runId} has no slot ${JSON.stringify(slot)}`);
69868
+ const where = { run_id: run.runId, path: run.path, slot };
69869
+ if (!e.session)
69870
+ return { ...where, cols: DEFAULT_PANE_COLS, rows: DEFAULT_PANE_ROWS, changed: false, seq: 0 };
69871
+ return { ...where, ...await e.session.resize(cols, rows) };
69872
+ }
68725
69873
  function teamLiveMaps(path, runId) {
68726
69874
  const run = addressRun(path, runId);
68727
69875
  if (!run)
@@ -68860,7 +70008,7 @@ function persistErrorLog(errorLogPath, header, sections) {
68860
70008
  } catch {}
68861
70009
  }
68862
70010
  function validateSessionPath(sessionPath) {
68863
- const resolved = resolve4(sessionPath);
70011
+ const resolved = resolve5(sessionPath);
68864
70012
  const cwd = process.cwd();
68865
70013
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68866
70014
  throw new Error(`Session path must be within current directory: ${sessionPath}`);
@@ -68868,7 +70016,7 @@ function validateSessionPath(sessionPath) {
68868
70016
  return resolved;
68869
70017
  }
68870
70018
  function readTeamInputFile(inputPath) {
68871
- const resolved = resolve4(inputPath);
70019
+ const resolved = resolve5(inputPath);
68872
70020
  const cwd = process.cwd();
68873
70021
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68874
70022
  throw new Error(`Input file must be within current directory: ${inputPath}`);
@@ -68886,14 +70034,14 @@ function setupSession(sessionPath, models, input) {
68886
70034
  if (models.length === 0) {
68887
70035
  throw new Error("At least one model is required");
68888
70036
  }
68889
- if (existsSync31(join49(sessionPath, "manifest.json"))) {
70037
+ if (existsSync31(join50(sessionPath, "manifest.json"))) {
68890
70038
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
68891
70039
  }
68892
- mkdirSync21(join49(sessionPath, "work"), { recursive: true });
68893
- mkdirSync21(join49(sessionPath, "errors"), { recursive: true });
70040
+ mkdirSync21(join50(sessionPath, "work"), { recursive: true });
70041
+ mkdirSync21(join50(sessionPath, "errors"), { recursive: true });
68894
70042
  if (input !== undefined) {
68895
- writeFileSync22(join49(sessionPath, "input.md"), input, "utf-8");
68896
- } else if (!existsSync31(join49(sessionPath, "input.md"))) {
70043
+ writeFileSync22(join50(sessionPath, "input.md"), input, "utf-8");
70044
+ } else if (!existsSync31(join50(sessionPath, "input.md"))) {
68897
70045
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
68898
70046
  }
68899
70047
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -68910,9 +70058,9 @@ function setupSession(sessionPath, models, input) {
68910
70058
  model: models[i],
68911
70059
  assignedAt: now
68912
70060
  };
68913
- mkdirSync21(join49(sessionPath, "work", anonId), { recursive: true });
70061
+ mkdirSync21(join50(sessionPath, "work", anonId), { recursive: true });
68914
70062
  }
68915
- writeFileSync22(join49(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
70063
+ writeFileSync22(join50(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
68916
70064
  const status = {
68917
70065
  startedAt: now,
68918
70066
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -68927,7 +70075,7 @@ function setupSession(sessionPath, models, input) {
68927
70075
  }
68928
70076
  ]))
68929
70077
  };
68930
- writeFileSync22(join49(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
70078
+ writeFileSync22(join50(sessionPath, "status.json"), JSON.stringify(status, null, 2), "utf-8");
68931
70079
  return manifest;
68932
70080
  }
68933
70081
  function assertValidRequirePattern(pattern) {
@@ -68955,14 +70103,14 @@ async function preflightTeamRun(opts) {
68955
70103
  throw new Error(`invalid_args: ${refusal}`);
68956
70104
  }
68957
70105
  await assertMagmuxAvailable();
68958
- assertNoActiveRun(resolve4(opts.path));
70106
+ assertNoActiveRun(resolve5(opts.path));
68959
70107
  const root = sockRootFor(opts.parentEnv ?? process.env);
68960
70108
  const live = livePaneCount(root);
68961
70109
  if (live + opts.slots > MAX_LIVE_PANES)
68962
70110
  throw new Error(`pane_limit: ${live} live panes for this user, ${opts.slots} more would exceed ${MAX_LIVE_PANES}`);
68963
70111
  }
68964
70112
  function mintRunId(path, startMs) {
68965
- return `${basename3(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
70113
+ return `${basename4(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
68966
70114
  }
68967
70115
  function startModels(sessionPath, opts = {}) {
68968
70116
  const flight = { aborted: false };
@@ -68986,10 +70134,10 @@ async function startModelsIn(flight, sessionPath, opts) {
68986
70134
  const flagCheck = checkChildFlags(claudeFlags);
68987
70135
  if (!flagCheck.ok)
68988
70136
  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");
70137
+ const path = resolve5(sessionPath);
70138
+ const manifest = JSON.parse(readFileSync31(join50(path, "manifest.json"), "utf-8"));
70139
+ const statusPath = join50(path, "status.json");
70140
+ const inputContent = readFileSync31(join50(path, "input.md"), "utf-8");
68993
70141
  const readAvailable = !flagsRemoveRead(claudeFlags);
68994
70142
  const refusal = deliveryRefusal(inputContent, readAvailable);
68995
70143
  if (refusal)
@@ -69027,13 +70175,13 @@ async function startModelsIn(flight, sessionPath, opts) {
69027
70175
  mkdirSync21(statsDir(path), { recursive: true });
69028
70176
  const entries = new Map;
69029
70177
  let run = null;
69030
- const errorLogPathOf = (id) => join49(path, "errors", `${id}.log`);
70178
+ const errorLogPathOf = (id) => join50(path, "errors", `${id}.log`);
69031
70179
  const upstreamLogOf = (id) => upstreamLogPath(path, id);
69032
70180
  const commandOf = (e) => ["claudish", "-i", "--model", e.spawnModel ?? e.model, "-y", "--quiet", ...claudeFlags].join(" ");
69033
70181
  function writeResponse(e, text) {
69034
70182
  e.answer = text;
69035
70183
  try {
69036
- writeFileSync22(join49(path, `response-${e.id}.md`), text, "utf-8");
70184
+ writeFileSync22(join50(path, `response-${e.id}.md`), text, "utf-8");
69037
70185
  } catch (err) {
69038
70186
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
69039
70187
  }
@@ -69350,7 +70498,7 @@ async function startModelsIn(flight, sessionPath, opts) {
69350
70498
  done.catch(() => {});
69351
70499
  return {
69352
70500
  runId,
69353
- teamSessionId: basename3(path),
70501
+ teamSessionId: basename4(path),
69354
70502
  sessionPath: path,
69355
70503
  slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
69356
70504
  done
@@ -69361,9 +70509,9 @@ async function runModels(sessionPath, opts = {}) {
69361
70509
  return handle.done;
69362
70510
  }
69363
70511
  async function judgeResponses(sessionPath, opts = {}) {
69364
- const active = activeRunAt(resolve4(sessionPath));
70512
+ const active = activeRunAt(resolve5(sessionPath));
69365
70513
  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`);
70514
+ 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
70515
  const responseFiles = readdirSync9(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
69368
70516
  if (responseFiles.length < 2) {
69369
70517
  throw new Error(`Need at least 2 responses to judge, found ${responseFiles.length}`);
@@ -69371,13 +70519,13 @@ async function judgeResponses(sessionPath, opts = {}) {
69371
70519
  const responses = {};
69372
70520
  for (const file of responseFiles) {
69373
70521
  const id = file.replace(/^response-/, "").replace(/\.md$/, "");
69374
- responses[id] = readFileSync31(join49(sessionPath, file), "utf-8");
70522
+ responses[id] = readFileSync31(join50(sessionPath, file), "utf-8");
69375
70523
  }
69376
- const input = readFileSync31(join49(sessionPath, "input.md"), "utf-8");
70524
+ const input = readFileSync31(join50(sessionPath, "input.md"), "utf-8");
69377
70525
  const judgePrompt = buildJudgePrompt(input, responses);
69378
- writeFileSync22(join49(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
70526
+ writeFileSync22(join50(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
69379
70527
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
69380
- const judgePath = join49(sessionPath, "judging");
70528
+ const judgePath = join50(sessionPath, "judging");
69381
70529
  mkdirSync21(judgePath, { recursive: true });
69382
70530
  setupSession(judgePath, judgeModels, judgePrompt);
69383
70531
  await runModels(judgePath, {
@@ -69388,11 +70536,11 @@ async function judgeResponses(sessionPath, opts = {}) {
69388
70536
  });
69389
70537
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
69390
70538
  const verdict = aggregateVerdict(votes, Object.keys(responses));
69391
- writeFileSync22(join49(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
70539
+ writeFileSync22(join50(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
69392
70540
  return verdict;
69393
70541
  }
69394
70542
  function getStatus(sessionPath) {
69395
- return JSON.parse(readFileSync31(join49(sessionPath, "status.json"), "utf-8"));
70543
+ return JSON.parse(readFileSync31(join50(sessionPath, "status.json"), "utf-8"));
69396
70544
  }
69397
70545
  function readTeamStatus(sessionPath) {
69398
70546
  try {
@@ -69428,7 +70576,7 @@ function fisherYatesShuffle(arr) {
69428
70576
  return arr;
69429
70577
  }
69430
70578
  function getDefaultJudgeModels(sessionPath) {
69431
- const manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70579
+ const manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69432
70580
  return Object.values(manifest.models).map((e) => e.model);
69433
70581
  }
69434
70582
  function buildJudgePrompt(input, responses) {
@@ -69491,7 +70639,7 @@ function parseJudgeVotes(judgePath, responseIds) {
69491
70639
  const judgeId = file.replace(/^response-/, "").replace(/\.md$/, "");
69492
70640
  let content;
69493
70641
  try {
69494
- content = readFileSync31(join49(judgePath, file), "utf-8");
70642
+ content = readFileSync31(join50(judgePath, file), "utf-8");
69495
70643
  } catch {
69496
70644
  continue;
69497
70645
  }
@@ -69543,7 +70691,7 @@ function aggregateVerdict(votes, responseIds) {
69543
70691
  function formatVerdict(verdict, sessionPath) {
69544
70692
  let manifest = null;
69545
70693
  try {
69546
- manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70694
+ manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69547
70695
  } catch {}
69548
70696
  let output = `# Team Verdict
69549
70697
 
@@ -69605,7 +70753,7 @@ import {
69605
70753
  statSync as statSync13,
69606
70754
  writeFileSync as writeFileSync23
69607
70755
  } from "fs";
69608
- import { join as join50, resolve as resolve5, sep } from "path";
70756
+ import { join as join51, resolve as resolve6, sep } from "path";
69609
70757
  function normaliseTimeoutSeconds(raw) {
69610
70758
  const n = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : raw;
69611
70759
  if (typeof n !== "number" || !Number.isFinite(n))
@@ -69771,7 +70919,7 @@ function dropLeadingFragment(tail) {
69771
70919
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
69772
70920
  }
69773
70921
  function diskAccounting(sessionDir) {
69774
- const stats = readTokenStatsAt(join50(sessionDir, "tokens.json"));
70922
+ const stats = readTokenStatsAt(join51(sessionDir, "tokens.json"));
69775
70923
  return {
69776
70924
  tokensIn: stats?.billed_input_tokens ?? null,
69777
70925
  tokensOut: stats?.output_tokens ?? null,
@@ -69794,10 +70942,10 @@ function diskProseBytes(tail, fileBytes) {
69794
70942
  function blankFinalCapture() {
69795
70943
  return {
69796
70944
  seq: 0,
69797
- cols: 160,
69798
- rows: 50,
70945
+ cols: DEFAULT_PANE_COLS,
70946
+ rows: DEFAULT_PANE_ROWS,
69799
70947
  cursor: { x: 0, y: 0 },
69800
- lines: Array.from({ length: 50 }, () => ""),
70948
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
69801
70949
  final: true
69802
70950
  };
69803
70951
  }
@@ -69842,7 +70990,7 @@ class SessionManager {
69842
70990
  let dir = "";
69843
70991
  for (let attempt = 0;; attempt++) {
69844
70992
  id = `team-${randomUUID6().replace(/-/g, "").slice(0, 8)}`;
69845
- dir = join50(this.sessionsDir, id);
70993
+ dir = join51(this.sessionsDir, id);
69846
70994
  try {
69847
70995
  mkdirSync22(dir);
69848
70996
  break;
@@ -69857,7 +71005,7 @@ class SessionManager {
69857
71005
  sessionId: id,
69858
71006
  parentClaudeSessionId: opts.parentClaudeSessionId,
69859
71007
  startedAt,
69860
- teamPath: resolve5(opts.teamPath),
71008
+ teamPath: resolve6(opts.teamPath),
69861
71009
  slots: opts.slots
69862
71010
  });
69863
71011
  this.teamRunStarts.set(id, startedAt);
@@ -69874,9 +71022,9 @@ class SessionManager {
69874
71022
  if (dir === null)
69875
71023
  throw new Error("not a record id");
69876
71024
  const completedAt = new Date().toISOString();
69877
- const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join50(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
71025
+ const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join51(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
69878
71026
  const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
69879
- writeJsonAtomic(join50(dir, "meta.json"), {
71027
+ writeJsonAtomic(join51(dir, "meta.json"), {
69880
71028
  kind: "team",
69881
71029
  status: outcome.status,
69882
71030
  startedAt,
@@ -69908,7 +71056,7 @@ class SessionManager {
69908
71056
  startedAt,
69909
71057
  ...rest
69910
71058
  };
69911
- writeJsonAtomic(join50(dir, "spawn.json"), body);
71059
+ writeJsonAtomic(join51(dir, "spawn.json"), body);
69912
71060
  }
69913
71061
  async createSession(opts) {
69914
71062
  if (this.activeSessions + this.starting >= this.maxSessions) {
@@ -69967,10 +71115,10 @@ class SessionManager {
69967
71115
  const claudeSessionId = randomUUID6();
69968
71116
  const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
69969
71117
  const startedAt = new Date().toISOString();
69970
- const sessionDir = opts.sessionDir ?? join50(this.sessionsDir, sessionId);
71118
+ const sessionDir = opts.sessionDir ?? join51(this.sessionsDir, sessionId);
69971
71119
  mkdirSync22(sessionDir, { recursive: true });
69972
71120
  if (opts.prompt)
69973
- writeFileSync23(join50(sessionDir, "prompt.md"), opts.prompt, "utf-8");
71121
+ writeFileSync23(join51(sessionDir, "prompt.md"), opts.prompt, "utf-8");
69974
71122
  this.writeSpawnRecord(sessionDir, {
69975
71123
  kind: "session",
69976
71124
  sessionId,
@@ -69983,7 +71131,7 @@ class SessionManager {
69983
71131
  const cwd = opts.cwd ?? process.cwd();
69984
71132
  const parentEnv = opts.parentEnv ?? this.parentEnv;
69985
71133
  const spawnModel = opts.spawnModel ?? null;
69986
- const tokenFile = opts.tokenFile ?? join50(sessionDir, "tokens.json");
71134
+ const tokenFile = opts.tokenFile ?? join51(sessionDir, "tokens.json");
69987
71135
  return {
69988
71136
  info: {
69989
71137
  sessionId,
@@ -70019,28 +71167,30 @@ class SessionManager {
70019
71167
  },
70020
71168
  session: null,
70021
71169
  scrollback: new ScrollbackBuffer(this.scrollbackCapacity),
70022
- outputLogStream: createWriteStream(join50(sessionDir, "output.log")),
71170
+ outputLogStream: createWriteStream(join51(sessionDir, "output.log")),
70023
71171
  sessionDir,
70024
71172
  cwd,
70025
71173
  tokenFile,
70026
- eventLogPath: join50(sessionDir, "events.jsonl"),
71174
+ eventLogPath: join51(sessionDir, "events.jsonl"),
70027
71175
  eventLogBytes: 0,
70028
- upstreamErrorLogPath: join50(sessionDir, "upstream-errors.jsonl"),
71176
+ upstreamErrorLogPath: join51(sessionDir, "upstream-errors.jsonl"),
70029
71177
  eventRing: [],
70030
71178
  proseBytes: 0,
70031
71179
  answeredTurns: new Map,
70032
- waitLogPath: join50(sessionDir, "waits.jsonl"),
71180
+ waitLogPath: join51(sessionDir, "waits.jsonl"),
70033
71181
  waitLogBytes: 0,
70034
71182
  waitSince: null,
70035
71183
  ended: false,
70036
71184
  evictHandle: null,
70037
71185
  lastLoggedTool: null,
70038
71186
  assistantIdsLogged: 0,
70039
- anomaliesLogged: new Set
71187
+ anomaliesLogged: new Set,
71188
+ requestedSize: null
70040
71189
  };
70041
71190
  }
70042
71191
  async startPane(entry, opts, p) {
70043
71192
  const info = entry.info;
71193
+ const launch = entry.requestedSize;
70044
71194
  const paneOpts = {
70045
71195
  kind: "s",
70046
71196
  label: info.sessionId,
@@ -70055,6 +71205,7 @@ class SessionManager {
70055
71205
  },
70056
71206
  shape: info.shape,
70057
71207
  ...opts.prompt ? { initialPrompt: opts.prompt } : {},
71208
+ ...launch ? { initialSize: launch } : {},
70058
71209
  readAvailable: p.readAvailable,
70059
71210
  decide: (turn) => this.decide(entry, turn),
70060
71211
  onBlocked: (b) => this.onBlocked(entry, b),
@@ -70070,6 +71221,12 @@ class SessionManager {
70070
71221
  this.failNeverSpawned(entry, err);
70071
71222
  throw err;
70072
71223
  }
71224
+ const asked = entry.requestedSize;
71225
+ entry.requestedSize = null;
71226
+ if (asked && (asked.cols !== launch?.cols || asked.rows !== launch?.rows))
71227
+ entry.session.resize(asked.cols, asked.rows).catch(() => {
71228
+ return;
71229
+ });
70073
71230
  if (!entry.ended)
70074
71231
  this.applySnapshot(entry, entry.session.snapshot());
70075
71232
  }
@@ -70163,7 +71320,7 @@ class SessionManager {
70163
71320
  try {
70164
71321
  const text = screenLines.join(`
70165
71322
  `).replace(/\s+$/, "");
70166
- writeFileSync23(join50(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
71323
+ writeFileSync23(join51(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
70167
71324
  `, "utf-8");
70168
71325
  } catch {}
70169
71326
  }
@@ -70177,7 +71334,7 @@ class SessionManager {
70177
71334
  entry.waitSince = null;
70178
71335
  }
70179
71336
  try {
70180
- writeJsonAtomic(join50(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
71337
+ writeJsonAtomic(join51(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
70181
71338
  } catch (err) {
70182
71339
  process.stderr.write(`[claudish] session ${entry.info.sessionId}: could not write meta.json: ` + `${err instanceof Error ? err.message : String(err)}
70183
71340
  `);
@@ -70321,6 +71478,8 @@ class SessionManager {
70321
71478
  registry: null,
70322
71479
  preambleBytes: 0,
70323
71480
  readCoverage: null,
71481
+ gate: null,
71482
+ lastSend: null,
70324
71483
  claudeCodeVersion: null,
70325
71484
  anomalies: entry.info.detail ? [entry.info.detail] : []
70326
71485
  };
@@ -70335,6 +71494,8 @@ class SessionManager {
70335
71494
  registry: pane.registry,
70336
71495
  preambleBytes: pane.preambleBytes,
70337
71496
  readCoverage: pane.readCoverage,
71497
+ gate: pane.gate,
71498
+ lastSend: pane.lastSend,
70338
71499
  claudeCodeVersion: snap.claudeCodeVersion,
70339
71500
  anomalies: snap.anomalies
70340
71501
  };
@@ -70348,6 +71509,26 @@ class SessionManager {
70348
71509
  const r = entry.session.cancel();
70349
71510
  return { session_id: sessionId, state: r.state, changed: r.changed };
70350
71511
  }
71512
+ async resizeSession(sessionId, cols, rows) {
71513
+ const entry = this.liveEntry(sessionId);
71514
+ if (!entry)
71515
+ throw unknownSession(sessionId);
71516
+ if (!entry.session) {
71517
+ if (isTerminalState(entry.info.state))
71518
+ return {
71519
+ session_id: sessionId,
71520
+ cols: DEFAULT_PANE_COLS,
71521
+ rows: DEFAULT_PANE_ROWS,
71522
+ changed: false,
71523
+ seq: 0
71524
+ };
71525
+ const size = clampPaneSize(cols, rows);
71526
+ entry.requestedSize = size;
71527
+ return { session_id: sessionId, ...size, changed: true, seq: 0, pending: true };
71528
+ }
71529
+ const r = await entry.session.resize(cols, rows);
71530
+ return { session_id: sessionId, ...r };
71531
+ }
70351
71532
  captureSession(sessionId, sinceSeq, spans) {
70352
71533
  const entry = this.liveEntry(sessionId);
70353
71534
  if (!entry)
@@ -70411,9 +71592,9 @@ class SessionManager {
70411
71592
  diskSessionDir(sessionId) {
70412
71593
  if (!SESSION_ID_RE.test(sessionId))
70413
71594
  return null;
70414
- const root = resolve5(this.sessionsDir);
70415
- const dir = resolve5(root, sessionId);
70416
- if (dir !== join50(root, sessionId))
71595
+ const root = resolve6(this.sessionsDir);
71596
+ const dir = resolve6(root, sessionId);
71597
+ if (dir !== join51(root, sessionId))
70417
71598
  return null;
70418
71599
  if (!dir.startsWith(root + sep))
70419
71600
  return null;
@@ -70432,10 +71613,10 @@ class SessionManager {
70432
71613
  } catch {
70433
71614
  return null;
70434
71615
  }
70435
- const spawnRecord = readJsonObject(join50(sessionDir, "spawn.json"), META_READ_LIMIT);
71616
+ const spawnRecord = readJsonObject(join51(sessionDir, "spawn.json"), META_READ_LIMIT);
70436
71617
  if (spawnRecord?.kind === "team")
70437
71618
  return null;
70438
- const meta = readJsonObject(join50(sessionDir, "meta.json"), META_READ_LIMIT);
71619
+ const meta = readJsonObject(join51(sessionDir, "meta.json"), META_READ_LIMIT);
70439
71620
  if (meta?.kind === "team")
70440
71621
  return null;
70441
71622
  const partial = meta === null;
@@ -70457,7 +71638,7 @@ class SessionManager {
70457
71638
  return stored;
70458
71639
  }
70459
71640
  diskOutput(record, tailLines) {
70460
- const tail = readTailText(join50(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
71641
+ const tail = readTailText(join51(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
70461
71642
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
70462
71643
  if (tail?.text)
70463
71644
  buffer.append(dropLeadingFragment(tail));
@@ -70477,12 +71658,12 @@ class SessionManager {
70477
71658
  }
70478
71659
  diskDiagnostics(record, limit) {
70479
71660
  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");
71661
+ const eventLogPath = join51(sessionDir, "events.jsonl");
71662
+ const upstreamErrorLogPath = join51(sessionDir, "upstream-errors.jsonl");
71663
+ const outputLogPath = join51(sessionDir, "output.log");
70483
71664
  const events = readTailLines2(eventLogPath, EVENT_TAIL_BYTES);
70484
71665
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
70485
- const screen = readTailText(join50(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
71666
+ const screen = readTailText(join51(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
70486
71667
  return {
70487
71668
  sessionId: info.sessionId,
70488
71669
  state: info.state,
@@ -70515,6 +71696,8 @@ class SessionManager {
70515
71696
  turnSource: info.turnSource,
70516
71697
  preambleBytes: 0,
70517
71698
  readCoverage: null,
71699
+ gate: null,
71700
+ lastSend: null,
70518
71701
  claudeCodeVersion: null,
70519
71702
  anomalies: record.partial ? [
70520
71703
  `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 +72050,7 @@ var init_progress_heartbeat = __esm(() => {
70867
72050
  import { spawn as spawn2 } from "child_process";
70868
72051
  import { mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync24 } from "fs";
70869
72052
  import { tmpdir as tmpdir2 } from "os";
70870
- import { join as join51 } from "path";
72053
+ import { join as join52 } from "path";
70871
72054
  function claudeCodePrintArgv(model, systemPromptFile) {
70872
72055
  const head = [PRINT_FLAGS[0], "--model", model, ...PRINT_FLAGS.slice(1)];
70873
72056
  return systemPromptFile !== undefined ? [...head, "--system-prompt-file", systemPromptFile] : [...head, "--system-prompt", ""];
@@ -70985,13 +72168,13 @@ async function runPromptViaClaudeCode(model, prompt, systemPrompt, opts = {}) {
70985
72168
  } catch (e) {
70986
72169
  throw new Error(`cannot start claudish (${parentEnv.CLAUDISH_BIN?.trim() || "claudish"}): ${e instanceof Error ? e.message : String(e)}`);
70987
72170
  }
70988
- const root = mkdtempSync3(join51(tmpdir2(), "claudish-run-prompt-"));
72171
+ const root = mkdtempSync3(join52(tmpdir2(), "claudish-run-prompt-"));
70989
72172
  try {
70990
- const cwd = join51(root, "cwd");
72173
+ const cwd = join52(root, "cwd");
70991
72174
  mkdirSync23(cwd, { mode: 448 });
70992
72175
  let systemPromptFile;
70993
72176
  if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
70994
- systemPromptFile = join51(root, "system-prompt.txt");
72177
+ systemPromptFile = join52(root, "system-prompt.txt");
70995
72178
  writeFileSync24(systemPromptFile, systemPrompt, { mode: 384 });
70996
72179
  }
70997
72180
  const argv = claudeCodePrintArgv(model, systemPromptFile);
@@ -71099,7 +72282,7 @@ var init_run_prompt_claude_code = __esm(() => {
71099
72282
  // src/mcp-server.ts
71100
72283
  import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync33, readdirSync as readdirSync10, writeFileSync as writeFileSync25 } from "fs";
71101
72284
  import { homedir as homedir35 } from "os";
71102
- import { dirname as dirname14, join as join52, resolve as resolve6 } from "path";
72285
+ import { dirname as dirname15, join as join53, resolve as resolve7 } from "path";
71103
72286
  import { fileURLToPath as fileURLToPath3 } from "url";
71104
72287
  async function loadAllModels(forceRefresh = false) {
71105
72288
  if (!forceRefresh && existsSync33(OPENROUTER_MODELS_CACHE_PATH)) {
@@ -71394,6 +72577,12 @@ function optionalInteger(raw, name) {
71394
72577
  throw new ContractErrorException("invalid_args", `'${name}' must be an integer`);
71395
72578
  return raw;
71396
72579
  }
72580
+ function requiredInteger(raw, name, mode) {
72581
+ const n = optionalInteger(raw, name);
72582
+ if (n === undefined)
72583
+ throw new ContractErrorException("invalid_args", `'${name}' is required${mode ? ` for mode '${mode}'` : ""}`);
72584
+ return n;
72585
+ }
71397
72586
  function teamStatusAnswer(path, runId) {
71398
72587
  let row = null;
71399
72588
  if (runId) {
@@ -71433,6 +72622,8 @@ async function teamContractVerb(mode, args) {
71433
72622
  const slot = optionalString(args.slot, "slot");
71434
72623
  const since = optionalInteger(args.since_seq, "since_seq");
71435
72624
  const spans = optionalBoolean(args.spans, "spans");
72625
+ const cols = mode === "resize" ? requiredInteger(args.cols, "cols", mode) : undefined;
72626
+ const rows = mode === "resize" ? requiredInteger(args.rows, "rows", mode) : undefined;
71436
72627
  switch (mode) {
71437
72628
  case "status":
71438
72629
  return contractAnswer(teamStatusAnswer(path, runId));
@@ -71443,6 +72634,11 @@ async function teamContractVerb(mode, args) {
71443
72634
  throw new ContractErrorException("invalid_args", "'slot' is required for mode 'capture'");
71444
72635
  return contractAnswer(captureTeamSlot(path, slot, since, spans, runId));
71445
72636
  }
72637
+ case "resize": {
72638
+ if (slot === undefined)
72639
+ throw new ContractErrorException("invalid_args", "'slot' is required for mode 'resize'");
72640
+ return contractAnswer(await resizeTeamSlot(path, slot, cols, rows, runId));
72641
+ }
71446
72642
  }
71447
72643
  } catch (e) {
71448
72644
  return contractErrorAnswer(e);
@@ -71816,18 +73012,18 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71816
73012
  });
71817
73013
  tools.push({
71818
73014
  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.",
73015
+ 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
73016
  inputSchema: {
71821
73017
  type: "object",
71822
73018
  properties: {
71823
73019
  mode: {
71824
73020
  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."
73021
+ enum: ["run", "judge", "run-and-judge", "status", "cancel", "list", "capture", "resize"],
73022
+ 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
73023
  },
71828
73024
  slot: {
71829
73025
  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."
73026
+ 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
73027
  },
71832
73028
  path: {
71833
73029
  type: "string",
@@ -71835,7 +73031,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71835
73031
  },
71836
73032
  run_id: {
71837
73033
  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'."
73034
+ 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
73035
  },
71840
73036
  since_seq: {
71841
73037
  type: "number",
@@ -71845,6 +73041,14 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71845
73041
  type: "boolean",
71846
73042
  description: "For 'capture': also return the colour/attribute spans of each line."
71847
73043
  },
73044
+ cols: {
73045
+ type: "integer",
73046
+ 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."
73047
+ },
73048
+ rows: {
73049
+ type: "integer",
73050
+ description: "For 'resize' (required): the terminal height in rows, clamped to 24\u20139999 and to " + "at most 4 000 000 cells (cols \xD7 rows)."
73051
+ },
71848
73052
  models: {
71849
73053
  type: "array",
71850
73054
  items: { type: "string" },
@@ -71886,7 +73090,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71886
73090
  heartbeat: true,
71887
73091
  handler: async (args, ctx) => {
71888
73092
  const mode = args.mode;
71889
- if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture") {
73093
+ if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture" || mode === "resize") {
71890
73094
  return teamContractVerb(mode, args);
71891
73095
  }
71892
73096
  try {
@@ -71918,7 +73122,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71918
73122
  const promptText = () => {
71919
73123
  if (input !== undefined)
71920
73124
  return input;
71921
- const existing = join52(resolved, "input.md");
73125
+ const existing = join53(resolved, "input.md");
71922
73126
  return existsSync33(existing) ? readFileSync33(existing, "utf-8") : undefined;
71923
73127
  };
71924
73128
  switch (mode) {
@@ -72077,16 +73281,16 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72077
73281
  const sp = session_path;
72078
73282
  for (const file of ["status.json", "manifest.json", "input.md"]) {
72079
73283
  try {
72080
- sessionData[file] = readFileSync33(join52(sp, file), "utf-8");
73284
+ sessionData[file] = readFileSync33(join53(sp, file), "utf-8");
72081
73285
  } catch {}
72082
73286
  }
72083
73287
  try {
72084
- const errorDir = join52(sp, "errors");
73288
+ const errorDir = join53(sp, "errors");
72085
73289
  if (existsSync33(errorDir)) {
72086
73290
  for (const f of readdirSync10(errorDir)) {
72087
73291
  if (f.endsWith(".log")) {
72088
73292
  try {
72089
- sessionData[`errors/${f}`] = readFileSync33(join52(errorDir, f), "utf-8");
73293
+ sessionData[`errors/${f}`] = readFileSync33(join53(errorDir, f), "utf-8");
72090
73294
  } catch {}
72091
73295
  }
72092
73296
  }
@@ -72096,7 +73300,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72096
73300
  for (const f of readdirSync10(sp)) {
72097
73301
  if (f.startsWith("response-") && f.endsWith(".md")) {
72098
73302
  try {
72099
- const content = readFileSync33(join52(sp, f), "utf-8");
73303
+ const content = readFileSync33(join53(sp, f), "utf-8");
72100
73304
  sessionData[f] = content.slice(0, 200) + (content.length > 200 ? "... (truncated)" : "");
72101
73305
  } catch {}
72102
73306
  }
@@ -72105,7 +73309,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72105
73309
  }
72106
73310
  let version = "unknown";
72107
73311
  try {
72108
- const pkgPath = join52(__dirname3, "../package.json");
73312
+ const pkgPath = join53(__dirname3, "../package.json");
72109
73313
  if (existsSync33(pkgPath)) {
72110
73314
  version = JSON.parse(readFileSync33(pkgPath, "utf-8")).version;
72111
73315
  }
@@ -72185,7 +73389,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72185
73389
  });
72186
73390
  tools.push({
72187
73391
  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).",
73392
+ 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.15.1 " + "(darwin and linux).",
72189
73393
  inputSchema: {
72190
73394
  type: "object",
72191
73395
  properties: {
@@ -72229,7 +73433,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72229
73433
  const requestedModel = args.model;
72230
73434
  const workDir = args.work_dir;
72231
73435
  const plan = await prehydrateCredentialsForSpawn([requestedModel], {
72232
- pin: workDir === undefined || resolve6(workDir) === process.cwd()
73436
+ pin: workDir === undefined || resolve7(workDir) === process.cwd()
72233
73437
  });
72234
73438
  const parentClaudeSessionId = await parentSessionForCall({
72235
73439
  hostPid: sessionManager.hostPid
@@ -72411,7 +73615,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72411
73615
  });
72412
73616
  tools.push({
72413
73617
  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.",
73618
+ 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
73619
  inputSchema: {
72416
73620
  type: "object",
72417
73621
  properties: {
@@ -72441,6 +73645,35 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72441
73645
  }
72442
73646
  }
72443
73647
  });
73648
+ tools.push({
73649
+ name: "resize_session",
73650
+ 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).",
73651
+ inputSchema: {
73652
+ type: "object",
73653
+ properties: {
73654
+ session_id: { type: "string", description: "Session ID from create_session" },
73655
+ cols: { type: "integer", description: "Terminal width in columns (clamped to 80\u20139999)" },
73656
+ rows: {
73657
+ type: "integer",
73658
+ description: "Terminal height in rows (clamped to 24\u20139999 and 4 000 000 cells)"
73659
+ }
73660
+ },
73661
+ required: ["session_id", "cols", "rows"]
73662
+ },
73663
+ group: "session",
73664
+ handler: async (args) => {
73665
+ try {
73666
+ const sessionId = optionalString(args.session_id, "session_id");
73667
+ if (sessionId === undefined)
73668
+ throw new ContractErrorException("invalid_args", "'session_id' is required");
73669
+ const cols = requiredInteger(args.cols, "cols");
73670
+ const rows = requiredInteger(args.rows, "rows");
73671
+ return contractAnswer(await sessionManager.resizeSession(sessionId, cols, rows));
73672
+ } catch (e) {
73673
+ return contractErrorAnswer(e);
73674
+ }
73675
+ }
73676
+ });
72444
73677
  return tools;
72445
73678
  }
72446
73679
  function resolveToolGroups(mode) {
@@ -72566,7 +73799,7 @@ Each session is an interactive Claude Code running the model in its own headless
72566
73799
  3. send_input may be called at any time; it is queued until the session is idle.
72567
73800
  4. COMPLETED: call get_output for the answer.
72568
73801
  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.
73802
+ 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
73803
 
72571
73804
  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
73805
  var init_mcp_server = __esm(() => {
@@ -72599,9 +73832,9 @@ var init_mcp_server = __esm(() => {
72599
73832
  import_dotenv2 = __toESM(require_main(), 1);
72600
73833
  import_dotenv2.config({ quiet: true });
72601
73834
  __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");
73835
+ __dirname3 = dirname15(__filename3);
73836
+ CLAUDISH_CACHE_DIR = join53(homedir35(), ".claudish");
73837
+ OPENROUTER_MODELS_CACHE_PATH = join53(CLAUDISH_CACHE_DIR, "openrouter-models.json");
72605
73838
  NEXT_STEP = {
72606
73839
  cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
72607
73840
  timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
@@ -72612,7 +73845,7 @@ var init_mcp_server = __esm(() => {
72612
73845
  prompt_not_accepted: "the REPL never took the prompt \u2014 read the evidence screen; retry",
72613
73846
  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
73847
  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",
73848
+ pane_lost: "magmux died \u2014 check `magmux --version` \u2265 0.15.1 and retry",
72616
73849
  blocked: "the child asked a question team cannot answer \u2014 make the prompt self-contained or use " + "create_session",
72617
73850
  api_error: "retry once, or route via a different provider (or@<model>)",
72618
73851
  refused: "the model refused \u2014 rephrase or drop the model",
@@ -72928,7 +74161,7 @@ var init_behavior_command = __esm(() => {
72928
74161
  import { spawn as spawn3 } from "child_process";
72929
74162
  import { existsSync as existsSync36, readFileSync as readFileSync36, writeFileSync as writeFileSync27 } from "fs";
72930
74163
  import { connect as netConnect2 } from "net";
72931
- import { join as join53 } from "path";
74164
+ import { join as join54 } from "path";
72932
74165
  import { setTimeout as wait } from "timers/promises";
72933
74166
  function paneRouteLine(explanation) {
72934
74167
  const origin = describeRouteExplanation(explanation);
@@ -73078,9 +74311,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
73078
74311
  const keep = opts?.keep ?? false;
73079
74312
  const manifest = setupSession(sessionPath, models, input);
73080
74313
  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");
74314
+ const gridfilePath = join54(sessionPath, "gridfile.txt");
74315
+ const prompt = readFileSync36(join54(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
74316
+ const rawPrompt = readFileSync36(join54(sessionPath, "input.md"), "utf-8");
73084
74317
  const usedBannerColors = new Set;
73085
74318
  const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
73086
74319
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
@@ -73112,7 +74345,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
73112
74345
  });
73113
74346
  const [{ results }] = await Promise.all([subscription, procExit]);
73114
74347
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
73115
- const statusPath = join53(sessionPath, "status.json");
74348
+ const statusPath = join54(sessionPath, "status.json");
73116
74349
  writeFileSync27(statusPath, JSON.stringify(status, null, 2), "utf-8");
73117
74350
  return status;
73118
74351
  }
@@ -73133,7 +74366,7 @@ var init_team_grid = __esm(() => {
73133
74366
 
73134
74367
  // src/team-cli.ts
73135
74368
  import { readFileSync as readFileSync37 } from "fs";
73136
- import { join as join54 } from "path";
74369
+ import { join as join55 } from "path";
73137
74370
  async function runPaneTeam(sessionPath, models, input, opts) {
73138
74371
  installPaneShutdownHooks({ exitAfter: true });
73139
74372
  try {
@@ -73185,7 +74418,7 @@ Options (run / run-and-judge):
73185
74418
  --timeout <secs> Grid modes only: magmux's own per-pane timeout (default: 300).
73186
74419
  json mode has no deadline \u2014 nothing kills a working model.
73187
74420
  json mode runs each model as an interactive pane in a headless
73188
- magmux (>= 0.14.0; darwin and linux).
74421
+ magmux (>= 0.15.1; darwin and linux).
73189
74422
  --grid Show all models in a magmux grid with live output + status bar
73190
74423
  --mode <m> run only: json (default, panes), default (the grid, as --grid)
73191
74424
  or interactive (as --interactive)
@@ -73278,7 +74511,7 @@ async function teamCommand(args) {
73278
74511
  }
73279
74512
  case "judge": {
73280
74513
  await judgeResponses(sessionPath, { judges });
73281
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74514
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73282
74515
  break;
73283
74516
  }
73284
74517
  case "run-and-judge": {
@@ -73294,7 +74527,7 @@ async function teamCommand(args) {
73294
74527
  });
73295
74528
  printStatus(status);
73296
74529
  await judgeResponses(sessionPath, { judges });
73297
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74530
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73298
74531
  break;
73299
74532
  }
73300
74533
  case "status": {
@@ -73883,7 +75116,7 @@ var init_keychain_command = __esm(() => {
73883
75116
  import { spawnSync as spawnSync2 } from "child_process";
73884
75117
  import { existsSync as existsSync37, unlinkSync as unlinkSync9 } from "fs";
73885
75118
  import { homedir as homedir36 } from "os";
73886
- import { join as join55 } from "path";
75119
+ import { join as join56 } from "path";
73887
75120
  async function defaultSuggestModel() {
73888
75121
  try {
73889
75122
  const tok = readSharedAntigravityToken();
@@ -74004,7 +75237,7 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
74004
75237
  async logout(deps) {
74005
75238
  deleteSharedAntigravityToken(deps);
74006
75239
  try {
74007
- const tokenFile = join55(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
75240
+ const tokenFile = join56(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
74008
75241
  if (existsSync37(tokenFile))
74009
75242
  unlinkSync9(tokenFile);
74010
75243
  } catch {}
@@ -82490,7 +83723,7 @@ import {
82490
83723
  writeFileSync as writeFileSync28
82491
83724
  } from "fs";
82492
83725
  import { homedir as homedir37, tmpdir as tmpdir3 } from "os";
82493
- import { dirname as dirname15, join as join56 } from "path";
83726
+ import { dirname as dirname16, join as join57 } from "path";
82494
83727
  import { isatty } from "tty";
82495
83728
  function releaseTerminalIsolation() {
82496
83729
  if (!restoreTerminal)
@@ -82555,7 +83788,7 @@ function hasResolvableAnthropicAuth(deps = {}) {
82555
83788
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
82556
83789
  return true;
82557
83790
  }
82558
- if (fileExists(join56(homedir37(), ".claude", ".credentials.json")))
83791
+ if (fileExists(join57(homedir37(), ".claude", ".credentials.json")))
82559
83792
  return true;
82560
83793
  return keychainProbe();
82561
83794
  }
@@ -82570,7 +83803,7 @@ function isProxyAuthMode(config) {
82570
83803
  }
82571
83804
  function managedSettingsPath() {
82572
83805
  if (isWindows2()) {
82573
- return join56(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
83806
+ return join57(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
82574
83807
  }
82575
83808
  if (process.platform === "darwin") {
82576
83809
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -82591,9 +83824,9 @@ function isWindows2() {
82591
83824
  }
82592
83825
  function createStatusLineScript(tokenFilePath) {
82593
83826
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82594
- const claudishDir = join56(homeDir, ".claudish");
83827
+ const claudishDir = join57(homeDir, ".claudish");
82595
83828
  const timestamp = Date.now();
82596
- const scriptPath = join56(claudishDir, `status-${timestamp}.js`);
83829
+ const scriptPath = join57(claudishDir, `status-${timestamp}.js`);
82597
83830
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
82598
83831
  const light = getThemeMode() === "light";
82599
83832
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -82731,7 +83964,7 @@ process.stdin.on('end', () => {
82731
83964
  }
82732
83965
  function initializeTokenFile(tokenFilePath) {
82733
83966
  try {
82734
- mkdirSync25(dirname15(tokenFilePath), { recursive: true });
83967
+ mkdirSync25(dirname16(tokenFilePath), { recursive: true });
82735
83968
  writeFileSync28(tokenFilePath, JSON.stringify({
82736
83969
  input_tokens: 0,
82737
83970
  output_tokens: 0,
@@ -82763,7 +83996,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
82763
83996
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
82764
83997
  continue;
82765
83998
  scanned++;
82766
- const full = join56(dir, name);
83999
+ const full = join57(dir, name);
82767
84000
  try {
82768
84001
  if (statSync14(full).mtimeMs >= cutoff)
82769
84002
  continue;
@@ -82792,9 +84025,9 @@ function parseSettingsArgSafe(value) {
82792
84025
  }
82793
84026
  function userSettingsFileCandidates(cwd) {
82794
84027
  return [
82795
- join56(homedir37(), ".claude", "settings.json"),
82796
- join56(cwd, ".claude", "settings.json"),
82797
- join56(cwd, ".claude", "settings.local.json")
84028
+ join57(homedir37(), ".claude", "settings.json"),
84029
+ join57(cwd, ".claude", "settings.json"),
84030
+ join57(cwd, ".claude", "settings.local.json")
82798
84031
  ];
82799
84032
  }
82800
84033
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
@@ -82835,12 +84068,12 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
82835
84068
  }
82836
84069
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
82837
84070
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82838
- const claudishDir = join56(homeDir, ".claudish");
84071
+ const claudishDir = join57(homeDir, ".claudish");
82839
84072
  try {
82840
84073
  mkdirSync25(claudishDir, { recursive: true });
82841
84074
  } catch {}
82842
84075
  const timestamp = Date.now();
82843
- const tempPath = join56(claudishDir, `settings-${timestamp}.json`);
84076
+ const tempPath = join57(claudishDir, `settings-${timestamp}.json`);
82844
84077
  const assignedFile = assignedTokenFile(process.env) !== null;
82845
84078
  const tokenFilePath = resolveTokenFilePath(port);
82846
84079
  cleanupStaleTokenFiles(claudishDir);
@@ -83219,7 +84452,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
83219
84452
  console.error(`
83220
84453
  Or set CLAUDE_PATH to your custom installation:`);
83221
84454
  const home = homedir37();
83222
- const localPath = isWindows2() ? join56(home, ".claude", "local", "claude.exe") : join56(home, ".claude", "local", "claude");
84455
+ const localPath = isWindows2() ? join57(home, ".claude", "local", "claude.exe") : join57(home, ".claude", "local", "claude");
83223
84456
  console.error(` export CLAUDE_PATH=${localPath}`);
83224
84457
  process.exit(1);
83225
84458
  }
@@ -83332,15 +84565,15 @@ async function findClaudeBinary() {
83332
84565
  }
83333
84566
  }
83334
84567
  const home = homedir37();
83335
- const localPath = isWindows ? join56(home, ".claude", "local", "claude.exe") : join56(home, ".claude", "local", "claude");
84568
+ const localPath = isWindows ? join57(home, ".claude", "local", "claude.exe") : join57(home, ".claude", "local", "claude");
83336
84569
  if (existsSync39(localPath)) {
83337
84570
  return localPath;
83338
84571
  }
83339
84572
  if (isWindows) {
83340
84573
  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")
84574
+ join57(home, "AppData", "Roaming", "npm", "claude.cmd"),
84575
+ join57(home, ".npm-global", "claude.cmd"),
84576
+ join57(home, "node_modules", ".bin", "claude.cmd")
83344
84577
  ];
83345
84578
  for (const path of windowsPaths) {
83346
84579
  if (existsSync39(path)) {
@@ -83351,11 +84584,11 @@ async function findClaudeBinary() {
83351
84584
  const commonPaths = [
83352
84585
  "/usr/local/bin/claude",
83353
84586
  "/opt/homebrew/bin/claude",
83354
- join56(home, ".npm-global/bin/claude"),
83355
- join56(home, ".local/bin/claude"),
83356
- join56(home, "node_modules/.bin/claude"),
84587
+ join57(home, ".npm-global/bin/claude"),
84588
+ join57(home, ".local/bin/claude"),
84589
+ join57(home, "node_modules/.bin/claude"),
83357
84590
  "/data/data/com.termux/files/usr/bin/claude",
83358
- join56(home, "../usr/bin/claude")
84591
+ join57(home, "../usr/bin/claude")
83359
84592
  ];
83360
84593
  for (const path of commonPaths) {
83361
84594
  if (existsSync39(path)) {
@@ -83440,16 +84673,16 @@ var init_claude_runner = __esm(() => {
83440
84673
  // src/diag-output.ts
83441
84674
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync26, unlinkSync as unlinkSync11, writeFileSync as writeFileSync29 } from "fs";
83442
84675
  import { homedir as homedir38 } from "os";
83443
- import { join as join57 } from "path";
84676
+ import { join as join58 } from "path";
83444
84677
  function getClaudishDir() {
83445
- const dir = join57(homedir38(), ".claudish");
84678
+ const dir = join58(homedir38(), ".claudish");
83446
84679
  try {
83447
84680
  mkdirSync26(dir, { recursive: true });
83448
84681
  } catch {}
83449
84682
  return dir;
83450
84683
  }
83451
84684
  function getDiagLogPath() {
83452
- return join57(getClaudishDir(), `diag-${process.pid}.log`);
84685
+ return join58(getClaudishDir(), `diag-${process.pid}.log`);
83453
84686
  }
83454
84687
 
83455
84688
  class LogFileDiagOutput {
@@ -86064,7 +87297,7 @@ init_startup_trace();
86064
87297
  init_picker_cancelled();
86065
87298
  var import_dotenv3 = __toESM(require_main(), 1);
86066
87299
  import { existsSync as existsSync40, readFileSync as readFileSync40 } from "fs";
86067
- import { join as join58, resolve as resolve7 } from "path";
87300
+ import { join as join59, resolve as resolve8 } from "path";
86068
87301
 
86069
87302
  // src/tui/runtime/should-open-picker.ts
86070
87303
  function noModelConfigured(gate) {
@@ -86176,7 +87409,7 @@ async function applyOpImport() {
86176
87409
  async function applyConfigOverride() {
86177
87410
  await Promise.resolve();
86178
87411
  const plan = planConfigOverride(process.argv.slice(2), process.env, {
86179
- resolve: resolve7,
87412
+ resolve: resolve8,
86180
87413
  exists: existsSync40
86181
87414
  });
86182
87415
  if (plan.kind === "none")
@@ -86380,7 +87613,7 @@ async function runCli() {
86380
87613
  process.exit(1);
86381
87614
  }
86382
87615
  const mode = cliConfig.teamMode ?? "default";
86383
- const sessionPath = join58(process.cwd(), `.claudish-team-${Date.now()}`);
87616
+ const sessionPath = join59(process.cwd(), `.claudish-team-${Date.now()}`);
86384
87617
  if (mode === "json") {
86385
87618
  await Promise.resolve().then(() => init_pane());
86386
87619
  await Promise.resolve().then(() => init_team_orchestrator());
@@ -86400,7 +87633,7 @@ async function runCli() {
86400
87633
  const status = await runModels(sessionPath);
86401
87634
  const result = { ...status, responses: {} };
86402
87635
  for (const anonId of Object.keys(status.models)) {
86403
- const responsePath = join58(sessionPath, `response-${anonId}.md`);
87636
+ const responsePath = join59(sessionPath, `response-${anonId}.md`);
86404
87637
  try {
86405
87638
  const raw = readFileSync40(responsePath, "utf-8").trim();
86406
87639
  try {