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 +421 -437
- package/docs/chatgpt-subscription-auth.md +68 -0
- package/package.json +6 -2
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} ${
|
|
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
|
-
|
|
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
|
|
487
|
-
if (Array.isArray(
|
|
488
|
-
const n =
|
|
489
|
-
writeln(`${indent}${G.info} ${c3.dim(n === 0 ? "no matches" : `${n} file${n === 1 ? "" : "s"}${
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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,
|
|
743
|
-
const labelStr = formatSubagentLabel(laneId, parentLabel
|
|
744
|
-
const prefix = activeLanes.size > 1 ||
|
|
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
|
|
794
|
-
|
|
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
|
-
|
|
829
|
-
|
|
780
|
+
write(rendered.output);
|
|
781
|
+
if (endWithNewline) {
|
|
782
|
+
write(`
|
|
783
|
+
`);
|
|
784
|
+
}
|
|
830
785
|
}
|
|
831
|
-
function
|
|
786
|
+
function flushCompleteLines() {
|
|
832
787
|
let boundary = rawBuffer.indexOf(`
|
|
833
|
-
|
|
834
788
|
`);
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
797
|
+
if (!rawBuffer) {
|
|
798
|
+
return;
|
|
849
799
|
}
|
|
850
|
-
|
|
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
|
-
|
|
808
|
+
write(`${G.reply} `);
|
|
858
809
|
inText = true;
|
|
859
810
|
}
|
|
860
811
|
rawBuffer += event.delta;
|
|
861
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1739
|
-
|
|
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
|
|
1747
|
-
if (!isRecord(item) || typeof item
|
|
1681
|
+
for (const item of payload[arrayKey]) {
|
|
1682
|
+
if (!isRecord(item) || typeof item[idKey] !== "string")
|
|
1748
1683
|
continue;
|
|
1749
|
-
const modelId = normalizeModelId(item
|
|
1684
|
+
const modelId = normalizeModelId(item[idKey]);
|
|
1750
1685
|
if (!modelId)
|
|
1751
1686
|
continue;
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2901
|
-
var ExaSearchSchema =
|
|
2902
|
-
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
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
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 =
|
|
2934
|
-
urls:
|
|
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
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
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
|
|
2966
|
-
var SubagentInput =
|
|
2967
|
-
prompt:
|
|
2968
|
-
agentName:
|
|
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
|
|
3015
|
-
import { z as
|
|
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 =
|
|
3145
|
-
path:
|
|
3146
|
-
content:
|
|
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
|
|
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 {
|
|
3170
|
-
import { z as
|
|
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 =
|
|
3186
|
-
pattern:
|
|
3187
|
-
ignore:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
3157
|
+
const fullPath = resolve2(cwd, relativePath);
|
|
3211
3158
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
3212
|
-
matches.push({
|
|
3159
|
+
matches.push({
|
|
3160
|
+
path: relativePath,
|
|
3161
|
+
mtime: stat?.mtime?.getTime() ?? 0
|
|
3162
|
+
});
|
|
3213
3163
|
} catch {
|
|
3214
|
-
matches.push({ path:
|
|
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) =>
|
|
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 {
|
|
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 =
|
|
3277
|
-
pattern:
|
|
3278
|
-
include:
|
|
3279
|
-
contextLines:
|
|
3280
|
-
caseSensitive:
|
|
3281
|
-
maxResults:
|
|
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
|
|
3258
|
+
for await (const fromPath of fileGlob.scan({
|
|
3310
3259
|
cwd,
|
|
3311
3260
|
onlyFiles: true,
|
|
3312
3261
|
dot: true
|
|
3313
3262
|
})) {
|
|
3314
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
3385
|
-
|
|
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 {
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
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
|
|
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 {
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
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
|
|
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:
|
|
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 {
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
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
|
|
3598
|
-
const
|
|
3599
|
-
const
|
|
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
|
|
3658
|
-
var ShellSchema =
|
|
3659
|
-
command:
|
|
3660
|
-
timeout:
|
|
3661
|
-
env:
|
|
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}
|
|
3737
|
+
return `mc-sub-${laneId}`;
|
|
3836
3738
|
}
|
|
3837
3739
|
function makeWorktreePath(laneId) {
|
|
3838
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 :
|
|
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")}
|
|
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
|
|
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(`@${
|
|
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 :
|
|
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
|
|
4893
|
-
|
|
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
|
|
4818
|
+
const onCancel = () => {
|
|
4896
4819
|
cleanup();
|
|
4897
4820
|
abortController.abort();
|
|
4898
4821
|
};
|
|
4899
4822
|
const onData = (chunk) => {
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
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
|
-
|
|
4836
|
+
terminal.setRawMode(false);
|
|
4911
4837
|
process.stdin.pause();
|
|
4912
4838
|
};
|
|
4913
|
-
terminal.setInterruptHandler(
|
|
4914
|
-
|
|
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
|
|
4920
|
-
var
|
|
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
|
-
|
|
4929
|
+
const pasteTokens = new Map;
|
|
4944
4930
|
const imageAttachments = [];
|
|
4945
|
-
|
|
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 = (
|
|
4952
|
-
const visualCursor =
|
|
4953
|
-
|
|
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 +
|
|
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
|
-
|
|
5022
|
-
cursor += imageResult.label.length;
|
|
5028
|
+
insertText(imageResult.label);
|
|
5023
5029
|
renderPrompt();
|
|
5024
5030
|
continue;
|
|
5025
5031
|
}
|
|
5026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5189
|
-
|
|
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
|
|
5219
|
-
const imageResult = await tryExtractImageFromPaste(
|
|
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
|
-
|
|
5223
|
-
cursor += imageResult.label.length;
|
|
5205
|
+
insertText(imageResult.label);
|
|
5224
5206
|
renderPrompt();
|
|
5225
5207
|
continue;
|
|
5226
5208
|
}
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
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
|
-
|
|
5235
|
-
cursor++;
|
|
5219
|
+
insertText(raw);
|
|
5236
5220
|
renderPrompt();
|
|
5237
5221
|
}
|
|
5238
5222
|
}
|
|
5239
5223
|
} finally {
|
|
5240
5224
|
process.stdout.write(BPASTE_DISABLE);
|
|
5241
|
-
|
|
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 =
|
|
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++;
|