mini-coder 0.0.14 → 0.0.15

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/mc.js CHANGED
@@ -349,17 +349,6 @@ function renderMarkdown(text) {
349
349
  }).join(`
350
350
  `);
351
351
  }
352
- function renderChunk(text, inFence) {
353
- let fence = inFence;
354
- const output = text.split(`
355
- `).map((raw) => {
356
- const r = renderLine(raw, fence);
357
- fence = r.inFence;
358
- return r.output;
359
- }).join(`
360
- `);
361
- return { output, inFence: fence };
362
- }
363
352
 
364
353
  // src/cli/tool-render.ts
365
354
  import { homedir as homedir2 } from "os";
@@ -433,7 +422,7 @@ function toolCallLine(name, args) {
433
422
  if (name === "shell") {
434
423
  const cmd = String(a.command ?? "");
435
424
  const shortCmd = cmd.length > 72 ? `${cmd.slice(0, 69)}\u2026` : cmd;
436
- return `${G.run} ${c3.dim("$")} ${shortCmd}`;
425
+ return `${G.run} ${shortCmd}`;
437
426
  }
438
427
  if (name === "subagent") {
439
428
  const prompt = String(a.prompt ?? "");
@@ -474,19 +463,25 @@ function renderDiff(diff) {
474
463
  }
475
464
  }
476
465
  }
