laohuang 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/bin.js +1050 -403
  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,7 +7288,7 @@ 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);
7291
+ _MainScreenRenderer.#validateLines(newLines, width);
7098
7292
  const widthChanged = this.#previousWidth !== 0 && this.#previousWidth !== width;
7099
7293
  const heightChanged = this.#previousHeight !== 0 && this.#previousHeight !== height;
7100
7294
  const previousBufferLength = this.#previousHeight > 0 ? this.#previousViewportTop + this.#previousHeight : height;
@@ -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,13 @@ 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) {
7513
+ lines2.forEach((line2, index) => {
7320
7514
  if (/[\r\n]/u.test(line2)) {
7321
7515
  throw new Error(`rendered line ${index} contains a physical newline`);
7322
7516
  }
@@ -7515,7 +7709,7 @@ function lineText(value) {
7515
7709
  }
7516
7710
  function wrapStyledSpans(spans, width) {
7517
7711
  const targetWidth = Math.max(1, width);
7518
- const lines = [];
7712
+ const lines2 = [];
7519
7713
  let current = [];
7520
7714
  let currentWidth = 0;
7521
7715
  let skipLineFeed = false;
@@ -7528,7 +7722,7 @@ function wrapStyledSpans(spans, width) {
7528
7722
  current.push(span(text, style));
7529
7723
  };
7530
7724
  const finish = () => {
7531
- lines.push(line(...current));
7725
+ lines2.push(line(...current));
7532
7726
  current = [];
7533
7727
  currentWidth = 0;
7534
7728
  };
@@ -7560,7 +7754,7 @@ function wrapStyledSpans(spans, width) {
7560
7754
  }
7561
7755
  }
7562
7756
  finish();
7563
- return lines;
7757
+ return lines2;
7564
7758
  }
7565
7759
  function truncateStyledLine(value, width, ellipsis = "\u2026") {
7566
7760
  if (width <= 0) {
@@ -7649,6 +7843,7 @@ var InputActionKind = {
7649
7843
  CursorLeft: "cursor_left",
7650
7844
  CursorRight: "cursor_right",
7651
7845
  Backspace: "backspace",
7846
+ DeleteToLineStart: "delete_to_line_start",
7652
7847
  Key: "key",
7653
7848
  Dismiss: "dismiss",
7654
7849
  Cancel: "cancel",
@@ -7697,6 +7892,8 @@ function toTuiInputEvent(input) {
7697
7892
  return { type: "key", key: makeKeyInput("right") };
7698
7893
  case InputActionKind.Backspace:
7699
7894
  return { type: "key", key: makeKeyInput("backspace") };
7895
+ case InputActionKind.DeleteToLineStart:
7896
+ return { type: "key", key: makeKeyInput("ctrl_u", { ctrl: true }) };
7700
7897
  case InputActionKind.Dismiss:
7701
7898
  return { type: "key", key: makeKeyInput("escape") };
7702
7899
  case InputActionKind.Cancel:
@@ -7829,13 +8026,15 @@ var ABANDONED_NEGOTIATION_TAIL_RE = /^\x1b\[\?[\d;]*([A-Za-z])$/;
7829
8026
  var TerminalInputFilter = class {
7830
8027
  pendingNegotiationPrefix = "";
7831
8028
  isAppleTerminal;
8029
+ isWindowsConsole;
7832
8030
  shiftPressed;
7833
8031
  enableModifyOtherKeys;
7834
8032
  disableModifyOtherKeys;
7835
8033
  kittyProtocolActive = false;
7836
8034
  constructor(options = {}) {
7837
8035
  this.isAppleTerminal = options.isAppleTerminal ?? isAppleTerminalSession;
7838
- this.shiftPressed = options.shiftPressed ?? (() => false);
8036
+ this.isWindowsConsole = options.isWindowsConsole ?? isLocalWindowsConsole;
8037
+ this.shiftPressed = options.shiftPressed ?? isNativeShiftPressed;
7839
8038
  this.enableModifyOtherKeys = options.enableModifyOtherKeys ?? (() => {
7840
8039
  });
7841
8040
  this.disableModifyOtherKeys = options.disableModifyOtherKeys ?? (() => {
@@ -7914,7 +8113,7 @@ var TerminalInputFilter = class {
7914
8113
  return match[1];
7915
8114
  }
7916
8115
  normalizePlatformInput(sequence) {
7917
- if (sequence === "\r" && this.isAppleTerminal() && this.shiftPressed()) {
8116
+ if (sequence === "\r" && (this.isAppleTerminal() || this.isWindowsConsole()) && this.shiftPressed()) {
7918
8117
  return APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE;
7919
8118
  }
7920
8119
  return sequence;
@@ -7937,6 +8136,7 @@ var CONTROL_ACTIONS = /* @__PURE__ */ new Map([
7937
8136
  [4, InputActionKind.Eof],
7938
8137
  [9, InputActionKind.Complete],
7939
8138
  [8, InputActionKind.Backspace],
8139
+ [21, InputActionKind.DeleteToLineStart],
7940
8140
  [127, InputActionKind.Backspace]
7941
8141
  ]);
7942
8142
  var CONTROL_KEYS = /* @__PURE__ */ new Map([
@@ -8367,10 +8567,13 @@ function decodeSpecialEscapeAction(sequence) {
8367
8567
  return kind !== void 0 ? inputAction(kind) : null;
8368
8568
  }
8369
8569
  function decodeModifiedControlKey(sequence) {
8370
- const kitty = /^\x1b\[(13|57414|9);(\d+)(?::(\d+))?u$/.exec(sequence);
8570
+ const kitty = /^\x1b\[(13|57414|9|117)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
8371
8571
  if (kitty !== null) {
8372
8572
  const code = kitty[1];
8373
8573
  const modifier2 = Number.parseInt(kitty[2], 10) - 1;
8574
+ if (code === "117" && matchesKittyModifiers(modifier2, 4) && (isKittyPressEvent(kitty[3]) || kitty[3] === "2")) {
8575
+ return inputAction(InputActionKind.DeleteToLineStart);
8576
+ }
8374
8577
  if (!isKittyPressEvent(kitty[3])) {
8375
8578
  return null;
8376
8579
  }
@@ -8388,6 +8591,9 @@ function decodeModifiedControlKey(sequence) {
8388
8591
  }
8389
8592
  const modifier = Number.parseInt(modifyOtherKeys[1], 10) - 1;
8390
8593
  const codepoint = Number.parseInt(modifyOtherKeys[2], 10);
8594
+ if (codepoint === 117 && modifier === 4) {
8595
+ return inputAction(InputActionKind.DeleteToLineStart);
8596
+ }
8391
8597
  if (codepoint === 13 && modifier === 2) {
8392
8598
  return inputAction(InputActionKind.Key, "", makeKeyInput("enter", { alt: true }));
8393
8599
  }
@@ -8506,13 +8712,13 @@ var EditorState = class _EditorState {
8506
8712
  const projection = this.#renderProjection(width, prompt, options.mask ?? false);
8507
8713
  const promptStyle = options.styles?.prompt ?? ((text) => text);
8508
8714
  const textStyle = options.styles?.text ?? ((text) => text);
8509
- const lines = projection.rows.map((row, index) => {
8715
+ const lines2 = projection.rows.map((row, index) => {
8510
8716
  const promptText = index === 0 ? projection.prompt : " ".repeat(projection.promptWidth);
8511
8717
  const renderedPrompt = index === 0 ? promptStyle(promptText) : promptText;
8512
8718
  return renderedPrompt + textStyle(row);
8513
8719
  });
8514
8720
  return {
8515
- lines,
8721
+ lines: lines2,
8516
8722
  cursorRow: projection.cursorRow,
8517
8723
  cursorColumn: projection.cursorColumn
8518
8724
  };
@@ -8609,6 +8815,8 @@ var EditorState = class _EditorState {
8609
8815
  this.clearCompletions();
8610
8816
  this.text = this.text.slice(0, this.cursor - 1) + this.text.slice(this.cursor);
8611
8817
  this.cursor -= 1;
8818
+ } else if (action.kind === InputActionKind.DeleteToLineStart) {
8819
+ this.deleteToLineStart();
8612
8820
  } else if (action.kind === InputActionKind.CursorLeft) {
8613
8821
  this.clearCompletions();
8614
8822
  this.cursor = Math.max(0, this.cursor - 1);
@@ -8631,6 +8839,22 @@ var EditorState = class _EditorState {
8631
8839
  this.cursor += text.length;
8632
8840
  this.historyIndex = null;
8633
8841
  }
8842
+ deleteToLineStart() {
8843
+ if (this.cursor === 0) {
8844
+ return;
8845
+ }
8846
+ const previousNewline = this.text.lastIndexOf("\n", this.cursor - 1);
8847
+ const lineStart = previousNewline + 1;
8848
+ this.clearCompletions();
8849
+ if (lineStart === this.cursor) {
8850
+ this.text = this.text.slice(0, previousNewline) + this.text.slice(this.cursor);
8851
+ this.cursor = previousNewline;
8852
+ } else {
8853
+ this.text = this.text.slice(0, lineStart) + this.text.slice(this.cursor);
8854
+ this.cursor = lineStart;
8855
+ }
8856
+ this.historyIndex = null;
8857
+ }
8634
8858
  acceptCompletion() {
8635
8859
  if (this.selectedCompletion === null) {
8636
8860
  return;
@@ -8728,6 +8952,7 @@ var DEFAULT_KEYBINDINGS = [
8728
8952
  { context: "terminal", key: "ctrl+o", action: "toggle_tool_output" },
8729
8953
  { context: "terminal", key: "ctrl+l", action: "select_model" },
8730
8954
  { context: "terminal", key: "ctrl+t", action: "toggle_thinking" },
8955
+ { context: "editor", key: "ctrl+u", action: "delete_to_line_start" },
8731
8956
  { context: "editor", key: "ctrl+s", action: "steer_now" },
8732
8957
  { context: "editor", key: "alt+enter", action: "submit_follow_up" },
8733
8958
  { context: "editor", key: "escape", action: "dismiss" },
@@ -8762,6 +8987,9 @@ function controlKeyId(character) {
8762
8987
  if (character === "l") {
8763
8988
  return "ctrl_l";
8764
8989
  }
8990
+ if (character === "u") {
8991
+ return "ctrl_u";
8992
+ }
8765
8993
  return "character";
8766
8994
  }
8767
8995
  function parseKey(value) {
@@ -9035,6 +9263,14 @@ var UIEventReducer = class _UIEventReducer {
9035
9263
  }
9036
9264
  return createUpdate(kind, { text, correlationId, stream });
9037
9265
  }
9266
+ if (kind === "tool.output_snapshot") {
9267
+ const stream = String(payload.stream ?? "stdout");
9268
+ const text = String(payload.text ?? "");
9269
+ const tool = this.state.activeTools.get(correlationId);
9270
+ if (tool !== void 0 && (stream === "stdout" || stream === "stderr"))
9271
+ tool[stream] = text;
9272
+ return createUpdate(kind, { text, correlationId, stream, payload });
9273
+ }
9038
9274
  if (kind === "tool.finished") {
9039
9275
  const tool = this.state.activeTools.get(correlationId);
9040
9276
  this.state.activeTools.delete(correlationId);
@@ -9094,11 +9330,13 @@ var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
9094
9330
  var HIGH_FREQUENCY_EVENTS = /* @__PURE__ */ new Set([
9095
9331
  "model.reasoning_delta",
9096
9332
  "model.tool_call_delta",
9097
- "tool.output_delta"
9333
+ "tool.output_delta",
9334
+ "tool.output_snapshot"
9098
9335
  ]);
9099
9336
  var TOOL_EVENTS = /* @__PURE__ */ new Set([
9100
9337
  "tool.started",
9101
9338
  "tool.output_delta",
9339
+ "tool.output_snapshot",
9102
9340
  "tool.finished"
9103
9341
  ]);
9104
9342
  var SENSITIVE_FIELDS2 = /* @__PURE__ */ new Set([
@@ -9129,6 +9367,26 @@ function asPayload(value) {
9129
9367
  function redactToolText(value) {
9130
9368
  return SENSITIVE_TEXT_PATTERNS2.reduce((text, pattern) => text.replace(pattern, "$1[REDACTED]"), value);
9131
9369
  }
9370
+ function redactToolSnapshot(value, startMidLine = false) {
9371
+ const text = startMidLine ? omitPartialOutputLine(value) : value;
9372
+ return redactToolText(text);
9373
+ }
9374
+ function toolOutputNote(payload) {
9375
+ const notes = [];
9376
+ if (payload.truncated === true)
9377
+ notes.push("Output preview truncated.");
9378
+ if (isRecord3(payload.output_files)) {
9379
+ const label = payload.output_file_complete === true ? "Full output" : "Partial output";
9380
+ notes.push(`${label}: ${Object.values(payload.output_files).filter((value) => typeof value === "string").join(", ")}`);
9381
+ }
9382
+ if (payload.output_file_error)
9383
+ notes.push(String(payload.output_file_error));
9384
+ if (payload.output_complete === false)
9385
+ notes.push("Output collection incomplete.");
9386
+ if (payload.error)
9387
+ notes.push(String(payload.error));
9388
+ return redactToolText(notes.join("\n"));
9389
+ }
9132
9390
  function redactToolValue(value) {
9133
9391
  if (typeof value === "string")
9134
9392
  return redactToolText(value);
@@ -9199,8 +9457,15 @@ var DisplayPolicy = class {
9199
9457
  const text = kind === "tool.output_delta" ? this.#toolOutputRedactor.redact(correlationId, stream || "stdout", String(rawPayload.text ?? rawPayload.chunk ?? "")) : String(payload.text ?? payload.chunk ?? "");
9200
9458
  if (kind === "tool.output_delta") {
9201
9459
  payload = { ...payload, text };
9460
+ } else if (kind === "tool.output_snapshot") {
9461
+ payload = { ...payload, text: redactToolSnapshot(String(rawPayload.text ?? ""), rawPayload.start_mid_line === true) };
9202
9462
  } else if (kind === "tool.finished") {
9203
9463
  this.#toolOutputRedactor.clear(correlationId);
9464
+ for (const name of ["stdout", "stderr"]) {
9465
+ if (typeof rawPayload[name] === "string") {
9466
+ payload[name] = redactToolSnapshot(rawPayload[name], rawPayload[`${name}_start_mid_line`] === true);
9467
+ }
9468
+ }
9204
9469
  }
9205
9470
  const projected = [];
9206
9471
  const dropped = droppedCount(payload);
@@ -9216,11 +9481,11 @@ var DisplayPolicy = class {
9216
9481
  if (kind === "model.reasoning_delta" && !this.showReasoning) {
9217
9482
  return projected;
9218
9483
  }
9219
- if (this.foldToolOutput && kind === "tool.output_delta" && (stream || "stdout") === "stdout") {
9484
+ if (this.foldToolOutput && (kind === "tool.output_delta" || kind === "tool.output_snapshot") && (stream || "stdout") === "stdout") {
9220
9485
  return projected;
9221
9486
  }
9222
9487
  if (LIFECYCLE_EVENTS.has(kind) || kind.length > 0) {
9223
- projected.push({ kind, correlationId, stream, text, payload });
9488
+ projected.push({ kind, correlationId, stream, text: String(payload.text ?? text), payload });
9224
9489
  }
9225
9490
  return projected;
9226
9491
  }
@@ -9412,11 +9677,27 @@ var TranscriptStore = class {
9412
9677
  }
9413
9678
  return;
9414
9679
  }
9680
+ if (kind === "tool.output_snapshot") {
9681
+ const item = this.#byCorrelation.get(`tool:${correlationId}`);
9682
+ if (item?.kind === "tool" && item.mutable && (update.stream === "stdout" || update.stream === "stderr")) {
9683
+ item[update.stream] = redactToolSnapshot(update.text, update.payload.start_mid_line === true);
9684
+ if (update.payload.truncated === true)
9685
+ item.outputNote = "Output preview truncated.";
9686
+ touchBlock(item);
9687
+ }
9688
+ return;
9689
+ }
9415
9690
  if (kind === "tool.finished") {
9416
9691
  const item = this.#getOrCreateTool(correlationId);
9417
9692
  item.status = String(update.payload.status ?? "completed");
9418
9693
  item.exitCode = typeof update.payload.exit_code === "number" ? Math.trunc(update.payload.exit_code) : null;
9419
9694
  item.durationMs = typeof update.payload.duration_ms === "number" ? Math.trunc(update.payload.duration_ms) : null;
9695
+ for (const stream of ["stdout", "stderr"]) {
9696
+ const text = update.payload[stream];
9697
+ if (typeof text === "string")
9698
+ item[stream] = redactToolSnapshot(text, update.payload[`${stream}_start_mid_line`] === true);
9699
+ }
9700
+ item.outputNote = toolOutputNote(update.payload);
9420
9701
  item.mutable = false;
9421
9702
  touchBlock(item);
9422
9703
  this.#toolOutputRedactor.clear(correlationId);
@@ -9581,13 +9862,13 @@ var Composer = class {
9581
9862
  mask: this.#mask
9582
9863
  });
9583
9864
  const borderStyle = { foreground: "dim" };
9584
- const lines = [
9865
+ const lines2 = [
9585
9866
  line(span("\u256D", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256E", borderStyle)),
9586
9867
  ...rendered.lines.map((value) => line(span("\u2502", borderStyle), ...padStyledLine(truncateStyledLine(value, innerWidth, ""), innerWidth).spans, span("\u2502", borderStyle))),
9587
9868
  line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle))
9588
9869
  ];
9589
9870
  return {
9590
- lines,
9871
+ lines: lines2,
9591
9872
  cursor: {
9592
9873
  row: rendered.cursorRow + 1,
9593
9874
  column: rendered.cursorColumn + 1
@@ -9702,22 +9983,22 @@ function renderMarkdownStyledLines(text, width) {
9702
9983
  return [];
9703
9984
  }
9704
9985
  const layoutWidth = Math.max(12, width);
9705
- const lines = [];
9986
+ const lines2 = [];
9706
9987
  let previous = null;
9707
9988
  for (const block of renderBlocks(clean, layoutWidth)) {
9708
9989
  const leadingBlank = previous === null ? block.kind === "list" || block.kind === "table" || block.kind === "quote" : previous !== "hr";
9709
9990
  if (leadingBlank) {
9710
- lines.push(line());
9991
+ lines2.push(line());
9711
9992
  }
9712
9993
  for (const line2 of block.lines) {
9713
- lines.push(truncateMarkdownLine(serializeLine(line2), width));
9994
+ lines2.push(truncateMarkdownLine(serializeLine(line2), width));
9714
9995
  }
9715
9996
  if (block.kind === "hr") {
9716
- lines.push(line());
9997
+ lines2.push(line());
9717
9998
  }
9718
9999
  previous = block.kind;
9719
10000
  }
9720
- return lines;
10001
+ return lines2;
9721
10002
  }
9722
10003
  function styleKey(style) {
9723
10004
  return [
@@ -9729,7 +10010,7 @@ function styleKey(style) {
9729
10010
  ].join("|");
9730
10011
  }
9731
10012
  function renderBlocks(text, layoutWidth) {
9732
- const lines = text.replace(/\r\n/g, "\n").split("\n");
10013
+ const lines2 = text.replace(/\r\n/g, "\n").split("\n");
9733
10014
  const blocks = [];
9734
10015
  let paragraph = [];
9735
10016
  let index = 0;
@@ -9743,8 +10024,8 @@ function renderBlocks(text, layoutWidth) {
9743
10024
  });
9744
10025
  }
9745
10026
  };
9746
- while (index < lines.length) {
9747
- const line2 = lines[index];
10027
+ while (index < lines2.length) {
10028
+ const line2 = lines2[index];
9748
10029
  const trimmed = line2.trim();
9749
10030
  if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
9750
10031
  flushParagraph();
@@ -9754,8 +10035,8 @@ function renderBlocks(text, layoutWidth) {
9754
10035
  foreground: "code"
9755
10036
  };
9756
10037
  const codeLines = [];
9757
- while (index < lines.length) {
9758
- const body = lines[index];
10038
+ while (index < lines2.length) {
10039
+ const body = lines2[index];
9759
10040
  if (body.trim().startsWith(fence)) {
9760
10041
  index += 1;
9761
10042
  break;
@@ -9771,15 +10052,15 @@ function renderBlocks(text, layoutWidth) {
9771
10052
  index += 1;
9772
10053
  continue;
9773
10054
  }
9774
- if (trimmed.includes("|") && index + 1 < lines.length) {
10055
+ if (trimmed.includes("|") && index + 1 < lines2.length) {
9775
10056
  const header = splitTableRow(trimmed);
9776
- const delimiter = splitTableRow(lines[index + 1].trim());
10057
+ const delimiter = splitTableRow(lines2[index + 1].trim());
9777
10058
  if (header !== null && delimiter !== null && delimiter.length === header.length && delimiter.every((cell) => /^:?-+:?$/.test(cell))) {
9778
10059
  flushParagraph();
9779
10060
  index += 2;
9780
10061
  const rows = [];
9781
- while (index < lines.length) {
9782
- const rowLine = lines[index].trim();
10062
+ while (index < lines2.length) {
10063
+ const rowLine = lines2[index].trim();
9783
10064
  if (!rowLine) {
9784
10065
  break;
9785
10066
  }
@@ -9826,8 +10107,8 @@ function renderBlocks(text, layoutWidth) {
9826
10107
  if (trimmed.startsWith(">")) {
9827
10108
  flushParagraph();
9828
10109
  const content = [];
9829
- while (index < lines.length) {
9830
- const match = /^>\s?(.*)$/.exec(lines[index].trim());
10110
+ while (index < lines2.length) {
10111
+ const match = /^>\s?(.*)$/.exec(lines2[index].trim());
9831
10112
  if (!match) {
9832
10113
  break;
9833
10114
  }
@@ -9846,8 +10127,8 @@ function renderBlocks(text, layoutWidth) {
9846
10127
  if (/^[-*+]\s+/.test(trimmed)) {
9847
10128
  flushParagraph();
9848
10129
  const items = [];
9849
- while (index < lines.length) {
9850
- const match = /^[-*+]\s+(.*)$/.exec(lines[index].trim());
10130
+ while (index < lines2.length) {
10131
+ const match = /^[-*+]\s+(.*)$/.exec(lines2[index].trim());
9851
10132
  if (!match) {
9852
10133
  break;
9853
10134
  }
@@ -9866,8 +10147,8 @@ function renderBlocks(text, layoutWidth) {
9866
10147
  const start = parseInt(numbered[1], 10);
9867
10148
  const items = [parseInline(numbered[2], {})];
9868
10149
  index += 1;
9869
- while (index < lines.length) {
9870
- const match = /^(\d+)[.)]\s+(.*)$/.exec(lines[index].trim());
10150
+ while (index < lines2.length) {
10151
+ const match = /^(\d+)[.)]\s+(.*)$/.exec(lines2[index].trim());
9871
10152
  if (!match) {
9872
10153
  break;
9873
10154
  }
@@ -9889,16 +10170,16 @@ function renderBlocks(text, layoutWidth) {
9889
10170
  }
9890
10171
  function renderListItems(items, prefixFor, layoutWidth) {
9891
10172
  const marker = { foreground: "accent" };
9892
- const lines = [];
10173
+ const lines2 = [];
9893
10174
  for (let index = 0; index < items.length; index += 1) {
9894
10175
  const prefix = prefixFor(index);
9895
10176
  const wrapped = wrapSegments(items[index], Math.max(1, layoutWidth - prefix.length));
9896
10177
  if (wrapped.length === 0) {
9897
- lines.push([{ style: marker, text: prefix }]);
10178
+ lines2.push([{ style: marker, text: prefix }]);
9898
10179
  continue;
9899
10180
  }
9900
10181
  for (let row = 0; row < wrapped.length; row += 1) {
9901
- lines.push([
10182
+ lines2.push([
9902
10183
  {
9903
10184
  style: marker,
9904
10185
  text: row === 0 ? prefix : " ".repeat(prefix.length)
@@ -9907,7 +10188,7 @@ function renderListItems(items, prefixFor, layoutWidth) {
9907
10188
  ]);
9908
10189
  }
9909
10190
  }
9910
- return lines;
10191
+ return lines2;
9911
10192
  }
9912
10193
  function splitTableRow(line2) {
9913
10194
  if (!line2.includes("|")) {
@@ -9955,15 +10236,15 @@ function renderTable(header, rows, layoutWidth) {
9955
10236
  widths[widest] = widths[widest] - 1;
9956
10237
  }
9957
10238
  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)} ` }]);
10239
+ const lines2 = [];
10240
+ lines2.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
10241
+ lines2.push(...renderTableRows(headerCells, widths, borderStyle, headerStyle));
10242
+ lines2.push([{ style: borderStyle, text: ` ${"\u2500".repeat(inner)} ` }]);
9962
10243
  for (const row of bodyCells) {
9963
- lines.push(...renderTableRows(row, widths, borderStyle, {}));
10244
+ lines2.push(...renderTableRows(row, widths, borderStyle, {}));
9964
10245
  }
9965
- lines.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
9966
- return lines;
10246
+ lines2.push([{ style: borderStyle, text: " ".repeat(inner + 2) }]);
10247
+ return lines2;
9967
10248
  }
9968
10249
  function renderTableRows(cells, widths, borderStyle, padStyle) {
9969
10250
  const wrappedCells = cells.map((cell, i) => {
@@ -9971,7 +10252,7 @@ function renderTableRows(cells, widths, borderStyle, padStyle) {
9971
10252
  return wrapped.length > 0 ? wrapped : [[]];
9972
10253
  });
9973
10254
  const height = Math.max(...wrappedCells.map((cell) => cell.length));
9974
- const lines = [];
10255
+ const lines2 = [];
9975
10256
  for (let row = 0; row < height; row += 1) {
9976
10257
  const line2 = [{ style: borderStyle, text: " " }];
9977
10258
  for (let i = 0; i < cells.length; i += 1) {
@@ -9989,9 +10270,9 @@ function renderTableRows(cells, widths, borderStyle, padStyle) {
9989
10270
  }
9990
10271
  }
9991
10272
  line2.push({ style: borderStyle, text: " " });
9992
- lines.push(mergeSegments(line2));
10273
+ lines2.push(mergeSegments(line2));
9993
10274
  }
9994
- return lines;
10275
+ return lines2;
9995
10276
  }
9996
10277
  var INLINE_PATTERNS = [
9997
10278
  {
@@ -10146,12 +10427,12 @@ function wrapSegments(segments, width) {
10146
10427
  if (current.length > 0) {
10147
10428
  words.push(current);
10148
10429
  }
10149
- const lines = [];
10430
+ const lines2 = [];
10150
10431
  let line2 = [];
10151
10432
  let lineWidth = 0;
10152
10433
  let pendingSpace = null;
10153
10434
  const flushLine = () => {
10154
- lines.push(line2);
10435
+ lines2.push(line2);
10155
10436
  line2 = [];
10156
10437
  lineWidth = 0;
10157
10438
  pendingSpace = null;
@@ -10192,7 +10473,7 @@ function wrapSegments(segments, width) {
10192
10473
  if (line2.length > 0) {
10193
10474
  flushLine();
10194
10475
  }
10195
- return lines.map((physical) => regroup(physical));
10476
+ return lines2.map((physical) => regroup(physical));
10196
10477
  }
10197
10478
  function regroup(clusters) {
10198
10479
  const segments = [];
@@ -10274,11 +10555,11 @@ var Text = class {
10274
10555
  this.#cache = null;
10275
10556
  }
10276
10557
  };
10277
- function applyTextPadding(lines, width, options) {
10558
+ function applyTextPadding(lines2, width, options) {
10278
10559
  const targetWidth = Math.max(0, width);
10279
10560
  const blank = () => line(span(" ".repeat(targetWidth), backgroundStyle(options.background)));
10280
10561
  const horizontalPadding = span(" ".repeat(options.paddingX), backgroundStyle(options.background));
10281
- const contentLines = lines.map((value) => {
10562
+ const contentLines = lines2.map((value) => {
10282
10563
  const contentSpans = options.background === void 0 ? value.spans : value.spans.map((item) => span(item.text, {
10283
10564
  ...item.style,
10284
10565
  background: item.style?.background ?? options.background
@@ -10362,7 +10643,7 @@ var Box = class {
10362
10643
  const width = Math.max(0, context.width);
10363
10644
  const child = this.#child.render({ ...context, width: Math.max(1, width - this.#paddingX * 2) });
10364
10645
  const blank = () => line(span(" ".repeat(width), backgroundStyle2(this.#background)));
10365
- const lines = child.lines.map((value) => this.#renderLine(value, width));
10646
+ const lines2 = child.lines.map((value) => this.#renderLine(value, width));
10366
10647
  const cursor = child.cursor === void 0 ? void 0 : {
10367
10648
  row: child.cursor.row + this.#paddingY,
10368
10649
  column: child.cursor.column + this.#paddingX
@@ -10370,7 +10651,7 @@ var Box = class {
10370
10651
  return {
10371
10652
  lines: [
10372
10653
  ...Array.from({ length: this.#paddingY }, blank),
10373
- ...lines,
10654
+ ...lines2,
10374
10655
  ...Array.from({ length: this.#paddingY }, blank)
10375
10656
  ],
10376
10657
  ...cursor === void 0 ? {} : { cursor }
@@ -10420,11 +10701,11 @@ var ToolMessage = class {
10420
10701
  options.exitCode === null ? "" : `exit ${options.exitCode}`,
10421
10702
  options.durationMs === null ? "" : `${options.durationMs}ms`
10422
10703
  ].filter(Boolean).join(" \xB7 ");
10423
- const output = options.expanded ? [stderr && clip(stderr, 1200), stdout && clip(stdout, 1200)].filter(Boolean) : [];
10704
+ const output = options.expanded ? [stderr, stdout].filter(Boolean).map((text) => text.length > 1200 ? `\u2026${text.slice(-1200)}` : text) : [];
10424
10705
  this.#content = new Box({
10425
10706
  child: new Text({
10426
10707
  spans: [...title, { text: `
10427
- ${[metadata, ...output].join("\n")}` }]
10708
+ ${[metadata, ...output, options.outputNote && redactToolText(options.outputNote)].filter(Boolean).join("\n")}` }]
10428
10709
  })
10429
10710
  });
10430
10711
  }
@@ -10447,12 +10728,12 @@ var UserMessage = class {
10447
10728
  }
10448
10729
  render(context) {
10449
10730
  const width = Math.max(3, context.width);
10450
- const lines = wrapStyledSpans([
10731
+ const lines2 = wrapStyledSpans([
10451
10732
  span("\u2728 ", { foreground: "accent" }),
10452
10733
  span(this.#text)
10453
10734
  ], width);
10454
10735
  return {
10455
- lines: lines.map((value) => padStyledLine(value, width))
10736
+ lines: lines2.map((value) => padStyledLine(value, width))
10456
10737
  };
10457
10738
  }
10458
10739
  invalidate() {
@@ -10474,7 +10755,7 @@ var WelcomeMessage = class {
10474
10755
  }
10475
10756
  const innerWidth = width - 2;
10476
10757
  const borderStyle = { foreground: "accent" };
10477
- const lines = [
10758
+ const lines2 = [
10478
10759
  line(span("\u256D", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256E", borderStyle)),
10479
10760
  frameRow(innerWidth, borderStyle, [
10480
10761
  span("H", { foreground: "accent", bold: true }),
@@ -10484,17 +10765,17 @@ var WelcomeMessage = class {
10484
10765
  ];
10485
10766
  const [subtitle, ...meta] = this.#details;
10486
10767
  if (subtitle !== void 0) {
10487
- lines.push(frameRow(innerWidth, borderStyle, [
10768
+ lines2.push(frameRow(innerWidth, borderStyle, [
10488
10769
  span(" "),
10489
10770
  span(subtitle, { foreground: "dim" })
10490
10771
  ]));
10491
10772
  }
10492
- lines.push(frameRow(innerWidth, borderStyle, [span("")]));
10773
+ lines2.push(frameRow(innerWidth, borderStyle, [span("")]));
10493
10774
  for (const detail of meta) {
10494
- lines.push(frameRow(innerWidth, borderStyle, metaSpans(detail)));
10775
+ lines2.push(frameRow(innerWidth, borderStyle, metaSpans(detail)));
10495
10776
  }
10496
- lines.push(line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle)));
10497
- return { lines };
10777
+ lines2.push(line(span("\u2570", borderStyle), span("\u2500".repeat(innerWidth), borderStyle), span("\u256F", borderStyle)));
10778
+ return { lines: lines2 };
10498
10779
  }
10499
10780
  invalidate() {
10500
10781
  }
@@ -10627,23 +10908,23 @@ var Transcript = class {
10627
10908
  }
10628
10909
  renderWithMetadata(context) {
10629
10910
  const usableWidth = Math.max(12, context.width);
10630
- const lines = [];
10911
+ const lines2 = [];
10631
10912
  let activeStart = null;
10632
10913
  const seenKeys = /* @__PURE__ */ new Set();
10633
10914
  for (const block of this.#blocks) {
10634
- if (lines.length > 0) {
10635
- lines.push(plainLine(""));
10915
+ if (lines2.length > 0) {
10916
+ lines2.push(plainLine(""));
10636
10917
  }
10637
10918
  if (block.mutable && activeStart === null)
10638
- activeStart = lines.length;
10919
+ activeStart = lines2.length;
10639
10920
  seenKeys.add(cacheKey(block));
10640
- lines.push(...this.#renderBlockCached(block, { ...context, width: usableWidth }));
10921
+ lines2.push(...this.#renderBlockCached(block, { ...context, width: usableWidth }));
10641
10922
  }
10642
10923
  for (const key of this.#cache.keys()) {
10643
10924
  if (!seenKeys.has(key))
10644
10925
  this.#cache.delete(key);
10645
10926
  }
10646
- return { lines, activeStart };
10927
+ return { lines: lines2, activeStart };
10647
10928
  }
10648
10929
  invalidate() {
10649
10930
  this.#cache.clear();
@@ -10656,23 +10937,23 @@ var Transcript = class {
10656
10937
  return cached.lines;
10657
10938
  }
10658
10939
  if (cached !== void 0 && cached.signature === signature) {
10659
- const lines2 = cached.component.render(context).lines;
10940
+ const lines3 = cached.component.render(context).lines;
10660
10941
  this.#cache.set(key, {
10661
10942
  ...cached,
10662
10943
  width: context.width,
10663
- lines: lines2
10944
+ lines: lines3
10664
10945
  });
10665
- return lines2;
10946
+ return lines3;
10666
10947
  }
10667
10948
  const component = this.#createBlockComponent(block);
10668
- const lines = component.render(context).lines;
10949
+ const lines2 = component.render(context).lines;
10669
10950
  this.#cache.set(key, {
10670
10951
  component,
10671
10952
  width: context.width,
10672
10953
  signature,
10673
- lines
10954
+ lines: lines2
10674
10955
  });
10675
- return lines;
10956
+ return lines2;
10676
10957
  }
10677
10958
  #createBlockComponent(block) {
10678
10959
  switch (block.kind) {
@@ -10744,7 +11025,7 @@ var TerminalTheme = class {
10744
11025
  return `\x1B[${channel};2;${r};${g};${b}m`;
10745
11026
  }
10746
11027
  };
10747
- var PI_DARK = new TerminalTheme("dark", {
11028
+ var DEFAULT_DARK_THEME = new TerminalTheme("dark", {
10748
11029
  accent: "#8abeb7",
10749
11030
  border: "#5f87ff",
10750
11031
  border_muted: "#505050",
@@ -10765,7 +11046,7 @@ var PI_DARK = new TerminalTheme("dark", {
10765
11046
  thinking: "#808080",
10766
11047
  bash: "#b5bd68"
10767
11048
  });
10768
- var PI_LIGHT = new TerminalTheme("light", {
11049
+ var DEFAULT_LIGHT_THEME = new TerminalTheme("light", {
10769
11050
  accent: "#5a8080",
10770
11051
  border: "#547da7",
10771
11052
  border_muted: "#b0b0b0",
@@ -10789,10 +11070,10 @@ var PI_LIGHT = new TerminalTheme("light", {
10789
11070
  function resolveTerminalTheme(name, env = process.env) {
10790
11071
  const requested = (name ?? "auto").trim().toLowerCase() || "auto";
10791
11072
  if (requested === "light") {
10792
- return PI_LIGHT;
11073
+ return DEFAULT_LIGHT_THEME;
10793
11074
  }
10794
11075
  if (requested === "dark") {
10795
- return PI_DARK;
11076
+ return DEFAULT_DARK_THEME;
10796
11077
  }
10797
11078
  if (requested !== "auto") {
10798
11079
  throw new Error("terminal theme must be 'auto', 'dark', or 'light'");
@@ -10800,9 +11081,9 @@ function resolveTerminalTheme(name, env = process.env) {
10800
11081
  const colorfgbg = env.COLORFGBG ?? "";
10801
11082
  const background = colorfgbg ? colorfgbg.split(";").pop() : "";
10802
11083
  if (/^\s*[+-]?\d+\s*$/.test(background)) {
10803
- return parseInt(background, 10) >= 7 ? PI_LIGHT : PI_DARK;
11084
+ return parseInt(background, 10) >= 7 ? DEFAULT_LIGHT_THEME : DEFAULT_DARK_THEME;
10804
11085
  }
10805
- return PI_DARK;
11086
+ return DEFAULT_DARK_THEME;
10806
11087
  }
10807
11088
 
10808
11089
  // ../../packages/terminal/tui/dist/tui/frame-builder.js
@@ -10829,9 +11110,9 @@ var FrameBuilder = class {
10829
11110
  const terminalWidth = Math.max(1, options.width);
10830
11111
  const width = Math.max(1, terminalWidth - 1);
10831
11112
  const mainScreen = options.compiledMainScreen ?? this.#fallbackMainScreen(options, width);
10832
- const lines = mainScreen.lines.map((value) => visibleWidth(value) <= width ? value : truncateToWidth(value, width));
11113
+ const lines2 = mainScreen.lines.map((value) => visibleWidth(value) <= width ? value : truncateToWidth(value, width));
10833
11114
  const cursor = {
10834
- row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines.length - 1))),
11115
+ row: Math.max(0, Math.min(mainScreen.cursor.row, Math.max(0, lines2.length - 1))),
10835
11116
  col: Math.max(0, Math.min(mainScreen.cursor.column, width - 1))
10836
11117
  };
10837
11118
  return {
@@ -10840,8 +11121,8 @@ var FrameBuilder = class {
10840
11121
  statusBar: this.statusBar(width),
10841
11122
  cursor,
10842
11123
  screen: {
10843
- lines,
10844
- activeStart: Math.max(0, Math.min(mainScreen.activeStart, lines.length)),
11124
+ lines: lines2,
11125
+ activeStart: Math.max(0, Math.min(mainScreen.activeStart, lines2.length)),
10845
11126
  cursorRow: cursor.row,
10846
11127
  cursorCol: cursor.col
10847
11128
  }
@@ -10866,9 +11147,9 @@ var FrameBuilder = class {
10866
11147
  }).render({ width, theme: this.#theme });
10867
11148
  const status = this.#status().render({ width, theme: this.#theme });
10868
11149
  const cursor = composer.cursor ?? { row: Math.max(0, composer.lines.length - 1), column: 0 };
10869
- const lines = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
11150
+ const lines2 = [...transcriptLines, ...composer.lines, ...completion.lines, ...status.lines];
10870
11151
  return {
10871
- lines: compileStyledLines(lines, width, this.#theme),
11152
+ lines: compileStyledLines(lines2, width, this.#theme),
10872
11153
  cursor: {
10873
11154
  row: transcriptLines.length + cursor.row,
10874
11155
  column: cursor.column
@@ -10972,8 +11253,8 @@ var SearchInput = class {
10972
11253
  const width = Math.max(1, context.width);
10973
11254
  const contentWidth = Math.max(1, width - displayWidth4(PROMPT));
10974
11255
  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, ""));
11256
+ const lines2 = renderLines(displayText || this.#placeholder, contentWidth, displayText.length === 0 && this.#placeholder.length > 0);
11257
+ 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
11258
  const cursor = this.focused ? cursorMetadata(displayText.slice(0, this.#editor.cursor), contentWidth) : void 0;
10978
11259
  return cursor === void 0 ? { lines: structuredLines } : {
10979
11260
  lines: structuredLines,
@@ -11041,15 +11322,15 @@ function renderLines(value, width, placeholder) {
11041
11322
  if (placeholder) {
11042
11323
  return [value];
11043
11324
  }
11044
- const lines = [];
11325
+ const lines2 = [];
11045
11326
  for (const source of value.split("\n")) {
11046
- lines.push(...wrapStyledSpans([span(source)], width).map((item) => item.spans.map((part) => part.text).join("")));
11327
+ lines2.push(...wrapStyledSpans([span(source)], width).map((item) => item.spans.map((part) => part.text).join("")));
11047
11328
  }
11048
11329
  const last = value.split("\n").at(-1) ?? "";
11049
11330
  if (value && !value.endsWith("\n") && displayWidth4(last) % width === 0) {
11050
- lines.push("");
11331
+ lines2.push("");
11051
11332
  }
11052
- return lines;
11333
+ return lines2;
11053
11334
  }
11054
11335
  function cursorMetadata(before, width) {
11055
11336
  const segments = before.split("\n");
@@ -11133,11 +11414,11 @@ var SelectList = class {
11133
11414
  const start = Math.max(0, Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.#filteredItems.length - this.#maxVisible));
11134
11415
  const end = Math.min(start + this.#maxVisible, this.#filteredItems.length);
11135
11416
  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));
11417
+ const lines2 = this.#filteredItems.slice(start, end).map((item, index) => this.#renderItem(item, start + index === this.#selectedIndex, context.width, primaryColumnWidth));
11137
11418
  if (start > 0 || end < this.#filteredItems.length) {
11138
- lines.push(truncateStyledLine(line(span(` (${this.#selectedIndex + 1}/${this.#filteredItems.length})`, { foreground: "muted" })), context.width, ""));
11419
+ lines2.push(truncateStyledLine(line(span(` (${this.#selectedIndex + 1}/${this.#filteredItems.length})`, { foreground: "muted" })), context.width, ""));
11139
11420
  }
11140
- return { lines };
11421
+ return { lines: lines2 };
11141
11422
  }
11142
11423
  handleInput(event) {
11143
11424
  if (!this.focused || event.type !== "key") {
@@ -11171,14 +11452,14 @@ var SelectList = class {
11171
11452
  const description = item.description === void 0 ? void 0 : normalizeDescription(item.description);
11172
11453
  if (description && width > 40) {
11173
11454
  const effectivePrimaryWidth = Math.max(1, Math.min(primaryColumnWidth, width - prefixWidth - 4));
11174
- const label = truncateText2(displayValue(item), Math.max(1, effectivePrimaryWidth - PRIMARY_COLUMN_GAP));
11455
+ const label = truncateText(displayValue(item), Math.max(1, effectivePrimaryWidth - PRIMARY_COLUMN_GAP));
11175
11456
  const spacing = " ".repeat(Math.max(1, effectivePrimaryWidth - displayWidth5(label)));
11176
11457
  const remainingWidth = width - prefixWidth - displayWidth5(label) - displayWidth5(spacing) - 2;
11177
11458
  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, "");
11459
+ return truncateStyledLine(line(prefix, span(label, selected ? { foreground: "accent" } : void 0), span(spacing), span(truncateText(description, remainingWidth), { foreground: "muted" })), width, "");
11179
11460
  }
11180
11461
  }
11181
- return truncateStyledLine(line(prefix, span(truncateText2(displayValue(item), Math.max(1, width - prefixWidth - 2)), selected ? { foreground: "accent" } : void 0)), width, "");
11462
+ return truncateStyledLine(line(prefix, span(truncateText(displayValue(item), Math.max(1, width - prefixWidth - 2)), selected ? { foreground: "accent" } : void 0)), width, "");
11182
11463
  }
11183
11464
  #primaryColumnWidth() {
11184
11465
  const widest = this.#filteredItems.reduce((value, item) => Math.max(value, displayWidth5(displayValue(item)) + PRIMARY_COLUMN_GAP), 0);
@@ -11191,7 +11472,7 @@ function displayValue(item) {
11191
11472
  function normalizeDescription(value) {
11192
11473
  return value.replace(/[\r\n]+/gu, " ").trim();
11193
11474
  }
11194
- function truncateText2(value, width) {
11475
+ function truncateText(value, width) {
11195
11476
  return lineText(truncateStyledLine(line(span(value)), width, ""));
11196
11477
  }
11197
11478
  function displayWidth5(value) {
@@ -11214,13 +11495,13 @@ var VStack = class {
11214
11495
  }
11215
11496
  }
11216
11497
  render(context) {
11217
- const lines = [];
11498
+ const lines2 = [];
11218
11499
  let cursor;
11219
11500
  let rowOffset = 0;
11220
11501
  for (const [index, child] of this.#children.entries()) {
11221
11502
  if (index > 0) {
11222
11503
  const gaps = Array.from({ length: this.#gap }, () => plainLine(""));
11223
- lines.push(...gaps);
11504
+ lines2.push(...gaps);
11224
11505
  rowOffset += gaps.length;
11225
11506
  }
11226
11507
  const rendered = child.render(context);
@@ -11230,11 +11511,11 @@ var VStack = class {
11230
11511
  column: rendered.cursor.column
11231
11512
  };
11232
11513
  }
11233
- lines.push(...rendered.lines);
11514
+ lines2.push(...rendered.lines);
11234
11515
  rowOffset += rendered.lines.length;
11235
11516
  }
11236
11517
  return {
11237
- lines,
11518
+ lines: lines2,
11238
11519
  ...cursor === void 0 ? {} : { cursor }
11239
11520
  };
11240
11521
  }
@@ -11480,19 +11761,19 @@ var ViewHost = class {
11480
11761
  this.#overlays = overlays;
11481
11762
  }
11482
11763
  openSelection(request) {
11483
- return new Promise((resolve2) => {
11764
+ return new Promise((resolve3) => {
11484
11765
  const component = this.#selectionComponent(request);
11485
- this.#open({ id: request.id, component, resolve: resolve2 }, "selector");
11766
+ this.#open({ id: request.id, component, resolve: resolve3 }, "selector");
11486
11767
  });
11487
11768
  }
11488
11769
  openPrompt(request) {
11489
- return new Promise((resolve2) => {
11770
+ return new Promise((resolve3) => {
11490
11771
  const component = new AuthDialog({
11491
11772
  request,
11492
11773
  onSubmit: (value) => this.#close(request.id, value),
11493
11774
  onCancel: () => this.#close(request.id, null)
11494
11775
  });
11495
- this.#open({ id: request.id, component, resolve: resolve2 }, "modal");
11776
+ this.#open({ id: request.id, component, resolve: resolve3 }, "modal");
11496
11777
  });
11497
11778
  }
11498
11779
  handleInput(event) {
@@ -11852,7 +12133,7 @@ var InteractiveTerminalLoop = class {
11852
12133
  this.#disableModifyOtherKeys();
11853
12134
  }
11854
12135
  });
11855
- this.#renderer = new PiMainScreenRenderer(driver);
12136
+ this.#renderer = new MainScreenRenderer(driver);
11856
12137
  }
11857
12138
  get editor() {
11858
12139
  return this.#editor;
@@ -11876,6 +12157,12 @@ var InteractiveTerminalLoop = class {
11876
12157
  if (!this.#ui.shouldQueueDisplayEvent(event)) {
11877
12158
  return;
11878
12159
  }
12160
+ if (isRecord5(event) && event.kind === "tool.output_snapshot" && isRecord5(event.payload)) {
12161
+ const stream = event.payload.stream;
12162
+ 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);
12163
+ if (previous >= 0)
12164
+ this.#work.splice(previous, 1);
12165
+ }
11879
12166
  if (this.#work.length >= _a.WORK_QUEUE_LIMIT - 128 && this.#ui.isHighFrequencyDisplayEvent(event)) {
11880
12167
  this.#ui.recordDisplayDrop(event);
11881
12168
  return;
@@ -11967,8 +12254,8 @@ var InteractiveTerminalLoop = class {
11967
12254
  this.#wakeupEnabled = true;
11968
12255
  try {
11969
12256
  if (!this.#exitRequested) {
11970
- await new Promise((resolve2) => {
11971
- this.#exitResolve = resolve2;
12257
+ await new Promise((resolve3) => {
12258
+ this.#exitResolve = resolve3;
11972
12259
  });
11973
12260
  }
11974
12261
  } finally {
@@ -12153,6 +12440,8 @@ var InteractiveTerminalLoop = class {
12153
12440
  }
12154
12441
  if (action === "editor_newline") {
12155
12442
  this.#applyEditorAction(inputAction(InputActionKind.Newline));
12443
+ } else if (action === "delete_to_line_start") {
12444
+ this.#applyEditorAction(inputAction(InputActionKind.DeleteToLineStart));
12156
12445
  } else if (action === "steer_now") {
12157
12446
  this.#applySteerSubmit();
12158
12447
  } else if (action === "submit_follow_up") {
@@ -12294,18 +12583,18 @@ var InteractiveTerminalLoop = class {
12294
12583
  if (this.#closed || !this.#running) {
12295
12584
  return Promise.resolve(null);
12296
12585
  }
12297
- return new Promise((resolve2) => {
12586
+ return new Promise((resolve3) => {
12298
12587
  if (type === "open_selection") {
12299
12588
  this.#work.push({
12300
12589
  type,
12301
12590
  request,
12302
- resolve: resolve2
12591
+ resolve: resolve3
12303
12592
  });
12304
12593
  } else {
12305
12594
  this.#work.push({
12306
12595
  type,
12307
12596
  request,
12308
- resolve: resolve2
12597
+ resolve: resolve3
12309
12598
  });
12310
12599
  }
12311
12600
  this.#scheduleWakeup();
@@ -12642,11 +12931,11 @@ var TerminalUI = class {
12642
12931
  effort: this.effort
12643
12932
  })
12644
12933
  }).renderWithMetadata({ width: contentWidth, theme: this.theme });
12645
- const lines = compileStyledLines(rendered.lines.map((line2) => truncateStyledLine(line2, contentWidth, "")), contentWidth, this.theme);
12934
+ const lines2 = compileStyledLines(rendered.lines.map((line2) => truncateStyledLine(line2, contentWidth, "")), contentWidth, this.theme);
12646
12935
  return this.#frameBuilder.build({
12647
12936
  ...options,
12648
12937
  compiledMainScreen: {
12649
- lines,
12938
+ lines: lines2,
12650
12939
  cursor: rendered.cursor ?? { row: 0, column: 0 },
12651
12940
  activeStart: rendered.activeStart
12652
12941
  }
@@ -12841,7 +13130,17 @@ var PlainEventSink = class {
12841
13130
  if (text) {
12842
13131
  this.outputFn(`[${correlationId || "unknown"}:${stream}] ${text}`);
12843
13132
  }
13133
+ } else if (kind === "tool.output_snapshot") {
13134
+ return;
12844
13135
  } else if (kind === "tool.finished") {
13136
+ for (const stream of ["stdout", "stderr"]) {
13137
+ if (typeof payload[stream] === "string" && payload[stream] !== "") {
13138
+ this.outputFn(`[${correlationId || "unknown"}:${stream}] ${payload[stream]}`);
13139
+ }
13140
+ }
13141
+ const note = toolOutputNote(payload);
13142
+ if (note)
13143
+ this.outputFn(note);
12845
13144
  const status = String(payload.status ?? "completed");
12846
13145
  this.outputFn(`[tool:${correlationId || "unknown"}] ${status}`);
12847
13146
  } else if (kind === "task.failed") {
@@ -12866,7 +13165,7 @@ function escapeDebugCapture(data) {
12866
13165
  return escaped;
12867
13166
  }
12868
13167
  var StdTerminalDriver = class {
12869
- #rawModeActive = false;
13168
+ #restoreRawMode = null;
12870
13169
  /**
12871
13170
  * Debug capture target from LAOHUANG_DEBUG_LOG: when set, every write is
12872
13171
  * teed to this file with escape sequences made visible (for diagnosing
@@ -12884,8 +13183,7 @@ var StdTerminalDriver = class {
12884
13183
  if (!process.stdin.isTTY) {
12885
13184
  return;
12886
13185
  }
12887
- process.stdin.setRawMode(true);
12888
- this.#rawModeActive = true;
13186
+ this.#restoreRawMode ??= enterTerminalRawMode(process.stdin);
12889
13187
  }
12890
13188
  write(data) {
12891
13189
  if (this.#debugLogPath !== null) {
@@ -12911,10 +13209,9 @@ var StdTerminalDriver = class {
12911
13209
  };
12912
13210
  }
12913
13211
  restore() {
12914
- if (this.#rawModeActive) {
12915
- process.stdin.setRawMode(false);
12916
- this.#rawModeActive = false;
12917
- }
13212
+ const restore = this.#restoreRawMode;
13213
+ this.#restoreRawMode = null;
13214
+ restore?.();
12918
13215
  }
12919
13216
  };
12920
13217
 
@@ -12922,21 +13219,23 @@ var StdTerminalDriver = class {
12922
13219
  import { promises as fs } from "node:fs";
12923
13220
  import { realpathSync as realpathSync3 } from "node:fs";
12924
13221
  import path3 from "node:path";
13222
+ var READ_MAX_BYTES = 50 * 1024;
13223
+ var READ_MAX_LINES = 2e3;
13224
+ var READ_MAX_LINE_CHARACTERS = 2e3;
12925
13225
  var DEFAULT_IO = {
12926
13226
  readFile: (target) => fs.readFile(target, "utf8"),
12927
13227
  writeFile: (target, content) => fs.writeFile(target, content, "utf8")
12928
13228
  };
12929
13229
  var mutationLocks = /* @__PURE__ */ new Map();
12930
13230
  function createFileToolDefinitions(options) {
12931
- const root = resolveNonStrictSync(path3.resolve(options.projectRoot));
13231
+ const root = resolveNonStrictSync(resolveLocalPath(options.projectRoot, process.cwd(), options.pathOptions));
12932
13232
  const io = options.fileIo ?? DEFAULT_IO;
12933
- const maxOutputChars = options.maxOutputChars ?? 2e4;
12934
- const resolvePath2 = (rawPath) => resolveNonStrict(path3.resolve(root, rawPath));
13233
+ const resolvePath = (rawPath) => resolveNonStrict(resolveLocalPath(rawPath, root, options.pathOptions));
12935
13234
  return [
12936
13235
  {
12937
13236
  spec: {
12938
13237
  name: "read",
12939
- description: "Read a UTF-8 text file.",
13238
+ 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
13239
  parameters: {
12941
13240
  type: "object",
12942
13241
  properties: {
@@ -12951,8 +13250,9 @@ function createFileToolDefinitions(options) {
12951
13250
  },
12952
13251
  limit: {
12953
13252
  type: "integer",
12954
- description: "Maximum number of lines to return.",
12955
- minimum: 1
13253
+ description: "Maximum number of lines to return (default and maximum: 2000).",
13254
+ minimum: 1,
13255
+ maximum: READ_MAX_LINES
12956
13256
  }
12957
13257
  },
12958
13258
  required: ["path"],
@@ -12965,26 +13265,57 @@ function createFileToolDefinitions(options) {
12965
13265
  ]
12966
13266
  },
12967
13267
  execute: async (args, execution) => {
12968
- const target = await resolvePath2(stringArgument(args, "path"));
13268
+ const target = await resolvePath(stringArgument(args, "path"));
12969
13269
  const offset = optionalPositiveInteger(args, "offset") ?? 1;
12970
- const limit = optionalPositiveInteger(args, "limit");
13270
+ const limit = optionalPositiveInteger(args, "limit") ?? READ_MAX_LINES;
13271
+ if (limit > READ_MAX_LINES) {
13272
+ throw new Error(`limit must be less than or equal to ${READ_MAX_LINES}`);
13273
+ }
12971
13274
  const content = await io.readFile(target);
12972
13275
  if (execution.isCancelled()) {
12973
13276
  return cancelledResult(execution);
12974
13277
  }
12975
- const lines = content === "" ? [] : content.split(/(?<=\n)/);
12976
- const totalLines = lines.length;
13278
+ const lines2 = content === "" ? [] : content.split(/(?<=\n)/);
13279
+ const totalLines = lines2.length;
12977
13280
  if (offset > Math.max(totalLines, 1)) {
12978
13281
  throw new Error(`offset ${offset} is out of range; the file has ${totalLines} lines`);
12979
13282
  }
12980
- const window = lines.slice(offset - 1, limit === void 0 ? void 0 : offset - 1 + limit);
13283
+ const window = [];
13284
+ const truncatedLineNumbers = [];
13285
+ let bytes = 0;
13286
+ for (const line2 of lines2.slice(offset - 1, offset - 1 + limit)) {
13287
+ const ending = line2.endsWith("\r\n") ? "\r\n" : line2.endsWith("\n") ? "\n" : "";
13288
+ const body = line2.slice(0, line2.length - ending.length);
13289
+ let end = 0;
13290
+ let characters = 0;
13291
+ for (const character of body) {
13292
+ if (characters === READ_MAX_LINE_CHARACTERS)
13293
+ break;
13294
+ end += character.length;
13295
+ characters += 1;
13296
+ }
13297
+ const rendered = body.slice(0, end) + ending;
13298
+ const lineBytes = Buffer.byteLength(rendered, "utf8");
13299
+ if (bytes + lineBytes > READ_MAX_BYTES)
13300
+ break;
13301
+ if (end < body.length)
13302
+ truncatedLineNumbers.push(offset + window.length);
13303
+ window.push(rendered);
13304
+ bytes += lineBytes;
13305
+ }
13306
+ const nextOffset = offset - 1 + window.length < totalLines ? offset + window.length : null;
12981
13307
  return withTouchedPath({
12982
13308
  ok: true,
12983
- content: truncateText(window.join(""), maxOutputChars),
13309
+ content: window.join(""),
12984
13310
  offset,
12985
- limit: limit ?? null,
13311
+ limit,
12986
13312
  total_lines: totalLines,
12987
- has_more: offset - 1 + window.length < totalLines
13313
+ has_more: nextOffset !== null || truncatedLineNumbers.length > 0,
13314
+ next_offset: nextOffset,
13315
+ truncated_line_numbers: truncatedLineNumbers,
13316
+ ...truncatedLineNumbers.length > 0 ? {
13317
+ note: "Listed lines were truncated to 2000 Unicode characters. Use Bash to inspect their omitted content; next_offset only continues to later lines."
13318
+ } : {}
12988
13319
  }, target);
12989
13320
  }
12990
13321
  },
@@ -13012,7 +13343,7 @@ function createFileToolDefinitions(options) {
13012
13343
  executionMode: "sequential",
13013
13344
  execute: async (args, execution) => {
13014
13345
  const rawPath = stringArgument(args, "path");
13015
- const target = await resolvePath2(rawPath);
13346
+ const target = await resolvePath(rawPath);
13016
13347
  return await withFileMutationLock(target, async () => {
13017
13348
  if (execution.isCancelled()) {
13018
13349
  return cancelledResult(execution);
@@ -13062,13 +13393,13 @@ function createFileToolDefinitions(options) {
13062
13393
  ]
13063
13394
  },
13064
13395
  executionMode: "sequential",
13065
- execute: async (args, execution) => executeEdit(args, execution, resolvePath2, io)
13396
+ execute: async (args, execution) => executeEdit(args, execution, resolvePath, io)
13066
13397
  }
13067
13398
  ];
13068
13399
  }
13069
- async function executeEdit(args, execution, resolvePath2, io) {
13400
+ async function executeEdit(args, execution, resolvePath, io) {
13070
13401
  const rawPath = stringArgument(args, "path");
13071
- const target = await resolvePath2(rawPath);
13402
+ const target = await resolvePath(rawPath);
13072
13403
  const edits = editsArgument(args);
13073
13404
  return await withFileMutationLock(target, async () => {
13074
13405
  if (execution.isCancelled()) {
@@ -13109,17 +13440,17 @@ async function executeEdit(args, execution, resolvePath2, io) {
13109
13440
  async function withFileMutationLock(key, fn) {
13110
13441
  const previous = mutationLocks.get(key) ?? Promise.resolve();
13111
13442
  let release;
13112
- const current = new Promise((resolve2) => {
13113
- release = resolve2;
13443
+ const current = new Promise((resolve3) => {
13444
+ release = resolve3;
13114
13445
  });
13115
- const tail = previous.then(() => current);
13116
- mutationLocks.set(key, tail);
13446
+ const tail2 = previous.then(() => current);
13447
+ mutationLocks.set(key, tail2);
13117
13448
  await previous;
13118
13449
  try {
13119
13450
  return await fn();
13120
13451
  } finally {
13121
13452
  release();
13122
- if (mutationLocks.get(key) === tail) {
13453
+ if (mutationLocks.get(key) === tail2) {
13123
13454
  mutationLocks.delete(key);
13124
13455
  }
13125
13456
  }
@@ -13198,14 +13529,232 @@ import { promises as fs2 } from "node:fs";
13198
13529
  import { realpathSync as realpathSync4 } from "node:fs";
13199
13530
  import path4 from "node:path";
13200
13531
 
13532
+ // ../../packages/shell/bash-local/dist/bash-path.js
13533
+ import { accessSync, constants, statSync as statSync3 } from "node:fs";
13534
+ import { homedir as homedir2 } from "node:os";
13535
+ import { posix as posix2, win32 as win322 } from "node:path";
13536
+ function resolveBashPath(options = {}) {
13537
+ const windows = (options.platform ?? process.platform) === "win32";
13538
+ const paths = windows ? win322 : posix2;
13539
+ const env = options.env ?? process.env;
13540
+ const isExecutable = options.isExecutable ?? ((pathname) => {
13541
+ try {
13542
+ if (!statSync3(pathname).isFile())
13543
+ return false;
13544
+ accessSync(pathname, windows ? constants.F_OK : constants.X_OK);
13545
+ return true;
13546
+ } catch {
13547
+ return false;
13548
+ }
13549
+ });
13550
+ const environment = (name) => {
13551
+ const key = windows ? Object.keys(env).find((entry) => entry.toLowerCase() === name.toLowerCase()) : name;
13552
+ return key === void 0 ? void 0 : env[key];
13553
+ };
13554
+ const isLegacyWsl = (pathname) => windows && /[\\/]windows[\\/](?:system32|sysnative)[\\/]bash\.exe$/i.test(pathname);
13555
+ if (options.shellPath !== void 0) {
13556
+ let explicit = options.shellPath;
13557
+ if (explicit === "~" || explicit.startsWith("~/") || windows && explicit.startsWith("~\\")) {
13558
+ explicit = paths.join(options.homeDirectory ?? homedir2(), explicit.slice(2));
13559
+ }
13560
+ if (!paths.isAbsolute(explicit)) {
13561
+ throw new Error("shell_path must be an absolute Bash path (or start with ~/).");
13562
+ }
13563
+ if (isLegacyWsl(explicit)) {
13564
+ throw new Error("shell_path points to a legacy WSL launcher; select Git for Windows bash.exe instead.");
13565
+ }
13566
+ if (!isExecutable(explicit)) {
13567
+ throw new Error(`Configured Bash is missing or not executable: ${explicit}`);
13568
+ }
13569
+ return explicit;
13570
+ }
13571
+ const candidates = [];
13572
+ if (windows) {
13573
+ for (const name of ["ProgramFiles", "ProgramFiles(x86)"]) {
13574
+ const directory = environment(name);
13575
+ if (directory && paths.isAbsolute(directory)) {
13576
+ candidates.push(paths.join(directory, "Git", "bin", "bash.exe"));
13577
+ }
13578
+ }
13579
+ } else {
13580
+ candidates.push("/bin/bash");
13581
+ }
13582
+ for (const entry of (environment("PATH") ?? "").split(paths.delimiter)) {
13583
+ const directory = windows && entry.startsWith('"') && entry.endsWith('"') ? entry.slice(1, -1) : entry;
13584
+ if (paths.isAbsolute(directory)) {
13585
+ candidates.push(paths.join(directory, windows ? "bash.exe" : "bash"));
13586
+ }
13587
+ }
13588
+ for (const candidate of new Set(candidates)) {
13589
+ if (!isLegacyWsl(candidate) && isExecutable(candidate))
13590
+ return candidate;
13591
+ }
13592
+ 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.");
13593
+ }
13594
+
13595
+ // ../../packages/shell/bash-local/dist/bash-output.js
13596
+ import { mkdir, mkdtemp, open, readdir, rm, stat, writeFile } from "node:fs/promises";
13597
+ import { homedir as homedir3 } from "node:os";
13598
+ import { join as join6, resolve as resolve2 } from "node:path";
13599
+ var BASH_OUTPUT_MAX_BYTES = 50 * 1024;
13600
+ var BASH_OUTPUT_MAX_LINES = 2e3;
13601
+ var OUTPUT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
13602
+ var OUTPUT_FILE_MAX_BYTES = 64 * 1024 * 1024;
13603
+ function lines(text) {
13604
+ return text === "" ? 0 : text.split("\n").length;
13605
+ }
13606
+ function tail(value, maxBytes, maxLines) {
13607
+ if (maxBytes <= 0 || maxLines <= 0)
13608
+ return { text: "", startMidLine: value.text !== "" || value.startMidLine };
13609
+ const bytes = Buffer.from(value.text);
13610
+ let start = Math.max(0, bytes.length - maxBytes);
13611
+ while (start < bytes.length && (bytes[start] & 192) === 128)
13612
+ start++;
13613
+ let text = bytes.subarray(start).toString("utf8");
13614
+ let startMidLine = start > 0 ? bytes[start - 1] !== 10 : value.startMidLine;
13615
+ const parts = text.split("\n");
13616
+ if (parts.length > maxLines) {
13617
+ text = parts.slice(-maxLines).join("\n");
13618
+ startMidLine = false;
13619
+ }
13620
+ return { text, startMidLine };
13621
+ }
13622
+ var BashOutput = class {
13623
+ maxBytes;
13624
+ maxLines;
13625
+ #directory;
13626
+ #fileLimit;
13627
+ #tails = {
13628
+ stdout: { text: "", startMidLine: false },
13629
+ stderr: { text: "", startMidLine: false }
13630
+ };
13631
+ #totals = { stdout: 0, stderr: 0 };
13632
+ #handles = {};
13633
+ #pending = Promise.resolve();
13634
+ #archiveDirectory = null;
13635
+ #fileBytes = 0;
13636
+ #spillAttempted = false;
13637
+ files = null;
13638
+ fileError = null;
13639
+ revision = 0;
13640
+ constructor(options = {}) {
13641
+ this.maxBytes = options.maxOutputBytes ?? BASH_OUTPUT_MAX_BYTES;
13642
+ this.maxLines = options.maxOutputLines ?? BASH_OUTPUT_MAX_LINES;
13643
+ this.#fileLimit = options.maxOutputFileBytes ?? OUTPUT_FILE_MAX_BYTES;
13644
+ for (const limit of [this.maxBytes, this.maxLines, this.#fileLimit]) {
13645
+ if (!Number.isSafeInteger(limit) || limit < 1)
13646
+ throw new RangeError("Bash output limits must be positive integers");
13647
+ }
13648
+ this.#directory = resolve2(options.outputDirectory ?? join6(homedir3(), ".laohuang", "tool-output"));
13649
+ }
13650
+ append(stream, text) {
13651
+ const next = this.#pending.then(() => this.#append(stream, text));
13652
+ this.#pending = next.catch(() => {
13653
+ });
13654
+ return next;
13655
+ }
13656
+ async #append(stream, text) {
13657
+ if (!text)
13658
+ return;
13659
+ const combined = { ...this.#tails[stream], text: this.#tails[stream].text + text };
13660
+ const other = stream === "stdout" ? "stderr" : "stdout";
13661
+ 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)) {
13662
+ this.#spillAttempted = true;
13663
+ await this.#startArchive();
13664
+ for (const name of ["stdout", "stderr"])
13665
+ await this.#write(name, this.#tails[name].text);
13666
+ }
13667
+ await this.#write(stream, text);
13668
+ this.#totals[stream] += Buffer.byteLength(text);
13669
+ this.#tails[stream] = tail(combined, this.maxBytes, this.maxLines);
13670
+ this.revision++;
13671
+ }
13672
+ snapshot() {
13673
+ const reserved = tail(this.#tails.stderr, Math.floor(this.maxBytes / 2), Math.floor(this.maxLines / 2));
13674
+ const stdout = tail(this.#tails.stdout, this.maxBytes - Buffer.byteLength(reserved.text), this.maxLines - lines(reserved.text));
13675
+ const stderr = tail(this.#tails.stderr, this.maxBytes - Buffer.byteLength(stdout.text), this.maxLines - lines(stdout.text));
13676
+ return {
13677
+ stdout: { ...stdout, truncated: Buffer.byteLength(stdout.text) < this.#totals.stdout },
13678
+ stderr: { ...stderr, truncated: Buffer.byteLength(stderr.text) < this.#totals.stderr }
13679
+ };
13680
+ }
13681
+ async #startArchive() {
13682
+ try {
13683
+ await mkdir(this.#directory, { recursive: true, mode: 448 });
13684
+ await this.#removeExpired();
13685
+ this.#archiveDirectory = await mkdtemp(join6(this.#directory, "bash-"));
13686
+ const files = {
13687
+ stdout: join6(this.#archiveDirectory, "stdout.log"),
13688
+ stderr: join6(this.#archiveDirectory, "stderr.log")
13689
+ };
13690
+ for (const stream of ["stdout", "stderr"])
13691
+ this.#handles[stream] = await open(files[stream], "wx", 384);
13692
+ this.files = files;
13693
+ } catch (error) {
13694
+ this.fileError = `Cannot save Bash output: ${String(error)}`;
13695
+ }
13696
+ }
13697
+ async #write(stream, text) {
13698
+ const handle = this.#handles[stream];
13699
+ if (!handle || this.fileError !== null || text === "")
13700
+ return;
13701
+ try {
13702
+ const bytes = Buffer.from(text);
13703
+ const available = Math.max(0, this.#fileLimit - this.#fileBytes);
13704
+ let end = Math.min(bytes.length, available);
13705
+ while (end > 0 && end < bytes.length && (bytes[end] & 192) === 128)
13706
+ end--;
13707
+ if (end > 0)
13708
+ await handle.writeFile(bytes.subarray(0, end));
13709
+ this.#fileBytes += end;
13710
+ if (end < bytes.length)
13711
+ this.fileError = `Bash output file limit reached (${this.#fileLimit} bytes); files contain partial output`;
13712
+ } catch (error) {
13713
+ this.fileError = `Cannot save Bash output: ${String(error)}`;
13714
+ }
13715
+ }
13716
+ async close() {
13717
+ await this.#pending;
13718
+ for (const handle of Object.values(this.#handles)) {
13719
+ try {
13720
+ await handle.close();
13721
+ } catch (error) {
13722
+ this.fileError ??= `Cannot close Bash output: ${String(error)}`;
13723
+ }
13724
+ }
13725
+ if (this.#archiveDirectory !== null) {
13726
+ try {
13727
+ await writeFile(join6(this.#archiveDirectory, "completed"), "", { mode: 384 });
13728
+ } catch {
13729
+ }
13730
+ }
13731
+ }
13732
+ async #removeExpired() {
13733
+ for (const entry of await readdir(this.#directory, { withFileTypes: true })) {
13734
+ if (!entry.isDirectory() || !entry.name.startsWith("bash-"))
13735
+ continue;
13736
+ const directory = join6(this.#directory, entry.name);
13737
+ try {
13738
+ if (Date.now() - (await stat(join6(directory, "completed"))).mtimeMs > OUTPUT_RETENTION_MS) {
13739
+ await rm(directory, { recursive: true, force: true });
13740
+ }
13741
+ } catch {
13742
+ }
13743
+ }
13744
+ }
13745
+ };
13746
+
13201
13747
  // ../../packages/shell/bash-local/dist/bash-runner.js
13202
- import { spawn } from "node:child_process";
13748
+ import { execFile, spawn } from "node:child_process";
13203
13749
  import { constants as osConstants } from "node:os";
13750
+ import { win32 as win323 } from "node:path";
13204
13751
  import { StringDecoder } from "node:string_decoder";
13205
- var FLUSH_INTERVAL_MS = 40;
13206
- var FLUSH_CHARS = 4096;
13752
+ var FLUSH_INTERVAL_MS = 100;
13207
13753
  var TERMINATION_GRACE_MS = 2e3;
13208
13754
  var WATCHDOG_INTERVAL_MS = 10;
13755
+ var POST_EXIT_IDLE_MS = 100;
13756
+ var TERMINATION_EXIT_GRACE_MS = 1e3;
13757
+ var TASKKILL_TIMEOUT_MS = 5e3;
13209
13758
  var MODEL_API_KEY_ENV_NAMES = [
13210
13759
  "OPENAI_API_KEY",
13211
13760
  "DEEPSEEK_API_KEY",
@@ -13246,11 +13795,10 @@ var ToolExecutionContext = class {
13246
13795
  if (sink == null)
13247
13796
  return;
13248
13797
  if (typeof sink === "function") {
13249
- sink(kind, payload);
13250
- return;
13798
+ return sink(kind, payload);
13251
13799
  }
13252
13800
  if (typeof sink.publish === "function") {
13253
- sink.publish(kind, {
13801
+ return sink.publish(kind, {
13254
13802
  source: "tool",
13255
13803
  session_id: this.sessionId ?? "local",
13256
13804
  task_id: this.taskId,
@@ -13268,6 +13816,12 @@ var BashResult = class {
13268
13816
  error;
13269
13817
  durationMs;
13270
13818
  truncated;
13819
+ outputComplete;
13820
+ outputFiles;
13821
+ outputFileComplete;
13822
+ outputFileError;
13823
+ stdoutStartMidLine;
13824
+ stderrStartMidLine;
13271
13825
  constructor(init) {
13272
13826
  this.status = init.status;
13273
13827
  this.stdout = init.stdout ?? "";
@@ -13276,6 +13830,12 @@ var BashResult = class {
13276
13830
  this.error = init.error ?? null;
13277
13831
  this.durationMs = init.durationMs ?? 0;
13278
13832
  this.truncated = init.truncated ?? false;
13833
+ this.outputComplete = init.outputComplete ?? true;
13834
+ this.outputFiles = init.outputFiles ?? null;
13835
+ this.outputFileComplete = init.outputFileComplete ?? false;
13836
+ this.outputFileError = init.outputFileError ?? null;
13837
+ this.stdoutStartMidLine = init.stdoutStartMidLine ?? false;
13838
+ this.stderrStartMidLine = init.stderrStartMidLine ?? false;
13279
13839
  }
13280
13840
  get ok() {
13281
13841
  return this.status === "completed" && this.exitCode === 0;
@@ -13288,66 +13848,21 @@ var BashResult = class {
13288
13848
  stdout: this.stdout,
13289
13849
  stderr: this.stderr,
13290
13850
  duration_ms: this.durationMs,
13291
- truncated: this.truncated
13851
+ truncated: this.truncated,
13852
+ output_complete: this.outputComplete,
13853
+ output_files: this.outputFiles,
13854
+ output_file_complete: this.outputFileComplete,
13855
+ stdout_start_mid_line: this.stdoutStartMidLine,
13856
+ stderr_start_mid_line: this.stderrStartMidLine
13292
13857
  };
13858
+ if (this.outputFileError)
13859
+ result["output_file_error"] = this.outputFileError;
13293
13860
  if (this.error) {
13294
13861
  result["error"] = this.error;
13295
13862
  }
13296
13863
  return result;
13297
13864
  }
13298
13865
  };
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
13866
  var TerminalSanitizer = class {
13352
13867
  state = "normal";
13353
13868
  feed(text) {
@@ -13402,15 +13917,31 @@ var TerminalSanitizer = class {
13402
13917
  }
13403
13918
  };
13404
13919
  function sleep(ms) {
13405
- return new Promise((resolve2) => setTimeout(resolve2, ms));
13920
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
13406
13921
  }
13407
13922
  function errnoCode(error) {
13408
13923
  return error?.code;
13409
13924
  }
13410
- async function terminateProcessGroup(child, graceMs = TERMINATION_GRACE_MS) {
13925
+ async function terminateProcessTree(child, graceMs = TERMINATION_GRACE_MS) {
13411
13926
  const pid = child.pid;
13412
13927
  if (pid === void 0)
13413
13928
  return;
13929
+ if (process.platform === "win32") {
13930
+ const taskkill = win323.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe");
13931
+ await new Promise((resolve3, reject) => {
13932
+ execFile(taskkill, ["/F", "/T", "/PID", String(pid)], {
13933
+ windowsHide: true,
13934
+ timeout: TASKKILL_TIMEOUT_MS,
13935
+ killSignal: "SIGKILL"
13936
+ }, (error) => {
13937
+ if (error)
13938
+ reject(new Error(`taskkill failed: ${error.message}`));
13939
+ else
13940
+ resolve3();
13941
+ });
13942
+ });
13943
+ return;
13944
+ }
13414
13945
  try {
13415
13946
  process.kill(-pid, "SIGTERM");
13416
13947
  } catch (error) {
@@ -13463,25 +13994,55 @@ function toExitCode(code, signal) {
13463
13994
  async function runBash(command, options) {
13464
13995
  const invokedAt = performance.now();
13465
13996
  const execution = options.context ?? new ToolExecutionContext();
13997
+ const output = new BashOutput(options);
13998
+ let publicationError = null;
13999
+ const publish = async (kind, payload) => {
14000
+ try {
14001
+ await execution.publish(kind, payload);
14002
+ } catch (error) {
14003
+ publicationError ??= `Bash event publication failed: ${String(error)}`;
14004
+ }
14005
+ };
14006
+ const finish = async (result2) => {
14007
+ await publish("tool.finished", result2.asDict());
14008
+ if (publicationError !== null) {
14009
+ result2.status = "failed";
14010
+ if (!result2.error?.includes(publicationError)) {
14011
+ result2.error = [result2.error, publicationError].filter(Boolean).join("; ");
14012
+ }
14013
+ }
14014
+ return result2;
14015
+ };
13466
14016
  if (execution.isCancelled()) {
13467
14017
  const result2 = new BashResult({
13468
14018
  status: "cancelled",
13469
14019
  error: execution.cancellationReason,
13470
14020
  durationMs: Math.trunc(performance.now() - invokedAt)
13471
14021
  });
13472
- execution.publish("tool.finished", result2.asDict());
13473
- return result2;
14022
+ return await finish(result2);
13474
14023
  }
13475
14024
  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));
14025
+ let child;
14026
+ try {
14027
+ const shellPath = resolveBashPath({ shellPath: options.shellPath, env: environment });
14028
+ child = spawn(shellPath, ["-lc", command], {
14029
+ cwd: options.cwd,
14030
+ env: environment,
14031
+ stdio: ["ignore", "pipe", "pipe"],
14032
+ detached: process.platform !== "win32",
14033
+ windowsHide: true
14034
+ });
14035
+ } catch (error) {
14036
+ const result2 = new BashResult({
14037
+ status: "spawn_failed",
14038
+ error: String(error),
14039
+ durationMs: Math.trunc(performance.now() - invokedAt)
14040
+ });
14041
+ return await finish(result2);
14042
+ }
14043
+ const spawnError = await new Promise((resolve3) => {
14044
+ child.once("spawn", () => resolve3(null));
14045
+ child.once("error", (error) => resolve3(error));
13485
14046
  });
13486
14047
  if (spawnError) {
13487
14048
  const result2 = new BashResult({
@@ -13489,76 +14050,130 @@ async function runBash(command, options) {
13489
14050
  error: String(spawnError),
13490
14051
  durationMs: Math.trunc(performance.now() - invokedAt)
13491
14052
  });
13492
- execution.publish("tool.finished", result2.asDict());
13493
- return result2;
14053
+ return await finish(result2);
13494
14054
  }
13495
14055
  child.on("error", () => {
13496
14056
  });
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)
14057
+ const started = publish("tool.started", { name: "bash", arguments: { command } });
14058
+ let publishing = null;
14059
+ let publishedRevision = 0;
14060
+ let exited = false;
14061
+ let lastOutputAt = performance.now();
14062
+ let outputIncomplete = false;
14063
+ const ingesting = { stdout: false, stderr: false };
14064
+ const closeOutput = (force = false) => {
14065
+ for (const stream of ["stdout", "stderr"]) {
14066
+ const pipe = child[stream];
14067
+ if (!force && (ingesting[stream] || (pipe?.readableLength ?? 0) > 0))
14068
+ continue;
14069
+ if (pipe && !pipe.readableEnded && !pipe.destroyed) {
14070
+ outputIncomplete = true;
14071
+ pipe.destroy();
14072
+ }
14073
+ }
13504
14074
  };
13505
- const pending = { stdout: "", stderr: "" };
13506
- const streamSequence = { stdout: 0, stderr: 0 };
13507
- const flush = (stream) => {
13508
- const text = pending[stream];
13509
- if (!text)
14075
+ const flush = () => {
14076
+ if (publishing !== null || publicationError !== null || publishedRevision === output.revision)
13510
14077
  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]
14078
+ const snapshot2 = output.snapshot();
14079
+ publishedRevision = output.revision;
14080
+ const revision = publishedRevision;
14081
+ publishing = (async () => {
14082
+ await started;
14083
+ for (const stream of ["stdout", "stderr"]) {
14084
+ if (publicationError !== null)
14085
+ break;
14086
+ await publish("tool.output_snapshot", {
14087
+ name: "bash",
14088
+ stream,
14089
+ text: snapshot2[stream].text,
14090
+ stream_sequence: revision,
14091
+ truncated: snapshot2[stream].truncated,
14092
+ start_mid_line: snapshot2[stream].startMidLine
14093
+ });
14094
+ }
14095
+ })().finally(() => {
14096
+ publishing = null;
13519
14097
  });
13520
14098
  };
13521
14099
  const readPipe = async (stream, pipe) => {
13522
14100
  const decoder = new StringDecoder("utf8");
13523
14101
  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
14102
  try {
13532
14103
  for await (const chunk of pipe) {
13533
- ingest(sanitizer.feed(decoder.write(chunk)));
14104
+ lastOutputAt = performance.now();
14105
+ ingesting[stream] = true;
14106
+ try {
14107
+ await output.append(stream, sanitizer.feed(decoder.write(chunk)));
14108
+ } finally {
14109
+ ingesting[stream] = false;
14110
+ lastOutputAt = performance.now();
14111
+ }
13534
14112
  }
13535
- ingest(sanitizer.feed(decoder.end()));
13536
14113
  } catch {
14114
+ outputIncomplete = true;
14115
+ } finally {
14116
+ await output.append(stream, sanitizer.feed(decoder.end()));
13537
14117
  }
13538
14118
  };
13539
14119
  const stdoutDone = readPipe("stdout", child.stdout);
13540
14120
  const stderrDone = readPipe("stderr", child.stderr);
13541
- const exitInfo = new Promise((resolve2) => {
13542
- child.once("exit", (code2, signal2) => resolve2({ code: code2, signal: signal2 }));
14121
+ let resolveExit;
14122
+ const exitInfo = new Promise((resolve3) => {
14123
+ resolveExit = resolve3;
13543
14124
  });
14125
+ const onExit = (code2, signal2) => {
14126
+ exited = true;
14127
+ lastOutputAt = performance.now();
14128
+ resolveExit({ code: code2, signal: signal2 });
14129
+ };
14130
+ child.once("exit", onExit);
13544
14131
  let terminalStatus = null;
13545
14132
  const startedAt = performance.now();
13546
14133
  let lastFlush = startedAt;
13547
14134
  const timeoutMs = options.timeout * 1e3;
14135
+ let termination = null;
14136
+ let terminationCompletedAt = null;
14137
+ let cleanupError = null;
14138
+ const terminate = () => {
14139
+ if (termination !== null)
14140
+ return;
14141
+ termination = terminateProcessTree(child).catch((error) => {
14142
+ cleanupError = error instanceof Error ? error.message : String(error);
14143
+ try {
14144
+ child.kill("SIGKILL");
14145
+ } catch {
14146
+ }
14147
+ }).finally(() => {
14148
+ terminationCompletedAt = performance.now();
14149
+ });
14150
+ };
13548
14151
  const watchdog = setInterval(() => {
13549
14152
  const now = performance.now();
13550
14153
  if (terminalStatus === null) {
13551
- if (execution.isCancelled()) {
14154
+ if (publicationError !== null) {
14155
+ terminalStatus = "failed";
14156
+ terminate();
14157
+ } else if (execution.isCancelled()) {
13552
14158
  terminalStatus = "cancelled";
13553
- void terminateProcessGroup(child);
14159
+ terminate();
13554
14160
  } else if (options.timeout >= 0 && now - startedAt >= timeoutMs) {
13555
14161
  terminalStatus = "timed_out";
13556
- void terminateProcessGroup(child);
14162
+ terminate();
14163
+ }
14164
+ }
14165
+ if (exited && now - lastOutputAt >= POST_EXIT_IDLE_MS)
14166
+ closeOutput();
14167
+ if (terminationCompletedAt !== null && now - terminationCompletedAt >= TERMINATION_EXIT_GRACE_MS) {
14168
+ closeOutput(true);
14169
+ if (!exited) {
14170
+ cleanupError ??= "Bash did not exit after process termination";
14171
+ child.unref();
14172
+ resolveExit({ code: null, signal: null });
13557
14173
  }
13558
14174
  }
13559
14175
  if (now - lastFlush >= FLUSH_INTERVAL_MS) {
13560
- flush("stdout");
13561
- flush("stderr");
14176
+ flush();
13562
14177
  lastFlush = now;
13563
14178
  }
13564
14179
  }, WATCHDOG_INTERVAL_MS);
@@ -13567,18 +14182,21 @@ async function runBash(command, options) {
13567
14182
  await Promise.all([stdoutDone, stderrDone, exitInfo]);
13568
14183
  } finally {
13569
14184
  clearInterval(watchdog);
13570
- if (child.exitCode === null && child.signalCode === null) {
13571
- await terminateProcessGroup(child);
13572
- }
13573
- }
13574
- flush("stdout");
13575
- flush("stderr");
14185
+ child.removeListener("exit", onExit);
14186
+ await termination;
14187
+ await output.close();
14188
+ }
14189
+ await started;
14190
+ await publishing;
14191
+ flush();
14192
+ await publishing;
13576
14193
  const { code, signal } = await exitInfo;
13577
14194
  const exitCode = toExitCode(code, signal);
13578
- const stdout = outputs.stdout.render();
13579
- const stderr = outputs.stderr.render();
14195
+ const snapshot = output.snapshot();
14196
+ const stdout = snapshot.stdout.text;
14197
+ const stderr = snapshot.stderr.text;
13580
14198
  const durationMs = Math.trunc(performance.now() - invokedAt);
13581
- const truncated = outputs.stdout.truncated || outputs.stderr.truncated;
14199
+ const truncated = snapshot.stdout.truncated || snapshot.stderr.truncated;
13582
14200
  let result;
13583
14201
  if (terminalStatus === "cancelled") {
13584
14202
  result = new BashResult({
@@ -13620,20 +14238,35 @@ async function runBash(command, options) {
13620
14238
  truncated
13621
14239
  });
13622
14240
  }
13623
- execution.publish("tool.finished", result.asDict());
13624
- return result;
14241
+ if (cleanupError !== null) {
14242
+ result.error = `${result.error ?? "Bash execution failed"}; Process cleanup failed: ${cleanupError}`;
14243
+ }
14244
+ result.outputComplete = !outputIncomplete;
14245
+ result.outputFiles = output.files;
14246
+ result.outputFileComplete = output.files !== null && output.fileError === null && !outputIncomplete;
14247
+ result.outputFileError = output.fileError;
14248
+ result.stdoutStartMidLine = snapshot.stdout.startMidLine;
14249
+ result.stderrStartMidLine = snapshot.stderr.startMidLine;
14250
+ if (publicationError !== null) {
14251
+ result.status = "failed";
14252
+ result.error = publicationError + (cleanupError === null ? "" : `; Process cleanup failed: ${cleanupError}`);
14253
+ }
14254
+ return await finish(result);
13625
14255
  }
13626
14256
 
13627
14257
  // ../../packages/shell/tool-bash/dist/tool-bash.js
13628
14258
  function createBashToolDefinition(options) {
13629
- const root = resolveNonStrictSync2(path4.resolve(options.projectRoot));
14259
+ const pathOptions = {
14260
+ env: options.env ?? process.env,
14261
+ shellPath: () => resolveBashPath({ shellPath: options.shellPath, env: options.env })
14262
+ };
14263
+ const root = resolveNonStrictSync2(resolveLocalPath(options.projectRoot, process.cwd(), pathOptions));
13630
14264
  const bashTimeoutSeconds = options.bashTimeoutSeconds ?? 120;
13631
- const maxOutputChars = options.maxOutputChars ?? 2e4;
13632
14265
  const runBash2 = options.runBash ?? defaultRunBash;
13633
14266
  return {
13634
14267
  spec: {
13635
14268
  name: "bash",
13636
- description: "Run a Bash command.",
14269
+ 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
14270
  parameters: {
13638
14271
  type: "object",
13639
14272
  properties: {
@@ -13659,39 +14292,52 @@ function createBashToolDefinition(options) {
13659
14292
  "Supply a concise description of what the command does.",
13660
14293
  "Use workdir instead of cd.",
13661
14294
  "Each call runs in an independent shell; state does not persist between calls.",
13662
- "On a non-zero exit, inspect the output before retrying."
14295
+ "On a non-zero exit, inspect the output before retrying.",
14296
+ "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
14297
  ]
13664
14298
  },
13665
14299
  execute: async (args, execution) => {
13666
14300
  const command = stringArgument(args, "command");
13667
14301
  stringArgument(args, "description");
13668
14302
  const workdir = args["workdir"];
13669
- const cwd = workdir === void 0 ? root : await resolvePath(root, stringArgument(args, "workdir"));
14303
+ const cwd = workdir === void 0 ? root : await resolveNonStrict2(resolveLocalPath(stringArgument(args, "workdir"), root, pathOptions));
13670
14304
  const timeoutMs = optionalPositiveInteger(args, "timeoutMs");
13671
14305
  return await runBash2(command, {
13672
14306
  cwd,
14307
+ shellPath: options.shellPath,
13673
14308
  timeoutSeconds: timeoutMs === void 0 ? bashTimeoutSeconds : timeoutMs / 1e3,
13674
- maxOutputChars,
14309
+ maxOutputBytes: options.maxOutputBytes,
14310
+ maxOutputLines: options.maxOutputLines,
14311
+ outputDirectory: options.outputDirectory,
14312
+ maxOutputFileBytes: options.maxOutputFileBytes,
13675
14313
  context: execution,
13676
- env: process.env
14314
+ env: options.env ?? process.env
13677
14315
  });
13678
14316
  }
13679
14317
  };
13680
14318
  }
13681
14319
  async function defaultRunBash(command, options) {
13682
- const context = typeof options.context.publish === "function" ? options.context : null;
14320
+ const execution = options.context;
14321
+ const context = {
14322
+ isCancelled: () => execution.isCancelled(),
14323
+ get cancellationReason() {
14324
+ return execution.cancellationReason;
14325
+ },
14326
+ publish: (kind, payload) => execution.publish?.(kind, payload)
14327
+ };
13683
14328
  const result = await runBash(command, {
13684
14329
  cwd: options.cwd,
14330
+ shellPath: options.shellPath,
13685
14331
  timeout: options.timeoutSeconds,
13686
- maxOutputChars: options.maxOutputChars,
14332
+ maxOutputBytes: options.maxOutputBytes,
14333
+ maxOutputLines: options.maxOutputLines,
14334
+ outputDirectory: options.outputDirectory,
14335
+ maxOutputFileBytes: options.maxOutputFileBytes,
13687
14336
  context,
13688
14337
  env: options.env
13689
14338
  });
13690
14339
  return result.asDict();
13691
14340
  }
13692
- async function resolvePath(root, rawPath) {
13693
- return await resolveNonStrict2(path4.resolve(root, rawPath));
13694
- }
13695
14341
  async function resolveNonStrict2(pathname) {
13696
14342
  const missing = [];
13697
14343
  let current = pathname;
@@ -13963,23 +14609,13 @@ function sessionDisplayTitle(session) {
13963
14609
  return lastUserText2 === "" ? "Untitled session" : lastUserText2;
13964
14610
  }
13965
14611
  function sessionDisplayDescription(session, options) {
13966
- const displayPath = displayPathFor(session.cwd ?? session.projectRoot ?? "", options.homeDirectory);
14612
+ const displayPath = displayLocalPath(session.cwd ?? session.projectRoot ?? "", options.homeDirectory);
13967
14613
  const updated = relativeTimeLabel(session.updatedAt, options.now);
13968
14614
  return displayPath === "" ? updated : `${updated} ${displayPath}`;
13969
14615
  }
13970
14616
  function cleanSingleLine(value) {
13971
14617
  return value?.replace(/\s+/g, " ").trim() ?? "";
13972
14618
  }
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
14619
  function relativeTimeLabel(updatedAt, now) {
13984
14620
  const updated = new Date(updatedAt);
13985
14621
  const timestamp = updated.getTime();
@@ -15595,7 +16231,7 @@ function defaultSecretInputFn(prompt) {
15595
16231
  }
15596
16232
  const byte = Buffer.alloc(1);
15597
16233
  let answer = "";
15598
- process.stdin.setRawMode(true);
16234
+ const restoreRawMode = enterTerminalRawMode(process.stdin);
15599
16235
  try {
15600
16236
  for (; ; ) {
15601
16237
  const bytesRead = readSync2(0, byte, 0, 1, null);
@@ -15621,7 +16257,7 @@ function defaultSecretInputFn(prompt) {
15621
16257
  answer += byte.toString("utf8", 0, 1);
15622
16258
  }
15623
16259
  } finally {
15624
- process.stdin.setRawMode(false);
16260
+ restoreRawMode();
15625
16261
  }
15626
16262
  process.stderr.write("\n");
15627
16263
  return answer;
@@ -15790,8 +16426,8 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
15790
16426
  const done = (async () => {
15791
16427
  for (; ; ) {
15792
16428
  if (queue.length === 0) {
15793
- await new Promise((resolve2) => {
15794
- wake = resolve2;
16429
+ await new Promise((resolve3) => {
16430
+ wake = resolve3;
15795
16431
  });
15796
16432
  wake = null;
15797
16433
  }
@@ -15837,14 +16473,14 @@ function startCoordinator(session, commandHandler, presenter, suggestCommand) {
15837
16473
  };
15838
16474
  }
15839
16475
  function delay(ms) {
15840
- return new Promise((resolve2) => {
15841
- setTimeout(resolve2, ms);
16476
+ return new Promise((resolve3) => {
16477
+ setTimeout(resolve3, ms);
15842
16478
  });
15843
16479
  }
15844
16480
  async function settlesWithin(promise, ms) {
15845
16481
  let timer;
15846
- const timeout = new Promise((resolve2) => {
15847
- timer = setTimeout(() => resolve2(false), ms);
16482
+ const timeout = new Promise((resolve3) => {
16483
+ timer = setTimeout(() => resolve3(false), ms);
15848
16484
  });
15849
16485
  const settled = promise.then(
15850
16486
  () => true,
@@ -16070,10 +16706,10 @@ async function main(argv, options = {}) {
16070
16706
  const configPath = options.configPath ?? defaultConfigPath(environ);
16071
16707
  const manager = new ConfigManager(configPath);
16072
16708
  const credentials = new CredentialStore(
16073
- options.credentialsPath ?? join6(dirname4(configPath), "credentials.json")
16709
+ options.credentialsPath ?? join7(dirname4(configPath), "credentials.json")
16074
16710
  );
16075
16711
  const modelCatalogStore = new ModelCatalogStore(
16076
- options.modelsPath ?? join6(dirname4(configPath), "models.json")
16712
+ options.modelsPath ?? join7(dirname4(configPath), "models.json")
16077
16713
  );
16078
16714
  const modelPlatform = await createPiAiPlatform({
16079
16715
  credentials,
@@ -16186,12 +16822,20 @@ async function main(argv, options = {}) {
16186
16822
  outputFn(`Verified: ${provider.verified ? "yes" : "no"}`);
16187
16823
  outputFn(`Configuration: ${configPath}`);
16188
16824
  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;
16825
+ let bashAvailable = true;
16826
+ try {
16827
+ outputFn(`Bash: ${resolveBashPath({ shellPath: manager.getShellPath(), env: environ })}`);
16828
+ } catch (error) {
16829
+ bashAvailable = false;
16830
+ outputFn(`Bash: ${errorMessage11(error)}`);
16831
+ }
16832
+ return provider !== void 0 && auth.configured && refreshOk && model !== void 0 && bashAvailable ? 0 : 1;
16191
16833
  }
16192
16834
  let config;
16835
+ let shellPath;
16193
16836
  try {
16194
- if (existsSync7(configPath)) {
16837
+ shellPath = manager.getShellPath();
16838
+ if (existsSync8(configPath)) {
16195
16839
  config = manager.resolve({
16196
16840
  environ,
16197
16841
  profile: args.profile,
@@ -16273,8 +16917,11 @@ async function main(argv, options = {}) {
16273
16917
  terminalUi.state.model = config.model;
16274
16918
  }
16275
16919
  const toolRegistry = new ToolRegistry([
16276
- ...createFileToolDefinitions({ projectRoot }),
16277
- createBashToolDefinition({ projectRoot })
16920
+ ...createFileToolDefinitions({ projectRoot, pathOptions: {
16921
+ env: environ,
16922
+ shellPath: () => resolveBashPath({ shellPath, env: environ })
16923
+ } }),
16924
+ createBashToolDefinition({ projectRoot, shellPath, env: environ })
16278
16925
  ]);
16279
16926
  const activeConversationHistory = {
16280
16927
  appendUser: (input) => {
@@ -16503,7 +17150,7 @@ async function main(argv, options = {}) {
16503
17150
  sessionController,
16504
17151
  onComposerText: (text) => terminalUi?.setComposerText(text),
16505
17152
  onSessionChanged: refreshSessionView,
16506
- homeDirectory: environ["HOME"],
17153
+ homeDirectory: getHomeDirectory({ env: environ }),
16507
17154
  onModelSelected: (selection) => {
16508
17155
  semanticClassifier.configure({
16509
17156
  provider: selection.config.provider,
@@ -16596,7 +17243,7 @@ async function main(argv, options = {}) {
16596
17243
  return cleanShutdown ? 0 : 1;
16597
17244
  }
16598
17245
  function defaultSessionsRoot(environ) {
16599
- return join6(environ["HOME"] ?? process.cwd(), ".laohuang", "sessions");
17246
+ return join7(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
16600
17247
  }
16601
17248
  function defaultContextPolicy() {
16602
17249
  return {