claudish 12.0.0 → 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.
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.0";
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",
@@ -46298,6 +46299,9 @@ function projectRecommendations(recommendations, catalog, richModels = [], now =
46298
46299
  },
46299
46300
  context: rich?.contextWindow ? String(rich.contextWindow) : model.contextWindow ? String(model.contextWindow) : "N/A",
46300
46301
  releaseDate: model.releaseDate,
46302
+ ...typeof model.supportsTools === "boolean" ? { supportsTools: model.supportsTools } : {},
46303
+ ...typeof model.reasoning?.supported === "boolean" ? { supportsReasoning: model.reasoning.supported } : {},
46304
+ ...typeof model.supportsVision === "boolean" ? { supportsVision: model.supportsVision } : {},
46301
46305
  ...subscription ? { subscription, subscriptions: [subscription] } : {}
46302
46306
  });
46303
46307
  }
@@ -63871,6 +63875,20 @@ function resolveClaudishSpawn(env = process.env) {
63871
63875
  }
63872
63876
  var CLAUDISH_BIN_ENV = "CLAUDISH_BIN";
63873
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
+
63874
63892
  // src/pane/process-identity.ts
63875
63893
  import { execFile, execFileSync as execFileSync2 } from "child_process";
63876
63894
  import { lstatSync } from "fs";
@@ -64230,8 +64248,8 @@ function buildPaneEnv(input) {
64230
64248
  continue;
64231
64249
  env[k] = v;
64232
64250
  }
64233
- env.COLUMNS = "160";
64234
- env.LINES = "50";
64251
+ env.COLUMNS = String(input.cols ?? DEFAULT_PANE_COLS);
64252
+ env.LINES = String(input.rows ?? DEFAULT_PANE_ROWS);
64235
64253
  if (input.parentEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined)
64236
64254
  env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
64237
64255
  Object.assign(env, input.slotEnv);
@@ -64357,24 +64375,46 @@ function versionAtLeast(v, min) {
64357
64375
  return true;
64358
64376
  }