466
+ function formatErrorBadge(result) {
467
+ const msg = typeof result === "string" ? result : result instanceof Error ? result.message : JSON.stringify(result);
468
+ const oneLiner = msg.split(`
469
+ `)[0] ?? msg;
470
+ return `${G.err} ${c3.red(oneLiner)}`;
471
+ }
472
+ function formatShellBadge(r) {
473
+ return r.timedOut ? c3.yellow("timeout") : r.success ? c3.green(`\u2714 ${r.exitCode}`) : c3.red(`\u2716 ${r.exitCode}`);
474
+ }
477
475
  function renderToolResultInline(toolName, result, isError, indent) {
478
476
  if (isError) {
479
- const msg = typeof result === "string" ? result : result instanceof Error ? result.message : JSON.stringify(result);
480
- const oneLiner = msg.split(`
481
- `)[0] ?? msg;
482
- writeln(`${indent}${G.err} ${c3.red(oneLiner)}`);
477
+ writeln(`${indent}${formatErrorBadge(result)}`);
483
478
  return;
484
479
  }
485
480
  if (toolName === "glob") {
486
- const r = result;
487
- if (Array.isArray(r?.files)) {
488
- const n = r.files.length;
489
- writeln(`${indent}${G.info} ${c3.dim(n === 0 ? "no matches" : `${n} file${n === 1 ? "" : "s"}${r.truncated ? " (capped)" : ""}`)}`);
481
+ const res = result;
482
+ if (Array.isArray(res?.files)) {
483
+ const n = res.files.length;
484
+ writeln(`${indent}${G.info} ${c3.dim(n === 0 ? "no matches" : `${n} file${n === 1 ? "" : "s"}${res.truncated ? " (capped)" : ""}`)}`);
490
485
  return;
491
486
  }
492
487
  }
@@ -517,7 +512,7 @@ function renderToolResultInline(toolName, result, isError, indent) {
517
512
  }
518
513
  if (toolName === "shell") {
519
514
  const r = result;
520
- const badge = r.timedOut ? c3.yellow("timeout") : r.success ? c3.green(`\u2714 ${r.exitCode}`) : c3.red(`\u2716 ${r.exitCode}`);
515
+ const badge = formatShellBadge(r);
521
516
  writeln(`${indent}${badge}`);
522
517
  return;
523
518
  }
@@ -556,10 +551,7 @@ function renderToolResultInline(toolName, result, isError, indent) {
556
551
  }
557
552
  function renderToolResult(toolName, result, isError) {
558
553
  if (isError) {
559
- const msg = typeof result === "string" ? result : result instanceof Error ? result.message : JSON.stringify(result);
560
- const oneLiner = msg.split(`
561
- `)[0] ?? msg;
562
- writeln(` ${G.err} ${c3.red(oneLiner)}`);
554
+ writeln(` ${formatErrorBadge(result)}`);
563
555
  return;
564
556
  }
565
557
  if (toolName === "glob") {
@@ -635,7 +627,7 @@ function renderToolResult(toolName, result, isError) {
635
627
  }
636
628
  if (toolName === "shell") {
637
629
  const r = result;
638
- const badge = r.timedOut ? c3.yellow("timeout") : r.success ? c3.green(`\u2714 ${r.exitCode}`) : c3.red(`\u2716 ${r.exitCode}`);
630
+ const badge = formatShellBadge(r);
639
631
  writeln(` ${badge}`);
640
632
  const outLines = r.stdout ? r.stdout.split(`
641
633
  `) : [];
@@ -727,21 +719,16 @@ function normalizeParentLaneLabel(parentLabel) {
727
719
  const lanePath = (inner.split("\xB7")[0] ?? inner).trim();
728
720
  return lanePath || inner;
729
721
  }
730
- function shortWorktreeBranch(branch) {
731
- const match = branch.match(/^(mc-sub-\d+)-\d+$/);
732
- return match?.[1] ?? branch;
733
- }
734
- function formatSubagentLabel(laneId, parentLabel, worktreeBranch) {
722
+ function formatSubagentLabel(laneId, parentLabel) {
735
723
  const parent = parentLabel ? normalizeParentLaneLabel(parentLabel) : "";
736
724
  const numStr = parent ? `${parent}.${laneId}` : `${laneId}`;
737
- const branchHint = worktreeBranch ? `\xB7${shortWorktreeBranch(worktreeBranch)}` : "";
738
- return c3.dim(c3.cyan(`[${numStr}${branchHint}]`));
725
+ return c3.dim(c3.cyan(`[${numStr}]`));
739
726
  }
740
727
  var laneBuffers = new Map;
741
728
  function renderSubagentEvent(event, opts) {
742
- const { laneId, parentLabel, worktreeBranch, activeLanes } = opts;
743
- const labelStr = formatSubagentLabel(laneId, parentLabel, worktreeBranch);
744
- const prefix = activeLanes.size > 1 || worktreeBranch ? `${labelStr} ` : "";
729
+ const { laneId, parentLabel, hasWorktree, activeLanes } = opts;
730
+ const labelStr = formatSubagentLabel(laneId, parentLabel);
731
+ const prefix = activeLanes.size > 1 || hasWorktree ? `${labelStr} ` : "";
745
732
  if (event.type === "text-delta") {
746
733
  const buf = (laneBuffers.get(laneId) ?? "") + event.delta;
747
734
  const lines = buf.split(`
@@ -783,82 +770,46 @@ async function renderTurn(events, spinner) {
783
770
  let inText = false;
784
771
  let rawBuffer = "";
785
772
  let inFence = false;
786
- let printQueue = "";
787
- let printPos = 0;
788
- let tickerHandle = null;
789
773
  let inputTokens = 0;
790
774
  let outputTokens = 0;
791
775
  let contextTokens = 0;
792
776
  let newMessages = [];
793
- function enqueuePrint(ansi) {
794
- printQueue += ansi;
795
- if (tickerHandle === null) {
796
- scheduleTick();
797
- }
798
- }
799
- function tick() {
800
- tickerHandle = null;
801
- if (printPos < printQueue.length) {
802
- process.stdout.write(printQueue[printPos]);
803
- printPos++;
804
- scheduleTick();
805
- } else {
806
- printQueue = "";
807
- printPos = 0;
808
- }
809
- }
810
- function scheduleTick() {
811
- tickerHandle = setTimeout(tick, 8);
812
- }
813
- function drainQueue() {
814
- if (tickerHandle !== null) {
815
- clearTimeout(tickerHandle);
816
- tickerHandle = null;
817
- }
818
- if (printPos < printQueue.length) {
819
- process.stdout.write(printQueue.slice(printPos));
820
- }
821
- printQueue = "";
822
- printPos = 0;
823
- }
824
- function renderAndTrim(end) {
825
- const chunk = rawBuffer.slice(0, end);
826
- const rendered = renderChunk(chunk, inFence);
777
+ function renderAndWrite(raw, endWithNewline) {
778
+ const rendered = renderLine(raw, inFence);
827
779
  inFence = rendered.inFence;
828
- enqueuePrint(rendered.output);
829
- rawBuffer = rawBuffer.slice(end);
780
+ write(rendered.output);
781
+ if (endWithNewline) {
782
+ write(`
783
+ `);
784
+ }
830
785
  }
831
- function flushChunks() {
786
+ function flushCompleteLines() {
832
787
  let boundary = rawBuffer.indexOf(`
833
-
834
788
  `);
835
- if (boundary !== -1) {
836
- renderAndTrim(boundary + 2);
837
- flushChunks();
838
- return;
839
- }
840
- boundary = rawBuffer.lastIndexOf(`
789
+ while (boundary !== -1) {
790
+ renderAndWrite(rawBuffer.slice(0, boundary), true);
791
+ rawBuffer = rawBuffer.slice(boundary + 1);
792
+ boundary = rawBuffer.indexOf(`
841
793
  `);
842
- if (boundary !== -1) {
843
- renderAndTrim(boundary + 1);
844
794
  }
845
795
  }
846
796
  function flushAll() {
847
- if (rawBuffer) {
848
- renderAndTrim(rawBuffer.length);
797
+ if (!rawBuffer) {
798
+ return;
849
799
  }
850
- drainQueue();
800
+ renderAndWrite(rawBuffer, false);
801
+ rawBuffer = "";
851
802
  }
852
803
  for await (const event of events) {
853
804
  switch (event.type) {
854
805
  case "text-delta": {
855
806
  if (!inText) {
856
807
  spinner.stop();
857
- process.stdout.write(`${G.reply} `);
808
+ write(`${G.reply} `);
858
809
  inText = true;
859
810
  }
860
811
  rawBuffer += event.delta;
861
- flushChunks();
812
+ flushCompleteLines();
862
813
  break;
863
814
  }
864
815
  case "tool-call-start": {
@@ -957,7 +908,7 @@ function renderStatusBar(opts) {
957
908
  left.push(c5.magenta("\u21BB ralph"));
958
909
  const right = [];
959
910
  if (opts.inputTokens > 0 || opts.outputTokens > 0) {
960
- right.push(c5.dim(`\u2191${fmtTokens(opts.inputTokens)} \u2193${fmtTokens(opts.outputTokens)}`));
911
+ right.push(c5.dim(`\u2191 ${fmtTokens(opts.inputTokens)} \u2193 ${fmtTokens(opts.outputTokens)}`));
961
912
  }
962
913
  if (opts.contextTokens > 0) {
963
914
  const ctxRaw = fmtTokens(opts.contextTokens);
@@ -995,7 +946,7 @@ function renderError(err, context = "render") {
995
946
 
996
947
  // src/cli/output.ts
997
948
  var HOME2 = homedir3();
998
- var PACKAGE_VERSION = "0.0.14";
949
+ var PACKAGE_VERSION = "0.0.15";
999
950
  function tildePath(p) {
1000
951
  return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
1001
952
  }
@@ -1041,7 +992,7 @@ function renderBanner(model, cwd) {
1041
992
  writeln();
1042
993
  writeln(` ${c7.cyan("mc")} ${c7.dim(`mini-coder \xB7 v${PACKAGE_VERSION}`)}`);
1043
994
  writeln(` ${c7.dim(model)} ${c7.dim("\xB7")} ${c7.dim(cwd)}`);
1044
- writeln(` ${c7.dim("/help for commands \xB7 ctrl+d to exit")}`);
995
+ writeln(` ${c7.dim("/help for commands \xB7 esc cancel \xB7 ctrl+c/ctrl+d exit")}`);
1045
996
  writeln();
1046
997
  }
1047
998
  function renderInfo(msg) {
@@ -1713,51 +1664,44 @@ async function fetchZenModels() {
1713
1664
  if (!key)
1714
1665
  return null;
1715
1666
  const payload = await fetchJson(`${ZEN_BASE}/models`, { headers: { Authorization: `Bearer ${key}` } }, 8000);
1716
- if (!isRecord(payload))
1717
- return null;
1718
- const data = payload.data;
1719
- if (!Array.isArray(data))
1720
- return null;
1721
- const out = [];
1722
- for (const item of data) {
1723
- if (!isRecord(item) || typeof item.id !== "string")
1724
- continue;
1725
- const modelId = normalizeModelId(item.id);
1726
- if (!modelId)
1727
- continue;
1667
+ return processModelsList(payload, "data", "id", (item, modelId) => {
1728
1668
  const contextWindow = typeof item.context_window === "number" && Number.isFinite(item.context_window) ? Math.max(0, Math.trunc(item.context_window)) : null;
1729
- out.push({
1669
+ return {
1730
1670
  providerModelId: modelId,
1731
1671
  displayName: item.id,
1732
1672
  contextWindow,
1733
1673
  free: item.id.endsWith("-free") || item.id === "gpt-5-nano" || item.id === "big-pickle"
1734
- });
1735
- }
1736
- return out;
1674
+ };
1675
+ });
1737
1676
  }
1738
- async function fetchOpenAIModels() {
1739
- const key = process.env.OPENAI_API_KEY;
1740
- if (!key)
1741
- return null;
1742
- const payload = await fetchJson(`${OPENAI_BASE}/v1/models`, { headers: { Authorization: `Bearer ${key}` } }, 6000);
1743
- if (!isRecord(payload) || !Array.isArray(payload.data))
1677
+ function processModelsList(payload, arrayKey, idKey, mapper) {
1678
+ if (!isRecord(payload) || !Array.isArray(payload[arrayKey]))
1744
1679
  return null;
1745
1680
  const out = [];
1746
- for (const item of payload.data) {
1747
- if (!isRecord(item) || typeof item.id !== "string")
1681
+ for (const item of payload[arrayKey]) {
1682
+ if (!isRecord(item) || typeof item[idKey] !== "string")
1748
1683
  continue;
1749
- const modelId = normalizeModelId(item.id);
1684
+ const modelId = normalizeModelId(item[idKey]);
1750
1685
  if (!modelId)
1751
1686
  continue;
1752
- out.push({
1753
- providerModelId: modelId,
1754
- displayName: item.id,
1755
- contextWindow: null,
1756
- free: false
1757
- });
1687
+ const mapped = mapper(item, modelId);
1688
+ if (mapped)
1689
+ out.push(mapped);
1758
1690
  }
1759
1691
  return out;
1760
1692
  }
1693
+ async function fetchOpenAIModels() {
1694
+ const key = process.env.OPENAI_API_KEY;
1695
+ if (!key)
1696
+ return null;
1697
+ const payload = await fetchJson(`${OPENAI_BASE}/v1/models`, { headers: { Authorization: `Bearer ${key}` } }, 6000);
1698
+ return processModelsList(payload, "data", "id", (item, modelId) => ({
1699
+ providerModelId: modelId,
1700
+ displayName: item.id,
1701
+ contextWindow: null,
1702
+ free: false
1703
+ }));
1704
+ }
1761
1705
  async function fetchAnthropicModels() {
1762
1706
  const key = process.env.ANTHROPIC_API_KEY;
1763
1707
  if (!key)
@@ -1768,75 +1712,48 @@ async function fetchAnthropicModels() {
1768
1712
  "anthropic-version": "2023-06-01"
1769
1713
  }
1770
1714
  }, 6000);
1771
- if (!isRecord(payload) || !Array.isArray(payload.data))
1772
- return null;
1773
- const out = [];
1774
- for (const item of payload.data) {
1775
- if (!isRecord(item) || typeof item.id !== "string")
1776
- continue;
1777
- const modelId = normalizeModelId(item.id);
1778
- if (!modelId)
1779
- continue;
1715
+ return processModelsList(payload, "data", "id", (item, modelId) => {
1780
1716
  const displayName = typeof item.display_name === "string" && item.display_name.trim().length > 0 ? item.display_name : item.id;
1781
- out.push({
1717
+ return {
1782
1718
  providerModelId: modelId,
1783
1719
  displayName,
1784
1720
  contextWindow: null,
1785
1721
  free: false
1786
- });
1787
- }
1788
- return out;
1722
+ };
1723
+ });
1789
1724
  }
1790
1725
  async function fetchGoogleModels() {
1791
1726
  const key = process.env.GOOGLE_API_KEY ?? process.env.GEMINI_API_KEY;
1792
1727
  if (!key)
1793
1728
  return null;
1794
1729
  const payload = await fetchJson(`${GOOGLE_BASE}/models?key=${encodeURIComponent(key)}`, {}, 6000);
1795
- if (!isRecord(payload) || !Array.isArray(payload.models))
1796
- return null;
1797
- const out = [];
1798
- for (const item of payload.models) {
1799
- if (!isRecord(item) || typeof item.name !== "string")
1800
- continue;
1801
- const modelId = normalizeModelId(item.name);
1802
- if (!modelId)
1803
- continue;
1730
+ return processModelsList(payload, "models", "name", (item, modelId) => {
1804
1731
  const displayName = typeof item.displayName === "string" && item.displayName.trim().length > 0 ? item.displayName : modelId;
1805
1732
  const contextWindow = typeof item.inputTokenLimit === "number" && Number.isFinite(item.inputTokenLimit) ? Math.max(0, Math.trunc(item.inputTokenLimit)) : null;
1806
- out.push({
1733
+ return {
1807
1734
  providerModelId: modelId,
1808
1735
  displayName,
1809
1736
  contextWindow,
1810
1737
  free: false
1811
- });
1812
- }
1813
- return out;
1738
+ };
1739
+ });
1814
1740
  }
1815
1741
  async function fetchOllamaModels() {
1816
1742
  const base = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
1817
1743
  const payload = await fetchJson(`${base}/api/tags`, {}, 3000);
1818
- if (!isRecord(payload) || !Array.isArray(payload.models))
1819
- return null;
1820
- const out = [];
1821
- for (const item of payload.models) {
1822
- if (!isRecord(item) || typeof item.name !== "string")
1823
- continue;
1824
- const modelId = normalizeModelId(item.name);
1825
- if (!modelId)
1826
- continue;
1744
+ return processModelsList(payload, "models", "name", (item, modelId) => {
1827
1745
  const details = item.details;
1828
1746
  let sizeSuffix = "";
1829
1747
  if (isRecord(details) && typeof details.parameter_size === "string") {
1830
1748
  sizeSuffix = ` (${details.parameter_size})`;
1831
1749
  }
1832
- out.push({
1750
+ return {
1833
1751
  providerModelId: modelId,
1834
1752
  displayName: `${item.name}${sizeSuffix}`,
1835
1753
  contextWindow: null,
1836
1754
  free: false
1837
- });
1838
- }
1839
- return out;
1755
+ };
1756
+ });
1840
1757
  }
1841
1758
  async function fetchProviderCandidates(provider) {
1842
1759
  switch (provider) {
@@ -2464,31 +2381,22 @@ function deleteAllSnapshots(sessionId) {
2464
2381
  }
2465
2382
  // src/agent/subagent-runner.ts
2466
2383
  import { tmpdir as tmpdir2 } from "os";
2467
- import { join as join15 } from "path";
2384
+ import { join as join10 } from "path";
2468
2385
 
2469
2386
  // src/llm-api/turn.ts
2470
2387
  import { dynamicTool, jsonSchema, stepCountIs, streamText } from "ai";
2471
- import { z } from "zod";
2472
2388
  var MAX_STEPS = 50;
2473
2389
  function isZodSchema(s) {
2474
2390
  return s !== null && typeof s === "object" && "_def" in s;
2475
2391
  }
2476
- function toCoreTool(def, claimWarning) {
2392
+ function toCoreTool(def) {
2477
2393
  const schema = isZodSchema(def.schema) ? def.schema : jsonSchema(def.schema);
2478
2394
  return dynamicTool({
2479
2395
  description: def.description,
2480
2396
  inputSchema: schema,
2481
2397
  execute: async (input) => {
2482
2398
  try {
2483
- const result = await def.execute(input);
2484
- if (claimWarning()) {
2485
- const warning = `
2486
-
2487
- <system-message>You have reached the maximum number of tool calls. ` + "No more tools will be available after this result. " + "Respond with a status update and list what still needs to be done.</system-message>";
2488
- const str = typeof result === "string" ? result : JSON.stringify(result);
2489
- return str + warning;
2490
- }
2491
- return result;
2399
+ return await def.execute(input);
2492
2400
  } catch (err) {
2493
2401
  throw err instanceof Error ? err : new Error(String(err));
2494
2402
  }
@@ -2510,16 +2418,9 @@ async function* runTurn(options) {
2510
2418
  thinkingEffort
2511
2419
  } = options;
2512
2420
  let stepCount = 0;
2513
- let warningClaimed = false;
2514
- function claimWarning() {
2515
- if (stepCount !== MAX_STEPS - 2 || warningClaimed)
2516
- return false;
2517
- warningClaimed = true;
2518
- return true;
2519
- }
2520
2421
  const toolSet = {};
2521
2422
  for (const def of tools) {
2522
- toolSet[def.name] = toCoreTool(def, claimWarning);
2423
+ toolSet[def.name] = toCoreTool(def);
2523
2424
  }
2524
2425
  let inputTokens = 0;
2525
2426
  let outputTokens = 0;
@@ -2554,7 +2455,6 @@ async function* runTurn(options) {
2554
2455
  outputTokens += step.usage?.outputTokens ?? 0;
2555
2456
  contextTokens = step.usage?.inputTokens ?? contextTokens;
2556
2457
  stepCount++;
2557
- warningClaimed = false;
2558
2458
  },
2559
2459
  prepareStep: ({ stepNumber }) => {
2560
2460
  if (stepNumber >= MAX_STEPS - 1) {
@@ -2610,7 +2510,7 @@ async function* runTurn(options) {
2610
2510
  };
2611
2511
  break;
2612
2512
  }
2613
- case "tool-error": {
2513
+ case "tool-error":
2614
2514
  yield {
2615
2515
  type: "tool-result",
2616
2516
  toolCallId: String(c9.toolCallId ?? ""),
@@ -2619,15 +2519,13 @@ async function* runTurn(options) {
2619
2519
  isError: true
2620
2520
  };
2621
2521
  break;
2622
- }
2623
2522
  case "error": {
2624
2523
  const err = c9.error;
2625
2524
  throw err instanceof Error ? err : new Error(String(err));
2626
2525
  }
2627
2526
  }
2628
2527
  }
2629
- const finalResponse = await result.response;
2630
- const newMessages = finalResponse?.messages ?? [];
2528
+ const newMessages = (await result.response)?.messages ?? [];
2631
2529
  logApiEvent("turn complete", {
2632
2530
  newMessagesCount: newMessages.length,
2633
2531
  inputTokens,
@@ -2897,75 +2795,62 @@ ${contextFile}`;
2897
2795
  }
2898
2796
 
2899
2797
  // src/tools/exa.ts
2900
- import { z as z2 } from "zod";
2901
- var ExaSearchSchema = z2.object({
2902
- query: z2.string().describe("The search query")
2798
+ import { z } from "zod";
2799
+ var ExaSearchSchema = z.object({
2800
+ query: z.string().describe("The search query")
2903
2801
  });
2802
+ async function fetchExa(endpoint, body) {
2803
+ const apiKey = process.env.EXA_API_KEY;
2804
+ if (!apiKey) {
2805
+ throw new Error("EXA_API_KEY is not set.");
2806
+ }
2807
+ const response = await fetch(`https://api.exa.ai/${endpoint}`, {
2808
+ method: "POST",
2809
+ headers: {
2810
+ "Content-Type": "application/json",
2811
+ "x-api-key": apiKey
2812
+ },
2813
+ body: JSON.stringify(body)
2814
+ });
2815
+ if (!response.ok) {
2816
+ const errorBody = await response.text();
2817
+ throw new Error(`Exa API error: ${response.status} ${response.statusText} - ${errorBody}`);
2818
+ }
2819
+ return await response.json();
2820
+ }
2904
2821
  var webSearchTool = {
2905
2822
  name: "webSearch",
2906
2823
  description: "Search the web for a query using Exa.",
2907
2824
  schema: ExaSearchSchema,
2908
2825
  execute: async (input) => {
2909
- const apiKey = process.env.EXA_API_KEY;
2910
- if (!apiKey) {
2911
- throw new Error("EXA_API_KEY is not set.");
2912
- }
2913
- const response = await fetch("https://api.exa.ai/search", {
2914
- method: "POST",
2915
- headers: {
2916
- "Content-Type": "application/json",
2917
- "x-api-key": apiKey
2918
- },
2919
- body: JSON.stringify({
2920
- query: input.query,
2921
- type: "auto",
2922
- numResults: 10,
2923
- contents: { text: { maxCharacters: 4000 } }
2924
- })
2826
+ return fetchExa("search", {
2827
+ query: input.query,
2828
+ type: "auto",
2829
+ numResults: 10,
2830
+ contents: { text: { maxCharacters: 4000 } }
2925
2831
  });
2926
- if (!response.ok) {
2927
- const errorBody = await response.text();
2928
- throw new Error(`Exa API error: ${response.status} ${response.statusText} - ${errorBody}`);
2929
- }
2930
- return await response.json();
2931
2832
  }
2932
2833
  };
2933
- var ExaContentSchema = z2.object({
2934
- urls: z2.array(z2.string()).max(3).describe("Array of URLs to retrieve content for (max 3)")
2834
+ var ExaContentSchema = z.object({
2835
+ urls: z.array(z.string()).max(3).describe("Array of URLs to retrieve content for (max 3)")
2935
2836
  });
2936
2837
  var webContentTool = {
2937
2838
  name: "webContent",
2938
2839
  description: "Get the full content of specific URLs using Exa.",
2939
2840
  schema: ExaContentSchema,
2940
2841
  execute: async (input) => {
2941
- const apiKey = process.env.EXA_API_KEY;
2942
- if (!apiKey) {
2943
- throw new Error("EXA_API_KEY is not set.");
2944
- }
2945
- const response = await fetch("https://api.exa.ai/contents", {
2946
- method: "POST",
2947
- headers: {
2948
- "Content-Type": "application/json",
2949
- "x-api-key": apiKey
2950
- },
2951
- body: JSON.stringify({
2952
- urls: input.urls,
2953
- text: { maxCharacters: 1e4 }
2954
- })
2842
+ return fetchExa("contents", {
2843
+ urls: input.urls,
2844
+ text: { maxCharacters: 1e4 }
2955
2845
  });
2956
- if (!response.ok) {
2957
- const errorBody = await response.text();
2958
- throw new Error(`Exa API error: ${response.status} ${response.statusText} - ${errorBody}`);
2959
- }
2960
- return await response.json();
2961
2846
  }
2962
2847
  };
2963
2848
 
2964
2849
  // src/tools/subagent.ts
2965
- import { z as z3 } from "zod";
2966
- var SubagentInput = z3.object({
2967
- prompt: z3.string().describe("The task or question to give the subagent"),
2968
- agentName: z3.string().optional().describe("Name of a custom agent to use (from .agents/agents/). Omit to use a generic subagent.")
2850
+ import { z as z2 } from "zod";
2851
+ var SubagentInput = z2.object({
2852
+ prompt: z2.string().describe("The task or question to give the subagent"),
2853
+ agentName: z2.string().optional().describe("Name of a custom agent to use (from .agents/agents/). Omit to use a generic subagent.")
2969
2854
  });
2970
2855
  function formatConflictFiles(conflictFiles) {
2971
2856
  if (conflictFiles.length === 0)
@@ -3011,8 +2896,8 @@ When the user's message contains @<agent-name>, delegate to that agent by settin
3011
2896
 
3012
2897
  // src/tools/create.ts
3013
2898
  import { existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
3014
- import { dirname as dirname2, join as join7, relative } from "path";
3015
- import { z as z4 } from "zod";
2899
+ import { dirname as dirname2 } from "path";
2900
+ import { z as z3 } from "zod";
3016
2901
 
3017
2902
  // src/tools/diff.ts
3018
2903
  function generateDiff(filePath, before, after) {
@@ -3140,19 +3025,50 @@ ${lines.join(`
3140
3025
  `);
3141
3026
  }
3142
3027
 
3028
+ // src/tools/shared.ts
3029
+ import { join as join7, relative } from "path";
3030
+ function resolvePath(cwdInput, pathInput) {
3031
+ const cwd = cwdInput ?? process.cwd();
3032
+ const filePath = pathInput.startsWith("/") ? pathInput : join7(cwd, pathInput);
3033
+ const relPath = relative(cwd, filePath);
3034
+ return { cwd, filePath, relPath };
3035
+ }
3036
+ async function resolveExistingFile(cwdInput, pathInput) {
3037
+ const { cwd, filePath, relPath } = resolvePath(cwdInput, pathInput);
3038
+ const file = Bun.file(filePath);
3039
+ if (!await file.exists()) {
3040
+ throw new Error(`File not found: "${relPath}". To create a new file use the \`create\` tool.`);
3041
+ }
3042
+ return { file, filePath, relPath, cwd };
3043
+ }
3044
+ function parseAnchor(value, name = "anchor") {
3045
+ const normalized = value.trim().endsWith("|") ? value.trim().slice(0, -1) : value;
3046
+ const match = /^\s*(\d+):([0-9a-fA-F]{2})\s*$/.exec(normalized);
3047
+ if (!match) {
3048
+ throw new Error(`Invalid ${name}. Expected format: "line:hh" (e.g. "11:a3").`);
3049
+ }
3050
+ const line = Number(match[1]);
3051
+ if (!Number.isInteger(line) || line < 1) {
3052
+ throw new Error(`Invalid ${name} line number.`);
3053
+ }
3054
+ const hash = match[2];
3055
+ if (!hash) {
3056
+ throw new Error(`Invalid ${name}. Expected format: "line:hh" (e.g. "11:a3").`);
3057
+ }
3058
+ return { line, hash: hash.toLowerCase() };
3059
+ }
3060
+
3143
3061
  // src/tools/create.ts
3144
- var CreateSchema = z4.object({
3145
- path: z4.string().describe("File path to write (absolute or relative to cwd)"),
3146
- content: z4.string().describe("Full content to write to the file")
3062
+ var CreateSchema = z3.object({
3063
+ path: z3.string().describe("File path to write (absolute or relative to cwd)"),
3064
+ content: z3.string().describe("Full content to write to the file")
3147
3065
  });
3148
3066
  var createTool = {
3149
3067
  name: "create",
3150
3068
  description: "Create a new file or fully overwrite an existing file with the given content. " + "Use this only for new files. " + "For targeted line edits on existing files, **use `replace` or `insert` instead**.",
3151
3069
  schema: CreateSchema,
3152
3070
  execute: async (input) => {
3153
- const cwd = input.cwd ?? process.cwd();
3154
- const filePath = input.path.startsWith("/") ? input.path : join7(cwd, input.path);
3155
- const relPath = relative(cwd, filePath);
3071
+ const { filePath, relPath } = resolvePath(input.cwd, input.path);
3156
3072
  const dir = dirname2(filePath);
3157
3073
  if (!existsSync5(dir))
3158
3074
  mkdirSync5(dir, { recursive: true });
@@ -3166,8 +3082,8 @@ var createTool = {
3166
3082
  };
3167
3083
 
3168
3084
  // src/tools/glob.ts
3169
- import { join as join9, relative as relative2 } from "path";
3170
- import { z as z5 } from "zod";
3085
+ import { resolve as resolve2 } from "path";
3086
+ import { z as z4 } from "zod";
3171
3087
 
3172
3088
  // src/tools/ignore.ts
3173
3089
  import { join as join8 } from "path";
@@ -3181,10 +3097,40 @@ async function loadGitignore(cwd) {
3181
3097
  }
3182
3098
  }
3183
3099
 
3100
+ // src/tools/scan-path.ts
3101
+ import { relative as relative2, resolve, sep } from "path";
3102
+ var LEADING_PARENT_SEGMENTS = /^(?:\.\.\/)+/;
3103
+ function getScannedPathInfo(cwd, scanPath) {
3104
+ const cwdAbsolute = resolve(cwd);
3105
+ const absolute = resolve(cwdAbsolute, scanPath);
3106
+ const relativePath = relative2(cwdAbsolute, absolute).replaceAll("\\", "/");
3107
+ const inCwd = absolute === cwdAbsolute || absolute.startsWith(cwdAbsolute === sep ? sep : `${cwdAbsolute}${sep}`);
3108
+ const ignoreTargets = getIgnoreTargets(relativePath, inCwd);
3109
+ return {
3110
+ absolute,
3111
+ relativePath,
3112
+ ignoreTargets,
3113
+ inCwd
3114
+ };
3115
+ }
3116
+ function getIgnoreTargets(path, inCwd) {
3117
+ if (inCwd)
3118
+ return [path];
3119
+ const normalized = path.replace(LEADING_PARENT_SEGMENTS, "");
3120
+ const segments = normalized.split("/").filter(Boolean);
3121
+ if (segments.length === 0)
3122
+ return [normalized];
3123
+ const targets = new Set([normalized]);
3124
+ for (let i = 1;i < segments.length; i++) {
3125
+ targets.add(segments.slice(i).join("/"));
3126
+ }
3127
+ return [...targets];
3128
+ }
3129
+
3184
3130
  // src/tools/glob.ts
3185
- var GlobSchema = z5.object({
3186
- pattern: z5.string().describe("Glob pattern to match files against, e.g. '**/*.ts'"),
3187
- ignore: z5.array(z5.string()).optional().describe("Glob patterns to exclude")
3131
+ var GlobSchema = z4.object({
3132
+ pattern: z4.string().describe("Glob pattern to match files against, e.g. '**/*.ts'"),
3133
+ ignore: z4.array(z4.string()).optional().describe("Glob patterns to exclude")
3188
3134
  });
3189
3135
  var MAX_RESULTS = 500;
3190
3136
  var globTool = {
@@ -3200,18 +3146,22 @@ var globTool = {
3200
3146
  const glob = new Bun.Glob(input.pattern);
3201
3147
  const matches = [];
3202
3148
  for await (const file of glob.scan({ cwd, onlyFiles: true, dot: true })) {
3203
- if (ig?.ignores(file))
3149
+ const { relativePath, ignoreTargets } = getScannedPathInfo(cwd, file);
3150
+ const firstSegment = relativePath.split("/")[0] ?? "";
3151
+ if (ignoreTargets.some((path) => ig?.ignores(path)))
3204
3152
  continue;
3205
- const firstSegment = file.split("/")[0] ?? "";
3206
- const ignored = ignoreGlobs.some((g) => g.match(file) || g.match(firstSegment));
3153
+ const ignored = ignoreTargets.some((candidate) => ignoreGlobs.some((g) => g.match(candidate) || g.match(firstSegment)));
3207
3154
  if (ignored)
3208
3155
  continue;
3209
3156
  try {
3210
- const fullPath = join9(cwd, file);
3157
+ const fullPath = resolve2(cwd, relativePath);
3211
3158
  const stat = await Bun.file(fullPath).stat?.() ?? null;
3212
- matches.push({ path: file, mtime: stat?.mtime?.getTime() ?? 0 });
3159
+ matches.push({
3160
+ path: relativePath,
3161
+ mtime: stat?.mtime?.getTime() ?? 0
3162
+ });
3213
3163
  } catch {
3214
- matches.push({ path: file, mtime: 0 });
3164
+ matches.push({ path: relativePath, mtime: 0 });
3215
3165
  }
3216
3166
  if (matches.length >= MAX_RESULTS + 1)
3217
3167
  break;
@@ -3220,14 +3170,13 @@ var globTool = {
3220
3170
  if (truncated)
3221
3171
  matches.pop();
3222
3172
  matches.sort((a, b) => b.mtime - a.mtime);
3223
- const files = matches.map((m) => relative2(cwd, join9(cwd, m.path)));
3173
+ const files = matches.map((m) => m.path);
3224
3174
  return { files, count: files.length, truncated };
3225
3175
  }
3226
3176
  };
3227
3177
 
3228
3178
  // src/tools/grep.ts
3229
- import { join as join10 } from "path";
3230
- import { z as z6 } from "zod";
3179
+ import { z as z5 } from "zod";
3231
3180
 
3232
3181
  // src/tools/hashline.ts
3233
3182
  var FNV_OFFSET_BASIS = 2166136261;
@@ -3273,17 +3222,17 @@ function findLineByHash(lines, hash, hintLine) {
3273
3222
  }
3274
3223
 
3275
3224
  // src/tools/grep.ts
3276
- var GrepSchema = z6.object({
3277
- pattern: z6.string().describe("Regular expression to search for"),
3278
- include: z6.string().optional().describe("Glob pattern to filter files, e.g. '*.ts' or '*.{ts,tsx}'"),
3279
- contextLines: z6.number().int().min(0).max(10).optional().default(2).describe("Lines of context to include around each match"),
3280
- caseSensitive: z6.boolean().optional().default(true),
3281
- maxResults: z6.number().int().min(1).max(200).optional().default(50)
3225
+ var GrepSchema = z5.object({
3226
+ pattern: z5.string().describe("Regular expression to search for"),
3227
+ include: z5.string().optional().describe("Glob pattern to filter files, e.g. '*.ts' or '*.{ts,tsx}'"),
3228
+ contextLines: z5.number().int().min(0).max(10).optional().default(2).describe("Lines of context to include around each match"),
3229
+ caseSensitive: z5.boolean().optional().default(true),
3230
+ maxResults: z5.number().int().min(1).max(200).optional().default(50)
3282
3231
  });
3283
3232
  var DEFAULT_IGNORE = [
3284
- "node_modules",
3285
- ".git",
3286
- "dist",
3233
+ "node_modules/**",
3234
+ ".git/**",
3235
+ "dist/**",
3287
3236
  "*.db",
3288
3237
  "*.db-shm",
3289
3238
  "*.db-wal",
@@ -3306,18 +3255,19 @@ var grepTool = {
3306
3255
  let truncated = false;
3307
3256
  const ig = await loadGitignore(cwd);
3308
3257
  outer:
3309
- for await (const relPath of fileGlob.scan({
3258
+ for await (const fromPath of fileGlob.scan({
3310
3259
  cwd,
3311
3260
  onlyFiles: true,
3312
3261
  dot: true
3313
3262
  })) {
3314
- if (ig?.ignores(relPath))
3263
+ const { absolute, relativePath, ignoreTargets } = getScannedPathInfo(cwd, fromPath);
3264
+ const firstSegment = relativePath.split("/")[0] ?? "";
3265
+ if (ignoreTargets.some((path) => ig?.ignores(path)))
3315
3266
  continue;
3316
- const firstSegment = relPath.split("/")[0] ?? "";
3317
- if (ignoreGlob.some((g) => g.match(relPath) || g.match(firstSegment))) {
3267
+ if (ignoreTargets.some((candidate) => ignoreGlob.some((g) => g.match(candidate) || g.match(firstSegment)))) {
3318
3268
  continue;
3319
3269
  }
3320
- const fullPath = join10(cwd, relPath);
3270
+ const fullPath = absolute;
3321
3271
  let text;
3322
3272
  try {
3323
3273
  text = await Bun.file(fullPath).text();
@@ -3344,7 +3294,7 @@ var grepTool = {
3344
3294
  });
3345
3295
  }
3346
3296
  allMatches.push({
3347
- file: relPath,
3297
+ file: relativePath,
3348
3298
  line: i + 1,
3349
3299
  column: match.index + 1,
3350
3300
  text: formatHashLine(i + 1, line),
@@ -3369,7 +3319,7 @@ var grepTool = {
3369
3319
  // src/tools/hooks.ts
3370
3320
  import { constants, accessSync } from "fs";
3371
3321
  import { homedir as homedir7 } from "os";
3372
- import { join as join11 } from "path";
3322
+ import { join as join9 } from "path";
3373
3323
  function isExecutable(filePath) {
3374
3324
  try {
3375
3325
  accessSync(filePath, constants.X_OK);
@@ -3381,8 +3331,8 @@ function isExecutable(filePath) {
3381
3331
  function findHook(toolName, cwd) {
3382
3332
  const scriptName = `post-${toolName}`;
3383
3333
  const candidates = [
3384
- join11(cwd, ".agents", "hooks", scriptName),
3385
- join11(homedir7(), ".agents", "hooks", scriptName)
3334
+ join9(cwd, ".agents", "hooks", scriptName),
3335
+ join9(homedir7(), ".agents", "hooks", scriptName)
3386
3336
  ];
3387
3337
  for (const p of candidates) {
3388
3338
  if (isExecutable(p))
@@ -3471,13 +3421,12 @@ function hookEnvForRead(input, cwd) {
3471
3421
  }
3472
3422
 
3473
3423
  // src/tools/insert.ts
3474
- import { join as join12, relative as relative3 } from "path";
3475
- import { z as z7 } from "zod";
3476
- var InsertSchema = z7.object({
3477
- path: z7.string().describe("File path to edit (absolute or relative to cwd)"),
3478
- anchor: z7.string().describe('Anchor line from a prior read/grep, e.g. "11:a3"'),
3479
- position: z7.enum(["before", "after"]).describe('Insert the content "before" or "after" the anchor line'),
3480
- content: z7.string().describe("Text to insert")
3424
+ import { z as z6 } from "zod";
3425
+ var InsertSchema = z6.object({
3426
+ path: z6.string().describe("File path to edit (absolute or relative to cwd)"),
3427
+ anchor: z6.string().describe('Anchor line from a prior read/grep, e.g. "11:a3"'),
3428
+ position: z6.enum(["before", "after"]).describe('Insert the content "before" or "after" the anchor line'),
3429
+ content: z6.string().describe("Text to insert")
3481
3430
  });
3482
3431
  var HASH_NOT_FOUND_ERROR = "Hash not found. Re-read the file to get current anchors.";
3483
3432
  var insertTool = {
@@ -3485,13 +3434,7 @@ var insertTool = {
3485
3434
  description: "Insert new lines before or after an anchor line in an existing file. " + "The anchor line itself is not modified. " + 'Anchors come from the `read` or `grep` tools (format: "line:hash", e.g. "11:a3"). ' + "To replace or delete lines use `replace`. To create a file use `create`.",
3486
3435
  schema: InsertSchema,
3487
3436
  execute: async (input) => {
3488
- const cwd = input.cwd ?? process.cwd();
3489
- const filePath = input.path.startsWith("/") ? input.path : join12(cwd, input.path);
3490
- const relPath = relative3(cwd, filePath);
3491
- const file = Bun.file(filePath);
3492
- if (!await file.exists()) {
3493
- throw new Error(`File not found: "${relPath}". To create a new file use the \`create\` tool.`);
3494
- }
3437
+ const { file, filePath, relPath } = await resolveExistingFile(input.cwd, input.path);
3495
3438
  const parsed = parseAnchor(input.anchor);
3496
3439
  const original = await file.text();
3497
3440
  const lines = original.split(`
@@ -3514,30 +3457,13 @@ var insertTool = {
3514
3457
  return { path: relPath, diff };
3515
3458
  }
3516
3459
  };
3517
- function parseAnchor(value) {
3518
- const normalized = value.trim().endsWith("|") ? value.trim().slice(0, -1) : value;
3519
- const match = /^\s*(\d+):([0-9a-fA-F]{2})\s*$/.exec(normalized);
3520
- if (!match) {
3521
- throw new Error(`Invalid anchor. Expected format: "line:hh" (e.g. "11:a3").`);
3522
- }
3523
- const line = Number(match[1]);
3524
- if (!Number.isInteger(line) || line < 1) {
3525
- throw new Error("Invalid anchor line number.");
3526
- }
3527
- const hash = match[2];
3528
- if (!hash) {
3529
- throw new Error(`Invalid anchor. Expected format: "line:hh" (e.g. "11:a3").`);
3530
- }
3531
- return { line, hash: hash.toLowerCase() };
3532
- }
3533
3460
 
3534
3461
  // src/tools/read.ts
3535
- import { join as join13, relative as relative4 } from "path";
3536
- import { z as z8 } from "zod";
3537
- var ReadSchema = z8.object({
3538
- path: z8.string().describe("File path to read (absolute or relative to cwd)"),
3539
- line: z8.number().int().min(1).optional().describe("1-indexed starting line (default: 1)"),
3540
- count: z8.number().int().min(1).max(500).optional().describe("Lines to read (default: 500, max: 500)")
3462
+ import { z as z7 } from "zod";
3463
+ var ReadSchema = z7.object({
3464
+ path: z7.string().describe("File path to read (absolute or relative to cwd)"),
3465
+ line: z7.number().int().min(1).optional().describe("1-indexed starting line (default: 1)"),
3466
+ count: z7.number().int().min(1).max(500).optional().describe("Lines to read (default: 500, max: 500)")
3541
3467
  });
3542
3468
  var MAX_COUNT = 500;
3543
3469
  var MAX_BYTES = 1e6;
@@ -3546,8 +3472,7 @@ var readTool = {
3546
3472
  description: "Read a file's contents. `line` sets the starting line (1-indexed, default 1). " + "`count` sets how many lines to read (default 500, max 500). " + "Check `truncated` and `totalLines` in the result to detect when more content exists; " + "paginate by incrementing `line`.",
3547
3473
  schema: ReadSchema,
3548
3474
  execute: async (input) => {
3549
- const cwd = input.cwd ?? process.cwd();
3550
- const filePath = input.path.startsWith("/") ? input.path : join13(cwd, input.path);
3475
+ const { filePath, relPath } = resolvePath(input.cwd, input.path);
3551
3476
  const file = Bun.file(filePath);
3552
3477
  const exists = await file.exists();
3553
3478
  if (!exists) {
@@ -3570,7 +3495,7 @@ var readTool = {
3570
3495
  const content = selectedLines.map((line, i) => formatHashLine(clampedStart + i, line)).join(`
3571
3496
  `);
3572
3497
  return {
3573
- path: relative4(cwd, filePath),
3498
+ path: relPath,
3574
3499
  content,
3575
3500
  totalLines,
3576
3501
  line: clampedStart,
@@ -3580,13 +3505,12 @@ var readTool = {
3580
3505
  };
3581
3506
 
3582
3507
  // src/tools/replace.ts
3583
- import { join as join14, relative as relative5 } from "path";
3584
- import { z as z9 } from "zod";
3585
- var ReplaceSchema = z9.object({
3586
- path: z9.string().describe("File path to edit (absolute or relative to cwd)"),
3587
- startAnchor: z9.string().describe('Start anchor from a prior read/grep, e.g. "11:a3"'),
3588
- endAnchor: z9.string().optional().describe('End anchor (inclusive), e.g. "33:0e". Omit to target only the startAnchor line.'),
3589
- newContent: z9.string().optional().describe("Replacement text. Omit or pass empty string to delete the range.")
3508
+ import { z as z8 } from "zod";
3509
+ var ReplaceSchema = z8.object({
3510
+ path: z8.string().describe("File path to edit (absolute or relative to cwd)"),
3511
+ startAnchor: z8.string().describe('Start anchor from a prior read/grep, e.g. "11:a3"'),
3512
+ endAnchor: z8.string().optional().describe('End anchor (inclusive), e.g. "33:0e". Omit to target only the startAnchor line.'),
3513
+ newContent: z8.string().optional().describe("Replacement text. Omit or pass empty string to delete the range.")
3590
3514
  });
3591
3515
  var HASH_NOT_FOUND_ERROR2 = "Hash not found. Re-read the file to get current anchors.";
3592
3516
  var replaceTool = {
@@ -3594,15 +3518,9 @@ var replaceTool = {
3594
3518
  description: "Replace or delete a range of lines in an existing file using hashline anchors. " + 'Anchors come from the `read` or `grep` tools (format: "line:hash", e.g. "11:a3"). ' + "Provide startAnchor alone to target a single line, or add endAnchor for a range. " + "Set newContent to the replacement text, or omit it to delete the range. " + "To create a file use `create`. To insert without replacing any lines use `insert`.",
3595
3519
  schema: ReplaceSchema,
3596
3520
  execute: async (input) => {
3597
- const cwd = input.cwd ?? process.cwd();
3598
- const filePath = input.path.startsWith("/") ? input.path : join14(cwd, input.path);
3599
- const relPath = relative5(cwd, filePath);
3600
- const file = Bun.file(filePath);
3601
- if (!await file.exists()) {
3602
- throw new Error(`File not found: "${relPath}". To create a new file use the \`create\` tool.`);
3603
- }
3604
- const start = parseAnchor2(input.startAnchor, "startAnchor");
3605
- const end = input.endAnchor ? parseAnchor2(input.endAnchor, "endAnchor") : null;
3521
+ const { file, filePath, relPath } = await resolveExistingFile(input.cwd, input.path);
3522
+ const start = parseAnchor(input.startAnchor, "startAnchor");
3523
+ const end = input.endAnchor ? parseAnchor(input.endAnchor, "endAnchor") : null;
3606
3524
  if (end && end.line < start.line) {
3607
3525
  throw new Error("endAnchor line number must be >= startAnchor line number.");
3608
3526
  }
@@ -3636,29 +3554,13 @@ var replaceTool = {
3636
3554
  return { path: relPath, diff, deleted: replacement.length === 0 };
3637
3555
  }
3638
3556
  };
3639
- function parseAnchor2(value, name) {
3640
- const normalized = value.trim().endsWith("|") ? value.trim().slice(0, -1) : value;
3641
- const match = /^\s*(\d+):([0-9a-fA-F]{2})\s*$/.exec(normalized);
3642
- if (!match) {
3643
- throw new Error(`Invalid ${name}. Expected format: "line:hh" (e.g. "11:a3").`);
3644
- }
3645
- const line = Number(match[1]);
3646
- if (!Number.isInteger(line) || line < 1) {
3647
- throw new Error(`Invalid ${name} line number.`);
3648
- }
3649
- const hash = match[2];
3650
- if (!hash) {
3651
- throw new Error(`Invalid ${name}. Expected format: "line:hh" (e.g. "11:a3").`);
3652
- }
3653
- return { line, hash: hash.toLowerCase() };
3654
- }
3655
3557
 
3656
3558
  // src/tools/shell.ts
3657
- import { z as z10 } from "zod";
3658
- var ShellSchema = z10.object({
3659
- command: z10.string().describe("Shell command to execute"),
3660
- timeout: z10.number().int().min(1000).max(300000).optional().default(30000).describe("Timeout in milliseconds (default: 30s, max: 5min)"),
3661
- env: z10.record(z10.string(), z10.string()).optional().describe("Additional environment variables to set")
3559
+ import { z as z9 } from "zod";
3560
+ var ShellSchema = z9.object({
3561
+ command: z9.string().describe("Shell command to execute"),
3562
+ timeout: z9.number().int().min(1000).max(300000).optional().default(30000).describe("Timeout in milliseconds (default: 30s, max: 5min)"),
3563
+ env: z9.record(z9.string(), z9.string()).optional().describe("Additional environment variables to set")
3662
3564
  });
3663
3565
  var MAX_OUTPUT_BYTES = 1e4;
3664
3566
  var shellTool = {
@@ -3832,14 +3734,12 @@ function buildReadOnlyToolSet(opts) {
3832
3734
 
3833
3735
  // src/agent/subagent-runner.ts
3834
3736
  function makeWorktreeBranch(laneId) {
3835
- return `mc-sub-${laneId}-${Date.now()}`;
3737
+ return `mc-sub-${laneId}`;
3836
3738
  }
3837
3739
  function makeWorktreePath(laneId) {
3838
- const suffix = crypto.randomUUID().replace(/-/g, "").slice(0, 10);
3839
- return join15(tmpdir2(), `mc-wt-${laneId}-${suffix}`);
3740
+ return join10(tmpdir2(), `mc-wt-${laneId}`);
3840
3741
  }
3841
3742
  function createSubagentRunner(cwd, reporter, getCurrentModel, getThinkingEffort) {
3842
- let nextLaneId = 1;
3843
3743
  const activeLanes = new Set;
3844
3744
  const worktreesEnabledPromise = isGitRepo(cwd);
3845
3745
  let mergeLock = Promise.resolve();
@@ -3853,7 +3753,7 @@ function createSubagentRunner(cwd, reporter, getCurrentModel, getThinkingEffort)
3853
3753
  return task;
3854
3754
  };
3855
3755
  const runSubagent = async (prompt, depth = 0, agentName, modelOverride, parentLabel) => {
3856
- const laneId = nextLaneId++;
3756
+ const laneId = crypto.randomUUID().split("-")[0];
3857
3757
  activeLanes.add(laneId);
3858
3758
  let subagentCwd = cwd;
3859
3759
  let worktreeBranch;
@@ -3907,7 +3807,7 @@ function createSubagentRunner(cwd, reporter, getCurrentModel, getThinkingEffort)
3907
3807
  reporter.renderSubagentEvent(event, {
3908
3808
  laneId,
3909
3809
  ...parentLabel ? { parentLabel } : {},
3910
- ...worktreeBranch ? { worktreeBranch } : {},
3810
+ hasWorktree: !!worktreeBranch,
3911
3811
  activeLanes
3912
3812
  });
3913
3813
  reporter.startSpinner("thinking");
@@ -3976,7 +3876,7 @@ function createSubagentRunner(cwd, reporter, getCurrentModel, getThinkingEffort)
3976
3876
 
3977
3877
  // src/tools/snapshot.ts
3978
3878
  import { readFileSync as readFileSync3, unlinkSync as unlinkSync2 } from "fs";
3979
- import { join as join16 } from "path";
3879
+ import { join as join11 } from "path";
3980
3880
  async function gitBytes(args, cwd) {
3981
3881
  try {
3982
3882
  const proc = Bun.spawn(["git", ...args], {
@@ -4067,7 +3967,7 @@ async function takeSnapshot(cwd, sessionId, turnIndex) {
4067
3967
  return false;
4068
3968
  const files = [];
4069
3969
  for (const entry of entries) {
4070
- const absPath = join16(repoRoot, entry.path);
3970
+ const absPath = join11(repoRoot, entry.path);
4071
3971
  if (!entry.existsOnDisk) {
4072
3972
  const { bytes, code } = await gitBytes(["show", `HEAD:${entry.path}`], repoRoot);
4073
3973
  if (code === 0) {
@@ -4116,7 +4016,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
4116
4016
  const root = repoRoot ?? cwd;
4117
4017
  let anyFailed = false;
4118
4018
  for (const file of files) {
4119
- const absPath = join16(root, file.path);
4019
+ const absPath = join11(root, file.path);
4120
4020
  if (!file.existed) {
4121
4021
  try {
4122
4022
  if (await Bun.file(absPath).exists()) {
@@ -4183,7 +4083,7 @@ async function undoLastTurn(ctx) {
4183
4083
  }
4184
4084
 
4185
4085
  // src/agent/agent-helpers.ts
4186
- import { join as join17 } from "path";
4086
+ import { join as join12 } from "path";
4187
4087
  import * as c9 from "yoctocolors";
4188
4088
 
4189
4089
  // src/cli/image-types.ts
@@ -4282,7 +4182,7 @@ ${skill.content}
4282
4182
  result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
4283
4183
  continue;
4284
4184
  }
4285
- const filePath = ref.startsWith("/") ? ref : join17(cwd, ref);
4185
+ const filePath = ref.startsWith("/") ? ref : join12(cwd, ref);
4286
4186
  if (isImageFilename(ref)) {
4287
4187
  const attachment = await loadImageFile(filePath);
4288
4188
  if (attachment) {
@@ -4731,7 +4631,7 @@ function handleHelp(ctx, custom) {
4731
4631
  writeln(` ${c10.green("@file".padEnd(26))} ${c10.dim("inject file contents into prompt (Tab to complete)")}`);
4732
4632
  writeln(` ${c10.green("!cmd".padEnd(26))} ${c10.dim("run shell command, output added as context")}`);
4733
4633
  writeln();
4734
- writeln(` ${c10.dim("ctrl+c")} cancel ${c10.dim("\xB7")} ${c10.dim("ctrl+d")} exit ${c10.dim("\xB7")} ${c10.dim("ctrl+r")} history search ${c10.dim("\xB7")} ${c10.dim("\u2191\u2193")} history`);
4634
+ writeln(` ${c10.dim("esc")} cancel response ${c10.dim("\xB7")} ${c10.dim("ctrl+c")} exit ${c10.dim("\xB7")} ${c10.dim("ctrl+d")} exit ${c10.dim("\xB7")} ${c10.dim("ctrl+r")} history search ${c10.dim("\xB7")} ${c10.dim("\u2191\u2193")} history`);
4735
4635
  writeln();
4736
4636
  }
4737
4637
  async function handleCommand(command, args, ctx) {
@@ -4778,7 +4678,7 @@ async function handleCommand(command, args, ctx) {
4778
4678
  }
4779
4679
 
4780
4680
  // src/cli/input.ts
4781
- import { join as join18, relative as relative6 } from "path";
4681
+ import { join as join13, relative as relative3 } from "path";
4782
4682
  import * as c11 from "yoctocolors";
4783
4683
  var ESC = "\x1B";
4784
4684
  var CSI = `${ESC}[`;
@@ -4807,6 +4707,8 @@ var CTRL_K = "\v";
4807
4707
  var CTRL_L = "\f";
4808
4708
  var CTRL_R = "\x12";
4809
4709
  var TAB = "\t";
4710
+ var ESC_BYTE = 27;
4711
+ var CTRL_C_BYTE = 3;
4810
4712
  async function getAtCompletions(prefix, cwd) {
4811
4713
  const query = prefix.startsWith("@") ? prefix.slice(1) : prefix;
4812
4714
  const results = [];
@@ -4830,7 +4732,7 @@ async function getAtCompletions(prefix, cwd) {
4830
4732
  for await (const file of glob.scan({ cwd, onlyFiles: true })) {
4831
4733
  if (file.includes("node_modules") || file.includes(".git"))
4832
4734
  continue;
4833
- results.push(`@${relative6(cwd, join18(cwd, file))}`);
4735
+ results.push(`@${relative3(cwd, join13(cwd, file))}`);
4834
4736
  if (results.length >= MAX)
4835
4737
  break;
4836
4738
  }
@@ -4852,7 +4754,7 @@ async function tryExtractImageFromPaste(pasted, cwd) {
4852
4754
  }
4853
4755
  }
4854
4756
  if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
4855
- const filePath = trimmed.startsWith("/") ? trimmed : join18(cwd, trimmed);
4757
+ const filePath = trimmed.startsWith("/") ? trimmed : join13(cwd, trimmed);
4856
4758
  const attachment = await loadImageFile(filePath);
4857
4759
  if (attachment) {
4858
4760
  const name = filePath.split("/").pop() ?? trimmed;
@@ -4889,35 +4791,67 @@ async function readKey(reader) {
4889
4791
  return "";
4890
4792
  return new TextDecoder().decode(value);
4891
4793
  }
4892
- function watchForInterrupt(abortController) {
4893
- if (!process.stdin.isTTY)
4794
+ function getTurnControlAction(chunk) {
4795
+ for (const byte of chunk) {
4796
+ if (byte === ESC_BYTE)
4797
+ return "cancel";
4798
+ if (byte === CTRL_C_BYTE)
4799
+ return "quit";
4800
+ }
4801
+ return null;
4802
+ }
4803
+ function exitOnCtrlC(opts) {
4804
+ if (opts?.printNewline)
4805
+ process.stdout.write(`
4806
+ `);
4807
+ if (opts?.disableBracketedPaste)
4808
+ process.stdout.write(BPASTE_DISABLE);
4809
+ terminal.setInterruptHandler(null);
4810
+ terminal.setRawMode(false);
4811
+ process.stdin.pause();
4812
+ terminal.restoreTerminal();
4813
+ process.exit(130);
4814
+ }
4815
+ function watchForCancel(abortController) {
4816
+ if (!terminal.isTTY)
4894
4817
  return () => {};
4895
- const onInterrupt = () => {
4818
+ const onCancel = () => {
4896
4819
  cleanup();
4897
4820
  abortController.abort();
4898
4821
  };
4899
4822
  const onData = (chunk) => {
4900
- for (const byte of chunk) {
4901
- if (byte === 3) {
4902
- onInterrupt();
4903
- return;
4904
- }
4823
+ const action = getTurnControlAction(chunk);
4824
+ if (action === "cancel") {
4825
+ onCancel();
4826
+ return;
4827
+ }
4828
+ if (action === "quit") {
4829
+ cleanup();
4830
+ exitOnCtrlC({ printNewline: true });
4905
4831
  }
4906
4832
  };
4907
4833
  const cleanup = () => {
4908
4834
  process.stdin.removeListener("data", onData);
4909
4835
  terminal.setInterruptHandler(null);
4910
- process.stdin.setRawMode(false);
4836
+ terminal.setRawMode(false);
4911
4837
  process.stdin.pause();
4912
4838
  };
4913
- terminal.setInterruptHandler(onInterrupt);
4914
- process.stdin.setRawMode(true);
4839
+ terminal.setInterruptHandler(onCancel);
4840
+ terminal.setRawMode(true);
4915
4841
  process.stdin.resume();
4916
4842
  process.stdin.on("data", onData);
4917
4843
  return cleanup;
4918
4844
  }
4919
- var PASTE_SENTINEL = "\x00PASTE\x00";
4920
- var PASTE_SENTINEL_LEN = PASTE_SENTINEL.length;
4845
+ var PASTE_TOKEN_START = 57344;
4846
+ var PASTE_TOKEN_END = 63743;
4847
+ function createPasteToken(buf, pasteTokens) {
4848
+ for (let code = PASTE_TOKEN_START;code <= PASTE_TOKEN_END; code++) {
4849
+ const token = String.fromCharCode(code);
4850
+ if (!buf.includes(token) && !pasteTokens.has(token))
4851
+ return token;
4852
+ }
4853
+ throw new Error("Too many pasted chunks in a single prompt");
4854
+ }
4921
4855
  function pasteLabel(text) {
4922
4856
  const lines = text.split(`
4923
4857
  `);
@@ -4927,6 +4861,58 @@ function pasteLabel(text) {
4927
4861
  const more = extra > 0 ? ` +${extra} more line${extra === 1 ? "" : "s"}` : "";
4928
4862
  return `[pasted: "${preview}"${more}]`;
4929
4863
  }
4864
+ function processInputBuffer(buf, pasteTokens, replacer) {
4865
+ let out = "";
4866
+ for (let i = 0;i < buf.length; i++) {
4867
+ const ch = buf[i] ?? "";
4868
+ out += replacer(ch, pasteTokens.get(ch));
4869
+ }
4870
+ return out;
4871
+ }
4872
+ function renderInputBuffer(buf, pasteTokens) {
4873
+ return processInputBuffer(buf, pasteTokens, (ch, pasted) => pasted ? pasteLabel(pasted) : ch);
4874
+ }
4875
+ function expandInputBuffer(buf, pasteTokens) {
4876
+ return processInputBuffer(buf, pasteTokens, (ch, pasted) => pasted ?? ch);
4877
+ }
4878
+ function pruneInputPasteTokens(pasteTokens, ...buffers) {
4879
+ const referenced = buffers.join("");
4880
+ const next = new Map;
4881
+ for (const [token, text] of pasteTokens) {
4882
+ if (referenced.includes(token))
4883
+ next.set(token, text);
4884
+ }
4885
+ return next;
4886
+ }
4887
+ function getVisualCursor(buf, cursor, pasteTokens) {
4888
+ let visual = 0;
4889
+ for (let i = 0;i < Math.min(cursor, buf.length); i++) {
4890
+ const ch = buf[i] ?? "";
4891
+ const pasted = pasteTokens.get(ch);
4892
+ visual += pasted ? pasteLabel(pasted).length : 1;
4893
+ }
4894
+ return visual;
4895
+ }
4896
+ function buildPromptDisplay(text, cursor, maxLen) {
4897
+ const clampedCursor = Math.max(0, Math.min(cursor, text.length));
4898
+ if (maxLen <= 0)
4899
+ return { display: "", cursor: 0 };
4900
+ if (text.length <= maxLen)
4901
+ return { display: text, cursor: clampedCursor };
4902
+ let start = Math.max(0, clampedCursor - maxLen);
4903
+ const end = Math.min(text.length, start + maxLen);
4904
+ if (end - start < maxLen)
4905
+ start = Math.max(0, end - maxLen);
4906
+ let display = text.slice(start, end);
4907
+ if (start > 0 && display.length > 0)
4908
+ display = `\u2026${display.slice(1)}`;
4909
+ if (end < text.length && display.length > 0)
4910
+ display = `${display.slice(0, -1)}\u2026`;
4911
+ return {
4912
+ display,
4913
+ cursor: Math.min(clampedCursor - start, display.length)
4914
+ };
4915
+ }
4930
4916
  var PROMPT = c11.green("\u25B6 ");
4931
4917
  var PROMPT_PLAN = c11.yellow("\u2B22 ");
4932
4918
  var PROMPT_RALPH = c11.magenta("\u21BB ");
@@ -4940,24 +4926,44 @@ async function readline(opts) {
4940
4926
  let savedInput = "";
4941
4927
  let searchMode = false;
4942
4928
  let searchQuery = "";
4943
- let pasteBuffer = null;
4929
+ const pasteTokens = new Map;
4944
4930
  const imageAttachments = [];
4945
- process.stdin.setRawMode(true);
4931
+ function prunePasteTokens() {
4932
+ const next = pruneInputPasteTokens(pasteTokens, buf, savedInput);
4933
+ pasteTokens.clear();
4934
+ for (const [token, text] of next)
4935
+ pasteTokens.set(token, text);
4936
+ }
4937
+ function insertText(text) {
4938
+ buf = buf.slice(0, cursor) + text + buf.slice(cursor);
4939
+ cursor += text.length;
4940
+ }
4941
+ function deleteWordBackward() {
4942
+ const end = cursor;
4943
+ while (cursor > 0 && buf[cursor - 1] === " ")
4944
+ cursor--;
4945
+ while (cursor > 0 && buf[cursor - 1] !== " ")
4946
+ cursor--;
4947
+ buf = buf.slice(0, cursor) + buf.slice(end);
4948
+ prunePasteTokens();
4949
+ renderPrompt();
4950
+ }
4951
+ function insertPasteToken(text) {
4952
+ const token = createPasteToken(buf, pasteTokens);
4953
+ pasteTokens.set(token, text);
4954
+ insertText(token);
4955
+ }
4956
+ terminal.setRawMode(true);
4946
4957
  process.stdin.resume();
4947
4958
  process.stdout.write(BPASTE_ENABLE);
4948
4959
  const reader = getStdinReader();
4949
4960
  function renderPrompt() {
4950
4961
  const cols = process.stdout.columns ?? 80;
4951
- const visualBuf = (pasteBuffer ? buf.replace(PASTE_SENTINEL, c11.dim(pasteLabel(pasteBuffer))) : buf).replace(/\[image: [^\]]+\]/g, (m) => c11.dim(c11.cyan(m)));
4952
- const visualCursor = pasteBuffer ? (() => {
4953
- const sentinelPos = buf.indexOf(PASTE_SENTINEL);
4954
- if (sentinelPos === -1 || cursor <= sentinelPos)
4955
- return cursor;
4956
- return cursor - PASTE_SENTINEL_LEN + pasteLabel(pasteBuffer).length;
4957
- })() : cursor;
4958
- const display = visualBuf.length > cols - PROMPT_RAW_LEN - 2 ? `\u2026${visualBuf.slice(-(cols - PROMPT_RAW_LEN - 3))}` : visualBuf;
4962
+ const visualBuf = renderInputBuffer(buf, pasteTokens);
4963
+ const visualCursor = getVisualCursor(buf, cursor, pasteTokens);
4964
+ const { display, cursor: displayCursor } = buildPromptDisplay(visualBuf, visualCursor, Math.max(1, cols - PROMPT_RAW_LEN - 2));
4959
4965
  const prompt = opts.planMode ? PROMPT_PLAN : opts.ralphMode ? PROMPT_RALPH : PROMPT;
4960
- process.stdout.write(`${CLEAR_LINE}${prompt}${display}${CSI}${PROMPT_RAW_LEN + visualCursor + 1}G`);
4966
+ process.stdout.write(`${CLEAR_LINE}${prompt}${display}${CSI}${PROMPT_RAW_LEN + displayCursor + 1}G`);
4961
4967
  }
4962
4968
  function renderSearchPrompt() {
4963
4969
  process.stdout.write(`${CLEAR_LINE}${c11.cyan("search:")} ${searchQuery}\u2588`);
@@ -4969,6 +4975,7 @@ async function readline(opts) {
4969
4975
  buf = savedInput;
4970
4976
  }
4971
4977
  cursor = buf.length;
4978
+ prunePasteTokens();
4972
4979
  renderPrompt();
4973
4980
  }
4974
4981
  renderPrompt();
@@ -5018,14 +5025,11 @@ async function readline(opts) {
5018
5025
  const imageResult = await tryExtractImageFromPaste(pasted, cwd);
5019
5026
  if (imageResult) {
5020
5027
  imageAttachments.push(imageResult.attachment);
5021
- buf = buf.slice(0, cursor) + imageResult.label + buf.slice(cursor);
5022
- cursor += imageResult.label.length;
5028
+ insertText(imageResult.label);
5023
5029
  renderPrompt();
5024
5030
  continue;
5025
5031
  }
5026
- pasteBuffer = pasted;
5027
- buf = buf.slice(0, cursor) + PASTE_SENTINEL + buf.slice(cursor);
5028
- cursor += PASTE_SENTINEL_LEN;
5032
+ insertPasteToken(pasted);
5029
5033
  renderPrompt();
5030
5034
  continue;
5031
5035
  }
@@ -5077,15 +5081,7 @@ async function readline(opts) {
5077
5081
  continue;
5078
5082
  }
5079
5083
  if (raw === `${ESC}${BACKSPACE}`) {
5080
- const end = cursor;
5081
- while (cursor > 0 && buf[cursor - 1] === " ")
5082
- cursor--;
5083
- while (cursor > 0 && buf[cursor - 1] !== " ")
5084
- cursor--;
5085
- buf = buf.slice(0, cursor) + buf.slice(end);
5086
- if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
5087
- pasteBuffer = null;
5088
- renderPrompt();
5084
+ deleteWordBackward();
5089
5085
  continue;
5090
5086
  }
5091
5087
  if (raw === ESC) {
@@ -5096,9 +5092,7 @@ async function readline(opts) {
5096
5092
  continue;
5097
5093
  }
5098
5094
  if (raw === CTRL_C) {
5099
- process.stdout.write(`
5100
- `);
5101
- return { type: "interrupt" };
5095
+ exitOnCtrlC({ printNewline: true, disableBracketedPaste: true });
5102
5096
  }
5103
5097
  if (raw === CTRL_D) {
5104
5098
  process.stdout.write(`
@@ -5116,29 +5110,19 @@ async function readline(opts) {
5116
5110
  continue;
5117
5111
  }
5118
5112
  if (raw === CTRL_W) {
5119
- const end = cursor;
5120
- while (cursor > 0 && buf[cursor - 1] === " ")
5121
- cursor--;
5122
- while (cursor > 0 && buf[cursor - 1] !== " ")
5123
- cursor--;
5124
- buf = buf.slice(0, cursor) + buf.slice(end);
5125
- if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
5126
- pasteBuffer = null;
5127
- renderPrompt();
5113
+ deleteWordBackward();
5128
5114
  continue;
5129
5115
  }
5130
5116
  if (raw === CTRL_U) {
5131
5117
  buf = buf.slice(cursor);
5132
5118
  cursor = 0;
5133
- if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
5134
- pasteBuffer = null;
5119
+ prunePasteTokens();
5135
5120
  renderPrompt();
5136
5121
  continue;
5137
5122
  }
5138
5123
  if (raw === CTRL_K) {
5139
5124
  buf = buf.slice(0, cursor);
5140
- if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
5141
- pasteBuffer = null;
5125
+ prunePasteTokens();
5142
5126
  renderPrompt();
5143
5127
  continue;
5144
5128
  }
@@ -5157,8 +5141,7 @@ async function readline(opts) {
5157
5141
  if (cursor > 0) {
5158
5142
  buf = buf.slice(0, cursor - 1) + buf.slice(cursor);
5159
5143
  cursor--;
5160
- if (pasteBuffer && !buf.includes(PASTE_SENTINEL))
5161
- pasteBuffer = null;
5144
+ prunePasteTokens();
5162
5145
  renderPrompt();
5163
5146
  }
5164
5147
  continue;
@@ -5185,8 +5168,8 @@ async function readline(opts) {
5185
5168
  continue;
5186
5169
  }
5187
5170
  if (raw === ENTER || raw === NEWLINE) {
5188
- const expanded = pasteBuffer ? buf.replace(PASTE_SENTINEL, pasteBuffer) : buf;
5189
- pasteBuffer = null;
5171
+ const expanded = expandInputBuffer(buf, pasteTokens);
5172
+ pasteTokens.clear();
5190
5173
  const text = expanded.trim();
5191
5174
  process.stdout.write(`
5192
5175
  `);
@@ -5215,30 +5198,31 @@ async function readline(opts) {
5215
5198
  };
5216
5199
  }
5217
5200
  if (raw.length > 1) {
5218
- const pasted = raw.replace(/\r?\n$/, "");
5219
- const imageResult = await tryExtractImageFromPaste(pasted, cwd);
5201
+ const chunk = raw.replace(/\r?\n$/, "");
5202
+ const imageResult = await tryExtractImageFromPaste(chunk, cwd);
5220
5203
  if (imageResult) {
5221
5204
  imageAttachments.push(imageResult.attachment);
5222
- buf = buf.slice(0, cursor) + imageResult.label + buf.slice(cursor);
5223
- cursor += imageResult.label.length;
5205
+ insertText(imageResult.label);
5224
5206
  renderPrompt();
5225
5207
  continue;
5226
5208
  }
5227
- pasteBuffer = pasted;
5228
- buf = buf.slice(0, cursor) + PASTE_SENTINEL + buf.slice(cursor);
5229
- cursor += PASTE_SENTINEL_LEN;
5209
+ if (chunk.includes(`
5210
+ `) || chunk.includes("\r")) {
5211
+ insertPasteToken(chunk);
5212
+ } else {
5213
+ insertText(chunk);
5214
+ }
5230
5215
  renderPrompt();
5231
5216
  continue;
5232
5217
  }
5233
5218
  if (raw >= " " || raw === "\t") {
5234
- buf = buf.slice(0, cursor) + raw + buf.slice(cursor);
5235
- cursor++;
5219
+ insertText(raw);
5236
5220
  renderPrompt();
5237
5221
  }
5238
5222
  }
5239
5223
  } finally {
5240
5224
  process.stdout.write(BPASTE_DISABLE);
5241
- process.stdin.setRawMode(false);
5225
+ terminal.setRawMode(false);
5242
5226
  process.stdin.pause();
5243
5227
  }
5244
5228
  }
@@ -5422,7 +5406,7 @@ class SessionRunner {
5422
5406
  abortController.signal.addEventListener("abort", () => {
5423
5407
  wasAborted = true;
5424
5408
  });
5425
- const stopWatcher = watchForInterrupt(abortController);
5409
+ const stopWatcher = watchForCancel(abortController);
5426
5410
  const { text: resolvedText, images: refImages } = await resolveFileRefs(text, this.cwd);
5427
5411
  const allImages = [...pastedImages, ...refImages];
5428
5412
  const thisTurn = this.turnIndex++;