@hydra-acp/cli 0.1.62 → 0.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -86,6 +86,12 @@ var init_paths = __esm({
86
86
  sessionDir: (id) => path.join(hydraHome(), "sessions", id),
87
87
  sessionFile: (id) => path.join(hydraHome(), "sessions", id, "meta.json"),
88
88
  historyFile: (id) => path.join(hydraHome(), "sessions", id, "history.jsonl"),
89
+ // Content-addressed store for heavy tool payload (diff bodies, stdout)
90
+ // externalized out of history.jsonl. One file per unique blob, named by
91
+ // its sha256, so repeated identical content (e.g. an agent re-emitting
92
+ // the same full-file diff on every status tick) dedupes to one file.
93
+ toolsDir: (id) => path.join(hydraHome(), "sessions", id, "tools"),
94
+ toolBlobFile: (id, hash) => path.join(hydraHome(), "sessions", id, "tools", hash),
89
95
  // Persisted prompt queue for a session. ndjson, one record per
90
96
  // entry. Survives daemon restarts so queued prompts get a chance to
91
97
  // run rather than being silently lost. Entries are removed BEFORE
@@ -499,6 +505,29 @@ var init_config = __esm({
499
505
  // suppress them — the TUI hotkey ^T toggles this at runtime without
500
506
  // persisting back to config.
501
507
  showThoughts: z.boolean().default(true),
508
+ // How the terminal renders East-Asian "Ambiguous" width glyphs (em-dash
509
+ // —, smart quotes “ ”, ellipsis …, middle-dot ·). Most modern terminals
510
+ // draw them 1 col wide ("narrow"); CJK-locale / legacy setups draw them 2
511
+ // cols wide ("wide"). Defaults to "wide": counting ambiguous glyphs as 2
512
+ // cols never overflows the right margin (the worst case is wrapping a
513
+ // column early on narrow terminals, which is benign), whereas "narrow"
514
+ // bleeds past the margin on wide terminals. The thought gutter uses an
515
+ // ASCII marker, so this no longer affects marker alignment either way.
516
+ ambiguousWidth: z.enum(["narrow", "wide"]).default("wide"),
517
+ // How the TUI receives tool payload on attach/replay.
518
+ // "references" — the lean path (default): the daemon ships blob refs and
519
+ // the TUI fetches a diff/output body on demand when
520
+ // expanded, cutting replay size on tool-heavy sessions.
521
+ // Collapsed rows never fetch (they show a size hint), and
522
+ // old inline sessions/live turns are unaffected (no refs).
523
+ // "inline" — full content up front (the pre-externalization shape).
524
+ toolContent: z.enum(["inline", "references"]).default("references"),
525
+ // Unchanged context lines shown around each change in an expanded Edited
526
+ // diff. Some agents (e.g. pi) report edits as full-file old/new text via
527
+ // ACP "diff" content blocks; without hunking a 1-line edit would render
528
+ // the entire file. This bounds the context so only the changed region (±N
529
+ // lines) shows, with runs of unchanged lines collapsed to a marker.
530
+ diffContextLines: z.number().int().min(0).default(3),
502
531
  // Cap on entries kept in the cross-session global prompt-history file
503
532
  // (~/.hydra-acp/prompt-history). This is the ^P / ^R recall list
504
533
  // shared across all sessions; it's append-only on disk, so long-lived
@@ -614,6 +643,12 @@ var init_config = __esm({
614
643
  // a literal string ("~", "~/dev", "$HOME/work") so the config file is
615
644
  // portable across machines; expanded via expandHome at use time.
616
645
  defaultCwd: z.string().default("~"),
646
+ // Gzip externalized tool-content blobs at rest (tools/<sha256>.gz).
647
+ // Default true — text diffs/output compress ~3.5x and decompression is
648
+ // lazy (only on diff expand in references mode). Set false to write plain
649
+ // blobs instead, as an escape hatch if gzip CPU is ever a problem; reads
650
+ // transparently handle both, so flipping it only affects new writes.
651
+ compressToolContent: z.boolean().default(true),
617
652
  // Cap on cold sessions shown in CLI `sessions` listing and the TUI
618
653
  // picker. Live sessions are always included; cold are sorted by
619
654
  // recency and truncated to this count. `--all` overrides in the CLI.
@@ -635,6 +670,9 @@ var init_config = __esm({
635
670
  progressIndicator: true,
636
671
  defaultEnterAction: "amend",
637
672
  showThoughts: true,
673
+ ambiguousWidth: "wide",
674
+ toolContent: "references",
675
+ diffContextLines: 3,
638
676
  promptHistoryMaxEntries: 2e3,
639
677
  maxToolItems: 5,
640
678
  maxPlanItems: 5,
@@ -850,7 +888,7 @@ async function promptPassword(prompt) {
850
888
  if (!process.stdin.isTTY) {
851
889
  return readLineFromStdin();
852
890
  }
853
- return new Promise((resolve8, reject) => {
891
+ return new Promise((resolve9, reject) => {
854
892
  const stdin = process.stdin;
855
893
  const wasRaw = stdin.isRaw === true;
856
894
  let buffer = "";
@@ -867,7 +905,7 @@ async function promptPassword(prompt) {
867
905
  if (byte === 10 || byte === 13) {
868
906
  process.stdout.write("\n");
869
907
  cleanup();
870
- resolve8(buffer);
908
+ resolve9(buffer);
871
909
  return;
872
910
  }
873
911
  if (byte === 3) {
@@ -893,7 +931,7 @@ async function promptPassword(prompt) {
893
931
  });
894
932
  }
895
933
  function readLineFromStdin() {
896
- return new Promise((resolve8, reject) => {
934
+ return new Promise((resolve9, reject) => {
897
935
  let buffer = "";
898
936
  process.stdin.setEncoding("utf8");
899
937
  const onData = (chunk) => {
@@ -902,7 +940,7 @@ function readLineFromStdin() {
902
940
  if (nl !== -1) {
903
941
  process.stdin.removeListener("data", onData);
904
942
  process.stdin.removeListener("error", onError);
905
- resolve8(buffer.slice(0, nl).replace(/\r$/, ""));
943
+ resolve9(buffer.slice(0, nl).replace(/\r$/, ""));
906
944
  }
907
945
  };
908
946
  const onError = (err) => {
@@ -1102,6 +1140,9 @@ function extractHydraMeta(meta) {
1102
1140
  if (typeof obj.dripSpeed === "number" && obj.dripSpeed > 0) {
1103
1141
  out.dripSpeed = obj.dripSpeed;
1104
1142
  }
1143
+ if (obj.toolContent === "inline" || obj.toolContent === "references") {
1144
+ out.toolContent = obj.toolContent;
1145
+ }
1105
1146
  if (obj.detachStatus === "detached") {
1106
1147
  out.detachStatus = obj.detachStatus;
1107
1148
  }
@@ -1736,9 +1777,9 @@ var init_connection = __esm({
1736
1777
  }
1737
1778
  const id = nanoid();
1738
1779
  const message = { jsonrpc: "2.0", id, method, params };
1739
- const response = new Promise((resolve8, reject) => {
1780
+ const response = new Promise((resolve9, reject) => {
1740
1781
  this.pending.set(id, {
1741
- resolve: (result) => resolve8(result),
1782
+ resolve: (result) => resolve9(result),
1742
1783
  reject
1743
1784
  });
1744
1785
  this.stream.send(message).catch((err) => {
@@ -2069,14 +2110,14 @@ var init_stream_buffer = __esm({
2069
2110
  if (cap === 0) {
2070
2111
  return Promise.resolve("timeout");
2071
2112
  }
2072
- return new Promise((resolve8) => {
2113
+ return new Promise((resolve9) => {
2073
2114
  const waiter = {
2074
2115
  resolve: (outcome) => {
2075
2116
  if (waiter.timer !== void 0) {
2076
2117
  clearTimeout(waiter.timer);
2077
2118
  waiter.timer = void 0;
2078
2119
  }
2079
- resolve8(outcome);
2120
+ resolve9(outcome);
2080
2121
  },
2081
2122
  timer: setTimeout(() => {
2082
2123
  const idx = this.waiters.indexOf(waiter);
@@ -2084,7 +2125,7 @@ var init_stream_buffer = __esm({
2084
2125
  this.waiters.splice(idx, 1);
2085
2126
  }
2086
2127
  waiter.timer = void 0;
2087
- resolve8("timeout");
2128
+ resolve9("timeout");
2088
2129
  }, cap)
2089
2130
  };
2090
2131
  this.waiters.push(waiter);
@@ -2287,8 +2328,8 @@ var init_stream_buffer = __esm({
2287
2328
  return out;
2288
2329
  }
2289
2330
  scheduleFileWrite(chunk) {
2290
- const path21 = this.filePath;
2291
- if (path21 === void 0) {
2331
+ const path22 = this.filePath;
2332
+ if (path22 === void 0) {
2292
2333
  return;
2293
2334
  }
2294
2335
  if (this.fileCapReached) {
@@ -2303,7 +2344,7 @@ var init_stream_buffer = __esm({
2303
2344
  const slice = chunk.length <= remaining ? chunk : chunk.subarray(0, remaining);
2304
2345
  this.fileBytesWritten += slice.length;
2305
2346
  const willHitCap = this.fileBytesWritten >= this.fileCapBytes;
2306
- this.fileWriteChain = this.fileWriteChain.then(() => fsp3.appendFile(path21, slice)).catch((err) => {
2347
+ this.fileWriteChain = this.fileWriteChain.then(() => fsp3.appendFile(path22, slice)).catch((err) => {
2307
2348
  this.logWriteError?.(err);
2308
2349
  });
2309
2350
  if (willHitCap && !this.fileCapReached) {
@@ -2353,6 +2394,35 @@ var init_hydra_commands = __esm({
2353
2394
  }
2354
2395
  });
2355
2396
 
2397
+ // src/core/model-resolve.ts
2398
+ function trailingSegment(modelId) {
2399
+ const slash = modelId.lastIndexOf("/");
2400
+ const tail = slash === -1 ? modelId : modelId.slice(slash + 1);
2401
+ return tail.toLowerCase();
2402
+ }
2403
+ function resolveModelId(requested, advertised) {
2404
+ if (advertised.length === 0) {
2405
+ return { kind: "none", requested };
2406
+ }
2407
+ if (advertised.some((m) => m.modelId === requested)) {
2408
+ return { kind: "exact", modelId: requested };
2409
+ }
2410
+ const wantKey = trailingSegment(requested);
2411
+ const candidates = advertised.map((m) => m.modelId).filter((id) => trailingSegment(id) === wantKey);
2412
+ if (candidates.length === 1) {
2413
+ return { kind: "resolved", modelId: candidates[0], requested };
2414
+ }
2415
+ if (candidates.length > 1) {
2416
+ return { kind: "ambiguous", requested, candidates };
2417
+ }
2418
+ return { kind: "unknown", requested };
2419
+ }
2420
+ var init_model_resolve = __esm({
2421
+ "src/core/model-resolve.ts"() {
2422
+ "use strict";
2423
+ }
2424
+ });
2425
+
2356
2426
  // src/core/coalesce-replay.ts
2357
2427
  function coalesceReplay(entries) {
2358
2428
  if (entries.length === 0) {
@@ -2360,6 +2430,7 @@ function coalesceReplay(entries) {
2360
2430
  }
2361
2431
  const lastToolUpdateIndex = /* @__PURE__ */ new Map();
2362
2432
  const mergedToolContent = /* @__PURE__ */ new Map();
2433
+ const carriedRawInput = /* @__PURE__ */ new Map();
2363
2434
  for (let i = 0; i < entries.length; i++) {
2364
2435
  const entry = entries[i];
2365
2436
  if (entry === void 0) {
@@ -2374,6 +2445,9 @@ function coalesceReplay(entries) {
2374
2445
  continue;
2375
2446
  }
2376
2447
  lastToolUpdateIndex.set(id, i);
2448
+ if (upd.rawInput && typeof upd.rawInput === "object" && !Array.isArray(upd.rawInput) && Object.keys(upd.rawInput).length > 0) {
2449
+ carriedRawInput.set(id, upd.rawInput);
2450
+ }
2377
2451
  if (Array.isArray(upd.content) && upd.content.length > 0) {
2378
2452
  const buf = mergedToolContent.get(id);
2379
2453
  if (buf) {
@@ -2413,11 +2487,11 @@ function coalesceReplay(entries) {
2413
2487
  if (id !== void 0 && lastToolUpdateIndex.get(id) !== i) {
2414
2488
  continue;
2415
2489
  }
2416
- if (id !== void 0 && mergedToolContent.has(id)) {
2417
- out.push(withReplacedContent(entry, mergedToolContent.get(id) ?? []));
2418
- } else {
2419
- out.push(entry);
2490
+ let emitted = id !== void 0 && mergedToolContent.has(id) ? withReplacedContent(entry, mergedToolContent.get(id) ?? []) : entry;
2491
+ if (id !== void 0 && carriedRawInput.has(id) && !hasRawInput(emitted)) {
2492
+ emitted = withRawInput(emitted, carriedRawInput.get(id));
2420
2493
  }
2494
+ out.push(emitted);
2421
2495
  continue;
2422
2496
  }
2423
2497
  if (kind === "plan") {
@@ -2488,6 +2562,22 @@ function withReplacedContent(entry, content) {
2488
2562
  }
2489
2563
  };
2490
2564
  }
2565
+ function hasRawInput(entry) {
2566
+ const update = readUpdate(entry);
2567
+ const ri = update?.rawInput;
2568
+ return !!ri && typeof ri === "object" && !Array.isArray(ri) && Object.keys(ri).length > 0;
2569
+ }
2570
+ function withRawInput(entry, rawInput) {
2571
+ const params = entry.params ?? {};
2572
+ const update = params.update ?? {};
2573
+ return {
2574
+ ...entry,
2575
+ params: {
2576
+ ...params,
2577
+ update: { ...update, rawInput }
2578
+ }
2579
+ };
2580
+ }
2491
2581
  var init_coalesce_replay = __esm({
2492
2582
  "src/core/coalesce-replay.ts"() {
2493
2583
  "use strict";
@@ -2495,22 +2585,22 @@ var init_coalesce_replay = __esm({
2495
2585
  });
2496
2586
 
2497
2587
  // src/core/queue-store.ts
2498
- import * as fs8 from "fs/promises";
2588
+ import * as fs9 from "fs/promises";
2499
2589
  async function rewriteQueue(sessionId, entries) {
2500
2590
  const file = paths.queueFile(sessionId);
2501
2591
  if (entries.length === 0) {
2502
- await fs8.unlink(file).catch(() => void 0);
2592
+ await fs9.unlink(file).catch(() => void 0);
2503
2593
  return;
2504
2594
  }
2505
- await fs8.mkdir(paths.sessionDir(sessionId), { recursive: true });
2595
+ await fs9.mkdir(paths.sessionDir(sessionId), { recursive: true });
2506
2596
  const body = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
2507
- await fs8.writeFile(file, body, "utf8");
2597
+ await fs9.writeFile(file, body, "utf8");
2508
2598
  }
2509
2599
  async function loadQueue(sessionId) {
2510
2600
  const file = paths.queueFile(sessionId);
2511
2601
  let text;
2512
2602
  try {
2513
- text = await fs8.readFile(file, "utf8");
2603
+ text = await fs9.readFile(file, "utf8");
2514
2604
  } catch (err) {
2515
2605
  if (err.code === "ENOENT") {
2516
2606
  return [];
@@ -2532,7 +2622,7 @@ async function loadQueue(sessionId) {
2532
2622
  }
2533
2623
  async function deleteQueue(sessionId) {
2534
2624
  const file = paths.queueFile(sessionId);
2535
- await fs8.unlink(file).catch(() => void 0);
2625
+ await fs9.unlink(file).catch(() => void 0);
2536
2626
  }
2537
2627
  var init_queue_store = __esm({
2538
2628
  "src/core/queue-store.ts"() {
@@ -2840,6 +2930,7 @@ var init_session = __esm({
2840
2930
  "use strict";
2841
2931
  init_stream_buffer();
2842
2932
  init_hydra_commands();
2933
+ init_model_resolve();
2843
2934
  init_coalesce_replay();
2844
2935
  init_queue_store();
2845
2936
  init_types();
@@ -3236,7 +3327,7 @@ var init_session = __esm({
3236
3327
  const claimIdx = i;
3237
3328
  const claimEnvelope = envelope;
3238
3329
  const claimOriginatedBy = new Set(originatedBy);
3239
- await new Promise((resolve8) => {
3330
+ await new Promise((resolve9) => {
3240
3331
  const timer = setTimeout(() => {
3241
3332
  if (this.pendingClaims.delete(token)) {
3242
3333
  this.broadcastQueueNotification(
@@ -3247,14 +3338,14 @@ var init_session = __esm({
3247
3338
  claimEnvelope,
3248
3339
  /* @__PURE__ */ new Set([...claimOriginatedBy, t.name]),
3249
3340
  claimIdx + 1
3250
- ).then(resolve8);
3341
+ ).then(resolve9);
3251
3342
  }
3252
3343
  }, TRANSFORMER_CLAIM_TIMEOUT_MS);
3253
3344
  if (typeof timer.unref === "function") {
3254
3345
  timer.unref();
3255
3346
  }
3256
3347
  this.pendingClaims.set(token, {
3257
- resolve: () => resolve8(),
3348
+ resolve: () => resolve9(),
3258
3349
  timer,
3259
3350
  transformerName: t.name,
3260
3351
  method: "session/update",
@@ -3348,11 +3439,11 @@ var init_session = __esm({
3348
3439
  // Read the persisted history from disk. Returns [] if no history
3349
3440
  // file exists (fresh session, never prompted). Used by attach() and
3350
3441
  // the HTTP /history endpoint.
3351
- async getHistorySnapshot() {
3442
+ async getHistorySnapshot(tools = "inline") {
3352
3443
  if (!this.historyStore) {
3353
3444
  return [];
3354
3445
  }
3355
- return this.historyStore.load(this.sessionId).catch(() => []);
3446
+ return this.historyStore.load(this.sessionId, { tools }).catch(() => []);
3356
3447
  }
3357
3448
  // Subscribe to recordable broadcast entries — fires once per entry
3358
3449
  // that lands in history (so snapshot-shaped session_info/model/mode/
@@ -3402,7 +3493,7 @@ var init_session = __esm({
3402
3493
  }
3403
3494
  async loadReplay(historyPolicy, opts) {
3404
3495
  const maybeCoalesce = (entries) => opts.raw ? entries : coalesceReplay(entries);
3405
- const raw = await this.getHistorySnapshot();
3496
+ const raw = await this.getHistorySnapshot(opts.toolContent ?? "inline");
3406
3497
  const state = this.buildStateSnapshotReplay();
3407
3498
  if (historyPolicy === "after_message") {
3408
3499
  const cutoff = opts.afterMessageId ? findMessageIdIndex(raw, opts.afterMessageId) : -1;
@@ -4132,7 +4223,7 @@ var init_session = __esm({
4132
4223
  const claimIdx = i;
4133
4224
  const claimEnvelope = envelope;
4134
4225
  const claimOriginatedBy = new Set(originatedBy);
4135
- return new Promise((resolve8) => {
4226
+ return new Promise((resolve9) => {
4136
4227
  const timer = setTimeout(() => {
4137
4228
  if (this.pendingClaims.delete(token)) {
4138
4229
  this.broadcastQueueNotification(
@@ -4144,14 +4235,14 @@ var init_session = __esm({
4144
4235
  claimEnvelope,
4145
4236
  /* @__PURE__ */ new Set([...claimOriginatedBy, t.name]),
4146
4237
  claimIdx + 1
4147
- ).then(resolve8).catch(() => resolve8(defaultStopPayload(method)));
4238
+ ).then(resolve9).catch(() => resolve9(defaultStopPayload(method)));
4148
4239
  }
4149
4240
  }, TRANSFORMER_CLAIM_TIMEOUT_MS);
4150
4241
  if (typeof timer.unref === "function") {
4151
4242
  timer.unref();
4152
4243
  }
4153
4244
  this.pendingClaims.set(token, {
4154
- resolve: resolve8,
4245
+ resolve: resolve9,
4155
4246
  timer,
4156
4247
  transformerName: t.name,
4157
4248
  method,
@@ -5071,6 +5162,27 @@ ${text}
5071
5162
  sessionUpdate: "agent_message_chunk",
5072
5163
  content: { type: "text", text: `
5073
5164
  ${body}
5165
+ ` },
5166
+ _meta: { "hydra-acp": { synthetic: true } }
5167
+ }
5168
+ });
5169
+ return { stopReason: "end_turn" };
5170
+ }
5171
+ const resolution = resolveModelId(arg, this.agentAdvertisedModels);
5172
+ let modelId = arg;
5173
+ if (resolution.kind === "resolved") {
5174
+ modelId = resolution.modelId;
5175
+ } else if (resolution.kind === "ambiguous" || resolution.kind === "unknown") {
5176
+ const known = this.agentAdvertisedModels.map((m) => m.modelId).join("\n ");
5177
+ const reason = resolution.kind === "ambiguous" ? `"${arg}" matches multiple models: ${resolution.candidates.join(", ")}` : `"${arg}" is not an available model`;
5178
+ this.recordAndBroadcast("session/update", {
5179
+ sessionId: this.upstreamSessionId,
5180
+ update: {
5181
+ sessionUpdate: "agent_message_chunk",
5182
+ content: { type: "text", text: `
5183
+ ${reason}.
5184
+ Available models:
5185
+ ${known}
5074
5186
  ` },
5075
5187
  _meta: { "hydra-acp": { synthetic: true } }
5076
5188
  }
@@ -5079,7 +5191,7 @@ ${body}
5079
5191
  }
5080
5192
  await this.forwardRequest("session/set_model", {
5081
5193
  sessionId: this.sessionId,
5082
- modelId: arg
5194
+ modelId
5083
5195
  });
5084
5196
  return { stopReason: "end_turn" };
5085
5197
  }
@@ -5625,12 +5737,12 @@ ${body}
5625
5737
  this.clients.clear();
5626
5738
  if (this.streamBuffer !== void 0) {
5627
5739
  const buf = this.streamBuffer;
5628
- const path21 = this.streamFilePath;
5740
+ const path22 = this.streamFilePath;
5629
5741
  this.streamBuffer = void 0;
5630
5742
  this.streamFilePath = void 0;
5631
5743
  buf.close();
5632
- if (path21 !== void 0) {
5633
- void buf.drainFileWrites().then(() => fsp4.unlink(path21).catch(() => void 0));
5744
+ if (path22 !== void 0) {
5745
+ void buf.drainFileWrites().then(() => fsp4.unlink(path22).catch(() => void 0));
5634
5746
  }
5635
5747
  }
5636
5748
  for (const handler of this.closeHandlers) {
@@ -5792,7 +5904,7 @@ ${body}
5792
5904
  }
5793
5905
  const clientParams = this.rewriteForClient(params);
5794
5906
  const toolCallId = extractToolCallId(clientParams);
5795
- return new Promise((resolve8, reject) => {
5907
+ return new Promise((resolve9, reject) => {
5796
5908
  let settled = false;
5797
5909
  const outbound = [];
5798
5910
  const entry = { addClient: sendTo };
@@ -5831,7 +5943,7 @@ ${body}
5831
5943
  update
5832
5944
  }).catch(() => void 0);
5833
5945
  }
5834
- resolve8(result);
5946
+ resolve9(result);
5835
5947
  });
5836
5948
  }).catch((err) => {
5837
5949
  settle(() => reject(err));
@@ -5847,14 +5959,14 @@ ${body}
5847
5959
  // in flight, but doesn't emit prompt_queue_* broadcasts — clients
5848
5960
  // shouldn't see hydra's housekeeping in their chip list.
5849
5961
  async enqueuePrompt(task) {
5850
- return new Promise((resolve8, reject) => {
5962
+ return new Promise((resolve9, reject) => {
5851
5963
  const entry = {
5852
5964
  kind: "internal",
5853
5965
  messageId: generateMessageId(),
5854
5966
  enqueuedAt: Date.now(),
5855
5967
  cancelled: false,
5856
5968
  task,
5857
- resolve: resolve8,
5969
+ resolve: resolve9,
5858
5970
  reject
5859
5971
  };
5860
5972
  this.promptQueue.push(entry);
@@ -5873,7 +5985,7 @@ ${body}
5873
5985
  if (client.clientInfo?.name) originator.name = client.clientInfo.name;
5874
5986
  if (client.clientInfo?.version)
5875
5987
  originator.version = client.clientInfo.version;
5876
- return new Promise((resolve8, reject) => {
5988
+ return new Promise((resolve9, reject) => {
5877
5989
  const entry = {
5878
5990
  kind: "user",
5879
5991
  messageId,
@@ -5882,7 +5994,7 @@ ${body}
5882
5994
  prompt: promptArray,
5883
5995
  enqueuedAt: Date.now(),
5884
5996
  cancelled: false,
5885
- resolve: resolve8,
5997
+ resolve: resolve9,
5886
5998
  reject
5887
5999
  };
5888
6000
  this.promptQueue.push(entry);
@@ -6062,14 +6174,14 @@ ${body}
6062
6174
  // src/core/hydra-version.ts
6063
6175
  import { fileURLToPath } from "url";
6064
6176
  import * as path8 from "path";
6065
- import * as fs11 from "fs";
6177
+ import * as fs12 from "fs";
6066
6178
  function resolveVersion() {
6067
6179
  try {
6068
6180
  let dir = path8.dirname(fileURLToPath(import.meta.url));
6069
6181
  for (let i = 0; i < 8; i += 1) {
6070
6182
  const candidate = path8.join(dir, "package.json");
6071
- if (fs11.existsSync(candidate)) {
6072
- const pkg = JSON.parse(fs11.readFileSync(candidate, "utf8"));
6183
+ if (fs12.existsSync(candidate)) {
6184
+ const pkg = JSON.parse(fs12.readFileSync(candidate, "utf8"));
6073
6185
  if (typeof pkg.version === "string" && pkg.version.length > 0 && (typeof pkg.name !== "string" || pkg.name.includes("hydra-acp"))) {
6074
6186
  return pkg.version;
6075
6187
  }
@@ -6094,12 +6206,12 @@ var init_hydra_version = __esm({
6094
6206
  });
6095
6207
 
6096
6208
  // src/tui/history.ts
6097
- import { promises as fs14 } from "fs";
6209
+ import { promises as fs15 } from "fs";
6098
6210
  import * as path9 from "path";
6099
6211
  async function loadHistory(file) {
6100
6212
  let text;
6101
6213
  try {
6102
- text = await fs14.readFile(file, "utf8");
6214
+ text = await fs15.readFile(file, "utf8");
6103
6215
  } catch (err) {
6104
6216
  if (err.code === "ENOENT") {
6105
6217
  return [];
@@ -6139,17 +6251,17 @@ function appendEntry(history, entry, cap = HISTORY_CAP) {
6139
6251
  return out;
6140
6252
  }
6141
6253
  async function saveHistory(file, history) {
6142
- await fs14.mkdir(path9.dirname(file), { recursive: true });
6254
+ await fs15.mkdir(path9.dirname(file), { recursive: true });
6143
6255
  const lines = history.map((entry) => JSON.stringify(entry));
6144
- await fs14.writeFile(file, lines.length > 0 ? lines.join("\n") + "\n" : "");
6256
+ await fs15.writeFile(file, lines.length > 0 ? lines.join("\n") + "\n" : "");
6145
6257
  }
6146
6258
  async function appendHistoryLine(file, entry) {
6147
6259
  const trimmed = entry.replace(/\n+$/, "");
6148
6260
  if (trimmed.length === 0) {
6149
6261
  return;
6150
6262
  }
6151
- await fs14.mkdir(path9.dirname(file), { recursive: true });
6152
- await fs14.appendFile(file, JSON.stringify(trimmed) + "\n", {
6263
+ await fs15.mkdir(path9.dirname(file), { recursive: true });
6264
+ await fs15.appendFile(file, JSON.stringify(trimmed) + "\n", {
6153
6265
  encoding: "utf8"
6154
6266
  });
6155
6267
  }
@@ -6422,6 +6534,23 @@ function isExitPlanModeTool(name) {
6422
6534
  const normalised = name.toLowerCase().replace(/[_\s-]/g, "");
6423
6535
  return normalised === "exitplanmode";
6424
6536
  }
6537
+ function readDiffField(value) {
6538
+ if (typeof value === "string") {
6539
+ return { text: value };
6540
+ }
6541
+ if (value && typeof value === "object" && !Array.isArray(value)) {
6542
+ const v = value;
6543
+ if (typeof v.__hydraBlob === "string") {
6544
+ return {
6545
+ ref: {
6546
+ hash: v.__hydraBlob,
6547
+ bytes: typeof v.bytes === "number" ? v.bytes : 0
6548
+ }
6549
+ };
6550
+ }
6551
+ }
6552
+ return void 0;
6553
+ }
6425
6554
  function extractEditDiff(u) {
6426
6555
  const content = u.content;
6427
6556
  if (Array.isArray(content)) {
@@ -6433,16 +6562,18 @@ function extractEditDiff(u) {
6433
6562
  if (b.type !== "diff") {
6434
6563
  continue;
6435
6564
  }
6436
- const oldText = typeof b.oldText === "string" ? b.oldText : void 0;
6437
- const newText = typeof b.newText === "string" ? b.newText : void 0;
6438
- if (oldText === void 0 && newText === void 0) {
6565
+ const oldField = readDiffField(b.oldText);
6566
+ const newField = readDiffField(b.newText);
6567
+ if (oldField === void 0 && newField === void 0) {
6439
6568
  continue;
6440
6569
  }
6441
- const path21 = typeof b.path === "string" ? b.path : void 0;
6570
+ const path22 = typeof b.path === "string" ? b.path : void 0;
6442
6571
  return {
6443
- ...path21 !== void 0 ? { path: path21 } : {},
6444
- oldText: oldText ?? "",
6445
- newText: newText ?? ""
6572
+ ...path22 !== void 0 ? { path: path22 } : {},
6573
+ oldText: oldField?.text ?? "",
6574
+ newText: newField?.text ?? "",
6575
+ ...oldField?.ref ? { oldRef: oldField.ref } : {},
6576
+ ...newField?.ref ? { newRef: newField.ref } : {}
6446
6577
  };
6447
6578
  }
6448
6579
  }
@@ -6510,8 +6641,35 @@ function mapToolCall(u) {
6510
6641
  if (diff !== null) {
6511
6642
  event.editDiff = diff;
6512
6643
  }
6644
+ const detail = extractToolDetail(u);
6645
+ if (detail !== void 0) {
6646
+ event.detail = detail;
6647
+ }
6513
6648
  return event;
6514
6649
  }
6650
+ function extractToolDetail(u) {
6651
+ const rawInput = u.rawInput;
6652
+ if (!rawInput || typeof rawInput !== "object" || Array.isArray(rawInput)) {
6653
+ return void 0;
6654
+ }
6655
+ const r = rawInput;
6656
+ if (typeof r.command === "string" && r.command.trim().length > 0) {
6657
+ const firstLine3 = sanitizeSingleLine(r.command).trim();
6658
+ const cmd = firstLine3.replace(/^cd\s+\S+\s+&&\s+/, "");
6659
+ return clipHead(cmd, TOOL_DETAIL_MAX);
6660
+ }
6661
+ const path22 = typeof r.file_path === "string" ? r.file_path : typeof r.path === "string" ? r.path : void 0;
6662
+ if (path22 !== void 0 && path22.length > 0) {
6663
+ return clipTail(shortenHomePath(sanitizeSingleLine(path22)), TOOL_DETAIL_MAX);
6664
+ }
6665
+ return void 0;
6666
+ }
6667
+ function clipHead(s, max) {
6668
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
6669
+ }
6670
+ function clipTail(s, max) {
6671
+ return s.length > max ? `\u2026${s.slice(-(max - 1))}` : s;
6672
+ }
6515
6673
  function mapToolCallUpdate(u) {
6516
6674
  const toolCallId = readString(u, "toolCallId") ?? readString(u, "id");
6517
6675
  if (!toolCallId) {
@@ -6521,7 +6679,8 @@ function mapToolCallUpdate(u) {
6521
6679
  const title = rawTitle !== void 0 ? sanitizeSingleLine(rawTitle) : void 0;
6522
6680
  const status = readString(u, "status");
6523
6681
  const diff = extractEditDiff(u);
6524
- const meaningful = title !== void 0 || diff !== null || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
6682
+ const detail = extractToolDetail(u);
6683
+ const meaningful = title !== void 0 || diff !== null || detail !== void 0 || status === "completed" || status === "failed" || status === "rejected" || status === "cancelled";
6525
6684
  if (!meaningful) {
6526
6685
  return null;
6527
6686
  }
@@ -6541,6 +6700,9 @@ function mapToolCallUpdate(u) {
6541
6700
  if (title !== void 0) {
6542
6701
  event.title = title;
6543
6702
  }
6703
+ if (detail !== void 0) {
6704
+ event.detail = detail;
6705
+ }
6544
6706
  if (status !== void 0) {
6545
6707
  event.status = status;
6546
6708
  }
@@ -6693,11 +6855,13 @@ function readString(u, key) {
6693
6855
  const v = u[key];
6694
6856
  return typeof v === "string" ? v : void 0;
6695
6857
  }
6696
- var STRIP_CONTROLS;
6858
+ var STRIP_CONTROLS, TOOL_DETAIL_MAX;
6697
6859
  var init_render_update = __esm({
6698
6860
  "src/core/render-update.ts"() {
6699
6861
  "use strict";
6862
+ init_paths();
6700
6863
  STRIP_CONTROLS = /[\x00-\x08\x0b-\x1f\x7f]/g;
6864
+ TOOL_DETAIL_MAX = 64;
6701
6865
  }
6702
6866
  });
6703
6867
 
@@ -6746,13 +6910,13 @@ function wsToMessageStream(ws) {
6746
6910
  throw new Error("ws is closed");
6747
6911
  }
6748
6912
  const text = JSON.stringify(message);
6749
- await new Promise((resolve8, reject) => {
6913
+ await new Promise((resolve9, reject) => {
6750
6914
  ws.send(text, (err) => {
6751
6915
  if (err) {
6752
6916
  reject(err);
6753
6917
  return;
6754
6918
  }
6755
- resolve8();
6919
+ resolve9();
6756
6920
  });
6757
6921
  });
6758
6922
  },
@@ -7354,7 +7518,7 @@ function formatEvent(event, options = {}) {
7354
7518
  case "agent-text":
7355
7519
  return formatBlock(event.text, " ", "agent");
7356
7520
  case "agent-thought":
7357
- return formatBlock(event.text, "\xB7 ", "thought", "thought");
7521
+ return formatBlock(event.text, " ", "thought", "thought");
7358
7522
  case "tool-call":
7359
7523
  case "tool-call-update":
7360
7524
  return [];
@@ -7417,7 +7581,7 @@ function parseMarkdown(text, opts) {
7417
7581
  maxWidth
7418
7582
  } = opts;
7419
7583
  const out = [];
7420
- const lines = text.split("\n");
7584
+ const lines = text.replace(/^\s+/, "").split("\n");
7421
7585
  let inCode = false;
7422
7586
  let codeLang = "";
7423
7587
  let codeBuffer = [];
@@ -7553,7 +7717,7 @@ function parseThoughtMarkdown(text) {
7553
7717
  proseStyle: "thought",
7554
7718
  highlightCode: false,
7555
7719
  prefixStyle: "thought",
7556
- firstPrefix: "\xB7 ",
7720
+ firstPrefix: " ",
7557
7721
  inlineOpts: { codeOpen: "^c", boldReset: "^-", codeReset: "^K" }
7558
7722
  });
7559
7723
  }
@@ -7848,7 +8012,7 @@ function highlightFencedBlock(lang, lines) {
7848
8012
  }));
7849
8013
  }
7850
8014
  function formatBlock(text, prefix, bodyStyle, prefixStyle, sentBy, fillRow) {
7851
- const lines = text.split("\n");
8015
+ const lines = text.replace(/^\s+/, "").split("\n");
7852
8016
  const out = [];
7853
8017
  if (sentBy) {
7854
8018
  out.push({
@@ -7872,6 +8036,15 @@ function formatBlock(text, prefix, bodyStyle, prefixStyle, sentBy, fillRow) {
7872
8036
  }
7873
8037
  return out;
7874
8038
  }
8039
+ function formatDiffBytes(bytes) {
8040
+ if (bytes < 1024) {
8041
+ return `${bytes} B`;
8042
+ }
8043
+ if (bytes < 1024 * 1024) {
8044
+ return `${Math.round(bytes / 1024)} KB`;
8045
+ }
8046
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
8047
+ }
7875
8048
  function formatElapsed(ms) {
7876
8049
  const totalSec = Math.floor(ms / 1e3);
7877
8050
  if (totalSec < 60) {
@@ -7899,6 +8072,9 @@ function formatToolLine2(state, now = Date.now()) {
7899
8072
  } else {
7900
8073
  title = `${initial} \xB7 ${latest}`;
7901
8074
  }
8075
+ if (state.detail && !title.includes(state.detail)) {
8076
+ title = `${title} \xB7 ${state.detail}`;
8077
+ }
7902
8078
  if (state.startedAt !== void 0) {
7903
8079
  const end = state.endedAt ?? now;
7904
8080
  title = `${title} \xB7 ${formatElapsed(end - state.startedAt)}`;
@@ -7920,17 +8096,27 @@ function formatToolLine2(state, now = Date.now()) {
7920
8096
  }
7921
8097
  return lines;
7922
8098
  }
7923
- function formatEditDiffBlock(diff, mode) {
8099
+ function setDiffContextLines(n) {
8100
+ diffContextLines = n >= 0 ? n : 0;
8101
+ }
8102
+ function formatEditDiffBlock(diff, mode, opts = {}) {
7924
8103
  const lines = [];
7925
- const counts = countDiffChanges(diff);
7926
- const summaryParts = [];
7927
- if (counts.added > 0) {
7928
- summaryParts.push(`+${counts.added}`);
7929
- }
7930
- if (counts.removed > 0) {
7931
- summaryParts.push(`-${counts.removed}`);
8104
+ const deferred = diff.oldRef !== void 0 || diff.newRef !== void 0;
8105
+ let summary;
8106
+ if (deferred) {
8107
+ const bytes = (diff.oldRef?.bytes ?? 0) + (diff.newRef?.bytes ?? 0);
8108
+ summary = ` (~${formatDiffBytes(bytes)})`;
8109
+ } else {
8110
+ const counts = countDiffChanges(diff);
8111
+ const summaryParts = [];
8112
+ if (counts.added > 0) {
8113
+ summaryParts.push(`+${counts.added}`);
8114
+ }
8115
+ if (counts.removed > 0) {
8116
+ summaryParts.push(`-${counts.removed}`);
8117
+ }
8118
+ summary = summaryParts.length > 0 ? ` (${summaryParts.join(" ")})` : "";
7932
8119
  }
7933
- const summary = summaryParts.length > 0 ? ` (${summaryParts.join(" ")})` : "";
7934
8120
  const header = (open2) => ({
7935
8121
  prefix: " ",
7936
8122
  body: `${open2 ? "\u25BE" : "\u25B8"} Edited ${sanitizeSingleLine(shortenHomePath(diff.path))}${summary}`,
@@ -7942,6 +8128,24 @@ function formatEditDiffBlock(diff, mode) {
7942
8128
  }
7943
8129
  return lines;
7944
8130
  }
8131
+ if (deferred) {
8132
+ if (diff.path) {
8133
+ lines.push(header(true));
8134
+ lines.push(
8135
+ opts.deferredStatus === "error" ? {
8136
+ prefix: " ",
8137
+ body: "\u26A0 failed to load diff",
8138
+ bodyStyle: "tool-status-fail"
8139
+ } : {
8140
+ prefix: " ",
8141
+ body: "\u22EF fetching diff\u2026",
8142
+ bodyStyle: "dim"
8143
+ }
8144
+ );
8145
+ lines.unshift({ body: "" });
8146
+ }
8147
+ return lines;
8148
+ }
7945
8149
  const body = buildUnifiedDiff(diff, { maxLines: Infinity });
7946
8150
  if (body.length === 0) {
7947
8151
  if (diff.path) {
@@ -7983,32 +8187,100 @@ function countDiffChanges(diff) {
7983
8187
  }
7984
8188
  return { added, removed };
7985
8189
  }
8190
+ function renderDiffOp(op) {
8191
+ if (op.op === "=") {
8192
+ return ` ${op.text}`;
8193
+ }
8194
+ return op.op === "-" ? `- ${op.text}` : `+ ${op.text}`;
8195
+ }
7986
8196
  function buildUnifiedDiff(diff, opts = {}) {
7987
8197
  const maxLines = opts.maxLines ?? EDIT_DIFF_MAX_LINES;
8198
+ const ctx = opts.contextLines ?? diffContextLines;
7988
8199
  const { oldLines, newLines } = diffLinePair(diff);
7989
8200
  const ops = diffLines(oldLines, newLines);
8201
+ const display = [];
8202
+ if (!Number.isFinite(ctx)) {
8203
+ for (const op of ops) {
8204
+ display.push(renderDiffOp(op));
8205
+ }
8206
+ } else {
8207
+ const hasChange = ops.some((o) => o.op !== "=");
8208
+ if (!hasChange) {
8209
+ return "";
8210
+ }
8211
+ const keep = new Array(ops.length).fill(false);
8212
+ for (let i2 = 0; i2 < ops.length; i2++) {
8213
+ if (ops[i2].op !== "=") {
8214
+ const lo = Math.max(0, i2 - ctx);
8215
+ const hi = Math.min(ops.length - 1, i2 + ctx);
8216
+ for (let k = lo; k <= hi; k++) {
8217
+ keep[k] = true;
8218
+ }
8219
+ }
8220
+ }
8221
+ let i = 0;
8222
+ while (i < ops.length) {
8223
+ if (keep[i]) {
8224
+ display.push(renderDiffOp(ops[i]));
8225
+ i++;
8226
+ continue;
8227
+ }
8228
+ let j = i;
8229
+ while (j < ops.length && !keep[j]) {
8230
+ j++;
8231
+ }
8232
+ const skipped = j - i;
8233
+ display.push(` \u22EF ${skipped} unchanged line${skipped === 1 ? "" : "s"}`);
8234
+ i = j;
8235
+ }
8236
+ }
7990
8237
  const rendered = [];
7991
- for (let idx = 0; idx < ops.length; idx++) {
7992
- const op = ops[idx];
7993
- const wouldTruncate = rendered.length >= maxLines - 1 && idx < ops.length - 1;
8238
+ for (let idx = 0; idx < display.length; idx++) {
8239
+ const wouldTruncate = rendered.length >= maxLines - 1 && idx < display.length - 1;
7994
8240
  if (wouldTruncate) {
7995
- const remaining = ops.length - idx;
8241
+ const remaining = display.length - idx;
7996
8242
  rendered.push(`\u2026 ${remaining} more line${remaining === 1 ? "" : "s"}`);
7997
8243
  break;
7998
8244
  }
7999
- if (op.op === "=") {
8000
- rendered.push(` ${op.text}`);
8001
- } else if (op.op === "-") {
8002
- rendered.push(`- ${op.text}`);
8003
- } else {
8004
- rendered.push(`+ ${op.text}`);
8005
- }
8245
+ rendered.push(display[idx]);
8006
8246
  }
8007
8247
  return rendered.join("\n");
8008
8248
  }
8009
8249
  function diffLines(a, b) {
8250
+ let start = 0;
8251
+ const minLen = Math.min(a.length, b.length);
8252
+ while (start < minLen && a[start] === b[start]) {
8253
+ start++;
8254
+ }
8255
+ let endA = a.length;
8256
+ let endB = b.length;
8257
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
8258
+ endA--;
8259
+ endB--;
8260
+ }
8261
+ const out = [];
8262
+ for (let k = 0; k < start; k++) {
8263
+ out.push({ op: "=", text: a[k] });
8264
+ }
8265
+ out.push(...lcsDiff(a.slice(start, endA), b.slice(start, endB)));
8266
+ for (let k = endA; k < a.length; k++) {
8267
+ out.push({ op: "=", text: a[k] });
8268
+ }
8269
+ return out;
8270
+ }
8271
+ function lcsDiff(a, b) {
8010
8272
  const m = a.length;
8011
8273
  const n = b.length;
8274
+ if (m === 0 || n === 0) {
8275
+ const out2 = [];
8276
+ for (const text of a) {
8277
+ out2.push({ op: "-", text });
8278
+ }
8279
+ for (const text of b) {
8280
+ out2.push({ op: "+", text });
8281
+ }
8282
+ return out2;
8283
+ }
8012
8284
  const dp = Array.from(
8013
8285
  { length: m + 1 },
8014
8286
  () => new Array(n + 1).fill(0)
@@ -8245,7 +8517,7 @@ function toolStatusStyle(status) {
8245
8517
  return "tool-status-pending";
8246
8518
  }
8247
8519
  }
8248
- var TABLE_MIN_COL, TABLE_PREFIX_WIDTH, TABLE_SEP_WIDTH, highlightChalk, HIGHLIGHT_THEME, EDIT_DIFF_MAX_LINES, PLAN_VISIBLE_LIMIT;
8520
+ var TABLE_MIN_COL, TABLE_PREFIX_WIDTH, TABLE_SEP_WIDTH, highlightChalk, HIGHLIGHT_THEME, EDIT_DIFF_MAX_LINES, diffContextLines, PLAN_VISIBLE_LIMIT;
8249
8521
  var init_format = __esm({
8250
8522
  "src/tui/format.ts"() {
8251
8523
  "use strict";
@@ -8280,6 +8552,7 @@ var init_format = __esm({
8280
8552
  name: highlightChalk.cyanBright
8281
8553
  };
8282
8554
  EDIT_DIFF_MAX_LINES = 40;
8555
+ diffContextLines = Number.POSITIVE_INFINITY;
8283
8556
  PLAN_VISIBLE_LIMIT = 5;
8284
8557
  }
8285
8558
  });
@@ -8291,11 +8564,11 @@ function isResponse(msg) {
8291
8564
  return !("method" in msg) && "id" in msg && msg.id !== void 0;
8292
8565
  }
8293
8566
  async function openWs(url, subprotocols) {
8294
- return new Promise((resolve8, reject) => {
8567
+ return new Promise((resolve9, reject) => {
8295
8568
  const ws = new WebSocket(url, subprotocols);
8296
8569
  const onOpen = () => {
8297
8570
  ws.off("error", onError);
8298
- resolve8(wsToMessageStream(ws));
8571
+ resolve9(wsToMessageStream(ws));
8299
8572
  };
8300
8573
  const onError = (err) => {
8301
8574
  ws.off("open", onOpen);
@@ -8366,8 +8639,8 @@ var init_resilient_ws = __esm({
8366
8639
  throw new Error("resilient ws stream not connected");
8367
8640
  }
8368
8641
  const id = message.id;
8369
- const promise = new Promise((resolve8, reject) => {
8370
- this.pendingRequests.set(id, { resolve: resolve8, reject });
8642
+ const promise = new Promise((resolve9, reject) => {
8643
+ this.pendingRequests.set(id, { resolve: resolve9, reject });
8371
8644
  });
8372
8645
  try {
8373
8646
  await this.current.send(message);
@@ -8395,8 +8668,8 @@ var init_resilient_ws = __esm({
8395
8668
  this.bindStream(stream);
8396
8669
  const wasFirst = this.firstConnect;
8397
8670
  this.firstConnect = false;
8398
- this.connectGate = new Promise((resolve8) => {
8399
- this.releaseConnectGate = resolve8;
8671
+ this.connectGate = new Promise((resolve9) => {
8672
+ this.releaseConnectGate = resolve9;
8400
8673
  });
8401
8674
  try {
8402
8675
  if (this.opts.onConnect) {
@@ -8717,7 +8990,7 @@ var init_update_check = __esm({
8717
8990
  });
8718
8991
 
8719
8992
  // src/core/cwd.ts
8720
- import * as fs23 from "fs/promises";
8993
+ import * as fs24 from "fs/promises";
8721
8994
  import * as path17 from "path";
8722
8995
  async function validateLocalCwd(input) {
8723
8996
  const trimmed = input.trim();
@@ -8727,7 +9000,7 @@ async function validateLocalCwd(input) {
8727
9000
  const resolved = path17.resolve(expandHome(trimmed));
8728
9001
  let stat5;
8729
9002
  try {
8730
- stat5 = await fs23.stat(resolved);
9003
+ stat5 = await fs24.stat(resolved);
8731
9004
  } catch {
8732
9005
  return { ok: false, reason: `${resolved} does not exist` };
8733
9006
  }
@@ -8753,7 +9026,7 @@ async function pickInitialLocalCwd(sessionCwd) {
8753
9026
  }
8754
9027
  for (const candidate of candidates) {
8755
9028
  try {
8756
- const stat5 = await fs23.stat(candidate);
9029
+ const stat5 = await fs24.stat(candidate);
8757
9030
  if (stat5.isDirectory()) {
8758
9031
  return candidate;
8759
9032
  }
@@ -8779,7 +9052,7 @@ async function completeLocalPath(input) {
8779
9052
  const resolvedDir = path17.resolve(expandHome(dirForRead));
8780
9053
  let entries;
8781
9054
  try {
8782
- const list = await fs23.readdir(resolvedDir, { withFileTypes: true });
9055
+ const list = await fs24.readdir(resolvedDir, { withFileTypes: true });
8783
9056
  entries = list.map((e) => ({ name: e.name, isDir: e.isDirectory() }));
8784
9057
  } catch {
8785
9058
  return { prefix, basePrefix, matches: [] };
@@ -8918,6 +9191,17 @@ var init_input = __esm({
8918
9191
  this.col = text.length;
8919
9192
  }
8920
9193
  }
9194
+ // Replace a [start, end) span on the current line with `text`, landing
9195
+ // the cursor at the end of the inserted text. Used by file-path tab
9196
+ // completion: the typed path token gets swapped for the completed one.
9197
+ // Out-of-range bounds are clamped to the line length.
9198
+ replaceRangeOnCurrentLine(start, end, text) {
9199
+ const line = this.currentLine();
9200
+ const s = Math.max(0, Math.min(start, line.length));
9201
+ const e = Math.max(s, Math.min(end, line.length));
9202
+ this.setCurrentLine(line.slice(0, s) + text + line.slice(e));
9203
+ this.col = s + text.length;
9204
+ }
8921
9205
  // Public seed for the buffer (used for Escape pre-fill). Treated like a
8922
9206
  // fresh draft: nav state and any saved draft are cleared, cursor lands
8923
9207
  // at the end so the user can edit immediately. Attachments restore
@@ -9999,7 +10283,12 @@ function writeBodyWithHighlight(termObj, text, style, term, activeCol = null, _a
9999
10283
  }
10000
10284
  }
10001
10285
  function bodyStyleUsesMarkup(style) {
10002
- return style === "agent" || style === "heading-1" || style === "heading-2" || style === "heading-3";
10286
+ return style === "agent" || // Thoughts switched to the markup-interpreting writer (writeStyled's
10287
+ // "thought" case uses term.brightBlack without .noFormat), so their
10288
+ // caret spans (^ccode^K, ^+bold^-) are zero-width on screen. wrap/
10289
+ // truncate must strip them too or thought lines wrap several columns
10290
+ // short of the margin whenever they contain inline code/bold.
10291
+ style === "thought" || style === "heading-1" || style === "heading-2" || style === "heading-3";
10003
10292
  }
10004
10293
  function writeStyled(term, text, style) {
10005
10294
  if (text.length === 0) {
@@ -10082,6 +10371,12 @@ function wrapAnsiBody(text, width) {
10082
10371
  }
10083
10372
  return wrapAnsi(text, width, { hard: true, trim: false }).split("\n");
10084
10373
  }
10374
+ function setAmbiguousWide(wide) {
10375
+ ambiguousWide = wide;
10376
+ }
10377
+ function cellWidth(text) {
10378
+ return stringWidth2(text, { ambiguousIsNarrow: !ambiguousWide });
10379
+ }
10085
10380
  function matchTkMarkupAt(text, i) {
10086
10381
  if (text.charCodeAt(i) !== 94) {
10087
10382
  return null;
@@ -10139,7 +10434,7 @@ function* segmentForWidth(text) {
10139
10434
  continue;
10140
10435
  }
10141
10436
  for (const { segment } of SEGMENTER.segment(text.slice(i, runEnd))) {
10142
- yield { text: segment, width: stringWidth2(segment) };
10437
+ yield { text: segment, width: cellWidth(segment) };
10143
10438
  }
10144
10439
  i = runEnd;
10145
10440
  }
@@ -10233,7 +10528,7 @@ function wrapVisible(text, width, stripMarkup) {
10233
10528
  function graphemeSegments(text) {
10234
10529
  const out = [];
10235
10530
  for (const { segment } of SEGMENTER.segment(text)) {
10236
- out.push({ text: segment, width: stringWidth2(segment) });
10531
+ out.push({ text: segment, width: cellWidth(segment) });
10237
10532
  }
10238
10533
  return out;
10239
10534
  }
@@ -10246,7 +10541,7 @@ function truncate(text, max, opts = {}) {
10246
10541
  return text;
10247
10542
  }
10248
10543
  if (!stripMarkup) {
10249
- const visible2 = stringWidth2(text);
10544
+ const visible2 = cellWidth(text);
10250
10545
  if (visible2 <= max) {
10251
10546
  return text;
10252
10547
  }
@@ -10275,7 +10570,7 @@ function takeByWidth(text, budget) {
10275
10570
  let out = "";
10276
10571
  let used = 0;
10277
10572
  for (const { segment } of SEGMENTER.segment(text)) {
10278
- const w = stringWidth2(segment);
10573
+ const w = cellWidth(segment);
10279
10574
  if (used + w > budget) {
10280
10575
  break;
10281
10576
  }
@@ -10540,7 +10835,7 @@ function mapCsiUToKeyName(code, mod) {
10540
10835
  }
10541
10836
  return null;
10542
10837
  }
10543
- var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_OPTIONS_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, TK_MARKUP_STYLE_CHAR, shortId;
10838
+ var SESSIONBAR_ROWS, BANNER_ROWS, SEPARATOR_ROWS, MAX_PROMPT_ROWS, MAX_QUEUED_ROWS, MAX_PERMISSION_ROWS, MAX_OPTIONS_ROWS, MAX_HELP_ROWS, MAX_COMPLETION_ROWS, MAX_CHIP_ROWS, CONFIRM_PROMPT_ROWS, DEFAULT_CONTENT_REPAINT_THROTTLE_MS, DEFAULT_MAX_SCROLLBACK_LINES, BARE_URL_RE, Screen, NON_ASCII, SEGMENTER, ambiguousWide, TK_MARKUP_STYLE_CHAR, shortId;
10544
10839
  var init_screen = __esm({
10545
10840
  "src/tui/screen.ts"() {
10546
10841
  "use strict";
@@ -10569,6 +10864,9 @@ var init_screen = __esm({
10569
10864
  dispatcher;
10570
10865
  onKey;
10571
10866
  onBlockClick;
10867
+ onBlockVisible;
10868
+ // Keyed blocks awaiting a one-shot "became visible" notification.
10869
+ pendingVisibleKeys = /* @__PURE__ */ new Set();
10572
10870
  lines = [];
10573
10871
  // Tracks contiguous blocks of lines that callers may want to mutate in
10574
10872
  // place (e.g. tool-call rows that update from "pending" to "completed",
@@ -10716,6 +11014,7 @@ var init_screen = __esm({
10716
11014
  this.dispatcher = opts.dispatcher;
10717
11015
  this.onKey = opts.onKey;
10718
11016
  this.onBlockClick = opts.onBlockClick;
11017
+ this.onBlockVisible = opts.onBlockVisible;
10719
11018
  this.contentRepaintThrottleMs = opts.repaintThrottleMs ?? DEFAULT_CONTENT_REPAINT_THROTTLE_MS;
10720
11019
  this.maxScrollbackLines = opts.maxScrollbackLines ?? DEFAULT_MAX_SCROLLBACK_LINES;
10721
11020
  this.mouseEnabled = opts.mouse ?? false;
@@ -11721,7 +12020,11 @@ uncaught: ${err.stack ?? err.message}
11721
12020
  // plan to its eventual tail position, and the separator we're adding
11722
12021
  // here is for whatever content is *about* to be appended (which will
11723
12022
  // land at sticky.start and push the plan back via moveStickyToEnd).
11724
- ensureSeparator() {
12023
+ // `bodyStyle` tags the inserted blank so a draw-time filter can drop it
12024
+ // together with the block it precedes. Thoughts pass "thought" so the ^T
12025
+ // hide-thoughts filter removes the gap above a hidden thought instead of
12026
+ // leaving an orphaned blank line in scrollback.
12027
+ ensureSeparator(bodyStyle) {
11725
12028
  if (this.lines.length === 0) {
11726
12029
  return;
11727
12030
  }
@@ -11736,6 +12039,9 @@ uncaught: ${err.stack ?? err.message}
11736
12039
  return;
11737
12040
  }
11738
12041
  const sep2 = { body: "" };
12042
+ if (bodyStyle !== void 0) {
12043
+ sep2.bodyStyle = bodyStyle;
12044
+ }
11739
12045
  if (stickyAtEnd) {
11740
12046
  this.lines.splice(sticky.start, 0, sep2);
11741
12047
  sticky.start += 1;
@@ -12299,6 +12605,36 @@ uncaught: ${err.stack ?? err.message}
12299
12605
  }
12300
12606
  });
12301
12607
  }
12608
+ if (this.onBlockVisible && this.pendingVisibleKeys.size > 0) {
12609
+ const visible = /* @__PURE__ */ new Set();
12610
+ for (const r of slice) {
12611
+ if (r.blockKey !== void 0) {
12612
+ visible.add(r.blockKey);
12613
+ }
12614
+ }
12615
+ const fire = [];
12616
+ for (const key of this.pendingVisibleKeys) {
12617
+ if (visible.has(key)) {
12618
+ fire.push(key);
12619
+ }
12620
+ }
12621
+ for (const key of fire) {
12622
+ this.pendingVisibleKeys.delete(key);
12623
+ this.onBlockVisible(key);
12624
+ }
12625
+ }
12626
+ }
12627
+ // Register a keyed block to receive a single onBlockVisible callback the
12628
+ // next time any of its rows are painted in the visible window. If it's
12629
+ // already on screen the pending repaint fires it promptly; otherwise it
12630
+ // waits until the block scrolls into view. No-op without an onBlockVisible
12631
+ // handler.
12632
+ notifyWhenVisible(key) {
12633
+ if (!this.onBlockVisible) {
12634
+ return;
12635
+ }
12636
+ this.pendingVisibleKeys.add(key);
12637
+ this.scheduleRepaint();
12302
12638
  }
12303
12639
  queuedRows() {
12304
12640
  return Math.min(MAX_QUEUED_ROWS, this.queuedTexts.length);
@@ -12878,7 +13214,8 @@ uncaught: ${err.stack ?? err.message}
12878
13214
  }
12879
13215
  }
12880
13216
  const prefix = line.prefix ?? "";
12881
- const room = Math.max(1, width - prefix.length);
13217
+ const prefixCols = cellWidth(prefix);
13218
+ const room = Math.max(1, width - prefixCols);
12882
13219
  const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
12883
13220
  const chunks = line.ansi ? wrapAnsiBody(line.body, room) : wrap(line.body, room, { stripMarkup });
12884
13221
  const wrapped = [];
@@ -12886,7 +13223,7 @@ uncaught: ${err.stack ?? err.message}
12886
13223
  for (let i = 0; i < chunks.length; i++) {
12887
13224
  const chunk = chunks[i] ?? "";
12888
13225
  const wrappedLine = {
12889
- prefix: i === 0 ? line.prefix : " ".repeat(prefix.length),
13226
+ prefix: i === 0 ? line.prefix : " ".repeat(prefixCols),
12890
13227
  body: chunk
12891
13228
  };
12892
13229
  if (line.prefixStyle !== void 0) {
@@ -12927,7 +13264,7 @@ uncaught: ${err.stack ?? err.message}
12927
13264
  if (line.prefix) {
12928
13265
  writeStyled(this.term, line.prefix, line.prefixStyle ?? line.bodyStyle);
12929
13266
  }
12930
- const remaining = Math.max(0, width - (line.prefix?.length ?? 0));
13267
+ const remaining = Math.max(0, width - cellWidth(line.prefix ?? ""));
12931
13268
  const stripMarkup = bodyStyleUsesMarkup(line.bodyStyle);
12932
13269
  const bodyText = line.ansi ? line.body : truncate(line.body, remaining, { stripMarkup });
12933
13270
  if (this.scrollbackHighlight !== null && !line.ansi) {
@@ -12943,7 +13280,7 @@ uncaught: ${err.stack ?? err.message}
12943
13280
  writeStyled(this.term, bodyText, line.bodyStyle);
12944
13281
  }
12945
13282
  if (line.fillRow) {
12946
- const visible = line.ansi ? stringWidth2(bodyText) : bodyText.length;
13283
+ const visible = line.ansi ? stringWidth2(bodyText) : cellWidth(bodyText);
12947
13284
  const pad = remaining - visible;
12948
13285
  if (pad > 0) {
12949
13286
  writeStyled(this.term, " ".repeat(pad), line.bodyStyle);
@@ -12962,6 +13299,7 @@ uncaught: ${err.stack ?? err.message}
12962
13299
  };
12963
13300
  NON_ASCII = /[^\x20-\x7e]/;
12964
13301
  SEGMENTER = new Intl.Segmenter(void 0, { granularity: "grapheme" });
13302
+ ambiguousWide = false;
12965
13303
  TK_MARKUP_STYLE_CHAR = /[a-zA-Z+\-:_!#/]/;
12966
13304
  shortId = stripHydraSessionPrefix;
12967
13305
  }
@@ -13166,7 +13504,7 @@ async function promptForImportAction(term, session) {
13166
13504
  };
13167
13505
  render();
13168
13506
  term.hideCursor();
13169
- return await new Promise((resolve8) => {
13507
+ return await new Promise((resolve9) => {
13170
13508
  let resolved = false;
13171
13509
  const cleanup = () => {
13172
13510
  if (resolved) {
@@ -13181,7 +13519,7 @@ async function promptForImportAction(term, session) {
13181
13519
  };
13182
13520
  const finish = (value) => {
13183
13521
  cleanup();
13184
- resolve8(value);
13522
+ resolve9(value);
13185
13523
  };
13186
13524
  const onResize = () => {
13187
13525
  if (resolved) {
@@ -13285,7 +13623,7 @@ async function promptForLaunchOrView(term, session, focus) {
13285
13623
  };
13286
13624
  render();
13287
13625
  term.hideCursor();
13288
- return await new Promise((resolve8) => {
13626
+ return await new Promise((resolve9) => {
13289
13627
  let resolved = false;
13290
13628
  const cleanup = () => {
13291
13629
  resolved = true;
@@ -13293,7 +13631,7 @@ async function promptForLaunchOrView(term, session, focus) {
13293
13631
  const finish = (value) => {
13294
13632
  cleanup();
13295
13633
  focus.pop();
13296
- resolve8(value);
13634
+ resolve9(value);
13297
13635
  };
13298
13636
  const onKey = (name, _m, data) => {
13299
13637
  if (name === "CTRL_C" || name === "CTRL_D") {
@@ -14228,7 +14566,7 @@ async function pickSession(term, opts) {
14228
14566
  }
14229
14567
  };
14230
14568
  renderFromScratch();
14231
- return await new Promise((resolve8) => {
14569
+ return await new Promise((resolve9) => {
14232
14570
  let resolved = false;
14233
14571
  let autoRefreshTimer = null;
14234
14572
  let autoRefreshInFlight = false;
@@ -14315,7 +14653,7 @@ async function pickSession(term, opts) {
14315
14653
  };
14316
14654
  const tryAbort = () => {
14317
14655
  cleanup();
14318
- resolve8({ kind: currentSessionGone ? "exit" : "abort" });
14656
+ resolve9({ kind: currentSessionGone ? "exit" : "abort" });
14319
14657
  return true;
14320
14658
  };
14321
14659
  const renderFingerprint = () => {
@@ -14495,7 +14833,7 @@ ${cells}`;
14495
14833
  onKey: (name) => {
14496
14834
  if (name === "CTRL_C") {
14497
14835
  cleanup();
14498
- resolve8({ kind: "abort" });
14836
+ resolve9({ kind: "abort" });
14499
14837
  return;
14500
14838
  }
14501
14839
  popLayer();
@@ -14609,7 +14947,7 @@ ${cells}`;
14609
14947
  if (session.agentId !== void 0) {
14610
14948
  result.agentId = session.agentId;
14611
14949
  }
14612
- resolve8(result);
14950
+ resolve9(result);
14613
14951
  return;
14614
14952
  }
14615
14953
  void (async () => {
@@ -14620,7 +14958,7 @@ ${cells}`;
14620
14958
  }, focus);
14621
14959
  if (action === "cancel") {
14622
14960
  cleanup();
14623
- resolve8({ kind: "abort" });
14961
+ resolve9({ kind: "abort" });
14624
14962
  return;
14625
14963
  }
14626
14964
  if (action === "back") return;
@@ -14633,7 +14971,7 @@ ${cells}`;
14633
14971
  if (session?.agentId !== void 0) {
14634
14972
  result.agentId = session.agentId;
14635
14973
  }
14636
- resolve8(result);
14974
+ resolve9(result);
14637
14975
  })();
14638
14976
  return;
14639
14977
  }
@@ -14794,9 +15132,9 @@ ${cells}`;
14794
15132
  cleanup();
14795
15133
  const text = composer.expandedText();
14796
15134
  if (text.trim().length === 0) {
14797
- resolve8({ kind: "new" });
15135
+ resolve9({ kind: "new" });
14798
15136
  } else {
14799
- resolve8({ kind: "new", prompt: text });
15137
+ resolve9({ kind: "new", prompt: text });
14800
15138
  }
14801
15139
  return;
14802
15140
  }
@@ -14920,7 +15258,7 @@ ${cells}`;
14920
15258
  }
14921
15259
  if (name === "c" || name === "C") {
14922
15260
  cleanup();
14923
- resolve8({ kind: "new" });
15261
+ resolve9({ kind: "new" });
14924
15262
  return;
14925
15263
  }
14926
15264
  if (name === "q" || name === "Q") {
@@ -14977,7 +15315,7 @@ ${cells}`;
14977
15315
  if (session.agentId !== void 0) {
14978
15316
  result.agentId = session.agentId;
14979
15317
  }
14980
- resolve8(result);
15318
+ resolve9(result);
14981
15319
  return;
14982
15320
  }
14983
15321
  if ((name === "f" || name === "F") && selectedIdx > 0) {
@@ -15000,7 +15338,7 @@ ${cells}`;
15000
15338
  if (session.upstreamSessionId !== void 0) {
15001
15339
  result.sourceUpstreamSessionId = session.upstreamSessionId;
15002
15340
  }
15003
- resolve8(result);
15341
+ resolve9(result);
15004
15342
  return;
15005
15343
  }
15006
15344
  if ((name === "k" || name === "K") && selectedIdx > 0) {
@@ -15089,12 +15427,12 @@ ${cells}`;
15089
15427
  case "KP_ENTER": {
15090
15428
  cleanup();
15091
15429
  if (selectedIdx === 0) {
15092
- resolve8({ kind: "new" });
15430
+ resolve9({ kind: "new" });
15093
15431
  return;
15094
15432
  }
15095
15433
  const session = visible[selectedIdx - 1];
15096
15434
  if (!session) {
15097
- resolve8({ kind: "abort" });
15435
+ resolve9({ kind: "abort" });
15098
15436
  return;
15099
15437
  }
15100
15438
  const result = {
@@ -15104,7 +15442,7 @@ ${cells}`;
15104
15442
  if (session.agentId !== void 0) {
15105
15443
  result.agentId = session.agentId;
15106
15444
  }
15107
- resolve8(result);
15445
+ resolve9(result);
15108
15446
  return;
15109
15447
  }
15110
15448
  case "ESCAPE":
@@ -15385,7 +15723,7 @@ async function promptForImportCwd(term, session, opts = {}) {
15385
15723
  }
15386
15724
  };
15387
15725
  render();
15388
- return await new Promise((resolve8) => {
15726
+ return await new Promise((resolve9) => {
15389
15727
  let resolved = false;
15390
15728
  const cleanup = () => {
15391
15729
  if (resolved) {
@@ -15400,7 +15738,7 @@ async function promptForImportCwd(term, session, opts = {}) {
15400
15738
  };
15401
15739
  const finish = (value) => {
15402
15740
  cleanup();
15403
- resolve8(value);
15741
+ resolve9(value);
15404
15742
  };
15405
15743
  const onResize = () => {
15406
15744
  if (resolved) {
@@ -15606,7 +15944,7 @@ async function promptForAgent(term, agents) {
15606
15944
  };
15607
15945
  render();
15608
15946
  term.hideCursor();
15609
- return await new Promise((resolve8) => {
15947
+ return await new Promise((resolve9) => {
15610
15948
  let resolved = false;
15611
15949
  const cleanup = () => {
15612
15950
  if (resolved) {
@@ -15621,7 +15959,7 @@ async function promptForAgent(term, agents) {
15621
15959
  };
15622
15960
  const finish = (value) => {
15623
15961
  cleanup();
15624
- resolve8(value);
15962
+ resolve9(value);
15625
15963
  };
15626
15964
  const onResize = () => {
15627
15965
  if (resolved) {
@@ -15717,7 +16055,7 @@ var init_agent_prompt = __esm({
15717
16055
 
15718
16056
  // src/tui/clipboard.ts
15719
16057
  import { spawn as nodeSpawn } from "child_process";
15720
- import fs24 from "fs/promises";
16058
+ import fs25 from "fs/promises";
15721
16059
  import os6 from "os";
15722
16060
  import path19 from "path";
15723
16061
  async function readClipboard(envIn = {}) {
@@ -15758,7 +16096,7 @@ async function readMacOS(env) {
15758
16096
  return img;
15759
16097
  }
15760
16098
  } catch {
15761
- await fs24.unlink(tmpPath).catch(() => void 0);
16099
+ await fs25.unlink(tmpPath).catch(() => void 0);
15762
16100
  }
15763
16101
  try {
15764
16102
  const buf = await runCapture(env.spawn, "pbpaste", []);
@@ -15873,9 +16211,9 @@ async function which(env, cmd) {
15873
16211
  }
15874
16212
  async function readFileAsAttachment(p, unlinkAfter) {
15875
16213
  try {
15876
- const buf = await fs24.readFile(p);
16214
+ const buf = await fs25.readFile(p);
15877
16215
  if (unlinkAfter) {
15878
- await fs24.unlink(p).catch(() => void 0);
16216
+ await fs25.unlink(p).catch(() => void 0);
15879
16217
  }
15880
16218
  if (buf.length === 0) {
15881
16219
  return { ok: false, reason: "no image on clipboard" };
@@ -15901,14 +16239,14 @@ async function readFileAsAttachment(p, unlinkAfter) {
15901
16239
  }
15902
16240
  }
15903
16241
  function run2(spawn7, cmd, args) {
15904
- return new Promise((resolve8, reject) => {
16242
+ return new Promise((resolve9, reject) => {
15905
16243
  const proc = spawn7(cmd, args);
15906
16244
  proc.stdout?.on("data", () => void 0);
15907
16245
  proc.stderr?.on("data", () => void 0);
15908
16246
  proc.on("error", reject);
15909
16247
  proc.on("close", (code) => {
15910
16248
  if (code === 0) {
15911
- resolve8();
16249
+ resolve9();
15912
16250
  } else {
15913
16251
  reject(new Error(`${cmd} exited ${code}`));
15914
16252
  }
@@ -15916,7 +16254,7 @@ function run2(spawn7, cmd, args) {
15916
16254
  });
15917
16255
  }
15918
16256
  function runCapture(spawn7, cmd, args) {
15919
- return new Promise((resolve8, reject) => {
16257
+ return new Promise((resolve9, reject) => {
15920
16258
  const proc = spawn7(cmd, args);
15921
16259
  const chunks = [];
15922
16260
  let stdoutEnded = proc.stdout === null;
@@ -15928,7 +16266,7 @@ function runCapture(spawn7, cmd, args) {
15928
16266
  }
15929
16267
  settled = true;
15930
16268
  if (closedCode === 0) {
15931
- resolve8(Buffer.concat(chunks));
16269
+ resolve9(Buffer.concat(chunks));
15932
16270
  } else {
15933
16271
  reject(new Error(`${cmd} exited ${closedCode}`));
15934
16272
  }
@@ -15974,6 +16312,109 @@ var init_clipboard = __esm({
15974
16312
  }
15975
16313
  });
15976
16314
 
16315
+ // src/tui/file-completion.ts
16316
+ import * as fs26 from "fs";
16317
+ import * as os7 from "os";
16318
+ import * as path20 from "path";
16319
+ function extractPathToken(line, col) {
16320
+ let start = col;
16321
+ while (start > 0) {
16322
+ const ch = line[start - 1] ?? "";
16323
+ if (/\s/.test(ch)) {
16324
+ if (ch === " " && line[start - 2] === "\\") {
16325
+ start -= 2;
16326
+ continue;
16327
+ }
16328
+ break;
16329
+ }
16330
+ start -= 1;
16331
+ }
16332
+ if (start === col) {
16333
+ return null;
16334
+ }
16335
+ return { token: line.slice(start, col), start };
16336
+ }
16337
+ function looksLikePath(token) {
16338
+ if (token.length === 0) {
16339
+ return false;
16340
+ }
16341
+ if (token.includes("/")) {
16342
+ return true;
16343
+ }
16344
+ return token === "~" || token === "." || token === "..";
16345
+ }
16346
+ function unescapeToken(token) {
16347
+ return token.replace(/\\ /g, " ");
16348
+ }
16349
+ function escapeToken(token) {
16350
+ return token.replace(/ /g, "\\ ");
16351
+ }
16352
+ function splitToken(token) {
16353
+ const slash = token.lastIndexOf("/");
16354
+ if (slash === -1) {
16355
+ return { dirPrefix: "", base: token };
16356
+ }
16357
+ return { dirPrefix: token.slice(0, slash + 1), base: token.slice(slash + 1) };
16358
+ }
16359
+ function resolveDir(dirPrefix, cwd) {
16360
+ let p = dirPrefix.length === 0 ? "." : dirPrefix;
16361
+ if (p === "~" || p.startsWith("~/")) {
16362
+ p = os7.homedir() + p.slice(1);
16363
+ }
16364
+ return path20.isAbsolute(p) ? p : path20.resolve(cwd, p);
16365
+ }
16366
+ function readDir(dir) {
16367
+ let dirents;
16368
+ try {
16369
+ dirents = fs26.readdirSync(dir, { withFileTypes: true });
16370
+ } catch {
16371
+ return null;
16372
+ }
16373
+ return dirents.map((d) => ({
16374
+ name: d.name,
16375
+ isDir: d.isDirectory()
16376
+ }));
16377
+ }
16378
+ function completePathToken(token, cwd, listDir = readDir) {
16379
+ if (!looksLikePath(token)) {
16380
+ return null;
16381
+ }
16382
+ const raw = unescapeToken(token);
16383
+ const { dirPrefix, base } = splitToken(raw);
16384
+ const dir = resolveDir(dirPrefix, cwd);
16385
+ const entries = listDir(dir);
16386
+ if (entries === null) {
16387
+ return null;
16388
+ }
16389
+ const showHidden = base.startsWith(".");
16390
+ const matched = entries.filter(
16391
+ (e) => e.name.startsWith(base) && (showHidden || !e.name.startsWith("."))
16392
+ );
16393
+ if (matched.length === 0) {
16394
+ return null;
16395
+ }
16396
+ const candidates = matched.map((e) => e.isDir ? e.name + "/" : e.name);
16397
+ if (matched.length === 1) {
16398
+ const only = matched[0];
16399
+ const completedBase = only.isDir ? only.name + "/" : only.name;
16400
+ return {
16401
+ replacement: escapeToken(dirPrefix + completedBase),
16402
+ candidates
16403
+ };
16404
+ }
16405
+ const common = longestCommonPrefix(matched.map((e) => e.name));
16406
+ return {
16407
+ replacement: escapeToken(dirPrefix + common),
16408
+ candidates
16409
+ };
16410
+ }
16411
+ var init_file_completion = __esm({
16412
+ "src/tui/file-completion.ts"() {
16413
+ "use strict";
16414
+ init_completion();
16415
+ }
16416
+ });
16417
+
15977
16418
  // src/tui/reconnect-state.ts
15978
16419
  function parseReattachResponse(result) {
15979
16420
  const out = {};
@@ -16024,8 +16465,8 @@ var init_reconnect_state = __esm({
16024
16465
  import { appendFileSync, statSync as statSync2, renameSync as renameSync2 } from "fs";
16025
16466
  import { nanoid as nanoid3 } from "nanoid";
16026
16467
  import termkit from "terminal-kit";
16027
- import fs25 from "fs/promises";
16028
- import path20 from "path";
16468
+ import fs27 from "fs/promises";
16469
+ import path21 from "path";
16029
16470
  function parseResponseConfigOptions(raw) {
16030
16471
  if (!Array.isArray(raw)) {
16031
16472
  return void 0;
@@ -16441,10 +16882,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16441
16882
  if (pendingPermission.toolCallId && toolCallId && pendingPermission.toolCallId !== toolCallId) {
16442
16883
  return;
16443
16884
  }
16444
- const resolve8 = pendingPermission.resolve;
16885
+ const resolve9 = pendingPermission.resolve;
16445
16886
  pendingPermission = null;
16446
16887
  screen.setPermissionPrompt(null);
16447
- resolve8(result ?? { outcome: { outcome: "cancelled" } });
16888
+ resolve9(result ?? { outcome: { outcome: "cancelled" } });
16448
16889
  };
16449
16890
  const maybeDismissPermissionByToolUpdate = (update) => {
16450
16891
  if (!pendingPermission?.toolCallId) {
@@ -16478,14 +16919,14 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16478
16919
  if (!pendingPermission) {
16479
16920
  return;
16480
16921
  }
16481
- const { options, resolve: resolve8 } = pendingPermission;
16922
+ const { options, resolve: resolve9 } = pendingPermission;
16482
16923
  pendingPermission = null;
16483
16924
  screen.setPermissionPrompt(null);
16484
16925
  if (optionId === null) {
16485
- resolve8({ outcome: { outcome: "cancelled" } });
16926
+ resolve9({ outcome: { outcome: "cancelled" } });
16486
16927
  return;
16487
16928
  }
16488
- resolve8({ outcome: { outcome: "selected", optionId } });
16929
+ resolve9({ outcome: { outcome: "selected", optionId } });
16489
16930
  void options;
16490
16931
  };
16491
16932
  conn.onRequest("session/request_permission", async (params) => {
@@ -16518,13 +16959,13 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16518
16959
  ]);
16519
16960
  return { outcome: { outcome: "cancelled" } };
16520
16961
  }
16521
- return new Promise((resolve8) => {
16962
+ return new Promise((resolve9) => {
16522
16963
  pendingPermission = {
16523
16964
  title,
16524
16965
  detail,
16525
16966
  options,
16526
16967
  selectedIndex: 0,
16527
- resolve: resolve8,
16968
+ resolve: resolve9,
16528
16969
  toolCallId
16529
16970
  };
16530
16971
  refreshPermissionPrompt();
@@ -16617,6 +17058,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16617
17058
  if (opts.readonly === true) {
16618
17059
  attachHydraMeta.readonly = true;
16619
17060
  }
17061
+ if (config.tui.toolContent === "references") {
17062
+ attachHydraMeta.toolContent = "references";
17063
+ }
16620
17064
  if (opts.drip === true) {
16621
17065
  attachHydraMeta.replayMode = "drip";
16622
17066
  if (opts.dripSpeed !== void 0) {
@@ -16705,6 +17149,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16705
17149
  }
16706
17150
  let turnInFlight = null;
16707
17151
  let pendingPrefill = null;
17152
+ setAmbiguousWide(config.tui.ambiguousWidth === "wide");
17153
+ setDiffContextLines(config.tui.diffContextLines);
16708
17154
  const screen = new Screen({
16709
17155
  term,
16710
17156
  dispatcher,
@@ -16719,6 +17165,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16719
17165
  onBlockClick: (key) => {
16720
17166
  handleBlockClick(key);
16721
17167
  },
17168
+ // Lazy-load deferred (references-mode) diff bodies only when the block
17169
+ // scrolls into view.
17170
+ onBlockVisible: (key) => {
17171
+ handleBlockVisible(key);
17172
+ },
16722
17173
  onKey: (events) => {
16723
17174
  for (const ev of events) {
16724
17175
  if (pendingPermission && tryHandlePermissionKey(ev)) {
@@ -16798,26 +17249,58 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16798
17249
  }
16799
17250
  return matches;
16800
17251
  };
17252
+ let fileCompletions = [];
16801
17253
  const refreshCompletions = () => {
16802
- screen.setCompletions(currentCompletions());
17254
+ const slash = currentCompletions();
17255
+ if (slash.length > 0) {
17256
+ fileCompletions = [];
17257
+ screen.setCompletions(slash);
17258
+ return;
17259
+ }
17260
+ screen.setCompletions(fileCompletions);
16803
17261
  };
16804
17262
  const tryHandleCompletionKey = (ev) => {
16805
17263
  if (ev.type !== "key" || ev.name !== "tab") {
17264
+ if (fileCompletions.length > 0) {
17265
+ fileCompletions = [];
17266
+ }
16806
17267
  return false;
16807
17268
  }
16808
17269
  const matches = currentCompletions();
16809
- if (matches.length === 0) {
17270
+ if (matches.length > 0) {
17271
+ fileCompletions = [];
17272
+ const firstLine3 = dispatcher.state().buffer[0] ?? "";
17273
+ const next = computeTabCompletion({
17274
+ matches: matches.map((m) => m.name),
17275
+ firstLine: firstLine3
17276
+ });
17277
+ if (next === null) {
17278
+ return true;
17279
+ }
17280
+ dispatcher.replaceFirstLine(next);
17281
+ return true;
17282
+ }
17283
+ return tryHandleFileCompletion();
17284
+ };
17285
+ const tryHandleFileCompletion = () => {
17286
+ const st = dispatcher.state();
17287
+ const line = st.buffer[st.row] ?? "";
17288
+ const tok = extractPathToken(line, st.col);
17289
+ if (tok === null) {
16810
17290
  return false;
16811
17291
  }
16812
- const firstLine3 = dispatcher.state().buffer[0] ?? "";
16813
- const next = computeTabCompletion({
16814
- matches: matches.map((m) => m.name),
16815
- firstLine: firstLine3
16816
- });
16817
- if (next === null) {
16818
- return true;
17292
+ const result = completePathToken(tok.token, resolvedCwd);
17293
+ if (result === null) {
17294
+ return false;
16819
17295
  }
16820
- dispatcher.replaceFirstLine(next);
17296
+ if (result.replacement !== tok.token) {
17297
+ dispatcher.replaceRangeOnCurrentLine(
17298
+ tok.start,
17299
+ st.col,
17300
+ result.replacement
17301
+ );
17302
+ }
17303
+ fileCompletions = result.candidates.length > 1 ? result.candidates.map((name) => ({ name })) : [];
16821
17304
  return true;
16822
17305
  };
16823
17306
  const tryHandleScrollbackSearchKey = (ev) => {
@@ -16937,8 +17420,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
16937
17420
  }
16938
17421
  });
16939
17422
  let finishSession = null;
16940
- const sessionDone = new Promise((resolve8) => {
16941
- finishSession = resolve8;
17423
+ const sessionDone = new Promise((resolve9) => {
17424
+ finishSession = resolve9;
16942
17425
  });
16943
17426
  const cancelRemoteTurn = () => {
16944
17427
  conn.notify("session/cancel", { sessionId: resolvedSessionId }).catch(() => void 0);
@@ -17557,11 +18040,11 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
17557
18040
  }
17558
18041
  const mimeType = mimeFromExtension(token);
17559
18042
  if (!mimeType) {
17560
- screen.notify(`unsupported image type: ${path20.basename(token)}`);
18043
+ screen.notify(`unsupported image type: ${path21.basename(token)}`);
17561
18044
  continue;
17562
18045
  }
17563
18046
  try {
17564
- const buf = await fs25.readFile(token);
18047
+ const buf = await fs27.readFile(token);
17565
18048
  if (buf.length > MAX_ATTACHMENT_BYTES) {
17566
18049
  screen.notify(
17567
18050
  `image too large (${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)})`
@@ -17571,13 +18054,13 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
17571
18054
  dispatcher.addAttachment({
17572
18055
  mimeType,
17573
18056
  data: buf.toString("base64"),
17574
- name: path20.basename(token),
18057
+ name: path21.basename(token),
17575
18058
  sizeBytes: buf.length
17576
18059
  });
17577
18060
  added++;
17578
18061
  } catch (err) {
17579
18062
  screen.notify(
17580
- `cannot read ${path20.basename(token)}: ${err.message}`
18063
+ `cannot read ${path21.basename(token)}: ${err.message}`
17581
18064
  );
17582
18065
  }
17583
18066
  }
@@ -18132,7 +18615,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18132
18615
  if (text.length === 0)
18133
18616
  return;
18134
18617
  if (thoughtKey === null) {
18135
- screen.ensureSeparator();
18618
+ screen.ensureSeparator("thought");
18136
18619
  thoughtKey = `thought:${thoughtSeq}`;
18137
18620
  thoughtSeq += 1;
18138
18621
  thoughtBuffer = "";
@@ -18250,7 +18733,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18250
18733
  toolsBlockStopReason = null;
18251
18734
  renderToolsBlock();
18252
18735
  };
18253
- const recordToolCall = (id, title, status, errorText, editDiff) => {
18736
+ const recordToolCall = (id, title, status, errorText, editDiff, detail) => {
18254
18737
  const wasNew = !toolStates.has(id);
18255
18738
  const existing = toolStates.get(id);
18256
18739
  const state = existing ?? {
@@ -18262,6 +18745,9 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18262
18745
  if (existing && title !== void 0) {
18263
18746
  state.latestTitle = title;
18264
18747
  }
18748
+ if (detail !== void 0 && state.detail === void 0) {
18749
+ state.detail = detail;
18750
+ }
18265
18751
  if (existing && status !== void 0) {
18266
18752
  state.status = status;
18267
18753
  }
@@ -18289,41 +18775,100 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18289
18775
  toolCallOrder.push(id);
18290
18776
  }
18291
18777
  };
18292
- const maybeRenderEditDiff = (toolCallId) => {
18293
- const key = `editdiff:${toolCallId}`;
18294
- const lines = (() => {
18295
- const mode = viewPrefs.showFileUpdates;
18296
- if (mode === "none") {
18297
- return null;
18778
+ const fetchToolContent = async (hash) => {
18779
+ try {
18780
+ const res = await conn.request("hydra-acp/session/tool_content", {
18781
+ sessionId: resolvedSessionId,
18782
+ hash
18783
+ });
18784
+ return typeof res?.content === "string" ? res.content : null;
18785
+ } catch {
18786
+ return null;
18787
+ }
18788
+ };
18789
+ const fetchingDiffs = /* @__PURE__ */ new Set();
18790
+ const resolveDeferredDiff = (toolCallId, diff) => {
18791
+ if (fetchingDiffs.has(toolCallId)) {
18792
+ return;
18793
+ }
18794
+ if (diff.oldRef === void 0 && diff.newRef === void 0) {
18795
+ return;
18796
+ }
18797
+ fetchingDiffs.add(toolCallId);
18798
+ void (async () => {
18799
+ const [oldText, newText] = await Promise.all([
18800
+ diff.oldRef ? fetchToolContent(diff.oldRef.hash) : Promise.resolve(diff.oldText),
18801
+ diff.newRef ? fetchToolContent(diff.newRef.hash) : Promise.resolve(diff.newText)
18802
+ ]);
18803
+ const failed = diff.oldRef !== void 0 && oldText === null || diff.newRef !== void 0 && newText === null;
18804
+ if (failed) {
18805
+ fetchingDiffs.delete(toolCallId);
18806
+ const out = formatEditDiffBlock(diff, "diff", { deferredStatus: "error" });
18807
+ if (out.length > 0) {
18808
+ screen.upsertLines(`editdiff:${toolCallId}`, out);
18809
+ screen.repaintNow();
18810
+ }
18811
+ return;
18298
18812
  }
18299
- const state = toolStates.get(toolCallId);
18300
- if (!state?.editDiff || state.status !== "completed") {
18301
- return null;
18813
+ const resolved = {
18814
+ ...diff.path !== void 0 ? { path: diff.path } : {},
18815
+ oldText: oldText ?? "",
18816
+ newText: newText ?? ""
18817
+ };
18818
+ renderedEditDiffs.set(toolCallId, resolved);
18819
+ const st = toolStates.get(toolCallId);
18820
+ if (st?.editDiff) {
18821
+ st.editDiff = resolved;
18302
18822
  }
18823
+ fetchingDiffs.delete(toolCallId);
18303
18824
  const override = editDiffOverrides.get(toolCallId);
18304
- if (override !== void 0) {
18305
- const out2 = formatEditDiffBlock(
18306
- state.editDiff,
18307
- override ? "diff" : "edit"
18308
- );
18309
- return out2.length > 0 ? out2 : null;
18825
+ const mode = override === void 0 ? viewPrefs.showFileUpdates : override ? "diff" : "edit";
18826
+ if (mode !== "none") {
18827
+ renderEditDiffBlock(toolCallId, resolved, mode === "diff" ? "diff" : "edit");
18828
+ screen.repaintNow();
18310
18829
  }
18311
- if (mode === "diff") {
18312
- const out2 = formatEditDiffBlock(state.editDiff, "diff");
18313
- return out2.length > 0 ? out2 : null;
18314
- }
18315
- const out = formatEditDiffBlock(state.editDiff, "edit");
18316
- return out.length > 0 ? out : null;
18317
18830
  })();
18318
- if (lines === null) {
18831
+ };
18832
+ const renderEditDiffBlock = (toolCallId, diff, mode) => {
18833
+ const key = `editdiff:${toolCallId}`;
18834
+ const out = formatEditDiffBlock(diff, mode);
18835
+ if (out.length === 0) {
18319
18836
  screen.removeKey(key);
18320
18837
  return;
18321
18838
  }
18322
- const diff = toolStates.get(toolCallId)?.editDiff;
18323
- if (diff) {
18324
- renderedEditDiffs.set(toolCallId, diff);
18839
+ screen.upsertLines(key, out);
18840
+ if (mode === "diff" && (diff.oldRef !== void 0 || diff.newRef !== void 0)) {
18841
+ screen.notifyWhenVisible(key);
18325
18842
  }
18326
- screen.upsertLines(key, lines);
18843
+ };
18844
+ const handleBlockVisible = (key) => {
18845
+ if (!key.startsWith("editdiff:")) {
18846
+ return;
18847
+ }
18848
+ const id = key.slice("editdiff:".length);
18849
+ const diff = renderedEditDiffs.get(id);
18850
+ if (diff && (diff.oldRef !== void 0 || diff.newRef !== void 0)) {
18851
+ resolveDeferredDiff(id, diff);
18852
+ }
18853
+ };
18854
+ const maybeRenderEditDiff = (toolCallId) => {
18855
+ const key = `editdiff:${toolCallId}`;
18856
+ const globalMode = viewPrefs.showFileUpdates;
18857
+ const state = toolStates.get(toolCallId);
18858
+ let mode;
18859
+ if (globalMode === "none" || !state?.editDiff || state.status !== "completed") {
18860
+ mode = null;
18861
+ } else {
18862
+ const override = editDiffOverrides.get(toolCallId);
18863
+ mode = override !== void 0 ? override ? "diff" : "edit" : globalMode === "diff" ? "diff" : "edit";
18864
+ }
18865
+ if (mode === null) {
18866
+ screen.removeKey(key);
18867
+ return;
18868
+ }
18869
+ const diff = state.editDiff;
18870
+ renderedEditDiffs.set(toolCallId, diff);
18871
+ renderEditDiffBlock(toolCallId, diff, mode);
18327
18872
  };
18328
18873
  const reRenderAllEditDiffs = () => {
18329
18874
  const globalMode = viewPrefs.showFileUpdates;
@@ -18335,12 +18880,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18335
18880
  screen.removeKey(key);
18336
18881
  continue;
18337
18882
  }
18338
- const out = formatEditDiffBlock(diff, mode);
18339
- if (out.length === 0) {
18340
- screen.removeKey(key);
18341
- } else {
18342
- screen.upsertLines(key, out);
18343
- }
18883
+ renderEditDiffBlock(toolCallId, diff, mode === "diff" ? "diff" : "edit");
18344
18884
  }
18345
18885
  };
18346
18886
  const handleBlockClick = (key) => {
@@ -18353,12 +18893,7 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18353
18893
  const current = editDiffOverrides.get(id) ?? viewPrefs.showFileUpdates === "diff";
18354
18894
  editDiffOverrides.set(id, !current);
18355
18895
  const next = !current ? "diff" : "edit";
18356
- const out = formatEditDiffBlock(diff, next);
18357
- if (out.length === 0) {
18358
- screen.removeKey(key);
18359
- } else {
18360
- screen.upsertLines(key, out);
18361
- }
18896
+ renderEditDiffBlock(id, diff, next);
18362
18897
  screen.repaintNow();
18363
18898
  return;
18364
18899
  }
@@ -18512,7 +19047,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18512
19047
  event.title,
18513
19048
  event.status,
18514
19049
  void 0,
18515
- event.editDiff
19050
+ event.editDiff,
19051
+ event.detail
18516
19052
  );
18517
19053
  renderToolsBlock();
18518
19054
  maybeRenderEditDiff(event.toolCallId);
@@ -18540,7 +19076,8 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18540
19076
  event.title,
18541
19077
  event.status,
18542
19078
  event.errorText,
18543
- event.editDiff
19079
+ event.editDiff,
19080
+ event.detail
18544
19081
  );
18545
19082
  if (event.upstreamInterrupted) {
18546
19083
  upstreamInterruptedSeen = true;
@@ -18669,10 +19206,10 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18669
19206
  }
18670
19207
  const resetInFlightUiState = () => {
18671
19208
  if (pendingPermission) {
18672
- const resolve8 = pendingPermission.resolve;
19209
+ const resolve9 = pendingPermission.resolve;
18673
19210
  pendingPermission = null;
18674
19211
  screen.setPermissionPrompt(null);
18675
- resolve8({ outcome: { outcome: "cancelled" } });
19212
+ resolve9({ outcome: { outcome: "cancelled" } });
18676
19213
  }
18677
19214
  closeAgentText();
18678
19215
  closeThought();
@@ -18724,17 +19261,20 @@ async function runSession(term, config, target, opts, exitHint, viewPrefs, picke
18724
19261
  historyPolicy: useAfterMessage ? "after_message" : "none",
18725
19262
  ...useAfterMessage ? { afterMessageId: lastSeenMessageId } : {},
18726
19263
  clientInfo: { name: "hydra-acp-tui", version: HYDRA_VERSION },
18727
- ...upstreamSessionId !== void 0 ? {
18728
- _meta: {
18729
- [HYDRA_META_KEY]: {
18730
- resume: {
18731
- upstreamSessionId,
18732
- agentId: resolvedAgentId,
18733
- cwd: resolvedCwd
18734
- }
18735
- }
19264
+ ...(() => {
19265
+ const meta = {};
19266
+ if (upstreamSessionId !== void 0) {
19267
+ meta.resume = {
19268
+ upstreamSessionId,
19269
+ agentId: resolvedAgentId,
19270
+ cwd: resolvedCwd
19271
+ };
18736
19272
  }
18737
- } : {}
19273
+ if (config.tui.toolContent === "references") {
19274
+ meta.toolContent = "references";
19275
+ }
19276
+ return Object.keys(meta).length > 0 ? { _meta: { [HYDRA_META_KEY]: meta } } : {};
19277
+ })()
18738
19278
  }
18739
19279
  };
18740
19280
  reconnectReplayBuffer = [];
@@ -19232,6 +19772,7 @@ var init_app = __esm({
19232
19772
  init_attachments();
19233
19773
  init_clipboard();
19234
19774
  init_completion();
19775
+ init_file_completion();
19235
19776
  init_reconnect_state();
19236
19777
  init_render_update();
19237
19778
  init_format();
@@ -19239,7 +19780,7 @@ var init_app = __esm({
19239
19780
  HELP_ENTRIES_TAIL = [
19240
19781
  ["Alt+Enter", "newline in prompt"],
19241
19782
  ["Shift+Tab", "cycle agent modes (plan / accept-edits / etc.)"],
19242
- ["Tab", "indent \xB7 slash-command completion"],
19783
+ ["Tab", "indent \xB7 slash-command / file-path completion"],
19243
19784
  null,
19244
19785
  ["\u2191 / \u2193", "prompt history \xB7 queue navigation"],
19245
19786
  ["\u2190/\u2192 Home/End", "cursor movement"],
@@ -19284,7 +19825,7 @@ var init_tui = __esm({
19284
19825
  // src/cli.ts
19285
19826
  import { readFileSync as readFileSync2 } from "fs";
19286
19827
  import { fileURLToPath as fileURLToPath2 } from "url";
19287
- import { dirname as dirname8, resolve as resolve7 } from "path";
19828
+ import { dirname as dirname8, resolve as resolve8 } from "path";
19288
19829
 
19289
19830
  // src/cli/parse-args.ts
19290
19831
  var KNOWN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -19481,7 +20022,7 @@ import { setTimeout as sleep2 } from "timers/promises";
19481
20022
  import chalk from "chalk";
19482
20023
 
19483
20024
  // src/daemon/server.ts
19484
- import * as fs18 from "fs";
20025
+ import * as fs19 from "fs";
19485
20026
  import * as fsp7 from "fs/promises";
19486
20027
  import Fastify from "fastify";
19487
20028
  import websocketPlugin from "@fastify/websocket";
@@ -19500,16 +20041,117 @@ init_config();
19500
20041
  import pino from "pino";
19501
20042
  import createPinoRoll from "pino-roll";
19502
20043
 
20044
+ // src/core/tool-store.ts
20045
+ init_paths();
20046
+ import * as fs5 from "fs/promises";
20047
+ import { createHash } from "crypto";
20048
+ import { gzip as gzipCb, gunzip as gunzipCb } from "zlib";
20049
+ import { promisify } from "util";
20050
+ var gzip = promisify(gzipCb);
20051
+ var gunzip = promisify(gunzipCb);
20052
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
20053
+ var HASH_PATTERN = /^[a-f0-9]{64}$/;
20054
+ var compressBlobs = true;
20055
+ function setToolBlobCompression(enabled) {
20056
+ compressBlobs = enabled;
20057
+ }
20058
+ function safe(sessionId, hash) {
20059
+ if (!SESSION_ID_PATTERN.test(sessionId)) {
20060
+ return false;
20061
+ }
20062
+ return hash === void 0 || HASH_PATTERN.test(hash);
20063
+ }
20064
+ function gzPath(sessionId, hash) {
20065
+ return `${paths.toolBlobFile(sessionId, hash)}.gz`;
20066
+ }
20067
+ async function putToolBlob(sessionId, text) {
20068
+ if (!safe(sessionId)) {
20069
+ return null;
20070
+ }
20071
+ const hash = createHash("sha256").update(text, "utf8").digest("hex");
20072
+ const gzFile = gzPath(sessionId, hash);
20073
+ const plainFile = paths.toolBlobFile(sessionId, hash);
20074
+ for (const existing of [gzFile, plainFile]) {
20075
+ try {
20076
+ await fs5.access(existing);
20077
+ return hash;
20078
+ } catch {
20079
+ }
20080
+ }
20081
+ await fs5.mkdir(paths.toolsDir(sessionId), { recursive: true });
20082
+ const file = compressBlobs ? gzFile : plainFile;
20083
+ const data = compressBlobs ? await gzip(Buffer.from(text, "utf8")) : Buffer.from(text, "utf8");
20084
+ await fs5.writeFile(file, data, { mode: 384, flag: "wx" }).catch(async (err) => {
20085
+ if (err.code !== "EEXIST") {
20086
+ throw err;
20087
+ }
20088
+ });
20089
+ return hash;
20090
+ }
20091
+ async function getToolBlob(sessionId, hash) {
20092
+ if (!safe(sessionId, hash)) {
20093
+ return null;
20094
+ }
20095
+ try {
20096
+ const buf = await fs5.readFile(gzPath(sessionId, hash));
20097
+ return (await gunzip(buf)).toString("utf8");
20098
+ } catch {
20099
+ }
20100
+ try {
20101
+ return await fs5.readFile(paths.toolBlobFile(sessionId, hash), "utf8");
20102
+ } catch {
20103
+ return null;
20104
+ }
20105
+ }
20106
+ async function readToolBlobGz(sessionId, hash) {
20107
+ if (!safe(sessionId, hash)) {
20108
+ return null;
20109
+ }
20110
+ try {
20111
+ return await fs5.readFile(gzPath(sessionId, hash));
20112
+ } catch {
20113
+ }
20114
+ try {
20115
+ const plain = await fs5.readFile(paths.toolBlobFile(sessionId, hash));
20116
+ return await gzip(plain);
20117
+ } catch {
20118
+ return null;
20119
+ }
20120
+ }
20121
+ async function writeToolBlobGz(sessionId, hash, gzBytes) {
20122
+ if (!safe(sessionId, hash)) {
20123
+ return;
20124
+ }
20125
+ const file = gzPath(sessionId, hash);
20126
+ try {
20127
+ await fs5.access(file);
20128
+ return;
20129
+ } catch {
20130
+ }
20131
+ await fs5.mkdir(paths.toolsDir(sessionId), { recursive: true });
20132
+ await fs5.writeFile(file, gzBytes, { mode: 384, flag: "wx" }).catch((err) => {
20133
+ if (err.code !== "EEXIST") {
20134
+ throw err;
20135
+ }
20136
+ });
20137
+ }
20138
+ async function deleteToolBlobs(sessionId) {
20139
+ if (!SESSION_ID_PATTERN.test(sessionId)) {
20140
+ return;
20141
+ }
20142
+ await fs5.rm(paths.toolsDir(sessionId), { recursive: true, force: true }).catch(() => void 0);
20143
+ }
20144
+
19503
20145
  // src/core/registry.ts
19504
20146
  init_paths();
19505
20147
  init_json_store();
19506
- import * as fs6 from "fs/promises";
20148
+ import * as fs7 from "fs/promises";
19507
20149
  import * as path5 from "path";
19508
20150
  import { z as z2 } from "zod";
19509
20151
 
19510
20152
  // src/core/binary-install.ts
19511
20153
  init_paths();
19512
- import * as fs5 from "fs";
20154
+ import * as fs6 from "fs";
19513
20155
  import * as fsp from "fs/promises";
19514
20156
  import * as path3 from "path";
19515
20157
  import { spawn } from "child_process";
@@ -19642,7 +20284,7 @@ async function downloadTo(args) {
19642
20284
  );
19643
20285
  }
19644
20286
  const total = Number(response.headers.get("content-length") ?? "0");
19645
- const out = fs5.createWriteStream(dest);
20287
+ const out = fs6.createWriteStream(dest);
19646
20288
  const nodeStream = Readable.fromWeb(response.body);
19647
20289
  safeEmit(args.onProgress, {
19648
20290
  phase: "download_start",
@@ -19673,10 +20315,10 @@ async function downloadTo(args) {
19673
20315
  logSink(formatProgress(args.agentId, received, total));
19674
20316
  }
19675
20317
  });
19676
- await new Promise((resolve8, reject) => {
20318
+ await new Promise((resolve9, reject) => {
19677
20319
  nodeStream.on("error", reject);
19678
20320
  out.on("error", reject);
19679
- out.on("finish", () => resolve8());
20321
+ out.on("finish", () => resolve9());
19680
20322
  nodeStream.pipe(out);
19681
20323
  });
19682
20324
  logSink(formatProgress(
@@ -19728,14 +20370,14 @@ async function extract(archivePath, dest) {
19728
20370
  throw new Error(`Unsupported archive format: ${archivePath}`);
19729
20371
  }
19730
20372
  function run(cmd, args) {
19731
- return new Promise((resolve8, reject) => {
20373
+ return new Promise((resolve9, reject) => {
19732
20374
  const child = spawn(cmd, args, {
19733
20375
  stdio: ["ignore", "ignore", "inherit"]
19734
20376
  });
19735
20377
  child.on("error", reject);
19736
20378
  child.on("exit", (code, signal) => {
19737
20379
  if (code === 0) {
19738
- resolve8();
20380
+ resolve9();
19739
20381
  return;
19740
20382
  }
19741
20383
  reject(
@@ -19747,11 +20389,11 @@ function run(cmd, args) {
19747
20389
  });
19748
20390
  }
19749
20391
  async function hasCommand(name) {
19750
- return new Promise((resolve8) => {
20392
+ return new Promise((resolve9) => {
19751
20393
  const finder = process.platform === "win32" ? "where" : "which";
19752
20394
  const child = spawn(finder, [name], { stdio: "ignore" });
19753
- child.on("error", () => resolve8(false));
19754
- child.on("exit", (code) => resolve8(code === 0));
20395
+ child.on("error", () => resolve9(false));
20396
+ child.on("exit", (code) => resolve9(code === 0));
19755
20397
  });
19756
20398
  }
19757
20399
  async function fileExists(p) {
@@ -19879,7 +20521,7 @@ function runNpmInstall(args) {
19879
20521
  }
19880
20522
  async function runNpmInstallOnce(args, attempt) {
19881
20523
  try {
19882
- await new Promise((resolve8, reject) => {
20524
+ await new Promise((resolve9, reject) => {
19883
20525
  const registryArgs = args.registry ? ["--registry", args.registry] : [];
19884
20526
  let child;
19885
20527
  try {
@@ -19921,7 +20563,7 @@ async function runNpmInstallOnce(args, attempt) {
19921
20563
  });
19922
20564
  child.on("exit", (code, signal) => {
19923
20565
  if (code === 0) {
19924
- resolve8();
20566
+ resolve9();
19925
20567
  return;
19926
20568
  }
19927
20569
  const reason = code !== null ? `exit code ${code}` : `signal ${signal ?? "unknown"}`;
@@ -20297,7 +20939,7 @@ async function agentInstallState(agent) {
20297
20939
  }
20298
20940
  async function fileExists3(p) {
20299
20941
  try {
20300
- await fs6.access(p);
20942
+ await fs7.access(p);
20301
20943
  return true;
20302
20944
  } catch {
20303
20945
  return false;
@@ -20380,7 +21022,7 @@ async function planSpawn(agent, callerArgs = [], options = {}) {
20380
21022
 
20381
21023
  // src/core/agent-instance.ts
20382
21024
  import { spawn as spawn3 } from "child_process";
20383
- import * as fs7 from "fs";
21025
+ import * as fs8 from "fs";
20384
21026
  import * as path6 from "path";
20385
21027
 
20386
21028
  // src/acp/framing.ts
@@ -20437,13 +21079,13 @@ function ndjsonStreamFromStdio(stdout, stdin) {
20437
21079
  throw new Error("stream is closed");
20438
21080
  }
20439
21081
  const line = JSON.stringify(message) + "\n";
20440
- await new Promise((resolve8, reject) => {
21082
+ await new Promise((resolve9, reject) => {
20441
21083
  stdin.write(line, (err) => {
20442
21084
  if (err) {
20443
21085
  reject(err);
20444
21086
  return;
20445
21087
  }
20446
- resolve8();
21088
+ resolve9();
20447
21089
  });
20448
21090
  });
20449
21091
  },
@@ -20602,8 +21244,8 @@ stderr: ${tail}` : reason;
20602
21244
  function openAgentLog(agentId) {
20603
21245
  try {
20604
21246
  const logPath = paths.agentLogFile(agentId);
20605
- fs7.mkdirSync(path6.dirname(logPath), { recursive: true });
20606
- const stream = fs7.createWriteStream(logPath, { flags: "a" });
21247
+ fs8.mkdirSync(path6.dirname(logPath), { recursive: true });
21248
+ const stream = fs8.createWriteStream(logPath, { flags: "a" });
20607
21249
  stream.on("error", () => void 0);
20608
21250
  return stream;
20609
21251
  } catch {
@@ -20612,7 +21254,7 @@ function openAgentLog(agentId) {
20612
21254
  }
20613
21255
 
20614
21256
  // src/core/session-manager.ts
20615
- import * as fs15 from "fs/promises";
21257
+ import * as fs16 from "fs/promises";
20616
21258
  import * as os3 from "os";
20617
21259
  import * as path10 from "path";
20618
21260
  import { customAlphabet as customAlphabet3 } from "nanoid";
@@ -20621,7 +21263,7 @@ init_session();
20621
21263
  // src/core/session-store.ts
20622
21264
  init_paths();
20623
21265
  init_json_store();
20624
- import * as fs9 from "fs/promises";
21266
+ import * as fs10 from "fs/promises";
20625
21267
  import * as path7 from "path";
20626
21268
  import { customAlphabet as customAlphabet2 } from "nanoid";
20627
21269
  import { z as z5 } from "zod";
@@ -20828,9 +21470,9 @@ var SessionRecord = z5.object({
20828
21470
  createdAt: z5.string(),
20829
21471
  updatedAt: z5.string()
20830
21472
  });
20831
- var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
21473
+ var SESSION_ID_PATTERN2 = /^[A-Za-z0-9_-]+$/;
20832
21474
  function assertSafeId(id) {
20833
- if (!SESSION_ID_PATTERN.test(id)) {
21475
+ if (!SESSION_ID_PATTERN2.test(id)) {
20834
21476
  throw new Error(`unsafe session id: ${id}`);
20835
21477
  }
20836
21478
  }
@@ -20843,7 +21485,7 @@ var SessionStore = class {
20843
21485
  });
20844
21486
  }
20845
21487
  async read(sessionId) {
20846
- if (!SESSION_ID_PATTERN.test(sessionId)) {
21488
+ if (!SESSION_ID_PATTERN2.test(sessionId)) {
20847
21489
  return void 0;
20848
21490
  }
20849
21491
  const parsed = await readJsonSafe(paths.sessionFile(sessionId));
@@ -20857,11 +21499,11 @@ var SessionStore = class {
20857
21499
  }
20858
21500
  }
20859
21501
  async delete(sessionId) {
20860
- if (!SESSION_ID_PATTERN.test(sessionId)) {
21502
+ if (!SESSION_ID_PATTERN2.test(sessionId)) {
20861
21503
  return;
20862
21504
  }
20863
21505
  try {
20864
- await fs9.unlink(paths.sessionFile(sessionId));
21506
+ await fs10.unlink(paths.sessionFile(sessionId));
20865
21507
  } catch (err) {
20866
21508
  const e = err;
20867
21509
  if (e.code !== "ENOENT") {
@@ -20869,7 +21511,7 @@ var SessionStore = class {
20869
21511
  }
20870
21512
  }
20871
21513
  try {
20872
- await fs9.rmdir(paths.sessionDir(sessionId));
21514
+ await fs10.rmdir(paths.sessionDir(sessionId));
20873
21515
  } catch (err) {
20874
21516
  const e = err;
20875
21517
  if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
@@ -20899,7 +21541,7 @@ var SessionStore = class {
20899
21541
  async list() {
20900
21542
  let entries;
20901
21543
  try {
20902
- entries = await fs9.readdir(paths.sessionsDir());
21544
+ entries = await fs10.readdir(paths.sessionsDir());
20903
21545
  } catch (err) {
20904
21546
  const e = err;
20905
21547
  if (e.code === "ENOENT") {
@@ -20952,7 +21594,7 @@ function recordFromMemorySession(args) {
20952
21594
  // src/core/tombstone-store.ts
20953
21595
  init_paths();
20954
21596
  init_json_store();
20955
- import * as fs10 from "fs/promises";
21597
+ import * as fs11 from "fs/promises";
20956
21598
  import { z as z6 } from "zod";
20957
21599
  var Tombstone = z6.object({
20958
21600
  version: z6.literal(1),
@@ -20981,7 +21623,7 @@ var TombstoneStore = class {
20981
21623
  }
20982
21624
  async has(agentId, upstreamSessionId) {
20983
21625
  try {
20984
- await fs10.access(paths.tombstoneFile(agentId, upstreamSessionId));
21626
+ await fs11.access(paths.tombstoneFile(agentId, upstreamSessionId));
20985
21627
  return true;
20986
21628
  } catch {
20987
21629
  return false;
@@ -21019,7 +21661,7 @@ var TombstoneStore = class {
21019
21661
  }
21020
21662
  async remove(agentId, upstreamSessionId) {
21021
21663
  try {
21022
- await fs10.unlink(paths.tombstoneFile(agentId, upstreamSessionId));
21664
+ await fs11.unlink(paths.tombstoneFile(agentId, upstreamSessionId));
21023
21665
  } catch (err) {
21024
21666
  const e = err;
21025
21667
  if (e.code !== "ENOENT") {
@@ -21027,7 +21669,7 @@ var TombstoneStore = class {
21027
21669
  }
21028
21670
  }
21029
21671
  try {
21030
- await fs10.rmdir(paths.tombstoneAgentDir(agentId));
21672
+ await fs11.rmdir(paths.tombstoneAgentDir(agentId));
21031
21673
  } catch (err) {
21032
21674
  const e = err;
21033
21675
  if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
@@ -21041,7 +21683,7 @@ var TombstoneStore = class {
21041
21683
  }
21042
21684
  let agents;
21043
21685
  try {
21044
- agents = await fs10.readdir(paths.tombstonesDir());
21686
+ agents = await fs11.readdir(paths.tombstonesDir());
21045
21687
  } catch (err) {
21046
21688
  const e = err;
21047
21689
  if (e.code === "ENOENT") {
@@ -21064,7 +21706,7 @@ var TombstoneStore = class {
21064
21706
  async listForAgent(agentId) {
21065
21707
  let files;
21066
21708
  try {
21067
- files = await fs10.readdir(paths.tombstoneAgentDir(agentId));
21709
+ files = await fs11.readdir(paths.tombstoneAgentDir(agentId));
21068
21710
  } catch (err) {
21069
21711
  const e = err;
21070
21712
  if (e.code === "ENOENT") {
@@ -21099,7 +21741,7 @@ function shouldResurrectFromUpstream(tombstone, listingUpdatedAt) {
21099
21741
  }
21100
21742
 
21101
21743
  // src/core/synopsis-coordinator.ts
21102
- import * as fs12 from "fs/promises";
21744
+ import * as fs13 from "fs/promises";
21103
21745
 
21104
21746
  // src/core/synopsis-agent.ts
21105
21747
  init_types();
@@ -21311,13 +21953,13 @@ ${SNAPSHOT_PROMPT}` : SNAPSHOT_PROMPT;
21311
21953
  return parsed;
21312
21954
  })();
21313
21955
  return await new Promise(
21314
- (resolve8, reject) => {
21956
+ (resolve9, reject) => {
21315
21957
  timer = setTimeout(() => {
21316
21958
  timedOut = true;
21317
21959
  opts.logger?.warn(
21318
21960
  `synopsis: agent ${opts.agentId} timed out after ${timeoutMs}ms`
21319
21961
  );
21320
- resolve8(void 0);
21962
+ resolve9(void 0);
21321
21963
  }, timeoutMs);
21322
21964
  timer.unref?.();
21323
21965
  work.then(
@@ -21326,7 +21968,7 @@ ${SNAPSHOT_PROMPT}` : SNAPSHOT_PROMPT;
21326
21968
  clearTimeout(timer);
21327
21969
  }
21328
21970
  if (!timedOut) {
21329
- resolve8(v);
21971
+ resolve9(v);
21330
21972
  }
21331
21973
  },
21332
21974
  (err) => {
@@ -21418,10 +22060,10 @@ function extractFilesTouchedDetailed(history) {
21418
22060
  byTool.set(call.toolName, (byTool.get(call.toolName) ?? 0) + 1);
21419
22061
  }
21420
22062
  }
21421
- return [...fileTouches.entries()].map(([path21, byTool]) => {
22063
+ return [...fileTouches.entries()].map(([path22, byTool]) => {
21422
22064
  const perTool = [...byTool.entries()].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
21423
22065
  const total = perTool.reduce((s, t) => s + t.count, 0);
21424
- return { path: path21, count: total, byTool: perTool };
22066
+ return { path: path22, count: total, byTool: perTool };
21425
22067
  }).sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
21426
22068
  }
21427
22069
  function extractFilesTouched(history) {
@@ -21622,7 +22264,7 @@ var SynopsisCoordinator = class {
21622
22264
  });
21623
22265
  const modelId = this.opts.synopsisModel;
21624
22266
  const synopsisCwd = paths.sessionDir(sessionId);
21625
- await fs12.mkdir(synopsisCwd, { recursive: true }).catch(() => void 0);
22267
+ await fs13.mkdir(synopsisCwd, { recursive: true }).catch(() => void 0);
21626
22268
  this.opts.logger?.info(
21627
22269
  `synopsis: start sessionId=${sessionId} agentId=${synopsisAgentId} historyLen=${history.length} model=${JSON.stringify(modelId ?? "(default)")} cwd=${synopsisCwd}`
21628
22270
  );
@@ -21726,8 +22368,217 @@ function describeFields(s) {
21726
22368
 
21727
22369
  // src/core/history-store.ts
21728
22370
  init_paths();
21729
- import * as fs13 from "fs/promises";
21730
- var SESSION_ID_PATTERN2 = /^[A-Za-z0-9_-]+$/;
22371
+ import * as fs14 from "fs/promises";
22372
+
22373
+ // src/core/tool-content.ts
22374
+ function parseToolContentMode(raw) {
22375
+ return raw === "summary" ? "summary" : "inline";
22376
+ }
22377
+ var SUMMARY_TEXT_CAP = 256;
22378
+ function applyToolContentMode(entries, mode) {
22379
+ if (mode !== "summary") {
22380
+ return entries;
22381
+ }
22382
+ return entries.map(summarizeEntry);
22383
+ }
22384
+ function summarizeEntry(entry) {
22385
+ if (entry.method !== "session/update") {
22386
+ return entry;
22387
+ }
22388
+ const params = entry.params;
22389
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
22390
+ return entry;
22391
+ }
22392
+ const p = params;
22393
+ const update = p.update;
22394
+ if (!update || typeof update !== "object" || Array.isArray(update)) {
22395
+ return entry;
22396
+ }
22397
+ const u = update;
22398
+ if (u.sessionUpdate !== "tool_call" && u.sessionUpdate !== "tool_call_update") {
22399
+ return entry;
22400
+ }
22401
+ const newUpdate = { ...u };
22402
+ if (Array.isArray(u.content)) {
22403
+ newUpdate.content = u.content.map(summarizeBlock);
22404
+ }
22405
+ const rawOutput = u.rawOutput;
22406
+ if (rawOutput && typeof rawOutput === "object" && !Array.isArray(rawOutput)) {
22407
+ const ro = rawOutput;
22408
+ const slim = {};
22409
+ if (ro.error !== void 0) {
22410
+ slim.error = clip(ro.error);
22411
+ }
22412
+ if (ro.metadata !== void 0) {
22413
+ slim.metadata = ro.metadata;
22414
+ }
22415
+ newUpdate.rawOutput = slim;
22416
+ }
22417
+ return { ...entry, params: { ...p, update: newUpdate } };
22418
+ }
22419
+ function isDiffBlock(block) {
22420
+ return !!block && typeof block === "object" && !Array.isArray(block) && block.type === "diff";
22421
+ }
22422
+ function summarizeBlock(block) {
22423
+ if (isDiffBlock(block)) {
22424
+ const b2 = block;
22425
+ const out2 = {
22426
+ type: "diff",
22427
+ oldText: "",
22428
+ newText: ""
22429
+ };
22430
+ if (typeof b2.path === "string") {
22431
+ out2.path = b2.path;
22432
+ }
22433
+ return out2;
22434
+ }
22435
+ if (!block || typeof block !== "object" || Array.isArray(block)) {
22436
+ return block;
22437
+ }
22438
+ const b = block;
22439
+ const out = { ...b };
22440
+ if (typeof b.text === "string") {
22441
+ out.text = clip(b.text);
22442
+ }
22443
+ if (typeof b.content === "string") {
22444
+ out.content = clip(b.content);
22445
+ } else if (b.content && typeof b.content === "object") {
22446
+ out.content = summarizeBlock(b.content);
22447
+ }
22448
+ return out;
22449
+ }
22450
+ function clip(value) {
22451
+ if (typeof value === "string" && value.length > SUMMARY_TEXT_CAP) {
22452
+ const elided = value.length - SUMMARY_TEXT_CAP;
22453
+ return `${value.slice(0, SUMMARY_TEXT_CAP)}\u2026[+${elided} chars omitted from summary export]`;
22454
+ }
22455
+ return value;
22456
+ }
22457
+ var TOOL_BLOB_THRESHOLD = 2048;
22458
+ function collectToolBlobHashes(entries) {
22459
+ const out = /* @__PURE__ */ new Set();
22460
+ const walk = (value) => {
22461
+ if (isToolBlobRef(value)) {
22462
+ out.add(value.__hydraBlob);
22463
+ return;
22464
+ }
22465
+ if (Array.isArray(value)) {
22466
+ for (const item of value) {
22467
+ walk(item);
22468
+ }
22469
+ return;
22470
+ }
22471
+ if (value && typeof value === "object") {
22472
+ for (const v of Object.values(value)) {
22473
+ walk(v);
22474
+ }
22475
+ }
22476
+ };
22477
+ for (const entry of entries) {
22478
+ walk(entry.params);
22479
+ }
22480
+ return out;
22481
+ }
22482
+ function isToolBlobRef(value) {
22483
+ return !!value && typeof value === "object" && !Array.isArray(value) && typeof value.__hydraBlob === "string";
22484
+ }
22485
+ function isToolEntry(entry) {
22486
+ if (entry.method !== "session/update") {
22487
+ return false;
22488
+ }
22489
+ const params = entry.params;
22490
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
22491
+ return false;
22492
+ }
22493
+ const update = params.update;
22494
+ if (!update || typeof update !== "object" || Array.isArray(update)) {
22495
+ return false;
22496
+ }
22497
+ const kind = update.sessionUpdate;
22498
+ return kind === "tool_call" || kind === "tool_call_update";
22499
+ }
22500
+ async function externalizeToolEntry(entry, put) {
22501
+ if (!isToolEntry(entry)) {
22502
+ return entry;
22503
+ }
22504
+ const p = entry.params;
22505
+ const update = p.update;
22506
+ const newUpdate = await deepExternalize(update, put);
22507
+ return { ...entry, params: { ...p, update: newUpdate } };
22508
+ }
22509
+ async function deepExternalize(value, put) {
22510
+ if (typeof value === "string") {
22511
+ if (value.length <= TOOL_BLOB_THRESHOLD) {
22512
+ return value;
22513
+ }
22514
+ const hash = await put(value);
22515
+ if (hash === null) {
22516
+ return value;
22517
+ }
22518
+ const ref = { __hydraBlob: hash, bytes: value.length };
22519
+ return ref;
22520
+ }
22521
+ if (Array.isArray(value)) {
22522
+ const out = [];
22523
+ for (const item of value) {
22524
+ out.push(await deepExternalize(item, put));
22525
+ }
22526
+ return out;
22527
+ }
22528
+ if (value && typeof value === "object") {
22529
+ const out = {};
22530
+ for (const [k, v] of Object.entries(value)) {
22531
+ out[k] = await deepExternalize(v, put);
22532
+ }
22533
+ return out;
22534
+ }
22535
+ return value;
22536
+ }
22537
+ async function expandToolRefs(entry, get) {
22538
+ const params = entry.params;
22539
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
22540
+ return entry;
22541
+ }
22542
+ const expanded = await deepExpand(params, get);
22543
+ if (expanded === params) {
22544
+ return entry;
22545
+ }
22546
+ return { ...entry, params: expanded };
22547
+ }
22548
+ async function deepExpand(value, get) {
22549
+ if (isToolBlobRef(value)) {
22550
+ const text = await get(value.__hydraBlob);
22551
+ return text ?? "";
22552
+ }
22553
+ if (Array.isArray(value)) {
22554
+ let changed = false;
22555
+ const out = [];
22556
+ for (const item of value) {
22557
+ const next = await deepExpand(item, get);
22558
+ if (next !== item) {
22559
+ changed = true;
22560
+ }
22561
+ out.push(next);
22562
+ }
22563
+ return changed ? out : value;
22564
+ }
22565
+ if (value && typeof value === "object") {
22566
+ let changed = false;
22567
+ const out = {};
22568
+ for (const [k, v] of Object.entries(value)) {
22569
+ const next = await deepExpand(v, get);
22570
+ if (next !== v) {
22571
+ changed = true;
22572
+ }
22573
+ out[k] = next;
22574
+ }
22575
+ return changed ? out : value;
22576
+ }
22577
+ return value;
22578
+ }
22579
+
22580
+ // src/core/history-store.ts
22581
+ var SESSION_ID_PATTERN3 = /^[A-Za-z0-9_-]+$/;
21731
22582
  var DEFAULT_MAX_ENTRIES = 1e3;
21732
22583
  var HistoryStore = class {
21733
22584
  // Serialize writes per session id so appends and rewrites don't
@@ -21739,26 +22590,34 @@ var HistoryStore = class {
21739
22590
  this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
21740
22591
  }
21741
22592
  async append(sessionId, entry) {
21742
- if (!SESSION_ID_PATTERN2.test(sessionId)) {
22593
+ if (!SESSION_ID_PATTERN3.test(sessionId)) {
21743
22594
  return;
21744
22595
  }
21745
22596
  return this.enqueue(sessionId, async () => {
21746
- await fs13.mkdir(paths.sessionDir(sessionId), { recursive: true });
21747
- const line = JSON.stringify(entry) + "\n";
21748
- await fs13.appendFile(paths.historyFile(sessionId), line, {
22597
+ await fs14.mkdir(paths.sessionDir(sessionId), { recursive: true });
22598
+ const stored = await externalizeToolEntry(
22599
+ entry,
22600
+ (t) => putToolBlob(sessionId, t)
22601
+ );
22602
+ const line = JSON.stringify(stored) + "\n";
22603
+ await fs14.appendFile(paths.historyFile(sessionId), line, {
21749
22604
  encoding: "utf8",
21750
22605
  mode: 384
21751
22606
  });
21752
22607
  });
21753
22608
  }
21754
22609
  async rewrite(sessionId, entries) {
21755
- if (!SESSION_ID_PATTERN2.test(sessionId)) {
22610
+ if (!SESSION_ID_PATTERN3.test(sessionId)) {
21756
22611
  return;
21757
22612
  }
21758
22613
  return this.enqueue(sessionId, async () => {
21759
- await fs13.mkdir(paths.sessionDir(sessionId), { recursive: true });
21760
- const body = entries.length === 0 ? "" : entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
21761
- await fs13.writeFile(paths.historyFile(sessionId), body, {
22614
+ await fs14.mkdir(paths.sessionDir(sessionId), { recursive: true });
22615
+ const stored = [];
22616
+ for (const e of entries) {
22617
+ stored.push(await externalizeToolEntry(e, (t) => putToolBlob(sessionId, t)));
22618
+ }
22619
+ const body = stored.length === 0 ? "" : stored.map((e) => JSON.stringify(e)).join("\n") + "\n";
22620
+ await fs14.writeFile(paths.historyFile(sessionId), body, {
21762
22621
  encoding: "utf8",
21763
22622
  mode: 384
21764
22623
  });
@@ -21769,13 +22628,13 @@ var HistoryStore = class {
21769
22628
  // it's safe to invoke alongside ongoing writes; a no-op if the file is
21770
22629
  // already at or below the cap.
21771
22630
  async compact(sessionId, maxEntries) {
21772
- if (!SESSION_ID_PATTERN2.test(sessionId)) {
22631
+ if (!SESSION_ID_PATTERN3.test(sessionId)) {
21773
22632
  return;
21774
22633
  }
21775
22634
  return this.enqueue(sessionId, async () => {
21776
22635
  let raw;
21777
22636
  try {
21778
- raw = await fs13.readFile(paths.historyFile(sessionId), "utf8");
22637
+ raw = await fs14.readFile(paths.historyFile(sessionId), "utf8");
21779
22638
  } catch (err) {
21780
22639
  const e = err;
21781
22640
  if (e.code === "ENOENT") {
@@ -21788,23 +22647,29 @@ var HistoryStore = class {
21788
22647
  return;
21789
22648
  }
21790
22649
  const trimmed = lines.slice(-maxEntries);
21791
- await fs13.writeFile(paths.historyFile(sessionId), trimmed.join("\n") + "\n", {
22650
+ await fs14.writeFile(paths.historyFile(sessionId), trimmed.join("\n") + "\n", {
21792
22651
  encoding: "utf8",
21793
22652
  mode: 384
21794
22653
  });
21795
22654
  });
21796
22655
  }
21797
- async load(sessionId) {
21798
- if (!SESSION_ID_PATTERN2.test(sessionId)) {
22656
+ // `tools` selects how externalized tool content is materialized:
22657
+ // "inline" (default) — expand blob refs back to full content, so every
22658
+ // consumer sees the original recorded shape.
22659
+ // "references" — leave references in place (the lean form) for
22660
+ // clients that fetch tool content on demand.
22661
+ async load(sessionId, opts = {}) {
22662
+ if (!SESSION_ID_PATTERN3.test(sessionId)) {
21799
22663
  return [];
21800
22664
  }
22665
+ const expand = (opts.tools ?? "inline") === "inline";
21801
22666
  const pending = this.writeQueues.get(sessionId);
21802
22667
  if (pending) {
21803
22668
  await pending;
21804
22669
  }
21805
22670
  let raw;
21806
22671
  try {
21807
- raw = await fs13.readFile(paths.historyFile(sessionId), "utf8");
22672
+ raw = await fs14.readFile(paths.historyFile(sessionId), "utf8");
21808
22673
  } catch (err) {
21809
22674
  const e = err;
21810
22675
  if (e.code === "ENOENT") {
@@ -21839,10 +22704,25 @@ var HistoryStore = class {
21839
22704
  recordedAt: obj.recordedAt
21840
22705
  });
21841
22706
  }
21842
- if (out.length > this.maxEntries) {
21843
- return out.slice(-this.maxEntries);
22707
+ const kept = out.length > this.maxEntries ? out.slice(-this.maxEntries) : out;
22708
+ if (!expand) {
22709
+ return kept;
21844
22710
  }
21845
- return out;
22711
+ const blobCache = /* @__PURE__ */ new Map();
22712
+ const get = async (hash) => {
22713
+ const cached2 = blobCache.get(hash);
22714
+ if (cached2 !== void 0) {
22715
+ return cached2;
22716
+ }
22717
+ const value = await getToolBlob(sessionId, hash);
22718
+ blobCache.set(hash, value);
22719
+ return value;
22720
+ };
22721
+ const inlined = [];
22722
+ for (const entry of kept) {
22723
+ inlined.push(await expandToolRefs(entry, get));
22724
+ }
22725
+ return inlined;
21846
22726
  }
21847
22727
  // Wait for every pending append/rewrite/compact across all sessions to
21848
22728
  // settle. Daemon shutdown calls this after closing sessions so the final
@@ -21858,20 +22738,21 @@ var HistoryStore = class {
21858
22738
  await Promise.allSettled(pending);
21859
22739
  }
21860
22740
  async delete(sessionId) {
21861
- if (!SESSION_ID_PATTERN2.test(sessionId)) {
22741
+ if (!SESSION_ID_PATTERN3.test(sessionId)) {
21862
22742
  return;
21863
22743
  }
21864
22744
  return this.enqueue(sessionId, async () => {
21865
22745
  try {
21866
- await fs13.unlink(paths.historyFile(sessionId));
22746
+ await fs14.unlink(paths.historyFile(sessionId));
21867
22747
  } catch (err) {
21868
22748
  const e = err;
21869
22749
  if (e.code !== "ENOENT") {
21870
22750
  throw err;
21871
22751
  }
21872
22752
  }
22753
+ await deleteToolBlobs(sessionId);
21873
22754
  try {
21874
- await fs13.rmdir(paths.sessionDir(sessionId));
22755
+ await fs14.rmdir(paths.sessionDir(sessionId));
21875
22756
  } catch (err) {
21876
22757
  const e = err;
21877
22758
  if (e.code !== "ENOENT" && e.code !== "ENOTEMPTY") {
@@ -21958,7 +22839,12 @@ var Bundle = z7.object({
21958
22839
  }),
21959
22840
  session: BundleSession,
21960
22841
  history: z7.array(HistoryEntrySchema),
21961
- promptHistory: z7.array(z7.string()).optional()
22842
+ promptHistory: z7.array(z7.string()).optional(),
22843
+ // Externalized tool-content blobs referenced by the history, present when
22844
+ // the bundle was exported with tools=references. Map of sha256 → base64
22845
+ // of the gzipped content. Restored to the session's tools/ store on
22846
+ // import; the ref-form history hydrates from them.
22847
+ toolBlobs: z7.record(z7.string()).optional()
21962
22848
  });
21963
22849
  function encodeBundle(params) {
21964
22850
  const bundle = {
@@ -21993,6 +22879,9 @@ function encodeBundle(params) {
21993
22879
  if (params.promptHistory !== void 0) {
21994
22880
  bundle.promptHistory = params.promptHistory;
21995
22881
  }
22882
+ if (params.toolBlobs !== void 0 && Object.keys(params.toolBlobs).length > 0) {
22883
+ bundle.toolBlobs = params.toolBlobs;
22884
+ }
21996
22885
  return bundle;
21997
22886
  }
21998
22887
  function decodeBundle(raw) {
@@ -22000,6 +22889,7 @@ function decodeBundle(raw) {
22000
22889
  }
22001
22890
 
22002
22891
  // src/core/session-manager.ts
22892
+ init_model_resolve();
22003
22893
  init_types();
22004
22894
  init_hydra_version();
22005
22895
  init_queue_store();
@@ -22410,7 +23300,7 @@ var SessionManager = class {
22410
23300
  }
22411
23301
  async dirExists(cwd) {
22412
23302
  try {
22413
- return (await fs15.stat(cwd)).isDirectory();
23303
+ return (await fs16.stat(cwd)).isDirectory();
22414
23304
  } catch {
22415
23305
  return false;
22416
23306
  }
@@ -22600,6 +23490,25 @@ var SessionManager = class {
22600
23490
  }
22601
23491
  return out;
22602
23492
  }
23493
+ // Issue session/set_model for a seed model (defaultModels / --model) at
23494
+ // bootstrap, logging success or a non-fatal rejection. `where` is the
23495
+ // human-readable provenance string used in log lines. A bad id in config
23496
+ // shouldn't break session creation, so a rejection is swallowed.
23497
+ async applySeedModel(agent, sessionId, modelId, where) {
23498
+ try {
23499
+ await agent.connection.request("session/set_model", {
23500
+ sessionId,
23501
+ modelId
23502
+ });
23503
+ this.logger?.info(`${where}: session/set_model accepted`);
23504
+ return true;
23505
+ } catch (err) {
23506
+ this.logger?.warn(
23507
+ `${where} rejected by agent (${err.message}); session will use the agent's own default`
23508
+ );
23509
+ return false;
23510
+ }
23511
+ }
22603
23512
  // Bootstrap a fresh agent process: registry resolve → spawn → initialize
22604
23513
  // → session/new. Shared by create() and the /hydra agent path so both
22605
23514
  // go through the same env / capabilities / error-handling.
@@ -22648,23 +23557,31 @@ var SessionManager = class {
22648
23557
  const initialModels = extractInitialModels(newResult);
22649
23558
  const desired = params.model ?? this.defaultModels[params.agentId];
22650
23559
  if (desired && desired !== initialModel) {
22651
- const validates = initialModels.length === 0 || initialModels.some((m) => m.modelId === desired);
22652
- if (validates) {
22653
- try {
22654
- await agent.connection.request("session/set_model", {
22655
- sessionId: sessionIdRaw,
22656
- modelId: desired
22657
- });
23560
+ const resolution = resolveModelId(desired, initialModels);
23561
+ const where = params.model !== void 0 ? `model=${JSON.stringify(desired)}` : `defaultModels[${params.agentId}]=${JSON.stringify(desired)}`;
23562
+ if (resolution.kind === "exact" || resolution.kind === "none") {
23563
+ if (await this.applySeedModel(agent, sessionIdRaw, desired, where)) {
22658
23564
  initialModel = desired;
22659
- } catch (err) {
22660
- this.logger?.warn(
22661
- `defaultModels[${params.agentId}]=${JSON.stringify(desired)} rejected by agent (${err.message}); session will use ${JSON.stringify(initialModel)}`
22662
- );
22663
23565
  }
23566
+ } else if (resolution.kind === "resolved") {
23567
+ if (resolution.modelId === initialModel) {
23568
+ initialModel = resolution.modelId;
23569
+ } else if (await this.applySeedModel(
23570
+ agent,
23571
+ sessionIdRaw,
23572
+ resolution.modelId,
23573
+ `${where} resolved to ${JSON.stringify(resolution.modelId)}`
23574
+ )) {
23575
+ initialModel = resolution.modelId;
23576
+ }
23577
+ } else if (resolution.kind === "ambiguous") {
23578
+ this.logger?.warn(
23579
+ `${where} is ambiguous (trailing-segment matches [${resolution.candidates.join(", ")}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
23580
+ );
22664
23581
  } else {
22665
23582
  const known = initialModels.map((m) => m.modelId).join(", ");
22666
23583
  this.logger?.warn(
22667
- `defaultModels[${params.agentId}]=${JSON.stringify(desired)} not in agent's availableModels ([${known}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
23584
+ `${where} not in agent's availableModels ([${known}]); skipping session/set_model, session will use ${JSON.stringify(initialModel)}`
22668
23585
  );
22669
23586
  }
22670
23587
  }
@@ -22797,6 +23714,12 @@ var SessionManager = class {
22797
23714
  async loadHistory(sessionId) {
22798
23715
  return this.histories.load(sessionId);
22799
23716
  }
23717
+ // Read a single externalized tool-content blob by sha256 (the lean
23718
+ // `tools: "references"` fetch-on-expand path). Null if the session id or
23719
+ // hash is malformed, or the blob isn't present.
23720
+ async loadToolBlob(sessionId, hash) {
23721
+ return getToolBlob(sessionId, hash);
23722
+ }
22800
23723
  async loadFromDisk(sessionId) {
22801
23724
  const record = await this.store.read(sessionId);
22802
23725
  if (!record) {
@@ -23034,7 +23957,7 @@ var SessionManager = class {
23034
23957
  // disk. Backfills lineageId if the on-disk record pre-dates that
23035
23958
  // field. Returns undefined if the session doesn't exist. Callers
23036
23959
  // populate the bundle's exportedFrom metadata themselves.
23037
- async exportBundle(sessionId) {
23960
+ async exportBundle(sessionId, opts = {}) {
23038
23961
  const record = await this.store.read(sessionId);
23039
23962
  if (!record) {
23040
23963
  return void 0;
@@ -23057,9 +23980,20 @@ var SessionManager = class {
23057
23980
  }).catch(() => void 0);
23058
23981
  withLineage = backfilled;
23059
23982
  }
23060
- const history = await this.histories.load(sessionId).catch(() => []);
23983
+ const tools = opts.tools ?? "inline";
23984
+ const history = await this.histories.load(sessionId, tools === "references" ? { tools: "references" } : {}).catch(() => []);
23061
23985
  const promptHistory = await loadPromptHistorySafely(sessionId);
23062
- return { record: withLineage, history, promptHistory };
23986
+ if (tools !== "references") {
23987
+ return { record: withLineage, history, promptHistory };
23988
+ }
23989
+ const toolBlobs = {};
23990
+ for (const hash of collectToolBlobHashes(history)) {
23991
+ const gz = await readToolBlobGz(sessionId, hash);
23992
+ if (gz) {
23993
+ toolBlobs[hash] = gz.toString("base64");
23994
+ }
23995
+ }
23996
+ return { record: withLineage, history, promptHistory, toolBlobs };
23063
23997
  }
23064
23998
  // Create a local session from an imported bundle. Without `replace`,
23065
23999
  // a bundle with a lineageId we already have on disk throws
@@ -23220,9 +24154,18 @@ var SessionManager = class {
23220
24154
  args.sessionId,
23221
24155
  args.bundle.history
23222
24156
  );
24157
+ if (args.bundle.toolBlobs) {
24158
+ for (const [hash, b64] of Object.entries(args.bundle.toolBlobs)) {
24159
+ await writeToolBlobGz(
24160
+ args.sessionId,
24161
+ hash,
24162
+ Buffer.from(b64, "base64")
24163
+ ).catch(() => void 0);
24164
+ }
24165
+ }
23223
24166
  const sourceMtime = new Date(args.bundle.session.updatedAt);
23224
24167
  if (!Number.isNaN(sourceMtime.getTime())) {
23225
- await fs15.utimes(paths.historyFile(args.sessionId), sourceMtime, sourceMtime).catch(() => void 0);
24168
+ await fs16.utimes(paths.historyFile(args.sessionId), sourceMtime, sourceMtime).catch(() => void 0);
23226
24169
  }
23227
24170
  if (args.bundle.promptHistory && args.bundle.promptHistory.length > 0) {
23228
24171
  await saveHistory(
@@ -23869,7 +24812,7 @@ function findLastTurnComplete(history) {
23869
24812
  }
23870
24813
  async function loadPromptHistorySafely(sessionId) {
23871
24814
  try {
23872
- const raw = await fs15.readFile(paths.tuiHistoryFile(sessionId), "utf8");
24815
+ const raw = await fs16.readFile(paths.tuiHistoryFile(sessionId), "utf8");
23873
24816
  const out = [];
23874
24817
  for (const line of raw.split("\n")) {
23875
24818
  if (line.length === 0) {
@@ -23890,7 +24833,7 @@ async function loadPromptHistorySafely(sessionId) {
23890
24833
  }
23891
24834
  async function historyStatus(sessionId) {
23892
24835
  try {
23893
- const st = await fs15.stat(paths.historyFile(sessionId));
24836
+ const st = await fs16.stat(paths.historyFile(sessionId));
23894
24837
  return {
23895
24838
  mtime: new Date(st.mtimeMs).toISOString(),
23896
24839
  hasContent: st.size > 0
@@ -23911,7 +24854,7 @@ function effectiveInteractive(record, hasContent) {
23911
24854
 
23912
24855
  // src/core/child-supervisor.ts
23913
24856
  import { spawn as spawn4 } from "child_process";
23914
- import * as fs16 from "fs";
24857
+ import * as fs17 from "fs";
23915
24858
  import * as fsp5 from "fs/promises";
23916
24859
  import * as path11 from "path";
23917
24860
 
@@ -24026,9 +24969,9 @@ var ChildSupervisor = class {
24026
24969
  } catch {
24027
24970
  }
24028
24971
  tasks.push(
24029
- new Promise((resolve8) => {
24972
+ new Promise((resolve9) => {
24030
24973
  if (child.exitCode !== null || child.signalCode !== null) {
24031
- resolve8();
24974
+ resolve9();
24032
24975
  return;
24033
24976
  }
24034
24977
  const timer = setTimeout(() => {
@@ -24036,11 +24979,11 @@ var ChildSupervisor = class {
24036
24979
  child.kill("SIGKILL");
24037
24980
  } catch {
24038
24981
  }
24039
- resolve8();
24982
+ resolve9();
24040
24983
  }, STOP_GRACE_MS);
24041
24984
  child.on("exit", () => {
24042
24985
  clearTimeout(timer);
24043
- resolve8();
24986
+ resolve9();
24044
24987
  });
24045
24988
  })
24046
24989
  );
@@ -24164,8 +25107,8 @@ var ChildSupervisor = class {
24164
25107
  if (child.exitCode !== null || child.signalCode !== null) {
24165
25108
  return;
24166
25109
  }
24167
- const exited = new Promise((resolve8) => {
24168
- entry.exitWaiters.push(resolve8);
25110
+ const exited = new Promise((resolve9) => {
25111
+ entry.exitWaiters.push(resolve9);
24169
25112
  });
24170
25113
  try {
24171
25114
  child.kill("SIGTERM");
@@ -24284,7 +25227,7 @@ var ChildSupervisor = class {
24284
25227
  }
24285
25228
  const cfg = entry.config;
24286
25229
  const command = cfg.command.length > 0 ? cfg.command : [cfg.name];
24287
- const logStream = fs16.createWriteStream(
25230
+ const logStream = fs17.createWriteStream(
24288
25231
  this.adapter.paths.logFile(cfg.name),
24289
25232
  { flags: "a" }
24290
25233
  );
@@ -24340,7 +25283,7 @@ var ChildSupervisor = class {
24340
25283
  }
24341
25284
  if (typeof child.pid === "number") {
24342
25285
  try {
24343
- fs16.writeFileSync(
25286
+ fs17.writeFileSync(
24344
25287
  this.adapter.paths.pidFile(cfg.name),
24345
25288
  `${child.pid}
24346
25289
  `,
@@ -24366,7 +25309,7 @@ var ChildSupervisor = class {
24366
25309
  });
24367
25310
  child.on("exit", (code, signal) => {
24368
25311
  try {
24369
- fs16.unlinkSync(this.adapter.paths.pidFile(cfg.name));
25312
+ fs17.unlinkSync(this.adapter.paths.pidFile(cfg.name));
24370
25313
  } catch {
24371
25314
  }
24372
25315
  logStream.write(
@@ -24381,8 +25324,8 @@ var ChildSupervisor = class {
24381
25324
  entry.processToken = void 0;
24382
25325
  }
24383
25326
  const waiters = entry.exitWaiters.splice(0);
24384
- for (const resolve8 of waiters) {
24385
- resolve8();
25327
+ for (const resolve9 of waiters) {
25328
+ resolve9();
24386
25329
  }
24387
25330
  if (this.stopping || entry.manuallyStopped) {
24388
25331
  try {
@@ -24717,7 +25660,7 @@ init_hydra_version();
24717
25660
  init_paths();
24718
25661
  init_json_store();
24719
25662
  import * as path13 from "path";
24720
- import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
25663
+ import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
24721
25664
  var TOKEN_PREFIX = "hydra_session_";
24722
25665
  var DEFAULT_TTL_SEC = 60 * 60 * 24 * 30;
24723
25666
  var ID_LENGTH = 12;
@@ -24727,7 +25670,7 @@ function tokensFilePath() {
24727
25670
  return path13.join(paths.home(), "session-tokens.json");
24728
25671
  }
24729
25672
  function sha256Hex(input) {
24730
- return createHash("sha256").update(input).digest("hex");
25673
+ return createHash2("sha256").update(input).digest("hex");
24731
25674
  }
24732
25675
  function randomHex(bytes) {
24733
25676
  return randomBytes2(bytes).toString("hex");
@@ -25844,15 +26787,21 @@ function registerSessionRoutes(app, manager, defaults) {
25844
26787
  app.get("/v1/sessions/:id/export", async (request, reply) => {
25845
26788
  const raw = request.params.id;
25846
26789
  const id = await manager.resolveCanonicalId(raw) ?? raw;
25847
- const exported = await manager.exportBundle(id);
26790
+ const toolsRaw = request.query?.tools;
26791
+ const toolMode = toolsRaw === "references" ? "references" : parseToolContentMode(toolsRaw);
26792
+ const exported = await manager.exportBundle(
26793
+ id,
26794
+ toolMode === "references" ? { tools: "references" } : {}
26795
+ );
25848
26796
  if (!exported) {
25849
26797
  reply.code(404).send({ error: "session not found" });
25850
26798
  return;
25851
26799
  }
25852
26800
  const bundle = encodeBundle({
25853
26801
  record: exported.record,
25854
- history: exported.history,
26802
+ history: toolMode === "summary" ? applyToolContentMode(exported.history, "summary") : exported.history,
25855
26803
  promptHistory: exported.promptHistory.length > 0 ? exported.promptHistory : void 0,
26804
+ ...exported.toolBlobs !== void 0 ? { toolBlobs: exported.toolBlobs } : {},
25856
26805
  hydraVersion: HYDRA_VERSION,
25857
26806
  machine: os4.hostname(),
25858
26807
  hydraHost: resolveHydraHost(defaults)
@@ -25864,6 +26813,17 @@ function registerSessionRoutes(app, manager, defaults) {
25864
26813
  );
25865
26814
  reply.code(200).send(bundle);
25866
26815
  });
26816
+ app.get("/v1/sessions/:id/tools/:hash", async (request, reply) => {
26817
+ const params = request.params;
26818
+ const id = await manager.resolveCanonicalId(params.id) ?? params.id;
26819
+ const blob = await manager.loadToolBlob(id, params.hash);
26820
+ if (blob === null) {
26821
+ reply.code(404).send({ error: "tool blob not found" });
26822
+ return;
26823
+ }
26824
+ reply.header("Content-Type", "text/plain; charset=utf-8");
26825
+ reply.code(200).send(blob);
26826
+ });
25867
26827
  app.get("/v1/sessions/:id/transcript", async (request, reply) => {
25868
26828
  const raw = request.params.id;
25869
26829
  const id = await manager.resolveCanonicalId(raw) ?? raw;
@@ -26350,11 +27310,11 @@ import { z as z8 } from "zod";
26350
27310
 
26351
27311
  // src/core/password.ts
26352
27312
  init_paths();
26353
- import * as fs17 from "fs/promises";
27313
+ import * as fs18 from "fs/promises";
26354
27314
  import * as path15 from "path";
26355
27315
  import { randomBytes as randomBytes3, scrypt, timingSafeEqual as timingSafeEqual2 } from "crypto";
26356
- import { promisify } from "util";
26357
- var scryptAsync = promisify(scrypt);
27316
+ import { promisify as promisify2 } from "util";
27317
+ var scryptAsync = promisify2(scrypt);
26358
27318
  function passwordHashPath() {
26359
27319
  return path15.join(paths.home(), "password-hash");
26360
27320
  }
@@ -26377,15 +27337,15 @@ async function setPassword(plaintext) {
26377
27337
  });
26378
27338
  const encoded = `scrypt$${DEFAULT_N}$${DEFAULT_R}$${DEFAULT_P}$${salt.toString("hex")}$${key.toString("hex")}
26379
27339
  `;
26380
- await fs17.mkdir(paths.home(), { recursive: true });
26381
- await fs17.writeFile(passwordHashPath(), encoded, {
27340
+ await fs18.mkdir(paths.home(), { recursive: true });
27341
+ await fs18.writeFile(passwordHashPath(), encoded, {
26382
27342
  encoding: "utf8",
26383
27343
  mode: 384
26384
27344
  });
26385
27345
  }
26386
27346
  async function hasPassword() {
26387
27347
  try {
26388
- const text = await fs17.readFile(passwordHashPath(), "utf8");
27348
+ const text = await fs18.readFile(passwordHashPath(), "utf8");
26389
27349
  return text.trim().length > 0;
26390
27350
  } catch (err) {
26391
27351
  const e = err;
@@ -26401,7 +27361,7 @@ async function verifyPassword(plaintext) {
26401
27361
  }
26402
27362
  let line;
26403
27363
  try {
26404
- line = (await fs17.readFile(passwordHashPath(), "utf8")).trim();
27364
+ line = (await fs18.readFile(passwordHashPath(), "utf8")).trim();
26405
27365
  } catch (err) {
26406
27366
  const e = err;
26407
27367
  if (e.code === "ENOENT") {
@@ -26523,6 +27483,7 @@ function remoteIp(request) {
26523
27483
  // src/daemon/acp-ws.ts
26524
27484
  init_connection();
26525
27485
  init_ws_stream();
27486
+ init_model_resolve();
26526
27487
  init_types();
26527
27488
  import { nanoid as nanoid2 } from "nanoid";
26528
27489
  init_hydra_version();
@@ -26778,13 +27739,13 @@ function registerAcpWsEndpoint(app, deps) {
26778
27739
  { code: JsonRpcErrorCodes.SessionNotFound }
26779
27740
  );
26780
27741
  }
26781
- return new Promise((resolve8) => {
27742
+ return new Promise((resolve9) => {
26782
27743
  const entries = [];
26783
27744
  let unsubscribe;
26784
27745
  const finish = () => {
26785
27746
  clearTimeout(timer);
26786
27747
  unsubscribe?.();
26787
- resolve8({ entries });
27748
+ resolve9({ entries });
26788
27749
  };
26789
27750
  unsubscribe = child.onBroadcast((entry) => {
26790
27751
  entries.push(entry);
@@ -26826,6 +27787,24 @@ function registerAcpWsEndpoint(app, deps) {
26826
27787
  return { ok: true };
26827
27788
  });
26828
27789
  }
27790
+ connection.onRequest("hydra-acp/session/tool_content", async (raw) => {
27791
+ const params = raw ?? {};
27792
+ if (typeof params.sessionId !== "string" || typeof params.hash !== "string") {
27793
+ throw Object.assign(
27794
+ new Error("hydra-acp/session/tool_content requires sessionId and hash"),
27795
+ { code: JsonRpcErrorCodes.InvalidParams }
27796
+ );
27797
+ }
27798
+ const id = await deps.manager.resolveCanonicalId(params.sessionId) ?? params.sessionId;
27799
+ const content = await deps.manager.loadToolBlob(id, params.hash);
27800
+ if (content === null) {
27801
+ throw Object.assign(
27802
+ new Error("tool content not found"),
27803
+ { code: JsonRpcErrorCodes.SessionNotFound }
27804
+ );
27805
+ }
27806
+ return { content };
27807
+ });
26829
27808
  connection.onRequest("session/new", async (raw) => {
26830
27809
  const params = SessionNewParams.parse(raw);
26831
27810
  const hydraMeta = extractHydraMeta(
@@ -27032,7 +28011,13 @@ function registerAcpWsEndpoint(app, deps) {
27032
28011
  const { entries: replay, appliedPolicy } = await session.attach(
27033
28012
  client,
27034
28013
  params.historyPolicy,
27035
- { afterMessageId: params.afterMessageId, raw: drip }
28014
+ {
28015
+ afterMessageId: params.afterMessageId,
28016
+ raw: drip,
28017
+ // Lean clients opt into ref-form tool content via _meta; default
28018
+ // stays inline so existing/third-party clients are unaffected.
28019
+ ...hydraAttach.toolContent !== void 0 ? { toolContent: hydraAttach.toolContent } : {}
28020
+ }
27036
28021
  );
27037
28022
  state.attached.set(session.sessionId, {
27038
28023
  sessionId: session.sessionId,
@@ -27066,8 +28051,13 @@ function registerAcpWsEndpoint(app, deps) {
27066
28051
  }
27067
28052
  })();
27068
28053
  } else {
27069
- for (const note of replay) {
27070
- await connection.notify(note.method, note.params);
28054
+ const REPLAY_FLUSH_EVERY = 200;
28055
+ for (let i = 0; i < replay.length; i++) {
28056
+ const note = replay[i];
28057
+ const pending = connection.notify(note.method, note.params).catch(() => void 0);
28058
+ if ((i + 1) % REPLAY_FLUSH_EVERY === 0) {
28059
+ await pending;
28060
+ }
27071
28061
  }
27072
28062
  }
27073
28063
  session.replayPendingPermissions(client);
@@ -27324,8 +28314,11 @@ function registerAcpWsEndpoint(app, deps) {
27324
28314
  return null;
27325
28315
  }
27326
28316
  app.log.info(decision.logMessage);
27327
- const { modelId } = rawParams;
27328
- const result = await decision.session.forwardRequest("session/set_model", rawParams);
28317
+ const { modelId } = decision;
28318
+ const result = await decision.session.forwardRequest("session/set_model", {
28319
+ ...rawParams,
28320
+ modelId
28321
+ });
27329
28322
  decision.session.applyModelChange(modelId);
27330
28323
  return result;
27331
28324
  });
@@ -27547,36 +28540,47 @@ function decideSetModel(rawParams, manager) {
27547
28540
  };
27548
28541
  }
27549
28542
  const advertised = session.availableModels();
27550
- if (advertised.length === 0) {
28543
+ const resolution = resolveModelId(params.modelId, advertised);
28544
+ if (resolution.kind === "none") {
27551
28545
  return {
27552
28546
  kind: "ok",
27553
28547
  session,
28548
+ modelId: params.modelId,
27554
28549
  logMessage: `session/set_model passthrough (no availableModels) sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)}`
27555
28550
  };
27556
28551
  }
27557
- const match = advertised.find((m) => m.modelId === params.modelId);
27558
- if (!match) {
27559
- const known = advertised.map((m) => m.modelId).join(", ");
27560
- if (session.currentModel !== void 0 && session.currentModel.length > 0) {
27561
- return {
27562
- kind: "no_op",
27563
- session,
27564
- sessionId: params.sessionId,
27565
- currentModel: session.currentModel,
27566
- logMessage: `session/set_model no_op (resyncing client) sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} actual=${JSON.stringify(session.currentModel)} agentId=${session.agentId} known=[${known}]`
27567
- };
27568
- }
28552
+ if (resolution.kind === "exact") {
27569
28553
  return {
27570
- kind: "error",
27571
- code: JsonRpcErrorCodes.InvalidParams,
27572
- message: `model "${params.modelId}" is not in this session's availableModels (agent ${session.agentId}); known models: ${known}`,
27573
- logMessage: `session/set_model rejected sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)} agentId=${session.agentId} known=[${known}] (no current model to fall back to)`
28554
+ kind: "ok",
28555
+ session,
28556
+ modelId: params.modelId,
28557
+ logMessage: `session/set_model accepted sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)}`
28558
+ };
28559
+ }
28560
+ if (resolution.kind === "resolved") {
28561
+ return {
28562
+ kind: "ok",
28563
+ session,
28564
+ modelId: resolution.modelId,
28565
+ logMessage: `session/set_model resolved sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} \u2192 ${JSON.stringify(resolution.modelId)}`
28566
+ };
28567
+ }
28568
+ const known = advertised.map((m) => m.modelId).join(", ");
28569
+ const detail = resolution.kind === "ambiguous" ? `ambiguous (trailing-segment matches [${resolution.candidates.join(", ")}])` : `not in availableModels`;
28570
+ if (session.currentModel !== void 0 && session.currentModel.length > 0) {
28571
+ return {
28572
+ kind: "no_op",
28573
+ session,
28574
+ sessionId: params.sessionId,
28575
+ currentModel: session.currentModel,
28576
+ logMessage: `session/set_model no_op (resyncing client) sessionId=${params.sessionId} requested=${JSON.stringify(params.modelId)} ${detail} actual=${JSON.stringify(session.currentModel)} agentId=${session.agentId} known=[${known}]`
27574
28577
  };
27575
28578
  }
27576
28579
  return {
27577
- kind: "ok",
27578
- session,
27579
- logMessage: `session/set_model accepted sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)}`
28580
+ kind: "error",
28581
+ code: JsonRpcErrorCodes.InvalidParams,
28582
+ message: `model "${params.modelId}" is ${detail === "not in availableModels" ? "not in this session's availableModels" : detail} (agent ${session.agentId}); known models: ${known}`,
28583
+ logMessage: `session/set_model rejected sessionId=${params.sessionId} modelId=${JSON.stringify(params.modelId)} ${detail} agentId=${session.agentId} known=[${known}] (no current model to fall back to)`
27580
28584
  };
27581
28585
  }
27582
28586
  function buildViewerResponseMeta(fromDisk) {
@@ -27714,8 +28718,8 @@ var McpTokenRegistry = class {
27714
28718
  }
27715
28719
  let resolveSession2;
27716
28720
  let rejectSession;
27717
- const sessionReady = new Promise((resolve8, reject) => {
27718
- resolveSession2 = resolve8;
28721
+ const sessionReady = new Promise((resolve9, reject) => {
28722
+ resolveSession2 = resolve9;
27719
28723
  rejectSession = reject;
27720
28724
  });
27721
28725
  sessionReady.catch(() => void 0);
@@ -28016,8 +29020,8 @@ function registerStdinMcpRoutes(app, tokenRegistry) {
28016
29020
  session = entry.session;
28017
29021
  } else {
28018
29022
  let timer;
28019
- const timeout = new Promise((resolve8) => {
28020
- timer = setTimeout(() => resolve8(void 0), SESSION_READY_TIMEOUT_MS);
29023
+ const timeout = new Promise((resolve9) => {
29024
+ timer = setTimeout(() => resolve9(void 0), SESSION_READY_TIMEOUT_MS);
28021
29025
  });
28022
29026
  const resolved = await Promise.race([
28023
29027
  entry.sessionReady.catch(() => void 0),
@@ -28270,8 +29274,8 @@ function registerExtensionMcpRoutes(app, tokenRegistry, extensionMcp, options =
28270
29274
  }
28271
29275
  if (entry.session === void 0) {
28272
29276
  let timer;
28273
- const timeout = new Promise((resolve8) => {
28274
- timer = setTimeout(() => resolve8(void 0), SESSION_READY_TIMEOUT_MS2);
29277
+ const timeout = new Promise((resolve9) => {
29278
+ timer = setTimeout(() => resolve9(void 0), SESSION_READY_TIMEOUT_MS2);
28275
29279
  });
28276
29280
  const resolved = await Promise.race([
28277
29281
  entry.sessionReady.catch(() => void 0),
@@ -28376,6 +29380,7 @@ async function startDaemon(config, serviceToken) {
28376
29380
  stderrTailBytes: config.daemon.agentStderrTailBytes,
28377
29381
  logger: agentLogger
28378
29382
  });
29383
+ setToolBlobCompression(config.compressToolContent);
28379
29384
  const extensionCommands = new ExtensionCommandRegistry();
28380
29385
  const manager = new SessionManager(registry, spawner, void 0, {
28381
29386
  idleTimeoutMs: config.daemon.sessionIdleTimeoutSeconds * 1e3,
@@ -28507,7 +29512,7 @@ async function startDaemon(config, serviceToken) {
28507
29512
  setAgentPruneLogger(null);
28508
29513
  await app.close();
28509
29514
  try {
28510
- fs18.unlinkSync(paths.pidFile());
29515
+ fs19.unlinkSync(paths.pidFile());
28511
29516
  } catch {
28512
29517
  }
28513
29518
  try {
@@ -28557,7 +29562,7 @@ init_daemon_bootstrap();
28557
29562
  init_hydra_version();
28558
29563
 
28559
29564
  // src/cli/commands/log-tail.ts
28560
- import * as fs19 from "fs";
29565
+ import * as fs20 from "fs";
28561
29566
  import * as fsp8 from "fs/promises";
28562
29567
  async function runLogTail(logPath, argv, notFoundMessage) {
28563
29568
  const opts = parseLogTailFlags(argv);
@@ -28581,7 +29586,7 @@ async function runLogTail(logPath, argv, notFoundMessage) {
28581
29586
  process.stdout.write(`-- following ${logPath} --
28582
29587
  `);
28583
29588
  let pending = false;
28584
- const watcher = fs19.watch(logPath, () => {
29589
+ const watcher = fs20.watch(logPath, () => {
28585
29590
  if (pending) {
28586
29591
  return;
28587
29592
  }
@@ -28609,10 +29614,10 @@ async function runLogTail(logPath, argv, notFoundMessage) {
28609
29614
  }
28610
29615
  });
28611
29616
  });
28612
- await new Promise((resolve8) => {
29617
+ await new Promise((resolve9) => {
28613
29618
  const finish = () => {
28614
29619
  watcher.close();
28615
- resolve8();
29620
+ resolve9();
28616
29621
  };
28617
29622
  process.once("SIGINT", finish);
28618
29623
  process.once("SIGTERM", finish);
@@ -28903,7 +29908,7 @@ init_remote_target();
28903
29908
  init_remote_url();
28904
29909
  init_session();
28905
29910
  init_discovery();
28906
- import * as fs20 from "fs/promises";
29911
+ import * as fs21 from "fs/promises";
28907
29912
  import * as path16 from "path";
28908
29913
  init_session_row();
28909
29914
  async function runSessionsList(opts = {}) {
@@ -29026,18 +30031,19 @@ async function runSessionsRemove(id) {
29026
30031
  process.stdout.write(`Removed ${id}
29027
30032
  `);
29028
30033
  }
29029
- async function runSessionsExport(id, outPath) {
30034
+ async function runSessionsExport(id, outPath, tools) {
29030
30035
  if (!id) {
29031
30036
  process.stderr.write(
29032
- "Usage: hydra-acp sessions export <session-id> [--out <file>]\n"
30037
+ "Usage: hydra-acp sessions export <session-id> [--out <file>] [--tools inline|summary]\n"
29033
30038
  );
29034
30039
  process.exit(2);
29035
30040
  }
29036
30041
  const config = await loadConfig();
29037
30042
  const serviceToken = await loadServiceToken();
29038
30043
  const baseUrl = httpBase(config.daemon.host, config.daemon.port, !!config.daemon.tls);
30044
+ const query = tools === "summary" ? "?tools=summary" : "";
29039
30045
  const response = await fetch(
29040
- `${baseUrl}/v1/sessions/${encodeURIComponent(id)}/export`,
30046
+ `${baseUrl}/v1/sessions/${encodeURIComponent(id)}/export${query}`,
29041
30047
  {
29042
30048
  headers: { Authorization: `Bearer ${serviceToken}` }
29043
30049
  }
@@ -29057,8 +30063,8 @@ async function runSessionsExport(id, outPath) {
29057
30063
  return;
29058
30064
  }
29059
30065
  const resolved = outPath === "." ? deriveFilenameFrom(response, id) : outPath;
29060
- await fs20.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
29061
- await fs20.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
30066
+ await fs21.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
30067
+ await fs21.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
29062
30068
  process.stdout.write(`Wrote ${resolved}
29063
30069
  `);
29064
30070
  }
@@ -29105,21 +30111,21 @@ async function runSessionsTranscript(idOrFile, outPath) {
29105
30111
  return;
29106
30112
  }
29107
30113
  const resolved = outPath === "." ? defaultName : outPath;
29108
- await fs20.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
29109
- await fs20.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
30114
+ await fs21.mkdir(path16.dirname(path16.resolve(resolved)), { recursive: true });
30115
+ await fs21.writeFile(resolved, body, { encoding: "utf8", mode: 384 });
29110
30116
  process.stdout.write(`Wrote ${resolved}
29111
30117
  `);
29112
30118
  }
29113
30119
  async function readBundleFileIfExists(arg) {
29114
30120
  try {
29115
- const stat5 = await fs20.stat(arg);
30121
+ const stat5 = await fs21.stat(arg);
29116
30122
  if (!stat5.isFile()) {
29117
30123
  return null;
29118
30124
  }
29119
30125
  } catch {
29120
30126
  return null;
29121
30127
  }
29122
- const text = await fs20.readFile(arg, "utf8");
30128
+ const text = await fs21.readFile(arg, "utf8");
29123
30129
  try {
29124
30130
  return { raw: JSON.parse(text) };
29125
30131
  } catch (err) {
@@ -29148,7 +30154,7 @@ async function runSessionsImport(file, opts = {}) {
29148
30154
  if (opts.cwd !== void 0) {
29149
30155
  const resolved = path16.resolve(opts.cwd);
29150
30156
  try {
29151
- const stat5 = await fs20.stat(resolved);
30157
+ const stat5 = await fs21.stat(resolved);
29152
30158
  if (!stat5.isDirectory()) {
29153
30159
  process.stderr.write(`--cwd ${resolved} is not a directory
29154
30160
  `);
@@ -29165,7 +30171,7 @@ async function runSessionsImport(file, opts = {}) {
29165
30171
  if (file === "-") {
29166
30172
  body = await readStdin();
29167
30173
  } else {
29168
- body = await fs20.readFile(file, "utf8");
30174
+ body = await fs21.readFile(file, "utf8");
29169
30175
  }
29170
30176
  let bundle;
29171
30177
  try {
@@ -29349,7 +30355,7 @@ function splitHostPort(input, defaultPort) {
29349
30355
  // src/cli/commands/sessions-info.ts
29350
30356
  init_config();
29351
30357
  init_service_token();
29352
- import * as fs21 from "fs/promises";
30358
+ import * as fs22 from "fs/promises";
29353
30359
 
29354
30360
  // src/core/history-edits.ts
29355
30361
  init_render_update();
@@ -29393,8 +30399,8 @@ function aggregateFileEdits(history) {
29393
30399
  }
29394
30400
  }
29395
30401
  const out = [];
29396
- for (const [path21, agg] of byPath) {
29397
- out.push({ path: path21, hunks: agg.hunks, created: agg.created });
30402
+ for (const [path22, agg] of byPath) {
30403
+ out.push({ path: path22, hunks: agg.hunks, created: agg.created });
29398
30404
  }
29399
30405
  return out;
29400
30406
  }
@@ -29464,13 +30470,13 @@ function extractRawEdits(update) {
29464
30470
  if (b.type !== "diff") {
29465
30471
  continue;
29466
30472
  }
29467
- const path21 = typeof b.path === "string" ? b.path : void 0;
29468
- if (path21 === void 0) {
30473
+ const path22 = typeof b.path === "string" ? b.path : void 0;
30474
+ if (path22 === void 0) {
29469
30475
  continue;
29470
30476
  }
29471
30477
  const oldText = typeof b.oldText === "string" ? b.oldText : "";
29472
30478
  const newText = typeof b.newText === "string" ? b.newText : "";
29473
- out.push({ path: path21, oldText, newText });
30479
+ out.push({ path: path22, oldText, newText });
29474
30480
  }
29475
30481
  if (out.length > 0) {
29476
30482
  return out;
@@ -29563,16 +30569,16 @@ function openPager(opts = {}) {
29563
30569
  `);
29564
30570
  }
29565
30571
  });
29566
- const childExited = new Promise((resolve8) => {
29567
- child.once("exit", () => resolve8());
29568
- child.once("error", () => resolve8());
30572
+ const childExited = new Promise((resolve9) => {
30573
+ child.once("exit", () => resolve9());
30574
+ child.once("error", () => resolve9());
29569
30575
  });
29570
30576
  return {
29571
30577
  stream: wrapper,
29572
30578
  flush: async () => {
29573
30579
  if (childStdin.writable) {
29574
- await new Promise((resolve8) => {
29575
- wrapper.end(() => resolve8());
30580
+ await new Promise((resolve9) => {
30581
+ wrapper.end(() => resolve9());
29576
30582
  });
29577
30583
  }
29578
30584
  await childExited;
@@ -31542,7 +32548,7 @@ function maxLen5(headerCell, values) {
31542
32548
  }
31543
32549
 
31544
32550
  // src/shim/proxy.ts
31545
- import * as fs22 from "fs";
32551
+ import * as fs23 from "fs";
31546
32552
  init_config();
31547
32553
  init_remote_target();
31548
32554
  init_daemon_bootstrap();
@@ -32017,10 +33023,10 @@ function wireLog(direction, msg) {
32017
33023
  wireLogChecked = true;
32018
33024
  try {
32019
33025
  wireLogPath = paths.shimWireLogFile();
32020
- fs22.mkdirSync(paths.home(), { recursive: true });
32021
- const st = fs22.statSync(wireLogPath, { throwIfNoEntry: false });
33026
+ fs23.mkdirSync(paths.home(), { recursive: true });
33027
+ const st = fs23.statSync(wireLogPath, { throwIfNoEntry: false });
32022
33028
  if (st && st.size > WIRE_LOG_MAX_BYTES) {
32023
- fs22.renameSync(wireLogPath, `${wireLogPath}.1`);
33029
+ fs23.renameSync(wireLogPath, `${wireLogPath}.1`);
32024
33030
  }
32025
33031
  } catch {
32026
33032
  wireLogPath = null;
@@ -32036,7 +33042,7 @@ function wireLog(direction, msg) {
32036
33042
  dir: direction,
32037
33043
  msg
32038
33044
  }) + "\n";
32039
- fs22.appendFile(wireLogPath, line, () => void 0);
33045
+ fs23.appendFile(wireLogPath, line, () => void 0);
32040
33046
  } catch {
32041
33047
  }
32042
33048
  }
@@ -32421,8 +33427,8 @@ async function runCatLoop(args) {
32421
33427
  };
32422
33428
  let exitCode = 0;
32423
33429
  let resolveDone;
32424
- const done = new Promise((resolve8) => {
32425
- resolveDone = resolve8;
33430
+ const done = new Promise((resolve9) => {
33431
+ resolveDone = resolve9;
32426
33432
  });
32427
33433
  let settled = false;
32428
33434
  const settle = async (code) => {
@@ -32729,11 +33735,11 @@ async function openOrAttachSession(conn, opts, useAutoStream) {
32729
33735
  return created.sessionId;
32730
33736
  }
32731
33737
  async function openWs2(url, subprotocols) {
32732
- return new Promise((resolve8, reject) => {
33738
+ return new Promise((resolve9, reject) => {
32733
33739
  const ws = new WebSocket2(url, subprotocols);
32734
33740
  const onOpen = () => {
32735
33741
  ws.off("error", onError);
32736
- resolve8(ws);
33742
+ resolve9(ws);
32737
33743
  };
32738
33744
  const onError = (err) => {
32739
33745
  ws.off("open", onOpen);
@@ -33010,7 +34016,8 @@ async function main() {
33010
34016
  }
33011
34017
  if (sub === "export") {
33012
34018
  const out = resolveOption(flags, "out");
33013
- await runSessionsExport(positional[2], out);
34019
+ const tools = resolveOption(flags, "tools");
34020
+ await runSessionsExport(positional[2], out, tools);
33014
34021
  return;
33015
34022
  }
33016
34023
  if (sub === "transcript") {
@@ -33341,7 +34348,7 @@ function readVersion() {
33341
34348
  try {
33342
34349
  const here = dirname8(fileURLToPath2(import.meta.url));
33343
34350
  const pkg = JSON.parse(
33344
- readFileSync2(resolve7(here, "../package.json"), "utf8")
34351
+ readFileSync2(resolve8(here, "../package.json"), "utf8")
33345
34352
  );
33346
34353
  return pkg.version ?? "unknown";
33347
34354
  } catch {