64359
64377
  async function assertMagmuxAvailable(binary) {
64360
- const cached = magmuxCache.get(binary ?? "");
64378
+ const key = binary ?? "";
64379
+ const cached = magmuxCache.get(key);
64361
64380
  if (cached)
64362
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) {
64363
64398
  const found = binary ?? findMagmuxBinaryOrNull();
64364
64399
  if (!found)
64365
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) {
64366
64409
  let out = "";
64367
64410
  try {
64368
- 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}`;
64369
64414
  } catch (e) {
64370
64415
  throw new MagmuxUnavailableError(`magmux_unavailable: ${found} --version failed: ${e instanceof Error ? e.message : String(e)}`);
64371
64416
  }
64372
- const version = out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "";
64373
- if (!version || !versionAtLeast(version, MIN_MAGMUX_VERSION))
64374
- throw new MagmuxUnavailableError(`magmux_unavailable: ${found} is version ${version || "unknown"}; panes need >= ${MIN_MAGMUX_VERSION}`);
64375
- const result = { binary: found, version };
64376
- magmuxCache.set(binary ?? "", result);
64377
- return result;
64417
+ return out.match(/(\d+\.\d+\.\d+)/)?.[1] ?? "";
64378
64418
  }
64379
64419
  function resolveClaudishLaunch(parentEnv) {
64380
64420
  const t = resolveClaudishSpawn(parentEnv);
@@ -64423,21 +64463,35 @@ function spawnPaneWatcher(args) {
64423
64463
  proc.stdin?.unref?.();
64424
64464
  return proc;
64425
64465
  }
64426
- function spawnPaneMagmux(input) {
64427
- const proc = spawn(input.binary, [
64466
+ function paneMagmuxArgv(input) {
64467
+ return [
64428
64468
  "--headless",
64429
64469
  "--no-status",
64470
+ "--controller",
64471
+ "claude-code",
64472
+ "--session-id",
64473
+ input.sessionUuid,
64474
+ "--transcript",
64475
+ resolve3(input.transcriptPath),
64430
64476
  "--id",
64431
64477
  input.paneId,
64432
64478
  "--sock-dir",
64433
64479
  input.sockRoot,
64434
64480
  "-e",
64435
64481
  paneCommand(input.ctlDir)
64436
- ], { 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
+ });
64437
64491
  proc.on("error", () => {});
64438
64492
  return proc;
64439
64493
  }
64440
- 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
64441
64495
  [ "$1" = -l ] && shift
64442
64496
  exec /bin/sh "$@"
64443
64497
  `, FATAL_MAGMUX_STDERR;
@@ -64611,6 +64665,7 @@ var init_pane_launch = __esm(() => {
64611
64665
  code = "magmux_unavailable";
64612
64666
  };
64613
64667
  magmuxCache = new Map;
64668
+ magmuxProbes = new Map;
64614
64669
  execFileAsync = promisify5(execFile2);
64615
64670
  ClaudishNotFoundError = class ClaudishNotFoundError extends Error {
64616
64671
  code = "pane_lost";
@@ -64620,6 +64675,20 @@ var init_pane_launch = __esm(() => {
64620
64675
 
64621
64676
  // src/pane/magmux-client.ts
64622
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
+ }
64623
64692
  function dialOnce(sockPath) {
64624
64693
  return new Promise((resolve, reject) => {
64625
64694
  const s = new Socket;
@@ -64746,7 +64815,7 @@ class MagmuxClient {
64746
64815
  return;
64747
64816
  clearTimeout(p.timer);
64748
64817
  this.pending.delete(m.id);
64749
- 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));
64750
64819
  return;
64751
64820
  }
64752
64821
  if (m.type === "shutdown")
@@ -65244,6 +65313,352 @@ var init_process_tree = __esm(() => {
65244
65313
  KILL_PROCESS_GROUP = process.platform !== "win32";
65245
65314
  });
65246
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
+
65247
65662
  // src/pane/screen-model.ts
65248
65663
  function emptyScreen(cols, rows) {
65249
65664
  return {
@@ -65261,7 +65676,7 @@ function emptyScreen(cols, rows) {
65261
65676
  function isBoxRule(line, cols) {
65262
65677
  if (!line || !/^\u2500+(?: [^\u2500]+ \u2500+)?$/.test(line))
65263
65678
  return false;
65264
- return line.length >= Math.max(20, Math.min(cols, 160) - 1);
65679
+ return line.length >= Math.max(20, cols - 1);
65265
65680
  }
65266
65681
  function boxTopRow(lines, cols) {
65267
65682
  for (let i = lines.length - 2;i >= 0; i--) {
@@ -65285,22 +65700,26 @@ function withSize(s, rows) {
65285
65700
  spans.push([]);
65286
65701
  return { lines, spans };
65287
65702
  }
65288
- function aboveBoxText(lines, cols) {
65289
- const top = boxTopRow(lines, cols);
65290
- return (top >= 0 ? lines.slice(0, top) : lines).join(`
65291
- `);
65703
+ function aboveBoxText(s) {
65704
+ const box = locateBox(s);
65705
+ return box ? s.lines.slice(0, box.top).join(`
65706
+ `) : null;
65292
65707
  }
65293
65708
  function finish(prev, next, now) {
65294
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]));
65295
65710
  if (!visible)
65296
65711
  return prev;
65297
- const aboveChanged = prev.seq === 0 || aboveBoxText(prev.lines, prev.cols) !== aboveBoxText(next.lines, next.cols);
65298
- return {
65712
+ const out = {
65299
65713
  ...next,
65300
65714
  seq: prev.seq + 1,
65301
65715
  changedAt: now,
65302
- aboveBoxChangedAt: aboveChanged ? now : prev.aboveBoxChangedAt
65716
+ aboveBoxChangedAt: prev.aboveBoxChangedAt
65303
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;
65304
65723
  }
65305
65724
  function applyFrame(s, frame, now = Date.now()) {
65306
65725
  const rows = frame.rows;
@@ -65325,6 +65744,9 @@ function applyFrame(s, frame, now = Date.now()) {
65325
65744
  alt: frame.alt ?? s.alt
65326
65745
  }, now);
65327
65746
  }
65747
+ var init_screen_model = __esm(() => {
65748
+ init_box_locate();
65749
+ });
65328
65750
 
65329
65751
  // src/pane/screen-classifier.ts
65330
65752
  function screenText(s) {
@@ -65334,18 +65756,44 @@ function screenText(s) {
65334
65756
  return lines.join(`
65335
65757
  `);
65336
65758
  }
65337
- 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) {
65338
65772
  if (!spans?.length)
65339
65773
  return line;
65340
65774
  const chars = Array.from(line);
65341
65775
  for (const [col, len, , , attr] of spans) {
65342
- if ((attr & ATTR_FAINT) === 0)
65776
+ if ((attr & ATTR_FAINT2) === 0)
65343
65777
  continue;
65344
65778
  for (let c = col;c < col + len && c < chars.length; c++)
65345
65779
  chars[c] = " ";
65346
65780
  }
65347
65781
  return chars.join("");
65348
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
+ }
65349
65797
  function inputBox(s) {
65350
65798
  const top = boxTopRow(s.lines, s.cols);
65351
65799
  if (top < 0)
@@ -65364,7 +65812,7 @@ function boxBottomRow(s, top) {
65364
65812
  function boxText(s, top, bottom) {
65365
65813
  const parts = [];
65366
65814
  for (let i = top + 1;i < bottom; i++) {
65367
- const raw = visibleText(s.lines[i] ?? "", s.spans[i]);
65815
+ const raw = visibleText2(s.lines[i] ?? "", s.spans[i]);
65368
65816
  const body = i === top + 1 ? raw.replace(/^\u276F\s?/, "") : raw.replace(/^ {2}/, "");
65369
65817
  parts.push(body.replace(/\s+$/, ""));
65370
65818
  }
@@ -65397,7 +65845,8 @@ function choiceDialog(s) {
65397
65845
  const lines = s.lines;
65398
65846
  const start = lastBulletRow(lines) + 1;
65399
65847
  const below = lines.slice(start);
65400
- 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;
65401
65850
  if (!kind)
65402
65851
  return null;
65403
65852
  if (!below.some((l) => FIRST_OPTION.test(l)))
@@ -65405,14 +65854,19 @@ function choiceDialog(s) {
65405
65854
  return { kind, text: below.filter((l) => l.trim()).join(`
65406
65855
  `) };
65407
65856
  }
65408
- function hasChoiceDialog(s) {
65409
- return choiceDialog(s) !== null;
65857
+ function dialogText(s) {
65858
+ return s.lines.slice(lastBulletRow(s.lines) + 1).filter((l) => l.trim()).join(`
65859
+ `);
65410
65860
  }
65411
65861
  function isWorking(s) {
65412
65862
  const top = boxTopRow(s.lines, s.cols);
65413
65863
  const region = top >= 0 ? s.lines.slice(0, top) : s.lines;
65414
65864
  return region.some((l) => WORKING_ROW.test(l));
65415
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
+ }
65416
65870
  function stopHookRunning(s) {
65417
65871
  return s.lines.some((l) => /\(running Stop hook\b/.test(l));
65418
65872
  }
@@ -65444,7 +65898,7 @@ function screenAnswer(s, echo) {
65444
65898
  const lines = s.lines;
65445
65899
  const top = boxTopRow(lines, s.cols);
65446
65900
  const end = top >= 0 ? top : lines.length;
65447
- const start = echoRow(lines, end, `\u276F ${echo.slice(0, 40)}`) + 1;
65901
+ const start = echoRow(lines, end, echoHead(echo, s.cols)) + 1;
65448
65902
  if (start <= 0)
65449
65903
  return "";
65450
65904
  const out = [];
@@ -65459,8 +65913,9 @@ function screenAnswer(s, echo) {
65459
65913
  `).trim();
65460
65914
  }
65461
65915
  function echoRow(lines, end, head) {
65916
+ const above = lines.slice(0, end);
65462
65917
  for (let i = end - 1;i >= 0; i--)
65463
- if ((lines[i] ?? "").startsWith(head))
65918
+ if (echoAt(above, i, head))
65464
65919
  return i;
65465
65920
  return -1;
65466
65921
  }
@@ -65482,9 +65937,9 @@ function agentRejectedLine(text) {
65482
65937
  const m = text.match(/--agent '([^']+)' not found\.(?: Available agents:[^\n]*)?/);
65483
65938
  return m ? m[0] : null;
65484
65939
  }
65485
- function namedDialog(text) {
65940
+ function namedDialog(flat) {
65486
65941
  for (const d of NAMED_DIALOGS)
65487
- if (text.includes(d.marker))
65942
+ if (flat.includes(d.marker))
65488
65943
  return d.name;
65489
65944
  return null;
65490
65945
  }
@@ -65493,22 +65948,20 @@ function isUnnamedChoice(lines) {
65493
65948
  const yesNo = lines.some((l) => /^\s*(\u276F\s*)?(\d+\.\s+)?(Yes|No)\b|\(esc\)/.test(l));
65494
65949
  if (numbered && yesNo)
65495
65950
  return true;
65496
- return lines.some((l) => /Press Enter to continue/.test(l) || SELECT_FOOTER.test(l));
65497
- }
65498
- function readBoot(s) {
65499
- const text = screenText(s);
65500
- const box = inputBox(s);
65501
- const named = namedDialog(text);
65502
- if (box && !named)
65503
- 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));
65504
65955
  if (named)
65505
- return { kind: "dialog", name: named, text };
65956
+ return { kind: "dialog", name: named, text: screenText(s) };
65506
65957
  if (isUnnamedChoice(s.lines))
65507
- return { kind: "choice", text };
65958
+ return { kind: "choice", text: screenText(s) };
65508
65959
  return { kind: "booting" };
65509
65960
  }
65510
- 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;
65511
65962
  var init_screen_classifier = __esm(() => {
65963
+ init_box_locate();
65964
+ init_screen_model();
65512
65965
  NAMED_DIALOGS = [
65513
65966
  { name: "onboarding", marker: "Choose the text style" },
65514
65967
  { name: "trust", marker: "Is this a project you created or one you trust" },
@@ -65519,15 +65972,15 @@ var init_screen_classifier = __esm(() => {
65519
65972
  { kind: "permission", header: /\bDo you want to proceed\?/ },
65520
65973
  {
65521
65974
  kind: "permission",
65522
- 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}?\?/
65523
65976
  },
65524
65977
  { kind: "plan", header: /\bWould you like to proceed\?/ },
65525
65978
  { kind: "question", header: /Enter to select \u00B7 \u2191\/\u2193 to navigate/ }
65526
65979
  ];
65527
65980
  NUMBERED_OPTION = /^\s*(\u276F\s*)?\d+\.\s+\S/;
65528
65981
  FIRST_OPTION = /^\s*(\u276F\s*)?1\.\s+\S/;
65529
- SELECT_FOOTER = /Enter to (confirm|select)\b.*Esc to cancel/;
65530
- 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*$/;
65531
65984
  BULLET = /^\u23FA(\s|$)/;
65532
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/;
65533
65986
  MENU_ROW = /^\s{2}\u276F \/\S|^\s{4}\/\S|^\s{4}[*+] \S/;
@@ -65536,7 +65989,7 @@ var init_screen_classifier = __esm(() => {
65536
65989
 
65537
65990
  // src/pane/session-registry.ts
65538
65991
  import { readFileSync as readFileSync29, readdirSync as readdirSync6 } from "fs";
65539
- import { join as join45 } from "path";
65992
+ import { join as join46 } from "path";
65540
65993
  function parseRegistryRecord(raw) {
65541
65994
  let parsed;
65542
65995
  try {
@@ -65581,7 +66034,7 @@ class SessionRegistry {
65581
66034
  isAlive;
65582
66035
  constructor(o) {
65583
66036
  this.o = o;
65584
- this.dir = join45(o.configDir, "sessions");
66037
+ this.dir = join46(o.configDir, "sessions");
65585
66038
  this.now = o.now ?? Date.now;
65586
66039
  this.isAlive = o.isAlive ?? pidAlive;
65587
66040
  }
@@ -65617,7 +66070,7 @@ class SessionRegistry {
65617
66070
  readPid(pid) {
65618
66071
  let raw;
65619
66072
  try {
65620
- raw = readFileSync29(join45(this.dir, `${pid}.json`), "utf8");
66073
+ raw = readFileSync29(join46(this.dir, `${pid}.json`), "utf8");
65621
66074
  } catch {
65622
66075
  return null;
65623
66076
  }
@@ -65662,14 +66115,28 @@ function activityOf(t) {
65662
66115
  return "finishing";
65663
66116
  return "thinking";
65664
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
+ }
65665
66128
  function corroborated(input) {
65666
66129
  const quiet = input.corroborationMs ?? CORROBORATION_MS;
65667
66130
  if (input.chatQuietMs < quiet)
65668
66131
  return { ok: false };
65669
- if (!input.screen)
66132
+ if (!input.facts)
65670
66133
  return { ok: true, anomaly: "screen_unverified" };
65671
- const s = input.screen;
65672
- 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 };
65673
66140
  }
65674
66141
  function settledAs(by, stopReason, c, extra = []) {
65675
66142
  return {
@@ -65702,10 +66169,13 @@ function pathPrimary(t, input) {
65702
66169
  function pathSecondary(t, input) {
65703
66170
  if (!endingStop(t) || !stopHooksFinished(t, input.session))
65704
66171
  return null;
65705
- if (!input.screen)
66172
+ if (!input.facts)
65706
66173
  return null;
65707
66174
  const window2 = input.secondaryQuietMs ?? SECONDARY_QUIET_MS;
65708
- 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)
65709
66179
  return null;
65710
66180
  const c = corroborated(input);
65711
66181
  return c.ok ? settledAs("quiet", t.lastAssistant?.stopReason ?? null, c, ["settled_without_turn_duration"]) : null;
@@ -65736,9 +66206,10 @@ function decideSettle(input) {
65736
66206
  return endingStop(t) ? settledAs("exit", t.lastAssistant.stopReason, {}) : notYet;
65737
66207
  return pathSecondary(t, input) ?? notYet;
65738
66208
  }
65739
- var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING;
66209
+ var SECONDARY_QUIET_MS = 32000, CORROBORATION_MS = 500, CONTINUING, CORROBORATION_GRACE_MS;
65740
66210
  var init_settle_rule = __esm(() => {
65741
66211
  CONTINUING = new Set(["tool_use", "pause_turn"]);
66212
+ CORROBORATION_GRACE_MS = SECONDARY_QUIET_MS;
65742
66213
  });
65743
66214
 
65744
66215
  // src/pane/slot-state.ts
@@ -65879,10 +66350,10 @@ import {
65879
66350
  openSync as openSync8,
65880
66351
  readSync as readSync3,
65881
66352
  readdirSync as readdirSync7,
65882
- realpathSync as realpathSync2,
66353
+ realpathSync as realpathSync3,
65883
66354
  statSync as statSync9
65884
66355
  } from "fs";
65885
- import { join as join46 } from "path";
66356
+ import { join as join47 } from "path";
65886
66357
  function initialFollowerState(resolvePath = safeRealpath) {
65887
66358
  return {
65888
66359
  turns: [],
@@ -65897,7 +66368,7 @@ function initialFollowerState(resolvePath = safeRealpath) {
65897
66368
  }
65898
66369
  function safeRealpath(p) {
65899
66370
  try {
65900
- return realpathSync2(p);
66371
+ return realpathSync3(p);
65901
66372
  } catch {
65902
66373
  return p;
65903
66374
  }
@@ -66410,7 +66881,7 @@ class TranscriptFollower {
66410
66881
  for (const f of this.subagentFiles()) {
66411
66882
  let tail = this.subs.get(f);
66412
66883
  if (!tail) {
66413
- tail = new Tail(join46(this.subagentsDir, f));
66884
+ tail = new Tail(join47(this.subagentsDir, f));
66414
66885
  this.subs.set(f, tail);
66415
66886
  }
66416
66887
  for (const { line } of tail.read()) {
@@ -66493,8 +66964,8 @@ var init_transcript_follower = __esm(() => {
66493
66964
  });
66494
66965
 
66495
66966
  // src/pane/pane-session.ts
66496
- import { readFileSync as readFileSync30, realpathSync as realpathSync3, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
66497
- 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";
66498
66969
  function iso(ms) {
66499
66970
  return new Date(ms).toISOString();
66500
66971
  }
@@ -66531,7 +67002,7 @@ class PaneSessionImpl {
66531
67002
  initialPending;
66532
67003
  shape;
66533
67004
  queue = [];
66534
- screen = emptyScreen(160, 50);
67005
+ screen;
66535
67006
  final = false;
66536
67007
  turnIndex = 0;
66537
67008
  admission = null;
@@ -66567,8 +67038,25 @@ class PaneSessionImpl {
66567
67038
  disagreeCounted = new Set;
66568
67039
  exitScrollback = "";
66569
67040
  exitLastLine = "";
66570
- bootKind = null;
66571
- 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;
66572
67060
  magmux = null;
66573
67061
  watcher = null;
66574
67062
  client = null;
@@ -66599,12 +67087,16 @@ class PaneSessionImpl {
66599
67087
  this.turnDir = ids.turnDir;
66600
67088
  this.identity = { paneId: ids.paneId, sessionUuid: o.sessionUuid, ctlDir: ids.ctlDir };
66601
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);
66602
67094
  this.initialPending = !!o.initialPrompt;
66603
67095
  if (o.initialPrompt)
66604
67096
  this.queue.push(o.initialPrompt);
66605
67097
  this.secondaryQuietMs = o.timings?.secondaryQuietMs ?? SECONDARY_QUIET_MS;
66606
67098
  this.corroborationMs = o.timings?.corroborationMs ?? CORROBORATION_MS;
66607
- 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"));
66608
67100
  this.registry = new SessionRegistry({
66609
67101
  configDir: claudeConfigDir(o.parentEnv ?? process.env),
66610
67102
  sessionUuid: o.sessionUuid
@@ -66640,7 +67132,9 @@ class PaneSessionImpl {
66640
67132
  sockRoot: this.root,
66641
67133
  ctlDir: this.ctlDir,
66642
67134
  cwd: input.cwd,
66643
- env: input.env
67135
+ env: input.env,
67136
+ sessionUuid: this.o.sessionUuid,
67137
+ transcriptPath: this.o.transcriptPath
66644
67138
  });
66645
67139
  } catch (e) {
66646
67140
  return this.spawnFailed(e);
@@ -66649,7 +67143,7 @@ class PaneSessionImpl {
66649
67143
  return this.spawnFailed(new Error("magmux did not start"));
66650
67144
  this.magmux = proc;
66651
67145
  try {
66652
- writeFileSync21(join47(this.ctlDir, "magmux.pid"), `${proc.pid}
67146
+ writeFileSync21(join48(this.ctlDir, "magmux.pid"), `${proc.pid}
66653
67147
  `, { mode: 384 });
66654
67148
  } catch {}
66655
67149
  updateRecord(this.root, this.paneId, { magmuxPid: proc.pid });
@@ -66657,6 +67151,7 @@ class PaneSessionImpl {
66657
67151
  proc.stderr?.on("data", (d) => this.onStderr(d));
66658
67152
  proc.on("exit", (code, sig) => this.onMagmuxExit(code, sig));
66659
67153
  this.ticker = setInterval(() => this.step(() => this.evaluate()), TICK_MS);
67154
+ this.gateTimer = setInterval(() => this.pollGate(), GATE_POLL_MS);
66660
67155
  if (this.o.timeoutMs !== undefined)
66661
67156
  this.timeoutTimer = setTimeout(() => this.step(() => this.fire("timeout")), Math.max(0, this.o.timeoutMs));
66662
67157
  await this.connectFirst();
@@ -66686,7 +67181,6 @@ class PaneSessionImpl {
66686
67181
  client.close();
66687
67182
  return;
66688
67183
  }
66689
- this.client = client;
66690
67184
  client.on("snapshot", (e) => this.onSnapshot(e));
66691
67185
  client.on("frame", (f) => this.onFrame(f));
66692
67186
  client.on("exit", (e) => void this.onPaneExit(e));
@@ -66698,8 +67192,15 @@ class PaneSessionImpl {
66698
67192
  const caps = await client.request({
66699
67193
  type: "capabilities"
66700
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 ?? "") : "";
66701
67202
  if (caps.ok) {
66702
- const v = String(caps.result.version ?? "");
67203
+ const v = magmuxVersion;
66703
67204
  if (caps.result.protocol !== 1 || !versionAtLeast(v, MIN_MAGMUX_VERSION)) {
66704
67205
  this.step(() => this.fire("pane_lost", {
66705
67206
  detail: `magmux_unavailable: magmux ${v || "?"} protocol ${caps.result.protocol ?? "?"}; panes need >= ${MIN_MAGMUX_VERSION}, protocol 1`
@@ -66707,6 +67208,9 @@ class PaneSessionImpl {
66707
67208
  return;
66708
67209
  }
66709
67210
  }
67211
+ this.lastSentSize = null;
67212
+ if (this.supportsResize && !samePaneSize(this.appliedSize, this.desiredSize))
67213
+ this.kickResize();
66710
67214
  await client.request({ type: "watch", pane: 0, mode: "frames", fps: WATCH_FPS });
66711
67215
  const list = await client.request({ type: "list" });
66712
67216
  const p0 = list.ok ? list.result.panes?.find((p) => p.pane === 0) : undefined;
@@ -66716,7 +67220,76 @@ class PaneSessionImpl {
66716
67220
  if (!reconnect)
66717
67221
  await this.nextFrame(500);
66718
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;
66719
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;
66720
67293
  }
66721
67294
  onStderr(d) {
66722
67295
  if (this.stderrHead.length < 8192)
@@ -66739,7 +67312,10 @@ class PaneSessionImpl {
66739
67312
  onFrame(f) {
66740
67313
  if (f.pane !== undefined && f.pane !== 0)
66741
67314
  return;
67315
+ const seq = this.screen.seq;
66742
67316
  this.screen = applyFrame(this.screen, f);
67317
+ if (this.screen.seq !== seq && !this.inResizeQuiet(this.screen.changedAt))
67318
+ this.activityChangedAt = this.screen.changedAt;
66743
67319
  this.framePending = true;
66744
67320
  if (!this.claudeCodeVersion) {
66745
67321
  const m = this.screen.lines.join(`
@@ -66750,8 +67326,11 @@ class PaneSessionImpl {
66750
67326
  this.step(() => this.evaluate());
66751
67327
  }
66752
67328
  onDisconnected(client) {
66753
- if (this.client === client)
67329
+ if (this.client === client) {
66754
67330
  this.client = null;
67331
+ this.gate = null;
67332
+ this.gateTrusted = false;
67333
+ }
66755
67334
  if (this.reaping || !alive(this.magmux))
66756
67335
  return;
66757
67336
  if (isTerminalPhase(this.phase))
@@ -66943,11 +67522,15 @@ class PaneSessionImpl {
66943
67522
  if (!this.client)
66944
67523
  return;
66945
67524
  const idle = status === "idle";
66946
- const dialog = hasChoiceDialog(this.screen);
66947
- this.disagree("registry_idle_screen_booting", this.phase === "BOOTING" && idle && readBoot(this.screen).kind !== "repl", now);
66948
67525
  this.disagree("registry_idle_screen_working", this.phase === "RUNNING" && idle && isWorking(this.screen), now);
66949
- this.disagree("registry_waiting_no_dialog", status === "waiting" && !dialog, now);
66950
- 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);
66951
67534
  }
66952
67535
  disagree(key, holds, now, forMs = REGISTRY_DISAGREE_MS) {
66953
67536
  if (!holds) {
@@ -66983,33 +67566,42 @@ class PaneSessionImpl {
66983
67566
  }
66984
67567
  evalBoot() {
66985
67568
  const now = Date.now();
66986
- const r = readBoot(this.screen);
66987
- if (r.kind !== this.bootKind) {
66988
- this.bootKind = r.kind;
66989
- this.bootKindSince = now;
66990
- }
66991
- const staticFor = this.screen.seq === 0 ? 0 : now - this.screen.changedAt;
66992
- const staticMs = this.t("bootStaticMs", BOOT_STATIC_MS);
66993
- if (r.kind === "repl" && now - this.bootKindSince >= this.t("replStableMs", REPL_STABLE_MS)) {
67569
+ const g = this.gateNow();
67570
+ if (g?.ready) {
66994
67571
  this.bootReady();
66995
67572
  return;
66996
67573
  }
66997
- if (r.kind === "dialog" && staticFor >= staticMs) {
66998
- this.fire("boot_dialog", { detail: r.text });
66999
- return;
67000
- }
67001
- if (r.kind === "choice" && staticFor >= staticMs) {
67002
- 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 });
67003
67579
  return;
67004
67580
  }
67005
67581
  if (now - this.startedAtMs >= (this.o.bootTimeoutMs ?? BOOT_TIMEOUT_MS)) {
67006
- 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";
67007
67584
  this.fire("boot_deadline", {
67008
67585
  reason: blocked ? "boot_blocked" : "boot_timeout",
67009
- detail: screenTailOf(this.screen)
67586
+ detail: `${screenTailOf(this.screen)}
67587
+ ${gate}`
67010
67588
  });
67011
67589
  }
67012
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
+ }
67013
67605
  bootReady() {
67014
67606
  this.bootReadyAtMs = Date.now();
67015
67607
  if (this.queue.length === 0) {
@@ -67035,7 +67627,7 @@ class PaneSessionImpl {
67035
67627
  const head = this.queue.shift();
67036
67628
  if (isControl(head)) {
67037
67629
  this.exitRequested = true;
67038
- this.typeLine(head.trim(), true);
67630
+ this.submitControl(head.trim());
67039
67631
  return;
67040
67632
  }
67041
67633
  this.fire("admit");
@@ -67059,6 +67651,9 @@ class PaneSessionImpl {
67059
67651
  this.screenTurn = null;
67060
67652
  this.rewakeOfSettled = false;
67061
67653
  this.settledChatOffset = null;
67654
+ this.turnAcceptedAt = null;
67655
+ this.witnessMissingCounted = false;
67656
+ this.abandonedIndex = null;
67062
67657
  if (plan.mode === "refused" || plan.mode === "control") {
67063
67658
  this.anomaly(`delivery_refused:${plan.mode === "refused" ? plan.reason : "control"}`);
67064
67659
  this.admission = null;
@@ -67074,79 +67669,385 @@ class PaneSessionImpl {
67074
67669
  }
67075
67670
  const delivery = (plan.mode === "file" || plan.mode === "command") && plan.file && plan.fileContent !== undefined ? { file: plan.file, text: plan.fileContent } : null;
67076
67671
  this.follower.openTurn({ index, offset, witness: plan.witness, delivery });
67077
- this.admission = {
67078
- index,
67079
- text,
67080
- plan,
67081
- initial,
67082
- startedAt: Date.now(),
67083
- deliveredAt: null,
67084
- resent: false,
67085
- panelEscaped: false,
67086
- degraded: false
67087
- };
67088
- 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";
67089
67679
  }
67090
- async deliver(adm) {
67091
- if (await this.typeAndSubmit(adm.plan))
67092
- 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
+ }
67093
67701
  }
67094
- async typeAndSubmit(plan) {
67095
- 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)
67096
67713
  return false;
67097
- if (plan.mode === "command") {
67098
- await this.typeLine(plan.line, false);
67099
- await this.nextFrame(500);
67100
- const box = inputBox(this.screen);
67101
- const name = plan.line.match(/^\/\S+/)?.[0] ?? "";
67102
- if (box?.menuOpen && box.menuItem !== name)
67103
- await this.key("escape");
67104
- await this.key("enter");
67105
- } else {
67106
- 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;
67107
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 });
67108
67730
  return true;
67109
67731
  }
67110
- async typeLine(text, enter) {
67111
- let why = "disconnected";
67112
- for (let attempt = 0;attempt < 2; attempt++) {
67113
- const c = this.client;
67114
- if (c) {
67115
- const r = await c.request({ type: "send", pane: 0, text, typed: true, enter }, 1e4);
67116
- if (r.ok)
67117
- return true;
67118
- why = r.code;
67119
- if (r.code === "client_timeout" || r.code === "client_lost") {
67120
- if (await this.landedAfterLostReply(text, enter))
67121
- 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;
67122
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;
67123
67796
  }
67124
- await Bun.sleep(500);
67125
67797
  }
67126
- 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
+ }
67127
67833
  return false;
67128
67834
  }
67129
- async landedAfterLostReply(text, enter) {
67130
- await this.nextFrame(1000);
67131
- const head = text.slice(0, 40);
67132
- const box = inputBox(this.screen);
67133
- let landed = !!box && box.text.startsWith(head);
67134
- if (landed && enter)
67135
- await this.key("enter");
67136
- if (!landed && enter) {
67137
- this.follower.poll();
67138
- const cur = this.follower.view().current;
67139
- 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;
67140
68037
  }
67141
- if (landed)
67142
- this.anomaly("send_reply_lost");
67143
- 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}` : ""}`);
67144
68045
  }
67145
68046
  async key(k) {
67146
68047
  const c = this.client;
67147
68048
  if (!c)
67148
68049
  return false;
67149
- 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);
67150
68051
  return r.ok;
67151
68052
  }
67152
68053
  nextFrame(ms) {
@@ -67166,139 +68067,134 @@ class PaneSessionImpl {
67166
68067
  const adm = this.admission;
67167
68068
  if (!adm)
67168
68069
  return;
67169
- const cur = view.current;
67170
- if (cur && cur.index === adm.index && cur.acceptedAt !== null) {
68070
+ if (this.witnessed(view.current)) {
67171
68071
  this.accept();
67172
68072
  return;
67173
68073
  }
67174
- const now = Date.now();
67175
- const since = adm.deliveredAt ? now - adm.deliveredAt : 0;
67176
- if (adm.deliveredAt && adm.plan.mode === "command" && this.panelCommand(adm, since))
68074
+ if (adm.panel)
68075
+ this.panelStep(adm);
68076
+ if (!this.admitting(adm))
67177
68077
  return;
67178
- if (adm.deliveredAt && since >= this.t("resendAfterMs", RESEND_AFTER_MS))
67179
- this.nudge(adm);
67180
- if (adm.deliveredAt && (view.availability === "off" || since >= this.t("degradedEntryMs", DEGRADED_ENTRY_MS)))
67181
- this.maybeDegradedAccept(adm, view);
67182
- if (this.phase !== "ADMITTING")
67183
- return;
67184
- if (now - adm.startedAt >= (this.o.admitTimeoutMs ?? ADMIT_TIMEOUT_MS))
68078
+ if (Date.now() >= adm.deadline && !adm.inFlight)
67185
68079
  this.admitExpired(adm);
67186
68080
  }
67187
- panelOpen() {
67188
- 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));
67189
68083
  }
67190
- panelCommand(adm, since) {
67191
- if (!adm.panelEscaped) {
67192
- if (since < PANEL_SETTLE_MS || !this.panelOpen())
67193
- return false;
67194
- adm.panelEscaped = true;
67195
- this.anomaly("panel_dismissed");
67196
- this.key("escape");
67197
- return true;
67198
- }
67199
- const box = inputBox(this.screen);
67200
- if (!box || box.text !== "" || this.panelOpen())
67201
- return false;
67202
- this.admission = null;
67203
- this.anomaly("panel_command");
67204
- if (adm.initial)
67205
- this.fire("admit_deadline", {
67206
- detail: `${adm.text.trim()} opened a panel and wrote no transcript record`
67207
- });
67208
- else
67209
- this.fire("admit_abandoned");
67210
- return true;
67211
- }
67212
- async awaitClearedBox(adm, head) {
67213
- const end = Date.now() + this.t("nudgeClearDeadlineMs", NUDGE_CLEAR_DEADLINE_MS);
67214
- for (;; ) {
67215
- if (this.admission !== adm || this.phase !== "ADMITTING")
67216
- return "gone";
67217
- const box = inputBox(this.screen);
67218
- if (box && box.text === "")
67219
- return "empty";
67220
- if (box && !box.text.startsWith(head))
67221
- return "other";
67222
- const left = end - Date.now();
67223
- if (left <= 0)
67224
- return box ? "line" : "other";
67225
- await this.nextFrame(left);
67226
- }
67227
- }
67228
- nudge(adm) {
67229
- const box = inputBox(this.screen);
67230
- const line = adm.plan.mode === "control" || adm.plan.mode === "refused" ? "" : adm.plan.line;
67231
- const head = line.slice(0, 40);
67232
- if (!adm.resent && box && line && box.text.startsWith(head)) {
67233
- adm.resent = true;
67234
- this.anomaly("resent_enter");
67235
- (async () => {
67236
- if (box.menuOpen)
67237
- await this.key("escape");
67238
- await this.key("ctrl-u");
67239
- const after = await this.awaitClearedBox(adm, head);
67240
- if (after === "gone")
67241
- return;
67242
- if (after === "empty") {
67243
- this.anomaly("retyped_prompt");
67244
- await this.typeAndSubmit(adm.plan);
67245
- } else if (after === "line") {
67246
- await this.key("enter");
67247
- } else {
67248
- this.anomaly("clear_failed");
67249
- }
67250
- })();
68084
+ panelStep(adm) {
68085
+ const p = adm.panel;
68086
+ if (!p)
67251
68087
  return;
67252
- }
67253
- if (!adm.panelEscaped && !box && !hasChoiceDialog(this.screen) && /Esc to cancel/.test(screenText(this.screen))) {
67254
- adm.panelEscaped = true;
67255
- 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");
67256
68100
  this.key("escape");
67257
- }
67258
- }
67259
- maybeDegradedAccept(adm, view) {
67260
- if (view.availability === "ok")
67261
68101
  return;
67262
- const box = inputBox(this.screen);
67263
- if (!box || box.text !== "")
68102
+ }
68103
+ if (!g)
67264
68104
  return;
67265
- const line = adm.plan.mode === "control" || adm.plan.mode === "refused" ? "" : adm.plan.line;
67266
- const echo = this.screen.lines.some((l) => l.startsWith(`\u276F ${line.slice(0, 40)}`));
67267
- const changed = adm.deliveredAt !== null && this.screen.aboveBoxChangedAt > adm.deliveredAt;
67268
- 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");
67269
68114
  return;
67270
- this.turnSource = "screen";
67271
- this.anomaly("degraded_mode");
67272
- this.screenTurn = { echo: line, acceptedAt: Date.now(), errorLogSize: this.errorLogSize() };
67273
- this.accept();
68115
+ }
68116
+ if (!p.entered && g.reason === "box_not_empty")
68117
+ adm.panel = null;
67274
68118
  }
67275
68119
  accept() {
67276
68120
  this.admission = null;
67277
68121
  this.initialDelivery = false;
67278
- this.lastChatSeenAt = Date.now();
68122
+ const now = Date.now();
68123
+ this.lastChatSeenAt = now;
68124
+ this.turnAcceptedAt = now;
68125
+ this.turnErrorLogSize = this.errorLogSize();
67279
68126
  this.fire("prompt_accepted");
67280
68127
  }
67281
68128
  admitExpired(adm) {
67282
68129
  this.admission = null;
67283
68130
  if (adm.initial) {
67284
- this.fire("admit_deadline", { detail: screenTailOf(this.screen) });
68131
+ this.fire("admit_deadline", {
68132
+ detail: `${this.notAcceptedLine(adm)}
68133
+ ${screenTailOf(this.screen)}`
68134
+ });
67285
68135
  return;
67286
68136
  }
67287
68137
  this.anomaly("send_not_accepted");
68138
+ this.abandonedIndex = adm.index;
67288
68139
  this.fire("admit_abandoned");
67289
68140
  }
67290
- 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() {
67291
68161
  if (!this.client)
67292
68162
  return null;
67293
- const box = inputBox(this.screen);
68163
+ const g = this.gateNow();
67294
68164
  return {
67295
- boxEmpty: !!box && box.text === "",
67296
- choiceDialog: hasChoiceDialog(this.screen),
67297
- working: isWorking(this.screen),
67298
- 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()
67299
68168
  };
67300
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
+ }
67301
68195
  evalRunning(view) {
68196
+ if (this.turnSource === "transcript")
68197
+ this.checkUnwitnessed(view);
67302
68198
  if (this.turnSource === "screen") {
67303
68199
  this.evalScreenTurn(view);
67304
68200
  return;
@@ -67318,22 +68214,24 @@ class PaneSessionImpl {
67318
68214
  if (this.detectBlocked(t))
67319
68215
  return;
67320
68216
  const now = Date.now();
68217
+ const transcriptQuietMs = now - (view.lastAppendAt ?? this.startedAtMs);
67321
68218
  const decision = decideSettle({
67322
68219
  turn: t,
67323
68220
  session: view.session,
67324
- transcriptQuietMs: now - (view.lastAppendAt ?? this.startedAtMs),
68221
+ transcriptQuietMs,
67325
68222
  chatQuietMs: now - this.lastChatSeenAt,
67326
- screen: this.screenFacts(),
68223
+ facts: this.corroborationFacts(),
67327
68224
  paneExited: false,
67328
68225
  secondaryQuietMs: this.secondaryQuietMs,
67329
- corroborationMs: this.corroborationMs
68226
+ corroborationMs: this.corroborationMs,
68227
+ corroborationGraceMs: this.t("gateGraceMs", GATE_GRACE_MS)
67330
68228
  });
67331
68229
  if (decision.settled) {
67332
68230
  this.settle(t, decision, null);
67333
68231
  return;
67334
68232
  }
67335
68233
  this.activity = decision.activity;
67336
- this.checkTurnEndMissing();
68234
+ this.checkTurnEndMissing(t, transcriptQuietMs);
67337
68235
  this.checkAgentCountMismatch(t);
67338
68236
  }
67339
68237
  checkEscape() {
@@ -67344,14 +68242,14 @@ class PaneSessionImpl {
67344
68242
  this.anomaly("escape_not_effective");
67345
68243
  this.escapeSentAt = null;
67346
68244
  }
67347
- checkTurnEndMissing() {
67348
- if (this.activity !== "finishing") {
68245
+ checkTurnEndMissing(t, transcriptQuietMs) {
68246
+ if (this.activity !== "finishing" || t.turnDurationAfterLast || t.interruptAfterLast) {
67349
68247
  this.turnEndMissing = false;
67350
68248
  return;
67351
68249
  }
67352
68250
  if (!this.client)
67353
68251
  return;
67354
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68252
+ const quiet = this.aboveBoxQuietMs() ?? transcriptQuietMs;
67355
68253
  const missing = quiet >= TURN_END_MISSING_FACTOR * this.secondaryQuietMs && !stopHookRunning(this.screen);
67356
68254
  if (missing && !this.turnEndMissing)
67357
68255
  this.anomaly("turn_end_record_missing");
@@ -67371,14 +68269,19 @@ class PaneSessionImpl {
67371
68269
  let kind = null;
67372
68270
  if (pending.name === "AskUserQuestion")
67373
68271
  kind = "question";
67374
- else if (this.client && hasChoiceDialog(this.screen))
67375
- 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
+ }
67376
68279
  if (!kind)
67377
68280
  return false;
67378
68281
  this.blockedToolId = pending.id;
67379
68282
  this.fire(kind === "question" ? "blocked_question" : "blocked_permission");
67380
68283
  this.activity = pending.name;
67381
- const text = blockText3(pending, choiceDialog(this.screen)?.text ?? null);
68284
+ const text = blockText3(pending, this.screen.seq > 0 ? dialogText(this.screen) || null : null);
67382
68285
  let v;
67383
68286
  try {
67384
68287
  v = this.o.onBlocked({ kind, tool: pending.name, text });
@@ -67391,6 +68294,16 @@ class PaneSessionImpl {
67391
68294
  }
67392
68295
  evalIdle(view) {
67393
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
+ }
67394
68307
  if (!t || this.settledChatOffset === null || this.exitRequested)
67395
68308
  return;
67396
68309
  if (t.lastChatOffset !== null && t.lastChatOffset > this.settledChatOffset) {
@@ -67469,10 +68382,10 @@ class PaneSessionImpl {
67469
68382
  return !!t && t.index === this.turnIndex && t.acceptedAt !== null;
67470
68383
  }
67471
68384
  screenSettleReady(view, answer, apiError) {
67472
- const box = inputBox(this.screen);
67473
- 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)
67474
68387
  return false;
67475
- const quiet = Date.now() - this.screen.aboveBoxChangedAt;
68388
+ const quiet = this.aboveBoxQuietMs() ?? Date.now() - this.screen.changedAt;
67476
68389
  if (view.availability === "off" && quiet >= this.secondaryQuietMs)
67477
68390
  return true;
67478
68391
  return (!!answer || !!apiError) && quiet >= 2 * this.t("screenSettleQuietMs", SCREEN_SETTLE_QUIET_MS);
@@ -67551,7 +68464,7 @@ class PaneSessionImpl {
67551
68464
  session: view.session,
67552
68465
  transcriptQuietMs: Number.MAX_SAFE_INTEGER,
67553
68466
  chatQuietMs: Number.MAX_SAFE_INTEGER,
67554
- screen: null,
68467
+ facts: null,
67555
68468
  paneExited: true,
67556
68469
  secondaryQuietMs: this.secondaryQuietMs,
67557
68470
  corroborationMs: 0
@@ -67601,7 +68514,7 @@ ${screenTailOf(this.screen)}`
67601
68514
  snapshot() {
67602
68515
  const view = this.follower.view();
67603
68516
  const terminal = isTerminalPhase(this.phase);
67604
- const lastAct = Math.max(this.screen.changedAt, view.lastAppendAt ?? 0);
68517
+ const lastAct = Math.max(this.activityChangedAt, view.lastAppendAt ?? 0);
67605
68518
  const cur = view.current;
67606
68519
  const hasTranscript = view.availability === "ok" && view.usage.tokensIn + view.usage.tokensOut > 0;
67607
68520
  return {
@@ -67686,6 +68599,115 @@ ${screenTailOf(this.screen)}`
67686
68599
  return { ok: false, reason: "terminal" };
67687
68600
  return { ok: true, queued: this.queue.length };
67688
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
+ }
67689
68711
  turnAnswer(index) {
67690
68712
  const done = this.answers.get(index);
67691
68713
  if (done !== undefined)
@@ -67701,6 +68723,7 @@ ${screenTailOf(this.screen)}`
67701
68723
  diagnostics() {
67702
68724
  const cur = this.follower.view().current;
67703
68725
  const rec = this.registryRec;
68726
+ const g = this.gate;
67704
68727
  return {
67705
68728
  magmuxStderr: this.stderrHead,
67706
68729
  registry: rec ? { status: rec.status, waitingFor: rec.waitingFor, version: rec.version } : null,
@@ -67710,7 +68733,9 @@ ${screenTailOf(this.screen)}`
67710
68733
  linesReturned: cur.readCoverage.linesReturned,
67711
68734
  complete: cur.readCoverage.complete,
67712
68735
  reads: cur.readCoverage.reads
67713
- } : 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
67714
68739
  };
67715
68740
  }
67716
68741
  cancel() {
@@ -67767,6 +68792,9 @@ ${screenTailOf(this.screen)}`
67767
68792
  if (this.ticker)
67768
68793
  clearInterval(this.ticker);
67769
68794
  this.ticker = null;
68795
+ if (this.gateTimer)
68796
+ clearInterval(this.gateTimer);
68797
+ this.gateTimer = null;
67770
68798
  await this.captureGroupBeforeReap();
67771
68799
  const c = this.client;
67772
68800
  if (c && !c.isClosed && !this.paneClosed) {
@@ -67878,6 +68906,94 @@ function signalQuietly(target, sig) {
67878
68906
  process.kill(target, sig);
67879
68907
  } catch {}
67880
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
+ }
67881
68997
  function isControl(text) {
67882
68998
  const t = text.trim();
67883
68999
  return t === "/exit" || t === "/quit";
@@ -67897,12 +69013,18 @@ function preparePane(o, root, parentEnv) {
67897
69013
  try {
67898
69014
  const paneId = mintPaneId(o.kind, o.label);
67899
69015
  const sockPath = sockPathFor(root, paneId);
67900
- const cwd = realpathSync3(o.cwd);
69016
+ const cwd = realpathSync4(o.cwd);
67901
69017
  const launch = o.claudishSpawn ?? resolveClaudishLaunch(parentEnv);
67902
69018
  dirs = createLaunchDirs(root);
67903
69019
  const argv = buildClaudishPaneArgv(o.spawnModel, o.sessionUuid, dirs.turnDir, o.callerFlags);
67904
69020
  writeLauncherFiles(dirs.ctlDir, launcherScript(cwd, launch, argv));
67905
- 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
+ });
67906
69028
  return { paneId, sockPath, cwd, ...dirs, magmuxEnv };
67907
69029
  } catch (e) {
67908
69030
  for (const d of dirs ? [dirs.ctlDir, dirs.turnDir] : [])
@@ -67971,21 +69093,38 @@ async function startPaneSession(o) {
67971
69093
  await session.start({ magmuxBinary: magmux.binary, env: p.magmuxEnv, cwd: p.cwd });
67972
69094
  return session;
67973
69095
  }
67974
- 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;
67975
69097
  var init_pane_session = __esm(() => {
67976
69098
  init_parent_session();
67977
69099
  init_process_tree();
67978
69100
  init_redact();
69101
+ init_box_locate();
69102
+ init_contract();
69103
+ init_gate_binding();
69104
+ init_input_gate();
67979
69105
  init_magmux_client();
67980
69106
  init_pane_launch();
67981
69107
  init_pane_registry();
67982
69108
  init_process_identity();
67983
69109
  init_prompt_delivery();
67984
69110
  init_screen_classifier();
69111
+ init_screen_model();
67985
69112
  init_session_registry();
67986
69113
  init_settle_rule();
67987
69114
  init_slot_state();
67988
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
+ ]);
67989
69128
  EVENT_REASON = {
67990
69129
  boot_dialog: "first_run_dialog",
67991
69130
  boot_blocked: "boot_blocked",
@@ -68037,10 +69176,10 @@ __export(exports_session_discovery, {
68037
69176
  transcriptPathFor: () => transcriptPathFor
68038
69177
  });
68039
69178
  import { execFile as execFile3, execFileSync as execFileSync3 } from "child_process";
68040
- import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, readdirSync as readdirSync8, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
68041
- 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";
68042
69181
  function projectsDir(env = process.env) {
68043
- return join48(claudeConfigDir(env), "projects");
69182
+ return join49(claudeConfigDir(env), "projects");
68044
69183
  }
68045
69184
  function slugForPath(absPath) {
68046
69185
  return projectDirNameFor(absPath);
@@ -68048,9 +69187,9 @@ function slugForPath(absPath) {
68048
69187
  function transcriptPathFor(cwd, sessionUuid, projectsRoot = projectsDir()) {
68049
69188
  let real = cwd;
68050
69189
  try {
68051
- real = realpathSync4(cwd);
69190
+ real = realpathSync5(cwd);
68052
69191
  } catch {}
68053
- return join48(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
69192
+ return join49(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
68054
69193
  }
68055
69194
  function isAgentSession(row) {
68056
69195
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -68097,7 +69236,7 @@ function projectDirs() {
68097
69236
  }
68098
69237
  }
68099
69238
  function sessionsIn(dirName) {
68100
- const dir = join48(projectsDir(), dirName);
69239
+ const dir = join49(projectsDir(), dirName);
68101
69240
  let names;
68102
69241
  try {
68103
69242
  names = readdirSync8(dir).filter((n) => n.endsWith(".jsonl"));
@@ -68106,13 +69245,13 @@ function sessionsIn(dirName) {
68106
69245
  }
68107
69246
  const rows = [];
68108
69247
  for (const n of names) {
68109
- const file = join48(dir, n);
69248
+ const file = join49(dir, n);
68110
69249
  try {
68111
69250
  const st = statSync11(file);
68112
69251
  if (st.size === 0)
68113
69252
  continue;
68114
69253
  const row = {
68115
- id: basename2(n, ".jsonl"),
69254
+ id: basename3(n, ".jsonl"),
68116
69255
  file,
68117
69256
  mtimeMs: st.mtimeMs,
68118
69257
  birthtimeMs: st.birthtimeMs,
@@ -68214,7 +69353,7 @@ function discoverWorktreeGroups(repo) {
68214
69353
  if (hit) {
68215
69354
  path = hit.path;
68216
69355
  live = true;
68217
- name = hit.path === repo.root ? "(root)" : basename2(hit.path);
69356
+ name = hit.path === repo.root ? "(root)" : basename3(hit.path);
68218
69357
  } else if (at !== -1 && dir.startsWith(rootSlug)) {
68219
69358
  path = null;
68220
69359
  live = false;
@@ -68484,7 +69623,7 @@ var init_session_discovery = __esm(() => {
68484
69623
  // src/team-orchestrator.ts
68485
69624
  import { randomBytes as randomBytes8, randomUUID as randomUUID5 } from "crypto";
68486
69625
  import { existsSync as existsSync31, mkdirSync as mkdirSync21, readFileSync as readFileSync31, readdirSync as readdirSync9, statSync as statSync12, writeFileSync as writeFileSync22 } from "fs";
68487
- 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";
68488
69627
  function slotTerminal(e) {
68489
69628
  if (!e.session)
68490
69629
  return true;
@@ -68521,13 +69660,13 @@ function addressRun(path, runId) {
68521
69660
  pruneRuns();
68522
69661
  if (runId) {
68523
69662
  const run = teamRuns.get(runId) ?? null;
68524
- if (run && path !== undefined && resolve4(path) !== run.path)
69663
+ if (run && path !== undefined && resolve5(path) !== run.path)
68525
69664
  return null;
68526
69665
  return run;
68527
69666
  }
68528
69667
  if (path === undefined)
68529
69668
  return null;
68530
- const id = newestRunByPath.get(resolve4(path));
69669
+ const id = newestRunByPath.get(resolve5(path));
68531
69670
  return id ? teamRuns.get(id) ?? null : null;
68532
69671
  }
68533
69672
  function activeRunAt(path) {
@@ -68553,7 +69692,7 @@ function notableAnomalies(snap) {
68553
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"));
68554
69693
  }
68555
69694
  function upstreamLogPath(path, id) {
68556
- return join49(path, "errors", `${id}-upstream.jsonl`);
69695
+ return join50(path, "errors", `${id}-upstream.jsonl`);
68557
69696
  }
68558
69697
  function neverSpawnedRow(e, m) {
68559
69698
  return {
@@ -68646,9 +69785,9 @@ function teamRunRowFromDisk(path, status) {
68646
69785
  const settled = slots.length > 0 && slots.every((s) => isTerminalState(s.state));
68647
69786
  const completions = Object.values(status.models ?? {}).map((m) => m?.completedAt ? Date.parse(m.completedAt) : Number.NaN).filter(Number.isFinite);
68648
69787
  return {
68649
- run_id: status.runId ?? basename3(path),
69788
+ run_id: status.runId ?? basename4(path),
68650
69789
  path,
68651
- kind: status.kind ?? (basename3(path) === "judging" ? "judge" : "run"),
69790
+ kind: status.kind ?? (basename4(path) === "judging" ? "judge" : "run"),
68652
69791
  started_at: status.startedAt,
68653
69792
  finished_at: settled ? new Date(completions.length ? Math.max(...completions) : statusMtimeMs(path) ?? Date.parse(status.startedAt)).toISOString() : null,
68654
69793
  state: settled ? "SETTLED" : "ACTIVE",
@@ -68658,14 +69797,14 @@ function teamRunRowFromDisk(path, status) {
68658
69797
  }
68659
69798
  function statusMtimeMs(path) {
68660
69799
  try {
68661
- return statSync12(join49(path, "status.json")).mtimeMs;
69800
+ return statSync12(join50(path, "status.json")).mtimeMs;
68662
69801
  } catch {
68663
69802
  return null;
68664
69803
  }
68665
69804
  }
68666
69805
  function readManifestOrNull(path) {
68667
69806
  try {
68668
- return JSON.parse(readFileSync31(join49(path, "manifest.json"), "utf-8"));
69807
+ return JSON.parse(readFileSync31(join50(path, "manifest.json"), "utf-8"));
68669
69808
  } catch {
68670
69809
  return null;
68671
69810
  }
@@ -68699,10 +69838,10 @@ async function cancelTeamRun(path, slot, runId) {
68699
69838
  function neverSpawnedCapture() {
68700
69839
  return {
68701
69840
  seq: 0,
68702
- cols: 160,
68703
- rows: 50,
69841
+ cols: DEFAULT_PANE_COLS,
69842
+ rows: DEFAULT_PANE_ROWS,
68704
69843
  cursor: { x: 0, y: 0 },
68705
- lines: Array.from({ length: 50 }, () => ""),
69844
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
68706
69845
  final: true
68707
69846
  };
68708
69847
  }
@@ -68719,6 +69858,18 @@ function captureTeamSlot(path, slot, sinceSeq, spans, runId) {
68719
69858
  }
68720
69859
  return e.session.capture(sinceSeq, { spans: spans === true });
68721
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
+ }
68722
69873
  function teamLiveMaps(path, runId) {
68723
69874
  const run = addressRun(path, runId);
68724
69875
  if (!run)
@@ -68857,7 +70008,7 @@ function persistErrorLog(errorLogPath, header, sections) {
68857
70008
  } catch {}
68858
70009
  }
68859
70010
  function validateSessionPath(sessionPath) {
68860
- const resolved = resolve4(sessionPath);
70011
+ const resolved = resolve5(sessionPath);
68861
70012
  const cwd = process.cwd();
68862
70013
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68863
70014
  throw new Error(`Session path must be within current directory: ${sessionPath}`);
@@ -68865,7 +70016,7 @@ function validateSessionPath(sessionPath) {
68865
70016
  return resolved;
68866
70017
  }
68867
70018
  function readTeamInputFile(inputPath) {
68868
- const resolved = resolve4(inputPath);
70019
+ const resolved = resolve5(inputPath);
68869
70020
  const cwd = process.cwd();
68870
70021
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
68871
70022
  throw new Error(`Input file must be within current directory: ${inputPath}`);
@@ -68883,14 +70034,14 @@ function setupSession(sessionPath, models, input) {
68883
70034
  if (models.length === 0) {
68884
70035
  throw new Error("At least one model is required");
68885
70036
  }
68886
- if (existsSync31(join49(sessionPath, "manifest.json"))) {
70037
+ if (existsSync31(join50(sessionPath, "manifest.json"))) {
68887
70038
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
68888
70039
  }
68889
- mkdirSync21(join49(sessionPath, "work"), { recursive: true });
68890
- mkdirSync21(join49(sessionPath, "errors"), { recursive: true });
70040
+ mkdirSync21(join50(sessionPath, "work"), { recursive: true });
70041
+ mkdirSync21(join50(sessionPath, "errors"), { recursive: true });
68891
70042
  if (input !== undefined) {
68892
- writeFileSync22(join49(sessionPath, "input.md"), input, "utf-8");
68893
- } else if (!existsSync31(join49(sessionPath, "input.md"))) {
70043
+ writeFileSync22(join50(sessionPath, "input.md"), input, "utf-8");
70044
+ } else if (!existsSync31(join50(sessionPath, "input.md"))) {
68894
70045
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
68895
70046
  }
68896
70047
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -68907,9 +70058,9 @@ function setupSession(sessionPath, models, input) {
68907
70058
  model: models[i],
68908
70059
  assignedAt: now
68909
70060
  };
68910
- mkdirSync21(join49(sessionPath, "work", anonId), { recursive: true });
70061
+ mkdirSync21(join50(sessionPath, "work", anonId), { recursive: true });
68911
70062
  }
68912
- 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");
68913
70064
  const status = {
68914
70065
  startedAt: now,
68915
70066
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -68924,7 +70075,7 @@ function setupSession(sessionPath, models, input) {
68924
70075
  }
68925
70076
  ]))
68926
70077
  };
68927
- 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");
68928
70079
  return manifest;
68929
70080
  }
68930
70081
  function assertValidRequirePattern(pattern) {
@@ -68952,14 +70103,14 @@ async function preflightTeamRun(opts) {
68952
70103
  throw new Error(`invalid_args: ${refusal}`);
68953
70104
  }
68954
70105
  await assertMagmuxAvailable();
68955
- assertNoActiveRun(resolve4(opts.path));
70106
+ assertNoActiveRun(resolve5(opts.path));
68956
70107
  const root = sockRootFor(opts.parentEnv ?? process.env);
68957
70108
  const live = livePaneCount(root);
68958
70109
  if (live + opts.slots > MAX_LIVE_PANES)
68959
70110
  throw new Error(`pane_limit: ${live} live panes for this user, ${opts.slots} more would exceed ${MAX_LIVE_PANES}`);
68960
70111
  }
68961
70112
  function mintRunId(path, startMs) {
68962
- return `${basename3(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
70113
+ return `${basename4(path)}-${startMs.toString(36)}-${randomBytes8(3).toString("hex")}`;
68963
70114
  }
68964
70115
  function startModels(sessionPath, opts = {}) {
68965
70116
  const flight = { aborted: false };
@@ -68983,10 +70134,10 @@ async function startModelsIn(flight, sessionPath, opts) {
68983
70134
  const flagCheck = checkChildFlags(claudeFlags);
68984
70135
  if (!flagCheck.ok)
68985
70136
  throw new Error(`invalid_args: ${flagCheck.message}`);
68986
- const path = resolve4(sessionPath);
68987
- const manifest = JSON.parse(readFileSync31(join49(path, "manifest.json"), "utf-8"));
68988
- const statusPath = join49(path, "status.json");
68989
- 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");
68990
70141
  const readAvailable = !flagsRemoveRead(claudeFlags);
68991
70142
  const refusal = deliveryRefusal(inputContent, readAvailable);
68992
70143
  if (refusal)
@@ -69024,13 +70175,13 @@ async function startModelsIn(flight, sessionPath, opts) {
69024
70175
  mkdirSync21(statsDir(path), { recursive: true });
69025
70176
  const entries = new Map;
69026
70177
  let run = null;
69027
- const errorLogPathOf = (id) => join49(path, "errors", `${id}.log`);
70178
+ const errorLogPathOf = (id) => join50(path, "errors", `${id}.log`);
69028
70179
  const upstreamLogOf = (id) => upstreamLogPath(path, id);
69029
70180
  const commandOf = (e) => ["claudish", "-i", "--model", e.spawnModel ?? e.model, "-y", "--quiet", ...claudeFlags].join(" ");
69030
70181
  function writeResponse(e, text) {
69031
70182
  e.answer = text;
69032
70183
  try {
69033
- writeFileSync22(join49(path, `response-${e.id}.md`), text, "utf-8");
70184
+ writeFileSync22(join50(path, `response-${e.id}.md`), text, "utf-8");
69034
70185
  } catch (err) {
69035
70186
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
69036
70187
  }
@@ -69347,7 +70498,7 @@ async function startModelsIn(flight, sessionPath, opts) {
69347
70498
  done.catch(() => {});
69348
70499
  return {
69349
70500
  runId,
69350
- teamSessionId: basename3(path),
70501
+ teamSessionId: basename4(path),
69351
70502
  sessionPath: path,
69352
70503
  slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
69353
70504
  done
@@ -69358,9 +70509,9 @@ async function runModels(sessionPath, opts = {}) {
69358
70509
  return handle.done;
69359
70510
  }
69360
70511
  async function judgeResponses(sessionPath, opts = {}) {
69361
- const active = activeRunAt(resolve4(sessionPath));
70512
+ const active = activeRunAt(resolve5(sessionPath));
69362
70513
  if (active)
69363
- 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`);
69364
70515
  const responseFiles = readdirSync9(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
69365
70516
  if (responseFiles.length < 2) {
69366
70517
  throw new Error(`Need at least 2 responses to judge, found ${responseFiles.length}`);
@@ -69368,13 +70519,13 @@ async function judgeResponses(sessionPath, opts = {}) {
69368
70519
  const responses = {};
69369
70520
  for (const file of responseFiles) {
69370
70521
  const id = file.replace(/^response-/, "").replace(/\.md$/, "");
69371
- responses[id] = readFileSync31(join49(sessionPath, file), "utf-8");
70522
+ responses[id] = readFileSync31(join50(sessionPath, file), "utf-8");
69372
70523
  }
69373
- const input = readFileSync31(join49(sessionPath, "input.md"), "utf-8");
70524
+ const input = readFileSync31(join50(sessionPath, "input.md"), "utf-8");
69374
70525
  const judgePrompt = buildJudgePrompt(input, responses);
69375
- writeFileSync22(join49(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
70526
+ writeFileSync22(join50(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
69376
70527
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
69377
- const judgePath = join49(sessionPath, "judging");
70528
+ const judgePath = join50(sessionPath, "judging");
69378
70529
  mkdirSync21(judgePath, { recursive: true });
69379
70530
  setupSession(judgePath, judgeModels, judgePrompt);
69380
70531
  await runModels(judgePath, {
@@ -69385,11 +70536,11 @@ async function judgeResponses(sessionPath, opts = {}) {
69385
70536
  });
69386
70537
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
69387
70538
  const verdict = aggregateVerdict(votes, Object.keys(responses));
69388
- writeFileSync22(join49(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
70539
+ writeFileSync22(join50(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
69389
70540
  return verdict;
69390
70541
  }
69391
70542
  function getStatus(sessionPath) {
69392
- return JSON.parse(readFileSync31(join49(sessionPath, "status.json"), "utf-8"));
70543
+ return JSON.parse(readFileSync31(join50(sessionPath, "status.json"), "utf-8"));
69393
70544
  }
69394
70545
  function readTeamStatus(sessionPath) {
69395
70546
  try {
@@ -69425,7 +70576,7 @@ function fisherYatesShuffle(arr) {
69425
70576
  return arr;
69426
70577
  }
69427
70578
  function getDefaultJudgeModels(sessionPath) {
69428
- const manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70579
+ const manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69429
70580
  return Object.values(manifest.models).map((e) => e.model);
69430
70581
  }
69431
70582
  function buildJudgePrompt(input, responses) {
@@ -69488,7 +70639,7 @@ function parseJudgeVotes(judgePath, responseIds) {
69488
70639
  const judgeId = file.replace(/^response-/, "").replace(/\.md$/, "");
69489
70640
  let content;
69490
70641
  try {
69491
- content = readFileSync31(join49(judgePath, file), "utf-8");
70642
+ content = readFileSync31(join50(judgePath, file), "utf-8");
69492
70643
  } catch {
69493
70644
  continue;
69494
70645
  }
@@ -69540,7 +70691,7 @@ function aggregateVerdict(votes, responseIds) {
69540
70691
  function formatVerdict(verdict, sessionPath) {
69541
70692
  let manifest = null;
69542
70693
  try {
69543
- manifest = JSON.parse(readFileSync31(join49(sessionPath, "manifest.json"), "utf-8"));
70694
+ manifest = JSON.parse(readFileSync31(join50(sessionPath, "manifest.json"), "utf-8"));
69544
70695
  } catch {}
69545
70696
  let output = `# Team Verdict
69546
70697
 
@@ -69602,7 +70753,7 @@ import {
69602
70753
  statSync as statSync13,
69603
70754
  writeFileSync as writeFileSync23
69604
70755
  } from "fs";
69605
- import { join as join50, resolve as resolve5, sep } from "path";
70756
+ import { join as join51, resolve as resolve6, sep } from "path";
69606
70757
  function normaliseTimeoutSeconds(raw) {
69607
70758
  const n = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : raw;
69608
70759
  if (typeof n !== "number" || !Number.isFinite(n))
@@ -69768,7 +70919,7 @@ function dropLeadingFragment(tail) {
69768
70919
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
69769
70920
  }
69770
70921
  function diskAccounting(sessionDir) {
69771
- const stats = readTokenStatsAt(join50(sessionDir, "tokens.json"));
70922
+ const stats = readTokenStatsAt(join51(sessionDir, "tokens.json"));
69772
70923
  return {
69773
70924
  tokensIn: stats?.billed_input_tokens ?? null,
69774
70925
  tokensOut: stats?.output_tokens ?? null,
@@ -69791,10 +70942,10 @@ function diskProseBytes(tail, fileBytes) {
69791
70942
  function blankFinalCapture() {
69792
70943
  return {
69793
70944
  seq: 0,
69794
- cols: 160,
69795
- rows: 50,
70945
+ cols: DEFAULT_PANE_COLS,
70946
+ rows: DEFAULT_PANE_ROWS,
69796
70947
  cursor: { x: 0, y: 0 },
69797
- lines: Array.from({ length: 50 }, () => ""),
70948
+ lines: Array.from({ length: DEFAULT_PANE_ROWS }, () => ""),
69798
70949
  final: true
69799
70950
  };
69800
70951
  }
@@ -69839,7 +70990,7 @@ class SessionManager {
69839
70990
  let dir = "";
69840
70991
  for (let attempt = 0;; attempt++) {
69841
70992
  id = `team-${randomUUID6().replace(/-/g, "").slice(0, 8)}`;
69842
- dir = join50(this.sessionsDir, id);
70993
+ dir = join51(this.sessionsDir, id);
69843
70994
  try {
69844
70995
  mkdirSync22(dir);
69845
70996
  break;
@@ -69854,7 +71005,7 @@ class SessionManager {
69854
71005
  sessionId: id,
69855
71006
  parentClaudeSessionId: opts.parentClaudeSessionId,
69856
71007
  startedAt,
69857
- teamPath: resolve5(opts.teamPath),
71008
+ teamPath: resolve6(opts.teamPath),
69858
71009
  slots: opts.slots
69859
71010
  });
69860
71011
  this.teamRunStarts.set(id, startedAt);
@@ -69871,9 +71022,9 @@ class SessionManager {
69871
71022
  if (dir === null)
69872
71023
  throw new Error("not a record id");
69873
71024
  const completedAt = new Date().toISOString();
69874
- 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;
69875
71026
  const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
69876
- writeJsonAtomic(join50(dir, "meta.json"), {
71027
+ writeJsonAtomic(join51(dir, "meta.json"), {
69877
71028
  kind: "team",
69878
71029
  status: outcome.status,
69879
71030
  startedAt,
@@ -69905,7 +71056,7 @@ class SessionManager {
69905
71056
  startedAt,
69906
71057
  ...rest
69907
71058
  };
69908
- writeJsonAtomic(join50(dir, "spawn.json"), body);
71059
+ writeJsonAtomic(join51(dir, "spawn.json"), body);
69909
71060
  }
69910
71061
  async createSession(opts) {
69911
71062
  if (this.activeSessions + this.starting >= this.maxSessions) {
@@ -69964,10 +71115,10 @@ class SessionManager {
69964
71115
  const claudeSessionId = randomUUID6();
69965
71116
  const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
69966
71117
  const startedAt = new Date().toISOString();
69967
- const sessionDir = opts.sessionDir ?? join50(this.sessionsDir, sessionId);
71118
+ const sessionDir = opts.sessionDir ?? join51(this.sessionsDir, sessionId);
69968
71119
  mkdirSync22(sessionDir, { recursive: true });
69969
71120
  if (opts.prompt)
69970
- writeFileSync23(join50(sessionDir, "prompt.md"), opts.prompt, "utf-8");
71121
+ writeFileSync23(join51(sessionDir, "prompt.md"), opts.prompt, "utf-8");
69971
71122
  this.writeSpawnRecord(sessionDir, {
69972
71123
  kind: "session",
69973
71124
  sessionId,
@@ -69980,7 +71131,7 @@ class SessionManager {
69980
71131
  const cwd = opts.cwd ?? process.cwd();
69981
71132
  const parentEnv = opts.parentEnv ?? this.parentEnv;
69982
71133
  const spawnModel = opts.spawnModel ?? null;
69983
- const tokenFile = opts.tokenFile ?? join50(sessionDir, "tokens.json");
71134
+ const tokenFile = opts.tokenFile ?? join51(sessionDir, "tokens.json");
69984
71135
  return {
69985
71136
  info: {
69986
71137
  sessionId,
@@ -70016,28 +71167,30 @@ class SessionManager {
70016
71167
  },
70017
71168
  session: null,
70018
71169
  scrollback: new ScrollbackBuffer(this.scrollbackCapacity),
70019
- outputLogStream: createWriteStream(join50(sessionDir, "output.log")),
71170
+ outputLogStream: createWriteStream(join51(sessionDir, "output.log")),
70020
71171
  sessionDir,
70021
71172
  cwd,
70022
71173
  tokenFile,
70023
- eventLogPath: join50(sessionDir, "events.jsonl"),
71174
+ eventLogPath: join51(sessionDir, "events.jsonl"),
70024
71175
  eventLogBytes: 0,
70025
- upstreamErrorLogPath: join50(sessionDir, "upstream-errors.jsonl"),
71176
+ upstreamErrorLogPath: join51(sessionDir, "upstream-errors.jsonl"),
70026
71177
  eventRing: [],
70027
71178
  proseBytes: 0,
70028
71179
  answeredTurns: new Map,
70029
- waitLogPath: join50(sessionDir, "waits.jsonl"),
71180
+ waitLogPath: join51(sessionDir, "waits.jsonl"),
70030
71181
  waitLogBytes: 0,
70031
71182
  waitSince: null,
70032
71183
  ended: false,
70033
71184
  evictHandle: null,
70034
71185
  lastLoggedTool: null,
70035
71186
  assistantIdsLogged: 0,
70036
- anomaliesLogged: new Set
71187
+ anomaliesLogged: new Set,
71188
+ requestedSize: null
70037
71189
  };
70038
71190
  }
70039
71191
  async startPane(entry, opts, p) {
70040
71192
  const info = entry.info;
71193
+ const launch = entry.requestedSize;
70041
71194
  const paneOpts = {
70042
71195
  kind: "s",
70043
71196
  label: info.sessionId,
@@ -70052,6 +71205,7 @@ class SessionManager {
70052
71205
  },
70053
71206
  shape: info.shape,
70054
71207
  ...opts.prompt ? { initialPrompt: opts.prompt } : {},
71208
+ ...launch ? { initialSize: launch } : {},
70055
71209
  readAvailable: p.readAvailable,
70056
71210
  decide: (turn) => this.decide(entry, turn),
70057
71211
  onBlocked: (b) => this.onBlocked(entry, b),
@@ -70067,6 +71221,12 @@ class SessionManager {
70067
71221
  this.failNeverSpawned(entry, err);
70068
71222
  throw err;
70069
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
+ });
70070
71230
  if (!entry.ended)
70071
71231
  this.applySnapshot(entry, entry.session.snapshot());
70072
71232
  }
@@ -70160,7 +71320,7 @@ class SessionManager {
70160
71320
  try {
70161
71321
  const text = screenLines.join(`
70162
71322
  `).replace(/\s+$/, "");
70163
- writeFileSync23(join50(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
71323
+ writeFileSync23(join51(entry.sessionDir, "screen.txt"), `${redactSecrets2(text)}
70164
71324
  `, "utf-8");
70165
71325
  } catch {}
70166
71326
  }
@@ -70174,7 +71334,7 @@ class SessionManager {
70174
71334
  entry.waitSince = null;
70175
71335
  }
70176
71336
  try {
70177
- writeJsonAtomic(join50(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
71337
+ writeJsonAtomic(join51(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
70178
71338
  } catch (err) {
70179
71339
  process.stderr.write(`[claudish] session ${entry.info.sessionId}: could not write meta.json: ` + `${err instanceof Error ? err.message : String(err)}
70180
71340
  `);
@@ -70318,6 +71478,8 @@ class SessionManager {
70318
71478
  registry: null,
70319
71479
  preambleBytes: 0,
70320
71480
  readCoverage: null,
71481
+ gate: null,
71482
+ lastSend: null,
70321
71483
  claudeCodeVersion: null,
70322
71484
  anomalies: entry.info.detail ? [entry.info.detail] : []
70323
71485
  };
@@ -70332,6 +71494,8 @@ class SessionManager {
70332
71494
  registry: pane.registry,
70333
71495
  preambleBytes: pane.preambleBytes,
70334
71496
  readCoverage: pane.readCoverage,
71497
+ gate: pane.gate,
71498
+ lastSend: pane.lastSend,
70335
71499
  claudeCodeVersion: snap.claudeCodeVersion,
70336
71500
  anomalies: snap.anomalies
70337
71501
  };
@@ -70345,6 +71509,26 @@ class SessionManager {
70345
71509
  const r = entry.session.cancel();
70346
71510
  return { session_id: sessionId, state: r.state, changed: r.changed };
70347
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
+ }
70348
71532
  captureSession(sessionId, sinceSeq, spans) {
70349
71533
  const entry = this.liveEntry(sessionId);
70350
71534
  if (!entry)
@@ -70408,9 +71592,9 @@ class SessionManager {
70408
71592
  diskSessionDir(sessionId) {
70409
71593
  if (!SESSION_ID_RE.test(sessionId))
70410
71594
  return null;
70411
- const root = resolve5(this.sessionsDir);
70412
- const dir = resolve5(root, sessionId);
70413
- if (dir !== join50(root, sessionId))
71595
+ const root = resolve6(this.sessionsDir);
71596
+ const dir = resolve6(root, sessionId);
71597
+ if (dir !== join51(root, sessionId))
70414
71598
  return null;
70415
71599
  if (!dir.startsWith(root + sep))
70416
71600
  return null;
@@ -70429,10 +71613,10 @@ class SessionManager {
70429
71613
  } catch {
70430
71614
  return null;
70431
71615
  }
70432
- const spawnRecord = readJsonObject(join50(sessionDir, "spawn.json"), META_READ_LIMIT);
71616
+ const spawnRecord = readJsonObject(join51(sessionDir, "spawn.json"), META_READ_LIMIT);
70433
71617
  if (spawnRecord?.kind === "team")
70434
71618
  return null;
70435
- const meta = readJsonObject(join50(sessionDir, "meta.json"), META_READ_LIMIT);
71619
+ const meta = readJsonObject(join51(sessionDir, "meta.json"), META_READ_LIMIT);
70436
71620
  if (meta?.kind === "team")
70437
71621
  return null;
70438
71622
  const partial = meta === null;
@@ -70454,7 +71638,7 @@ class SessionManager {
70454
71638
  return stored;
70455
71639
  }
70456
71640
  diskOutput(record, tailLines) {
70457
- const tail = readTailText(join50(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
71641
+ const tail = readTailText(join51(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
70458
71642
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
70459
71643
  if (tail?.text)
70460
71644
  buffer.append(dropLeadingFragment(tail));
@@ -70474,12 +71658,12 @@ class SessionManager {
70474
71658
  }
70475
71659
  diskDiagnostics(record, limit) {
70476
71660
  const { sessionDir, info } = record;
70477
- const eventLogPath = join50(sessionDir, "events.jsonl");
70478
- const upstreamErrorLogPath = join50(sessionDir, "upstream-errors.jsonl");
70479
- 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");
70480
71664
  const events = readTailLines2(eventLogPath, EVENT_TAIL_BYTES);
70481
71665
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
70482
- const screen = readTailText(join50(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
71666
+ const screen = readTailText(join51(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
70483
71667
  return {
70484
71668
  sessionId: info.sessionId,
70485
71669
  state: info.state,
@@ -70512,6 +71696,8 @@ class SessionManager {
70512
71696
  turnSource: info.turnSource,
70513
71697
  preambleBytes: 0,
70514
71698
  readCoverage: null,
71699
+ gate: null,
71700
+ lastSend: null,
70515
71701
  claudeCodeVersion: null,
70516
71702
  anomalies: record.partial ? [
70517
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."
@@ -70864,7 +72050,7 @@ var init_progress_heartbeat = __esm(() => {
70864
72050
  import { spawn as spawn2 } from "child_process";
70865
72051
  import { mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync24 } from "fs";
70866
72052
  import { tmpdir as tmpdir2 } from "os";
70867
- import { join as join51 } from "path";
72053
+ import { join as join52 } from "path";
70868
72054
  function claudeCodePrintArgv(model, systemPromptFile) {
70869
72055
  const head = [PRINT_FLAGS[0], "--model", model, ...PRINT_FLAGS.slice(1)];
70870
72056
  return systemPromptFile !== undefined ? [...head, "--system-prompt-file", systemPromptFile] : [...head, "--system-prompt", ""];
@@ -70982,13 +72168,13 @@ async function runPromptViaClaudeCode(model, prompt, systemPrompt, opts = {}) {
70982
72168
  } catch (e) {
70983
72169
  throw new Error(`cannot start claudish (${parentEnv.CLAUDISH_BIN?.trim() || "claudish"}): ${e instanceof Error ? e.message : String(e)}`);
70984
72170
  }
70985
- const root = mkdtempSync3(join51(tmpdir2(), "claudish-run-prompt-"));
72171
+ const root = mkdtempSync3(join52(tmpdir2(), "claudish-run-prompt-"));
70986
72172
  try {
70987
- const cwd = join51(root, "cwd");
72173
+ const cwd = join52(root, "cwd");
70988
72174
  mkdirSync23(cwd, { mode: 448 });
70989
72175
  let systemPromptFile;
70990
72176
  if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
70991
- systemPromptFile = join51(root, "system-prompt.txt");
72177
+ systemPromptFile = join52(root, "system-prompt.txt");
70992
72178
  writeFileSync24(systemPromptFile, systemPrompt, { mode: 384 });
70993
72179
  }
70994
72180
  const argv = claudeCodePrintArgv(model, systemPromptFile);
@@ -71096,7 +72282,7 @@ var init_run_prompt_claude_code = __esm(() => {
71096
72282
  // src/mcp-server.ts
71097
72283
  import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync33, readdirSync as readdirSync10, writeFileSync as writeFileSync25 } from "fs";
71098
72284
  import { homedir as homedir35 } from "os";
71099
- 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";
71100
72286
  import { fileURLToPath as fileURLToPath3 } from "url";
71101
72287
  async function loadAllModels(forceRefresh = false) {
71102
72288
  if (!forceRefresh && existsSync33(OPENROUTER_MODELS_CACHE_PATH)) {
@@ -71391,6 +72577,12 @@ function optionalInteger(raw, name) {
71391
72577
  throw new ContractErrorException("invalid_args", `'${name}' must be an integer`);
71392
72578
  return raw;
71393
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
+ }
71394
72586
  function teamStatusAnswer(path, runId) {
71395
72587
  let row = null;
71396
72588
  if (runId) {
@@ -71430,6 +72622,8 @@ async function teamContractVerb(mode, args) {
71430
72622
  const slot = optionalString(args.slot, "slot");
71431
72623
  const since = optionalInteger(args.since_seq, "since_seq");
71432
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;
71433
72627
  switch (mode) {
71434
72628
  case "status":
71435
72629
  return contractAnswer(teamStatusAnswer(path, runId));
@@ -71440,6 +72634,11 @@ async function teamContractVerb(mode, args) {
71440
72634
  throw new ContractErrorException("invalid_args", "'slot' is required for mode 'capture'");
71441
72635
  return contractAnswer(captureTeamSlot(path, slot, since, spans, runId));
71442
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
+ }
71443
72642
  }
71444
72643
  } catch (e) {
71445
72644
  return contractErrorAnswer(e);
@@ -71813,18 +73012,18 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71813
73012
  });
71814
73013
  tools.push({
71815
73014
  name: "team",
71816
- 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.",
71817
73016
  inputSchema: {
71818
73017
  type: "object",
71819
73018
  properties: {
71820
73019
  mode: {
71821
73020
  type: "string",
71822
- enum: ["run", "judge", "run-and-judge", "status", "cancel", "list", "capture"],
71823
- 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."
71824
73023
  },
71825
73024
  slot: {
71826
73025
  type: "string",
71827
- 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."
71828
73027
  },
71829
73028
  path: {
71830
73029
  type: "string",
@@ -71832,7 +73031,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71832
73031
  },
71833
73032
  run_id: {
71834
73033
  type: "string",
71835
- 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'."
71836
73035
  },
71837
73036
  since_seq: {
71838
73037
  type: "number",
@@ -71842,6 +73041,14 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71842
73041
  type: "boolean",
71843
73042
  description: "For 'capture': also return the colour/attribute spans of each line."
71844
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
+ },
71845
73052
  models: {
71846
73053
  type: "array",
71847
73054
  items: { type: "string" },
@@ -71883,7 +73090,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71883
73090
  heartbeat: true,
71884
73091
  handler: async (args, ctx) => {
71885
73092
  const mode = args.mode;
71886
- if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture") {
73093
+ if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture" || mode === "resize") {
71887
73094
  return teamContractVerb(mode, args);
71888
73095
  }
71889
73096
  try {
@@ -71915,7 +73122,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
71915
73122
  const promptText = () => {
71916
73123
  if (input !== undefined)
71917
73124
  return input;
71918
- const existing = join52(resolved, "input.md");
73125
+ const existing = join53(resolved, "input.md");
71919
73126
  return existsSync33(existing) ? readFileSync33(existing, "utf-8") : undefined;
71920
73127
  };
71921
73128
  switch (mode) {
@@ -72074,16 +73281,16 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72074
73281
  const sp = session_path;
72075
73282
  for (const file of ["status.json", "manifest.json", "input.md"]) {
72076
73283
  try {
72077
- sessionData[file] = readFileSync33(join52(sp, file), "utf-8");
73284
+ sessionData[file] = readFileSync33(join53(sp, file), "utf-8");
72078
73285
  } catch {}
72079
73286
  }
72080
73287
  try {
72081
- const errorDir = join52(sp, "errors");
73288
+ const errorDir = join53(sp, "errors");
72082
73289
  if (existsSync33(errorDir)) {
72083
73290
  for (const f of readdirSync10(errorDir)) {
72084
73291
  if (f.endsWith(".log")) {
72085
73292
  try {
72086
- sessionData[`errors/${f}`] = readFileSync33(join52(errorDir, f), "utf-8");
73293
+ sessionData[`errors/${f}`] = readFileSync33(join53(errorDir, f), "utf-8");
72087
73294
  } catch {}
72088
73295
  }
72089
73296
  }
@@ -72093,7 +73300,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72093
73300
  for (const f of readdirSync10(sp)) {
72094
73301
  if (f.startsWith("response-") && f.endsWith(".md")) {
72095
73302
  try {
72096
- const content = readFileSync33(join52(sp, f), "utf-8");
73303
+ const content = readFileSync33(join53(sp, f), "utf-8");
72097
73304
  sessionData[f] = content.slice(0, 200) + (content.length > 200 ? "... (truncated)" : "");
72098
73305
  } catch {}
72099
73306
  }
@@ -72102,7 +73309,7 @@ Use with: run_prompt(model="${suggested}", prompt="your prompt")`;
72102
73309
  }
72103
73310
  let version = "unknown";
72104
73311
  try {
72105
- const pkgPath = join52(__dirname3, "../package.json");
73312
+ const pkgPath = join53(__dirname3, "../package.json");
72106
73313
  if (existsSync33(pkgPath)) {
72107
73314
  version = JSON.parse(readFileSync33(pkgPath, "utf-8")).version;
72108
73315
  }
@@ -72182,7 +73389,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72182
73389
  });
72183
73390
  tools.push({
72184
73391
  name: "create_session",
72185
- 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).",
72186
73393
  inputSchema: {
72187
73394
  type: "object",
72188
73395
  properties: {
@@ -72226,7 +73433,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72226
73433
  const requestedModel = args.model;
72227
73434
  const workDir = args.work_dir;
72228
73435
  const plan = await prehydrateCredentialsForSpawn([requestedModel], {
72229
- pin: workDir === undefined || resolve6(workDir) === process.cwd()
73436
+ pin: workDir === undefined || resolve7(workDir) === process.cwd()
72230
73437
  });
72231
73438
  const parentClaudeSessionId = await parentSessionForCall({
72232
73439
  hostPid: sessionManager.hostPid
@@ -72408,7 +73615,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72408
73615
  });
72409
73616
  tools.push({
72410
73617
  name: "capture_session",
72411
- 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.",
72412
73619
  inputSchema: {
72413
73620
  type: "object",
72414
73621
  properties: {
@@ -72438,6 +73645,35 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
72438
73645
  }
72439
73646
  }
72440
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
+ });
72441
73677
  return tools;
72442
73678
  }
72443
73679
  function resolveToolGroups(mode) {
@@ -72563,7 +73799,7 @@ Each session is an interactive Claude Code running the model in its own headless
72563
73799
  3. send_input may be called at any time; it is queued until the session is idle.
72564
73800
  4. COMPLETED: call get_output for the answer.
72565
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.
72566
- 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.
72567
73803
 
72568
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;
72569
73805
  var init_mcp_server = __esm(() => {
@@ -72596,9 +73832,9 @@ var init_mcp_server = __esm(() => {
72596
73832
  import_dotenv2 = __toESM(require_main(), 1);
72597
73833
  import_dotenv2.config({ quiet: true });
72598
73834
  __filename3 = fileURLToPath3(import.meta.url);
72599
- __dirname3 = dirname14(__filename3);
72600
- CLAUDISH_CACHE_DIR = join52(homedir35(), ".claudish");
72601
- 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");
72602
73838
  NEXT_STEP = {
72603
73839
  cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
72604
73840
  timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
@@ -72609,7 +73845,7 @@ var init_mcp_server = __esm(() => {
72609
73845
  prompt_not_accepted: "the REPL never took the prompt \u2014 read the evidence screen; retry",
72610
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",
72611
73847
  child_exited: "read the evidence screen, then retry or drop the model",
72612
- 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",
72613
73849
  blocked: "the child asked a question team cannot answer \u2014 make the prompt self-contained or use " + "create_session",
72614
73850
  api_error: "retry once, or route via a different provider (or@<model>)",
72615
73851
  refused: "the model refused \u2014 rephrase or drop the model",
@@ -72925,7 +74161,7 @@ var init_behavior_command = __esm(() => {
72925
74161
  import { spawn as spawn3 } from "child_process";
72926
74162
  import { existsSync as existsSync36, readFileSync as readFileSync36, writeFileSync as writeFileSync27 } from "fs";
72927
74163
  import { connect as netConnect2 } from "net";
72928
- import { join as join53 } from "path";
74164
+ import { join as join54 } from "path";
72929
74165
  import { setTimeout as wait } from "timers/promises";
72930
74166
  function paneRouteLine(explanation) {
72931
74167
  const origin = describeRouteExplanation(explanation);
@@ -73075,9 +74311,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
73075
74311
  const keep = opts?.keep ?? false;
73076
74312
  const manifest = setupSession(sessionPath, models, input);
73077
74313
  const startedAt = new Date().toISOString();
73078
- const gridfilePath = join53(sessionPath, "gridfile.txt");
73079
- const prompt = readFileSync36(join53(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
73080
- 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");
73081
74317
  const usedBannerColors = new Set;
73082
74318
  const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
73083
74319
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
@@ -73109,7 +74345,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
73109
74345
  });
73110
74346
  const [{ results }] = await Promise.all([subscription, procExit]);
73111
74347
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
73112
- const statusPath = join53(sessionPath, "status.json");
74348
+ const statusPath = join54(sessionPath, "status.json");
73113
74349
  writeFileSync27(statusPath, JSON.stringify(status, null, 2), "utf-8");
73114
74350
  return status;
73115
74351
  }
@@ -73130,7 +74366,7 @@ var init_team_grid = __esm(() => {
73130
74366
 
73131
74367
  // src/team-cli.ts
73132
74368
  import { readFileSync as readFileSync37 } from "fs";
73133
- import { join as join54 } from "path";
74369
+ import { join as join55 } from "path";
73134
74370
  async function runPaneTeam(sessionPath, models, input, opts) {
73135
74371
  installPaneShutdownHooks({ exitAfter: true });
73136
74372
  try {
@@ -73182,7 +74418,7 @@ Options (run / run-and-judge):
73182
74418
  --timeout <secs> Grid modes only: magmux's own per-pane timeout (default: 300).
73183
74419
  json mode has no deadline \u2014 nothing kills a working model.
73184
74420
  json mode runs each model as an interactive pane in a headless
73185
- magmux (>= 0.14.0; darwin and linux).
74421
+ magmux (>= 0.15.1; darwin and linux).
73186
74422
  --grid Show all models in a magmux grid with live output + status bar
73187
74423
  --mode <m> run only: json (default, panes), default (the grid, as --grid)
73188
74424
  or interactive (as --interactive)
@@ -73275,7 +74511,7 @@ async function teamCommand(args) {
73275
74511
  }
73276
74512
  case "judge": {
73277
74513
  await judgeResponses(sessionPath, { judges });
73278
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74514
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73279
74515
  break;
73280
74516
  }
73281
74517
  case "run-and-judge": {
@@ -73291,7 +74527,7 @@ async function teamCommand(args) {
73291
74527
  });
73292
74528
  printStatus(status);
73293
74529
  await judgeResponses(sessionPath, { judges });
73294
- console.log(readFileSync37(join54(sessionPath, "verdict.md"), "utf-8"));
74530
+ console.log(readFileSync37(join55(sessionPath, "verdict.md"), "utf-8"));
73295
74531
  break;
73296
74532
  }
73297
74533
  case "status": {
@@ -73880,7 +75116,7 @@ var init_keychain_command = __esm(() => {
73880
75116
  import { spawnSync as spawnSync2 } from "child_process";
73881
75117
  import { existsSync as existsSync37, unlinkSync as unlinkSync9 } from "fs";
73882
75118
  import { homedir as homedir36 } from "os";
73883
- import { join as join55 } from "path";
75119
+ import { join as join56 } from "path";
73884
75120
  async function defaultSuggestModel() {
73885
75121
  try {
73886
75122
  const tok = readSharedAntigravityToken();
@@ -74001,7 +75237,7 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
74001
75237
  async logout(deps) {
74002
75238
  deleteSharedAntigravityToken(deps);
74003
75239
  try {
74004
- const tokenFile = join55(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
75240
+ const tokenFile = join56(homedir36(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
74005
75241
  if (existsSync37(tokenFile))
74006
75242
  unlinkSync9(tokenFile);
74007
75243
  } catch {}
@@ -82487,7 +83723,7 @@ import {
82487
83723
  writeFileSync as writeFileSync28
82488
83724
  } from "fs";
82489
83725
  import { homedir as homedir37, tmpdir as tmpdir3 } from "os";
82490
- import { dirname as dirname15, join as join56 } from "path";
83726
+ import { dirname as dirname16, join as join57 } from "path";
82491
83727
  import { isatty } from "tty";
82492
83728
  function releaseTerminalIsolation() {
82493
83729
  if (!restoreTerminal)
@@ -82552,7 +83788,7 @@ function hasResolvableAnthropicAuth(deps = {}) {
82552
83788
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
82553
83789
  return true;
82554
83790
  }
82555
- if (fileExists(join56(homedir37(), ".claude", ".credentials.json")))
83791
+ if (fileExists(join57(homedir37(), ".claude", ".credentials.json")))
82556
83792
  return true;
82557
83793
  return keychainProbe();
82558
83794
  }
@@ -82567,7 +83803,7 @@ function isProxyAuthMode(config) {
82567
83803
  }
82568
83804
  function managedSettingsPath() {
82569
83805
  if (isWindows2()) {
82570
- return join56(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
83806
+ return join57(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
82571
83807
  }
82572
83808
  if (process.platform === "darwin") {
82573
83809
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -82588,9 +83824,9 @@ function isWindows2() {
82588
83824
  }
82589
83825
  function createStatusLineScript(tokenFilePath) {
82590
83826
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82591
- const claudishDir = join56(homeDir, ".claudish");
83827
+ const claudishDir = join57(homeDir, ".claudish");
82592
83828
  const timestamp = Date.now();
82593
- const scriptPath = join56(claudishDir, `status-${timestamp}.js`);
83829
+ const scriptPath = join57(claudishDir, `status-${timestamp}.js`);
82594
83830
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
82595
83831
  const light = getThemeMode() === "light";
82596
83832
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -82728,7 +83964,7 @@ process.stdin.on('end', () => {
82728
83964
  }
82729
83965
  function initializeTokenFile(tokenFilePath) {
82730
83966
  try {
82731
- mkdirSync25(dirname15(tokenFilePath), { recursive: true });
83967
+ mkdirSync25(dirname16(tokenFilePath), { recursive: true });
82732
83968
  writeFileSync28(tokenFilePath, JSON.stringify({
82733
83969
  input_tokens: 0,
82734
83970
  output_tokens: 0,
@@ -82760,7 +83996,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
82760
83996
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
82761
83997
  continue;
82762
83998
  scanned++;
82763
- const full = join56(dir, name);
83999
+ const full = join57(dir, name);
82764
84000
  try {
82765
84001
  if (statSync14(full).mtimeMs >= cutoff)
82766
84002
  continue;
@@ -82789,9 +84025,9 @@ function parseSettingsArgSafe(value) {
82789
84025
  }
82790
84026
  function userSettingsFileCandidates(cwd) {
82791
84027
  return [
82792
- join56(homedir37(), ".claude", "settings.json"),
82793
- join56(cwd, ".claude", "settings.json"),
82794
- 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")
82795
84031
  ];
82796
84032
  }
82797
84033
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
@@ -82832,12 +84068,12 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
82832
84068
  }
82833
84069
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
82834
84070
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
82835
- const claudishDir = join56(homeDir, ".claudish");
84071
+ const claudishDir = join57(homeDir, ".claudish");
82836
84072
  try {
82837
84073
  mkdirSync25(claudishDir, { recursive: true });
82838
84074
  } catch {}
82839
84075
  const timestamp = Date.now();
82840
- const tempPath = join56(claudishDir, `settings-${timestamp}.json`);
84076
+ const tempPath = join57(claudishDir, `settings-${timestamp}.json`);
82841
84077
  const assignedFile = assignedTokenFile(process.env) !== null;
82842
84078
  const tokenFilePath = resolveTokenFilePath(port);
82843
84079
  cleanupStaleTokenFiles(claudishDir);
@@ -83216,7 +84452,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
83216
84452
  console.error(`
83217
84453
  Or set CLAUDE_PATH to your custom installation:`);
83218
84454
  const home = homedir37();
83219
- 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");
83220
84456
  console.error(` export CLAUDE_PATH=${localPath}`);
83221
84457
  process.exit(1);
83222
84458
  }
@@ -83329,15 +84565,15 @@ async function findClaudeBinary() {
83329
84565
  }
83330
84566
  }
83331
84567
  const home = homedir37();
83332
- 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");
83333
84569
  if (existsSync39(localPath)) {
83334
84570
  return localPath;
83335
84571
  }
83336
84572
  if (isWindows) {
83337
84573
  const windowsPaths = [
83338
- join56(home, "AppData", "Roaming", "npm", "claude.cmd"),
83339
- join56(home, ".npm-global", "claude.cmd"),
83340
- 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")
83341
84577
  ];
83342
84578
  for (const path of windowsPaths) {
83343
84579
  if (existsSync39(path)) {
@@ -83348,11 +84584,11 @@ async function findClaudeBinary() {
83348
84584
  const commonPaths = [
83349
84585
  "/usr/local/bin/claude",
83350
84586
  "/opt/homebrew/bin/claude",
83351
- join56(home, ".npm-global/bin/claude"),
83352
- join56(home, ".local/bin/claude"),
83353
- 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"),
83354
84590
  "/data/data/com.termux/files/usr/bin/claude",
83355
- join56(home, "../usr/bin/claude")
84591
+ join57(home, "../usr/bin/claude")
83356
84592
  ];
83357
84593
  for (const path of commonPaths) {
83358
84594
  if (existsSync39(path)) {
@@ -83437,16 +84673,16 @@ var init_claude_runner = __esm(() => {
83437
84673
  // src/diag-output.ts
83438
84674
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync26, unlinkSync as unlinkSync11, writeFileSync as writeFileSync29 } from "fs";
83439
84675
  import { homedir as homedir38 } from "os";
83440
- import { join as join57 } from "path";
84676
+ import { join as join58 } from "path";
83441
84677
  function getClaudishDir() {
83442
- const dir = join57(homedir38(), ".claudish");
84678
+ const dir = join58(homedir38(), ".claudish");
83443
84679
  try {
83444
84680
  mkdirSync26(dir, { recursive: true });
83445
84681
  } catch {}
83446
84682
  return dir;
83447
84683
  }
83448
84684
  function getDiagLogPath() {
83449
- return join57(getClaudishDir(), `diag-${process.pid}.log`);
84685
+ return join58(getClaudishDir(), `diag-${process.pid}.log`);
83450
84686
  }
83451
84687
 
83452
84688
  class LogFileDiagOutput {
@@ -86061,7 +87297,7 @@ init_startup_trace();
86061
87297
  init_picker_cancelled();
86062
87298
  var import_dotenv3 = __toESM(require_main(), 1);
86063
87299
  import { existsSync as existsSync40, readFileSync as readFileSync40 } from "fs";
86064
- import { join as join58, resolve as resolve7 } from "path";
87300
+ import { join as join59, resolve as resolve8 } from "path";
86065
87301
 
86066
87302
  // src/tui/runtime/should-open-picker.ts
86067
87303
  function noModelConfigured(gate) {
@@ -86173,7 +87409,7 @@ async function applyOpImport() {
86173
87409
  async function applyConfigOverride() {
86174
87410
  await Promise.resolve();
86175
87411
  const plan = planConfigOverride(process.argv.slice(2), process.env, {
86176
- resolve: resolve7,
87412
+ resolve: resolve8,
86177
87413
  exists: existsSync40
86178
87414
  });
86179
87415
  if (plan.kind === "none")
@@ -86377,7 +87613,7 @@ async function runCli() {
86377
87613
  process.exit(1);
86378
87614
  }
86379
87615
  const mode = cliConfig.teamMode ?? "default";
86380
- const sessionPath = join58(process.cwd(), `.claudish-team-${Date.now()}`);
87616
+ const sessionPath = join59(process.cwd(), `.claudish-team-${Date.now()}`);
86381
87617
  if (mode === "json") {
86382
87618
  await Promise.resolve().then(() => init_pane());
86383
87619
  await Promise.resolve().then(() => init_team_orchestrator());
@@ -86397,7 +87633,7 @@ async function runCli() {
86397
87633
  const status = await runModels(sessionPath);
86398
87634
  const result = { ...status, responses: {} };
86399
87635
  for (const anonId of Object.keys(status.models)) {
86400
- const responsePath = join58(sessionPath, `response-${anonId}.md`);
87636
+ const responsePath = join59(sessionPath, `response-${anonId}.md`);
86401
87637
  try {
86402
87638
  const raw = readFileSync40(responsePath, "utf-8").trim();
86403
87639
  try {