claudish 14.11.0 → 14.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1531 -232
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -5491,7 +5491,7 @@ var init_onepassword_config = __esm(() => {
5491
5491
  });
5492
5492
 
5493
5493
  // src/version.ts
5494
- var VERSION = "14.11.0";
5494
+ var VERSION = "14.13.0";
5495
5495
 
5496
5496
  // src/redact.ts
5497
5497
  function isPlaceholder(value) {
@@ -5587,22 +5587,134 @@ function redactForClient(text) {
5587
5587
  function redactSecrets(text) {
5588
5588
  if (!text)
5589
5589
  return "";
5590
- let out = redactSigningValues(text);
5591
- out = out.replace(ASSIGNMENT_RE, (match, lhs, value) => isPlaceholder(value) ? match : `${lhs}${MASK}`);
5592
- for (const { re } of TOKEN_PATTERNS) {
5593
- out = out.replace(re, (match) => {
5594
- const scheme = /^(Bearer|Basic|Token)\s/.exec(match);
5595
- return scheme ? `${scheme[1]} ${MASK}` : MASK;
5596
- });
5590
+ return redactPatterns(redactSigningValues(text));
5591
+ }
5592
+ function applyParts(match, parts) {
5593
+ if (parts === null)
5594
+ return match;
5595
+ let out = "";
5596
+ for (const p of parts)
5597
+ out += typeof p === "string" ? p : match.slice(p[0], p[1]);
5598
+ return out;
5599
+ }
5600
+ function redactPatterns(text) {
5601
+ if (!text)
5602
+ return text;
5603
+ let out = text;
5604
+ for (const rule of PATTERN_RULES) {
5605
+ out = out.replace(rule.re, (match, ...rest) => applyParts(match, rule.parts(match, rest.slice(0, -2))));
5597
5606
  }
5598
5607
  return out;
5599
5608
  }
5609
+
5610
+ class SegmentBuilder {
5611
+ segments = [];
5612
+ push(length, origin) {
5613
+ if (length <= 0)
5614
+ return;
5615
+ const last = this.segments[this.segments.length - 1];
5616
+ const bothInserted = last !== undefined && last.origin < 0 && origin < 0;
5617
+ const contiguousOriginal = last !== undefined && last.origin >= 0 && origin >= 0 && last.origin + last.length === origin;
5618
+ if (last && (bothInserted || contiguousOriginal))
5619
+ last.length += length;
5620
+ else
5621
+ this.segments.push({ length, origin });
5622
+ }
5623
+ }
5624
+
5625
+ class SegmentCursor {
5626
+ segs;
5627
+ idx = 0;
5628
+ segStart = 0;
5629
+ constructor(segs) {
5630
+ this.segs = segs;
5631
+ }
5632
+ copy(from, to, into) {
5633
+ let pos = from;
5634
+ while (pos < to) {
5635
+ let seg = this.segs[this.idx];
5636
+ while (this.segStart + seg.length <= pos) {
5637
+ this.segStart += seg.length;
5638
+ seg = this.segs[++this.idx];
5639
+ }
5640
+ const take = Math.min(to, this.segStart + seg.length) - pos;
5641
+ into.push(take, seg.origin < 0 ? -1 : seg.origin + (pos - this.segStart));
5642
+ pos += take;
5643
+ }
5644
+ }
5645
+ }
5646
+ function redactPatternsMapped(text) {
5647
+ let cur = text;
5648
+ let segs = text ? [{ length: text.length, origin: 0 }] : [];
5649
+ for (const rule of PATTERN_RULES) {
5650
+ if (!cur)
5651
+ break;
5652
+ const out = new SegmentBuilder;
5653
+ const src = new SegmentCursor(segs);
5654
+ let built = "";
5655
+ let last = 0;
5656
+ let hit = false;
5657
+ for (const m of cur.matchAll(rule.re)) {
5658
+ const parts = rule.parts(m[0], m.slice(1));
5659
+ if (parts === null)
5660
+ continue;
5661
+ hit = true;
5662
+ const at = m.index;
5663
+ built += cur.slice(last, at);
5664
+ src.copy(last, at, out);
5665
+ for (const p of parts) {
5666
+ if (typeof p === "string") {
5667
+ built += p;
5668
+ out.push(p.length, -1);
5669
+ } else {
5670
+ built += cur.slice(at + p[0], at + p[1]);
5671
+ src.copy(at + p[0], at + p[1], out);
5672
+ }
5673
+ }
5674
+ last = at + m[0].length;
5675
+ }
5676
+ if (!hit)
5677
+ continue;
5678
+ built += cur.slice(last);
5679
+ src.copy(last, cur.length, out);
5680
+ cur = built;
5681
+ segs = out.segments;
5682
+ }
5683
+ return { text: cur, segments: segs };
5684
+ }
5685
+ function signingValueRanges(text) {
5686
+ if (liveSigningValues.size === 0 || !text)
5687
+ return [];
5688
+ if (!sortedSigningValues) {
5689
+ sortedSigningValues = [...liveSigningValues.keys()].sort((a, b) => b.length - a.length);
5690
+ }
5691
+ const found = [];
5692
+ for (const value of sortedSigningValues) {
5693
+ let at = text.indexOf(value);
5694
+ while (at !== -1) {
5695
+ found.push([at, at + value.length]);
5696
+ at = text.indexOf(value, at + 1);
5697
+ }
5698
+ }
5699
+ if (found.length === 0)
5700
+ return [];
5701
+ found.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
5702
+ const merged = [];
5703
+ for (const [s, e] of found) {
5704
+ const last = merged[merged.length - 1];
5705
+ if (last && s <= last[1])
5706
+ last[1] = Math.max(last[1], e);
5707
+ else
5708
+ merged.push([s, e]);
5709
+ }
5710
+ return merged;
5711
+ }
5600
5712
  function sanitizeForReport(text) {
5601
5713
  if (!text)
5602
5714
  return "";
5603
5715
  return redactSecrets(text).replace(/\/Users\/[^/\s]+/g, "/Users/***").replace(/\/home\/[^/\s]+/g, "/home/***").replace(/[A-Z]:\\Users\\[^\\\s]+/gi, "C:\\Users\\***").replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "***@***.***");
5604
5716
  }
5605
- var MASK = "***REDACTED***", PLACEHOLDER, ASSIGNMENT_RE, TOKEN_PATTERNS, SIGNING_MASK = "[redacted]", SIGNING_VALUE_MIN_LENGTH = 8, liveSigningValues, sortedSigningValues = null;
5717
+ var MASK = "***REDACTED***", PLACEHOLDER, ASSIGNMENT_RE, TOKEN_PATTERNS, SIGNING_MASK = "[redacted]", SIGNING_VALUE_MIN_LENGTH = 8, liveSigningValues, sortedSigningValues = null, PATTERN_RULES;
5606
5718
  var init_redact = __esm(() => {
5607
5719
  PLACEHOLDER = /^(['"`]?)(your[-_a-z0-9]*|<[^>]*>|\.\.\.|x{3,}|\*{3,}|REDACTED|CHANGEME|TODO|\$\{[^}]*\}|)(['"`]?)$/i;
5608
5720
  ASSIGNMENT_RE = /(["']?\b[A-Z][A-Z0-9_]*(?:_API_KEY|_KEY|_TOKEN|_SECRET|_PASSWORD)\b["']?\s*[:=]\s*)(["'`]?\$\{[^}]*\}["'`]?|["'`]?[^\s"'`,}]+["'`]?)/g;
@@ -5617,6 +5729,19 @@ var init_redact = __esm(() => {
5617
5729
  { name: "bearer", re: /\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9_.\-+/=]{12,}/g }
5618
5730
  ];
5619
5731
  liveSigningValues = new Map;
5732
+ PATTERN_RULES = [
5733
+ {
5734
+ re: ASSIGNMENT_RE,
5735
+ parts: (_m, [lhs = "", value = ""]) => isPlaceholder(value) ? null : [[0, lhs.length], MASK]
5736
+ },
5737
+ ...TOKEN_PATTERNS.map(({ re }) => ({
5738
+ re,
5739
+ parts: (m) => {
5740
+ const scheme = /^(Bearer|Basic|Token)\s/.exec(m);
5741
+ return scheme ? [[0, scheme[1].length], ` ${MASK}`] : [MASK];
5742
+ }
5743
+ }))
5744
+ ];
5620
5745
  });
5621
5746
 
5622
5747
  // src/logger.ts
@@ -73038,7 +73163,7 @@ function isTerminalState(s) {
73038
73163
  function contractMeta() {
73039
73164
  return { contract_version: CONTRACT_VERSION, capabilities: [...CAPABILITIES] };
73040
73165
  }
73041
- var CONTRACT_VERSION = 1, CAPABILITIES, SLOT_STATES, TERMINAL_STATES, FAILURE_REASONS, ContractErrorException;
73166
+ var CONTRACT_VERSION = 1, CAPABILITIES, SLOT_STATES, TERMINAL_STATES, FAILURE_REASONS, MAX_TRANSCRIPT_LINES = 200, TRANSCRIPT_LINE_MAX_CHARS = 8000, TRANSCRIPT_LINE_MAX_JSON_BYTES = 16000, TRANSCRIPT_ANSWER_MAX_BYTES = 32000, ContractErrorException;
73042
73167
  var init_contract = __esm(() => {
73043
73168
  CAPABILITIES = [
73044
73169
  "list",
@@ -73048,7 +73173,9 @@ var init_contract = __esm(() => {
73048
73173
  "capture_since_seq",
73049
73174
  "capture_spans",
73050
73175
  "resize",
73051
- "allowance"
73176
+ "allowance",
73177
+ "scroll",
73178
+ "transcript"
73052
73179
  ];
73053
73180
  SLOT_STATES = [
73054
73181
  "STARTING",
@@ -93473,6 +93600,39 @@ var init_screen_classifier = __esm(() => {
93473
93600
  NON_ANSWER_ROW = /^\s+\u23BF|^\s{2}(Ran|Read|Searched|Wrote|Edited|Listed|Updated) \d+ |^\s*[\u00B7\u2722\u2733\u2736\u273B\u273D*\u25CF\u2217]\s|^\s*\(ctrl\+/;
93474
93601
  });
93475
93602
 
93603
+ // src/pane/scroll-input.ts
93604
+ function isLiveBottom(pos) {
93605
+ return pos.atBottom && pos.lastDirection === null;
93606
+ }
93607
+ function scrollRowsOf(by) {
93608
+ if (!Number.isFinite(by))
93609
+ return 0;
93610
+ return Math.min(Math.abs(Math.trunc(by)), MAX_SCROLL_ROWS);
93611
+ }
93612
+ function planScroll(pos, req) {
93613
+ if ("to" in req)
93614
+ return { bytes: CTRL_END, events: 1, next: LIVE_BOTTOM };
93615
+ const n = scrollRowsOf(req.by);
93616
+ if (n === 0)
93617
+ return { bytes: "", events: 0, next: pos };
93618
+ const dir = req.by < 0 ? "up" : "down";
93619
+ const extra = pos.atBottom || pos.lastDirection !== dir ? 1 : 0;
93620
+ const events = n + extra;
93621
+ return {
93622
+ bytes: (dir === "up" ? WHEEL_UP : WHEEL_DOWN).repeat(events),
93623
+ events,
93624
+ next: { atBottom: false, lastDirection: dir }
93625
+ };
93626
+ }
93627
+ var MAX_SCROLL_ROWS = 200, WHEEL_UP = "\x1B[<64;1;1M", WHEEL_DOWN = "\x1B[<65;1;1M", CTRL_END = "\x1B[1;5F", LIVE_BOTTOM, UNKNOWN_POSITION;
93628
+ var init_scroll_input = __esm(() => {
93629
+ LIVE_BOTTOM = Object.freeze({ atBottom: true, lastDirection: null });
93630
+ UNKNOWN_POSITION = Object.freeze({
93631
+ atBottom: false,
93632
+ lastDirection: null
93633
+ });
93634
+ });
93635
+
93476
93636
  // src/pane/session-registry.ts
93477
93637
  import { readFileSync as readFileSync40, readdirSync as readdirSync6 } from "fs";
93478
93638
  import { join as join48 } from "path";
@@ -94411,7 +94571,7 @@ class TranscriptFollower {
94411
94571
  let any = false;
94412
94572
  for (const { offset, line } of this.main.read()) {
94413
94573
  any = true;
94414
- const rec = parse8(line);
94574
+ const rec = parseRecord3(line);
94415
94575
  if (rec)
94416
94576
  applyRecord(this.state, rec, offset + 1);
94417
94577
  }
@@ -94440,7 +94600,7 @@ class TranscriptFollower {
94440
94600
  }
94441
94601
  for (const { line } of tail.read()) {
94442
94602
  any = true;
94443
- const rec = parse8(line);
94603
+ const rec = parseRecord3(line);
94444
94604
  if (rec)
94445
94605
  applySubagentRecord(this.state, rec, this.main.offset + 1);
94446
94606
  }
@@ -94502,7 +94662,7 @@ function lastHumanPromptOffset(path) {
94502
94662
  const tail = new Tail(path);
94503
94663
  let found = null;
94504
94664
  for (const { offset, line } of tail.read()) {
94505
- const rec = parse8(line);
94665
+ const rec = parseRecord3(line);
94506
94666
  if (rec && isHumanPrompt(rec))
94507
94667
  found = offset;
94508
94668
  }
@@ -94515,7 +94675,7 @@ function fileSize(p) {
94515
94675
  return 0;
94516
94676
  }
94517
94677
  }
94518
- function parse8(line) {
94678
+ function parseRecord3(line) {
94519
94679
  if (!line.trim())
94520
94680
  return null;
94521
94681
  try {
@@ -94617,6 +94777,9 @@ class PaneSessionImpl {
94617
94777
  resizeQuietFrom = 0;
94618
94778
  resizeQuietUntil = 0;
94619
94779
  activityChangedAt = 0;
94780
+ scrollPos = LIVE_BOTTOM;
94781
+ scrollEndsInFlight = 0;
94782
+ scrollEndConfirmedAt = 0;
94620
94783
  gate = null;
94621
94784
  gateTrusted = false;
94622
94785
  gateInFlight = false;
@@ -94649,6 +94812,7 @@ class PaneSessionImpl {
94649
94812
  reaping = false;
94650
94813
  fastReap = false;
94651
94814
  reapPromise = null;
94815
+ verifiedReap = Promise.withResolvers();
94652
94816
  reconnecting = false;
94653
94817
  depth = 0;
94654
94818
  stepFrom = null;
@@ -95407,8 +95571,8 @@ ${gate}`
95407
95571
  await this.pause(adm, TICK_MS);
95408
95572
  continue;
95409
95573
  }
95410
- if (!this.client || !this.gateTrusted) {
95411
- await this.awaitTrustedClient(adm);
95574
+ if (!this.trustedConnection() || this.needsAdmissionLiveBottom(adm)) {
95575
+ await this.connectionStep(adm);
95412
95576
  continue;
95413
95577
  }
95414
95578
  if (await this.beforeSend(adm) !== "send")
@@ -95420,8 +95584,22 @@ ${gate}`
95420
95584
  adm.inFlight = false;
95421
95585
  }
95422
95586
  }
95587
+ needsAdmissionLiveBottom(adm) {
95588
+ if (adm.liveBottomClient === this.client)
95589
+ return false;
95590
+ adm.liveBottomClient = this.client;
95591
+ return !isLiveBottom(this.scrollPos);
95592
+ }
95593
+ trustedConnection() {
95594
+ return !!this.client && !this.client.isClosed && this.gateTrusted;
95595
+ }
95596
+ connectionStep(adm) {
95597
+ if (!this.trustedConnection())
95598
+ return this.awaitTrustedClient(adm);
95599
+ return this.returnToLiveBottom(Math.max(0, adm.deadline - Date.now()));
95600
+ }
95423
95601
  awaitTrustedClient(adm) {
95424
- return this.waitFor(() => !this.admitting(adm) || !!this.client && this.gateTrusted, Math.max(0, adm.deadline - Date.now()));
95602
+ return this.waitFor(() => !this.admitting(adm) || this.trustedConnection(), Math.max(0, adm.deadline - Date.now()));
95425
95603
  }
95426
95604
  async attemptOnce(adm) {
95427
95605
  const c = this.client;
@@ -95846,6 +96024,7 @@ ${screenTailOf(this.screen)}` });
95846
96024
  accept() {
95847
96025
  this.admission = null;
95848
96026
  this.initialDelivery = false;
96027
+ this.scrollPos = LIVE_BOTTOM;
95849
96028
  const now = Date.now();
95850
96029
  this.lastChatSeenAt = now;
95851
96030
  this.turnAcceptedAt = now;
@@ -95922,6 +96101,7 @@ ${screenTailOf(this.screen)}`
95922
96101
  const d = this.currentDelivery;
95923
96102
  this.turnSource = "screen";
95924
96103
  this.anomaly("degraded_mode");
96104
+ this.returnToLiveBottom(SCROLL_REPLY_MS);
95925
96105
  this.screenTurn = {
95926
96106
  echo: d && d.mode !== "refused" ? d.line : "",
95927
96107
  acceptedAt: at,
@@ -96399,11 +96579,21 @@ ${screenTailOf(this.screen)}`
96399
96579
  const g = this.gateNow();
96400
96580
  if (!g || !g.ready || g.dialog !== null || workingAboveBox(this.screen) === true)
96401
96581
  return false;
96402
- const quiet = this.aboveBoxQuietMs() ?? Date.now() - this.screen.changedAt;
96582
+ let quiet = this.aboveBoxQuietMs() ?? Date.now() - this.screen.changedAt;
96583
+ if (this.scrollEndConfirmedAt > 0)
96584
+ quiet = Math.min(quiet, Date.now() - this.scrollEndConfirmedAt);
96403
96585
  if (view.availability === "off" && quiet >= this.secondaryQuietMs)
96404
96586
  return true;
96405
96587
  return (!!answer || !!apiError) && quiet >= 2 * this.t("screenSettleQuietMs", SCREEN_SETTLE_QUIET_MS);
96406
96588
  }
96589
+ screenViewLive() {
96590
+ if (this.scrollEndsInFlight > 0)
96591
+ return false;
96592
+ if (isLiveBottom(this.scrollPos))
96593
+ return true;
96594
+ this.returnToLiveBottom(SCROLL_REPLY_MS);
96595
+ return false;
96596
+ }
96407
96597
  evalScreenTurn(view) {
96408
96598
  if (this.witnessed(view.current)) {
96409
96599
  this.turnSource = "transcript";
@@ -96418,7 +96608,7 @@ ${screenTailOf(this.screen)}`
96418
96608
  const answer = screenAnswer(this.screen, st.echo);
96419
96609
  const apiError = this.screenApiError(st, answer);
96420
96610
  this.activity = "thinking";
96421
- if (!this.screenSettleReady(view, answer, apiError))
96611
+ if (!this.screenViewLive() || !this.screenSettleReady(view, answer, apiError))
96422
96612
  return;
96423
96613
  this.follower.poll();
96424
96614
  if (this.witnessed(this.follower.view().current))
@@ -96749,6 +96939,69 @@ ${screenTailOf(this.screen)}`
96749
96939
  answer({ ...set, changed: true, seq: this.screen.seq });
96750
96940
  }
96751
96941
  }
96942
+ async scroll(req) {
96943
+ const refusal = this.scrollRefusal();
96944
+ if (refusal)
96945
+ throw new ContractErrorException("scroll_unavailable", refusal);
96946
+ const c = this.client;
96947
+ if (!c || c.isClosed)
96948
+ throw new ContractErrorException("scroll_unavailable", "the pane's socket is reconnecting");
96949
+ const plan = planScroll(this.scrollPos, req);
96950
+ if (plan.bytes === "")
96951
+ return { seq: this.screen.seq };
96952
+ this.scrollPos = plan.next;
96953
+ const seq0 = this.screen.seq;
96954
+ this.extendResizeQuiet();
96955
+ const r = await c.request({ type: "send", pane: 0, text: plan.bytes, enter: false, noGate: true }, SCROLL_REPLY_MS);
96956
+ if (!r.ok) {
96957
+ this.scrollPos = UNKNOWN_POSITION;
96958
+ if (r.code === "client_lost" || r.code === "client_timeout")
96959
+ this.extendResizeQuiet();
96960
+ throw new ContractErrorException("scroll_unavailable", `magmux did not confirm the scroll: ${r.code}${r.error ? `: ${r.error}` : ""}`);
96961
+ }
96962
+ this.extendResizeQuiet();
96963
+ await this.waitFor(() => isTerminalPhase(this.phase) || this.screen.seq !== seq0, this.t("scrollScreenMs", SCROLL_SCREEN_MS));
96964
+ return { seq: this.screen.seq };
96965
+ }
96966
+ screenTurnActive() {
96967
+ if (this.turnSource !== "screen")
96968
+ return false;
96969
+ return this.phase === "RUNNING" || this.phase === "QUESTION" || this.phase === "PERMISSION";
96970
+ }
96971
+ scrollRefusal() {
96972
+ if (isTerminalPhase(this.phase) || this.final)
96973
+ return "the session has finished";
96974
+ if (this.phase === "BOOTING" || this.screen.seq === 0)
96975
+ return "the session is still starting";
96976
+ if (this.phase === "ADMITTING")
96977
+ return "a prompt is being delivered to the session";
96978
+ if (this.screenTurnActive())
96979
+ return "the session's turn is being read from its screen (no transcript)";
96980
+ if (!this.client || this.client.isClosed || !this.gateTrusted)
96981
+ return "the pane's socket is reconnecting";
96982
+ return null;
96983
+ }
96984
+ async returnToLiveBottom(ms) {
96985
+ if (isLiveBottom(this.scrollPos))
96986
+ return;
96987
+ const c = this.client;
96988
+ if (!c || c.isClosed || !this.gateTrusted || ms <= 0)
96989
+ return;
96990
+ this.scrollPos = LIVE_BOTTOM;
96991
+ this.extendResizeQuiet();
96992
+ this.scrollEndsInFlight++;
96993
+ try {
96994
+ const r = await c.request({ type: "send", pane: 0, text: CTRL_END, enter: false, noGate: true }, Math.min(SCROLL_REPLY_MS, ms));
96995
+ if (r.ok) {
96996
+ this.scrollEndConfirmedAt = Date.now();
96997
+ } else {
96998
+ this.scrollPos = UNKNOWN_POSITION;
96999
+ this.anomaly(`scroll_end_unconfirmed:${r.code}`);
97000
+ }
97001
+ } finally {
97002
+ this.scrollEndsInFlight--;
97003
+ }
97004
+ }
96752
97005
  turnAnswer(index) {
96753
97006
  const done = this.answers.get(index);
96754
97007
  if (done !== undefined)
@@ -96806,6 +97059,9 @@ ${screenTailOf(this.screen)}`
96806
97059
  reaped() {
96807
97060
  return this.reapPromise ?? this.terminal.then(() => this.reapPromise ?? Promise.resolve());
96808
97061
  }
97062
+ reapVerified() {
97063
+ return this.verifiedReap.promise;
97064
+ }
96809
97065
  panePid() {
96810
97066
  return this.panePidValue;
96811
97067
  }
@@ -96932,6 +97188,7 @@ ${screenTailOf(this.screen)}`
96932
97188
  if (groupGone && magGone) {
96933
97189
  removePaneFiles(this.root, this.files());
96934
97190
  this.reapFailed = false;
97191
+ this.verifiedReap.resolve();
96935
97192
  await this.endWatcher();
96936
97193
  unregisterPane(this.paneId);
96937
97194
  return;
@@ -97001,7 +97258,8 @@ function newAdmission(a) {
97001
97258
  clearAfter: 0,
97002
97259
  lostType: null,
97003
97260
  last: null,
97004
- noted: new Set
97261
+ noted: new Set,
97262
+ liveBottomClient: null
97005
97263
  };
97006
97264
  }
97007
97265
  function markClearFirst(adm) {
@@ -97168,7 +97426,7 @@ async function startPaneSession(o) {
97168
97426
  await session.start({ magmuxBinary: magmux.binary, env: p.magmuxEnv, cwd: p.cwd });
97169
97427
  return session;
97170
97428
  }
97171
- 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_MS2 = 1000, GATE_GRACE_MS, GATE_DEAD_BACKSTOP_MS = 1000, EXIT_CODE_QUERY_MS = 2000, EXIT_CODE_RETRY_MS = 50, 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, DEFAULT_MAX_CONSECUTIVE = 3, EVENT_REASON, VERDICT_EVENT, PaneStartError;
97429
+ 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, SCROLL_REPLY_MS = 5000, SCROLL_SCREEN_MS = 1000, GATE_POLL_MS = 250, GATE_REPLY_MS = 1000, GATE_STALE_MS2 = 1000, GATE_GRACE_MS, GATE_DEAD_BACKSTOP_MS = 1000, EXIT_CODE_QUERY_MS = 2000, EXIT_CODE_RETRY_MS = 50, 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, DEFAULT_MAX_CONSECUTIVE = 3, EVENT_REASON, VERDICT_EVENT, PaneStartError;
97172
97430
  var init_pane_session = __esm(() => {
97173
97431
  init_queue();
97174
97432
  init_parent_session();
@@ -97185,6 +97443,7 @@ var init_pane_session = __esm(() => {
97185
97443
  init_prompt_delivery();
97186
97444
  init_screen_classifier();
97187
97445
  init_screen_model();
97446
+ init_scroll_input();
97188
97447
  init_session_registry();
97189
97448
  init_settle_rule();
97190
97449
  init_slot_state();
@@ -97223,6 +97482,759 @@ var init_pane_session = __esm(() => {
97223
97482
  };
97224
97483
  });
97225
97484
 
97485
+ // src/pane/transcript-lines.ts
97486
+ function sanitiseText(s) {
97487
+ if (!s)
97488
+ return "";
97489
+ return s.replace(OSC_RE, "").replace(STRING_SEQ_RE, "").replace(CSI_RE, "").replace(CSI8_RE, "").replace(ESC2_RE, "").replace(/\r\n/g, `
97490
+ `).replace(CONTROL_RE, "").replace(LONE_SURROGATE_RE, "\uFFFD").trimEnd();
97491
+ }
97492
+ function jsonUnitBytes(s, i) {
97493
+ const c = s.charCodeAt(i);
97494
+ if (c === 34 || c === 92)
97495
+ return 2;
97496
+ if (c < 32)
97497
+ return c === 8 || c === 9 || c === 10 || c === 12 || c === 13 ? 2 : 6;
97498
+ if (c < 128)
97499
+ return 1;
97500
+ if (c < 2048)
97501
+ return 2;
97502
+ if (c >= 55296 && c <= 56319) {
97503
+ const n = s.charCodeAt(i + 1);
97504
+ return n >= 56320 && n <= 57343 ? 2 : 6;
97505
+ }
97506
+ if (c >= 56320 && c <= 57343) {
97507
+ const p = s.charCodeAt(i - 1);
97508
+ return i > 0 && p >= 55296 && p <= 56319 ? 2 : 6;
97509
+ }
97510
+ return 3;
97511
+ }
97512
+ function isHigh(c) {
97513
+ return c >= 55296 && c <= 56319;
97514
+ }
97515
+ function isLow(c) {
97516
+ return c >= 56320 && c <= 57343;
97517
+ }
97518
+ function nextCut(s, pos, maxChars, maxJsonBytes) {
97519
+ const contentBytes = Math.max(1, maxJsonBytes - 2);
97520
+ let end = pos;
97521
+ let bytes = 0;
97522
+ const limit = Math.min(s.length, pos + Math.max(1, maxChars));
97523
+ while (end < limit) {
97524
+ const b = jsonUnitBytes(s, end);
97525
+ if (bytes + b > contentBytes)
97526
+ break;
97527
+ bytes += b;
97528
+ end++;
97529
+ }
97530
+ if (end === s.length)
97531
+ return end;
97532
+ if (end > pos && isHigh(s.charCodeAt(end - 1)) && isLow(s.charCodeAt(end)))
97533
+ end--;
97534
+ if (end <= pos)
97535
+ return Math.min(s.length, pos + (isHigh(s.charCodeAt(pos)) ? 2 : 1));
97536
+ let space = -1;
97537
+ for (let i = end - 1;i >= pos; i--) {
97538
+ const c = s.charCodeAt(i);
97539
+ if (c === 10)
97540
+ return i + 1;
97541
+ if (space === -1 && (c === 32 || c === 9))
97542
+ space = i;
97543
+ }
97544
+ return space !== -1 ? space + 1 : end;
97545
+ }
97546
+ function str4(v) {
97547
+ return typeof v === "string" && v.trim() !== "" ? v : undefined;
97548
+ }
97549
+ function firstStringInput(input) {
97550
+ for (const v of Object.values(input)) {
97551
+ const s = str4(v);
97552
+ if (s !== undefined)
97553
+ return s;
97554
+ }
97555
+ return;
97556
+ }
97557
+ function collapseWhitespace(s) {
97558
+ return s.replace(/\s+/g, " ").trim();
97559
+ }
97560
+ function ellipsisCut(s, max) {
97561
+ if (s.length <= max)
97562
+ return s.length;
97563
+ let end = max - 1;
97564
+ if (end > 0 && isHigh(s.charCodeAt(end - 1)))
97565
+ end--;
97566
+ return end;
97567
+ }
97568
+ function toolCall(name, input) {
97569
+ const n = collapseWhitespace(sanitiseText(typeof name === "string" ? name : "")) || "tool";
97570
+ const rec = input && typeof input === "object" && !Array.isArray(input) ? input : {};
97571
+ const raw = TOOL_ARG[n]?.(rec) ?? firstStringInput(rec);
97572
+ const arg = raw === undefined ? "" : sanitiseText(raw);
97573
+ return arg ? { kind: "tool", text: n, arg } : { kind: "tool", text: n };
97574
+ }
97575
+ function line(kind, raw) {
97576
+ const text = sanitiseText(raw);
97577
+ return text ? [{ kind, text }] : [];
97578
+ }
97579
+ function userContentText(content) {
97580
+ if (typeof content === "string")
97581
+ return content;
97582
+ if (!Array.isArray(content))
97583
+ return "";
97584
+ const parts = [];
97585
+ for (const b of content) {
97586
+ if (b?.type === "text" && typeof b.text === "string")
97587
+ parts.push(b.text);
97588
+ else if (b?.type === "image")
97589
+ parts.push("[image]");
97590
+ else if (b?.type === "document")
97591
+ parts.push("[document]");
97592
+ }
97593
+ return parts.join(`
97594
+ `);
97595
+ }
97596
+ function isTaskNotificationText(text, origin) {
97597
+ return origin?.kind === "task-notification" || text.includes("<task-notification>");
97598
+ }
97599
+ function promptTextLines(text, origin, fallback) {
97600
+ if (text.startsWith(INTERRUPT_PREFIX))
97601
+ return line("system", text);
97602
+ if (isTaskNotificationText(text, origin)) {
97603
+ const m = SUMMARY_RE.exec(text);
97604
+ return line("system", m ? m[1] ?? "" : text);
97605
+ }
97606
+ if (isLocalCommandText(text) || COMMAND_TAG_RE.test(text)) {
97607
+ if (CAVEAT_RE.test(text))
97608
+ return [];
97609
+ const name = COMMAND_NAME_RE.exec(text);
97610
+ if (name) {
97611
+ let cmd = (name[1] ?? "").trim();
97612
+ if (cmd && !cmd.startsWith("/"))
97613
+ cmd = `/${cmd}`;
97614
+ const args = (COMMAND_ARGS_RE.exec(text)?.[1] ?? "").trim();
97615
+ return line("user", args ? `${cmd} ${args}` : cmd);
97616
+ }
97617
+ const out = STDOUT_RE.exec(text);
97618
+ return out ? line("system", out[2] ?? "") : [];
97619
+ }
97620
+ return fallback ? line(fallback, text) : [];
97621
+ }
97622
+ function assistantLines(r) {
97623
+ const content = r.message?.content;
97624
+ const blocks = typeof content === "string" ? [{ type: "text", text: content }] : Array.isArray(content) ? content : [];
97625
+ if (r.isApiErrorMessage === true) {
97626
+ const text = blocks.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join(`
97627
+ `);
97628
+ return line("error", text);
97629
+ }
97630
+ const out = [];
97631
+ for (const b of blocks) {
97632
+ if (b?.type === "text" && typeof b.text === "string") {
97633
+ if (!b.text.trim())
97634
+ continue;
97635
+ out.push(...line(parseInBandApiError(b.text) ? "error" : "assistant", b.text));
97636
+ } else if (b?.type === "tool_use" || b?.type === "server_tool_use") {
97637
+ out.push(toolCall(b.name, b.input));
97638
+ }
97639
+ }
97640
+ return out;
97641
+ }
97642
+ function userLines(r) {
97643
+ if (r.isCompactSummary === true || r.isMeta === true)
97644
+ return [];
97645
+ const content = r.message?.content;
97646
+ if (Array.isArray(content) && content.some((b) => b?.type === "tool_result")) {
97647
+ const file = r.toolUseResult?.file;
97648
+ const isTaskFile = typeof file?.filePath === "string" && TASK_FILE_RE.test(file.filePath) && typeof file.content === "string";
97649
+ const out = [];
97650
+ let taskFileShown = false;
97651
+ for (const b of content) {
97652
+ if (b?.type === "tool_result") {
97653
+ if (isTaskFile && !taskFileShown) {
97654
+ taskFileShown = true;
97655
+ out.push(...line("task_file", file.content));
97656
+ }
97657
+ } else if (b?.type === "text" && typeof b.text === "string" && b.text.startsWith(INTERRUPT_PREFIX)) {
97658
+ out.push(...line("system", b.text));
97659
+ }
97660
+ }
97661
+ return out;
97662
+ }
97663
+ return promptTextLines(userContentText(content), r.origin, "user");
97664
+ }
97665
+ function systemLines(r) {
97666
+ const subtype = typeof r.subtype === "string" ? r.subtype : "";
97667
+ const content = typeof r.content === "string" ? r.content : undefined;
97668
+ switch (subtype) {
97669
+ case "compact_boundary":
97670
+ return line("system", content ?? "Conversation compacted");
97671
+ case "local_command":
97672
+ return content === undefined ? [] : promptTextLines(content, null, null);
97673
+ case "scheduled_task_fire":
97674
+ case "agents_killed":
97675
+ case "model_refusal_fallback":
97676
+ return line("system", content ?? SYSTEM_WORDS[subtype] ?? subtype);
97677
+ case "api_error": {
97678
+ const e = r.error;
97679
+ const text = str4(e?.formatted) ?? str4(e?.message) ?? content ?? "API error";
97680
+ return line("error", text);
97681
+ }
97682
+ case "model_refusal_no_fallback":
97683
+ return line("error", content ?? "model refused, no fallback");
97684
+ }
97685
+ if (r.level === "error")
97686
+ return line("error", content ?? subtype.replace(/_/g, " "));
97687
+ return [];
97688
+ }
97689
+ function renderEntry(rec) {
97690
+ if (!rec || typeof rec !== "object" || Array.isArray(rec))
97691
+ return [];
97692
+ const r = rec;
97693
+ if (r.isSidechain === true)
97694
+ return [];
97695
+ switch (r.type) {
97696
+ case "assistant":
97697
+ return assistantLines(r);
97698
+ case "user":
97699
+ return userLines(r);
97700
+ case "attachment": {
97701
+ const a = r.attachment;
97702
+ if (a?.type !== "queued_command" || typeof a.prompt !== "string")
97703
+ return [];
97704
+ return promptTextLines(a.prompt, a.origin ?? r.origin, "user");
97705
+ }
97706
+ case "system":
97707
+ return systemLines(r);
97708
+ case "continued-in": {
97709
+ const id = typeof r.continuedInSessionId === "string" ? r.continuedInSessionId : "";
97710
+ return line("system", `conversation continued in session ${id}`.trimEnd());
97711
+ }
97712
+ default:
97713
+ return [];
97714
+ }
97715
+ }
97716
+ var TOOL_ARG_MAX_CHARS = 160, OSC_RE, STRING_SEQ_RE, CSI_RE, CSI8_RE, ESC2_RE, CONTROL_RE, LONE_SURROGATE_RE, TOOL_ARG, TASK_FILE_RE, COMMAND_NAME_RE, COMMAND_ARGS_RE, CAVEAT_RE, COMMAND_TAG_RE, SUMMARY_RE, SYSTEM_WORDS;
97717
+ var init_transcript_lines = __esm(() => {
97718
+ init_transcript_follower();
97719
+ OSC_RE = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
97720
+ STRING_SEQ_RE = /\x1b[PX^_][\s\S]*?\x1b\\/g;
97721
+ CSI_RE = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
97722
+ CSI8_RE = /\x9b[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
97723
+ ESC2_RE = /\x1b[\x20-\x7e]/g;
97724
+ CONTROL_RE = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
97725
+ LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
97726
+ TOOL_ARG = {
97727
+ Bash: (i) => str4(i.command),
97728
+ Monitor: (i) => str4(i.description) ?? str4(i.command),
97729
+ Read: (i) => str4(i.file_path),
97730
+ Write: (i) => str4(i.file_path),
97731
+ Edit: (i) => str4(i.file_path),
97732
+ MultiEdit: (i) => str4(i.file_path),
97733
+ NotebookEdit: (i) => str4(i.notebook_path),
97734
+ Glob: (i) => str4(i.pattern),
97735
+ Grep: (i) => str4(i.pattern),
97736
+ Agent: (i) => str4(i.description) ?? str4(i.subagent_type),
97737
+ Task: (i) => str4(i.description) ?? str4(i.subagent_type),
97738
+ Skill: (i) => str4(i.skill),
97739
+ WebFetch: (i) => str4(i.url),
97740
+ WebSearch: (i) => str4(i.query),
97741
+ ToolSearch: (i) => str4(i.query),
97742
+ SendMessage: (i) => str4(i.to) ?? str4(i.recipient),
97743
+ AskUserQuestion: (i) => Array.isArray(i.questions) ? str4(i.questions[0]?.question) : undefined,
97744
+ TaskStop: (i) => str4(i.task_id),
97745
+ TaskCreate: (i) => str4(i.subject),
97746
+ TaskUpdate: (i) => str4(i.taskId),
97747
+ CronCreate: (i) => str4(i.cron),
97748
+ ScheduleWakeup: (i) => str4(i.reason)
97749
+ };
97750
+ TASK_FILE_RE = /\/launch-[A-Za-z0-9]{6}\/turn-\d+\.md$/;
97751
+ COMMAND_NAME_RE = /<command-name>([\s\S]*?)<\/command-name>/;
97752
+ COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
97753
+ CAVEAT_RE = /^\s*<local-command-caveat>/;
97754
+ COMMAND_TAG_RE = /^\s*<command-(?:message|args)>[\s\S]*<command-name>/;
97755
+ SUMMARY_RE = /<summary>([\s\S]*?)<\/summary>/;
97756
+ SYSTEM_WORDS = {
97757
+ scheduled_task_fire: "scheduled task fired",
97758
+ agents_killed: "agents killed",
97759
+ model_refusal_fallback: "model refusal: fell back"
97760
+ };
97761
+ });
97762
+
97763
+ // src/pane/transcript-redaction.ts
97764
+ function isHigh2(c) {
97765
+ return c >= 55296 && c <= 56319;
97766
+ }
97767
+ function isLow2(c) {
97768
+ return c >= 56320 && c <= 57343;
97769
+ }
97770
+ function firstRangeEndingAfter(ranges, pos) {
97771
+ let lo = 0;
97772
+ let hi = ranges.length;
97773
+ while (lo < hi) {
97774
+ const mid = lo + hi >> 1;
97775
+ if (ranges[mid][1] <= pos)
97776
+ lo = mid + 1;
97777
+ else
97778
+ hi = mid;
97779
+ }
97780
+ return lo;
97781
+ }
97782
+ function redactCarrying(t0, find = signingValueRanges) {
97783
+ const ranges = find(t0);
97784
+ const { text: plain, segments } = redactPatternsMapped(t0);
97785
+ if (ranges.length === 0)
97786
+ return { plain, masked: [] };
97787
+ const masked = [];
97788
+ const add = (from, to) => {
97789
+ const splitsStart = from > 0 && isLow2(plain.charCodeAt(from)) && isHigh2(plain.charCodeAt(from - 1));
97790
+ const splitsEnd = to < plain.length && isLow2(plain.charCodeAt(to)) && isHigh2(plain.charCodeAt(to - 1));
97791
+ const s = splitsStart ? from - 1 : from;
97792
+ const e = splitsEnd ? to + 1 : to;
97793
+ const last = masked[masked.length - 1];
97794
+ if (last && s <= last[1])
97795
+ last[1] = Math.max(last[1], e);
97796
+ else
97797
+ masked.push([s, e]);
97798
+ };
97799
+ let p = 0;
97800
+ for (const seg of segments) {
97801
+ if (seg.origin >= 0) {
97802
+ const o = seg.origin;
97803
+ const oEnd = o + seg.length;
97804
+ for (let i = firstRangeEndingAfter(ranges, o);i < ranges.length; i++) {
97805
+ const [s, e] = ranges[i];
97806
+ if (s >= oEnd)
97807
+ break;
97808
+ add(p + Math.max(s, o) - o, p + Math.min(e, oEnd) - o);
97809
+ }
97810
+ }
97811
+ p += seg.length;
97812
+ }
97813
+ return { plain, masked };
97814
+ }
97815
+ function maskedSlice(r, a, b) {
97816
+ let out = "";
97817
+ if (r.masked.length > 0) {
97818
+ let at = a;
97819
+ for (let i = firstRangeEndingAfter(r.masked, a);i < r.masked.length; i++) {
97820
+ const [s, e] = r.masked[i];
97821
+ if (s >= b)
97822
+ break;
97823
+ const from = Math.max(s, a);
97824
+ const to = Math.min(e, b);
97825
+ out += r.plain.slice(at, from) + r.plain.slice(from, to).replace(/\S/g, SIGNING_UNIT_MASK);
97826
+ at = to;
97827
+ }
97828
+ out += r.plain.slice(at, b);
97829
+ } else
97830
+ out = r.plain.slice(a, b);
97831
+ return out.replace(LONE_SURROGATE_RE2, "\uFFFD");
97832
+ }
97833
+ function toolText(r, find) {
97834
+ const name = redactCarrying(r.text, find);
97835
+ const nameMasked = maskedSlice(name, 0, name.plain.length);
97836
+ if (!r.arg)
97837
+ return { plain: `${name.plain}()`, masked: `${nameMasked}()` };
97838
+ const arg = redactCarrying(r.arg, find);
97839
+ const plain = collapseWhitespace(arg.plain);
97840
+ const masked = collapseWhitespace(maskedSlice(arg, 0, arg.plain.length));
97841
+ const end = ellipsisCut(plain, TOOL_ARG_MAX_CHARS);
97842
+ const cut = end < plain.length;
97843
+ const tail = cut ? "\u2026" : "";
97844
+ return {
97845
+ plain: `${name.plain}(${plain.slice(0, end)}${tail})`,
97846
+ masked: `${nameMasked}(${masked.slice(0, end)}${tail})`
97847
+ };
97848
+ }
97849
+ function* finishLine(r, find) {
97850
+ let plain;
97851
+ let slice;
97852
+ if (r.kind === "tool") {
97853
+ const t = toolText(r, find);
97854
+ plain = t.plain;
97855
+ slice = (a, b) => t.masked.slice(a, b);
97856
+ } else {
97857
+ const red = redactCarrying(r.text, find);
97858
+ plain = red.plain;
97859
+ slice = (a, b) => maskedSlice(red, a, b);
97860
+ }
97861
+ let first = true;
97862
+ for (let pos = 0;pos < plain.length; ) {
97863
+ const a = pos;
97864
+ const b = nextCut(plain, pos, TRANSCRIPT_LINE_MAX_CHARS, TRANSCRIPT_LINE_MAX_JSON_BYTES);
97865
+ pos = b;
97866
+ const cont = !first;
97867
+ first = false;
97868
+ yield {
97869
+ kind: r.kind,
97870
+ cont,
97871
+ build: () => cont ? { kind: r.kind, text: slice(a, b), cont: true } : { kind: r.kind, text: slice(a, b) }
97872
+ };
97873
+ }
97874
+ }
97875
+ function* entryLines(raw, find = signingValueRanges) {
97876
+ const rec = parseRecord3(raw);
97877
+ if (!rec || Array.isArray(rec))
97878
+ return;
97879
+ for (const r of renderEntry(rec))
97880
+ yield* finishLine(r, find);
97881
+ }
97882
+ var SIGNING_UNIT_MASK = "*", LONE_SURROGATE_RE2;
97883
+ var init_transcript_redaction = __esm(() => {
97884
+ init_redact();
97885
+ init_contract();
97886
+ init_transcript_follower();
97887
+ init_transcript_lines();
97888
+ LONE_SURROGATE_RE2 = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
97889
+ });
97890
+
97891
+ // src/pane/transcript-page.ts
97892
+ import {
97893
+ constants as constants4,
97894
+ closeSync as closeSync12,
97895
+ fstatSync as fstatSync5,
97896
+ lstatSync as lstatSync6,
97897
+ openSync as openSync12,
97898
+ readSync as readSync5,
97899
+ readdirSync as readdirSync8,
97900
+ readlinkSync,
97901
+ realpathSync as realpathSync6,
97902
+ statSync as statSync16
97903
+ } from "fs";
97904
+ import { basename as basename4, dirname as dirname20, join as join51, sep } from "path";
97905
+ function isUuid(v) {
97906
+ return typeof v === "string" && UUID_RE.test(v);
97907
+ }
97908
+ function id8Of(uuid) {
97909
+ return uuid.replace(/-/g, "").slice(0, 8).toLowerCase();
97910
+ }
97911
+ function decodeCursor(s) {
97912
+ const m = typeof s === "string" ? CURSOR_RE.exec(s) : null;
97913
+ if (!m)
97914
+ throw new ContractErrorException("invalid_args", "'since' is not a transcript cursor");
97915
+ const offset = Number(m[1]);
97916
+ const k = Number(m[2]);
97917
+ if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(k))
97918
+ throw new ContractErrorException("invalid_args", "'since' is not a transcript cursor");
97919
+ return { offset, k, id8: m[3] };
97920
+ }
97921
+ function encodeCursor(c) {
97922
+ return `${CURSOR_PREFIX}.${c.offset}.${c.k}.${c.id8}`;
97923
+ }
97924
+
97925
+ class LineScanner {
97926
+ fd;
97927
+ size;
97928
+ chunk = Buffer.alloc(0);
97929
+ chunkStart;
97930
+ idx = 0;
97931
+ constructor(fd, size, start) {
97932
+ this.fd = fd;
97933
+ this.size = size;
97934
+ this.chunkStart = start;
97935
+ }
97936
+ get position() {
97937
+ return this.chunkStart + this.idx;
97938
+ }
97939
+ next(collect, maxBytes) {
97940
+ const start = this.position;
97941
+ const parts = [];
97942
+ let len = 0;
97943
+ let keep = collect;
97944
+ for (;; ) {
97945
+ if (this.idx >= this.chunk.length) {
97946
+ const at = this.chunkStart + this.chunk.length;
97947
+ if (at >= this.size)
97948
+ return null;
97949
+ const n = Math.min(READ_CHUNK, this.size - at);
97950
+ const b = Buffer.alloc(n);
97951
+ const got = readSync5(this.fd, b, 0, n, at);
97952
+ if (got <= 0)
97953
+ return null;
97954
+ this.chunk = got < n ? b.subarray(0, got) : b;
97955
+ this.chunkStart = at;
97956
+ this.idx = 0;
97957
+ }
97958
+ const nl = this.chunk.indexOf(10, this.idx);
97959
+ const stop = nl === -1 ? this.chunk.length : nl;
97960
+ if (keep) {
97961
+ const piece = this.chunk.subarray(this.idx, stop);
97962
+ len += piece.length;
97963
+ if (len > maxBytes) {
97964
+ keep = false;
97965
+ parts.length = 0;
97966
+ } else
97967
+ parts.push(piece);
97968
+ }
97969
+ if (nl === -1) {
97970
+ this.idx = this.chunk.length;
97971
+ continue;
97972
+ }
97973
+ this.idx = nl + 1;
97974
+ return { start, end: this.chunkStart + nl, data: keep ? Buffer.concat(parts) : null };
97975
+ }
97976
+ }
97977
+ }
97978
+ function lineCost(l) {
97979
+ return Buffer.byteLength(JSON.stringify(l)) + 1;
97980
+ }
97981
+ function absentPage(from) {
97982
+ return { lines: [], next: from, more: false, at: [] };
97983
+ }
97984
+ function closeQuietly(fd) {
97985
+ try {
97986
+ closeSync12(fd);
97987
+ } catch {}
97988
+ }
97989
+ function readTranscriptPageFd(fd, from, limits) {
97990
+ const lim = { ...DEFAULT_PAGE_LIMITS, ...limits };
97991
+ const st = fstatSync5(fd);
97992
+ if (!st.isFile())
97993
+ return absentPage(from);
97994
+ const size = st.size;
97995
+ if (from.offset > size)
97996
+ throw new ContractErrorException("invalid_args", "'since' is past the end of the transcript");
97997
+ if (from.offset > 0) {
97998
+ const b = Buffer.alloc(1);
97999
+ const got = readSync5(fd, b, 0, 1, from.offset - 1);
98000
+ if (got !== 1 || b[0] !== 10)
98001
+ throw new ContractErrorException("invalid_args", "'since' is not at a line start");
98002
+ }
98003
+ const scanner = new LineScanner(fd, size, from.offset);
98004
+ const lines = [];
98005
+ const at = [];
98006
+ let bytes = lim.envelopeBytes;
98007
+ let next = { ...from };
98008
+ let more = false;
98009
+ for (;; ) {
98010
+ if (scanner.position - from.offset >= lim.scanBytes) {
98011
+ if (scanner.next(false, 0))
98012
+ more = true;
98013
+ break;
98014
+ }
98015
+ const l = scanner.next(true, lim.maxEntryBytes);
98016
+ if (!l)
98017
+ break;
98018
+ const startK = l.start === from.offset ? from.k : 0;
98019
+ if (l.data) {
98020
+ let j = 0;
98021
+ for (const pending of entryLines(l.data.toString("utf-8"))) {
98022
+ if (j < startK) {
98023
+ j++;
98024
+ continue;
98025
+ }
98026
+ const stop = { lines, next: { offset: l.start, k: j, id8: from.id8 }, more: true, at };
98027
+ if (lines.length >= lim.maxLines)
98028
+ return stop;
98029
+ const line = pending.build();
98030
+ const cost = lineCost(line);
98031
+ if (lines.length > 0 && bytes + cost > lim.maxAnswerBytes)
98032
+ return stop;
98033
+ lines.push(line);
98034
+ at.push({ offset: l.start, k: j, id8: from.id8 });
98035
+ bytes += cost;
98036
+ j++;
98037
+ }
98038
+ }
98039
+ next = { offset: l.end + 1, k: 0, id8: from.id8 };
98040
+ }
98041
+ return { lines, next, more, at };
98042
+ }
98043
+ function locateTranscriptByUuid(projectsRoot, uuid) {
98044
+ if (!isUuid(uuid))
98045
+ return null;
98046
+ let dirs;
98047
+ try {
98048
+ dirs = readdirSync8(projectsRoot);
98049
+ } catch {
98050
+ return null;
98051
+ }
98052
+ let hit = null;
98053
+ for (const d of dirs) {
98054
+ const p = join51(projectsRoot, d, `${uuid}.jsonl`);
98055
+ try {
98056
+ if (!statSync16(p).isFile())
98057
+ continue;
98058
+ } catch {
98059
+ continue;
98060
+ }
98061
+ if (hit !== null)
98062
+ return null;
98063
+ hit = p;
98064
+ }
98065
+ return hit;
98066
+ }
98067
+ function writerLiveness(pid) {
98068
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0)
98069
+ return "unknown";
98070
+ try {
98071
+ process.kill(pid, 0);
98072
+ return "alive";
98073
+ } catch (e) {
98074
+ const code = e?.code;
98075
+ if (code === "ESRCH")
98076
+ return "dead";
98077
+ if (code === "EPERM")
98078
+ return "alive";
98079
+ return "unknown";
98080
+ }
98081
+ }
98082
+ function fileAgeMs(path, now = Date.now()) {
98083
+ try {
98084
+ return now - statSync16(path).mtimeMs;
98085
+ } catch {
98086
+ return null;
98087
+ }
98088
+ }
98089
+ function transcriptQuiet(path, now, graceMs) {
98090
+ if (path === null)
98091
+ return true;
98092
+ const age = fileAgeMs(path, now);
98093
+ return age === null || age >= graceMs;
98094
+ }
98095
+ function logRejection(path, why) {
98096
+ if (loggedRejections.has(path) || loggedRejections.size >= MAX_LOGGED_REJECTIONS)
98097
+ return;
98098
+ loggedRejections.add(path);
98099
+ log(`[transcript] refused transcript path ${path}: ${why}`);
98100
+ }
98101
+ function isEnoent(e) {
98102
+ const code = e?.code;
98103
+ return code === "ENOENT" || code === "ENOTDIR";
98104
+ }
98105
+ function errText(e) {
98106
+ return e instanceof Error ? e.message : String(e);
98107
+ }
98108
+ function openContainedTranscript(path, uuid, projectsRoot) {
98109
+ if (basename4(path) !== `${uuid}.jsonl`) {
98110
+ logRejection(path, "basename is not the session uuid");
98111
+ return null;
98112
+ }
98113
+ let real;
98114
+ let root;
98115
+ try {
98116
+ real = realpathSync6(path);
98117
+ root = realpathSync6(projectsRoot);
98118
+ } catch (e) {
98119
+ if (!isEnoent(e))
98120
+ logRejection(path, errText(e));
98121
+ return null;
98122
+ }
98123
+ if (!real.startsWith(root + sep)) {
98124
+ logRejection(path, "outside the Claude Code projects directory");
98125
+ return null;
98126
+ }
98127
+ let fd;
98128
+ try {
98129
+ fd = openSync12(real, OPEN_FLAGS);
98130
+ } catch (e) {
98131
+ if (e?.code === "ELOOP")
98132
+ logRejection(path, "the transcript became a symlink");
98133
+ else if (!isEnoent(e))
98134
+ logRejection(path, errText(e));
98135
+ return null;
98136
+ }
98137
+ try {
98138
+ const st = fstatSync5(fd);
98139
+ if (!st.isFile()) {
98140
+ logRejection(path, "not a regular file");
98141
+ closeQuietly(fd);
98142
+ return null;
98143
+ }
98144
+ const parent = dirname20(real);
98145
+ const named = lstatSync6(real);
98146
+ if (realpathSync6(parent) !== parent || !named.isFile() || named.dev !== st.dev || named.ino !== st.ino || !kernelPathMatches(fd, real)) {
98147
+ logRejection(path, "the opened file is not the one under the projects directory");
98148
+ closeQuietly(fd);
98149
+ return null;
98150
+ }
98151
+ return fd;
98152
+ } catch (e) {
98153
+ if (!isEnoent(e))
98154
+ logRejection(path, errText(e));
98155
+ closeQuietly(fd);
98156
+ return null;
98157
+ }
98158
+ }
98159
+ function kernelPathMatches(fd, real) {
98160
+ if (process.platform !== "linux")
98161
+ return true;
98162
+ let kernel;
98163
+ try {
98164
+ kernel = readlinkSync(`/proc/self/fd/${fd}`);
98165
+ } catch {
98166
+ return true;
98167
+ }
98168
+ return kernel === real;
98169
+ }
98170
+ function envelopeBytesOf(address) {
98171
+ return Buffer.byteLength(JSON.stringify({ ...address, cursor: WORST_CASE_CURSOR, lines: [], more: false, final: false }));
98172
+ }
98173
+ function transcriptAnswer(src, since, address, limits) {
98174
+ const uuid = isUuid(src.sessionUuid) ? src.sessionUuid.toLowerCase() : null;
98175
+ const id8 = uuid ? id8Of(uuid) : NO_UUID_ID8;
98176
+ if (since && since.id8 !== id8)
98177
+ throw new ContractErrorException("invalid_args", "the cursor belongs to another transcript");
98178
+ const from = since ?? { offset: 0, k: 0, id8 };
98179
+ const lim = { envelopeBytes: envelopeBytesOf(address), ...limits };
98180
+ const maxBytes = lim.maxAnswerBytes ?? DEFAULT_PAGE_LIMITS.maxAnswerBytes;
98181
+ let page;
98182
+ if (!uuid) {
98183
+ if (from.offset !== 0 || from.k !== 0)
98184
+ throw new ContractErrorException("invalid_args", "the cursor belongs to another transcript");
98185
+ page = absentPage(from);
98186
+ } else {
98187
+ const fd = src.transcriptPath === null ? null : openContainedTranscript(src.transcriptPath, uuid, src.projectsRoot);
98188
+ if (fd === null)
98189
+ page = absentPage(from);
98190
+ else {
98191
+ try {
98192
+ page = readTranscriptPageFd(fd, from, lim);
98193
+ } finally {
98194
+ closeQuietly(fd);
98195
+ }
98196
+ }
98197
+ }
98198
+ const build = () => ({
98199
+ ...address,
98200
+ cursor: encodeCursor(page.next),
98201
+ lines: page.lines,
98202
+ more: page.more,
98203
+ final: src.writerGone && !page.more
98204
+ });
98205
+ let answer = build();
98206
+ while (page.lines.length > 1 && Buffer.byteLength(JSON.stringify(answer)) > maxBytes) {
98207
+ const last = page.at[page.lines.length - 1];
98208
+ page = {
98209
+ lines: page.lines.slice(0, -1),
98210
+ at: page.at.slice(0, -1),
98211
+ next: last,
98212
+ more: true
98213
+ };
98214
+ answer = build();
98215
+ }
98216
+ return answer;
98217
+ }
98218
+ var NO_UUID_ID8 = "00000000", CURSOR_PREFIX = "v1", CURSOR_RE, UUID_RE, WORST_CASE_CURSOR, DEFAULT_PAGE_LIMITS, READ_CHUNK, OPEN_FLAGS, TRANSCRIPT_GRACE_MS = 30000, loggedRejections, MAX_LOGGED_REJECTIONS = 256;
98219
+ var init_transcript_page = __esm(() => {
98220
+ init_logger();
98221
+ init_contract();
98222
+ init_transcript_redaction();
98223
+ CURSOR_RE = /^v1\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.([0-9a-f]{8})$/;
98224
+ UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
98225
+ WORST_CASE_CURSOR = `${CURSOR_PREFIX}.${"9".repeat(16)}.${"9".repeat(16)}.${"f".repeat(8)}`;
98226
+ DEFAULT_PAGE_LIMITS = {
98227
+ maxLines: MAX_TRANSCRIPT_LINES,
98228
+ maxAnswerBytes: TRANSCRIPT_ANSWER_MAX_BYTES,
98229
+ envelopeBytes: 0,
98230
+ scanBytes: 16 * 1024 * 1024,
98231
+ maxEntryBytes: 32 * 1024 * 1024
98232
+ };
98233
+ READ_CHUNK = 1024 * 1024;
98234
+ OPEN_FLAGS = constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0) | (constants4.O_NONBLOCK ?? 0);
98235
+ loggedRejections = new Set;
98236
+ });
98237
+
97226
98238
  // src/pane/types.ts
97227
98239
  function chainAnswerOf(t) {
97228
98240
  return [...t.chainAnswers, t.answer].filter((a) => a !== "").join(`
@@ -97239,6 +98251,10 @@ var init_pane = __esm(() => {
97239
98251
  init_pane_launch();
97240
98252
  init_pane_registry();
97241
98253
  init_pane_session();
98254
+ init_scroll_input();
98255
+ init_transcript_lines();
98256
+ init_transcript_redaction();
98257
+ init_transcript_page();
97242
98258
  init_contract();
97243
98259
  });
97244
98260
 
@@ -97259,10 +98275,10 @@ __export(exports_session_discovery, {
97259
98275
  transcriptPathFor: () => transcriptPathFor
97260
98276
  });
97261
98277
  import { execFile as execFile4, execFileSync as execFileSync3 } from "child_process";
97262
- import { closeSync as closeSync12, openSync as openSync12, readSync as readSync5, readdirSync as readdirSync8, realpathSync as realpathSync6, statSync as statSync16 } from "fs";
97263
- import { basename as basename4, join as join51 } from "path";
98278
+ import { closeSync as closeSync13, openSync as openSync13, readSync as readSync6, readdirSync as readdirSync9, realpathSync as realpathSync7, statSync as statSync17 } from "fs";
98279
+ import { basename as basename5, join as join52 } from "path";
97264
98280
  function projectsDir(env = process.env) {
97265
- return join51(claudeConfigDir(env), "projects");
98281
+ return join52(claudeConfigDir(env), "projects");
97266
98282
  }
97267
98283
  function slugForPath(absPath) {
97268
98284
  return projectDirNameFor(absPath);
@@ -97270,9 +98286,9 @@ function slugForPath(absPath) {
97270
98286
  function transcriptPathFor(cwd, sessionUuid, projectsRoot = projectsDir()) {
97271
98287
  let real = cwd;
97272
98288
  try {
97273
- real = realpathSync6(cwd);
98289
+ real = realpathSync7(cwd);
97274
98290
  } catch {}
97275
- return join51(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
98291
+ return join52(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
97276
98292
  }
97277
98293
  function isAgentSession(row) {
97278
98294
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -97313,28 +98329,28 @@ function getRepoContext(cwd = process.cwd()) {
97313
98329
  }
97314
98330
  function projectDirs() {
97315
98331
  try {
97316
- return readdirSync8(projectsDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
98332
+ return readdirSync9(projectsDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
97317
98333
  } catch {
97318
98334
  return [];
97319
98335
  }
97320
98336
  }
97321
98337
  function sessionsIn2(dirName) {
97322
- const dir = join51(projectsDir(), dirName);
98338
+ const dir = join52(projectsDir(), dirName);
97323
98339
  let names;
97324
98340
  try {
97325
- names = readdirSync8(dir).filter((n) => n.endsWith(".jsonl"));
98341
+ names = readdirSync9(dir).filter((n) => n.endsWith(".jsonl"));
97326
98342
  } catch {
97327
98343
  return [];
97328
98344
  }
97329
98345
  const rows = [];
97330
98346
  for (const n of names) {
97331
- const file = join51(dir, n);
98347
+ const file = join52(dir, n);
97332
98348
  try {
97333
- const st = statSync16(file);
98349
+ const st = statSync17(file);
97334
98350
  if (st.size === 0)
97335
98351
  continue;
97336
98352
  const row = {
97337
- id: basename4(n, ".jsonl"),
98353
+ id: basename5(n, ".jsonl"),
97338
98354
  file,
97339
98355
  mtimeMs: st.mtimeMs,
97340
98356
  birthtimeMs: st.birthtimeMs,
@@ -97436,7 +98452,7 @@ function discoverWorktreeGroups(repo) {
97436
98452
  if (hit) {
97437
98453
  path = hit.path;
97438
98454
  live = true;
97439
- name = hit.path === repo.root ? "(root)" : basename4(hit.path);
98455
+ name = hit.path === repo.root ? "(root)" : basename5(hit.path);
97440
98456
  } else if (at !== -1 && dir.startsWith(rootSlug)) {
97441
98457
  path = null;
97442
98458
  live = false;
@@ -97491,7 +98507,7 @@ function discoverWorktreeGroups(repo) {
97491
98507
  g.activeNow = g.sessions.some((s) => isActive(s));
97492
98508
  if (g.path) {
97493
98509
  try {
97494
- g.createdMs = statSync16(g.path).birthtimeMs;
98510
+ g.createdMs = statSync17(g.path).birthtimeMs;
97495
98511
  } catch {}
97496
98512
  }
97497
98513
  if (!g.createdMs && g.sessions.length > 0) {
@@ -97509,16 +98525,16 @@ function readChunk(file, pos, len) {
97509
98525
  return "";
97510
98526
  let fd = null;
97511
98527
  try {
97512
- fd = openSync12(file, "r");
98528
+ fd = openSync13(file, "r");
97513
98529
  const buf = Buffer.allocUnsafe(len);
97514
- const n = readSync5(fd, buf, 0, len, pos);
98530
+ const n = readSync6(fd, buf, 0, len, pos);
97515
98531
  return buf.subarray(0, n).toString("utf-8");
97516
98532
  } catch {
97517
98533
  return "";
97518
98534
  } finally {
97519
98535
  if (fd !== null) {
97520
98536
  try {
97521
- closeSync12(fd);
98537
+ closeSync13(fd);
97522
98538
  } catch {}
97523
98539
  }
97524
98540
  }
@@ -97579,7 +98595,7 @@ function hydrateSession(row) {
97579
98595
  row.hydrated = true;
97580
98596
  if (isActive(row)) {
97581
98597
  try {
97582
- row.sizeBytes = statSync16(row.file).size;
98598
+ row.sizeBytes = statSync17(row.file).size;
97583
98599
  } catch {}
97584
98600
  }
97585
98601
  const head = parseRecords(readChunk(row.file, 0, Math.min(HEAD_BYTES, row.sizeBytes)), false);
@@ -97707,16 +98723,16 @@ var init_session_discovery = __esm(() => {
97707
98723
  import { randomBytes as randomBytes13, randomUUID as randomUUID8 } from "crypto";
97708
98724
  import {
97709
98725
  existsSync as existsSync36,
97710
- lstatSync as lstatSync6,
98726
+ lstatSync as lstatSync7,
97711
98727
  mkdirSync as mkdirSync24,
97712
98728
  readFileSync as readFileSync42,
97713
- readdirSync as readdirSync9,
97714
- realpathSync as realpathSync7,
98729
+ readdirSync as readdirSync10,
98730
+ realpathSync as realpathSync8,
97715
98731
  rmSync as rmSync12,
97716
- statSync as statSync17,
98732
+ statSync as statSync18,
97717
98733
  writeFileSync as writeFileSync22
97718
98734
  } from "fs";
97719
- import { basename as basename5, dirname as dirname20, join as join52, resolve as resolve5, sep } from "path";
98735
+ import { basename as basename6, dirname as dirname21, join as join53, resolve as resolve5, sep as sep2 } from "path";
97720
98736
  function slotTerminal(e) {
97721
98737
  if (!e.session)
97722
98738
  return true;
@@ -97785,7 +98801,7 @@ function notableAnomalies(snap) {
97785
98801
  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"));
97786
98802
  }
97787
98803
  function upstreamLogPath(path, id) {
97788
- return join52(path, "errors", `${id}-upstream.jsonl`);
98804
+ return join53(path, "errors", `${id}-upstream.jsonl`);
97789
98805
  }
97790
98806
  function neverSpawnedRow(e, m) {
97791
98807
  return {
@@ -97882,9 +98898,9 @@ function teamRunRowFromDisk(path, status) {
97882
98898
  const settled = slots.length > 0 && slots.every((s) => isTerminalState(s.state));
97883
98899
  const completions = Object.values(status.models ?? {}).map((m) => m?.completedAt ? Date.parse(m.completedAt) : Number.NaN).filter(Number.isFinite);
97884
98900
  return {
97885
- run_id: status.runId ?? basename5(path),
98901
+ run_id: status.runId ?? basename6(path),
97886
98902
  path,
97887
- kind: status.kind ?? (basename5(path) === "judging" ? "judge" : "run"),
98903
+ kind: status.kind ?? (basename6(path) === "judging" ? "judge" : "run"),
97888
98904
  started_at: status.startedAt,
97889
98905
  finished_at: settled ? new Date(completions.length ? Math.max(...completions) : statusMtimeMs(path) ?? Date.parse(status.startedAt)).toISOString() : null,
97890
98906
  state: settled ? "SETTLED" : "ACTIVE",
@@ -97894,14 +98910,14 @@ function teamRunRowFromDisk(path, status) {
97894
98910
  }
97895
98911
  function statusMtimeMs(path) {
97896
98912
  try {
97897
- return statSync17(join52(path, "status.json")).mtimeMs;
98913
+ return statSync18(join53(path, "status.json")).mtimeMs;
97898
98914
  } catch {
97899
98915
  return null;
97900
98916
  }
97901
98917
  }
97902
98918
  function readManifestOrNull(path) {
97903
98919
  try {
97904
- return JSON.parse(readFileSync42(join52(path, "manifest.json"), "utf-8"));
98920
+ return JSON.parse(readFileSync42(join53(path, "manifest.json"), "utf-8"));
97905
98921
  } catch {
97906
98922
  return null;
97907
98923
  }
@@ -97967,6 +98983,68 @@ async function resizeTeamSlot(path, slot, cols, rows, runId) {
97967
98983
  return { ...where, cols: DEFAULT_PANE_COLS, rows: DEFAULT_PANE_ROWS, changed: false, seq: 0 };
97968
98984
  return { ...where, ...await e.session.resize(cols, rows) };
97969
98985
  }
98986
+ async function scrollTeamSlot(path, slot, req, runId) {
98987
+ const run = addressRun(path, runId);
98988
+ if (!run)
98989
+ throw unknownRun(path, runId);
98990
+ const e = run.slots.get(slot);
98991
+ if (!e)
98992
+ throw new ContractErrorException("unknown_slot", `run ${run.runId} has no slot ${JSON.stringify(slot)}`);
98993
+ if (!e.session) {
98994
+ const state = run.status.models[e.id]?.state;
98995
+ throw new ContractErrorException("scroll_unavailable", state === undefined || isTerminalState(state) ? "the slot has finished" : "the slot is still starting");
98996
+ }
98997
+ const r = await e.session.scroll(req);
98998
+ return { run_id: run.runId, path: run.path, slot, seq: r.seq };
98999
+ }
99000
+ function transcriptTeamSlot(path, slot, since, runId, opts = {}) {
99001
+ const graceMs = opts.graceMs ?? TRANSCRIPT_GRACE_MS;
99002
+ const run = addressRun(path, runId);
99003
+ if (run) {
99004
+ const e = run.slots.get(slot);
99005
+ if (!e)
99006
+ throw new ContractErrorException("unknown_slot", `run ${run.runId} has no slot ${JSON.stringify(slot)}`);
99007
+ const m = run.status.models[e.id];
99008
+ const writerGone = e.session ? isTerminalState(e.session.snapshot().state) && e.reapVerified === true : m?.state === undefined || isTerminalState(m.state);
99009
+ const src = {
99010
+ transcriptPath: m?.transcriptPath || null,
99011
+ sessionUuid: m?.sessionUuid || null,
99012
+ writerGone,
99013
+ projectsRoot: run.projectsRoot
99014
+ };
99015
+ return transcriptAnswer(src, since, { run_id: run.runId, path: run.path, slot });
99016
+ }
99017
+ if (path === undefined)
99018
+ throw unknownRun(path, runId);
99019
+ const resolved = resolve5(path);
99020
+ let status;
99021
+ try {
99022
+ status = getStatus(resolved);
99023
+ } catch {
99024
+ throw unknownRun(path, runId);
99025
+ }
99026
+ if (!status || typeof status !== "object" || !status.models || typeof status.models !== "object")
99027
+ throw unknownRun(path, runId);
99028
+ const fileRunId = typeof status.runId === "string" && status.runId ? status.runId : basename6(resolved);
99029
+ if (runId !== undefined && runId !== fileRunId)
99030
+ throw unknownRun(path, runId);
99031
+ const m = Object.hasOwn(status.models, slot) ? status.models[slot] : undefined;
99032
+ if (!m || typeof m !== "object")
99033
+ throw new ContractErrorException("unknown_slot", `run ${fileRunId} has no slot ${JSON.stringify(slot)}`);
99034
+ const now = Date.now();
99035
+ const endedMs = typeof m.completedAt === "string" ? Date.parse(m.completedAt) : Number.NaN;
99036
+ const mtime = statusMtimeMs(resolved);
99037
+ const age = Number.isFinite(endedMs) ? now - endedMs : mtime === null ? null : now - mtime;
99038
+ const terminal = typeof m.state === "string" && SLOT_STATES.includes(m.state) && isTerminalState(m.state);
99039
+ const transcriptPath = typeof m.transcriptPath === "string" && m.transcriptPath ? m.transcriptPath : null;
99040
+ const src = {
99041
+ transcriptPath,
99042
+ sessionUuid: typeof m.sessionUuid === "string" && m.sessionUuid ? m.sessionUuid : null,
99043
+ writerGone: terminal && age !== null && age >= graceMs && transcriptQuiet(transcriptPath, now, graceMs),
99044
+ projectsRoot: projectsDir(opts.parentEnv ?? process.env)
99045
+ };
99046
+ return transcriptAnswer(src, since, { run_id: fileRunId, path: resolved, slot });
99047
+ }
97970
99048
  function teamLiveMaps(path, runId) {
97971
99049
  const run = addressRun(path, runId);
97972
99050
  if (!run)
@@ -98099,20 +99177,20 @@ function answerFailureDetail(v, answer, requirePattern, outcomes) {
98099
99177
  function readWrittenFile(path, root) {
98100
99178
  let realRoot;
98101
99179
  try {
98102
- realRoot = realpathSync7(root);
99180
+ realRoot = realpathSync8(root);
98103
99181
  } catch {
98104
99182
  realRoot = resolve5(root);
98105
99183
  }
98106
99184
  let real;
98107
99185
  try {
98108
- real = realpathSync7(resolve5(root, path));
99186
+ real = realpathSync8(resolve5(root, path));
98109
99187
  } catch {
98110
99188
  return { skip: "missing" };
98111
99189
  }
98112
- if (real !== realRoot && !real.startsWith(realRoot + sep))
99190
+ if (real !== realRoot && !real.startsWith(realRoot + sep2))
98113
99191
  return { skip: "outside the working directory" };
98114
99192
  try {
98115
- const st = statSync17(real);
99193
+ const st = statSync18(real);
98116
99194
  if (!st.isFile())
98117
99195
  return { skip: "not a regular file" };
98118
99196
  if (st.size > MAX_FILE_ANSWER_BYTES)
@@ -98165,24 +99243,24 @@ function validateSessionPath(sessionPath) {
98165
99243
  const tail = [];
98166
99244
  for (;; ) {
98167
99245
  try {
98168
- lstatSync6(existing);
99246
+ lstatSync7(existing);
98169
99247
  break;
98170
99248
  } catch {
98171
- const parent = dirname20(existing);
99249
+ const parent = dirname21(existing);
98172
99250
  if (parent === existing)
98173
99251
  break;
98174
- tail.unshift(basename5(existing));
99252
+ tail.unshift(basename6(existing));
98175
99253
  existing = parent;
98176
99254
  }
98177
99255
  }
98178
99256
  let real;
98179
99257
  try {
98180
- real = join52(realpathSync7(existing), ...tail);
99258
+ real = join53(realpathSync8(existing), ...tail);
98181
99259
  } catch {
98182
99260
  throw outside();
98183
99261
  }
98184
- const cwd = realpathSync7(process.cwd());
98185
- if (real !== cwd && !real.startsWith(cwd + sep)) {
99262
+ const cwd = realpathSync8(process.cwd());
99263
+ if (real !== cwd && !real.startsWith(cwd + sep2)) {
98186
99264
  throw outside();
98187
99265
  }
98188
99266
  return resolved;
@@ -98206,14 +99284,14 @@ function setupSession(sessionPath, models, input) {
98206
99284
  if (models.length === 0) {
98207
99285
  throw new Error("At least one model is required");
98208
99286
  }
98209
- if (existsSync36(join52(sessionPath, "manifest.json"))) {
99287
+ if (existsSync36(join53(sessionPath, "manifest.json"))) {
98210
99288
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
98211
99289
  }
98212
- mkdirSync24(join52(sessionPath, "work"), { recursive: true });
98213
- mkdirSync24(join52(sessionPath, "errors"), { recursive: true });
99290
+ mkdirSync24(join53(sessionPath, "work"), { recursive: true });
99291
+ mkdirSync24(join53(sessionPath, "errors"), { recursive: true });
98214
99292
  if (input !== undefined) {
98215
- writeFileSync22(join52(sessionPath, "input.md"), input, "utf-8");
98216
- } else if (!existsSync36(join52(sessionPath, "input.md"))) {
99293
+ writeFileSync22(join53(sessionPath, "input.md"), input, "utf-8");
99294
+ } else if (!existsSync36(join53(sessionPath, "input.md"))) {
98217
99295
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
98218
99296
  }
98219
99297
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -98230,9 +99308,9 @@ function setupSession(sessionPath, models, input) {
98230
99308
  model: models[i],
98231
99309
  assignedAt: now
98232
99310
  };
98233
- mkdirSync24(join52(sessionPath, "work", anonId), { recursive: true });
99311
+ mkdirSync24(join53(sessionPath, "work", anonId), { recursive: true });
98234
99312
  }
98235
- writeFileSync22(join52(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
99313
+ writeFileSync22(join53(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
98236
99314
  const status = {
98237
99315
  startedAt: now,
98238
99316
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -98247,7 +99325,7 @@ function setupSession(sessionPath, models, input) {
98247
99325
  }
98248
99326
  ]))
98249
99327
  };
98250
- writeFileAtomic(join52(sessionPath, "status.json"), JSON.stringify(status, null, 2));
99328
+ writeFileAtomic(join53(sessionPath, "status.json"), JSON.stringify(status, null, 2));
98251
99329
  return manifest;
98252
99330
  }
98253
99331
  function assertValidRequirePattern(pattern) {
@@ -98282,7 +99360,7 @@ async function preflightTeamRun(opts) {
98282
99360
  throw new Error(`pane_limit: ${live} live panes for this user, ${opts.slots} more would exceed ${MAX_LIVE_PANES}`);
98283
99361
  }
98284
99362
  function mintRunId(path, startMs) {
98285
- return `${basename5(path)}-${startMs.toString(36)}-${randomBytes13(3).toString("hex")}`;
99363
+ return `${basename6(path)}-${startMs.toString(36)}-${randomBytes13(3).toString("hex")}`;
98286
99364
  }
98287
99365
  function startModels(sessionPath, opts = {}) {
98288
99366
  const flight = { aborted: false };
@@ -98308,9 +99386,9 @@ async function startModelsIn(flight, sessionPath, opts) {
98308
99386
  throw new Error(`invalid_args: ${flagCheck.message}`);
98309
99387
  const agentRequested = requestedAgentOf(claudeFlags);
98310
99388
  const path = resolve5(sessionPath);
98311
- const manifest = JSON.parse(readFileSync42(join52(path, "manifest.json"), "utf-8"));
98312
- const statusPath = join52(path, "status.json");
98313
- const inputContent = readFileSync42(join52(path, "input.md"), "utf-8");
99389
+ const manifest = JSON.parse(readFileSync42(join53(path, "manifest.json"), "utf-8"));
99390
+ const statusPath = join53(path, "status.json");
99391
+ const inputContent = readFileSync42(join53(path, "input.md"), "utf-8");
98314
99392
  const readAvailable = !flagsRemoveRead(claudeFlags);
98315
99393
  const refusal = deliveryRefusal(inputContent, readAvailable);
98316
99394
  if (refusal)
@@ -98350,7 +99428,7 @@ async function startModelsIn(flight, sessionPath, opts) {
98350
99428
  mkdirSync24(statsDir(path), { recursive: true });
98351
99429
  const entries = new Map;
98352
99430
  let run = null;
98353
- const errorLogPathOf = (id) => join52(path, "errors", `${id}.log`);
99431
+ const errorLogPathOf = (id) => join53(path, "errors", `${id}.log`);
98354
99432
  const upstreamLogOf = (id) => upstreamLogPath(path, id);
98355
99433
  const flagsOf = (e) => {
98356
99434
  const effort = opts.efforts?.[e.model];
@@ -98360,7 +99438,7 @@ async function startModelsIn(flight, sessionPath, opts) {
98360
99438
  function writeResponse(e, text) {
98361
99439
  e.answer = text;
98362
99440
  try {
98363
- writeFileSync22(join52(path, `response-${e.id}.md`), text, "utf-8");
99441
+ writeFileSync22(join53(path, `response-${e.id}.md`), text, "utf-8");
98364
99442
  } catch (err) {
98365
99443
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
98366
99444
  }
@@ -98370,7 +99448,7 @@ async function startModelsIn(flight, sessionPath, opts) {
98370
99448
  if (e.answer !== null || !e.session)
98371
99449
  return;
98372
99450
  try {
98373
- writeFileSync22(join52(path, `response-${e.id}.md`), e.session.chainText(), "utf-8");
99451
+ writeFileSync22(join53(path, `response-${e.id}.md`), e.session.chainText(), "utf-8");
98374
99452
  } catch (err) {
98375
99453
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
98376
99454
  }
@@ -98567,7 +99645,7 @@ async function startModelsIn(flight, sessionPath, opts) {
98567
99645
  }
98568
99646
  async function startSlot(e, uuid, transcriptPath) {
98569
99647
  let session;
98570
- e.inboxPath = createHookInbox(join52(path, "work"), `${e.id}.hook-inbox.jsonl`);
99648
+ e.inboxPath = createHookInbox(join53(path, "work"), `${e.id}.hook-inbox.jsonl`);
98571
99649
  const binding = bindingFor(e, uuid);
98572
99650
  e.binding = binding;
98573
99651
  try {
@@ -98607,6 +99685,9 @@ async function startModelsIn(flight, sessionPath, opts) {
98607
99685
  return;
98608
99686
  }
98609
99687
  e.session = session;
99688
+ session.reapVerified?.().then(() => {
99689
+ e.reapVerified = true;
99690
+ });
98610
99691
  session.reaped().then(() => {
98611
99692
  if (!binding && e.inboxPath)
98612
99693
  reportOwnerWithoutHooks(e.inboxPath, hookEngine, { model: e.model, sessionId: uuid });
@@ -98746,7 +99827,8 @@ async function startModelsIn(flight, sessionPath, opts) {
98746
99827
  finishedAt: null,
98747
99828
  status: statusCache,
98748
99829
  slots: entries,
98749
- settled
99830
+ settled,
99831
+ projectsRoot: projects
98750
99832
  };
98751
99833
  teamRuns.set(runId, run);
98752
99834
  newestRunByPath.set(path, runId);
@@ -98782,7 +99864,7 @@ async function startModelsIn(flight, sessionPath, opts) {
98782
99864
  done.catch((err) => log(`[team] run failed at ${path}: ${err instanceof Error ? err.message : String(err)}`));
98783
99865
  return {
98784
99866
  runId,
98785
- teamSessionId: basename5(path),
99867
+ teamSessionId: basename6(path),
98786
99868
  sessionPath: path,
98787
99869
  slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
98788
99870
  done
@@ -98796,20 +99878,20 @@ async function judgeResponses(sessionPath, opts = {}) {
98796
99878
  const active = activeRunAt(resolve5(sessionPath));
98797
99879
  if (active)
98798
99880
  throw new Error(`invalid_args: the team run at ${resolve5(sessionPath)} is still ACTIVE (run_id ${active.runId}); judge it once it has settled`);
98799
- const responseFiles = readdirSync9(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
99881
+ const responseFiles = readdirSync10(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
98800
99882
  if (responseFiles.length < 2) {
98801
99883
  throw new Error(`Need at least 2 responses to judge, found ${responseFiles.length}`);
98802
99884
  }
98803
99885
  const responses = {};
98804
99886
  for (const file of responseFiles) {
98805
99887
  const id = file.replace(/^response-/, "").replace(/\.md$/, "");
98806
- responses[id] = readFileSync42(join52(sessionPath, file), "utf-8");
99888
+ responses[id] = readFileSync42(join53(sessionPath, file), "utf-8");
98807
99889
  }
98808
- const input = readFileSync42(join52(sessionPath, "input.md"), "utf-8");
99890
+ const input = readFileSync42(join53(sessionPath, "input.md"), "utf-8");
98809
99891
  const judgePrompt = buildJudgePrompt(input, responses);
98810
- writeFileSync22(join52(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
99892
+ writeFileSync22(join53(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
98811
99893
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
98812
- const judgePath = join52(sessionPath, "judging");
99894
+ const judgePath = join53(sessionPath, "judging");
98813
99895
  mkdirSync24(judgePath, { recursive: true });
98814
99896
  setupSession(judgePath, judgeModels, judgePrompt);
98815
99897
  await runModels(judgePath, {
@@ -98821,11 +99903,11 @@ async function judgeResponses(sessionPath, opts = {}) {
98821
99903
  });
98822
99904
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
98823
99905
  const verdict = aggregateVerdict(votes, Object.keys(responses));
98824
- writeFileSync22(join52(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
99906
+ writeFileSync22(join53(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
98825
99907
  return verdict;
98826
99908
  }
98827
99909
  function getStatus(sessionPath) {
98828
- return JSON.parse(readFileSync42(join52(sessionPath, "status.json"), "utf-8"));
99910
+ return JSON.parse(readFileSync42(join53(sessionPath, "status.json"), "utf-8"));
98829
99911
  }
98830
99912
  function readTeamStatus(sessionPath) {
98831
99913
  try {
@@ -98865,7 +99947,7 @@ function fisherYatesShuffle(arr) {
98865
99947
  return arr;
98866
99948
  }
98867
99949
  function getDefaultJudgeModels(sessionPath) {
98868
- const manifest = JSON.parse(readFileSync42(join52(sessionPath, "manifest.json"), "utf-8"));
99950
+ const manifest = JSON.parse(readFileSync42(join53(sessionPath, "manifest.json"), "utf-8"));
98869
99951
  return Object.values(manifest.models).map((e) => e.model);
98870
99952
  }
98871
99953
  function buildJudgePrompt(input, responses) {
@@ -98923,12 +100005,12 @@ function buildJudgePrompt(input, responses) {
98923
100005
  }
98924
100006
  function parseJudgeVotes(judgePath, responseIds) {
98925
100007
  const votes = [];
98926
- const responseFiles = readdirSync9(judgePath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
100008
+ const responseFiles = readdirSync10(judgePath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
98927
100009
  for (const file of responseFiles) {
98928
100010
  const judgeId = file.replace(/^response-/, "").replace(/\.md$/, "");
98929
100011
  let content;
98930
100012
  try {
98931
- content = readFileSync42(join52(judgePath, file), "utf-8");
100013
+ content = readFileSync42(join53(judgePath, file), "utf-8");
98932
100014
  } catch (err) {
98933
100015
  log(`[team] judge answer ${file} unreadable: ${err instanceof Error ? err.message : String(err)}`);
98934
100016
  continue;
@@ -98981,7 +100063,7 @@ function aggregateVerdict(votes, responseIds) {
98981
100063
  function formatVerdict(verdict, sessionPath) {
98982
100064
  let manifest = null;
98983
100065
  try {
98984
- manifest = JSON.parse(readFileSync42(join52(sessionPath, "manifest.json"), "utf-8"));
100066
+ manifest = JSON.parse(readFileSync42(join53(sessionPath, "manifest.json"), "utf-8"));
98985
100067
  } catch {}
98986
100068
  let output = `# Team Verdict
98987
100069
 
@@ -99037,8 +100119,8 @@ var init_team_orchestrator = __esm(() => {
99037
100119
  });
99038
100120
 
99039
100121
  // src/mcp-session/inherited-settings.ts
99040
- import { existsSync as existsSync37, readFileSync as readFileSync43, statSync as statSync18 } from "fs";
99041
- import { join as join53, resolve as resolve6 } from "path";
100122
+ import { existsSync as existsSync37, readFileSync as readFileSync43, statSync as statSync19 } from "fs";
100123
+ import { join as join54, resolve as resolve6 } from "path";
99042
100124
  function isPlainObject7(value) {
99043
100125
  return typeof value === "object" && value !== null && !Array.isArray(value);
99044
100126
  }
@@ -99092,7 +100174,7 @@ function readSettingsValue(value) {
99092
100174
  function buildInheritedSettingsLayer(setupFrom, callerFlags) {
99093
100175
  const layers = [];
99094
100176
  for (const name of ["settings.json", "settings.local.json"]) {
99095
- const file = join53(setupFrom, ".claude", name);
100177
+ const file = join54(setupFrom, ".claude", name);
99096
100178
  if (!existsSync37(file))
99097
100179
  continue;
99098
100180
  layers.push(parseLayer(readFileSync43(file, "utf-8"), file));
@@ -99125,7 +100207,7 @@ function resolveSetupFrom(arg, workDir, serverCwd) {
99125
100207
  let setupFrom = serverCwd;
99126
100208
  if (typeof arg === "string") {
99127
100209
  setupFrom = resolve6(arg);
99128
- if (!statSync18(setupFrom, { throwIfNoEntry: false })?.isDirectory()) {
100210
+ if (!statSync19(setupFrom, { throwIfNoEntry: false })?.isDirectory()) {
99129
100211
  throw new Error(`invalid_args: setup_from ${setupFrom} is not an existing directory`);
99130
100212
  }
99131
100213
  } else if (arg !== undefined) {
@@ -99139,19 +100221,19 @@ var init_inherited_settings = () => {};
99139
100221
  import { randomUUID as randomUUID9 } from "crypto";
99140
100222
  import {
99141
100223
  appendFileSync as appendFileSync7,
99142
- closeSync as closeSync13,
100224
+ closeSync as closeSync14,
99143
100225
  createWriteStream,
99144
100226
  existsSync as existsSync38,
99145
100227
  mkdirSync as mkdirSync25,
99146
- openSync as openSync13,
100228
+ openSync as openSync14,
99147
100229
  readFileSync as readFileSync44,
99148
- readSync as readSync6,
100230
+ readSync as readSync7,
99149
100231
  renameSync as renameSync10,
99150
100232
  rmSync as rmSync13,
99151
- statSync as statSync19,
100233
+ statSync as statSync20,
99152
100234
  writeFileSync as writeFileSync23
99153
100235
  } from "fs";
99154
- import { join as join54, resolve as resolve7, sep as sep2 } from "path";
100236
+ import { join as join55, resolve as resolve7, sep as sep3 } from "path";
99155
100237
  function normaliseTimeoutSeconds(raw) {
99156
100238
  const n = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : raw;
99157
100239
  if (typeof n !== "number" || !Number.isFinite(n))
@@ -99255,21 +100337,21 @@ function anomalyKey(a) {
99255
100337
  function readTailText(path, maxBytes) {
99256
100338
  let fd = null;
99257
100339
  try {
99258
- const size = statSync19(path).size;
100340
+ const size = statSync20(path).size;
99259
100341
  if (size === 0)
99260
100342
  return { text: "", truncated: false };
99261
100343
  const start = Math.max(0, size - maxBytes);
99262
100344
  const length = size - start;
99263
100345
  const buf = Buffer.alloc(length);
99264
- fd = openSync13(path, "r");
99265
- readSync6(fd, buf, 0, length, start);
100346
+ fd = openSync14(path, "r");
100347
+ readSync7(fd, buf, 0, length, start);
99266
100348
  return { text: buf.toString("utf-8"), truncated: start > 0 };
99267
100349
  } catch {
99268
100350
  return null;
99269
100351
  } finally {
99270
100352
  if (fd !== null) {
99271
100353
  try {
99272
- closeSync13(fd);
100354
+ closeSync14(fd);
99273
100355
  } catch {}
99274
100356
  }
99275
100357
  }
@@ -99286,7 +100368,7 @@ function readTailLines2(path, maxBytes) {
99286
100368
  }
99287
100369
  function fileSize2(path) {
99288
100370
  try {
99289
- return statSync19(path).size;
100371
+ return statSync20(path).size;
99290
100372
  } catch {
99291
100373
  return 0;
99292
100374
  }
@@ -99321,7 +100403,7 @@ function dropLeadingFragment(tail) {
99321
100403
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
99322
100404
  }
99323
100405
  function diskAccounting(sessionDir) {
99324
- const stats = readTokenStatsAt(join54(sessionDir, "tokens.json"));
100406
+ const stats = readTokenStatsAt(join55(sessionDir, "tokens.json"));
99325
100407
  return {
99326
100408
  tokensIn: stats?.billed_input_tokens ?? null,
99327
100409
  tokensOut: stats?.output_tokens ?? null,
@@ -99361,6 +100443,7 @@ class SessionManager {
99361
100443
  scrollbackCapacity;
99362
100444
  sessionsDir;
99363
100445
  terminalRetentionMs;
100446
+ transcriptGraceMs;
99364
100447
  parentEnv;
99365
100448
  paneTimings;
99366
100449
  starting = 0;
@@ -99380,6 +100463,7 @@ class SessionManager {
99380
100463
  this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
99381
100464
  this.scrollbackCapacity = options?.scrollbackCapacity ?? DEFAULT_SCROLLBACK;
99382
100465
  this.terminalRetentionMs = options?.terminalRetentionMs ?? TERMINAL_RETENTION_MS;
100466
+ this.transcriptGraceMs = options?.transcriptGraceMs ?? TRANSCRIPT_GRACE_MS;
99383
100467
  this.sessionsDir = options?.sessionsDir ?? sessionsDirFrom(process.env);
99384
100468
  this.parentEnv = options?.parentEnv ?? process.env;
99385
100469
  this.paneTimings = options?.paneTimings;
@@ -99404,7 +100488,7 @@ class SessionManager {
99404
100488
  let dir = "";
99405
100489
  for (let attempt = 0;; attempt++) {
99406
100490
  id = `team-${randomUUID9().replace(/-/g, "").slice(0, 8)}`;
99407
- dir = join54(this.sessionsDir, id);
100491
+ dir = join55(this.sessionsDir, id);
99408
100492
  try {
99409
100493
  mkdirSync25(dir);
99410
100494
  break;
@@ -99436,9 +100520,9 @@ class SessionManager {
99436
100520
  if (dir === null)
99437
100521
  throw new Error("not a record id");
99438
100522
  const completedAt = new Date().toISOString();
99439
- const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join54(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
100523
+ const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join55(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
99440
100524
  const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
99441
- writeJsonAtomic(join54(dir, "meta.json"), {
100525
+ writeJsonAtomic(join55(dir, "meta.json"), {
99442
100526
  kind: "team",
99443
100527
  status: outcome.status,
99444
100528
  startedAt,
@@ -99470,7 +100554,7 @@ class SessionManager {
99470
100554
  startedAt,
99471
100555
  ...rest
99472
100556
  };
99473
- writeJsonAtomic(join54(dir, "spawn.json"), body);
100557
+ writeJsonAtomic(join55(dir, "spawn.json"), body);
99474
100558
  }
99475
100559
  async createSession(opts) {
99476
100560
  if (this.activeSessions + this.starting >= this.maxSessions) {
@@ -99510,7 +100594,7 @@ class SessionManager {
99510
100594
  throw err;
99511
100595
  }
99512
100596
  if (inherited.layer) {
99513
- flags = [...inherited.restFlags, "--settings", join54(entry.sessionDir, INHERITED_SETTINGS)];
100597
+ flags = [...inherited.restFlags, "--settings", join55(entry.sessionDir, INHERITED_SETTINGS)];
99514
100598
  }
99515
100599
  this.sessions.set(entry.info.sessionId, entry);
99516
100600
  uncount();
@@ -99535,10 +100619,10 @@ class SessionManager {
99535
100619
  const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
99536
100620
  const agentRequested = requestedAgentOf(opts.claudishFlags ?? []);
99537
100621
  const startedAt = new Date().toISOString();
99538
- const sessionDir = opts.sessionDir ?? join54(this.sessionsDir, sessionId);
100622
+ const sessionDir = opts.sessionDir ?? join55(this.sessionsDir, sessionId);
99539
100623
  mkdirSync25(sessionDir, { recursive: true });
99540
100624
  if (opts.prompt)
99541
- writeFileSync23(join54(sessionDir, "prompt.md"), opts.prompt, "utf-8");
100625
+ writeFileSync23(join55(sessionDir, "prompt.md"), opts.prompt, "utf-8");
99542
100626
  this.writeSpawnRecord(sessionDir, {
99543
100627
  kind: "session",
99544
100628
  sessionId,
@@ -99551,13 +100635,13 @@ class SessionManager {
99551
100635
  ...agentRequested ? { agentRequested } : {}
99552
100636
  });
99553
100637
  if (inheritedLayer) {
99554
- writeFileSync23(join54(sessionDir, INHERITED_SETTINGS), `${JSON.stringify(inheritedLayer, null, 2)}
100638
+ writeFileSync23(join55(sessionDir, INHERITED_SETTINGS), `${JSON.stringify(inheritedLayer, null, 2)}
99555
100639
  `, { encoding: "utf-8", mode: 384 });
99556
100640
  }
99557
100641
  const cwd = opts.cwd ?? process.cwd();
99558
100642
  const parentEnv = opts.parentEnv ?? this.parentEnv;
99559
100643
  const spawnModel = opts.spawnModel ?? null;
99560
- const tokenFile = opts.tokenFile ?? join54(sessionDir, "tokens.json");
100644
+ const tokenFile = opts.tokenFile ?? join55(sessionDir, "tokens.json");
99561
100645
  return {
99562
100646
  info: {
99563
100647
  sessionId,
@@ -99595,18 +100679,18 @@ class SessionManager {
99595
100679
  },
99596
100680
  session: null,
99597
100681
  scrollback: new ScrollbackBuffer(this.scrollbackCapacity),
99598
- outputLogStream: createWriteStream(join54(sessionDir, "output.log")),
100682
+ outputLogStream: createWriteStream(join55(sessionDir, "output.log")),
99599
100683
  sessionDir,
99600
100684
  cwd,
99601
100685
  tokenFile,
99602
- eventLogPath: join54(sessionDir, "events.jsonl"),
100686
+ eventLogPath: join55(sessionDir, "events.jsonl"),
99603
100687
  eventLogBytes: 0,
99604
- upstreamErrorLogPath: join54(sessionDir, "upstream-errors.jsonl"),
100688
+ upstreamErrorLogPath: join55(sessionDir, "upstream-errors.jsonl"),
99605
100689
  eventRing: [],
99606
100690
  proseBytes: 0,
99607
100691
  answeredTurns: new Map,
99608
100692
  inboxPath: null,
99609
- waitLogPath: join54(sessionDir, "waits.jsonl"),
100693
+ waitLogPath: join55(sessionDir, "waits.jsonl"),
99610
100694
  waitLogBytes: 0,
99611
100695
  waitSince: null,
99612
100696
  ended: false,
@@ -99614,7 +100698,9 @@ class SessionManager {
99614
100698
  lastLoggedTool: null,
99615
100699
  assistantIdsLogged: 0,
99616
100700
  anomaliesLogged: new Set,
99617
- requestedSize: null
100701
+ requestedSize: null,
100702
+ reapVerified: false,
100703
+ projectsRoot: projectsDir(parentEnv)
99618
100704
  };
99619
100705
  }
99620
100706
  async startPane(entry, opts, p) {
@@ -99669,6 +100755,9 @@ class SessionManager {
99669
100755
  session.resize(asked.cols, asked.rows).catch(() => {
99670
100756
  return;
99671
100757
  });
100758
+ session.reapVerified?.().then(() => {
100759
+ entry.reapVerified = true;
100760
+ });
99672
100761
  session.reaped().then(() => {
99673
100762
  if (!binding && entry.inboxPath)
99674
100763
  reportOwnerWithoutHooks(entry.inboxPath, engine, {
@@ -99796,7 +100885,7 @@ class SessionManager {
99796
100885
  try {
99797
100886
  const text = screenLines.join(`
99798
100887
  `).replace(/\s+$/, "");
99799
- writeFileSync23(join54(entry.sessionDir, "screen.txt"), `${redactSecrets(text)}
100888
+ writeFileSync23(join55(entry.sessionDir, "screen.txt"), `${redactSecrets(text)}
99800
100889
  `, "utf-8");
99801
100890
  } catch {}
99802
100891
  }
@@ -99810,7 +100899,7 @@ class SessionManager {
99810
100899
  entry.waitSince = null;
99811
100900
  }
99812
100901
  try {
99813
- writeJsonAtomic(join54(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
100902
+ writeJsonAtomic(join55(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
99814
100903
  } catch (err) {
99815
100904
  process.stderr.write(`[claudish] session ${entry.info.sessionId}: could not write meta.json: ` + `${err instanceof Error ? err.message : String(err)}
99816
100905
  `);
@@ -100007,6 +101096,15 @@ class SessionManager {
100007
101096
  const r = await entry.session.resize(cols, rows);
100008
101097
  return { session_id: sessionId, ...r };
100009
101098
  }
101099
+ async scrollSession(sessionId, req) {
101100
+ const entry = this.liveEntry(sessionId);
101101
+ if (!entry)
101102
+ throw unknownSession(sessionId);
101103
+ if (!entry.session)
101104
+ throw new ContractErrorException("scroll_unavailable", isTerminalState(entry.info.state) ? "the session has finished" : "the session is still starting");
101105
+ const r = await entry.session.scroll(req);
101106
+ return { session_id: sessionId, seq: r.seq };
101107
+ }
100010
101108
  captureSession(sessionId, sinceSeq, spans) {
100011
101109
  const entry = this.liveEntry(sessionId);
100012
101110
  if (!entry)
@@ -100015,6 +101113,71 @@ class SessionManager {
100015
101113
  return sinceSeq === 0 ? { unchanged: true, seq: 0, final: true } : blankFinalCapture();
100016
101114
  return entry.session.capture(sinceSeq, { spans: spans === true });
100017
101115
  }
101116
+ transcriptSession(sessionId, since) {
101117
+ const address = { session_id: sessionId };
101118
+ const entry = this.sessions.get(sessionId);
101119
+ const src = entry ? this.liveTranscriptSource(entry) : this.diskTranscriptSource(sessionId);
101120
+ if (!src)
101121
+ throw unknownSession(sessionId);
101122
+ return transcriptAnswer(src, since, address);
101123
+ }
101124
+ liveTranscriptSource(entry) {
101125
+ const writerGone = entry.session ? isTerminalState(entry.session.snapshot().state) && entry.reapVerified : isTerminalState(entry.info.state);
101126
+ return {
101127
+ transcriptPath: entry.info.transcriptPath || null,
101128
+ sessionUuid: entry.info.claudeSessionId || null,
101129
+ writerGone,
101130
+ projectsRoot: entry.projectsRoot
101131
+ };
101132
+ }
101133
+ diskTranscriptSource(sessionId) {
101134
+ const sessionDir = this.diskSessionDir(sessionId);
101135
+ if (sessionDir === null)
101136
+ return null;
101137
+ try {
101138
+ if (!statSync20(sessionDir).isDirectory())
101139
+ return null;
101140
+ } catch {
101141
+ return null;
101142
+ }
101143
+ const spawnPath = join55(sessionDir, "spawn.json");
101144
+ const metaPath = join55(sessionDir, "meta.json");
101145
+ const spawnRecord = readJsonObject(spawnPath, META_READ_LIMIT);
101146
+ if (spawnRecord?.kind === "team")
101147
+ return null;
101148
+ const meta = readJsonObject(metaPath, META_READ_LIMIT);
101149
+ if (meta?.kind === "team")
101150
+ return null;
101151
+ const projectsRoot = projectsDir(this.parentEnv);
101152
+ const uuid = metaString(meta?.claudeSessionId) ?? metaString(spawnRecord?.claudeSessionId) ?? null;
101153
+ const now = Date.now();
101154
+ let writerGone;
101155
+ let transcriptPath = null;
101156
+ if (meta !== null) {
101157
+ const completedAt = metaString(meta.completedAt);
101158
+ const endedMs = completedAt ? Date.parse(completedAt) : Number.NaN;
101159
+ const age = Number.isFinite(endedMs) ? now - endedMs : fileAgeMs(metaPath, now);
101160
+ if (uuid) {
101161
+ const stored = metaString(meta.transcriptPath);
101162
+ const cwd = metaString(meta.cwd);
101163
+ if (stored && existsSync38(stored))
101164
+ transcriptPath = stored;
101165
+ else if (cwd)
101166
+ transcriptPath = transcriptPathFor(cwd, uuid, projectsRoot);
101167
+ else
101168
+ transcriptPath = locateTranscriptByUuid(projectsRoot, uuid);
101169
+ }
101170
+ writerGone = rawStateTerminal(meta) && age !== null && age >= this.transcriptGraceMs && transcriptQuiet(transcriptPath, now, this.transcriptGraceMs);
101171
+ } else {
101172
+ const pid = spawnRecord?.mcpPid;
101173
+ const liveness = pid === process.pid ? "dead" : writerLiveness(pid);
101174
+ if (uuid)
101175
+ transcriptPath = locateTranscriptByUuid(projectsRoot, uuid);
101176
+ const quiet = (transcriptPath !== null ? fileAgeMs(transcriptPath, now) : null) ?? fileAgeMs(spawnPath, now);
101177
+ writerGone = liveness === "dead" && quiet !== null && quiet >= this.transcriptGraceMs;
101178
+ }
101179
+ return { transcriptPath, sessionUuid: uuid, writerGone, projectsRoot };
101180
+ }
100018
101181
  listSessions(includeCompleted = false) {
100019
101182
  const sessions = [];
100020
101183
  for (const entry of this.sessions.values()) {
@@ -100072,9 +101235,9 @@ class SessionManager {
100072
101235
  return null;
100073
101236
  const root = resolve7(this.sessionsDir);
100074
101237
  const dir = resolve7(root, sessionId);
100075
- if (dir !== join54(root, sessionId))
101238
+ if (dir !== join55(root, sessionId))
100076
101239
  return null;
100077
- if (!dir.startsWith(root + sep2))
101240
+ if (!dir.startsWith(root + sep3))
100078
101241
  return null;
100079
101242
  return dir;
100080
101243
  }
@@ -100084,17 +101247,17 @@ class SessionManager {
100084
101247
  return null;
100085
101248
  let dirMtimeMs;
100086
101249
  try {
100087
- const stat = statSync19(sessionDir);
101250
+ const stat = statSync20(sessionDir);
100088
101251
  if (!stat.isDirectory())
100089
101252
  return null;
100090
101253
  dirMtimeMs = stat.mtimeMs;
100091
101254
  } catch {
100092
101255
  return null;
100093
101256
  }
100094
- const spawnRecord = readJsonObject(join54(sessionDir, "spawn.json"), META_READ_LIMIT);
101257
+ const spawnRecord = readJsonObject(join55(sessionDir, "spawn.json"), META_READ_LIMIT);
100095
101258
  if (spawnRecord?.kind === "team")
100096
101259
  return null;
100097
- const meta = readJsonObject(join54(sessionDir, "meta.json"), META_READ_LIMIT);
101260
+ const meta = readJsonObject(join55(sessionDir, "meta.json"), META_READ_LIMIT);
100098
101261
  if (meta?.kind === "team")
100099
101262
  return null;
100100
101263
  const partial = meta === null;
@@ -100116,7 +101279,7 @@ class SessionManager {
100116
101279
  return stored;
100117
101280
  }
100118
101281
  diskOutput(record, tailLines) {
100119
- const tail = readTailText(join54(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
101282
+ const tail = readTailText(join55(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
100120
101283
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
100121
101284
  if (tail?.text)
100122
101285
  buffer.append(dropLeadingFragment(tail));
@@ -100136,12 +101299,12 @@ class SessionManager {
100136
101299
  }
100137
101300
  diskDiagnostics(record, limit) {
100138
101301
  const { sessionDir, info } = record;
100139
- const eventLogPath = join54(sessionDir, "events.jsonl");
100140
- const upstreamErrorLogPath = join54(sessionDir, "upstream-errors.jsonl");
100141
- const outputLogPath = join54(sessionDir, "output.log");
101302
+ const eventLogPath = join55(sessionDir, "events.jsonl");
101303
+ const upstreamErrorLogPath = join55(sessionDir, "upstream-errors.jsonl");
101304
+ const outputLogPath = join55(sessionDir, "output.log");
100142
101305
  const events = readTailLines2(eventLogPath, EVENT_TAIL_BYTES);
100143
101306
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
100144
- const screen = readTailText(join54(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
101307
+ const screen = readTailText(join55(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
100145
101308
  return {
100146
101309
  sessionId: info.sessionId,
100147
101310
  state: info.state,
@@ -100337,6 +101500,13 @@ function diskState(meta) {
100337
101500
  return { state, reason: "timeout" };
100338
101501
  return { state, reason: null };
100339
101502
  }
101503
+ function rawStateTerminal(meta) {
101504
+ const raw = meta.state;
101505
+ if (typeof raw === "string" && SLOT_STATES.includes(raw))
101506
+ return isTerminalState(raw);
101507
+ const status = typeof meta.status === "string" ? STATE_OF_10_4_STATUS[meta.status] : undefined;
101508
+ return status !== undefined && isTerminalState(status);
101509
+ }
100340
101510
  function diskInfo(d) {
100341
101511
  const { meta, spawnRecord, measured } = d;
100342
101512
  const startedAt = metaString(meta?.startedAt) ?? metaString(spawnRecord?.startedAt) ?? new Date(d.dirMtimeMs).toISOString();
@@ -100442,7 +101612,7 @@ var NESTED_MCP_TOOL_NAMES, NESTED_INSTRUCTIONS = `Claudish MCP server provides a
100442
101612
 
100443
101613
  ## Nested mode \u2014 no sessions
100444
101614
 
100445
- This server runs inside a session that a claudish MCP server started (CLAUDISH_PANE_CHILD=1). One level of nesting is the limit, so these tools are not registered: create_session, team, send_input, get_output, cancel_session, list_sessions, get_diagnostics, capture_session, preflight, report_error. No channel notifications are sent.
101615
+ This server runs inside a session that a claudish MCP server started (CLAUDISH_PANE_CHILD=1). One level of nesting is the limit, so these tools are not registered: create_session, team, send_input, get_output, cancel_session, list_sessions, get_diagnostics, capture_session, resize_session, scroll_session, transcript_session, preflight, report_error. No channel notifications are sent.
100446
101616
 
100447
101617
  - run_prompt or compare_models: a one-shot answer from another model.
100448
101618
  - list_models, search_models: find a model.
@@ -100540,7 +101710,7 @@ var init_progress_heartbeat = __esm(() => {
100540
101710
  import { spawn as spawn2 } from "child_process";
100541
101711
  import { mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, rmSync as rmSync14, writeFileSync as writeFileSync24 } from "fs";
100542
101712
  import { tmpdir as tmpdir2 } from "os";
100543
- import { join as join55 } from "path";
101713
+ import { join as join56 } from "path";
100544
101714
  function claudeCodePrintArgv(model, systemPromptFile) {
100545
101715
  const head = [PRINT_FLAGS[0], "--model", model, ...PRINT_FLAGS.slice(1)];
100546
101716
  return systemPromptFile !== undefined ? [...head, "--system-prompt-file", systemPromptFile] : [...head, "--system-prompt", ""];
@@ -100658,13 +101828,13 @@ async function runPromptViaClaudeCode(model, prompt, systemPrompt, opts = {}) {
100658
101828
  } catch (e) {
100659
101829
  throw new Error(`cannot start claudish (${parentEnv.CLAUDISH_BIN?.trim() || "claudish"}): ${e instanceof Error ? e.message : String(e)}`);
100660
101830
  }
100661
- const root = mkdtempSync3(join55(tmpdir2(), "claudish-run-prompt-"));
101831
+ const root = mkdtempSync3(join56(tmpdir2(), "claudish-run-prompt-"));
100662
101832
  try {
100663
- const cwd = join55(root, "cwd");
101833
+ const cwd = join56(root, "cwd");
100664
101834
  mkdirSync26(cwd, { mode: 448 });
100665
101835
  let systemPromptFile;
100666
101836
  if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
100667
- systemPromptFile = join55(root, "system-prompt.txt");
101837
+ systemPromptFile = join56(root, "system-prompt.txt");
100668
101838
  writeFileSync24(systemPromptFile, systemPrompt, { mode: 384 });
100669
101839
  }
100670
101840
  const argv = claudeCodePrintArgv(model, systemPromptFile);
@@ -100964,9 +102134,9 @@ var init_preflight_chain = __esm(() => {
100964
102134
  });
100965
102135
 
100966
102136
  // src/mcp-server.ts
100967
- import { existsSync as existsSync39, readFileSync as readFileSync45, readdirSync as readdirSync10 } from "fs";
102137
+ import { existsSync as existsSync39, readFileSync as readFileSync45, readdirSync as readdirSync11 } from "fs";
100968
102138
  import { homedir as homedir37 } from "os";
100969
- import { dirname as dirname21, join as join56, resolve as resolve8 } from "path";
102139
+ import { dirname as dirname22, join as join57, resolve as resolve8 } from "path";
100970
102140
  import { fileURLToPath as fileURLToPath3 } from "url";
100971
102141
  async function loadAllModels(forceRefresh = false) {
100972
102142
  if (!forceRefresh && existsSync39(OPENROUTER_MODELS_CACHE_PATH)) {
@@ -101539,6 +102709,36 @@ function requiredInteger(raw, name, mode) {
101539
102709
  throw new ContractErrorException("invalid_args", `'${name}' is required${mode ? ` for mode '${mode}'` : ""}`);
101540
102710
  return n;
101541
102711
  }
102712
+ function scrollArgs(args) {
102713
+ const by = optionalInteger(args.by, "by");
102714
+ const to = args.to;
102715
+ if (to !== undefined && to !== null && to !== "end")
102716
+ throw new ContractErrorException("invalid_args", `'to' must be "end"`);
102717
+ const hasTo = to === "end";
102718
+ if (by !== undefined && hasTo)
102719
+ throw new ContractErrorException("invalid_args", "give exactly one of 'by' and 'to', not both");
102720
+ if (hasTo)
102721
+ return { to: "end" };
102722
+ if (by === undefined)
102723
+ throw new ContractErrorException("invalid_args", "one of 'by' and 'to' is required");
102724
+ if (by === 0)
102725
+ throw new ContractErrorException("invalid_args", "'by' must not be 0");
102726
+ return { by };
102727
+ }
102728
+ function transcriptSince(raw) {
102729
+ const since = givenString(raw, "since");
102730
+ return since === undefined ? undefined : decodeCursor(since);
102731
+ }
102732
+ function givenString(raw, name) {
102733
+ if (raw === undefined)
102734
+ return;
102735
+ if (typeof raw !== "string" || !raw)
102736
+ throw new ContractErrorException("invalid_args", `'${name}' must be a non-empty string`);
102737
+ return raw;
102738
+ }
102739
+ function grouped(n) {
102740
+ return n.toLocaleString("en-US");
102741
+ }
101542
102742
  function teamStatusAnswer(path, runId) {
101543
102743
  let row = null;
101544
102744
  if (runId) {
@@ -101577,12 +102777,14 @@ async function teamContractVerb(mode, args) {
101577
102777
  if (mode === "list")
101578
102778
  return contractAnswer(listTeamRuns());
101579
102779
  const path = contractPath(args.path, mode);
101580
- const runId = optionalString(args.run_id, "run_id");
102780
+ const runId = mode === "transcript" ? givenString(args.run_id, "run_id") : optionalString(args.run_id, "run_id");
101581
102781
  const slot = optionalString(args.slot, "slot");
101582
- const since = optionalInteger(args.since_seq, "since_seq");
102782
+ const sinceSeq = optionalInteger(args.since_seq, "since_seq");
101583
102783
  const spans = optionalBoolean(args.spans, "spans");
101584
102784
  const cols = mode === "resize" ? requiredInteger(args.cols, "cols", mode) : undefined;
101585
102785
  const rows = mode === "resize" ? requiredInteger(args.rows, "rows", mode) : undefined;
102786
+ const scroll = mode === "scroll" ? scrollArgs(args) : undefined;
102787
+ const since = mode === "transcript" ? transcriptSince(args.since) : undefined;
101586
102788
  switch (mode) {
101587
102789
  case "status":
101588
102790
  return contractAnswer(teamStatusAnswer(path, runId));
@@ -101591,13 +102793,23 @@ async function teamContractVerb(mode, args) {
101591
102793
  case "capture": {
101592
102794
  if (slot === undefined)
101593
102795
  throw new ContractErrorException("invalid_args", "'slot' is required for mode 'capture'");
101594
- return contractAnswer(captureTeamSlot(path, slot, since, spans, runId));
102796
+ return contractAnswer(captureTeamSlot(path, slot, sinceSeq, spans, runId));
101595
102797
  }
101596
102798
  case "resize": {
101597
102799
  if (slot === undefined)
101598
102800
  throw new ContractErrorException("invalid_args", "'slot' is required for mode 'resize'");
101599
102801
  return contractAnswer(await resizeTeamSlot(path, slot, cols, rows, runId));
101600
102802
  }
102803
+ case "scroll": {
102804
+ if (slot === undefined)
102805
+ throw new ContractErrorException("invalid_args", "'slot' is required for mode 'scroll'");
102806
+ return contractAnswer(await scrollTeamSlot(path, slot, scroll, runId));
102807
+ }
102808
+ case "transcript": {
102809
+ if (slot === undefined)
102810
+ throw new ContractErrorException("invalid_args", "'slot' is required for mode 'transcript'");
102811
+ return compactAnswer(transcriptTeamSlot(path, slot, since, runId));
102812
+ }
101601
102813
  }
101602
102814
  } catch (e) {
101603
102815
  return contractErrorAnswer(e);
@@ -101945,18 +103157,29 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
101945
103157
  });
101946
103158
  tools.push({
101947
103159
  name: "team",
101948
- 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.",
103160
+ 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), 'scroll' (scroll one slot's transcript view), 'transcript' (one slot's conversation as text lines), '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.",
101949
103161
  inputSchema: {
101950
103162
  type: "object",
101951
103163
  properties: {
101952
103164
  mode: {
101953
103165
  type: "string",
101954
- enum: ["run", "judge", "run-and-judge", "status", "cancel", "list", "capture", "resize"],
101955
- 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."
103166
+ enum: [
103167
+ "run",
103168
+ "judge",
103169
+ "run-and-judge",
103170
+ "status",
103171
+ "cancel",
103172
+ "list",
103173
+ "capture",
103174
+ "resize",
103175
+ "scroll",
103176
+ "transcript"
103177
+ ],
103178
+ 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; 'scroll' scrolls its transcript view (`by` or `to`). 'transcript' reads one slot's conversation as text lines (`since` pages; a `cont` chain may continue in the next answer after `cursor`, in order, never reordered or lost). 'run-and-judge' is the blocking pipeline and holds the call open for the whole run. 'cancel' stops one slot or the whole run."
101956
103179
  },
101957
103180
  slot: {
101958
103181
  type: "string",
101959
- 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."
103182
+ description: "For 'cancel', 'capture', 'resize', 'scroll' and 'transcript': the anonymised slot id (e.g. '02') from the slot map 'run' returned. 'capture', 'resize', 'scroll' and 'transcript' require it; for 'cancel', omit it to stop every slot in the run."
101960
103183
  },
101961
103184
  path: {
101962
103185
  type: "string",
@@ -101964,11 +103187,15 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
101964
103187
  },
101965
103188
  run_id: {
101966
103189
  type: "string",
101967
- 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'."
103190
+ description: "For 'status', 'cancel', 'capture', 'resize', 'scroll' and 'transcript': 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'."
101968
103191
  },
101969
103192
  since_seq: {
101970
103193
  type: "number",
101971
- description: "For 'capture': the `seq` of the screen you already hold. When it is still current the answer is `{unchanged:true}`; any other value returns the full screen."
103194
+ description: "For 'capture' only: the `seq` of the screen you already hold. When it is still current the answer is `{unchanged:true}`; any other value returns the full screen."
103195
+ },
103196
+ since: {
103197
+ type: "string",
103198
+ description: `For 'transcript' only (capture uses since_seq): the \`cursor\` of a previous answer; returns only the lines after it. Omit to read from the start. Answer {run_id, path, slot, cursor, lines, more, final}, at most ${MAX_TRANSCRIPT_LINES} lines and ${grouped(TRANSCRIPT_ANSWER_MAX_BYTES)} bytes per call; a \`cont\` chain may continue in the next answer after \`cursor\`, in order, never reordered or lost.`
101972
103199
  },
101973
103200
  spans: {
101974
103201
  type: "boolean",
@@ -101982,6 +103209,15 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
101982
103209
  type: "integer",
101983
103210
  description: "For 'resize' (required): the terminal height in rows, clamped to 24\u20139999 and to " + "at most 4 000 000 cells (cols \xD7 rows)."
101984
103211
  },
103212
+ by: {
103213
+ type: "integer",
103214
+ description: "For 'scroll': rows to scroll the slot's transcript view; negative = toward older " + `content, positive = toward the live bottom. A target, \xB1 1 row; |by| is capped at ${MAX_SCROLL_ROWS}. Not 0. Give exactly one of 'by' and 'to'. The answer {run_id, path, slot, seq} carries the screen's seq after the scroll.`
103215
+ },
103216
+ to: {
103217
+ type: "string",
103218
+ enum: ["end"],
103219
+ description: `For 'scroll': "end" returns the slot's view to the live bottom. Give exactly one of 'by' and 'to'.`
103220
+ },
101985
103221
  models: {
101986
103222
  type: "array",
101987
103223
  items: { type: "string" },
@@ -102038,7 +103274,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
102038
103274
  heartbeat: true,
102039
103275
  handler: async (args, ctx) => {
102040
103276
  const mode = args.mode;
102041
- if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture" || mode === "resize") {
103277
+ if (mode === "list" || mode === "status" || mode === "cancel" || mode === "capture" || mode === "resize" || mode === "scroll" || mode === "transcript") {
102042
103278
  return teamContractVerb(mode, args);
102043
103279
  }
102044
103280
  try {
@@ -102092,7 +103328,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
102092
103328
  const promptText = () => {
102093
103329
  if (input !== undefined)
102094
103330
  return input;
102095
- const existing = join56(resolved, "input.md");
103331
+ const existing = join57(resolved, "input.md");
102096
103332
  return existsSync39(existing) ? readFileSync45(existing, "utf-8") : undefined;
102097
103333
  };
102098
103334
  const choice = modelChoiceStop([
@@ -102275,26 +103511,26 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
102275
103511
  const sp = session_path;
102276
103512
  for (const file of ["status.json", "manifest.json", "input.md"]) {
102277
103513
  try {
102278
- sessionData[file] = readFileSync45(join56(sp, file), "utf-8");
103514
+ sessionData[file] = readFileSync45(join57(sp, file), "utf-8");
102279
103515
  } catch {}
102280
103516
  }
102281
103517
  try {
102282
- const errorDir = join56(sp, "errors");
103518
+ const errorDir = join57(sp, "errors");
102283
103519
  if (existsSync39(errorDir)) {
102284
- for (const f of readdirSync10(errorDir)) {
103520
+ for (const f of readdirSync11(errorDir)) {
102285
103521
  if (f.endsWith(".log")) {
102286
103522
  try {
102287
- sessionData[`errors/${f}`] = readFileSync45(join56(errorDir, f), "utf-8");
103523
+ sessionData[`errors/${f}`] = readFileSync45(join57(errorDir, f), "utf-8");
102288
103524
  } catch {}
102289
103525
  }
102290
103526
  }
102291
103527
  }
102292
103528
  } catch {}
102293
103529
  try {
102294
- for (const f of readdirSync10(sp)) {
103530
+ for (const f of readdirSync11(sp)) {
102295
103531
  if (f.startsWith("response-") && f.endsWith(".md")) {
102296
103532
  try {
102297
- const content = readFileSync45(join56(sp, f), "utf-8");
103533
+ const content = readFileSync45(join57(sp, f), "utf-8");
102298
103534
  sessionData[f] = content.slice(0, 200) + (content.length > 200 ? "... (truncated)" : "");
102299
103535
  } catch {}
102300
103536
  }
@@ -102303,7 +103539,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
102303
103539
  }
102304
103540
  let version = "unknown";
102305
103541
  try {
102306
- const pkgPath = join56(__dirname3, "../package.json");
103542
+ const pkgPath = join57(__dirname3, "../package.json");
102307
103543
  if (existsSync39(pkgPath)) {
102308
103544
  version = JSON.parse(readFileSync45(pkgPath, "utf-8")).version;
102309
103545
  }
@@ -102739,6 +103975,65 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
102739
103975
  }
102740
103976
  }
102741
103977
  });
103978
+ tools.push({
103979
+ name: "scroll_session",
103980
+ description: "Scroll a session's transcript view, as a mouse wheel over the child's Claude Code would: the next capture_session shows the child scrolled. `by` is a signed row count (negative = toward older content, positive = toward the live bottom); it is a " + `target, \xB1 1 row, and |by| is capped at ${MAX_SCROLL_ROWS} per call. \`to:"end"\` returns to the live bottom. Give exactly one of \`by\` and \`to\`; \`by:0\` is refused. It never types into the prompt, submits or answers a dialog, and a prompt claudish delivers next returns the view to the bottom first. The answer {session_id, seq} carries the screen's seq after the scroll (the first changed screen within 1 s, else the current one). Errors are a JSON ContractError: \`scroll_unavailable\` while the session is still starting, finished, delivering a prompt, reading its turn from the screen or reconnecting, or when magmux did not confirm the send (the scroll may still land). Nothing is queued.`,
103981
+ inputSchema: {
103982
+ type: "object",
103983
+ properties: {
103984
+ session_id: { type: "string", description: "Session ID from create_session" },
103985
+ by: {
103986
+ type: "integer",
103987
+ description: `Rows to scroll; negative = older content. Not 0; |by| capped at ${MAX_SCROLL_ROWS}; a target, \xB1 1 row`
103988
+ },
103989
+ to: {
103990
+ type: "string",
103991
+ enum: ["end"],
103992
+ description: '"end" returns the view to the live bottom'
103993
+ }
103994
+ },
103995
+ required: ["session_id"]
103996
+ },
103997
+ group: "session",
103998
+ handler: async (args) => {
103999
+ try {
104000
+ const sessionId = optionalString(args.session_id, "session_id");
104001
+ if (sessionId === undefined)
104002
+ throw new ContractErrorException("invalid_args", "'session_id' is required");
104003
+ const req = scrollArgs(args);
104004
+ return contractAnswer(await sessionManager.scrollSession(sessionId, req));
104005
+ } catch (e) {
104006
+ return contractErrorAnswer(e);
104007
+ }
104008
+ }
104009
+ });
104010
+ tools.push({
104011
+ name: "transcript_session",
104012
+ description: `Read a session's conversation as plain text lines, from its Claude Code transcript: prompts (\`user\`), replies (\`assistant\`), one line per tool call (\`tool\`, e.g. \`Bash(git status)\`), the content of a prompt delivered as a file (\`task_file\`), notices (\`system\`) and errors (\`error\`). Works while the session runs and after it finished. Page with \`since\`: pass the previous answer's \`cursor\` to get only newer lines; the cursor can move even when no line is returned. At most ${MAX_TRANSCRIPT_LINES} lines (and ${grouped(TRANSCRIPT_ANSWER_MAX_BYTES)} bytes) per call; \`more: true\` means call again now. A text over ${grouped(TRANSCRIPT_LINE_MAX_CHARS)} characters is split into lines marked \`cont: true\`; a \`cont\` chain may continue in the next answer after \`cursor\`, in order, never reordered or lost. \`final: true\` means the session finished, its process exited and every line has been returned; until then keep polling. Read only: it never sends anything to the session. The answer is {session_id, cursor, lines:[{kind, text, cont?}], more, final}. Errors are a JSON ContractError: \`unknown_session\`, \`invalid_args\` (a malformed cursor, or one from another session).`,
104013
+ inputSchema: {
104014
+ type: "object",
104015
+ properties: {
104016
+ session_id: { type: "string", description: "Session ID from create_session" },
104017
+ since: {
104018
+ type: "string",
104019
+ description: "The `cursor` of a previous answer; returns only the lines after it. Omit to read from the start. Opaque; valid for this session only."
104020
+ }
104021
+ },
104022
+ required: ["session_id"]
104023
+ },
104024
+ group: "session",
104025
+ handler: async (args) => {
104026
+ try {
104027
+ const sessionId = optionalString(args.session_id, "session_id");
104028
+ if (sessionId === undefined)
104029
+ throw new ContractErrorException("invalid_args", "'session_id' is required");
104030
+ const since = transcriptSince(args.since);
104031
+ return compactAnswer(sessionManager.transcriptSession(sessionId, since));
104032
+ } catch (e) {
104033
+ return contractErrorAnswer(e);
104034
+ }
104035
+ }
104036
+ });
102742
104037
  return tools;
102743
104038
  }
102744
104039
  function resolveToolGroups(mode) {
@@ -102882,7 +104177,7 @@ Each session is an interactive Claude Code running the model in its own headless
102882
104177
  3. send_input may be called at any time; it is queued until the session is idle.
102883
104178
  4. COMPLETED: call get_output for the answer.
102884
104179
  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.
102885
- 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.
104180
+ 6. capture_session reads a session's current screen; resize_session sets its terminal size (any time, mid-turn included); scroll_session scrolls the child's transcript view (by rows, or back to the live bottom); transcript_session reads the conversation as text lines (also after the session finished); cancel_session stops it. Nothing stops a session for being idle: a child inside a long build is silent and working.
102886
104181
 
102887
104182
  The session_id from create_session is the key for every session tool.`, proxyInstance = null, proxyStarting = null, SCORE_TIE_EPSILON = 0.000001, NEXT_STEP, KEEP_EFFORT = "default", USER_REQUIRED_MODEL_DESCRIPTION, sanitize2, TOOL_MODES;
102888
104183
  var init_mcp_server = __esm(() => {
@@ -102925,9 +104220,9 @@ var init_mcp_server = __esm(() => {
102925
104220
  init_team_orchestrator();
102926
104221
  init_preflight_chain();
102927
104222
  __filename3 = fileURLToPath3(import.meta.url);
102928
- __dirname3 = dirname21(__filename3);
102929
- CLAUDISH_CACHE_DIR = join56(homedir37(), ".claudish");
102930
- OPENROUTER_MODELS_CACHE_PATH = join56(CLAUDISH_CACHE_DIR, "openrouter-models.json");
104223
+ __dirname3 = dirname22(__filename3);
104224
+ CLAUDISH_CACHE_DIR = join57(homedir37(), ".claudish");
104225
+ OPENROUTER_MODELS_CACHE_PATH = join57(CLAUDISH_CACHE_DIR, "openrouter-models.json");
102931
104226
  NEXT_STEP = {
102932
104227
  cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
102933
104228
  timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
@@ -103056,9 +104351,9 @@ var init_serve_command = __esm(() => {
103056
104351
  var HOOKS_AMBIENT_DTS = '// claudish hook types \u2014 written by `claudish hooks init`. Do not edit: run\n// `claudish hooks init` from a newer claudish to refresh it.\n// With this file beside your hook, `import type { HookDefinition } from\n// "claudish/hooks"` type-checks with nothing installed.\ndeclare module "claudish/hooks" {\n /**\n * claudish/hooks \u2014 the public hook contract.\n *\n * Everything a hook author can import lives here, and nothing else does. This\n * file imports nothing from the rest of claudish and has no side effects, so it\n * can be built on its own into `dist/hooks.js` + `dist/hooks.d.ts` and served to\n * user modules as the `claudish/hooks` virtual module (see `loader.ts`).\n *\n * Every internal hooks module takes its types from here, so the contract has a\n * single source.\n *\n * ## Process isolation\n *\n * A hook module\'s top-level state is PER PROCESS. In the interactive launcher,\n * proxy events (`request` \u2026 `responseEnd`, raw) and session events\n * (`sessionStart` \u2026 `sessionEnd`) run in one process. In `create_session` and\n * team children they do not: proxy events run in the pane child, and session\n * events run in the MCP server or the CLI process that drives the pane. Pass\n * per-turn data from a proxy event to `sessionIdle` with `annotateTurn`, never\n * with module variables.\n *\n * ## Execution: a hook chain\n *\n * The hooks of one event run one after another, in effective order. Each hook\'s\n * actions are applied before the next hook is called, so `ctx.request`,\n * `ctx.tool.args` and `ctx.response` show what the earlier hooks did, and\n * `ctx.prior` lists it. A warn-severity or failing hook changes nothing for the\n * next one. Each event has a budget for all its hooks together; a hook\'s timeout\n * is cut to what is left of it.\n *\n * ## Output\n *\n * Log with `ctx.log`. `console.*` and `process.stdout`/`stderr` writes made by a\n * hook are routed to claudish\'s debug log, never to the terminal or the MCP\n * protocol stream.\n */\n export const HOOK_API_VERSION = 1;\n export type Severity = "off" | "warn" | "fix";\n export type HookOrder = "pre" | "normal" | "post";\n /** native = RouteInfo.native true; foreign = RouteInfo.native false; all = both */\n export type RouteScope = "foreign" | "native" | "all";\n export type HostMode = "interactive" | "pane-child" | "serve" | "mcp" | "other";\n export type JsonValue = string | number | boolean | null | ReadonlyArray<JsonValue> | {\n readonly [k: string]: JsonValue;\n };\n export interface HarnessFacts {\n readonly planModeActive: boolean;\n readonly planFilePath?: string;\n readonly planDir?: string;\n /** Claude Code session id from request metadata */\n readonly sessionId?: string;\n /** empty = no listing found, not "no skills" */\n readonly skills?: ReadonlyArray<{\n name: string;\n description: string;\n }>;\n }\n export interface RouteInfo {\n /** bare model name the handler serves, e.g. "glm-5" */\n readonly modelId: string;\n /** claudish provider name, e.g. "openrouter"; "anthropic" for native */\n readonly provider: string;\n /**\n * true when the request is served by the native Anthropic handler, OR when\n * !forceForeignModel && (/^claude[-.]/i.test(modelId) || provider === "anthropic")\n * where forceForeignModel is the provider profile\'s opt-in override. This is the exact\n * rule the plan-mode built-in has always used (a claude-* uid on an aggregator that sets\n * forceForeignModel is foreign).\n */\n readonly native: boolean;\n }\n export interface ClaudeTool {\n name: string;\n description?: string;\n input_schema?: unknown;\n }\n /** A Claude-format content block. Only `text` blocks are editable by closed actions. */\n export type ClaudeContentBlock = {\n type: "text";\n text: string;\n } | {\n type: "tool_use";\n id: string;\n name: string;\n input: unknown;\n } | {\n type: "tool_result";\n tool_use_id: string;\n content?: unknown;\n is_error?: boolean;\n } | {\n type: "thinking";\n thinking: string;\n signature?: string;\n } | {\n type: string;\n [k: string]: unknown;\n };\n export interface ClaudeMessage {\n role: "user" | "assistant";\n content: string | ReadonlyArray<ClaudeContentBlock>;\n }\n /**\n * In `request`, the request as the earlier steps of the hook chain left it. On\n * the response side and in `claims()`, the request chain\'s final view.\n */\n export interface RequestFacts {\n /** as Claude Code asked for it; frozen */\n readonly model: string;\n /** frozen */\n readonly stream: boolean;\n /** system prompt flattened to text, after earlier steps */\n readonly systemText: string;\n /** read-only view after earlier steps; change it through actions */\n readonly messages: ReadonlyArray<ClaudeMessage>;\n /**\n * read-only view after earlier steps; `description` includes earlier description actions as\n * stage B will apply them to the Claude-format tool (the provider copy may be summarised)\n */\n readonly tools: ReadonlyArray<ClaudeTool>;\n }\n /** Which session a proxy event belongs to, when claudish knows. */\n export interface ProxySessionRef {\n readonly kind: SessionKind;\n readonly id: string;\n readonly role: "slot" | "judge" | null;\n }\n /** One action an earlier step of this hook chain applied or handed on. */\n export interface PriorAction {\n readonly hookId: string;\n readonly action: HookAction;\n }\n interface BaseContext {\n readonly hookId: string;\n /** "warn" or "fix" (an "off" hook is never called) */\n readonly severity: Severity;\n /** writes to claudish\'s debug log; never to the terminal */\n log(message: string): void;\n /**\n * this step\'s own signal: aborted when its step timeout elapses (its own timeout, or the\n * event budget left, whichever is shorter) or when the dispatch is aborted. Never shared\n * with another hook\'s step.\n */\n readonly signal: AbortSignal;\n /**\n * actions earlier steps of this hook chain applied (or, for responseEnd/sessionStart/\n * sessionIdle, handed on), in order. Empty for the first step and in claims()/the toolCall\n * predicate. Never lists warn actions or anything a warn-severity hook returned.\n */\n readonly prior: ReadonlyArray<PriorAction>;\n }\n export interface ProxyContext extends BaseContext {\n readonly host: {\n mode: HostMode;\n };\n readonly route: RouteInfo;\n readonly request: RequestFacts;\n readonly harness: HarnessFacts;\n readonly session: ProxySessionRef | null;\n }\n export interface RequestContext extends ProxyContext {\n readonly event: "request";\n }\n export interface ToolCallContext extends ProxyContext {\n readonly event: "toolCall";\n readonly tool: {\n id: string;\n name: string;\n /** the arguments after every repair earlier hooks in the chain applied */\n args: Readonly<Record<string, unknown>>;\n /** the argument JSON exactly as the model produced it; never rewritten */\n rawArgs: string;\n };\n }\n export type ResponseBlock = {\n type: "text";\n text: string;\n } | {\n type: "thinking";\n thinking: string;\n } | {\n type: "tool_use";\n id: string;\n name: string;\n input: unknown;\n } | {\n type: "other";\n blockType: string;\n };\n export interface ResponseView {\n blocks: ReadonlyArray<ResponseBlock>;\n /** every text block, joined with "\\n\\n" */\n text: string;\n /** every thinking block, joined with "\\n\\n" */\n reasoning: string;\n toolNames: ReadonlyArray<string>;\n /** "tool_use" = an intermediate step of a tool loop; the turn continues after the tool runs */\n stopReason: string | null;\n }\n /** response = the held response after every text action earlier hooks in the chain applied */\n export interface ResponseRewriteContext extends ProxyContext {\n readonly event: "responseRewrite";\n readonly response: ResponseView;\n }\n /** response = the final bytes the client received: after any rewrite AND any raw `response` replacement */\n export interface ResponseEndContext extends ProxyContext {\n readonly event: "responseEnd";\n readonly response: ResponseView;\n }\n export type SessionKind = "interactive" | "channel" | "team";\n export interface SessionInfo {\n kind: SessionKind;\n /** team only; null otherwise */\n role: "slot" | "judge" | null;\n /** channel: the 8-hex session_id \xB7 team: "<run_id>:<slot id>" \xB7 interactive: the Claude Code session id */\n id: string;\n claudeSessionId: string | null;\n /** as the caller asked for it */\n model: string;\n cwd: string;\n /** a one-shot session stays "one-shot" through a hook-extended turn chain */\n shape: "interactive" | "one-shot";\n injection: "available" | "unavailable";\n }\n export interface SessionStartContext extends BaseContext {\n readonly event: "sessionStart";\n readonly session: SessionInfo;\n }\n export interface TurnAnnotation {\n hookId: string;\n key: string;\n value: JsonValue;\n }\n export interface SessionIdleContext extends BaseContext {\n readonly event: "sessionIdle";\n readonly session: SessionInfo;\n readonly turn: {\n /** 1-based within the session; a re-woken turn keeps its index */\n index: number;\n /** this turn\'s assistant text */\n answer: string;\n stopReason: string | null;\n apiError: boolean;\n /** this turn was started by an injection */\n hookCaused: boolean;\n /** annotateTurn actions from this turn\'s responses, in arrival order */\n annotations: ReadonlyArray<TurnAnnotation>;\n };\n readonly consecutiveHookTurns: number;\n /** caller/human input is queued; an injection returned now will be dropped as stale */\n readonly callerInputPending: boolean;\n }\n export interface SessionBlockedContext extends BaseContext {\n readonly event: "sessionBlocked";\n readonly session: SessionInfo;\n readonly block: {\n kind: "question" | "permission";\n text: string;\n };\n }\n export interface SessionEndContext extends BaseContext {\n readonly event: "sessionEnd";\n readonly session: SessionInfo;\n readonly outcome: {\n state: string;\n reason: string | null;\n exitCode: number | null;\n };\n }\n type TextMatch = {\n find: string;\n regex?: boolean;\n flags?: string;\n };\n /** default ["user"] */\n type MessageRoles = {\n roles?: ReadonlyArray<"user" | "assistant">;\n };\n export type InjectSystemNote = {\n type: "injectSystemNote";\n text: string;\n };\n export type AppendUserText = {\n type: "appendUserText";\n text: string;\n };\n export type ReplaceMessageText = {\n type: "replaceMessageText";\n replace: string;\n } & TextMatch & MessageRoles;\n export type RemoveMessageText = {\n type: "removeMessageText";\n } & TextMatch & MessageRoles;\n export type RewriteToolDescription = {\n type: "rewriteToolDescription";\n tool: string;\n append: string;\n };\n export type ReplaceToolDescription = {\n type: "replaceToolDescription";\n tool: string;\n description: string;\n };\n export type ReplaceToolSchema = {\n type: "replaceToolSchema";\n tool: string;\n inputSchema: {\n type: "object";\n [k: string]: unknown;\n };\n };\n export type RemoveTool = {\n type: "removeTool";\n tool: string;\n };\n export type RepairToolArgs = {\n type: "repairToolArgs";\n args: Record<string, unknown>;\n reason: string;\n };\n export type AppendText = {\n type: "appendText";\n text: string;\n };\n export type PrependText = {\n type: "prependText";\n text: string;\n };\n export type ReplaceText = {\n type: "replaceText";\n replace: string;\n } & TextMatch;\n export type RemoveText = {\n type: "removeText";\n } & TextMatch;\n export type InjectUserTurn = {\n type: "injectUserTurn";\n text: string;\n };\n export type AnnotateTurn = {\n type: "annotateTurn";\n key: string;\n value: JsonValue;\n };\n export type Warn = {\n type: "warn";\n message: string;\n };\n export type RequestAction = InjectSystemNote | AppendUserText | ReplaceMessageText | RemoveMessageText | RewriteToolDescription | ReplaceToolDescription | ReplaceToolSchema | RemoveTool | Warn;\n export type ToolCallAction = RepairToolArgs | Warn;\n export type RewriteAction = AppendText | PrependText | ReplaceText | RemoveText | Warn;\n export type ResponseEndAction = InjectSystemNote | InjectUserTurn | AnnotateTurn | Warn;\n export type SessionAction = InjectUserTurn | Warn;\n export type ObserveAction = Warn;\n export type HookAction = RequestAction | ToolCallAction | RewriteAction | ResponseEndAction | SessionAction;\n export type HookResult<A> = A[] | void | Promise<A[] | void>;\n export interface HookHandlers {\n request?(ctx: RequestContext): HookResult<RequestAction>;\n toolCall?: {\n /** tool names whose completed arguments this hook sees; "*" = every tool (costs streaming of all tool input) */\n tools: ReadonlyArray<string>;\n /** synchronous, per request; default true. false = not armed for this request */\n armed?(ctx: RequestContext): boolean;\n /** true = see the call after it was forwarded; can only warn; never holds the stream */\n observe?: boolean;\n handle(ctx: ToolCallContext): HookResult<ToolCallAction>;\n };\n responseRewrite?: {\n /** synchronous, per request, evaluated before the response starts; default true */\n claims?(ctx: RequestContext): boolean;\n handle(ctx: ResponseRewriteContext): HookResult<RewriteAction>;\n };\n responseEnd?(ctx: ResponseEndContext): HookResult<ResponseEndAction>;\n sessionStart?(ctx: SessionStartContext): HookResult<SessionAction>;\n sessionIdle?(ctx: SessionIdleContext): HookResult<SessionAction>;\n sessionBlocked?(ctx: SessionBlockedContext): HookResult<ObserveAction>;\n sessionEnd?(ctx: SessionEndContext): HookResult<ObserveAction>;\n }\n export interface RawProviderRequestContext {\n readonly hookId: string;\n readonly route: RouteInfo;\n claudeRequest: any;\n messages: any[];\n tools: any[];\n }\n export interface RawProviderChunkContext {\n readonly hookId: string;\n readonly route: RouteInfo;\n /** A clone of the parsed provider event. Whatever this field holds when the call returns is\n * what the parser reads from then on, so it may be edited in place or replaced. */\n chunk: any;\n /** The delta object inside `chunk` at call time (Responses wire: `chunk` itself). Edit it in\n * place; assigning a new object to this field has no effect. */\n delta: any;\n /** per-stream, shared across this hook\'s chunk calls; never cloned */\n metadata: Map<string, unknown>;\n }\n export interface RawProviderStreamEndContext {\n readonly hookId: string;\n readonly route: RouteInfo;\n metadata: Map<string, unknown>;\n }\n export interface RawResponseContext {\n readonly hookId: string;\n readonly route: RouteInfo;\n readonly request: RequestFacts;\n /** A replay of the current Claude-format response (SSE, or JSON for stream:false), read at the\n * hook\'s own pace. claudish drains the original itself, so leaving this unread delays nothing. */\n response: Response;\n }\n export interface RawMiddleware {\n /**\n * Receives clones. They replace the live request only when this call returns (or its promise\n * resolves) within its timeout. A throw, a rejection or a timeout discards every change.\n */\n providerRequest?(ctx: RawProviderRequestContext): void | Promise<void>;\n /**\n * MUST be synchronous. Called on the parser\'s loop, on a clone of the chunk. On a normal return\n * of anything but a thenable, the clone (whatever `ctx.chunk` holds then) is committed: it\n * becomes the object the parser reads, deletions included. On a throw nothing is committed.\n * A declared `async` function is rejected at load. Any other thenable return is an invalid\n * invocation: the clone is discarded even if the hook mutated it before returning, the call is\n * journaled `invalid` (`async_return`) and counts toward the circuit breaker, and a rejection\n * handler is attached, so a rejection is journaled `error` and never becomes an\n * unhandledRejection.\n */\n providerChunk?(ctx: RawProviderChunkContext): void;\n providerStreamEnd?(ctx: RawProviderStreamEndContext): void | Promise<void>;\n /**\n * Return a replacement Response, or nothing to keep the current one. A replacement is read\n * whole and forwarded only when it completes cleanly; on any failure the original is forwarded.\n * The client never sees a partial replacement. A replacement must close within this hook\'s\n * timeout after the later of this call\'s return and the end of the original; the original\'s end\n * never depends on whether `ctx.response` is read.\n */\n response?(ctx: RawResponseContext): Response | void | Promise<Response | void>;\n }\n interface HookBase {\n apiVersion: 1;\n id: string;\n description?: string;\n /** default "fix" for user hooks */\n defaultSeverity?: Severity;\n /** default "normal" */\n order?: HookOrder;\n /** globs on RouteInfo.modelId; default "*"; proxy events only */\n models?: string | ReadonlyArray<string>;\n /** default "all"; proxy events only */\n routes?: RouteScope;\n /** per call; clamped to the event\'s maximum and to the event budget left when the hook\'s turn comes */\n timeoutMs?: number;\n on?: HookHandlers;\n }\n export interface SafeHook extends HookBase {\n unsafe?: never;\n raw?: never;\n }\n export interface UnsafeHook extends HookBase {\n unsafe: true;\n raw: RawMiddleware;\n }\n /**\n * One hook. Module-level state is per process: in `create_session` and team\n * children, proxy events run in the pane child and session events in the MCP\n * server or CLI process. Pass per-turn data from proxy events to `sessionIdle`\n * with `annotateTurn`, never with module variables.\n */\n export type HookDefinition = SafeHook | UnsafeHook;\n /** Identity function: gives editor type-checking and inference. */\n export function defineHook<const H extends HookDefinition>(hook: H): H;\n /** Action constructors (plain data; equivalent to writing the object literal). */\n export const act: {\n injectSystemNote(text: string): InjectSystemNote;\n appendUserText(text: string): AppendUserText;\n replaceMessageText(find: string, replace: string, opts?: {\n regex?: boolean;\n flags?: string;\n roles?: ReadonlyArray<"user" | "assistant">;\n }): ReplaceMessageText;\n removeMessageText(find: string, opts?: {\n regex?: boolean;\n flags?: string;\n roles?: ReadonlyArray<"user" | "assistant">;\n }): RemoveMessageText;\n rewriteToolDescription(tool: string, append: string): RewriteToolDescription;\n replaceToolDescription(tool: string, description: string): ReplaceToolDescription;\n replaceToolSchema(tool: string, inputSchema: {\n type: "object";\n [k: string]: unknown;\n }): ReplaceToolSchema;\n removeTool(tool: string): RemoveTool;\n repairToolArgs(args: Record<string, unknown>, reason: string): RepairToolArgs;\n appendText(text: string): AppendText;\n prependText(text: string): PrependText;\n replaceText(find: string, replace: string, opts?: {\n regex?: boolean;\n flags?: string;\n }): ReplaceText;\n removeText(find: string, opts?: {\n regex?: boolean;\n flags?: string;\n }): RemoveText;\n injectUserTurn(text: string): InjectUserTurn;\n annotateTurn(key: string, value: JsonValue): AnnotateTurn;\n warn(message: string): Warn;\n };\n}\n';
103057
104352
 
103058
104353
  // src/hooks/observer/corpus.ts
103059
- import { appendFileSync as appendFileSync8, readFileSync as readFileSync47, readdirSync as readdirSync11, statSync as statSync20 } from "fs";
104354
+ import { appendFileSync as appendFileSync8, readFileSync as readFileSync47, readdirSync as readdirSync12, statSync as statSync21 } from "fs";
103060
104355
  import { homedir as homedir38 } from "os";
103061
- import { join as join57 } from "path";
104356
+ import { join as join58 } from "path";
103062
104357
  function directoryOf2(filePath) {
103063
104358
  const slash = filePath.lastIndexOf("/");
103064
104359
  return slash > 0 ? filePath.slice(0, slash) : undefined;
@@ -103132,31 +104427,31 @@ function listTranscripts(root) {
103132
104427
  const files = [];
103133
104428
  let projects;
103134
104429
  try {
103135
- projects = readdirSync11(root);
104430
+ projects = readdirSync12(root);
103136
104431
  } catch {
103137
104432
  return files;
103138
104433
  }
103139
104434
  for (const project of projects) {
103140
- const dir = join57(root, project);
104435
+ const dir = join58(root, project);
103141
104436
  try {
103142
- if (!statSync20(dir).isDirectory())
104437
+ if (!statSync21(dir).isDirectory())
103143
104438
  continue;
103144
- for (const f of readdirSync11(dir)) {
104439
+ for (const f of readdirSync12(dir)) {
103145
104440
  if (f.endsWith(".jsonl"))
103146
- files.push(join57(dir, f));
104441
+ files.push(join58(dir, f));
103147
104442
  }
103148
104443
  } catch {}
103149
104444
  }
103150
104445
  return files;
103151
104446
  }
103152
104447
  function buildCorpus(options = {}) {
103153
- const root = options.projectsRoot ?? join57(homedir38(), ".claude", "projects");
104448
+ const root = options.projectsRoot ?? join58(homedir38(), ".claude", "projects");
103154
104449
  const files = listTranscripts(root);
103155
104450
  const records = [];
103156
104451
  for (const f of files)
103157
104452
  records.push(...replayTranscript(f));
103158
104453
  if (options.write && records.length > 0) {
103159
- const outputPath = options.outputPath ?? join57(homedir38(), ".claudish", "behavior-divergences.jsonl");
104454
+ const outputPath = options.outputPath ?? join58(homedir38(), ".claudish", "behavior-divergences.jsonl");
103160
104455
  try {
103161
104456
  appendFileSync8(outputPath, `${records.map((r) => JSON.stringify(r)).join(`
103162
104457
  `)}
@@ -103174,7 +104469,7 @@ var init_corpus = __esm(() => {
103174
104469
  // src/hooks-command.ts
103175
104470
  import { existsSync as existsSync41, mkdirSync as mkdirSync27, readFileSync as readFileSync48, writeFileSync as writeFileSync25 } from "fs";
103176
104471
  import { homedir as homedir39 } from "os";
103177
- import { join as join58, resolve as resolve9 } from "path";
104472
+ import { join as join59, resolve as resolve9 } from "path";
103178
104473
  function severityColor(sev) {
103179
104474
  if (sev === "fix")
103180
104475
  return green(sev);
@@ -103297,10 +104592,10 @@ async function check(file, json) {
103297
104592
  return valid ? 0 : 1;
103298
104593
  }
103299
104594
  function init(dirArg) {
103300
- const dir = dirArg ? resolve9(process.cwd(), dirArg) : join58(homedir39(), ".claudish", "hooks");
104595
+ const dir = dirArg ? resolve9(process.cwd(), dirArg) : join59(homedir39(), ".claudish", "hooks");
103301
104596
  const files = [
103302
- [join58(dir, "example-hook.ts"), EXAMPLE_HOOK],
103303
- [join58(dir, "claudish-hooks.d.ts"), HOOKS_AMBIENT_DTS]
104597
+ [join59(dir, "example-hook.ts"), EXAMPLE_HOOK],
104598
+ [join59(dir, "claudish-hooks.d.ts"), HOOKS_AMBIENT_DTS]
103304
104599
  ];
103305
104600
  const existing = files.filter(([path]) => existsSync41(path)).map(([path]) => path);
103306
104601
  if (existing.length > 0) {
@@ -103503,7 +104798,7 @@ var init_hooks_command = __esm(() => {
103503
104798
  import { spawn as spawn3 } from "child_process";
103504
104799
  import { existsSync as existsSync42, readFileSync as readFileSync49, writeFileSync as writeFileSync26 } from "fs";
103505
104800
  import { connect as netConnect2 } from "net";
103506
- import { join as join59 } from "path";
104801
+ import { join as join60 } from "path";
103507
104802
  import { setTimeout as wait } from "timers/promises";
103508
104803
  function paneRouteLine(explanation) {
103509
104804
  const origin = describeRouteExplanation(explanation);
@@ -103653,9 +104948,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
103653
104948
  const keep = opts?.keep ?? false;
103654
104949
  const manifest = setupSession(sessionPath, models, input);
103655
104950
  const startedAt = new Date().toISOString();
103656
- const gridfilePath = join59(sessionPath, "gridfile.txt");
103657
- const prompt = readFileSync49(join59(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
103658
- const rawPrompt = readFileSync49(join59(sessionPath, "input.md"), "utf-8");
104951
+ const gridfilePath = join60(sessionPath, "gridfile.txt");
104952
+ const prompt = readFileSync49(join60(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
104953
+ const rawPrompt = readFileSync49(join60(sessionPath, "input.md"), "utf-8");
103659
104954
  const usedBannerColors = new Set;
103660
104955
  const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
103661
104956
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
@@ -103687,7 +104982,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
103687
104982
  });
103688
104983
  const [{ results }] = await Promise.all([subscription, procExit]);
103689
104984
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
103690
- const statusPath = join59(sessionPath, "status.json");
104985
+ const statusPath = join60(sessionPath, "status.json");
103691
104986
  writeFileSync26(statusPath, JSON.stringify(status, null, 2), "utf-8");
103692
104987
  return status;
103693
104988
  }
@@ -103708,7 +105003,7 @@ var init_team_grid = __esm(() => {
103708
105003
 
103709
105004
  // src/team-cli.ts
103710
105005
  import { readFileSync as readFileSync50 } from "fs";
103711
- import { join as join60 } from "path";
105006
+ import { join as join61 } from "path";
103712
105007
  async function runPaneTeam(sessionPath, models, input, opts) {
103713
105008
  installPaneShutdownHooks({ exitAfter: true });
103714
105009
  try {
@@ -103853,7 +105148,7 @@ async function teamCommand(args) {
103853
105148
  }
103854
105149
  case "judge": {
103855
105150
  await judgeResponses(sessionPath, { judges });
103856
- console.log(readFileSync50(join60(sessionPath, "verdict.md"), "utf-8"));
105151
+ console.log(readFileSync50(join61(sessionPath, "verdict.md"), "utf-8"));
103857
105152
  break;
103858
105153
  }
103859
105154
  case "run-and-judge": {
@@ -103869,7 +105164,7 @@ async function teamCommand(args) {
103869
105164
  });
103870
105165
  printStatus(status);
103871
105166
  await judgeResponses(sessionPath, { judges });
103872
- console.log(readFileSync50(join60(sessionPath, "verdict.md"), "utf-8"));
105167
+ console.log(readFileSync50(join61(sessionPath, "verdict.md"), "utf-8"));
103873
105168
  break;
103874
105169
  }
103875
105170
  case "status": {
@@ -105797,7 +107092,7 @@ var init_account_command = __esm(() => {
105797
107092
  import { spawnSync as spawnSync2 } from "child_process";
105798
107093
  import { existsSync as existsSync43, unlinkSync as unlinkSync12 } from "fs";
105799
107094
  import { homedir as homedir40 } from "os";
105800
- import { join as join61 } from "path";
107095
+ import { join as join62 } from "path";
105801
107096
  async function defaultSuggestModel() {
105802
107097
  try {
105803
107098
  const token = await getValidAntigravityAccessToken();
@@ -105916,7 +107211,7 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
105916
107211
  async logout(deps) {
105917
107212
  deleteSharedAntigravityToken(deps);
105918
107213
  try {
105919
- const tokenFile = join61(homedir40(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
107214
+ const tokenFile = join62(homedir40(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
105920
107215
  if (existsSync43(tokenFile))
105921
107216
  unlinkSync12(tokenFile);
105922
107217
  } catch {}
@@ -114194,8 +115489,8 @@ var init_own_pane = __esm(() => {
114194
115489
  });
114195
115490
 
114196
115491
  // src/hooks/session/interactive-driver.ts
114197
- import { existsSync as existsSync44, statSync as statSync21 } from "fs";
114198
- import { join as join62 } from "path";
115492
+ import { existsSync as existsSync44, statSync as statSync22 } from "fs";
115493
+ import { join as join63 } from "path";
114199
115494
  function nextDriverPhase(phase, ev) {
114200
115495
  const row = EDGES[phase];
114201
115496
  return row[ev] ?? null;
@@ -114413,7 +115708,7 @@ class InteractiveDriver {
114413
115708
  }
114414
115709
  follow(sid, offset) {
114415
115710
  const path = transcriptPathFor(this.o.cwd, sid, this.o.projectsRoot);
114416
- this.follower = new TranscriptFollower(path, join62(path.replace(/\.jsonl$/, ""), "subagents"));
115711
+ this.follower = new TranscriptFollower(path, join63(path.replace(/\.jsonl$/, ""), "subagents"));
114417
115712
  this.sessionId = sid;
114418
115713
  this.settledTurn = null;
114419
115714
  this.lastPollAt = Date.now();
@@ -114958,7 +116253,7 @@ function assertNeverPhase(p) {
114958
116253
  }
114959
116254
  function fileSizeOf(path) {
114960
116255
  try {
114961
- return statSync21(path).size;
116256
+ return statSync22(path).size;
114962
116257
  } catch {
114963
116258
  return 0;
114964
116259
  }
@@ -115515,20 +116810,20 @@ import { spawn as spawn4, spawnSync as spawnSync4 } from "child_process";
115515
116810
  import { randomUUID as randomUUID11 } from "crypto";
115516
116811
  import {
115517
116812
  chmodSync as chmodSync5,
115518
- closeSync as closeSync14,
116813
+ closeSync as closeSync15,
115519
116814
  existsSync as existsSync46,
115520
116815
  mkdirSync as mkdirSync28,
115521
116816
  mkdtempSync as mkdtempSync4,
115522
- openSync as openSync14,
116817
+ openSync as openSync15,
115523
116818
  readFileSync as readFileSync51,
115524
- readdirSync as readdirSync12,
116819
+ readdirSync as readdirSync13,
115525
116820
  rmSync as rmSync15,
115526
- statSync as statSync22,
116821
+ statSync as statSync23,
115527
116822
  unlinkSync as unlinkSync13,
115528
116823
  writeFileSync as writeFileSync27
115529
116824
  } from "fs";
115530
116825
  import { homedir as homedir42, tmpdir as tmpdir3 } from "os";
115531
- import { dirname as dirname22, join as join63 } from "path";
116826
+ import { dirname as dirname23, join as join64 } from "path";
115532
116827
  import { isatty } from "tty";
115533
116828
  function releaseTerminalIsolation() {
115534
116829
  if (!restoreTerminal)
@@ -115600,7 +116895,7 @@ function hasResolvableAnthropicAuth(deps = {}) {
115600
116895
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
115601
116896
  return true;
115602
116897
  }
115603
- if (fileExists(join63(homedir42(), ".claude", ".credentials.json")))
116898
+ if (fileExists(join64(homedir42(), ".claude", ".credentials.json")))
115604
116899
  return true;
115605
116900
  return keychainProbe();
115606
116901
  }
@@ -115700,10 +116995,10 @@ function readableClaudeSettingsLayers(config, env, deps) {
115700
116995
  const cwd = deps.cwd ?? process.cwd();
115701
116996
  const files = [
115702
116997
  managedSettingsPath(),
115703
- join63(home, ".claude", "settings.json"),
115704
- ...env.CLAUDE_CONFIG_DIR ? [join63(env.CLAUDE_CONFIG_DIR, "settings.json")] : [],
115705
- join63(cwd, ".claude", "settings.json"),
115706
- join63(cwd, ".claude", "settings.local.json")
116998
+ join64(home, ".claude", "settings.json"),
116999
+ ...env.CLAUDE_CONFIG_DIR ? [join64(env.CLAUDE_CONFIG_DIR, "settings.json")] : [],
117000
+ join64(cwd, ".claude", "settings.json"),
117001
+ join64(cwd, ".claude", "settings.local.json")
115707
117002
  ];
115708
117003
  const layers = [];
115709
117004
  for (const file of files) {
@@ -115735,7 +117030,7 @@ function readSettingsSource(source, readFile) {
115735
117030
  }
115736
117031
  function managedSettingsPath() {
115737
117032
  if (isWindows2()) {
115738
- return join63(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
117033
+ return join64(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
115739
117034
  }
115740
117035
  if (process.platform === "darwin") {
115741
117036
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -115756,8 +117051,8 @@ function isWindows2() {
115756
117051
  }
115757
117052
  function createStatusLineScript(tokenFilePath) {
115758
117053
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
115759
- const claudishDir = join63(homeDir, ".claudish");
115760
- const scriptPath = join63(claudishDir, `status-${process.pid}-${Date.now()}.js`);
117054
+ const claudishDir = join64(homeDir, ".claudish");
117055
+ const scriptPath = join64(claudishDir, `status-${process.pid}-${Date.now()}.js`);
115761
117056
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
115762
117057
  const light = getThemeMode() === "light";
115763
117058
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -115917,7 +117212,7 @@ process.stdin.on('end', () => {
115917
117212
  }
115918
117213
  function initializeTokenFile(tokenFilePath) {
115919
117214
  try {
115920
- mkdirSync28(dirname22(tokenFilePath), { recursive: true });
117215
+ mkdirSync28(dirname23(tokenFilePath), { recursive: true });
115921
117216
  writeFileSync27(tokenFilePath, JSON.stringify({
115922
117217
  input_tokens: 0,
115923
117218
  output_tokens: 0,
@@ -115937,7 +117232,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
115937
117232
  let removed = 0;
115938
117233
  let entries;
115939
117234
  try {
115940
- entries = readdirSync12(dir);
117235
+ entries = readdirSync13(dir);
115941
117236
  } catch {
115942
117237
  return 0;
115943
117238
  }
@@ -115949,9 +117244,9 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
115949
117244
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
115950
117245
  continue;
115951
117246
  scanned++;
115952
- const full = join63(dir, name);
117247
+ const full = join64(dir, name);
115953
117248
  try {
115954
- if (statSync22(full).mtimeMs >= cutoff)
117249
+ if (statSync23(full).mtimeMs >= cutoff)
115955
117250
  continue;
115956
117251
  unlinkSync13(full);
115957
117252
  removed++;
@@ -115978,9 +117273,9 @@ function parseSettingsArgSafe(value) {
115978
117273
  }
115979
117274
  function userSettingsFileCandidates(cwd) {
115980
117275
  return [
115981
- join63(homedir42(), ".claude", "settings.json"),
115982
- join63(cwd, ".claude", "settings.json"),
115983
- join63(cwd, ".claude", "settings.local.json")
117276
+ join64(homedir42(), ".claude", "settings.json"),
117277
+ join64(cwd, ".claude", "settings.json"),
117278
+ join64(cwd, ".claude", "settings.local.json")
115984
117279
  ];
115985
117280
  }
115986
117281
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
@@ -116021,11 +117316,11 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
116021
117316
  }
116022
117317
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
116023
117318
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
116024
- const claudishDir = join63(homeDir, ".claudish");
117319
+ const claudishDir = join64(homeDir, ".claudish");
116025
117320
  try {
116026
117321
  mkdirSync28(claudishDir, { recursive: true });
116027
117322
  } catch {}
116028
- const tempPath = join63(claudishDir, `settings-${process.pid}-${Date.now()}.json`);
117323
+ const tempPath = join64(claudishDir, `settings-${process.pid}-${Date.now()}.json`);
116029
117324
  const assignedFile = assignedTokenFile(process.env) !== null;
116030
117325
  const tokenFilePath = resolveTokenFilePath(port);
116031
117326
  cleanupStaleTokenFiles(claudishDir);
@@ -116084,6 +117379,7 @@ function buildClaudishSettingsOverlay(statusLine, proxyAuthMode, paneChild = isP
116084
117379
  if (paneChild) {
116085
117380
  settings.skipDangerousModePermissionPrompt = true;
116086
117381
  settings.editorMode = "normal";
117382
+ settings.wheelScrollAccelerationEnabled = false;
116087
117383
  }
116088
117384
  return settings;
116089
117385
  }
@@ -116108,6 +117404,9 @@ function mergeUserSettingsIfPresent(config, tempSettingsPath, statusLine, proxyA
116108
117404
  if (paneChild) {
116109
117405
  userSettings.editorMode = "normal";
116110
117406
  }
117407
+ if (paneChild) {
117408
+ userSettings.wheelScrollAccelerationEnabled = false;
117409
+ }
116111
117410
  chmodSync5(tempSettingsPath, 384);
116112
117411
  writeFileSync27(tempSettingsPath, JSON.stringify(userSettings, null, 2), {
116113
117412
  encoding: "utf-8",
@@ -116412,7 +117711,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
116412
117711
  console.error(`
116413
117712
  Or set CLAUDE_PATH to your custom installation:`);
116414
117713
  const home = homedir42();
116415
- const localPath = isWindows2() ? join63(home, ".claude", "local", "claude.exe") : join63(home, ".claude", "local", "claude");
117714
+ const localPath = isWindows2() ? join64(home, ".claude", "local", "claude.exe") : join64(home, ".claude", "local", "claude");
116416
117715
  console.error(` export CLAUDE_PATH=${localPath}`);
116417
117716
  process.exit(1);
116418
117717
  }
@@ -116423,11 +117722,11 @@ Or set CLAUDE_PATH to your custom installation:`);
116423
117722
  const childWantsTty = config.interactive && !process.stdout.isTTY && Boolean(process.stdin.isTTY);
116424
117723
  if (childWantsTty) {
116425
117724
  try {
116426
- const fd = openSync14("/dev/fd/0", "r+");
117725
+ const fd = openSync15("/dev/fd/0", "r+");
116427
117726
  if (isatty(fd)) {
116428
117727
  ttyFd = fd;
116429
117728
  } else {
116430
- closeSync14(fd);
117729
+ closeSync15(fd);
116431
117730
  }
116432
117731
  } catch {
116433
117732
  ttyFd = undefined;
@@ -116447,7 +117746,7 @@ Or set CLAUDE_PATH to your custom installation:`);
116447
117746
  let driverLaunch = null;
116448
117747
  if (surfaces.driver) {
116449
117748
  const minted = sessionFlags.sessionId || sessionFlags.resumes ? null : randomUUID11();
116450
- const turnDir = mkdtempSync4(join63(tmpdir3(), "claudish-turns-"));
117749
+ const turnDir = mkdtempSync4(join64(tmpdir3(), "claudish-turns-"));
116451
117750
  chmodSync5(turnDir, 448);
116452
117751
  claudeArgs.unshift(...driverLeadingArgs({ mintedSessionId: minted, turnDir }));
116453
117752
  const sink = new InProcessSink;
@@ -116530,7 +117829,7 @@ Or set CLAUDE_PATH to your custom installation:`);
116530
117829
  const fdToClose = ttyFd;
116531
117830
  proc.on("spawn", () => {
116532
117831
  try {
116533
- closeSync14(fdToClose);
117832
+ closeSync15(fdToClose);
116534
117833
  } catch {}
116535
117834
  });
116536
117835
  }
@@ -116596,15 +117895,15 @@ async function findClaudeBinary() {
116596
117895
  }
116597
117896
  }
116598
117897
  const home = homedir42();
116599
- const localPath = isWindows ? join63(home, ".claude", "local", "claude.exe") : join63(home, ".claude", "local", "claude");
117898
+ const localPath = isWindows ? join64(home, ".claude", "local", "claude.exe") : join64(home, ".claude", "local", "claude");
116600
117899
  if (existsSync46(localPath)) {
116601
117900
  return localPath;
116602
117901
  }
116603
117902
  if (isWindows) {
116604
117903
  const windowsPaths = [
116605
- join63(home, "AppData", "Roaming", "npm", "claude.cmd"),
116606
- join63(home, ".npm-global", "claude.cmd"),
116607
- join63(home, "node_modules", ".bin", "claude.cmd")
117904
+ join64(home, "AppData", "Roaming", "npm", "claude.cmd"),
117905
+ join64(home, ".npm-global", "claude.cmd"),
117906
+ join64(home, "node_modules", ".bin", "claude.cmd")
116608
117907
  ];
116609
117908
  for (const path of windowsPaths) {
116610
117909
  if (existsSync46(path)) {
@@ -116615,11 +117914,11 @@ async function findClaudeBinary() {
116615
117914
  const commonPaths = [
116616
117915
  "/usr/local/bin/claude",
116617
117916
  "/opt/homebrew/bin/claude",
116618
- join63(home, ".npm-global/bin/claude"),
116619
- join63(home, ".local/bin/claude"),
116620
- join63(home, "node_modules/.bin/claude"),
117917
+ join64(home, ".npm-global/bin/claude"),
117918
+ join64(home, ".local/bin/claude"),
117919
+ join64(home, "node_modules/.bin/claude"),
116621
117920
  "/data/data/com.termux/files/usr/bin/claude",
116622
- join63(home, "../usr/bin/claude")
117921
+ join64(home, "../usr/bin/claude")
116623
117922
  ];
116624
117923
  for (const path of commonPaths) {
116625
117924
  if (existsSync46(path)) {
@@ -116728,16 +118027,16 @@ var init_claude_runner = __esm(() => {
116728
118027
  // src/diag-output.ts
116729
118028
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync29, unlinkSync as unlinkSync14, writeFileSync as writeFileSync28 } from "fs";
116730
118029
  import { homedir as homedir43 } from "os";
116731
- import { join as join64 } from "path";
118030
+ import { join as join65 } from "path";
116732
118031
  function getClaudishDir() {
116733
- const dir = join64(homedir43(), ".claudish");
118032
+ const dir = join65(homedir43(), ".claudish");
116734
118033
  try {
116735
118034
  mkdirSync29(dir, { recursive: true });
116736
118035
  } catch {}
116737
118036
  return dir;
116738
118037
  }
116739
118038
  function getDiagLogPath() {
116740
- return join64(getClaudishDir(), `diag-${process.pid}.log`);
118039
+ return join65(getClaudishDir(), `diag-${process.pid}.log`);
116741
118040
  }
116742
118041
 
116743
118042
  class LogFileDiagOutput {
@@ -117302,7 +118601,7 @@ var init_advisor_startup = __esm(() => {
117302
118601
  });
117303
118602
 
117304
118603
  // src/session/conversation.ts
117305
- import { closeSync as closeSync15, openSync as openSync15, readSync as readSync7, statSync as statSync23 } from "fs";
118604
+ import { closeSync as closeSync16, openSync as openSync16, readSync as readSync8, statSync as statSync24 } from "fs";
117306
118605
  import { StringDecoder } from "string_decoder";
117307
118606
  function looksLikeTurn(line) {
117308
118607
  const assistant = line.includes('"type":"assistant"');
@@ -117357,8 +118656,8 @@ function readConversation(file, opts = {}) {
117357
118656
  };
117358
118657
  let fd = null;
117359
118658
  try {
117360
- const size = statSync23(file).size;
117361
- fd = openSync15(file, "r");
118659
+ const size = statSync24(file).size;
118660
+ fd = openSync16(file, "r");
117362
118661
  const buf = Buffer.allocUnsafe(chunkBytes);
117363
118662
  const decoder = new StringDecoder("utf-8");
117364
118663
  let pending = "";
@@ -117390,7 +118689,7 @@ function readConversation(file, opts = {}) {
117390
118689
  take({ role: raw.role, text, elided });
117391
118690
  };
117392
118691
  while (pos < size) {
117393
- const n = readSync7(fd, buf, 0, Math.min(chunkBytes, size - pos), pos);
118692
+ const n = readSync8(fd, buf, 0, Math.min(chunkBytes, size - pos), pos);
117394
118693
  if (n <= 0)
117395
118694
  break;
117396
118695
  pos += n;
@@ -117414,7 +118713,7 @@ function readConversation(file, opts = {}) {
117414
118713
  } catch {} finally {
117415
118714
  if (fd !== null) {
117416
118715
  try {
117417
- closeSync15(fd);
118716
+ closeSync16(fd);
117418
118717
  } catch {}
117419
118718
  }
117420
118719
  }
@@ -119399,7 +120698,7 @@ init_op_source();
119399
120698
  init_startup_trace();
119400
120699
  init_picker_cancelled();
119401
120700
  import { existsSync as existsSync47, readFileSync as readFileSync53 } from "fs";
119402
- import { join as join65, resolve as resolve11 } from "path";
120701
+ import { join as join66, resolve as resolve11 } from "path";
119403
120702
 
119404
120703
  // src/tui/runtime/should-open-picker.ts
119405
120704
  function noModelConfigured(gate) {
@@ -119416,7 +120715,7 @@ function requiresExplicitModel(gate, tty) {
119416
120715
  var autoloadDropped = [...droppedAtLaunch, ...takeLauncherDropped(process.env)];
119417
120716
  var dotenvText;
119418
120717
  try {
119419
- dotenvText = readFileSync53(join65(process.cwd(), ".env"));
120718
+ dotenvText = readFileSync53(join66(process.cwd(), ".env"));
119420
120719
  } catch {}
119421
120720
  if (dotenvText !== undefined) {
119422
120721
  await Promise.resolve().then(() => init_dotenv_allowlist());
@@ -119764,7 +121063,7 @@ async function runCli() {
119764
121063
  process.exit(1);
119765
121064
  }
119766
121065
  const mode = cliConfig.teamMode ?? "default";
119767
- const sessionPath = join65(process.cwd(), `.claudish-team-${Date.now()}`);
121066
+ const sessionPath = join66(process.cwd(), `.claudish-team-${Date.now()}`);
119768
121067
  if (mode === "json") {
119769
121068
  await Promise.resolve().then(() => init_pane());
119770
121069
  await Promise.resolve().then(() => init_team_orchestrator());
@@ -119784,7 +121083,7 @@ async function runCli() {
119784
121083
  const status = await runModels(sessionPath);
119785
121084
  const result = { ...status, responses: {} };
119786
121085
  for (const anonId of Object.keys(status.models)) {
119787
- const responsePath = join65(sessionPath, `response-${anonId}.md`);
121086
+ const responsePath = join66(sessionPath, `response-${anonId}.md`);
119788
121087
  try {
119789
121088
  const raw = readFileSync53(responsePath, "utf-8").trim();
119790
121089
  try {