laohuang 0.8.0 → 0.8.2

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 (3) hide show
  1. package/dist/bin.js +1145 -415
  2. package/dist/bin.js.map +4 -4
  3. package/package.json +3 -1
package/dist/bin.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
5
- import { dirname as dirname4, join as join6 } from "node:path";
4
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
5
+ import { dirname as dirname4, join as join7 } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  // ../../packages/core/agent-runtime/dist/agent.js
@@ -62,8 +62,8 @@ var CancelToken = class {
62
62
  continue;
63
63
  }
64
64
  }
65
- for (const resolve2 of this.#waiters) {
66
- resolve2();
65
+ for (const resolve3 of this.#waiters) {
66
+ resolve3();
67
67
  }
68
68
  this.#waiters.clear();
69
69
  return true;
@@ -76,20 +76,20 @@ var CancelToken = class {
76
76
  if (this.#cancelled) {
77
77
  return Promise.resolve(true);
78
78
  }
79
- return new Promise((resolve2) => {
79
+ return new Promise((resolve3) => {
80
80
  let timer;
81
81
  const onCancel = () => {
82
82
  if (timer !== void 0) {
83
83
  clearTimeout(timer);
84
84
  }
85
85
  this.#waiters.delete(onCancel);
86
- resolve2(true);
86
+ resolve3(true);
87
87
  };
88
88
  this.#waiters.add(onCancel);
89
89
  if (timeoutMs !== void 0) {
90
90
  timer = setTimeout(() => {
91
91
  this.#waiters.delete(onCancel);
92
- resolve2(false);
92
+ resolve3(false);
93
93
  }, timeoutMs);
94
94
  }
95
95
  });
@@ -149,6 +149,7 @@ var EventKind = {
149
149
  AgentRepeatWarning: "agent.repeat_warning",
150
150
  ToolStarted: "tool.started",
151
151
  ToolOutputDelta: "tool.output_delta",
152
+ ToolOutputSnapshot: "tool.output_snapshot",
152
153
  ToolFinished: "tool.finished",
153
154
  SessionReady: "session.ready",
154
155
  SessionStopped: "session.stopped",
@@ -222,7 +223,7 @@ var EventSpec = class {
222
223
  this.payload_types = options.payload_types ?? {};
223
224
  this.require_task_id = options.require_task_id ?? false;
224
225
  this.require_correlation_id = options.require_correlation_id ?? false;
225
- this.max_payload_chars = options.max_payload_chars ?? DEFAULT_MAX_PAYLOAD_CHARS;
226
+ this.max_payload_chars = options.max_payload_chars === void 0 ? DEFAULT_MAX_PAYLOAD_CHARS : options.max_payload_chars;
226
227
  this.validator = options.validator ?? null;
227
228
  }
228
229
  validate(event) {
@@ -254,7 +255,7 @@ var EventSpec = class {
254
255
  } catch (error) {
255
256
  throw new EventValidationError(`event ${event.kind} payload must be JSON-compatible`, { cause: error });
256
257
  }
257
- if (payloadSize > this.max_payload_chars) {
258
+ if (this.max_payload_chars !== null && payloadSize > this.max_payload_chars) {
258
259
  throw new EventValidationError(`event ${event.kind} payload exceeds ${this.max_payload_chars} characters`);
259
260
  }
260
261
  if (this.validator !== null && this.validator(event) === false) {
@@ -414,7 +415,16 @@ var EVENT_SPECS = new Map([
414
415
  payload_types: { stream: "string", text: "string" },
415
416
  require_task_id: true,
416
417
  require_correlation_id: true,
417
- max_payload_chars: 16384
418
+ max_payload_chars: null
419
+ }),
420
+ spec(EventKind.ToolOutputSnapshot, {
421
+ sources: [EventSource.Tool],
422
+ required_payload: ["stream", "text"],
423
+ payload_types: { stream: "string", text: "string", stream_sequence: "integer" },
424
+ require_task_id: true,
425
+ require_correlation_id: true,
426
+ // Producers own output budgets; JSON escaping must not reject valid previews.
427
+ max_payload_chars: null
418
428
  }),
419
429
  spec(EventKind.ToolFinished, {
420
430
  sources: [EventSource.Tool],
@@ -422,7 +432,7 @@ var EVENT_SPECS = new Map([
422
432
  payload_types: { status: "string" },
423
433
  require_task_id: true,
424
434
  require_correlation_id: true,
425
- max_payload_chars: 1e5
435
+ max_payload_chars: null
426
436
  }),
427
437
  spec(EventKind.RoutingDecided, {
428
438
  sources: [EventSource.Router],
@@ -528,6 +538,12 @@ function projectValue(value) {
528
538
  }
529
539
  return value;
530
540
  }
541
+ function omitPartialOutputLine(value) {
542
+ if (value === "")
543
+ return value;
544
+ const newline = value.indexOf("\n");
545
+ return `[partial line omitted]${newline >= 0 ? value.slice(newline) : ""}`;
546
+ }
531
547
  var EventProjector = class _EventProjector {
532
548
  static AUDIENCES = /* @__PURE__ */ new Set([
533
549
  "terminal",
@@ -538,6 +554,16 @@ var EventProjector = class _EventProjector {
538
554
  if (!_EventProjector.AUDIENCES.has(audience)) {
539
555
  throw new Error(`unknown event audience: ${audience}`);
540
556
  }
557
+ const payload = { ...event.payload };
558
+ if (event.kind === EventKind.ToolOutputSnapshot && payload["start_mid_line"] === true && typeof payload["text"] === "string") {
559
+ payload["text"] = omitPartialOutputLine(payload["text"]);
560
+ } else if (event.kind === EventKind.ToolFinished) {
561
+ for (const stream of ["stdout", "stderr"]) {
562
+ if (payload[`${stream}_start_mid_line`] === true && typeof payload[stream] === "string") {
563
+ payload[stream] = omitPartialOutputLine(payload[stream]);
564
+ }
565
+ }
566
+ }
541
567
  return {
542
568
  event_id: event.event_id,
543
569
  kind: event.kind,
@@ -546,7 +572,7 @@ var EventProjector = class _EventProjector {
546
572
  task_id: event.task_id,
547
573
  correlation_id: event.correlation_id,
548
574
  sequence: event.sequence,
549
- payload: projectValue(event.payload)
575
+ payload: projectValue(payload)
550
576
  };
551
577
  }
552
578
  };
@@ -554,7 +580,8 @@ var COALESCIBLE_EVENTS = /* @__PURE__ */ new Set([
554
580
  EventKind.ModelTextDelta,
555
581
  EventKind.ModelReasoningDelta,
556
582
  EventKind.ModelToolCallDelta,
557
- EventKind.ToolOutputDelta
583
+ EventKind.ToolOutputDelta,
584
+ EventKind.ToolOutputSnapshot
558
585
  ]);
559
586
  var MAX_COALESCED_TEXT_CHARS = 65536;
560
587
  var MAILBOX_CRITICAL_HEADROOM = 64;
@@ -582,7 +609,7 @@ var SubscriberMailbox = class _SubscriberMailbox {
582
609
  this.workerDone = this.run();
583
610
  }
584
611
  static merge(left, right) {
585
- if (left.kind !== right.kind || left.task_id !== right.task_id || left.correlation_id !== right.correlation_id || !COALESCIBLE_EVENTS.has(left.kind)) {
612
+ if (left.kind !== right.kind || left.task_id !== right.task_id || left.correlation_id !== right.correlation_id || left.kind === EventKind.ToolOutputSnapshot || !COALESCIBLE_EVENTS.has(left.kind)) {
586
613
  return null;
587
614
  }
588
615
  const leftText = left.payload["text"];
@@ -606,15 +633,23 @@ var SubscriberMailbox = class _SubscriberMailbox {
606
633
  if (this.closed) {
607
634
  return false;
608
635
  }
636
+ if (event.kind === EventKind.ToolOutputSnapshot) {
637
+ const previous = this.items.findIndex((item) => item.kind === event.kind && item.task_id === event.task_id && item.correlation_id === event.correlation_id && item.payload.stream === event.payload.stream);
638
+ if (previous >= 0) {
639
+ const old = this.items.splice(previous, 1)[0];
640
+ this.unfinished--;
641
+ this.pendingGap += gapOf(old.payload);
642
+ }
643
+ }
609
644
  if (this.items.length > 0) {
610
645
  const last = this.items[this.items.length - 1];
611
646
  const merged = _SubscriberMailbox.merge(last, event);
612
- if (merged !== null && this.items.length >= this.maxItems - MAILBOX_CRITICAL_HEADROOM) {
647
+ if (merged !== null && event.kind !== EventKind.ToolOutputSnapshot && this.items.length >= this.maxItems - MAILBOX_CRITICAL_HEADROOM) {
613
648
  this.items[this.items.length - 1] = this.withGapMarker(merged);
614
649
  return true;
615
650
  }
616
651
  }
617
- if (COALESCIBLE_EVENTS.has(event.kind) && this.items.length >= this.maxItems - MAILBOX_CRITICAL_HEADROOM) {
652
+ if (COALESCIBLE_EVENTS.has(event.kind) && event.kind !== EventKind.ToolOutputSnapshot && this.items.length >= this.maxItems - MAILBOX_CRITICAL_HEADROOM) {
618
653
  this.recordDrop();
619
654
  return false;
620
655
  }
@@ -650,8 +685,8 @@ var SubscriberMailbox = class _SubscriberMailbox {
650
685
  const deadline = timeoutMs === void 0 ? void 0 : Date.now() + timeoutMs;
651
686
  while (this.unfinished > 0) {
652
687
  if (deadline === void 0) {
653
- await new Promise((resolve2) => {
654
- this.flushWaiters.push(resolve2);
688
+ await new Promise((resolve3) => {
689
+ this.flushWaiters.push(resolve3);
655
690
  });
656
691
  continue;
657
692
  }
@@ -659,12 +694,12 @@ var SubscriberMailbox = class _SubscriberMailbox {
659
694
  if (remaining <= 0) {
660
695
  return false;
661
696
  }
662
- const waiter = new Promise((resolve2) => {
663
- this.flushWaiters.push(resolve2);
697
+ const waiter = new Promise((resolve3) => {
698
+ this.flushWaiters.push(resolve3);
664
699
  });
665
700
  let timer;
666
- const timeout = new Promise((resolve2) => {
667
- timer = setTimeout(resolve2, remaining);
701
+ const timeout = new Promise((resolve3) => {
702
+ timer = setTimeout(resolve3, remaining);
668
703
  });
669
704
  await Promise.race([waiter, timeout]);
670
705
  clearTimeout(timer);
@@ -693,22 +728,22 @@ var SubscriberMailbox = class _SubscriberMailbox {
693
728
  wake() {
694
729
  const waiters = this.wakeWaiters;
695
730
  this.wakeWaiters = [];
696
- for (const resolve2 of waiters) {
697
- resolve2();
731
+ for (const resolve3 of waiters) {
732
+ resolve3();
698
733
  }
699
734
  }
700
735
  notifyFlushWaiters() {
701
736
  const waiters = this.flushWaiters;
702
737
  this.flushWaiters = [];
703
- for (const resolve2 of waiters) {
704
- resolve2();
738
+ for (const resolve3 of waiters) {
739
+ resolve3();
705
740
  }
706
741
  }
707
742
  async run() {
708
743
  for (; ; ) {
709
744
  while (this.items.length === 0 && !this.closed) {
710
- await new Promise((resolve2) => {
711
- this.wakeWaiters.push(resolve2);
745
+ await new Promise((resolve3) => {
746
+ this.wakeWaiters.push(resolve3);
712
747
  });
713
748
  }
714
749
  if (this.items.length === 0 && this.closed) {
@@ -780,6 +815,11 @@ var EventBus = class {
780
815
  }
781
816
  this.sequence += 1;
782
817
  const published = { ...event, sequence: this.sequence };
818
+ if (event.kind === EventKind.ToolOutputSnapshot) {
819
+ const previous = this.buffer.findIndex((item) => item.kind === event.kind && item.task_id === event.task_id && item.correlation_id === event.correlation_id && item.payload.stream === event.payload.stream);
820
+ if (previous >= 0)
821
+ this.buffer.splice(previous, 1);
822
+ }
783
823
  if (this.buffer.length >= this.maxBufferedEvents) {
784
824
  this.buffer.shift();
785
825
  }
@@ -851,8 +891,8 @@ var EventBus = class {
851
891
  if (this.closed) {
852
892
  return Promise.reject(new EventBusClosedError());
853
893
  }
854
- return new Promise((resolve2, reject) => {
855
- const waiter = { resolve: resolve2, reject };
894
+ return new Promise((resolve3, reject) => {
895
+ const waiter = { resolve: resolve3, reject };
856
896
  if (timeoutMs !== void 0) {
857
897
  waiter.timer = setTimeout(() => {
858
898
  const index = this.bufferWaiters.indexOf(waiter);
@@ -1190,7 +1230,7 @@ async function defaultSleep(delayMs, cancelToken) {
1190
1230
  throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
1191
1231
  }
1192
1232
  if (cancelToken === null) {
1193
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
1233
+ await new Promise((resolve3) => setTimeout(resolve3, delayMs));
1194
1234
  }
1195
1235
  }
1196
1236
  function errorMessage(error) {
@@ -1229,20 +1269,20 @@ ${body}`;
1229
1269
  ].join("\n");
1230
1270
  }
1231
1271
  function runtimeFactLines(options) {
1232
- const lines = [];
1272
+ const lines2 = [];
1233
1273
  if (hasText(options.cliName)) {
1234
- lines.push(`- CLI: ${options.cliName}`);
1274
+ lines2.push(`- CLI: ${options.cliName}`);
1235
1275
  }
1236
1276
  if (hasText(options.cliVersion)) {
1237
- lines.push(`- CLI version: ${options.cliVersion}`);
1277
+ lines2.push(`- CLI version: ${options.cliVersion}`);
1238
1278
  }
1239
1279
  if (hasText(options.provider) && hasText(options.model)) {
1240
- lines.push(`- Provider/model: ${options.provider}/${options.model}`);
1280
+ lines2.push(`- Provider/model: ${options.provider}/${options.model}`);
1241
1281
  }
1242
1282
  if (hasText(options.promptCwd)) {
1243
- lines.push(`- Current working directory: ${options.promptCwd}`);
1283
+ lines2.push(`- Current working directory: ${options.promptCwd}`);
1244
1284
  }
1245
- return lines;
1285
+ return lines2;
1246
1286
  }
1247
1287
  function hasText(value) {
1248
1288
  return value !== null && value !== void 0 && value.length > 0;
@@ -1701,14 +1741,6 @@ function optionalPositiveInteger(args, key) {
1701
1741
  }
1702
1742
  return value;
1703
1743
  }
1704
- function truncateText(text, maxOutputChars) {
1705
- if (text.length <= maxOutputChars) {
1706
- return text;
1707
- }
1708
- const omitted = text.length - maxOutputChars;
1709
- return `${text.slice(0, maxOutputChars)}
1710
- ...[truncated ${omitted} chars]`;
1711
- }
1712
1744
  function errorMessage3(error) {
1713
1745
  return error instanceof Error ? error.message : String(error);
1714
1746
  }
@@ -2480,12 +2512,11 @@ function makeToolContext(context, toolCallId, cancelToken) {
2480
2512
  cancellationReason: cancelToken?.reason || "cancelled",
2481
2513
  publish: (kind, payload) => {
2482
2514
  if (typeof context?.publish === "function") {
2483
- context.publish(kind, {
2515
+ return context.publish(kind, {
2484
2516
  source: EventSource.Tool,
2485
2517
  correlation_id: toolCallId,
2486
2518
  payload
2487
2519
  });
2488
- return;
2489
2520
  }
2490
2521
  const bus = context?.eventBus ?? null;
2491
2522
  if (bus !== null) {
@@ -2534,14 +2565,14 @@ var ConversationHistory = class _ConversationHistory {
2534
2565
  }
2535
2566
  static fromReplay(replay, journal) {
2536
2567
  const history = new _ConversationHistory(replay.items.filter((item) => item.kind === "entry"), journal);
2537
- for (const open of replay.openToolCalls) {
2568
+ for (const open2 of replay.openToolCalls) {
2538
2569
  const entry = history.appendToolResults({
2539
2570
  requestId: "recovered",
2540
2571
  recovered: true,
2541
2572
  messages: [{
2542
2573
  role: "tool-result",
2543
- toolCallId: open.toolCallId,
2544
- toolName: open.toolName,
2574
+ toolCallId: open2.toolCallId,
2575
+ toolName: open2.toolName,
2545
2576
  content: JSON.stringify({
2546
2577
  ok: false,
2547
2578
  status: "interrupted",
@@ -2554,7 +2585,7 @@ var ConversationHistory = class _ConversationHistory {
2554
2585
  recordType: "error",
2555
2586
  payload: {
2556
2587
  type: "interrupted_tool_call",
2557
- toolCallId: open.toolCallId,
2588
+ toolCallId: open2.toolCallId,
2558
2589
  repairedEntryId: entry?.id ?? null
2559
2590
  }
2560
2591
  });
@@ -2901,26 +2932,26 @@ function selectCompactionPlan(input) {
2901
2932
  };
2902
2933
  }
2903
2934
  function serializeConversation(entries) {
2904
- const lines = ["<conversation>"];
2935
+ const lines2 = ["<conversation>"];
2905
2936
  for (const entry of entries) {
2906
- lines.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
2937
+ lines2.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
2907
2938
  if (entry.entryType === "assistant_message") {
2908
2939
  for (const block of entry.payload.message.content) {
2909
2940
  if (block.type === "tool-call") {
2910
- lines.push(`[assistant tool-call ${block.call.id} ${block.call.name}] ${JSON.stringify(block.call.arguments)}`);
2941
+ lines2.push(`[assistant tool-call ${block.call.id} ${block.call.name}] ${JSON.stringify(block.call.arguments)}`);
2911
2942
  } else {
2912
- lines.push(`[assistant ${block.type}] ${block.text}`);
2943
+ lines2.push(`[assistant ${block.type}] ${block.text}`);
2913
2944
  }
2914
2945
  }
2915
2946
  } else if (entry.entryType === "tool_result") {
2916
- lines.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
2947
+ lines2.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
2917
2948
  } else if (entry.entryType === "user_message" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
2918
- lines.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
2949
+ lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
2919
2950
  }
2920
- lines.push("</entry>");
2951
+ lines2.push("</entry>");
2921
2952
  }
2922
- lines.push("</conversation>");
2923
- return lines.join("\n");
2953
+ lines2.push("</conversation>");
2954
+ return lines2.join("\n");
2924
2955
  }
2925
2956
  function semanticUnits(entries) {
2926
2957
  const units = [];
@@ -3673,8 +3704,8 @@ var SessionManager = class {
3673
3704
  const visit = (directory) => {
3674
3705
  for (const entry of readdirSync(directory)) {
3675
3706
  const absolute = join2(directory, entry);
3676
- const stat = statSync2(absolute);
3677
- if (stat.isDirectory()) {
3707
+ const stat2 = statSync2(absolute);
3708
+ if (stat2.isDirectory()) {
3678
3709
  visit(absolute);
3679
3710
  } else if (absolute.endsWith(".jsonl")) {
3680
3711
  paths.push(absolute);
@@ -4679,31 +4710,106 @@ function errorMessage6(error) {
4679
4710
 
4680
4711
  // ../../packages/storage/local-config/dist/config.js
4681
4712
  import { randomUUID as randomUUID6 } from "node:crypto";
4682
- import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
4683
- import { homedir } from "node:os";
4713
+ import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
4684
4714
  import { basename as basename2, dirname, join as join3 } from "node:path";
4715
+
4716
+ // ../../packages/fs/local-paths/dist/index.js
4717
+ import { execFileSync } from "node:child_process";
4718
+ import { existsSync as existsSync4 } from "node:fs";
4719
+ import { homedir } from "node:os";
4720
+ import { posix, win32 } from "node:path";
4721
+ function envValue(env, name) {
4722
+ const key = Object.keys(env).find((key2) => key2.toLowerCase() === name.toLowerCase());
4723
+ return key === void 0 ? void 0 : env[key];
4724
+ }
4725
+ function convertVirtualPath(input, options) {
4726
+ if (options.convertShellPath)
4727
+ return options.convertShellPath(input);
4728
+ const candidates = [];
4729
+ const env = options.env ?? process.env;
4730
+ if (options.shellPath) {
4731
+ const directory = win32.dirname(options.shellPath());
4732
+ candidates.push(win32.join(directory, "cygpath.exe"), win32.resolve(directory, "../usr/bin/cygpath.exe"));
4733
+ } else {
4734
+ for (const name of ["ProgramFiles", "ProgramFiles(x86)"]) {
4735
+ const root = envValue(env, name);
4736
+ if (root && win32.isAbsolute(root))
4737
+ candidates.push(win32.join(root, "Git", "usr", "bin", "cygpath.exe"));
4738
+ }
4739
+ for (const entry of (envValue(env, "PATH") ?? "").split(";")) {
4740
+ const directory = entry.replace(/^"(.*)"$/, "$1");
4741
+ if (win32.isAbsolute(directory)) {
4742
+ candidates.push(win32.join(directory, "cygpath.exe"), win32.resolve(directory, "../usr/bin/cygpath.exe"));
4743
+ }
4744
+ }
4745
+ }
4746
+ const executable = candidates.find((candidate) => existsSync4(candidate));
4747
+ if (!executable)
4748
+ throw new Error(`Cannot map Git Bash path '${input}': cygpath.exe was not found. Use a Windows absolute path.`);
4749
+ return execFileSync(executable, ["-w", "--", input], {
4750
+ encoding: "utf8",
4751
+ windowsHide: true,
4752
+ timeout: 5e3
4753
+ }).replace(/\r?\n$/, "");
4754
+ }
4755
+ function getHomeDirectory(options = {}) {
4756
+ const windows = (options.platform ?? process.platform) === "win32";
4757
+ const env = options.env ?? process.env;
4758
+ const home = windows ? envValue(env, "USERPROFILE") || envValue(env, "HOME") || homedir() : env.HOME || homedir();
4759
+ return normalizeLocalPath(home, options, false);
4760
+ }
4761
+ function normalizeLocalPath(input, options = {}, expandHome = true) {
4762
+ if (input.includes("\0"))
4763
+ throw new Error("Paths cannot contain a NUL character");
4764
+ const windows = (options.platform ?? process.platform) === "win32";
4765
+ const paths = windows ? win32 : posix;
4766
+ if (expandHome && (input === "~" || input.startsWith("~/") || windows && input.startsWith("~\\"))) {
4767
+ return paths.join(getHomeDirectory(options), input.slice(2));
4768
+ }
4769
+ if (!windows)
4770
+ return input;
4771
+ if (input.startsWith("/") && !input.startsWith("//") && !input.includes("\\")) {
4772
+ const drive = /^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i.exec(input);
4773
+ if (drive)
4774
+ return `${drive[1].toUpperCase()}:\\${(drive[2] ?? "").replaceAll("/", "\\")}`;
4775
+ const converted = convertVirtualPath(input, options);
4776
+ if (!/^(?:[a-z]:[\\/]|\\\\)/i.test(converted)) {
4777
+ throw new Error(`Git Bash path did not resolve to a Windows absolute path: ${input}`);
4778
+ }
4779
+ return converted;
4780
+ }
4781
+ return input;
4782
+ }
4783
+ function resolveLocalPath(input, base = process.cwd(), options = {}) {
4784
+ const paths = (options.platform ?? process.platform) === "win32" ? win32 : posix;
4785
+ return paths.resolve(normalizeLocalPath(base, options), normalizeLocalPath(input, options));
4786
+ }
4787
+ function displayLocalPath(input, home, platform = process.platform) {
4788
+ if (!input || !home)
4789
+ return input;
4790
+ const paths = platform === "win32" ? win32 : posix;
4791
+ if (!paths.isAbsolute(input) || !paths.isAbsolute(home))
4792
+ return input;
4793
+ const relative = paths.relative(home, input);
4794
+ if (relative === "")
4795
+ return "~";
4796
+ return relative !== ".." && !relative.startsWith(`..${paths.sep}`) && !paths.isAbsolute(relative) ? `~/${relative.split(paths.sep).join("/")}` : input;
4797
+ }
4798
+
4799
+ // ../../packages/storage/local-config/dist/config.js
4685
4800
  function isPlainObject(value) {
4686
4801
  return typeof value === "object" && value !== null && !Array.isArray(value);
4687
4802
  }
4688
4803
  function errorMessage7(error) {
4689
4804
  return error instanceof Error ? error.message : String(error);
4690
4805
  }
4691
- function expandUser(input) {
4692
- if (input === "~") {
4693
- return homedir();
4694
- }
4695
- if (input.startsWith("~/")) {
4696
- return join3(homedir(), input.slice(2));
4697
- }
4698
- return input;
4699
- }
4700
4806
  function defaultConfigPath(environ = process.env) {
4701
4807
  const explicit = environ["LAOHUANG_CONFIG"];
4702
4808
  if (explicit) {
4703
- return expandUser(explicit);
4809
+ return normalizeLocalPath(explicit, { env: environ });
4704
4810
  }
4705
4811
  const configHome = environ["XDG_CONFIG_HOME"];
4706
- const root = configHome ? expandUser(configHome) : join3(homedir(), ".config");
4812
+ const root = configHome ? normalizeLocalPath(configHome, { env: environ }) : join3(getHomeDirectory({ env: environ }), ".config");
4707
4813
  return join3(root, "laohuang", "config.json");
4708
4814
  }
4709
4815
  var ConfigManager = class {
@@ -4734,6 +4840,9 @@ var ConfigManager = class {
4734
4840
  resolve(options = {}) {
4735
4841
  return this.resolveSettings(options);
4736
4842
  }
4843
+ getShellPath() {
4844
+ return this.readDocument({ optional: true })["shell_path"];
4845
+ }
4737
4846
  resolveSettings(options = {}) {
4738
4847
  const document = this.readDocument();
4739
4848
  const environment = options.environ ?? process.env;
@@ -4778,7 +4887,7 @@ var ConfigManager = class {
4778
4887
  });
4779
4888
  }
4780
4889
  readDocument(options = {}) {
4781
- if (!existsSync4(this.path)) {
4890
+ if (!existsSync5(this.path)) {
4782
4891
  if (options.optional) {
4783
4892
  return {};
4784
4893
  }
@@ -4807,6 +4916,10 @@ var ConfigManager = class {
4807
4916
  }
4808
4917
  };
4809
4918
  function validateDocument(document) {
4919
+ const shellPath = document["shell_path"];
4920
+ if (shellPath !== void 0 && (typeof shellPath !== "string" || !shellPath.trim())) {
4921
+ throw new Error("Configuration shell_path must be a non-empty string");
4922
+ }
4810
4923
  const version = document["version"] ?? 1;
4811
4924
  if (version !== 1) {
4812
4925
  throw new Error(`Unsupported configuration version: ${String(version)}`);
@@ -4843,7 +4956,7 @@ function validateDocument(document) {
4843
4956
 
4844
4957
  // ../../packages/storage/local-config/dist/credentials.js
4845
4958
  import { randomUUID as randomUUID7 } from "node:crypto";
4846
- import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
4959
+ import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
4847
4960
  import { basename as basename3, dirname as dirname2, join as join4 } from "node:path";
4848
4961
  function isPlainObject2(value) {
4849
4962
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4968,7 +5081,7 @@ var CredentialStore = class {
4968
5081
  await operation;
4969
5082
  }
4970
5083
  readDocument() {
4971
- if (!existsSync5(this.path)) {
5084
+ if (!existsSync6(this.path)) {
4972
5085
  return { version: 2, providers: {} };
4973
5086
  }
4974
5087
  let document;
@@ -4999,7 +5112,7 @@ var CredentialStore = class {
4999
5112
  }
5000
5113
  writeDocument(document) {
5001
5114
  const parent = dirname2(this.path);
5002
- const parentExisted = existsSync5(parent);
5115
+ const parentExisted = existsSync6(parent);
5003
5116
  mkdirSync3(parent, { recursive: true, mode: 448 });
5004
5117
  if (!parentExisted) {
5005
5118
  chmodSync2(parent, 448);
@@ -5024,7 +5137,7 @@ function normalizeStoredCredential(credential) {
5024
5137
 
5025
5138
  // ../../packages/storage/local-config/dist/model-catalog-store.js
5026
5139
  import { randomUUID as randomUUID8 } from "node:crypto";
5027
- import { chmodSync as chmodSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
5140
+ import { chmodSync as chmodSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync3, writeFileSync as writeFileSync3 } from "node:fs";
5028
5141
  import { basename as basename4, dirname as dirname3, join as join5 } from "node:path";
5029
5142
  function isPlainObject3(value) {
5030
5143
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -5056,7 +5169,7 @@ var ModelCatalogStore = class {
5056
5169
  }
5057
5170
  }
5058
5171
  readDocument() {
5059
- if (!existsSync6(this.path)) {
5172
+ if (!existsSync7(this.path)) {
5060
5173
  return { version: 1, providers: {} };
5061
5174
  }
5062
5175
  let document;
@@ -5083,7 +5196,7 @@ var ModelCatalogStore = class {
5083
5196
  }
5084
5197
  writeDocument(document) {
5085
5198
  const parent = dirname3(this.path);
5086
- const parentExisted = existsSync6(parent);
5199
+ const parentExisted = existsSync7(parent);
5087
5200
  mkdirSync4(parent, { recursive: true, mode: 448 });
5088
5201
  if (!parentExisted) {
5089
5202
  chmodSync3(parent, 448);
@@ -6609,20 +6722,20 @@ var AgentSession = class {
6609
6722
  if (timeoutMs !== void 0 && timeoutMs <= 0) {
6610
6723
  return Promise.resolve(false);
6611
6724
  }
6612
- return new Promise((resolve2) => {
6725
+ return new Promise((resolve3) => {
6613
6726
  let timer;
6614
6727
  const onIdle = () => {
6615
6728
  if (timer !== void 0) {
6616
6729
  clearTimeout(timer);
6617
6730
  }
6618
6731
  this.#idleWaiters.delete(onIdle);
6619
- resolve2(true);
6732
+ resolve3(true);
6620
6733
  };
6621
6734
  this.#idleWaiters.add(onIdle);
6622
6735
  if (timeoutMs !== void 0) {
6623
6736
  timer = setTimeout(() => {
6624
6737
  this.#idleWaiters.delete(onIdle);
6625
- resolve2(false);
6738
+ resolve3(false);
6626
6739
  }, timeoutMs);
6627
6740
  timer.unref();
6628
6741
  }
@@ -6936,8 +7049,8 @@ var AgentSession = class {
6936
7049
  if (value) {
6937
7050
  const waiters = [...this.#idleWaiters];
6938
7051
  this.#idleWaiters.clear();
6939
- for (const resolve2 of waiters) {
6940
- resolve2();
7052
+ for (const resolve3 of waiters) {
7053
+ resolve3();
6941
7054
  }
6942
7055
  }
6943
7056
  }
@@ -7070,8 +7183,89 @@ function unavailableActionNotice(action) {
7070
7183
  return "Cancellation is unavailable for this runtime.";
7071
7184
  }
7072
7185
 
7186
+ // ../../packages/terminal/tui/dist/tui/native-console.js
7187
+ import { createRequire } from "node:module";
7188
+ var require2 = createRequire(import.meta.url);
7189
+ var native;
7190
+ var activeConsoles = [];
7191
+ function getWindowsConsole() {
7192
+ if (process.platform !== "win32")
7193
+ return null;
7194
+ if (native !== void 0)
7195
+ return native;
7196
+ const koffi = require2("koffi");
7197
+ const kernel = koffi.load("kernel32.dll");
7198
+ const user = koffi.load("user32.dll");
7199
+ const getHandle = kernel.func("__stdcall", "GetStdHandle", "void *", ["uint32_t"]);
7200
+ const getMode = kernel.func("__stdcall", "GetConsoleMode", "int", ["void *", "void *"]);
7201
+ const setMode = kernel.func("__stdcall", "SetConsoleMode", "int", ["void *", "uint32_t"]);
7202
+ const getKey = user.func("__stdcall", "GetAsyncKeyState", "int16_t", ["int"]);
7203
+ const getError = kernel.func("__stdcall", "GetLastError", "uint32_t", []);
7204
+ const handle = getHandle(4294967286);
7205
+ const buffer = Buffer.alloc(4);
7206
+ if (!getMode(handle, buffer))
7207
+ return native = null;
7208
+ native = {
7209
+ getMode: () => {
7210
+ if (!getMode(handle, buffer))
7211
+ throw new Error(`GetConsoleMode failed (${getError()})`);
7212
+ return buffer.readUInt32LE();
7213
+ },
7214
+ setMode: (mode) => {
7215
+ if (!setMode(handle, mode))
7216
+ throw new Error(`SetConsoleMode failed (${getError()})`);
7217
+ },
7218
+ shiftPressed: () => (getKey(16) & 32768) !== 0
7219
+ };
7220
+ return native;
7221
+ }
7222
+ function isLocalWindowsConsole() {
7223
+ return process.platform === "win32" && activeConsoles.length > 0 && !process.env.SSH_CONNECTION && !process.env.SSH_CLIENT && !process.env.SSH_TTY;
7224
+ }
7225
+ function isNativeShiftPressed() {
7226
+ return isLocalWindowsConsole() && (activeConsoles.at(-1)?.shiftPressed() ?? false);
7227
+ }
7228
+ function enterTerminalRawMode(input, console2 = input === process.stdin ? getWindowsConsole() : null) {
7229
+ if (!input.setRawMode)
7230
+ return () => {
7231
+ };
7232
+ const wasRaw = input.isRaw ?? false;
7233
+ const originalMode = console2?.getMode();
7234
+ let active = true;
7235
+ let registered = false;
7236
+ const restore = () => {
7237
+ if (!active)
7238
+ return;
7239
+ active = false;
7240
+ if (registered && console2)
7241
+ activeConsoles.splice(activeConsoles.lastIndexOf(console2), 1);
7242
+ try {
7243
+ input.setRawMode?.(wasRaw);
7244
+ } finally {
7245
+ if (originalMode !== void 0)
7246
+ console2?.setMode(originalMode);
7247
+ }
7248
+ };
7249
+ try {
7250
+ input.setRawMode(true);
7251
+ if (console2) {
7252
+ console2.setMode(console2.getMode() | 512);
7253
+ activeConsoles.push(console2);
7254
+ registered = true;
7255
+ }
7256
+ return restore;
7257
+ } catch (error) {
7258
+ try {
7259
+ restore();
7260
+ } catch (restoreError) {
7261
+ throw new AggregateError([error, restoreError], "Terminal setup and mode restoration failed");
7262
+ }
7263
+ throw error;
7264
+ }
7265
+ }
7266
+
7073
7267
  // ../../packages/terminal/tui/dist/tui/screen.js
7074
- var PiMainScreenRenderer = class _PiMainScreenRenderer {
7268
+ var MainScreenRenderer = class _MainScreenRenderer {
7075
7269
  #terminal;
7076
7270
  #previousLines = [];
7077
7271
  #previousWidth = 0;
@@ -7084,7 +7278,7 @@ var PiMainScreenRenderer = class _PiMainScreenRenderer {
7084
7278
  #closed = false;
7085
7279
  constructor(terminal, env = process.env) {
7086
7280
  this.#terminal = terminal;
7087
- this.#clearOnShrink = env.PI_CLEAR_ON_SHRINK === "1";
7281
+ this.#clearOnShrink = env.LAOHUANG_CLEAR_ON_SHRINK === "1";
7088
7282
  }
7089
7283
  render(frame) {
7090
7284
  if (this.#closed) {
@@ -7094,9 +7288,9 @@ var PiMainScreenRenderer = class _PiMainScreenRenderer {
7094
7288
  const width = Math.max(1, size.columns);
7095
7289
  const height = Math.max(1, size.rows);
7096
7290
  const newLines = frame.lines;
7097
- _PiMainScreenRenderer.#validateLines(newLines, width);
7098
7291
  const widthChanged = this.#previousWidth !== 0 && this.#previousWidth !== width;
7099
7292
  const heightChanged = this.#previousHeight !== 0 && this.#previousHeight !== height;
7293
+ _MainScreenRenderer.#validateLines(newLines, width, widthChanged || heightChanged ? [] : this.#previousLines);
7100
7294
  const previousBufferLength = this.#previousHeight > 0 ? this.#previousViewportTop + this.#previousHeight : height;
7101
7295
  const prevViewportTop = heightChanged ? Math.max(0, previousBufferLength - height) : this.#previousViewportTop;
7102
7296
  let viewportTop = prevViewportTop;
@@ -7144,7 +7338,7 @@ var PiMainScreenRenderer = class _PiMainScreenRenderer {
7144
7338
  fullRender(true);
7145
7339
  return;
7146
7340
  }
7147
- const span2 = _PiMainScreenRenderer.#changedSpan(this.#previousLines, newLines);
7341
+ const span2 = _MainScreenRenderer.#changedSpan(this.#previousLines, newLines);
7148
7342
  if (span2 === null) {
7149
7343
  const buffer2 = this.#positionHardwareCursor(frame, width, newLines.length);
7150
7344
  this.#previousViewportTop = prevViewportTop;
@@ -7310,13 +7504,15 @@ var PiMainScreenRenderer = class _PiMainScreenRenderer {
7310
7504
  this.#hardwareCursorRow = targetRow;
7311
7505
  return buffer;
7312
7506
  }
7313
- #commitState(lines, width, height) {
7314
- this.#previousLines = [...lines];
7507
+ #commitState(lines2, width, height) {
7508
+ this.#previousLines = [...lines2];
7315
7509
  this.#previousWidth = width;
7316
7510
  this.#previousHeight = height;
7317
7511
  }
7318
- static #validateLines(lines, width) {
7319
- lines.forEach((line2, index) => {
7512
+ static #validateLines(lines2, width, previous) {
7513
+ lines2.forEach((line2, index) => {
7514
+ if (previous[index] === line2)
7515
+ return;
7320
7516
  if (/[\r\n]/u.test(line2)) {
7321
7517
  throw new Error(`rendered line ${index} contains a physical newline`);
7322
7518
  }
@@ -7327,15 +7523,28 @@ var PiMainScreenRenderer = class _PiMainScreenRenderer {
7327
7523
  });
7328
7524
  }
7329
7525
  };
7526
+ var WIDTH_CACHE_SIZE = 512;
7527
+ var WIDTH_CACHE_MAX_TEXT_LENGTH = 4096;
7528
+ var widthCache = /* @__PURE__ */ new Map();
7330
7529
  function visibleWidth(text) {
7331
- if (!text) {
7332
- return 0;
7333
- }
7530
+ if (/^[\x20-\x7e]*$/u.test(text))
7531
+ return text.length;
7532
+ const cached = widthCache.get(text);
7533
+ if (cached !== void 0)
7534
+ return cached;
7334
7535
  const stripped = stripTerminalControls(text).replace(/\t/g, " ");
7335
7536
  let width = 0;
7336
7537
  for (const cluster of graphemeClusters(stripped)) {
7337
7538
  width += clusterWidth(cluster);
7338
7539
  }
7540
+ if (text.length <= WIDTH_CACHE_MAX_TEXT_LENGTH) {
7541
+ if (widthCache.size >= WIDTH_CACHE_SIZE) {
7542
+ const oldest = widthCache.keys().next().value;
7543
+ if (oldest !== void 0)
7544
+ widthCache.delete(oldest);
7545
+ }
7546
+ widthCache.set(text, width);
7547
+ }
7339
7548
  return width;
7340
7549
  }
7341
7550
  function truncateToWidth(text, width) {
@@ -7515,7 +7724,7 @@ function lineText(value) {
7515
7724
  }
7516
7725
  function wrapStyledSpans(spans, width) {
7517
7726
  const targetWidth = Math.max(1, width);
7518
- const lines = [];
7727
+ const lines2 = [];
7519
7728
  let current = [];
7520
7729
  let currentWidth = 0;
7521
7730
  let skipLineFeed = false;
@@ -7528,7 +7737,7 @@ function wrapStyledSpans(spans, width) {
7528
7737
  current.push(span(text, style));
7529
7738
  };
7530
7739
  const finish = () => {
7531
- lines.push(line(...current));
7740
+ lines2.push(line(...current));
7532
7741
  current = [];
7533
7742
  currentWidth = 0;
7534
7743
  };
@@ -7560,7 +7769,7 @@ function wrapStyledSpans(spans, width) {
7560
7769
  }
7561
7770
  }
7562
7771
  finish();
7563
- return lines;
7772
+ return lines2;
7564
7773
  }
7565
7774
  function truncateStyledLine(value, width, ellipsis = "\u2026") {
7566
7775
  if (width <= 0) {
@@ -7589,7 +7798,7 @@ function truncateStyledLine(value, width, ellipsis = "\u2026") {
7589
7798
  if (!truncated) {
7590
7799
  return line(...mergeAdjacent(result));
7591
7800
  }
7592
- const ellipsisWidth = charCellWidth(ellipsis);
7801
+ const ellipsisWidth = ellipsis === "" ? 0 : charCellWidth(ellipsis);
7593
7802
  while (result.length > 0 && used + ellipsisWidth > width) {
7594
7803
  const removed = result.pop();
7595
7804
  used -= charCellWidth(removed.text);
@@ -7649,6 +7858,7 @@ var InputActionKind = {
7649
7858
  CursorLeft: "cursor_left",
7650
7859
  CursorRight: "cursor_right",
7651
7860
  Backspace: "backspace",
7861
+ DeleteToLineStart: "delete_to_line_start",
7652
7862
  Key: "key",
7653
7863
  Dismiss: "dismiss",
7654
7864
  Cancel: "cancel",
@@ -7697,6 +7907,8 @@ function toTuiInputEvent(input) {
7697
7907
  return { type: "key", key: makeKeyInput("right") };
7698
7908
  case InputActionKind.Backspace:
7699
7909
  return { type: "key", key: makeKeyInput("backspace") };
7910
+ case InputActionKind.DeleteToLineStart:
7911
+ return { type: "key", key: makeKeyInput("ctrl_u", { ctrl: true }) };
7700
7912
  case InputActionKind.Dismiss:
7701
7913
  return { type: "key", key: makeKeyInput("escape") };
7702
7914
  case InputActionKind.Cancel:
@@ -7829,13 +8041,15 @@ var ABANDONED_NEGOTIATION_TAIL_RE = /^\x1b\[\?[\d;]*([A-Za-z])$/;
7829
8041
  var TerminalInputFilter = class {
7830
8042
  pendingNegotiationPrefix = "";
7831
8043
  isAppleTerminal;
8044
+ isWindowsConsole;
7832
8045
  shiftPressed;
7833
8046
  enableModifyOtherKeys;
7834
8047
  disableModifyOtherKeys;
7835
8048
  kittyProtocolActive = false;
7836
8049
  constructor(options = {}) {
7837
8050
  this.isAppleTerminal = options.isAppleTerminal ?? isAppleTerminalSession;
7838
- this.shiftPressed = options.shiftPressed ?? (() => false);
8051
+ this.isWindowsConsole = options.isWindowsConsole ?? isLocalWindowsConsole;
8052
+ this.shiftPressed = options.shiftPressed ?? isNativeShiftPressed;
7839
8053
  this.enableModifyOtherKeys = options.enableModifyOtherKeys ?? (() => {
7840
8054
  });
7841
8055
  this.disableModifyOtherKeys = options.disableModifyOtherKeys ?? (() => {
@@ -7914,7 +8128,7 @@ var TerminalInputFilter = class {
7914
8128
  return match[1];
7915
8129
  }
7916
8130
  normalizePlatformInput(sequence) {
7917
- if (sequence === "\r" && this.isAppleTerminal() && this.shiftPressed()) {
8131
+ if (sequence === "\r" && (this.isAppleTerminal() || this.isWindowsConsole()) && this.shiftPressed()) {
7918
8132
  return APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE;
7919
8133
  }
7920
8134
  return sequence;
@@ -7937,6 +8151,7 @@ var CONTROL_ACTIONS = /* @__PURE__ */ new Map([
7937
8151
  [4, InputActionKind.Eof],
7938
8152
  [9, InputActionKind.Complete],
7939
8153
  [8, InputActionKind.Backspace],
8154
+ [21, InputActionKind.DeleteToLineStart],
7940
8155
  [127, InputActionKind.Backspace]
7941
8156
  ]);
7942
8157
  var CONTROL_KEYS = /* @__PURE__ */ new Map([
@@ -8367,10 +8582,13 @@ function decodeSpecialEscapeAction(sequence) {
8367
8582
  return kind !== void 0 ? inputAction(kind) : null;
8368
8583
  }
8369
8584
  function decodeModifiedControlKey(sequence) {
8370
- const kitty = /^\x1b\[(13|57414|9);(\d+)(?::(\d+))?u$/.exec(sequence);
8585
+ const kitty = /^\x1b\[(13|57414|9|117)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
8371
8586
  if (kitty !== null) {
8372
8587
  const code = kitty[1];
8373
8588
  const modifier2 = Number.parseInt(kitty[2], 10) - 1;
8589
+ if (code === "117" && matchesKittyModifiers(modifier2, 4) && (isKittyPressEvent(kitty[3]) || kitty[3] === "2")) {
8590
+ return inputAction(InputActionKind.DeleteToLineStart);
8591
+ }
8374
8592
  if (!isKittyPressEvent(kitty[3])) {
8375
8593
  return null;
8376
8594
  }
@@ -8388,6 +8606,9 @@ function decodeModifiedControlKey(sequence) {
8388
8606
  }
8389
8607
  const modifier = Number.parseInt(modifyOtherKeys[1], 10) - 1;
8390
8608
  const codepoint = Number.parseInt(modifyOtherKeys[2], 10);
8609
+ if (codepoint === 117 && modifier === 4) {
8610
+ return inputAction(InputActionKind.DeleteToLineStart);
8611
+ }
8391
8612
  if (codepoint === 13 && modifier === 2) {
8392
8613
  return inputAction(InputActionKind.Key, "", makeKeyInput("enter", { alt: true }));
8393
8614
  }
@@ -8506,13 +8727,13 @@ var EditorState = class _EditorState {
8506
8727
  const projection = this.#renderProjection(width, prompt, options.mask ?? false);
8507
8728
  const promptStyle = options.styles?.prompt ?? ((text) => text);
8508
8729
  const textStyle = options.styles?.text ?? ((text) => text);
8509
- const lines = projection.rows.map((row, index) => {
8730
+ const lines2 = projection.rows.map((row, index) => {
8510
8731
  const promptText = index === 0 ? projection.prompt : " ".repeat(projection.promptWidth);
8511
8732
  const renderedPrompt = index === 0 ? promptStyle(promptText) : promptText;
8512
8733
  return renderedPrompt + textStyle(row);
8513
8734
  });
8514
8735
  return {
8515
- lines,
8736
+ lines: lines2,
8516
8737
  cursorRow: projection.cursorRow,
8517
8738
  cursorColumn: projection.cursorColumn
8518
8739
  };
@@ -8609,6 +8830,8 @@ var EditorState = class _EditorState {
8609
8830
  this.clearCompletions();
8610
8831
  this.text = this.text.slice(0, this.cursor - 1) + this.text.slice(this.cursor);
8611
8832
  this.cursor -= 1;
8833
+ } else if (action.kind === InputActionKind.DeleteToLineStart) {
8834
+ this.deleteToLineStart();
8612
8835
  } else if (action.kind === InputActionKind.CursorLeft) {
8613
8836
  this.clearCompletions();
8614
8837
  this.cursor = Math.max(0, this.cursor - 1);
@@ -8631,6 +8854,22 @@ var EditorState = class _EditorState {
8631
8854
  this.cursor += text.length;
8632
8855
  this.historyIndex = null;
8633
8856
  }
8857
+ deleteToLineStart() {
8858
+ if (this.cursor === 0) {
8859
+ return;
8860
+ }
8861
+ const previousNewline = this.text.lastIndexOf("\n", this.cursor - 1);
8862
+ const lineStart = previousNewline + 1;
8863
+ this.clearCompletions();
8864
+ if (lineStart === this.cursor) {
8865
+ this.text = this.text.slice(0, previousNewline) + this.text.slice(this.cursor);
8866
+ this.cursor = previousNewline;
8867
+ } else {
8868
+ this.text = this.text.slice(0, lineStart) + this.text.slice(this.cursor);
8869
+ this.cursor = lineStart;
8870
+ }
8871
+ this.historyIndex = null;
8872
+ }
8634
8873
  acceptCompletion() {
8635
8874
  if (this.selectedCompletion === null) {
8636
8875
  return;
@@ -8728,6 +8967,7 @@ var DEFAULT_KEYBINDINGS = [
8728
8967
  { context: "terminal", key: "ctrl+o", action: "toggle_tool_output" },
8729
8968
  { context: "terminal", key: "ctrl+l", action: "select_model" },
8730
8969
  { context: "terminal", key: "ctrl+t", action: "toggle_thinking" },
8970
+ { context: "editor", key: "ctrl+u", action: "delete_to_line_start" },
8731
8971
  { context: "editor", key: "ctrl+s", action: "steer_now" },
8732
8972
  { context: "editor", key: "alt+enter", action: "submit_follow_up" },
8733
8973
  { context: "editor", key: "escape", action: "dismiss" },
@@ -8762,6 +9002,9 @@ function controlKeyId(character) {
8762
9002
  if (character === "l") {
8763
9003
  return "ctrl_l";
8764
9004
  }
9005
+ if (character === "u") {
9006
+ return "ctrl_u";
9007
+ }
8765
9008
  return "character";
8766
9009
  }
8767
9010
  function parseKey(value) {
@@ -8933,6 +9176,10 @@ var UIEventReducer = class _UIEventReducer {
8933
9176
  payload
8934
9177
  });
8935
9178
  }
9179
+ if (kind === "ui.context_usage") {
9180
+ this.#updateContextUsage(payload);
9181
+ return createUpdate(kind, { payload });
9182
+ }
8936
9183
  if (kind === "task.state_changed") {
8937
9184
  const rawState = payload.state ?? "IDLE";
8938
9185
  this.state.sessionState = String(unwrapValue(rawState)).toUpperCase();
@@ -9035,6 +9282,14 @@ var UIEventReducer = class _UIEventReducer {
9035
9282
  }
9036
9283
  return createUpdate(kind, { text, correlationId, stream });
9037
9284
  }
9285
+ if (kind === "tool.output_snapshot") {
9286
+ const stream = String(payload.stream ?? "stdout");
9287
+ const text = String(payload.text ?? "");
9288
+ const tool = this.state.activeTools.get(correlationId);
9289
+ if (tool !== void 0 && (stream === "stdout" || stream === "stderr"))
9290
+ tool[stream] = text;
9291
+ return createUpdate(kind, { text, correlationId, stream, payload });
9292
+ }
9038
9293
  if (kind === "tool.finished") {
9039
9294
  const tool = this.state.activeTools.get(correlationId);
9040
9295
  this.state.activeTools.delete(correlationId);
@@ -9094,11 +9349,13 @@ var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
9094
9349
  var HIGH_FREQUENCY_EVENTS = /* @__PURE__ */ new Set([
9095
9350
  "model.reasoning_delta",
9096
9351
  "model.tool_call_delta",
9097
- "tool.output_delta"
9352
+ "tool.output_delta",
9353
+ "tool.output_snapshot"
9098
9354
  ]);
9099
9355
  var TOOL_EVENTS = /* @__PURE__ */ new Set([
9100
9356
  "tool.started",
9101
9357
  "tool.output_delta",
9358
+ "tool.output_snapshot",
9102
9359
  "tool.finished"
9103
9360
  ]);
9104
9361
  var SENSITIVE_FIELDS2 = /* @__PURE__ */ new Set([
@@ -9129,6 +9386,26 @@ function asPayload(value) {
9129
9386
  function redactToolText(value) {
9130
9387
  return SENSITIVE_TEXT_PATTERNS2.reduce((text, pattern) => text.replace(pattern, "$1[REDACTED]"), value);
9131
9388
  }
9389
+ function redactToolSnapshot(value, startMidLine = false) {
9390
+ const text = startMidLine ? omitPartialOutputLine(value) : value;
9391
+ return redactToolText(text);
9392
+ }
9393
+ function toolOutputNote(payload) {
9394
+ const notes = [];
9395
+ if (payload.truncated === true)
9396
+ notes.push("Output preview truncated.");
9397
+ if (isRecord3(payload.output_files)) {
9398
+ const label = payload.output_file_complete === true ? "Full output" : "Partial output";
9399
+ notes.push(`${label}: ${Object.values(payload.output_files).filter((value) => typeof value === "string").join(", ")}`);
9400
+ }
9401
+ if (payload.output_file_error)
9402
+ notes.push(String(payload.output_file_error));
9403
+ if (payload.output_complete === false)
9404
+ notes.push("Output collection incomplete.");
9405
+ if (payload.error)
9406
+ notes.push(String(payload.error));
9407
+ return redactToolText(notes.join("\n"));
9408
+ }
9132
9409
  function redactToolValue(value) {
9133
9410
  if (typeof value === "string")
9134
9411
  return redactToolText(value);
@@ -9199,8 +9476,15 @@ var DisplayPolicy = class {
9199
9476
  const text = kind === "tool.output_delta" ? this.#toolOutputRedactor.redact(correlationId, stream || "stdout", String(rawPayload.text ?? rawPayload.chunk ?? "")) : String(payload.text ?? payload.chunk ?? "");
9200
9477
  if (kind === "tool.output_delta") {
9201
9478
  payload = { ...payload, text };
9479
+ } else if (kind === "tool.output_snapshot") {
9480
+ payload = { ...payload, text: redactToolSnapshot(String(rawPayload.text ?? ""), rawPayload.start_mid_line === true) };
9202
9481
  } else if (kind === "tool.finished") {
9203
9482
  this.#toolOutputRedactor.clear(correlationId);
9483
+ for (const name of ["stdout", "stderr"]) {
9484
+ if (typeof rawPayload[name] === "string") {
9485
+ payload[name] = redactToolSnapshot(rawPayload[name], rawPayload[`${name}_start_mid_line`] === true);
9486
+ }
9487
+ }
9204
9488
  }
9205
9489
  const projected = [];
9206
9490
  const dropped = droppedCount(payload);
@@ -9216,11 +9500,11 @@ var DisplayPolicy = class {
9216
9500
  if (kind === "model.reasoning_delta" && !this.showReasoning) {
9217
9501
  return projected;
9218
9502
  }
9219
- if (this.foldToolOutput && kind === "tool.output_delta" && (stream || "stdout") === "stdout") {
9503
+ if (this.foldToolOutput && (kind === "tool.output_delta" || kind === "tool.output_snapshot") && (stream || "stdout") === "stdout") {
9220
9504
  return projected;
9221
9505
  }
9222
9506
  if (LIFECYCLE_EVENTS.has(kind) || kind.length > 0) {
9223
- projected.push({ kind, correlationId, stream, text, payload });
9507
+ projected.push({ kind, correlationId, stream, text: String(payload.text ?? text), payload });
9224
9508
  }
9225
9509
  return projected;
9226
9510
  }
@@ -9412,11 +9696,27 @@ var TranscriptStore = class {
9412
9696
  }
9413
9697
  return;
9414
9698
  }
9699
+ if (kind === "tool.output_snapshot") {
9700
+ const item = this.#byCorrelation.get(`tool:${correlationId}`);
9701
+ if (item?.kind === "tool" && item.mutable && (update.stream === "stdout" || update.stream === "stderr")) {
9702
+ item[update.stream] = redactToolSnapshot(update.text, update.payload.start_mid_line === true);
9703
+ if (update.payload.truncated === true)
9704
+ item.outputNote = "Output preview truncated.";
9705
+ touchBlock(item);
9706
+ }
9707
+ return;
9708
+ }
9415
9709
  if (kind === "tool.finished") {
9416
9710
  const item = this.#getOrCreateTool(correlationId);
9417
9711
  item.status = String(update.payload.status ?? "completed");
9418
9712
  item.exitCode = typeof update.payload.exit_code === "number" ? Math.trunc(update.payload.exit_code) : null;
9419
9713
  item.durationMs = typeof update.payload.duration_ms === "number" ? Math.trunc(update.payload.duration_ms) : null;
9714
+ for (const stream of ["stdout", "stderr"]) {
9715
+ const text = update.payload[stream];
9716
+ if (typeof text === "string")
9717
+ item[stream] = redactToolSnapshot(text, update.payload[`${stream}_start_mid_line`] === true);
9718
+ }
9719
+ item.outputNote = toolOutputNote(update.payload);
9420
9720
  item.mutable = false;
9421
9721
  touchBlock(item);
9422
9722
  this.#toolOutputRedactor.clear(correlationId);
@@ -9480,6 +9780,23 @@ var TranscriptStore = class {
9480
9780
  };
9481
9781
 
9482
9782
  // ../../packages/terminal/tui/dist/tui/ansi-renderer.js
9783
+ var StyledLineCompiler = class {
9784
+ #cache = /* @__PURE__ */ new WeakMap();
9785
+ compile(values, width, theme) {
9786
+ return values.map((value) => {
9787
+ const cached = this.#cache.get(value);
9788
+ if (cached !== void 0 && cached.width === width && cached.theme === theme) {
9789
+ return cached.text;
9790
+ }
9791
+ const text = compileStyledLine(truncateStyledLine(value, width, ""), width, theme);
9792
+ this.#cache.set(value, { width, theme, text });
9793
+ return text;
9794
+ });
9795
+ }
9796
+ invalidate() {
9797
+ this.#cache = /* @__PURE__ */ new WeakMap();
9798
+ }
9799
+ };
9483
9800
  function compileStyledLine(value, width, theme) {
9484
9801
  const text = value.spans.map((item) => compileSpan(item, theme)).join("");
9485
9802
  const renderedWidth = visibleWidth(text);
@@ -9540,7 +9857,7 @@ var CompletionPopup = class {
9540
9857
  const description = item.description.replace(/[\r\n]+/gu, " ").trim();
9541
9858
  const value = line(span(selected ? "\u203A " : " ", selected ? { foreground: "accent" } : void 0), span(item.value, selected ? { foreground: "accent" } : void 0), ...description ? [span(` ${description}`, { foreground: "muted" })] : []);
9542
9859
  const boundedWidth = Math.max(1, width);
9543
- return displayWidth2(lineText(value)) <= boundedWidth ? value : truncateStyledLine(value, boundedWidth + 1, "");
9860
+ return displayWidth2(lineText(value)) <= boundedWidth ? value : truncateStyledLine(value, boundedWidth, "");
9544
9861
  }
9545
9862
  };
9546
9863
  function displayWidth2(value) {
@@ -9581,13 +9898,13 @@ var Composer = class {
9581
9898
  mask: this.#mask
9582
9899
  });
9583
9900
  const borderStyle = { foreground: "dim" };
9584
- const lines = [
9901
+ const lines2 = [
9585
9902
  line(span("\u256D", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256E", borderStyle)),
9586
9903
  ...rendered.lines.map((value) => line(span("\u2502", borderStyle), ...padStyledLine(truncateStyledLine(value, innerWidth, ""), innerWidth).spans, span("\u2502", borderStyle))),
9587
9904
  line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle))
9588
9905
  ];
9589
9906
  return {
9590
- lines,
9907
+ lines: lines2,
9591
9908
  cursor: {
9592
9909
  row: rendered.cursorRow + 1,
9593
9910
  column: rendered.cursorColumn + 1
@@ -9656,7 +9973,8 @@ function contextUsageLabel(tokens, contextWindow) {
9656
9973
  return null;
9657
9974
  }
9658
9975
  const used = normalizedCount(tokens);
9659
- const percent = Math.max(0, Math.min(100, Math.floor(used / window * 100)));
9976
+ const ratio = Math.min(100, used / window * 100);
9977
+ const percent = ratio > 0 && ratio < 0.1 ? "<0.1" : formatScaled(ratio);
9660
9978
  return `context: ${percent}% (${formatCompactCount(used)}/${formatCompactCount(window)})`;
9661
9979
  }
9662
9980
  function normalizedCount(value) {
@@ -9702,22 +10020,22 @@ function renderMarkdownStyledLines(text, width) {
9702
10020
  return [];
9703
10021
  }
9704
10022
  const layoutWidth = Math.max(12, width);
9705
- const lines = [];
10023
+ const lines2 = [];
9706
10024
  let previous = null;
9707
10025
  for (const block of renderBlocks(clean, layoutWidth)) {
9708
10026
  const leadingBlank = previous === null ? block.kind === "list" || block.kind === "table" || block.kind === "quote" : previous !== "hr";
9709
10027
  if (leadingBlank) {
9710
- lines.push(line());
10028
+ lines2.push(line());
9711
10029
  }
9712
10030
  for (const line2 of block.lines) {
9713
- lines.push(truncateMarkdownLine(serializeLine(line2), width));
10031
+ lines2.push(truncateMarkdownLine(serializeLine(line2), width));
9714
10032
  }
9715
10033
  if (block.kind === "hr") {
9716
- lines.push(line());
10034
+ lines2.push(line());
9717
10035
  }
9718
10036
  previous = block.kind;
9719
10037
  }
9720
- return lines;
10038
+ return lines2;
9721
10039
  }
9722
10040
  function styleKey(style) {
9723
10041
  return [
@@ -9729,7 +10047,7 @@ function styleKey(style) {
9729
10047
  ].join("|");
9730
10048
  }
9731
10049
  function renderBlocks(text, layoutWidth) {
9732
- const lines = text.replace(/\r\n/g, "\n").split("\n");
10050
+ const lines2 = text.replace(/\r\n/g, "\n").split("\n");
9733
10051
  const blocks = [];
9734
10052
  let paragraph = [];
9735
10053
  let index = 0;
@@ -9743,8 +10061,8 @@ function renderBlocks(text, layoutWidth) {
9743
10061
  });
9744
10062
  }
9745
10063
  };
9746
- while (index < lines.length) {
9747
- const line2 = lines[index];
10064
+ while (index < lines2.length) {
10065
+ const line2 = lines2[index];
9748
10066
  const trimmed = line2.trim();
9749
10067
  if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
9750
10068
  flushParagraph();
@@ -9754,8 +10072,8 @@ function renderBlocks(text, layoutWidth) {
9754
10072
  foreground: "code"
9755
10073
  };
9756
10074
  const codeLines = [];
9757
- while (index < lines.length) {
9758
- const body = lines[index];
10075
+ while (index < lines2.length) {
10076
+ const body = lines2[index];
9759
10077
  if (body.trim().startsWith(fence)) {
9760
10078
  index += 1;
9761
10079
  break;
@@ -9771,15 +10089,15 @@ function renderBlocks(text, layoutWidth) {
9771
10089
  index += 1;
9772
10090
  continue;
9773
10091
  }
9774
- if (trimmed.includes("|") && index + 1 < lines.length) {
10092
+ if (trimmed.includes("|") && index + 1 < lines2.length) {
9775
10093
  const header = splitTableRow(trimmed);
9776
- const delimiter = splitTableRow(lines[index + 1].trim());
10094
+ const delimiter = splitTableRow(lines2[index + 1].trim());
9777
10095
  if (header !== null && delimiter !== null && delimiter.length === header.length && delimiter.every((cell) => /^:?-+:?$/.test(cell))) {
9778
10096
  flushParagraph();
9779
10097
  index += 2;
9780
10098
  const rows = [];
9781
- while (index < lines.length) {
9782
- const rowLine = lines[index].trim();
10099
+ while (index < lines2.length) {
10100
+ const rowLine = lines2[index].trim();
9783
10101
  if (!rowLine) {
9784
10102
  break;
9785
10103
  }
@@ -9826,8 +10144,8 @@ function renderBlocks(text, layoutWidth) {
9826
10144
  if (trimmed.startsWith(">")) {
9827
10145
  flushParagraph();
9828
10146
  const content = [];
9829
- while (index < lines.length) {
9830
- const match = /^>\s?(.*)$/.exec(lines[index].trim());
10147
+ while (index < lines2.length) {
10148
+ const match = /^>\s?(.*)$/.exec(lines2[index].trim());
9831
10149
  if (!match) {
9832
10150
  break;
9833
10151
  }
@@ -9846,8 +10164,8 @@ function renderBlocks(text, layoutWidth) {
9846
10164
  if (/^[-*+]\s+/.test(trimmed)) {
9847
10165
  flushParagraph();
9848
10166
  const items = [];
9849
- while (index < lines.length) {
9850
- const match = /^[-*+]\s+(.*)$/.exec(lines[index].trim());
10167
+ while (index < lines2.length) {
10168
+ const match = /^[-*+]\s+(.*)$/.exec(lines2[index].trim());
9851
10169
  if (!match) {
9852
10170
  break;
9853
10171
  }
@@ -9866,8 +10184,8 @@ function renderBlocks(text, layoutWidth) {
9866
10184
  const start = parseInt(numbered[1], 10);
9867
10185
  const items = [parseInline(numbered[2], {})];
9868
10186
  index += 1;
9869
- while (index < lines.length) {
9870
- const match = /^(\d+)[.)]\s+(.*)$/.exec(lines[index].trim());
10187
+ while (index < lines2.length) {
10188
+ const match = /^(\d+)[.)]\s+(.*)$/.exec(lines2[index].trim());
9871
10189
  if (!match) {
9872
10190
  break;
9873
10191
  }
@@ -9889,16 +10207,16 @@ function renderBlocks(text, layoutWidth) {
9889
10207
  }
9890
10208
  function renderListItems(items, prefixFor, layoutWidth) {
9891
10209
  const marker = { foreground: "accent" };
9892
- const lines = [];
10210
+ const lines2 = [];
9893
10211
  for (let index = 0; index < items.length; index += 1) {
9894
10212
  const prefix = prefixFor(index);
9895
10213
  const wrapped = wrapSegments(items[index], Math.max(1, layoutWidth - prefix.length));
9896
10214
  if (wrapped.length === 0) {
9897
- lines.push([{ style: marker, text: prefix }]);
10215
+ lines2.push([{ style: marker, text: prefix }]);
9898
10216
  continue;
9899
10217
  }
9900
10218
  for (let row = 0; row < wrapped.length; row += 1) {
9901
- lines.push([
10219
+ lines2.push([
9902
10220
  {
9903
10221
  style: marker,
9904
10222
  text: row === 0 ? prefix : " ".repeat(prefix.length)
@@ -9907,7 +10225,7 @@ function renderListItems(items, prefixFor, layoutWidth) {
9907
10225
  ]);
9908
10226
  }
9909
10227
  }
9910
- return lines;
10228
+ return lines2;
9911
10229
  }
9912
10230
  function splitTableRow(line2) {
9913
10231
  if (!line2.includes("|")) {
@@ -9955,15 +10273,15 @@ function renderTable(header, rows, layoutWidth) {
9955
10273
  widths[widest] = widths[widest] - 1;
9956
10274
  }
9957
10275
  const inner = widths.reduce((total, w) => total + w, 0) + 2 * (columnCount - 1);
9958
- const lines = [];
9959
- lines.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
9960
- lines.push(...renderTableRows(headerCells, widths, borderStyle, headerStyle));
9961
- lines.push([{ style: borderStyle, text: ` ${"\u2500".repeat(inner)} ` }]);
10276
+ const lines2 = [];
10277
+ lines2.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
10278
+ lines2.push(...renderTableRows(headerCells, widths, borderStyle, headerStyle));
10279
+ lines2.push([{ style: borderStyle, text: ` ${"\u2500".repeat(inner)} ` }]);
9962
10280
  for (const row of bodyCells) {
9963
- lines.push(...renderTableRows(row, widths, borderStyle, {}));
10281
+ lines2.push(...renderTableRows(row, widths, borderStyle, {}));
9964
10282
  }
9965
- lines.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
9966
- return lines;
10283
+ lines2.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
10284
+ return lines2;
9967
10285
  }
9968
10286
  function renderTableRows(cells, widths, borderStyle, padStyle) {
9969
10287
  const wrappedCells = cells.map((cell, i) => {
@@ -9971,7 +10289,7 @@ function renderTableRows(cells, widths, borderStyle, padStyle) {
9971
10289
  return wrapped.length > 0 ? wrapped : [[]];
9972
10290
  });
9973
10291
  const height = Math.max(...wrappedCells.map((cell) => cell.length));
9974
- const lines = [];
10292
+ const lines2 = [];
9975
10293
  for (let row = 0; row < height; row += 1) {
9976
10294
  const line2 = [{ style: borderStyle, text: " " }];
9977
10295
  for (let i = 0; i < cells.length; i += 1) {
@@ -9989,9 +10307,9 @@ function renderTableRows(cells, widths, borderStyle, padStyle) {
9989
10307
  }
9990
10308
  }
9991
10309
  line2.push({ style: borderStyle, text: " " });
9992
- lines.push(mergeSegments(line2));
10310
+ lines2.push(mergeSegments(line2));
9993
10311
  }
9994
- return lines;
10312
+ return lines2;
9995
10313
  }
9996
10314
  var INLINE_PATTERNS = [
9997
10315
  {
@@ -10146,12 +10464,12 @@ function wrapSegments(segments, width) {
10146
10464
  if (current.length > 0) {
10147
10465
  words.push(current);
10148
10466
  }
10149
- const lines = [];
10467
+ const lines2 = [];
10150
10468
  let line2 = [];
10151
10469
  let lineWidth = 0;
10152
10470
  let pendingSpace = null;
10153
10471
  const flushLine = () => {
10154
- lines.push(line2);
10472
+ lines2.push(line2);
10155
10473
  line2 = [];
10156
10474
  lineWidth = 0;
10157
10475
  pendingSpace = null;
@@ -10192,7 +10510,7 @@ function wrapSegments(segments, width) {
10192
10510
  if (line2.length > 0) {
10193
10511
  flushLine();
10194
10512
  }
10195
- return lines.map((physical) => regroup(physical));
10513
+ return lines2.map((physical) => regroup(physical));
10196
10514
  }
10197
10515
  function regroup(clusters) {
10198
10516
  const segments = [];
@@ -10274,11 +10592,11 @@ var Text = class {
10274
10592
  this.#cache = null;
10275
10593
  }
10276
10594
  };
10277
- function applyTextPadding(lines, width, options) {
10595
+ function applyTextPadding(lines2, width, options) {
10278
10596
  const targetWidth = Math.max(0, width);
10279
10597
  const blank = () => line(span(" ".repeat(targetWidth), backgroundStyle(options.background)));
10280
10598
  const horizontalPadding = span(" ".repeat(options.paddingX), backgroundStyle(options.background));
10281
- const contentLines = lines.map((value) => {
10599
+ const contentLines = lines2.map((value) => {
10282
10600
  const contentSpans = options.background === void 0 ? value.spans : value.spans.map((item) => span(item.text, {
10283
10601
  ...item.style,
10284
10602
  background: item.style?.background ?? options.background
@@ -10362,7 +10680,7 @@ var Box = class {
10362
10680
  const width = Math.max(0, context.width);
10363
10681
  const child = this.#child.render({ ...context, width: Math.max(1, width - this.#paddingX * 2) });
10364
10682
  const blank = () => line(span(" ".repeat(width), backgroundStyle2(this.#background)));
10365
- const lines = child.lines.map((value) => this.#renderLine(value, width));
10683
+ const lines2 = child.lines.map((value) => this.#renderLine(value, width));
10366
10684
  const cursor = child.cursor === void 0 ? void 0 : {
10367
10685
  row: child.cursor.row + this.#paddingY,
10368
10686
  column: child.cursor.column + this.#paddingX
@@ -10370,7 +10688,7 @@ var Box = class {
10370
10688
  return {
10371
10689
  lines: [
10372
10690
  ...Array.from({ length: this.#paddingY }, blank),
10373
- ...lines,
10691
+ ...lines2,
10374
10692
  ...Array.from({ length: this.#paddingY }, blank)
10375
10693
  ],
10376
10694
  ...cursor === void 0 ? {} : { cursor }
@@ -10420,11 +10738,11 @@ var ToolMessage = class {
10420
10738
  options.exitCode === null ? "" : `exit ${options.exitCode}`,
10421
10739
  options.durationMs === null ? "" : `${options.durationMs}ms`
10422
10740
  ].filter(Boolean).join(" \xB7 ");
10423
- const output = options.expanded ? [stderr && clip(stderr, 1200), stdout && clip(stdout, 1200)].filter(Boolean) : [];
10741
+ const output = options.expanded ? [stderr, stdout].filter(Boolean).map((text) => text.length > 1200 ? `\u2026${text.slice(-1200)}` : text) : [];
10424
10742
  this.#content = new Box({
10425
10743
  child: new Text({
10426
10744
  spans: [...title, { text: `
10427
- ${[metadata, ...output].join("\n")}` }]
10745
+ ${[metadata, ...output, options.outputNote && redactToolText(options.outputNote)].filter(Boolean).join("\n")}` }]
10428
10746
  })
10429
10747
  });
10430
10748
  }
@@ -10447,12 +10765,12 @@ var UserMessage = class {
10447
10765
  }
10448
10766
  render(context) {
10449
10767
  const width = Math.max(3, context.width);
10450
- const lines = wrapStyledSpans([
10768
+ const lines2 = wrapStyledSpans([
10451
10769
  span("\u2728 ", { foreground: "accent" }),
10452
10770
  span(this.#text)
10453
10771
  ], width);
10454
10772
  return {
10455
- lines: lines.map((value) => padStyledLine(value, width))
10773
+ lines: lines2.map((value) => padStyledLine(value, width))
10456
10774
  };
10457
10775
  }
10458
10776
  invalidate() {
@@ -10474,7 +10792,7 @@ var WelcomeMessage = class {
10474
10792
  }
10475
10793
  const innerWidth = width - 2;
10476
10794
  const borderStyle = { foreground: "accent" };
10477
- const lines = [
10795
+ const lines2 = [
10478
10796
  line(span("\u256D", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256E", borderStyle)),
10479
10797
  frameRow(innerWidth, borderStyle, [
10480
10798
  span("H", { foreground: "accent", bold: true }),
@@ -10484,17 +10802,17 @@ var WelcomeMessage = class {
10484
10802
  ];
10485
10803
  const [subtitle, ...meta] = this.#details;
10486
10804
  if (subtitle !== void 0) {
10487
- lines.push(frameRow(innerWidth, borderStyle, [
10805
+ lines2.push(frameRow(innerWidth, borderStyle, [
10488
10806
  span(" "),
10489
10807
  span(subtitle, { foreground: "dim" })
10490
10808
  ]));
10491
10809
  }
10492
- lines.push(frameRow(innerWidth, borderStyle, [span("")]));
10810
+ lines2.push(frameRow(innerWidth, borderStyle, [span("")]));
10493
10811
  for (const detail of meta) {
10494
- lines.push(frameRow(innerWidth, borderStyle, metaSpans(detail)));
10812
+ lines2.push(frameRow(innerWidth, borderStyle, metaSpans(detail)));
10495
10813
  }
10496
- lines.push(line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle)));
10497
- return { lines };
10814
+ lines2.push(line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle)));
10815
+ return { lines: lines2 };
10498
10816
  }
10499
10817
  invalidate() {
10500
10818
  }
@@ -10627,23 +10945,23 @@ var Transcript = class {
10627
10945
  }
10628
10946
  renderWithMetadata(context) {
10629
10947
  const usableWidth = Math.max(12, context.width);
10630
- const lines = [];
10948
+ const lines2 = [];
10631
10949
  let activeStart = null;
10632
10950
  const seenKeys = /* @__PURE__ */ new Set();
10633
10951
  for (const block of this.#blocks) {
10634
- if (lines.length > 0) {
10635
- lines.push(plainLine(""));
10952
+ if (lines2.length > 0) {
10953
+ lines2.push(plainLine(""));
10636
10954
  }
10637
10955
  if (block.mutable && activeStart === null)
10638
- activeStart = lines.length;
10956
+ activeStart = lines2.length;
10639
10957
  seenKeys.add(cacheKey(block));
10640
- lines.push(...this.#renderBlockCached(block, { ...context, width: usableWidth }));
10958
+ lines2.push(...this.#renderBlockCached(block, { ...context, width: usableWidth }));
10641
10959
  }
10642
10960
  for (const key of this.#cache.keys()) {
10643
10961
  if (!seenKeys.has(key))
10644
10962
  this.#cache.delete(key);
10645
10963
  }
10646
- return { lines, activeStart };
10964
+ return { lines: lines2, activeStart };
10647
10965
  }
10648
10966
  invalidate() {
10649
10967
  this.#cache.clear();
@@ -10652,27 +10970,28 @@ var Transcript = class {
10652
10970
  const key = cacheKey(block);
10653
10971
  const signature = blockSignature(block);
10654
10972
  const cached = this.#cache.get(key);
10655
- if (cached !== void 0 && cached.width === context.width && cached.signature === signature) {
10973
+ if (cached !== void 0 && cached.block === block && cached.width === context.width && cached.signature === signature) {
10656
10974
  return cached.lines;
10657
10975
  }
10658
- if (cached !== void 0 && cached.signature === signature) {
10659
- const lines2 = cached.component.render(context).lines;
10976
+ if (cached !== void 0 && cached.block === block && cached.signature === signature) {
10977
+ const lines3 = cached.component.render(context).lines;
10660
10978
  this.#cache.set(key, {
10661
10979
  ...cached,
10662
10980
  width: context.width,
10663
- lines: lines2
10981
+ lines: lines3
10664
10982
  });
10665
- return lines2;
10983
+ return lines3;
10666
10984
  }
10667
10985
  const component = this.#createBlockComponent(block);
10668
- const lines = component.render(context).lines;
10986
+ const lines2 = component.render(context).lines;
10669
10987
  this.#cache.set(key, {
10988
+ block,
10670
10989
  component,
10671
10990
  width: context.width,
10672
10991
  signature,
10673
- lines
10992
+ lines: lines2
10674
10993
  });
10675
- return lines;
10994
+ return lines2;
10676
10995
  }
10677
10996
  #createBlockComponent(block) {
10678
10997
  switch (block.kind) {
@@ -10744,7 +11063,7 @@ var TerminalTheme = class {
10744
11063
  return `\x1B[${channel};2;${r};${g};${b}m`;
10745
11064
  }
10746
11065
  };
10747
- var PI_DARK = new TerminalTheme("dark", {
11066
+ var DEFAULT_DARK_THEME = new TerminalTheme("dark", {
10748
11067
  accent: "#8abeb7",
10749
11068
  border: "#5f87ff",
10750
11069
  border_muted: "#505050",
@@ -10765,7 +11084,7 @@ var PI_DARK = new TerminalTheme("dark", {
10765
11084
  thinking: "#808080",
10766
11085
  bash: "#b5bd68"
10767
11086
  });
10768
- var PI_LIGHT = new TerminalTheme("light", {
11087
+ var DEFAULT_LIGHT_THEME = new TerminalTheme("light", {
10769
11088
  accent: "#5a8080",
10770
11089
  border: "#547da7",
10771
11090
  border_muted: "#b0b0b0",
@@ -10789,10 +11108,10 @@ var PI_LIGHT = new TerminalTheme("light", {
10789
11108
  function resolveTerminalTheme(name, env = process.env) {
10790
11109
  const requested = (name ?? "auto").trim().toLowerCase() || "auto";
10791
11110
  if (requested === "light") {
10792
- return PI_LIGHT;
11111
+ return DEFAULT_LIGHT_THEME;
10793
11112
  }
10794
11113
  if (requested === "dark") {
10795
- return PI_DARK;
11114
+ return DEFAULT_DARK_THEME;
10796
11115
  }
10797
11116
  if (requested !== "auto") {
10798
11117
  throw new Error("terminal theme must be 'auto', 'dark', or 'light'");
@@ -10800,9 +11119,9 @@ function resolveTerminalTheme(name, env = process.env) {
10800
11119
  const colorfgbg = env.COLORFGBG ?? "";
10801
11120
  const background = colorfgbg ? colorfgbg.split(";").pop() : "";
10802
11121
  if (/^\s*[+-]?\d+\s*$/.test(background)) {
10803
- return parseInt(background, 10) >= 7 ? PI_LIGHT : PI_DARK;
11122
+ return parseInt(background, 10) >= 7 ? DEFAULT_LIGHT_THEME : DEFAULT_DARK_THEME;
10804
11123
  }
10805
- return PI_DARK;
11124
+ return DEFAULT_DARK_THEME;
10806
11125
  }
10807
11126
 
10808
11127
  // ../../packages/terminal/tui/dist/tui/frame-builder.js
@@ -10815,6 +11134,11 @@ var FrameBuilder = class {
10815
11134
  #effort;
10816
11135
  #title;
10817
11136
  #theme;
11137
+ #transcriptView;
11138
+ #lineCompiler = new StyledLineCompiler();
11139
+ #previousWidth = 0;
11140
+ #previousSources = [];
11141
+ #previousLines = [];
10818
11142
  constructor(options) {
10819
11143
  this.#state = options.state;
10820
11144
  this.#transcript = options.transcript;
@@ -10824,14 +11148,23 @@ var FrameBuilder = class {
10824
11148
  this.#effort = options.effort ?? null;
10825
11149
  this.#title = options.title ?? "laoHuang";
10826
11150
  this.#theme = options.theme ?? resolveTerminalTheme();
11151
+ this.#transcriptView = new Transcript({ blocks: this.#transcript.blocks() });
10827
11152
  }
10828
11153
  build(options) {
10829
11154
  const terminalWidth = Math.max(1, options.width);
10830
11155
  const width = Math.max(1, terminalWidth - 1);
10831
11156
  const mainScreen = options.compiledMainScreen ?? this.#fallbackMainScreen(options, width);
10832
- const lines = mainScreen.lines.map((value) => visibleWidth(value) <= width ? value : truncateToWidth(value, width));
11157
+ const lines2 = mainScreen.lines.map((value, index) => {
11158
+ if (this.#previousWidth === width && this.#previousSources[index] === value) {
11159
+ return this.#previousLines[index];
11160
+ }
11161
+ return visibleWidth(value) <= width ? value : truncateToWidth(value, width);
11162
+ });
11163
+ this.#previousWidth = width;
11164
+ this.#previousSources = [...mainScreen.lines];
11165
+ this.#previousLines = lines2;
10833
11166
  const cursor = {
10834
- row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines.length - 1))),
11167
+ row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines2.length - 1))),
10835
11168
  col: Math.max(0, Math.min(mainScreen.cursor.column, width - 1))
10836
11169
  };
10837
11170
  return {
@@ -10840,8 +11173,8 @@ var FrameBuilder = class {
10840
11173
  statusBar: this.statusBar(width),
10841
11174
  cursor,
10842
11175
  screen: {
10843
- lines,
10844
- activeStart: Math.max(0, Math.min(mainScreen.activeStart, lines.length)),
11176
+ lines: lines2,
11177
+ activeStart: Math.max(0, Math.min(mainScreen.activeStart, lines2.length)),
10845
11178
  cursorRow: cursor.row,
10846
11179
  cursorCol: cursor.col
10847
11180
  }
@@ -10851,9 +11184,7 @@ var FrameBuilder = class {
10851
11184
  return this.#status().render({ width: Math.max(1, width), theme: this.#theme }).lines.map(lineText).join("\n");
10852
11185
  }
10853
11186
  #fallbackMainScreen(options, width) {
10854
- const transcript = new Transcript({
10855
- blocks: this.#transcript.blocks()
10856
- }).renderWithMetadata({ width, theme: this.#theme });
11187
+ const transcript = this.#transcriptView.renderWithMetadata({ width, theme: this.#theme });
10857
11188
  const transcriptLines = transcript.lines;
10858
11189
  const composer = new Composer({
10859
11190
  editor: options.editor,
@@ -10866,9 +11197,9 @@ var FrameBuilder = class {
10866
11197
  }).render({ width, theme: this.#theme });
10867
11198
  const status = this.#status().render({ width, theme: this.#theme });
10868
11199
  const cursor = composer.cursor ?? { row: Math.max(0, composer.lines.length - 1), column: 0 };
10869
- const lines = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
11200
+ const lines2 = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
10870
11201
  return {
10871
- lines: compileStyledLines(lines, width, this.#theme),
11202
+ lines: this.#lineCompiler.compile(lines2, width, this.#theme),
10872
11203
  cursor: {
10873
11204
  row: transcriptLines.length + cursor.row,
10874
11205
  column: cursor.column
@@ -10972,8 +11303,8 @@ var SearchInput = class {
10972
11303
  const width = Math.max(1, context.width);
10973
11304
  const contentWidth = Math.max(1, width - displayWidth4(PROMPT));
10974
11305
  const displayText = this.#secret ? "\u2022".repeat(this.#editor.text.length) : this.#editor.text;
10975
- const lines = renderLines(displayText || this.#placeholder, contentWidth, displayText.length === 0 && this.#placeholder.length > 0);
10976
- const structuredLines = lines.map((value, index) => truncateStyledLine(line(span(index === 0 ? PROMPT : " ".repeat(displayWidth4(PROMPT)), index === 0 ? { foreground: "accent" } : void 0), span(value, displayText.length === 0 && this.#placeholder.length > 0 ? { foreground: "muted" } : void 0)), width, ""));
11306
+ const lines2 = renderLines(displayText || this.#placeholder, contentWidth, displayText.length === 0 && this.#placeholder.length > 0);
11307
+ const structuredLines = lines2.map((value, index) => truncateStyledLine(line(span(index === 0 ? PROMPT : " ".repeat(displayWidth4(PROMPT)), index === 0 ? { foreground: "accent" } : void 0), span(value, displayText.length === 0 && this.#placeholder.length > 0 ? { foreground: "muted" } : void 0)), width, ""));
10977
11308
  const cursor = this.focused ? cursorMetadata(displayText.slice(0, this.#editor.cursor), contentWidth) : void 0;
10978
11309
  return cursor === void 0 ? { lines: structuredLines } : {
10979
11310
  lines: structuredLines,
@@ -11041,15 +11372,15 @@ function renderLines(value, width, placeholder) {
11041
11372
  if (placeholder) {
11042
11373
  return [value];
11043
11374
  }
11044
- const lines = [];
11375
+ const lines2 = [];
11045
11376
  for (const source of value.split("\n")) {
11046
- lines.push(...wrapStyledSpans([span(source)], width).map((item) => item.spans.map((part) => part.text).join("")));
11377
+ lines2.push(...wrapStyledSpans([span(source)], width).map((item) => item.spans.map((part) => part.text).join("")));
11047
11378
  }
11048
11379
  const last = value.split("\n").at(-1) ?? "";
11049
11380
  if (value && !value.endsWith("\n") && displayWidth4(last) % width === 0) {
11050
- lines.push("");
11381
+ lines2.push("");
11051
11382
  }
11052
- return lines;
11383
+ return lines2;
11053
11384
  }
11054
11385
  function cursorMetadata(before, width) {
11055
11386
  const segments = before.split("\n");
@@ -11133,11 +11464,11 @@ var SelectList = class {
11133
11464
  const start = Math.max(0, Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.#filteredItems.length - this.#maxVisible));
11134
11465
  const end = Math.min(start + this.#maxVisible, this.#filteredItems.length);
11135
11466
  const primaryColumnWidth = this.#primaryColumnWidth();
11136
- const lines = this.#filteredItems.slice(start, end).map((item, index) => this.#renderItem(item, start + index === this.#selectedIndex, context.width, primaryColumnWidth));
11467
+ const lines2 = this.#filteredItems.slice(start, end).map((item, index) => this.#renderItem(item, start + index === this.#selectedIndex, context.width, primaryColumnWidth));
11137
11468
  if (start > 0 || end < this.#filteredItems.length) {
11138
- lines.push(truncateStyledLine(line(span(` (${this.#selectedIndex + 1}/${this.#filteredItems.length})`, { foreground: "muted" })), context.width, ""));
11469
+ lines2.push(truncateStyledLine(line(span(` (${this.#selectedIndex + 1}/${this.#filteredItems.length})`, { foreground: "muted" })), context.width, ""));
11139
11470
  }
11140
- return { lines };
11471
+ return { lines: lines2 };
11141
11472
  }
11142
11473
  handleInput(event) {
11143
11474
  if (!this.focused || event.type !== "key") {
@@ -11171,14 +11502,14 @@ var SelectList = class {
11171
11502
  const description = item.description === void 0 ? void 0 : normalizeDescription(item.description);
11172
11503
  if (description && width > 40) {
11173
11504
  const effectivePrimaryWidth = Math.max(1, Math.min(primaryColumnWidth, width - prefixWidth - 4));
11174
- const label = truncateText2(displayValue(item), Math.max(1, effectivePrimaryWidth - PRIMARY_COLUMN_GAP));
11505
+ const label = truncateText(displayValue(item), Math.max(1, effectivePrimaryWidth - PRIMARY_COLUMN_GAP));
11175
11506
  const spacing = " ".repeat(Math.max(1, effectivePrimaryWidth - displayWidth5(label)));
11176
11507
  const remainingWidth = width - prefixWidth - displayWidth5(label) - displayWidth5(spacing) - 2;
11177
11508
  if (remainingWidth > MIN_DESCRIPTION_WIDTH) {
11178
- return truncateStyledLine(line(prefix, span(label, selected ? { foreground: "accent" } : void 0), span(spacing), span(truncateText2(description, remainingWidth), { foreground: "muted" })), width, "");
11509
+ return truncateStyledLine(line(prefix, span(label, selected ? { foreground: "accent" } : void 0), span(spacing), span(truncateText(description, remainingWidth), { foreground: "muted" })), width, "");
11179
11510
  }
11180
11511
  }
11181
- return truncateStyledLine(line(prefix, span(truncateText2(displayValue(item), Math.max(1, width - prefixWidth - 2)), selected ? { foreground: "accent" } : void 0)), width, "");
11512
+ return truncateStyledLine(line(prefix, span(truncateText(displayValue(item), Math.max(1, width - prefixWidth - 2)), selected ? { foreground: "accent" } : void 0)), width, "");
11182
11513
  }
11183
11514
  #primaryColumnWidth() {
11184
11515
  const widest = this.#filteredItems.reduce((value, item) => Math.max(value, displayWidth5(displayValue(item)) + PRIMARY_COLUMN_GAP), 0);
@@ -11191,7 +11522,7 @@ function displayValue(item) {
11191
11522
  function normalizeDescription(value) {
11192
11523
  return value.replace(/[\r\n]+/gu, " ").trim();
11193
11524
  }
11194
- function truncateText2(value, width) {
11525
+ function truncateText(value, width) {
11195
11526
  return lineText(truncateStyledLine(line(span(value)), width, ""));
11196
11527
  }
11197
11528
  function displayWidth5(value) {
@@ -11214,13 +11545,13 @@ var VStack = class {
11214
11545
  }
11215
11546
  }
11216
11547
  render(context) {
11217
- const lines = [];
11548
+ const lines2 = [];
11218
11549
  let cursor;
11219
11550
  let rowOffset = 0;
11220
11551
  for (const [index, child] of this.#children.entries()) {
11221
11552
  if (index > 0) {
11222
11553
  const gaps = Array.from({ length: this.#gap }, () => plainLine(""));
11223
- lines.push(...gaps);
11554
+ lines2.push(...gaps);
11224
11555
  rowOffset += gaps.length;
11225
11556
  }
11226
11557
  const rendered = child.render(context);
@@ -11230,11 +11561,11 @@ var VStack = class {
11230
11561
  column: rendered.cursor.column
11231
11562
  };
11232
11563
  }
11233
- lines.push(...rendered.lines);
11564
+ lines2.push(...rendered.lines);
11234
11565
  rowOffset += rendered.lines.length;
11235
11566
  }
11236
11567
  return {
11237
- lines,
11568
+ lines: lines2,
11238
11569
  ...cursor === void 0 ? {} : { cursor }
11239
11570
  };
11240
11571
  }
@@ -11480,19 +11811,19 @@ var ViewHost = class {
11480
11811
  this.#overlays = overlays;
11481
11812
  }
11482
11813
  openSelection(request) {
11483
- return new Promise((resolve2) => {
11814
+ return new Promise((resolve3) => {
11484
11815
  const component = this.#selectionComponent(request);
11485
- this.#open({ id: request.id, component, resolve: resolve2 }, "selector");
11816
+ this.#open({ id: request.id, component, resolve: resolve3 }, "selector");
11486
11817
  });
11487
11818
  }
11488
11819
  openPrompt(request) {
11489
- return new Promise((resolve2) => {
11820
+ return new Promise((resolve3) => {
11490
11821
  const component = new AuthDialog({
11491
11822
  request,
11492
11823
  onSubmit: (value) => this.#close(request.id, value),
11493
11824
  onCancel: () => this.#close(request.id, null)
11494
11825
  });
11495
- this.#open({ id: request.id, component, resolve: resolve2 }, "modal");
11826
+ this.#open({ id: request.id, component, resolve: resolve3 }, "modal");
11496
11827
  });
11497
11828
  }
11498
11829
  handleInput(event) {
@@ -11852,7 +12183,7 @@ var InteractiveTerminalLoop = class {
11852
12183
  this.#disableModifyOtherKeys();
11853
12184
  }
11854
12185
  });
11855
- this.#renderer = new PiMainScreenRenderer(driver);
12186
+ this.#renderer = new MainScreenRenderer(driver);
11856
12187
  }
11857
12188
  get editor() {
11858
12189
  return this.#editor;
@@ -11876,6 +12207,12 @@ var InteractiveTerminalLoop = class {
11876
12207
  if (!this.#ui.shouldQueueDisplayEvent(event)) {
11877
12208
  return;
11878
12209
  }
12210
+ if (isRecord5(event) && event.kind === "tool.output_snapshot" && isRecord5(event.payload)) {
12211
+ const stream = event.payload.stream;
12212
+ const previous = this.#work.findIndex((item) => item.type === "event" && isRecord5(item.event) && item.event.kind === event.kind && item.event.correlation_id === event.correlation_id && isRecord5(item.event.payload) && item.event.payload.stream === stream);
12213
+ if (previous >= 0)
12214
+ this.#work.splice(previous, 1);
12215
+ }
11879
12216
  if (this.#work.length >= _a.WORK_QUEUE_LIMIT - 128 && this.#ui.isHighFrequencyDisplayEvent(event)) {
11880
12217
  this.#ui.recordDisplayDrop(event);
11881
12218
  return;
@@ -11967,8 +12304,8 @@ var InteractiveTerminalLoop = class {
11967
12304
  this.#wakeupEnabled = true;
11968
12305
  try {
11969
12306
  if (!this.#exitRequested) {
11970
- await new Promise((resolve2) => {
11971
- this.#exitResolve = resolve2;
12307
+ await new Promise((resolve3) => {
12308
+ this.#exitResolve = resolve3;
11972
12309
  });
11973
12310
  }
11974
12311
  } finally {
@@ -12153,6 +12490,8 @@ var InteractiveTerminalLoop = class {
12153
12490
  }
12154
12491
  if (action === "editor_newline") {
12155
12492
  this.#applyEditorAction(inputAction(InputActionKind.Newline));
12493
+ } else if (action === "delete_to_line_start") {
12494
+ this.#applyEditorAction(inputAction(InputActionKind.DeleteToLineStart));
12156
12495
  } else if (action === "steer_now") {
12157
12496
  this.#applySteerSubmit();
12158
12497
  } else if (action === "submit_follow_up") {
@@ -12294,18 +12633,18 @@ var InteractiveTerminalLoop = class {
12294
12633
  if (this.#closed || !this.#running) {
12295
12634
  return Promise.resolve(null);
12296
12635
  }
12297
- return new Promise((resolve2) => {
12636
+ return new Promise((resolve3) => {
12298
12637
  if (type === "open_selection") {
12299
12638
  this.#work.push({
12300
12639
  type,
12301
12640
  request,
12302
- resolve: resolve2
12641
+ resolve: resolve3
12303
12642
  });
12304
12643
  } else {
12305
12644
  this.#work.push({
12306
12645
  type,
12307
12646
  request,
12308
- resolve: resolve2
12647
+ resolve: resolve3
12309
12648
  });
12310
12649
  }
12311
12650
  this.#scheduleWakeup();
@@ -12352,6 +12691,7 @@ var TerminalUI = class {
12352
12691
  #loop = null;
12353
12692
  #transcript;
12354
12693
  #transcriptView;
12694
+ #lineCompiler = new StyledLineCompiler();
12355
12695
  #showReasoning = true;
12356
12696
  #displayPolicy = new DisplayPolicy({
12357
12697
  audience: "terminal",
@@ -12483,9 +12823,16 @@ var TerminalUI = class {
12483
12823
  this.#sessionId = sessionId;
12484
12824
  this.#loop?.requestRender();
12485
12825
  }
12826
+ setContextUsage(tokens, contextWindow) {
12827
+ this.publishEvent({
12828
+ kind: "ui.context_usage",
12829
+ payload: { context_tokens: tokens, context_window: contextWindow }
12830
+ });
12831
+ }
12486
12832
  replaceTranscript(items) {
12487
12833
  this.#transcript.replace(items);
12488
12834
  this.#transcriptView.invalidate();
12835
+ this.#lineCompiler.invalidate();
12489
12836
  this.#loop?.requestRender();
12490
12837
  }
12491
12838
  setComposerText(text) {
@@ -12611,7 +12958,7 @@ var TerminalUI = class {
12611
12958
  #buildHistoryFrameParts(width) {
12612
12959
  const rendered = this.#transcriptView.renderWithMetadata({ width, theme: this.theme });
12613
12960
  return {
12614
- lines: compileStyledLines(rendered.lines, Math.max(12, width), this.theme),
12961
+ lines: this.#lineCompiler.compile(rendered.lines, Math.max(12, width), this.theme),
12615
12962
  activeStart: rendered.activeStart
12616
12963
  };
12617
12964
  }
@@ -12642,11 +12989,11 @@ var TerminalUI = class {
12642
12989
  effort: this.effort
12643
12990
  })
12644
12991
  }).renderWithMetadata({ width: contentWidth, theme: this.theme });
12645
- const lines = compileStyledLines(rendered.lines.map((line2) => truncateStyledLine(line2, contentWidth, "")), contentWidth, this.theme);
12992
+ const lines2 = this.#lineCompiler.compile(rendered.lines, contentWidth, this.theme);
12646
12993
  return this.#frameBuilder.build({
12647
12994
  ...options,
12648
12995
  compiledMainScreen: {
12649
- lines,
12996
+ lines: lines2,
12650
12997
  cursor: rendered.cursor ?? { row: 0, column: 0 },
12651
12998
  activeStart: rendered.activeStart
12652
12999
  }
@@ -12841,7 +13188,17 @@ var PlainEventSink = class {
12841
13188
  if (text) {
12842
13189
  this.outputFn(`[${correlationId || "unknown"}:${stream}] ${text}`);
12843
13190
  }
13191
+ } else if (kind === "tool.output_snapshot") {
13192
+ return;
12844
13193
  } else if (kind === "tool.finished") {
13194
+ for (const stream of ["stdout", "stderr"]) {
13195
+ if (typeof payload[stream] === "string" && payload[stream] !== "") {
13196
+ this.outputFn(`[${correlationId || "unknown"}:${stream}] ${payload[stream]}`);
13197
+ }
13198
+ }
13199
+ const note = toolOutputNote(payload);
13200
+ if (note)
13201
+ this.outputFn(note);
12845
13202
  const status = String(payload.status ?? "completed");
12846
13203
  this.outputFn(`[tool:${correlationId || "unknown"}] ${status}`);
12847
13204
  } else if (kind === "task.failed") {
@@ -12866,7 +13223,7 @@ function escapeDebugCapture(data) {
12866
13223
  return escaped;
12867
13224
  }
12868
13225
  var StdTerminalDriver = class {
12869
- #rawModeActive = false;
13226
+ #restoreRawMode = null;
12870
13227
  /**
12871
13228
  * Debug capture target from LAOHUANG_DEBUG_LOG: when set, every write is
12872
13229
  * teed to this file with escape sequences made visible (for diagnosing
@@ -12884,8 +13241,7 @@ var StdTerminalDriver = class {
12884
13241
  if (!process.stdin.isTTY) {
12885
13242
  return;
12886
13243
  }
12887
- process.stdin.setRawMode(true);
12888
- this.#rawModeActive = true;
13244
+ this.#restoreRawMode ??= enterTerminalRawMode(process.stdin);
12889
13245
  }
12890
13246
  write(data) {
12891
13247
  if (this.#debugLogPath !== null) {
@@ -12911,10 +13267,9 @@ var StdTerminalDriver = class {
12911
13267
  };
12912
13268
  }
12913
13269
  restore() {
12914
- if (this.#rawModeActive) {
12915
- process.stdin.setRawMode(false);
12916
- this.#rawModeActive = false;
12917
- }
13270
+ const restore = this.#restoreRawMode;
13271
+ this.#restoreRawMode = null;
13272
+ restore?.();
12918
13273
  }
12919
13274
  };
12920
13275
 
@@ -12922,21 +13277,23 @@ var StdTerminalDriver = class {
12922
13277
  import { promises as fs } from "node:fs";
12923
13278
  import { realpathSync as realpathSync3 } from "node:fs";
12924
13279
  import path3 from "node:path";
13280
+ var READ_MAX_BYTES = 50 * 1024;
13281
+ var READ_MAX_LINES = 2e3;
13282
+ var READ_MAX_LINE_CHARACTERS = 2e3;
12925
13283
  var DEFAULT_IO = {
12926
13284
  readFile: (target) => fs.readFile(target, "utf8"),
12927
13285
  writeFile: (target, content) => fs.writeFile(target, content, "utf8")
12928
13286
  };
12929
13287
  var mutationLocks = /* @__PURE__ */ new Map();
12930
13288
  function createFileToolDefinitions(options) {
12931
- const root = resolveNonStrictSync(path3.resolve(options.projectRoot));
13289
+ const root = resolveNonStrictSync(resolveLocalPath(options.projectRoot, process.cwd(), options.pathOptions));
12932
13290
  const io = options.fileIo ?? DEFAULT_IO;
12933
- const maxOutputChars = options.maxOutputChars ?? 2e4;
12934
- const resolvePath2 = (rawPath) => resolveNonStrict(path3.resolve(root, rawPath));
13291
+ const resolvePath = (rawPath) => resolveNonStrict(resolveLocalPath(rawPath, root, options.pathOptions));
12935
13292
  return [
12936
13293
  {
12937
13294
  spec: {
12938
13295
  name: "read",
12939
- description: "Read a UTF-8 text file.",
13296
+ description: "Read a UTF-8 text file, up to 50 KiB of content, 2000 lines, and 2000 Unicode characters per line. has_more reports omitted content; next_offset resumes at the next unread line, or is null at EOF. truncated_line_numbers identifies shortened lines; use Bash to inspect their omitted content.",
12940
13297
  parameters: {
12941
13298
  type: "object",
12942
13299
  properties: {
@@ -12951,8 +13308,9 @@ function createFileToolDefinitions(options) {
12951
13308
  },
12952
13309
  limit: {
12953
13310
  type: "integer",
12954
- description: "Maximum number of lines to return.",
12955
- minimum: 1
13311
+ description: "Maximum number of lines to return (default and maximum: 2000).",
13312
+ minimum: 1,
13313
+ maximum: READ_MAX_LINES
12956
13314
  }
12957
13315
  },
12958
13316
  required: ["path"],
@@ -12965,26 +13323,57 @@ function createFileToolDefinitions(options) {
12965
13323
  ]
12966
13324
  },
12967
13325
  execute: async (args, execution) => {
12968
- const target = await resolvePath2(stringArgument(args, "path"));
13326
+ const target = await resolvePath(stringArgument(args, "path"));
12969
13327
  const offset = optionalPositiveInteger(args, "offset") ?? 1;
12970
- const limit = optionalPositiveInteger(args, "limit");
13328
+ const limit = optionalPositiveInteger(args, "limit") ?? READ_MAX_LINES;
13329
+ if (limit > READ_MAX_LINES) {
13330
+ throw new Error(`limit must be less than or equal to ${READ_MAX_LINES}`);
13331
+ }
12971
13332
  const content = await io.readFile(target);
12972
13333
  if (execution.isCancelled()) {
12973
13334
  return cancelledResult(execution);
12974
13335
  }
12975
- const lines = content === "" ? [] : content.split(/(?<=\n)/);
12976
- const totalLines = lines.length;
13336
+ const lines2 = content === "" ? [] : content.split(/(?<=\n)/);
13337
+ const totalLines = lines2.length;
12977
13338
  if (offset > Math.max(totalLines, 1)) {
12978
13339
  throw new Error(`offset ${offset} is out of range; the file has ${totalLines} lines`);
12979
13340
  }
12980
- const window = lines.slice(offset - 1, limit === void 0 ? void 0 : offset - 1 + limit);
13341
+ const window = [];
13342
+ const truncatedLineNumbers = [];
13343
+ let bytes = 0;
13344
+ for (const line2 of lines2.slice(offset - 1, offset - 1 + limit)) {
13345
+ const ending = line2.endsWith("\r\n") ? "\r\n" : line2.endsWith("\n") ? "\n" : "";
13346
+ const body = line2.slice(0, line2.length - ending.length);
13347
+ let end = 0;
13348
+ let characters = 0;
13349
+ for (const character of body) {
13350
+ if (characters === READ_MAX_LINE_CHARACTERS)
13351
+ break;
13352
+ end += character.length;
13353
+ characters += 1;
13354
+ }
13355
+ const rendered = body.slice(0, end) + ending;
13356
+ const lineBytes = Buffer.byteLength(rendered, "utf8");
13357
+ if (bytes + lineBytes > READ_MAX_BYTES)
13358
+ break;
13359
+ if (end < body.length)
13360
+ truncatedLineNumbers.push(offset + window.length);
13361
+ window.push(rendered);
13362
+ bytes += lineBytes;
13363
+ }
13364
+ const nextOffset = offset - 1 + window.length < totalLines ? offset + window.length : null;
12981
13365
  return withTouchedPath({
12982
13366
  ok: true,
12983
- content: truncateText(window.join(""), maxOutputChars),
13367
+ content: window.join(""),
12984
13368
  offset,
12985
- limit: limit ?? null,
13369
+ limit,
12986
13370
  total_lines: totalLines,
12987
- has_more: offset - 1 + window.length < totalLines
13371
+ has_more: nextOffset !== null || truncatedLineNumbers.length > 0,
13372
+ next_offset: nextOffset,
13373
+ truncated_line_numbers: truncatedLineNumbers,
13374
+ ...truncatedLineNumbers.length > 0 ? {
13375
+ note: "Listed lines were truncated to 2000 Unicode characters. Use Bash to inspect their omitted content; next_offset only continues to later lines."
13376
+ } : {}
12988
13377
  }, target);
12989
13378
  }
12990
13379
  },
@@ -13012,7 +13401,7 @@ function createFileToolDefinitions(options) {
13012
13401
  executionMode: "sequential",
13013
13402
  execute: async (args, execution) => {
13014
13403
  const rawPath = stringArgument(args, "path");
13015
- const target = await resolvePath2(rawPath);
13404
+ const target = await resolvePath(rawPath);
13016
13405
  return await withFileMutationLock(target, async () => {
13017
13406
  if (execution.isCancelled()) {
13018
13407
  return cancelledResult(execution);
@@ -13062,13 +13451,13 @@ function createFileToolDefinitions(options) {
13062
13451
  ]
13063
13452
  },
13064
13453
  executionMode: "sequential",
13065
- execute: async (args, execution) => executeEdit(args, execution, resolvePath2, io)
13454
+ execute: async (args, execution) => executeEdit(args, execution, resolvePath, io)
13066
13455
  }
13067
13456
  ];
13068
13457
  }
13069
- async function executeEdit(args, execution, resolvePath2, io) {
13458
+ async function executeEdit(args, execution, resolvePath, io) {
13070
13459
  const rawPath = stringArgument(args, "path");
13071
- const target = await resolvePath2(rawPath);
13460
+ const target = await resolvePath(rawPath);
13072
13461
  const edits = editsArgument(args);
13073
13462
  return await withFileMutationLock(target, async () => {
13074
13463
  if (execution.isCancelled()) {
@@ -13109,17 +13498,17 @@ async function executeEdit(args, execution, resolvePath2, io) {
13109
13498
  async function withFileMutationLock(key, fn) {
13110
13499
  const previous = mutationLocks.get(key) ?? Promise.resolve();
13111
13500
  let release;
13112
- const current = new Promise((resolve2) => {
13113
- release = resolve2;
13501
+ const current = new Promise((resolve3) => {
13502
+ release = resolve3;
13114
13503
  });
13115
- const tail = previous.then(() => current);
13116
- mutationLocks.set(key, tail);
13504
+ const tail2 = previous.then(() => current);
13505
+ mutationLocks.set(key, tail2);
13117
13506
  await previous;
13118
13507
  try {
13119
13508
  return await fn();
13120
13509
  } finally {
13121
13510
  release();
13122
- if (mutationLocks.get(key) === tail) {
13511
+ if (mutationLocks.get(key) === tail2) {
13123
13512
  mutationLocks.delete(key);
13124
13513
  }
13125
13514
  }
@@ -13198,14 +13587,232 @@ import { promises as fs2 } from "node:fs";
13198
13587
  import { realpathSync as realpathSync4 } from "node:fs";
13199
13588
  import path4 from "node:path";
13200
13589
 
13590
+ // ../../packages/shell/bash-local/dist/bash-path.js
13591
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
13592
+ import { homedir as homedir2 } from "node:os";
13593
+ import { posix as posix2, win32 as win322 } from "node:path";
13594
+ function resolveBashPath(options = {}) {
13595
+ const windows = (options.platform ?? process.platform) === "win32";
13596
+ const paths = windows ? win322 : posix2;
13597
+ const env = options.env ?? process.env;
13598
+ const isExecutable = options.isExecutable ?? ((pathname) => {
13599
+ try {
13600
+ if (!statSync3(pathname).isFile())
13601
+ return false;
13602
+ accessSync(pathname, windows ? constants.F_OK : constants.X_OK);
13603
+ return true;
13604
+ } catch {
13605
+ return false;
13606
+ }
13607
+ });
13608
+ const environment = (name) => {
13609
+ const key = windows ? Object.keys(env).find((entry) => entry.toLowerCase() === name.toLowerCase()) : name;
13610
+ return key === void 0 ? void 0 : env[key];
13611
+ };
13612
+ const isLegacyWsl = (pathname) => windows && /[\\/]windows[\\/](?:system32|sysnative)[\\/]bash\.exe$/i.test(pathname);
13613
+ if (options.shellPath !== void 0) {
13614
+ let explicit = options.shellPath;
13615
+ if (explicit === "~" || explicit.startsWith("~/") || windows && explicit.startsWith("~\\")) {
13616
+ explicit = paths.join(options.homeDirectory ?? homedir2(), explicit.slice(2));
13617
+ }
13618
+ if (!paths.isAbsolute(explicit)) {
13619
+ throw new Error("shell_path must be an absolute Bash path (or start with ~/).");
13620
+ }
13621
+ if (isLegacyWsl(explicit)) {
13622
+ throw new Error("shell_path points to a legacy WSL launcher; select Git for Windows bash.exe instead.");
13623
+ }
13624
+ if (!isExecutable(explicit)) {
13625
+ throw new Error(`Configured Bash is missing or not executable: ${explicit}`);
13626
+ }
13627
+ return explicit;
13628
+ }
13629
+ const candidates = [];
13630
+ if (windows) {
13631
+ for (const name of ["ProgramFiles", "ProgramFiles(x86)"]) {
13632
+ const directory = environment(name);
13633
+ if (directory && paths.isAbsolute(directory)) {
13634
+ candidates.push(paths.join(directory, "Git", "bin", "bash.exe"));
13635
+ }
13636
+ }
13637
+ } else {
13638
+ candidates.push("/bin/bash");
13639
+ }
13640
+ for (const entry of (environment("PATH") ?? "").split(paths.delimiter)) {
13641
+ const directory = windows && entry.startsWith('"') && entry.endsWith('"') ? entry.slice(1, -1) : entry;
13642
+ if (paths.isAbsolute(directory)) {
13643
+ candidates.push(paths.join(directory, windows ? "bash.exe" : "bash"));
13644
+ }
13645
+ }
13646
+ for (const candidate of new Set(candidates)) {
13647
+ if (!isLegacyWsl(candidate) && isExecutable(candidate))
13648
+ return candidate;
13649
+ }
13650
+ throw new Error(windows ? "Bash not found. Install Git for Windows, add its bin directory to PATH, or set shell_path in config.json." : "Bash not found. Install Bash, add its directory to PATH, or set shell_path in config.json.");
13651
+ }
13652
+
13653
+ // ../../packages/shell/bash-local/dist/bash-output.js
13654
+ import { mkdir, mkdtemp, open, readdir, rm, stat, writeFile } from "node:fs/promises";
13655
+ import { homedir as homedir3 } from "node:os";
13656
+ import { join as join6, resolve as resolve2 } from "node:path";
13657
+ var BASH_OUTPUT_MAX_BYTES = 50 * 1024;
13658
+ var BASH_OUTPUT_MAX_LINES = 2e3;
13659
+ var OUTPUT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
13660
+ var OUTPUT_FILE_MAX_BYTES = 64 * 1024 * 1024;
13661
+ function lines(text) {
13662
+ return text === "" ? 0 : text.split("\n").length;
13663
+ }
13664
+ function tail(value, maxBytes, maxLines) {
13665
+ if (maxBytes <= 0 || maxLines <= 0)
13666
+ return { text: "", startMidLine: value.text !== "" || value.startMidLine };
13667
+ const bytes = Buffer.from(value.text);
13668
+ let start = Math.max(0, bytes.length - maxBytes);
13669
+ while (start < bytes.length && (bytes[start] & 192) === 128)
13670
+ start++;
13671
+ let text = bytes.subarray(start).toString("utf8");
13672
+ let startMidLine = start > 0 ? bytes[start - 1] !== 10 : value.startMidLine;
13673
+ const parts = text.split("\n");
13674
+ if (parts.length > maxLines) {
13675
+ text = parts.slice(-maxLines).join("\n");
13676
+ startMidLine = false;
13677
+ }
13678
+ return { text, startMidLine };
13679
+ }
13680
+ var BashOutput = class {
13681
+ maxBytes;
13682
+ maxLines;
13683
+ #directory;
13684
+ #fileLimit;
13685
+ #tails = {
13686
+ stdout: { text: "", startMidLine: false },
13687
+ stderr: { text: "", startMidLine: false }
13688
+ };
13689
+ #totals = { stdout: 0, stderr: 0 };
13690
+ #handles = {};
13691
+ #pending = Promise.resolve();
13692
+ #archiveDirectory = null;
13693
+ #fileBytes = 0;
13694
+ #spillAttempted = false;
13695
+ files = null;
13696
+ fileError = null;
13697
+ revision = 0;
13698
+ constructor(options = {}) {
13699
+ this.maxBytes = options.maxOutputBytes ?? BASH_OUTPUT_MAX_BYTES;
13700
+ this.maxLines = options.maxOutputLines ?? BASH_OUTPUT_MAX_LINES;
13701
+ this.#fileLimit = options.maxOutputFileBytes ?? OUTPUT_FILE_MAX_BYTES;
13702
+ for (const limit of [this.maxBytes, this.maxLines, this.#fileLimit]) {
13703
+ if (!Number.isSafeInteger(limit) || limit < 1)
13704
+ throw new RangeError("Bash output limits must be positive integers");
13705
+ }
13706
+ this.#directory = resolve2(options.outputDirectory ?? join6(homedir3(), ".laohuang", "tool-output"));
13707
+ }
13708
+ append(stream, text) {
13709
+ const next = this.#pending.then(() => this.#append(stream, text));
13710
+ this.#pending = next.catch(() => {
13711
+ });
13712
+ return next;
13713
+ }
13714
+ async #append(stream, text) {
13715
+ if (!text)
13716
+ return;
13717
+ const combined = { ...this.#tails[stream], text: this.#tails[stream].text + text };
13718
+ const other = stream === "stdout" ? "stderr" : "stdout";
13719
+ if (!this.#spillAttempted && (Buffer.byteLength(combined.text) + Buffer.byteLength(this.#tails[other].text) > this.maxBytes || lines(combined.text) + lines(this.#tails[other].text) > this.maxLines)) {
13720
+ this.#spillAttempted = true;
13721
+ await this.#startArchive();
13722
+ for (const name of ["stdout", "stderr"])
13723
+ await this.#write(name, this.#tails[name].text);
13724
+ }
13725
+ await this.#write(stream, text);
13726
+ this.#totals[stream] += Buffer.byteLength(text);
13727
+ this.#tails[stream] = tail(combined, this.maxBytes, this.maxLines);
13728
+ this.revision++;
13729
+ }
13730
+ snapshot() {
13731
+ const reserved = tail(this.#tails.stderr, Math.floor(this.maxBytes / 2), Math.floor(this.maxLines / 2));
13732
+ const stdout = tail(this.#tails.stdout, this.maxBytes - Buffer.byteLength(reserved.text), this.maxLines - lines(reserved.text));
13733
+ const stderr = tail(this.#tails.stderr, this.maxBytes - Buffer.byteLength(stdout.text), this.maxLines - lines(stdout.text));
13734
+ return {
13735
+ stdout: { ...stdout, truncated: Buffer.byteLength(stdout.text) < this.#totals.stdout },
13736
+ stderr: { ...stderr, truncated: Buffer.byteLength(stderr.text) < this.#totals.stderr }
13737
+ };
13738
+ }
13739
+ async #startArchive() {
13740
+ try {
13741
+ await mkdir(this.#directory, { recursive: true, mode: 448 });
13742
+ await this.#removeExpired();
13743
+ this.#archiveDirectory = await mkdtemp(join6(this.#directory, "bash-"));
13744
+ const files = {
13745
+ stdout: join6(this.#archiveDirectory, "stdout.log"),
13746
+ stderr: join6(this.#archiveDirectory, "stderr.log")
13747
+ };
13748
+ for (const stream of ["stdout", "stderr"])
13749
+ this.#handles[stream] = await open(files[stream], "wx", 384);
13750
+ this.files = files;
13751
+ } catch (error) {
13752
+ this.fileError = `Cannot save Bash output: ${String(error)}`;
13753
+ }
13754
+ }
13755
+ async #write(stream, text) {
13756
+ const handle = this.#handles[stream];
13757
+ if (!handle || this.fileError !== null || text === "")
13758
+ return;
13759
+ try {
13760
+ const bytes = Buffer.from(text);
13761
+ const available = Math.max(0, this.#fileLimit - this.#fileBytes);
13762
+ let end = Math.min(bytes.length, available);
13763
+ while (end > 0 && end < bytes.length && (bytes[end] & 192) === 128)
13764
+ end--;
13765
+ if (end > 0)
13766
+ await handle.writeFile(bytes.subarray(0, end));
13767
+ this.#fileBytes += end;
13768
+ if (end < bytes.length)
13769
+ this.fileError = `Bash output file limit reached (${this.#fileLimit} bytes); files contain partial output`;
13770
+ } catch (error) {
13771
+ this.fileError = `Cannot save Bash output: ${String(error)}`;
13772
+ }
13773
+ }
13774
+ async close() {
13775
+ await this.#pending;
13776
+ for (const handle of Object.values(this.#handles)) {
13777
+ try {
13778
+ await handle.close();
13779
+ } catch (error) {
13780
+ this.fileError ??= `Cannot close Bash output: ${String(error)}`;
13781
+ }
13782
+ }
13783
+ if (this.#archiveDirectory !== null) {
13784
+ try {
13785
+ await writeFile(join6(this.#archiveDirectory, "completed"), "", { mode: 384 });
13786
+ } catch {
13787
+ }
13788
+ }
13789
+ }
13790
+ async #removeExpired() {
13791
+ for (const entry of await readdir(this.#directory, { withFileTypes: true })) {
13792
+ if (!entry.isDirectory() || !entry.name.startsWith("bash-"))
13793
+ continue;
13794
+ const directory = join6(this.#directory, entry.name);
13795
+ try {
13796
+ if (Date.now() - (await stat(join6(directory, "completed"))).mtimeMs > OUTPUT_RETENTION_MS) {
13797
+ await rm(directory, { recursive: true, force: true });
13798
+ }
13799
+ } catch {
13800
+ }
13801
+ }
13802
+ }
13803
+ };
13804
+
13201
13805
  // ../../packages/shell/bash-local/dist/bash-runner.js
13202
- import { spawn } from "node:child_process";
13806
+ import { execFile, spawn } from "node:child_process";
13203
13807
  import { constants as osConstants } from "node:os";
13808
+ import { win32 as win323 } from "node:path";
13204
13809
  import { StringDecoder } from "node:string_decoder";
13205
- var FLUSH_INTERVAL_MS = 40;
13206
- var FLUSH_CHARS = 4096;
13810
+ var FLUSH_INTERVAL_MS = 100;
13207
13811
  var TERMINATION_GRACE_MS = 2e3;
13208
13812
  var WATCHDOG_INTERVAL_MS = 10;
13813
+ var POST_EXIT_IDLE_MS = 100;
13814
+ var TERMINATION_EXIT_GRACE_MS = 1e3;
13815
+ var TASKKILL_TIMEOUT_MS = 5e3;
13209
13816
  var MODEL_API_KEY_ENV_NAMES = [
13210
13817
  "OPENAI_API_KEY",
13211
13818
  "DEEPSEEK_API_KEY",
@@ -13246,11 +13853,10 @@ var ToolExecutionContext = class {
13246
13853
  if (sink == null)
13247
13854
  return;
13248
13855
  if (typeof sink === "function") {
13249
- sink(kind, payload);
13250
- return;
13856
+ return sink(kind, payload);
13251
13857
  }
13252
13858
  if (typeof sink.publish === "function") {
13253
- sink.publish(kind, {
13859
+ return sink.publish(kind, {
13254
13860
  source: "tool",
13255
13861
  session_id: this.sessionId ?? "local",
13256
13862
  task_id: this.taskId,
@@ -13268,6 +13874,12 @@ var BashResult = class {
13268
13874
  error;
13269
13875
  durationMs;
13270
13876
  truncated;
13877
+ outputComplete;
13878
+ outputFiles;
13879
+ outputFileComplete;
13880
+ outputFileError;
13881
+ stdoutStartMidLine;
13882
+ stderrStartMidLine;
13271
13883
  constructor(init) {
13272
13884
  this.status = init.status;
13273
13885
  this.stdout = init.stdout ?? "";
@@ -13276,6 +13888,12 @@ var BashResult = class {
13276
13888
  this.error = init.error ?? null;
13277
13889
  this.durationMs = init.durationMs ?? 0;
13278
13890
  this.truncated = init.truncated ?? false;
13891
+ this.outputComplete = init.outputComplete ?? true;
13892
+ this.outputFiles = init.outputFiles ?? null;
13893
+ this.outputFileComplete = init.outputFileComplete ?? false;
13894
+ this.outputFileError = init.outputFileError ?? null;
13895
+ this.stdoutStartMidLine = init.stdoutStartMidLine ?? false;
13896
+ this.stderrStartMidLine = init.stderrStartMidLine ?? false;
13279
13897
  }
13280
13898
  get ok() {
13281
13899
  return this.status === "completed" && this.exitCode === 0;
@@ -13288,66 +13906,21 @@ var BashResult = class {
13288
13906
  stdout: this.stdout,
13289
13907
  stderr: this.stderr,
13290
13908
  duration_ms: this.durationMs,
13291
- truncated: this.truncated
13909
+ truncated: this.truncated,
13910
+ output_complete: this.outputComplete,
13911
+ output_files: this.outputFiles,
13912
+ output_file_complete: this.outputFileComplete,
13913
+ stdout_start_mid_line: this.stdoutStartMidLine,
13914
+ stderr_start_mid_line: this.stderrStartMidLine
13292
13915
  };
13916
+ if (this.outputFileError)
13917
+ result["output_file_error"] = this.outputFileError;
13293
13918
  if (this.error) {
13294
13919
  result["error"] = this.error;
13295
13920
  }
13296
13921
  return result;
13297
13922
  }
13298
13923
  };
13299
- var BoundedOutput = class {
13300
- limit;
13301
- headLimit;
13302
- tailLimit;
13303
- totalChars = 0;
13304
- whole = "";
13305
- head = "";
13306
- tail = "";
13307
- isTruncated = false;
13308
- constructor(limit) {
13309
- this.limit = Math.max(0, Math.trunc(limit));
13310
- this.headLimit = Math.trunc(this.limit * 0.4);
13311
- this.tailLimit = this.limit - this.headLimit;
13312
- }
13313
- append(text) {
13314
- if (!text)
13315
- return;
13316
- this.totalChars += text.length;
13317
- if (this.limit === 0) {
13318
- this.isTruncated = true;
13319
- return;
13320
- }
13321
- if (!this.isTruncated && this.whole.length + text.length <= this.limit) {
13322
- this.whole += text;
13323
- return;
13324
- }
13325
- if (!this.isTruncated) {
13326
- const combined = this.whole + text;
13327
- this.head = combined.slice(0, this.headLimit);
13328
- this.tail = this.tailLimit > 0 ? combined.slice(-this.tailLimit) : "";
13329
- this.whole = "";
13330
- this.isTruncated = true;
13331
- return;
13332
- }
13333
- if (this.tailLimit > 0) {
13334
- this.tail = (this.tail + text).slice(-this.tailLimit);
13335
- }
13336
- }
13337
- render() {
13338
- if (!this.isTruncated)
13339
- return this.whole;
13340
- const retained = this.head.length + this.tail.length;
13341
- const omitted = Math.max(0, this.totalChars - retained);
13342
- const marker = `
13343
- ...[truncated ${omitted} chars]...
13344
- `;
13345
- return this.head + marker + this.tail;
13346
- }
13347
- get truncated() {
13348
- return this.isTruncated;
13349
- }
13350
- };
13351
13924
  var TerminalSanitizer = class {
13352
13925
  state = "normal";
13353
13926
  feed(text) {
@@ -13402,15 +13975,31 @@ var TerminalSanitizer = class {
13402
13975
  }
13403
13976
  };
13404
13977
  function sleep(ms) {
13405
- return new Promise((resolve2) => setTimeout(resolve2, ms));
13978
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
13406
13979
  }
13407
13980
  function errnoCode(error) {
13408
13981
  return error?.code;
13409
13982
  }
13410
- async function terminateProcessGroup(child, graceMs = TERMINATION_GRACE_MS) {
13983
+ async function terminateProcessTree(child, graceMs = TERMINATION_GRACE_MS) {
13411
13984
  const pid = child.pid;
13412
13985
  if (pid === void 0)
13413
13986
  return;
13987
+ if (process.platform === "win32") {
13988
+ const taskkill = win323.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe");
13989
+ await new Promise((resolve3, reject) => {
13990
+ execFile(taskkill, ["/F", "/T", "/PID", String(pid)], {
13991
+ windowsHide: true,
13992
+ timeout: TASKKILL_TIMEOUT_MS,
13993
+ killSignal: "SIGKILL"
13994
+ }, (error) => {
13995
+ if (error)
13996
+ reject(new Error(`taskkill failed: ${error.message}`));
13997
+ else
13998
+ resolve3();
13999
+ });
14000
+ });
14001
+ return;
14002
+ }
13414
14003
  try {
13415
14004
  process.kill(-pid, "SIGTERM");
13416
14005
  } catch (error) {
@@ -13463,25 +14052,55 @@ function toExitCode(code, signal) {
13463
14052
  async function runBash(command, options) {
13464
14053
  const invokedAt = performance.now();
13465
14054
  const execution = options.context ?? new ToolExecutionContext();
14055
+ const output = new BashOutput(options);
14056
+ let publicationError = null;
14057
+ const publish = async (kind, payload) => {
14058
+ try {
14059
+ await execution.publish(kind, payload);
14060
+ } catch (error) {
14061
+ publicationError ??= `Bash event publication failed: ${String(error)}`;
14062
+ }
14063
+ };
14064
+ const finish = async (result2) => {
14065
+ await publish("tool.finished", result2.asDict());
14066
+ if (publicationError !== null) {
14067
+ result2.status = "failed";
14068
+ if (!result2.error?.includes(publicationError)) {
14069
+ result2.error = [result2.error, publicationError].filter(Boolean).join("; ");
14070
+ }
14071
+ }
14072
+ return result2;
14073
+ };
13466
14074
  if (execution.isCancelled()) {
13467
14075
  const result2 = new BashResult({
13468
14076
  status: "cancelled",
13469
14077
  error: execution.cancellationReason,
13470
14078
  durationMs: Math.trunc(performance.now() - invokedAt)
13471
14079
  });
13472
- execution.publish("tool.finished", result2.asDict());
13473
- return result2;
14080
+ return await finish(result2);
13474
14081
  }
13475
14082
  const environment = cleanEnvironment(options.env ?? process.env);
13476
- const child = spawn("/bin/bash", ["-lc", command], {
13477
- cwd: options.cwd,
13478
- env: environment,
13479
- stdio: ["ignore", "pipe", "pipe"],
13480
- detached: true
13481
- });
13482
- const spawnError = await new Promise((resolve2) => {
13483
- child.once("spawn", () => resolve2(null));
13484
- child.once("error", (error) => resolve2(error));
14083
+ let child;
14084
+ try {
14085
+ const shellPath = resolveBashPath({ shellPath: options.shellPath, env: environment });
14086
+ child = spawn(shellPath, ["-lc", command], {
14087
+ cwd: options.cwd,
14088
+ env: environment,
14089
+ stdio: ["ignore", "pipe", "pipe"],
14090
+ detached: process.platform !== "win32",
14091
+ windowsHide: true
14092
+ });
14093
+ } catch (error) {
14094
+ const result2 = new BashResult({
14095
+ status: "spawn_failed",
14096
+ error: String(error),
14097
+ durationMs: Math.trunc(performance.now() - invokedAt)
14098
+ });
14099
+ return await finish(result2);
14100
+ }
14101
+ const spawnError = await new Promise((resolve3) => {
14102
+ child.once("spawn", () => resolve3(null));
14103
+ child.once("error", (error) => resolve3(error));
13485
14104
  });
13486
14105
  if (spawnError) {
13487
14106
  const result2 = new BashResult({
@@ -13489,76 +14108,130 @@ async function runBash(command, options) {
13489
14108
  error: String(spawnError),
13490
14109
  durationMs: Math.trunc(performance.now() - invokedAt)
13491
14110
  });
13492
- execution.publish("tool.finished", result2.asDict());
13493
- return result2;
14111
+ return await finish(result2);
13494
14112
  }
13495
14113
  child.on("error", () => {
13496
14114
  });
13497
- execution.publish("tool.started", {
13498
- name: "bash",
13499
- arguments: { command }
13500
- });
13501
- const outputs = {
13502
- stdout: new BoundedOutput(options.maxOutputChars),
13503
- stderr: new BoundedOutput(options.maxOutputChars)
14115
+ const started = publish("tool.started", { name: "bash", arguments: { command } });
14116
+ let publishing = null;
14117
+ let publishedRevision = 0;
14118
+ let exited = false;
14119
+ let lastOutputAt = performance.now();
14120
+ let outputIncomplete = false;
14121
+ const ingesting = { stdout: false, stderr: false };
14122
+ const closeOutput = (force = false) => {
14123
+ for (const stream of ["stdout", "stderr"]) {
14124
+ const pipe = child[stream];
14125
+ if (!force && (ingesting[stream] || (pipe?.readableLength ?? 0) > 0))
14126
+ continue;
14127
+ if (pipe && !pipe.readableEnded && !pipe.destroyed) {
14128
+ outputIncomplete = true;
14129
+ pipe.destroy();
14130
+ }
14131
+ }
13504
14132
  };
13505
- const pending = { stdout: "", stderr: "" };
13506
- const streamSequence = { stdout: 0, stderr: 0 };
13507
- const flush = (stream) => {
13508
- const text = pending[stream];
13509
- if (!text)
14133
+ const flush = () => {
14134
+ if (publishing !== null || publicationError !== null || publishedRevision === output.revision)
13510
14135
  return;
13511
- pending[stream] = "";
13512
- outputs[stream].append(text);
13513
- streamSequence[stream] += 1;
13514
- execution.publish("tool.output_delta", {
13515
- name: "bash",
13516
- stream,
13517
- text,
13518
- stream_sequence: streamSequence[stream]
14136
+ const snapshot2 = output.snapshot();
14137
+ publishedRevision = output.revision;
14138
+ const revision = publishedRevision;
14139
+ publishing = (async () => {
14140
+ await started;
14141
+ for (const stream of ["stdout", "stderr"]) {
14142
+ if (publicationError !== null)
14143
+ break;
14144
+ await publish("tool.output_snapshot", {
14145
+ name: "bash",
14146
+ stream,
14147
+ text: snapshot2[stream].text,
14148
+ stream_sequence: revision,
14149
+ truncated: snapshot2[stream].truncated,
14150
+ start_mid_line: snapshot2[stream].startMidLine
14151
+ });
14152
+ }
14153
+ })().finally(() => {
14154
+ publishing = null;
13519
14155
  });
13520
14156
  };
13521
14157
  const readPipe = async (stream, pipe) => {
13522
14158
  const decoder = new StringDecoder("utf8");
13523
14159
  const sanitizer = new TerminalSanitizer();
13524
- const ingest = (text) => {
13525
- if (!text)
13526
- return;
13527
- pending[stream] += text;
13528
- if (pending[stream].length >= FLUSH_CHARS)
13529
- flush(stream);
13530
- };
13531
14160
  try {
13532
14161
  for await (const chunk of pipe) {
13533
- ingest(sanitizer.feed(decoder.write(chunk)));
14162
+ lastOutputAt = performance.now();
14163
+ ingesting[stream] = true;
14164
+ try {
14165
+ await output.append(stream, sanitizer.feed(decoder.write(chunk)));
14166
+ } finally {
14167
+ ingesting[stream] = false;
14168
+ lastOutputAt = performance.now();
14169
+ }
13534
14170
  }
13535
- ingest(sanitizer.feed(decoder.end()));
13536
14171
  } catch {
14172
+ outputIncomplete = true;
14173
+ } finally {
14174
+ await output.append(stream, sanitizer.feed(decoder.end()));
13537
14175
  }
13538
14176
  };
13539
14177
  const stdoutDone = readPipe("stdout", child.stdout);
13540
14178
  const stderrDone = readPipe("stderr", child.stderr);
13541
- const exitInfo = new Promise((resolve2) => {
13542
- child.once("exit", (code2, signal2) => resolve2({ code: code2, signal: signal2 }));
14179
+ let resolveExit;
14180
+ const exitInfo = new Promise((resolve3) => {
14181
+ resolveExit = resolve3;
13543
14182
  });
14183
+ const onExit = (code2, signal2) => {
14184
+ exited = true;
14185
+ lastOutputAt = performance.now();
14186
+ resolveExit({ code: code2, signal: signal2 });
14187
+ };
14188
+ child.once("exit", onExit);
13544
14189
  let terminalStatus = null;
13545
14190
  const startedAt = performance.now();
13546
14191
  let lastFlush = startedAt;
13547
14192
  const timeoutMs = options.timeout * 1e3;
14193
+ let termination = null;
14194
+ let terminationCompletedAt = null;
14195
+ let cleanupError = null;
14196
+ const terminate = () => {
14197
+ if (termination !== null)
14198
+ return;
14199
+ termination = terminateProcessTree(child).catch((error) => {
14200
+ cleanupError = error instanceof Error ? error.message : String(error);
14201
+ try {
14202
+ child.kill("SIGKILL");
14203
+ } catch {
14204
+ }
14205
+ }).finally(() => {
14206
+ terminationCompletedAt = performance.now();
14207
+ });
14208
+ };
13548
14209
  const watchdog = setInterval(() => {
13549
14210
  const now = performance.now();
13550
14211
  if (terminalStatus === null) {
13551
- if (execution.isCancelled()) {
14212
+ if (publicationError !== null) {
14213
+ terminalStatus = "failed";
14214
+ terminate();
14215
+ } else if (execution.isCancelled()) {
13552
14216
  terminalStatus = "cancelled";
13553
- void terminateProcessGroup(child);
14217
+ terminate();
13554
14218
  } else if (options.timeout >= 0 && now - startedAt >= timeoutMs) {
13555
14219
  terminalStatus = "timed_out";
13556
- void terminateProcessGroup(child);
14220
+ terminate();
14221
+ }
14222
+ }
14223
+ if (exited && now - lastOutputAt >= POST_EXIT_IDLE_MS)
14224
+ closeOutput();
14225
+ if (terminationCompletedAt !== null && now - terminationCompletedAt >= TERMINATION_EXIT_GRACE_MS) {
14226
+ closeOutput(true);
14227
+ if (!exited) {
14228
+ cleanupError ??= "Bash did not exit after process termination";
14229
+ child.unref();
14230
+ resolveExit({ code: null, signal: null });
13557
14231
  }
13558
14232
  }
13559
14233
  if (now - lastFlush >= FLUSH_INTERVAL_MS) {
13560
- flush("stdout");
13561
- flush("stderr");
14234
+ flush();
13562
14235
  lastFlush = now;
13563
14236
  }
13564
14237
  }, WATCHDOG_INTERVAL_MS);
@@ -13567,18 +14240,21 @@ async function runBash(command, options) {
13567
14240
  await Promise.all([stdoutDone, stderrDone, exitInfo]);
13568
14241
  } finally {
13569
14242
  clearInterval(watchdog);
13570
- if (child.exitCode === null && child.signalCode === null) {
13571
- await terminateProcessGroup(child);
13572
- }
13573
- }
13574
- flush("stdout");
13575
- flush("stderr");
14243
+ child.removeListener("exit", onExit);
14244
+ await termination;
14245
+ await output.close();
14246
+ }
14247
+ await started;
14248
+ await publishing;
14249
+ flush();
14250
+ await publishing;
13576
14251
  const { code, signal } = await exitInfo;
13577
14252
  const exitCode = toExitCode(code, signal);
13578
- const stdout = outputs.stdout.render();
13579
- const stderr = outputs.stderr.render();
14253
+ const snapshot = output.snapshot();
14254
+ const stdout = snapshot.stdout.text;
14255
+ const stderr = snapshot.stderr.text;
13580
14256
  const durationMs = Math.trunc(performance.now() - invokedAt);
13581
- const truncated = outputs.stdout.truncated || outputs.stderr.truncated;
14257
+ const truncated = snapshot.stdout.truncated || snapshot.stderr.truncated;
13582
14258
  let result;
13583
14259
  if (terminalStatus === "cancelled") {
13584
14260
  result = new BashResult({
@@ -13620,20 +14296,35 @@ async function runBash(command, options) {
13620
14296
  truncated
13621
14297
  });
13622
14298
  }
13623
- execution.publish("tool.finished", result.asDict());
13624
- return result;
14299
+ if (cleanupError !== null) {
14300
+ result.error = `${result.error ?? "Bash execution failed"}; Process cleanup failed: ${cleanupError}`;
14301
+ }
14302
+ result.outputComplete = !outputIncomplete;
14303
+ result.outputFiles = output.files;
14304
+ result.outputFileComplete = output.files !== null && output.fileError === null && !outputIncomplete;
14305
+ result.outputFileError = output.fileError;
14306
+ result.stdoutStartMidLine = snapshot.stdout.startMidLine;
14307
+ result.stderrStartMidLine = snapshot.stderr.startMidLine;
14308
+ if (publicationError !== null) {
14309
+ result.status = "failed";
14310
+ result.error = publicationError + (cleanupError === null ? "" : `; Process cleanup failed: ${cleanupError}`);
14311
+ }
14312
+ return await finish(result);
13625
14313
  }
13626
14314
 
13627
14315
  // ../../packages/shell/tool-bash/dist/tool-bash.js
13628
14316
  function createBashToolDefinition(options) {
13629
- const root = resolveNonStrictSync2(path4.resolve(options.projectRoot));
14317
+ const pathOptions = {
14318
+ env: options.env ?? process.env,
14319
+ shellPath: () => resolveBashPath({ shellPath: options.shellPath, env: options.env })
14320
+ };
14321
+ const root = resolveNonStrictSync2(resolveLocalPath(options.projectRoot, process.cwd(), pathOptions));
13630
14322
  const bashTimeoutSeconds = options.bashTimeoutSeconds ?? 120;
13631
- const maxOutputChars = options.maxOutputChars ?? 2e4;
13632
14323
  const runBash2 = options.runBash ?? defaultRunBash;
13633
14324
  return {
13634
14325
  spec: {
13635
14326
  name: "bash",
13636
- description: "Run a Bash command.",
14327
+ description: "Run a Bash command. Returns stdout and stderr, limited together to the latest 2000 lines or 50KiB by default. When truncated, output_files contains saved output paths; check output_file_complete before assuming the files are complete.",
13637
14328
  parameters: {
13638
14329
  type: "object",
13639
14330
  properties: {
@@ -13659,39 +14350,52 @@ function createBashToolDefinition(options) {
13659
14350
  "Supply a concise description of what the command does.",
13660
14351
  "Use workdir instead of cd.",
13661
14352
  "Each call runs in an independent shell; state does not persist between calls.",
13662
- "On a non-zero exit, inspect the output before retrying."
14353
+ "On a non-zero exit, inspect the output before retrying.",
14354
+ "When output is truncated, use read or a targeted Bash search on output_files instead of repeating the command. Saved output expires after 7 days; files are capped at 64MiB per call."
13663
14355
  ]
13664
14356
  },
13665
14357
  execute: async (args, execution) => {
13666
14358
  const command = stringArgument(args, "command");
13667
14359
  stringArgument(args, "description");
13668
14360
  const workdir = args["workdir"];
13669
- const cwd = workdir === void 0 ? root : await resolvePath(root, stringArgument(args, "workdir"));
14361
+ const cwd = workdir === void 0 ? root : await resolveNonStrict2(resolveLocalPath(stringArgument(args, "workdir"), root, pathOptions));
13670
14362
  const timeoutMs = optionalPositiveInteger(args, "timeoutMs");
13671
14363
  return await runBash2(command, {
13672
14364
  cwd,
14365
+ shellPath: options.shellPath,
13673
14366
  timeoutSeconds: timeoutMs === void 0 ? bashTimeoutSeconds : timeoutMs / 1e3,
13674
- maxOutputChars,
14367
+ maxOutputBytes: options.maxOutputBytes,
14368
+ maxOutputLines: options.maxOutputLines,
14369
+ outputDirectory: options.outputDirectory,
14370
+ maxOutputFileBytes: options.maxOutputFileBytes,
13675
14371
  context: execution,
13676
- env: process.env
14372
+ env: options.env ?? process.env
13677
14373
  });
13678
14374
  }
13679
14375
  };
13680
14376
  }
13681
14377
  async function defaultRunBash(command, options) {
13682
- const context = typeof options.context.publish === "function" ? options.context : null;
14378
+ const execution = options.context;
14379
+ const context = {
14380
+ isCancelled: () => execution.isCancelled(),
14381
+ get cancellationReason() {
14382
+ return execution.cancellationReason;
14383
+ },
14384
+ publish: (kind, payload) => execution.publish?.(kind, payload)
14385
+ };
13683
14386
  const result = await runBash(command, {
13684
14387
  cwd: options.cwd,
14388
+ shellPath: options.shellPath,
13685
14389
  timeout: options.timeoutSeconds,
13686
- maxOutputChars: options.maxOutputChars,
14390
+ maxOutputBytes: options.maxOutputBytes,
14391
+ maxOutputLines: options.maxOutputLines,
14392
+ outputDirectory: options.outputDirectory,
14393
+ maxOutputFileBytes: options.maxOutputFileBytes,
13687
14394
  context,
13688
14395
  env: options.env
13689
14396
  });
13690
14397
  return result.asDict();
13691
14398
  }
13692
- async function resolvePath(root, rawPath) {
13693
- return await resolveNonStrict2(path4.resolve(root, rawPath));
13694
- }
13695
14399
  async function resolveNonStrict2(pathname) {
13696
14400
  const missing = [];
13697
14401
  let current = pathname;
@@ -13963,23 +14667,13 @@ function sessionDisplayTitle(session) {
13963
14667
  return lastUserText2 === "" ? "Untitled session" : lastUserText2;
13964
14668
  }
13965
14669
  function sessionDisplayDescription(session, options) {
13966
- const displayPath = displayPathFor(session.cwd ?? session.projectRoot ?? "", options.homeDirectory);
14670
+ const displayPath = displayLocalPath(session.cwd ?? session.projectRoot ?? "", options.homeDirectory);
13967
14671
  const updated = relativeTimeLabel(session.updatedAt, options.now);
13968
14672
  return displayPath === "" ? updated : `${updated} ${displayPath}`;
13969
14673
  }
13970
14674
  function cleanSingleLine(value) {
13971
14675
  return value?.replace(/\s+/g, " ").trim() ?? "";
13972
14676
  }
13973
- function displayPathFor(path5, homeDirectory) {
13974
- if (path5 === "" || homeDirectory === null || homeDirectory === "") {
13975
- return path5;
13976
- }
13977
- const home = homeDirectory.endsWith("/") ? homeDirectory.slice(0, -1) : homeDirectory;
13978
- if (path5 === home) {
13979
- return "~";
13980
- }
13981
- return path5.startsWith(`${home}/`) ? `~/${path5.slice(home.length + 1)}` : path5;
13982
- }
13983
14677
  function relativeTimeLabel(updatedAt, now) {
13984
14678
  const updated = new Date(updatedAt);
13985
14679
  const timestamp = updated.getTime();
@@ -15595,7 +16289,7 @@ function defaultSecretInputFn(prompt) {
15595
16289
  }
15596
16290
  const byte = Buffer.alloc(1);
15597
16291
  let answer = "";
15598
- process.stdin.setRawMode(true);
16292
+ const restoreRawMode = enterTerminalRawMode(process.stdin);
15599
16293
  try {
15600
16294
  for (; ; ) {
15601
16295
  const bytesRead = readSync2(0, byte, 0, 1, null);
@@ -15621,7 +16315,7 @@ function defaultSecretInputFn(prompt) {
15621
16315
  answer += byte.toString("utf8", 0, 1);
15622
16316
  }
15623
16317
  } finally {
15624
- process.stdin.setRawMode(false);
16318
+ restoreRawMode();
15625
16319
  }
15626
16320
  process.stderr.write("\n");
15627
16321
  return answer;
@@ -15790,8 +16484,8 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
15790
16484
  const done = (async () => {
15791
16485
  for (; ; ) {
15792
16486
  if (queue.length === 0) {
15793
- await new Promise((resolve2) => {
15794
- wake = resolve2;
16487
+ await new Promise((resolve3) => {
16488
+ wake = resolve3;
15795
16489
  });
15796
16490
  wake = null;
15797
16491
  }
@@ -15837,14 +16531,14 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
15837
16531
  };
15838
16532
  }
15839
16533
  function delay(ms) {
15840
- return new Promise((resolve2) => {
15841
- setTimeout(resolve2, ms);
16534
+ return new Promise((resolve3) => {
16535
+ setTimeout(resolve3, ms);
15842
16536
  });
15843
16537
  }
15844
16538
  async function settlesWithin(promise, ms) {
15845
16539
  let timer;
15846
- const timeout = new Promise((resolve2) => {
15847
- timer = setTimeout(() => resolve2(false), ms);
16540
+ const timeout = new Promise((resolve3) => {
16541
+ timer = setTimeout(() => resolve3(false), ms);
15848
16542
  });
15849
16543
  const settled = promise.then(
15850
16544
  () => true,
@@ -16032,6 +16726,15 @@ function resetEmptySessionTranscript(terminalUi) {
16032
16726
  terminalUi?.replaceTranscript([]);
16033
16727
  terminalUi?.showWelcome();
16034
16728
  }
16729
+ function refreshSessionContextUsage(terminalUi, input) {
16730
+ if (terminalUi === null) return;
16731
+ const context = new ContextBuilder().build(input);
16732
+ const estimator = new DefaultTokenEstimator();
16733
+ terminalUi.setContextUsage(
16734
+ estimator.estimateMessages(context.messages) + estimator.estimateTools(input.tools),
16735
+ input.contextWindow
16736
+ );
16737
+ }
16035
16738
  async function main(argv, options = {}) {
16036
16739
  let parsed;
16037
16740
  try {
@@ -16070,10 +16773,10 @@ async function main(argv, options = {}) {
16070
16773
  const configPath = options.configPath ?? defaultConfigPath(environ);
16071
16774
  const manager = new ConfigManager(configPath);
16072
16775
  const credentials = new CredentialStore(
16073
- options.credentialsPath ?? join6(dirname4(configPath), "credentials.json")
16776
+ options.credentialsPath ?? join7(dirname4(configPath), "credentials.json")
16074
16777
  );
16075
16778
  const modelCatalogStore = new ModelCatalogStore(
16076
- options.modelsPath ?? join6(dirname4(configPath), "models.json")
16779
+ options.modelsPath ?? join7(dirname4(configPath), "models.json")
16077
16780
  );
16078
16781
  const modelPlatform = await createPiAiPlatform({
16079
16782
  credentials,
@@ -16186,12 +16889,20 @@ async function main(argv, options = {}) {
16186
16889
  outputFn(`Verified: ${provider.verified ? "yes" : "no"}`);
16187
16890
  outputFn(`Configuration: ${configPath}`);
16188
16891
  outputFn(`Node: ${process.version}`);
16189
- outputFn(`Bash: ${existsSync7("/bin/bash") ? "available" : "missing"}`);
16190
- return provider !== void 0 && auth.configured && refreshOk && model !== void 0 ? 0 : 1;
16892
+ let bashAvailable = true;
16893
+ try {
16894
+ outputFn(`Bash: ${resolveBashPath({ shellPath: manager.getShellPath(), env: environ })}`);
16895
+ } catch (error) {
16896
+ bashAvailable = false;
16897
+ outputFn(`Bash: ${errorMessage11(error)}`);
16898
+ }
16899
+ return provider !== void 0 && auth.configured && refreshOk && model !== void 0 && bashAvailable ? 0 : 1;
16191
16900
  }
16192
16901
  let config;
16902
+ let shellPath;
16193
16903
  try {
16194
- if (existsSync7(configPath)) {
16904
+ shellPath = manager.getShellPath();
16905
+ if (existsSync8(configPath)) {
16195
16906
  config = manager.resolve({
16196
16907
  environ,
16197
16908
  profile: args.profile,
@@ -16273,8 +16984,11 @@ async function main(argv, options = {}) {
16273
16984
  terminalUi.state.model = config.model;
16274
16985
  }
16275
16986
  const toolRegistry = new ToolRegistry([
16276
- ...createFileToolDefinitions({ projectRoot }),
16277
- createBashToolDefinition({ projectRoot })
16987
+ ...createFileToolDefinitions({ projectRoot, pathOptions: {
16988
+ env: environ,
16989
+ shellPath: () => resolveBashPath({ shellPath, env: environ })
16990
+ } }),
16991
+ createBashToolDefinition({ projectRoot, shellPath, env: environ })
16278
16992
  ]);
16279
16993
  const activeConversationHistory = {
16280
16994
  appendUser: (input) => {
@@ -16458,6 +17172,16 @@ async function main(argv, options = {}) {
16458
17172
  input: presenterInput,
16459
17173
  secretInput: presenterSecretInput
16460
17174
  }) : new TerminalCommandPresenter(terminalUi);
17175
+ const refreshContextUsage = () => {
17176
+ refreshSessionContextUsage(terminalUi, {
17177
+ entries: sessionController.history?.entries() ?? [],
17178
+ currentProvider: config.provider,
17179
+ currentModel: config.model,
17180
+ tools: toolRegistry.definitions,
17181
+ contextWindow: selectedModel?.contextWindow ?? 0
17182
+ });
17183
+ };
17184
+ refreshContextUsage();
16461
17185
  const refreshSessionView = () => {
16462
17186
  const currentSessionId = sessionController.currentSessionId;
16463
17187
  if (currentSessionId !== null) {
@@ -16479,6 +17203,7 @@ async function main(argv, options = {}) {
16479
17203
  agent.messages = [system];
16480
17204
  }
16481
17205
  resetEmptySessionTranscript(terminalUi);
17206
+ refreshContextUsage();
16482
17207
  return;
16483
17208
  }
16484
17209
  agent.messages = [...new ContextBuilder().build({
@@ -16487,6 +17212,7 @@ async function main(argv, options = {}) {
16487
17212
  currentModel: config.model
16488
17213
  }).messages];
16489
17214
  terminalUi?.replaceTranscript(projectTranscript(entries));
17215
+ refreshContextUsage();
16490
17216
  };
16491
17217
  const commands = new SessionCommands({
16492
17218
  agent,
@@ -16503,7 +17229,7 @@ async function main(argv, options = {}) {
16503
17229
  sessionController,
16504
17230
  onComposerText: (text) => terminalUi?.setComposerText(text),
16505
17231
  onSessionChanged: refreshSessionView,
16506
- homeDirectory: environ["HOME"],
17232
+ homeDirectory: getHomeDirectory({ env: environ }),
16507
17233
  onModelSelected: (selection) => {
16508
17234
  semanticClassifier.configure({
16509
17235
  provider: selection.config.provider,
@@ -16526,6 +17252,7 @@ async function main(argv, options = {}) {
16526
17252
  terminalUi.state.model = selection.config.model;
16527
17253
  terminalUi.setRuntimeCapabilities({ reasoning: model?.reasoning ?? false });
16528
17254
  }
17255
+ refreshContextUsage();
16529
17256
  }
16530
17257
  });
16531
17258
  const unsubscribers = [];
@@ -16533,6 +17260,9 @@ async function main(argv, options = {}) {
16533
17260
  unsubscribers.push(
16534
17261
  runtime.eventBus.subscribe((event) => {
16535
17262
  sessionSink.publishEvent(projector.project(event, "terminal"));
17263
+ if (event.session_id === runtime.sessionId && (event.kind === "task.completed" || event.kind === "task.cancelled" || event.kind === "task.failed")) {
17264
+ refreshContextUsage();
17265
+ }
16536
17266
  })
16537
17267
  );
16538
17268
  if (terminalUi !== null) {
@@ -16596,7 +17326,7 @@ async function main(argv, options = {}) {
16596
17326
  return cleanShutdown ? 0 : 1;
16597
17327
  }
16598
17328
  function defaultSessionsRoot(environ) {
16599
- return join6(environ["HOME"] ?? process.cwd(), ".laohuang", "sessions");
17329
+ return join7(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
16600
17330
  }
16601
17331
  function defaultContextPolicy() {
16602
17332
  return {