@botconnector/bccli 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-3PHBAI64.js → acp-UYC6APR5.js} +30 -6
- package/dist/acp.d.ts +1 -0
- package/dist/agent.d.ts +5 -1
- package/dist/args-NRTZWWEV.js +13 -0
- package/dist/args.d.ts +2 -0
- package/dist/checkpoints.d.ts +1 -0
- package/dist/{chunk-BKGFA2PT.js → chunk-266SY3MC.js} +1 -1
- package/dist/{chunk-MFHCQQEN.js → chunk-3I5BW3ZT.js} +3 -2
- package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
- package/dist/{chunk-WNACAUDK.js → chunk-5YJ3ZQVR.js} +66 -9
- package/dist/{chunk-24JN75FZ.js → chunk-6M55UPES.js} +2 -2
- package/dist/{chunk-XV7ZVL4J.js → chunk-6UO5GX55.js} +26 -12
- package/dist/{chunk-R5RE46CW.js → chunk-C64KM37J.js} +36 -6
- package/dist/{chunk-GN4W3T6E.js → chunk-H7XJDKJY.js} +4 -4
- package/dist/{chunk-JHPGOAXU.js → chunk-HQW4BROS.js} +4 -5
- package/dist/{chunk-75BECX4F.js → chunk-IYR5PYA3.js} +4 -4
- package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
- package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
- package/dist/{chunk-WI22M2ID.js → chunk-NQOGVP3I.js} +2 -2
- package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
- package/dist/{chunk-PZRKU4UM.js → chunk-R72OV7RF.js} +421 -108
- package/dist/{chunk-FLQPE4S5.js → chunk-RX4UQX43.js} +4 -1
- package/dist/{chunk-BC7UXFE4.js → chunk-SVD4LHJC.js} +64 -3
- package/dist/{chunk-BXQQCUAC.js → chunk-TIMSYZIR.js} +7 -7
- package/dist/{chunk-MHIYLXJC.js → chunk-WFZKJY3R.js} +8 -4
- package/dist/{chunk-BGXNR74O.js → chunk-ZEPTUKNW.js} +119 -31
- package/dist/cli.js +60 -43
- package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
- package/dist/{config-DIDLDONU.js → config-ENDRMOTP.js} +3 -3
- package/dist/{config-IU76CPK2.js → config-LAINORU5.js} +2 -2
- package/dist/config.d.ts +2 -0
- package/dist/{ghBotCli-E3VPHNZY.js → ghBotCli-TSLRORJJ.js} +21 -20
- package/dist/{integrationCli-Q2YPDTPG.js → integrationCli-HXCSEWH2.js} +2 -2
- package/dist/{login-FP3BB44A.js → login-RYESFZG6.js} +4 -4
- package/dist/lsp.d.ts +3 -0
- package/dist/mcp/manager.d.ts +18 -0
- package/dist/{mcpCli-SNBDUWJN.js → mcpCli-SETTCL3K.js} +6 -6
- package/dist/{modelsCli-CZ2HNGKR.js → modelsCli-OP7CDZC7.js} +6 -6
- package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
- package/dist/{pluginsCli-LPIUUDWB.js → pluginsCli-ZQNKWZHJ.js} +2 -2
- package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
- package/dist/prCli.d.ts +13 -0
- package/dist/print.d.ts +2 -0
- package/dist/provider.d.ts +5 -0
- package/dist/providerCli-5OZI2GQQ.js +15 -0
- package/dist/sdk.d.ts +5 -1
- package/dist/sdk.js +733 -176
- package/dist/{serve-X2ZV4S5U.js → serve-4J7HA4JO.js} +17 -17
- package/dist/{serve-KWOECK5W.js → serve-K5AH4MGO.js} +3 -3
- package/dist/session-YQXCCKXI.js +12 -0
- package/dist/session.d.ts +3 -0
- package/dist/setup-S4AJT6XE.js +24 -0
- package/dist/slash/info.d.ts +1 -0
- package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
- package/dist/tokenBudget.d.ts +1 -1
- package/dist/tools/edit.d.ts +7 -2
- package/dist/tools/grep.d.ts +14 -2
- package/dist/tools/ignore.d.ts +2 -2
- package/dist/tools/paths.d.ts +2 -0
- package/dist/{transferCli-YUFPI6AH.js → transferCli-R2KEIWQR.js} +8 -6
- package/dist/ui/complete.d.ts +5 -0
- package/dist/ui/vim.d.ts +1 -1
- package/dist/{ui-IVIFNUCC.js → ui-36ZEK65V.js} +177 -54
- package/dist/{update-TZ2G6KQ3.js → update-FSS7X5WV.js} +2 -2
- package/package.json +1 -1
- package/dist/args-W6BNKY7K.js +0 -13
- package/dist/providerCli-NMUFXAAS.js +0 -15
- package/dist/session-GKEXDJKZ.js +0 -12
- package/dist/setup-OV3NWQN5.js +0 -24
package/dist/sdk.js
CHANGED
|
@@ -500,7 +500,8 @@ Opsi:
|
|
|
500
500
|
"gh is not available ({error}); install GitHub CLI first": "gh tidak tersedia ({error}); pasang GitHub CLI dulu",
|
|
501
501
|
"gh pr checkout {n} failed (exit {code})": "gh pr checkout {n} gagal (exit {code})",
|
|
502
502
|
"checked out PR #{n}": "checkout PR #{n} selesai",
|
|
503
|
-
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya"
|
|
503
|
+
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya",
|
|
504
|
+
"view or change configuration settings": "lihat atau ubah pengaturan konfigurasi"
|
|
504
505
|
};
|
|
505
506
|
|
|
506
507
|
// src/i18n.ts
|
|
@@ -689,8 +690,11 @@ function loadConfig(cwd, env = process.env) {
|
|
|
689
690
|
const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
|
|
690
691
|
const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
|
|
691
692
|
const fromProject = untrustedProviders(project, known);
|
|
693
|
+
const rawFallbacks = project.fallbackModels ?? global.fallbackModels;
|
|
694
|
+
const fallbackModels = Array.isArray(rawFallbacks) ? rawFallbacks.filter((m) => typeof m === "string" && m.includes("/")) : void 0;
|
|
692
695
|
return {
|
|
693
696
|
model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
|
|
697
|
+
...fallbackModels?.length ? { fallbackModels } : {},
|
|
694
698
|
permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
|
|
695
699
|
reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
|
|
696
700
|
thinkingVisible: global.thinkingVisible === true,
|
|
@@ -774,7 +778,7 @@ function resolveModel(config, modelRef, env = process.env) {
|
|
|
774
778
|
}
|
|
775
779
|
|
|
776
780
|
// src/permissions.ts
|
|
777
|
-
import { isAbsolute, relative, resolve
|
|
781
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
778
782
|
var MODE_ORDER = ["default", "acceptEdits", "plan", "allowAll"];
|
|
779
783
|
var ALWAYS_ASK_COMMANDS = /* @__PURE__ */ new Set(["sudo", "sh", "bash", "zsh", "su"]);
|
|
780
784
|
function isAlwaysAsk(segment) {
|
|
@@ -832,7 +836,9 @@ function commandKey(segment) {
|
|
|
832
836
|
function requiresSensitiveToolApproval(req) {
|
|
833
837
|
const target = req.target.trim();
|
|
834
838
|
if (req.kind === "read" || req.kind === "edit") {
|
|
835
|
-
return /(?:^|[/\\])(?:\.env(
|
|
839
|
+
return /(?:^|[\s/\\])(?:\.env(?:\.[^\s/\\]+)?|\.ssh|\.aws|\.config[/\\]gcloud|\.npmrc|\.netrc|\.git-credentials|(?:secrets?|credentials?)(?:\.[a-z0-9_-]+)?)(?:[\s/\\]|$)/i.test(
|
|
840
|
+
target
|
|
841
|
+
) || /(?:id_rsa|id_ed25519|\.pem|\.p12|\.key)(?:\s|$)/i.test(target);
|
|
836
842
|
}
|
|
837
843
|
if (req.kind === "bash") {
|
|
838
844
|
return /\b(?:rm\s+-[a-z]*r[a-z]*f[a-z]*\s|rm\s+-[a-z]*f[a-z]*r[a-z]*\s|mkfs(?:\.|\s)|wipefs\b|shred\s|git\s+push\s+(?:--force|-f)\b|docker\s+(?:system\s+prune|volume\s+rm)|kubectl\s+delete\b|drop\s+(?:database|table)|npm\s+publish\b)/i.test(target) || /\b(?:deploy-service\.sh|aws\s+secretsmanager|cat\s+[^;&|]*\.env\b)/i.test(target);
|
|
@@ -857,7 +863,8 @@ var Permissions = class {
|
|
|
857
863
|
editInProject(target) {
|
|
858
864
|
const rel = relative(this.cwd, resolve(this.cwd, target));
|
|
859
865
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
860
|
-
|
|
866
|
+
const parts = rel.split(/[/\\]/).map((p) => p.toLowerCase());
|
|
867
|
+
return !parts.includes(".git");
|
|
861
868
|
}
|
|
862
869
|
/** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
|
|
863
870
|
rulesFor(req) {
|
|
@@ -875,7 +882,7 @@ var Permissions = class {
|
|
|
875
882
|
if (/`|\$\(|[<>]|^\s*[({]/.test(req.target)) return void 0;
|
|
876
883
|
const segments = req.target.split(/&&|\|\||[;&|\n]/).map((s) => s.trim()).filter(Boolean);
|
|
877
884
|
if (!segments.length) return void 0;
|
|
878
|
-
if (segments.some(isAlwaysAsk)) return void 0;
|
|
885
|
+
if (segments.some((s) => /^[({]/.test(s) || isAlwaysAsk(s))) return void 0;
|
|
879
886
|
return [...new Set(segments.map((s) => `bash(${commandKey(s)})`))];
|
|
880
887
|
}
|
|
881
888
|
if (req.kind === "mcp") return [`mcp(${req.tool})`];
|
|
@@ -953,9 +960,12 @@ var LOOPBACK_BIND = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
|
|
|
953
960
|
|
|
954
961
|
// src/args.ts
|
|
955
962
|
function parseCliArgs(argv) {
|
|
956
|
-
const expanded = argv.map(
|
|
957
|
-
|
|
958
|
-
|
|
963
|
+
const expanded = argv.map((a, i) => {
|
|
964
|
+
const nextIsFlagOrEnd = i + 1 >= argv.length || argv[i + 1].startsWith("-");
|
|
965
|
+
if (a === "--models" && nextIsFlagOrEnd) return "--models=all";
|
|
966
|
+
if (a === "--project" && nextIsFlagOrEnd) return "--project=";
|
|
967
|
+
return a;
|
|
968
|
+
});
|
|
959
969
|
const { values, positionals } = parseArgs({
|
|
960
970
|
args: expanded,
|
|
961
971
|
allowPositionals: true,
|
|
@@ -987,7 +997,8 @@ function parseCliArgs(argv) {
|
|
|
987
997
|
tools: { type: "string" },
|
|
988
998
|
models: { type: "string" },
|
|
989
999
|
project: { type: "string" },
|
|
990
|
-
repo: { type: "string" }
|
|
1000
|
+
repo: { type: "string" },
|
|
1001
|
+
"fallback-model": { type: "string" }
|
|
991
1002
|
}
|
|
992
1003
|
});
|
|
993
1004
|
const reasoning = values.reasoning;
|
|
@@ -1061,7 +1072,8 @@ function parseCliArgs(argv) {
|
|
|
1061
1072
|
tools,
|
|
1062
1073
|
models,
|
|
1063
1074
|
project: values.project,
|
|
1064
|
-
repo: values.repo
|
|
1075
|
+
repo: values.repo,
|
|
1076
|
+
fallbackModel: values["fallback-model"]
|
|
1065
1077
|
};
|
|
1066
1078
|
}
|
|
1067
1079
|
|
|
@@ -1153,14 +1165,14 @@ function detectRepetition(text) {
|
|
|
1153
1165
|
return -1;
|
|
1154
1166
|
}
|
|
1155
1167
|
function sleep(ms, signal) {
|
|
1156
|
-
return new Promise((
|
|
1168
|
+
return new Promise((resolve5, reject) => {
|
|
1157
1169
|
const onAbort = () => {
|
|
1158
1170
|
clearTimeout(timer);
|
|
1159
1171
|
reject(signal?.reason);
|
|
1160
1172
|
};
|
|
1161
1173
|
const timer = setTimeout(() => {
|
|
1162
1174
|
signal?.removeEventListener("abort", onAbort);
|
|
1163
|
-
|
|
1175
|
+
resolve5();
|
|
1164
1176
|
}, ms);
|
|
1165
1177
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1166
1178
|
});
|
|
@@ -1230,7 +1242,7 @@ async function readStream(res, onText, onThinking) {
|
|
|
1230
1242
|
}
|
|
1231
1243
|
current2 = target;
|
|
1232
1244
|
if (tc.id) target.id = tc.id;
|
|
1233
|
-
if (tc.function?.name &&
|
|
1245
|
+
if (tc.function?.name && target.name !== tc.function.name) target.name += tc.function.name;
|
|
1234
1246
|
if (tc.function?.arguments) target.arguments += tc.function.arguments;
|
|
1235
1247
|
}
|
|
1236
1248
|
};
|
|
@@ -1437,7 +1449,16 @@ function responsesFinishReason(items, toolCallCount, incompleteReason) {
|
|
|
1437
1449
|
function hasPendingProgrammaticReplay(messages) {
|
|
1438
1450
|
let pending = false;
|
|
1439
1451
|
for (const message of messages) {
|
|
1452
|
+
if (message.role === "user" && typeof message.content === "string") {
|
|
1453
|
+
pending = false;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
if (message.role === "tool" && message.content === "Cancelled by the user.") {
|
|
1457
|
+
pending = false;
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1440
1460
|
if (message.role !== "assistant") continue;
|
|
1461
|
+
if (message.content) pending = false;
|
|
1441
1462
|
for (const item of message.responses_output_items ?? []) {
|
|
1442
1463
|
if (item.type === "program") pending = true;
|
|
1443
1464
|
else if (item.type === "message" && pending) pending = false;
|
|
@@ -1511,7 +1532,7 @@ async function readResponsesStream(res, onText, onThinking) {
|
|
|
1511
1532
|
calls.set(index, {
|
|
1512
1533
|
id: String(event.item.call_id || current2?.id || event.item.id || `call_${index}`),
|
|
1513
1534
|
name: String(event.item.name || current2?.name || ""),
|
|
1514
|
-
arguments: String(event.item.arguments
|
|
1535
|
+
arguments: String(event.item.arguments || current2?.arguments || ""),
|
|
1515
1536
|
...programCaller(event.item) ?? current2?.caller ? { caller: programCaller(event.item) ?? current2?.caller } : {}
|
|
1516
1537
|
});
|
|
1517
1538
|
}
|
|
@@ -1747,7 +1768,7 @@ async function readResponsesWebSocketTurn(events, ws, onText, onThinking, signal
|
|
|
1747
1768
|
calls.set(index, {
|
|
1748
1769
|
id: String(event.item.call_id || current2?.id || event.item.id || `call_${index}`),
|
|
1749
1770
|
name: String(event.item.name || current2?.name || ""),
|
|
1750
|
-
arguments: String(event.item.arguments
|
|
1771
|
+
arguments: String(event.item.arguments || current2?.arguments || ""),
|
|
1751
1772
|
...programCaller(event.item) ?? current2?.caller ? { caller: programCaller(event.item) ?? current2?.caller } : {}
|
|
1752
1773
|
});
|
|
1753
1774
|
}
|
|
@@ -1808,7 +1829,7 @@ function createProvider(options) {
|
|
|
1808
1829
|
const isBotConnectorCloud = /^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_");
|
|
1809
1830
|
if (isBotConnectorCloud) {
|
|
1810
1831
|
headers["x-botconnector-client"] = "bccli";
|
|
1811
|
-
headers["x-botconnector-client-version"] = "0.4.
|
|
1832
|
+
headers["x-botconnector-client-version"] = "0.4.2";
|
|
1812
1833
|
}
|
|
1813
1834
|
const useNativeResponses = isBotConnectorCloud && providerId === "bc-cloud" && model === "gpt-6-luna";
|
|
1814
1835
|
const useResponsesWebSocket = useNativeResponses && Boolean(apiKey) && (fetchOverride === void 0 || responsesWebSocketFactory !== void 0);
|
|
@@ -1839,7 +1860,7 @@ function createProvider(options) {
|
|
|
1839
1860
|
baseURL: baseURL.replace(/\/+$/, ""),
|
|
1840
1861
|
defaultHeaders: {
|
|
1841
1862
|
"x-botconnector-client": "bccli",
|
|
1842
|
-
"x-botconnector-client-version": "0.4.
|
|
1863
|
+
"x-botconnector-client-version": "0.4.2"
|
|
1843
1864
|
}
|
|
1844
1865
|
});
|
|
1845
1866
|
lunaWs = responsesWebSocketFactory ? responsesWebSocketFactory(client) : new ResponsesWS(client, { reconnect: null });
|
|
@@ -1922,8 +1943,13 @@ function createProvider(options) {
|
|
|
1922
1943
|
}
|
|
1923
1944
|
async function post(path, body, signal) {
|
|
1924
1945
|
let lastError;
|
|
1946
|
+
let retryAfterWaitMs;
|
|
1925
1947
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
1926
|
-
if (attempt > 0)
|
|
1948
|
+
if (attempt > 0) {
|
|
1949
|
+
const delay = retryAfterWaitMs ?? retryDelayMs * 2 ** (attempt - 1);
|
|
1950
|
+
retryAfterWaitMs = void 0;
|
|
1951
|
+
await sleep(delay, signal);
|
|
1952
|
+
}
|
|
1927
1953
|
let res;
|
|
1928
1954
|
try {
|
|
1929
1955
|
res = await doFetch(`${baseURL}${path}`, { method: "POST", headers, body: JSON.stringify(body), signal });
|
|
@@ -1933,6 +1959,11 @@ function createProvider(options) {
|
|
|
1933
1959
|
continue;
|
|
1934
1960
|
}
|
|
1935
1961
|
if (res.ok) return res;
|
|
1962
|
+
const retryHeader = res.headers.get("retry-after");
|
|
1963
|
+
if (retryHeader) {
|
|
1964
|
+
const secs = Number(retryHeader);
|
|
1965
|
+
if (Number.isFinite(secs) && secs > 0 && secs <= 30) retryAfterWaitMs = Math.ceil(secs * 1e3);
|
|
1966
|
+
}
|
|
1936
1967
|
const text = await res.text().catch(() => "");
|
|
1937
1968
|
lastError = new ProviderError(t("{status} from {url}: {message}", { status: res.status, url: baseURL, message: errorMessage(text) }), res.status);
|
|
1938
1969
|
const deterministicQuota429 = res.status === 429 && /"code"\s*:\s*"(?:luna_monthly_quota_exhausted|plan_allowance_exhausted)"/i.test(text);
|
|
@@ -1995,6 +2026,43 @@ function createProvider(options) {
|
|
|
1995
2026
|
}
|
|
1996
2027
|
};
|
|
1997
2028
|
}
|
|
2029
|
+
function withFallbackProvider(primary, fallbacks) {
|
|
2030
|
+
return {
|
|
2031
|
+
async chat(req) {
|
|
2032
|
+
let emitted = false;
|
|
2033
|
+
const wrappedReq = {
|
|
2034
|
+
...req,
|
|
2035
|
+
onText: req.onText ? (delta) => {
|
|
2036
|
+
emitted = true;
|
|
2037
|
+
req.onText?.(delta);
|
|
2038
|
+
} : () => {
|
|
2039
|
+
emitted = true;
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
try {
|
|
2043
|
+
return await primary.chat(wrappedReq);
|
|
2044
|
+
} catch (primaryError) {
|
|
2045
|
+
if (emitted || req.signal?.aborted) throw primaryError;
|
|
2046
|
+
const status = primaryError instanceof ProviderError ? primaryError.status : void 0;
|
|
2047
|
+
const canFallback = status === void 0 || status === 429 || status === 408 || status >= 500;
|
|
2048
|
+
if (!canFallback) throw primaryError;
|
|
2049
|
+
const chain = fallbacks();
|
|
2050
|
+
if (!chain.length) throw primaryError;
|
|
2051
|
+
let lastError = primaryError;
|
|
2052
|
+
for (const fb of chain) {
|
|
2053
|
+
if (req.signal?.aborted) throw lastError;
|
|
2054
|
+
try {
|
|
2055
|
+
return await fb.chat(req);
|
|
2056
|
+
} catch (fbError) {
|
|
2057
|
+
lastError = fbError;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
throw lastError;
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
listModels: () => primary.listModels()
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
1998
2066
|
|
|
1999
2067
|
// src/textToolCalls.ts
|
|
2000
2068
|
var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
|
|
@@ -2114,7 +2182,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2114
2182
|
...payload.input !== void 0 ? { BCCLI_HOOK_INPUT: JSON.stringify(payload.input) } : {},
|
|
2115
2183
|
...payload.output !== void 0 ? { BCCLI_HOOK_OUTPUT: payload.output } : {}
|
|
2116
2184
|
};
|
|
2117
|
-
return new Promise((
|
|
2185
|
+
return new Promise((resolve5) => {
|
|
2118
2186
|
let stdout = "";
|
|
2119
2187
|
let stderr = "";
|
|
2120
2188
|
let timedOut = false;
|
|
@@ -2123,7 +2191,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2123
2191
|
if (settled) return;
|
|
2124
2192
|
settled = true;
|
|
2125
2193
|
clearTimeout(timer);
|
|
2126
|
-
|
|
2194
|
+
resolve5(result);
|
|
2127
2195
|
};
|
|
2128
2196
|
let child;
|
|
2129
2197
|
const shell = defaultShell();
|
|
@@ -2139,7 +2207,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2139
2207
|
child = spawn(command, { shell: true, env, cwd: opts.cwd ?? process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
|
|
2140
2208
|
}
|
|
2141
2209
|
} catch (error) {
|
|
2142
|
-
|
|
2210
|
+
resolve5({ failure: error.message });
|
|
2143
2211
|
return;
|
|
2144
2212
|
}
|
|
2145
2213
|
const timer = setTimeout(() => {
|
|
@@ -2194,7 +2262,9 @@ import { z as z8 } from "zod";
|
|
|
2194
2262
|
|
|
2195
2263
|
// src/tools/bash.ts
|
|
2196
2264
|
import { spawn as spawn2 } from "child_process";
|
|
2197
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2265
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2266
|
+
import { tmpdir } from "os";
|
|
2267
|
+
import { join as join2 } from "path";
|
|
2198
2268
|
import { z } from "zod";
|
|
2199
2269
|
|
|
2200
2270
|
// src/tools/sandbox.ts
|
|
@@ -2383,7 +2453,7 @@ function offlineEnv(env = process.env) {
|
|
|
2383
2453
|
};
|
|
2384
2454
|
}
|
|
2385
2455
|
function runCommand(command, opts) {
|
|
2386
|
-
return new Promise((
|
|
2456
|
+
return new Promise((resolve5) => {
|
|
2387
2457
|
const child = spawnChild(command, opts.cwd, opts.env, opts.shell ?? defaultShell());
|
|
2388
2458
|
if (!exitHookInstalled) {
|
|
2389
2459
|
process.on("exit", killAllCommands);
|
|
@@ -2396,7 +2466,14 @@ function runCommand(command, opts) {
|
|
|
2396
2466
|
let dropped = 0;
|
|
2397
2467
|
let timedOut = false;
|
|
2398
2468
|
let aborted = false;
|
|
2469
|
+
const fullChunks = [];
|
|
2470
|
+
let fullBytes = 0;
|
|
2471
|
+
const MAX_SPILL_BYTES = 10 * 1024 * 1024;
|
|
2399
2472
|
const onData = (data) => {
|
|
2473
|
+
if (fullBytes < MAX_SPILL_BYTES) {
|
|
2474
|
+
fullChunks.push(data);
|
|
2475
|
+
fullBytes += data.length;
|
|
2476
|
+
}
|
|
2400
2477
|
let s = data;
|
|
2401
2478
|
if (head.length < HALF) {
|
|
2402
2479
|
const take = s.slice(0, HALF - head.length);
|
|
@@ -2435,10 +2512,19 @@ function runCommand(command, opts) {
|
|
|
2435
2512
|
active.delete(killAsAborted);
|
|
2436
2513
|
clearTimeout(timer);
|
|
2437
2514
|
opts.signal.removeEventListener("abort", onAbort);
|
|
2515
|
+
let spillNote = "";
|
|
2516
|
+
if (dropped && fullChunks.length) {
|
|
2517
|
+
try {
|
|
2518
|
+
const spillPath = join2(tmpdir(), `bccli-bash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
|
|
2519
|
+
writeFileSync2(spillPath, fullChunks.join(""), "utf8");
|
|
2520
|
+
spillNote = `; full output saved to ${spillPath}`;
|
|
2521
|
+
} catch {
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2438
2524
|
const output = dropped ? `${head}
|
|
2439
|
-
\u2026 [${dropped} characters truncated] \u2026
|
|
2525
|
+
\u2026 [${dropped} characters truncated${spillNote}] \u2026
|
|
2440
2526
|
${tail}` : head + tail;
|
|
2441
|
-
|
|
2527
|
+
resolve5({ output: output + extra, exitCode, timedOut, aborted });
|
|
2442
2528
|
};
|
|
2443
2529
|
child.on("error", (error) => finish(null, error.message));
|
|
2444
2530
|
child.on("close", (code) => finish(code));
|
|
@@ -2560,46 +2646,139 @@ function formatDiff(lines) {
|
|
|
2560
2646
|
}
|
|
2561
2647
|
|
|
2562
2648
|
// src/tools/paths.ts
|
|
2649
|
+
import { statSync } from "fs";
|
|
2563
2650
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
2651
|
+
var readMtimes = /* @__PURE__ */ new WeakMap();
|
|
2564
2652
|
function resolvePath(cwd, path) {
|
|
2565
2653
|
return isAbsolute2(path) ? path : resolve2(cwd, path);
|
|
2566
2654
|
}
|
|
2567
2655
|
function errorCode(error) {
|
|
2568
2656
|
return error.code ?? error.message;
|
|
2569
2657
|
}
|
|
2658
|
+
function recordFileMtime(readFiles, abs) {
|
|
2659
|
+
readFiles.add(abs);
|
|
2660
|
+
try {
|
|
2661
|
+
let map = readMtimes.get(readFiles);
|
|
2662
|
+
if (!map) {
|
|
2663
|
+
map = /* @__PURE__ */ new Map();
|
|
2664
|
+
readMtimes.set(readFiles, map);
|
|
2665
|
+
}
|
|
2666
|
+
map.set(abs, statSync(abs).mtimeMs);
|
|
2667
|
+
} catch {
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function isFileModifiedSinceRead(readFiles, abs) {
|
|
2671
|
+
const recorded = readMtimes.get(readFiles)?.get(abs);
|
|
2672
|
+
if (recorded === void 0) return false;
|
|
2673
|
+
try {
|
|
2674
|
+
return statSync(abs).mtimeMs > recorded + 50;
|
|
2675
|
+
} catch {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2570
2679
|
|
|
2571
2680
|
// src/tools/edit.ts
|
|
2572
|
-
var
|
|
2573
|
-
path: z2.string().describe("File path, relative to the project or absolute"),
|
|
2681
|
+
var editEntrySchema = z2.object({
|
|
2574
2682
|
old_string: z2.string().describe("Exact text to replace; must be unique unless replace_all"),
|
|
2575
2683
|
new_string: z2.string().describe("Replacement text"),
|
|
2576
2684
|
replace_all: z2.boolean().optional().describe("Replace every occurrence")
|
|
2577
2685
|
});
|
|
2686
|
+
var schema = z2.object({
|
|
2687
|
+
path: z2.string().describe("File path, relative to the project or absolute"),
|
|
2688
|
+
old_string: z2.string().optional().describe("Exact text to replace; must be unique unless replace_all"),
|
|
2689
|
+
new_string: z2.string().optional().describe("Replacement text"),
|
|
2690
|
+
replace_all: z2.boolean().optional().describe("Replace every occurrence"),
|
|
2691
|
+
edits: z2.array(editEntrySchema).optional().describe("Optional list of sequential edits to apply atomically to the same file")
|
|
2692
|
+
});
|
|
2578
2693
|
function countOccurrences(haystack, needle) {
|
|
2579
2694
|
let count = 0;
|
|
2580
2695
|
for (let i = haystack.indexOf(needle); i !== -1; i = haystack.indexOf(needle, i + needle.length)) count++;
|
|
2581
2696
|
return count;
|
|
2582
2697
|
}
|
|
2583
|
-
function
|
|
2698
|
+
function normalizeTypographic(s) {
|
|
2699
|
+
return s.replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2013\u2014]/g, "-").replace(/\u00A0/g, " ");
|
|
2700
|
+
}
|
|
2701
|
+
function stripReadLinePrefixes(s) {
|
|
2702
|
+
const lines = s.split(/\r?\n/);
|
|
2703
|
+
if (lines.every((l) => !l.trim() || /^\s*\d+\t/.test(l))) {
|
|
2704
|
+
return lines.map((l) => l.replace(/^\s*\d+\t/, "")).join("\n");
|
|
2705
|
+
}
|
|
2706
|
+
return s;
|
|
2707
|
+
}
|
|
2708
|
+
function matchByNormalizedLines(content, oldText, newText, replaceAll) {
|
|
2709
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2710
|
+
const fileLines = content.split(/\r?\n/);
|
|
2711
|
+
const oldLines = stripReadLinePrefixes(oldText).split(/\r?\n/);
|
|
2712
|
+
if (!oldLines.length) return { count: 0 };
|
|
2713
|
+
const normLine = (l) => normalizeTypographic(l).trimEnd();
|
|
2714
|
+
const normOld = oldLines.map(normLine);
|
|
2715
|
+
const matches = [];
|
|
2716
|
+
for (let i = 0; i + oldLines.length <= fileLines.length; i++) {
|
|
2717
|
+
let ok = true;
|
|
2718
|
+
for (let j = 0; j < oldLines.length; j++) {
|
|
2719
|
+
if (normLine(fileLines[i + j]) !== normOld[j]) {
|
|
2720
|
+
ok = false;
|
|
2721
|
+
break;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
if (ok) {
|
|
2725
|
+
matches.push(i);
|
|
2726
|
+
if (replaceAll) i += oldLines.length - 1;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (matches.length === 0) return { count: 0 };
|
|
2730
|
+
if (matches.length > 1 && !replaceAll) return { count: matches.length };
|
|
2731
|
+
const replacementLines = stripReadLinePrefixes(newText).split(/\r?\n/);
|
|
2732
|
+
const outLines = [...fileLines];
|
|
2733
|
+
for (let m = matches.length - 1; m >= 0; m--) {
|
|
2734
|
+
outLines.splice(matches[m], oldLines.length, ...replacementLines);
|
|
2735
|
+
}
|
|
2736
|
+
return { count: matches.length, result: outLines.join(eol) };
|
|
2737
|
+
}
|
|
2738
|
+
function applySingleEdit(content, path, input) {
|
|
2584
2739
|
if (input.old_string === input.new_string) {
|
|
2585
2740
|
return { output: "old_string and new_string are identical; nothing to change.", isError: true };
|
|
2586
2741
|
}
|
|
2587
2742
|
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2588
2743
|
const toEol = (s) => s.replace(/\r?\n/g, eol);
|
|
2744
|
+
const strippedOld = stripReadLinePrefixes(input.old_string);
|
|
2745
|
+
const strippedNew = stripReadLinePrefixes(input.new_string);
|
|
2589
2746
|
const attempts = [[input.old_string, input.new_string]];
|
|
2590
2747
|
if (toEol(input.old_string) !== input.old_string) attempts.push([toEol(input.old_string), toEol(input.new_string)]);
|
|
2748
|
+
if (strippedOld !== input.old_string) {
|
|
2749
|
+
attempts.push([strippedOld, strippedNew], [toEol(strippedOld), toEol(strippedNew)]);
|
|
2750
|
+
}
|
|
2591
2751
|
for (const [oldText, newText] of attempts) {
|
|
2592
2752
|
const count = oldText ? countOccurrences(content, oldText) : 0;
|
|
2593
2753
|
if (count === 0) continue;
|
|
2594
2754
|
if (count > 1 && !input.replace_all) {
|
|
2595
2755
|
return {
|
|
2596
|
-
output: `old_string appears ${count} times in ${
|
|
2756
|
+
output: `old_string appears ${count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
2597
2757
|
isError: true
|
|
2598
2758
|
};
|
|
2599
2759
|
}
|
|
2600
2760
|
return input.replace_all ? content.split(oldText).join(newText) : content.replace(oldText, () => newText);
|
|
2601
2761
|
}
|
|
2602
|
-
|
|
2762
|
+
const fuzzy = matchByNormalizedLines(content, input.old_string, input.new_string, input.replace_all);
|
|
2763
|
+
if (fuzzy.count > 1 && !input.replace_all) {
|
|
2764
|
+
return {
|
|
2765
|
+
output: `old_string appears ${fuzzy.count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
2766
|
+
isError: true
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
if (fuzzy.count > 0 && fuzzy.result !== void 0) return fuzzy.result;
|
|
2770
|
+
return { output: `old_string not found in ${path}. Re-read the file.`, isError: true };
|
|
2771
|
+
}
|
|
2772
|
+
function applyEdit(content, input) {
|
|
2773
|
+
const list2 = input.edits?.length ? input.edits : input.old_string !== void 0 && input.new_string !== void 0 ? [{ old_string: input.old_string, new_string: input.new_string, replace_all: input.replace_all }] : [];
|
|
2774
|
+
if (!list2.length) return { output: "Provide old_string and new_string (or edits array).", isError: true };
|
|
2775
|
+
let current2 = content;
|
|
2776
|
+
for (const item of list2) {
|
|
2777
|
+
const next = applySingleEdit(current2, input.path, item);
|
|
2778
|
+
if (typeof next !== "string") return next;
|
|
2779
|
+
current2 = next;
|
|
2780
|
+
}
|
|
2781
|
+
return current2;
|
|
2603
2782
|
}
|
|
2604
2783
|
var editTool = defineTool({
|
|
2605
2784
|
name: "edit",
|
|
@@ -2610,6 +2789,9 @@ var editTool = defineTool({
|
|
|
2610
2789
|
async validate(input, ctx) {
|
|
2611
2790
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
2612
2791
|
if (!ctx.readFiles.has(abs)) return `Read ${input.path} with read before editing it.`;
|
|
2792
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
2793
|
+
return `${input.path} has been modified since it was last read. Read it again before editing.`;
|
|
2794
|
+
}
|
|
2613
2795
|
let content;
|
|
2614
2796
|
try {
|
|
2615
2797
|
content = await readFile(abs, "utf8");
|
|
@@ -2630,6 +2812,9 @@ var editTool = defineTool({
|
|
|
2630
2812
|
if (!ctx.readFiles.has(abs)) {
|
|
2631
2813
|
return { output: `Read ${input.path} with read before editing it.`, isError: true };
|
|
2632
2814
|
}
|
|
2815
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
2816
|
+
return { output: `${input.path} has been modified since it was last read. Read it again before editing.`, isError: true };
|
|
2817
|
+
}
|
|
2633
2818
|
let content;
|
|
2634
2819
|
try {
|
|
2635
2820
|
content = await readFile(abs, "utf8");
|
|
@@ -2640,6 +2825,7 @@ var editTool = defineTool({
|
|
|
2640
2825
|
if (typeof next !== "string") return next;
|
|
2641
2826
|
await ctx.checkpoint?.(abs);
|
|
2642
2827
|
await writeFile(abs, next);
|
|
2828
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
2643
2829
|
return { output: `Edited ${input.path}.`, display: formatDiff(diffLines(content, next)) };
|
|
2644
2830
|
}
|
|
2645
2831
|
});
|
|
@@ -2911,20 +3097,22 @@ async function loadPage(inputUrl, signal, pinned, fetchPage) {
|
|
|
2911
3097
|
var fetchTool = createFetchTool();
|
|
2912
3098
|
|
|
2913
3099
|
// src/tools/glob.ts
|
|
3100
|
+
import { statSync as statSync2 } from "fs";
|
|
2914
3101
|
import { glob as nativeGlob } from "fs/promises";
|
|
3102
|
+
import { join as join4 } from "path";
|
|
2915
3103
|
import { z as z4 } from "zod";
|
|
2916
3104
|
|
|
2917
3105
|
// src/abort.ts
|
|
2918
3106
|
var ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
2919
3107
|
function raceAbort(promise, signal) {
|
|
2920
3108
|
if (signal.aborted) return Promise.resolve(ABORTED);
|
|
2921
|
-
return new Promise((
|
|
2922
|
-
const onAbort = () =>
|
|
3109
|
+
return new Promise((resolve5, reject) => {
|
|
3110
|
+
const onAbort = () => resolve5(ABORTED);
|
|
2923
3111
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2924
3112
|
promise.then(
|
|
2925
3113
|
(value) => {
|
|
2926
3114
|
signal.removeEventListener("abort", onAbort);
|
|
2927
|
-
|
|
3115
|
+
resolve5(value);
|
|
2928
3116
|
},
|
|
2929
3117
|
(error) => {
|
|
2930
3118
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -2950,22 +3138,38 @@ async function collectUntilAbort(iterable, signal, onItem) {
|
|
|
2950
3138
|
|
|
2951
3139
|
// src/tools/ignore.ts
|
|
2952
3140
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2953
|
-
import { join as
|
|
2954
|
-
function ignorePatterns(root) {
|
|
3141
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
3142
|
+
function ignorePatterns(root, projectRoot) {
|
|
2955
3143
|
const patterns = ["**/node_modules/**", "**/.git/**"];
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3144
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3145
|
+
const dirs2 = [];
|
|
3146
|
+
let cur = resolve3(root);
|
|
3147
|
+
const stop = projectRoot ? resolve3(projectRoot) : cur;
|
|
3148
|
+
while (true) {
|
|
3149
|
+
if (!seen.has(cur)) {
|
|
3150
|
+
seen.add(cur);
|
|
3151
|
+
dirs2.push(cur);
|
|
3152
|
+
}
|
|
3153
|
+
if (cur === stop || !cur.startsWith(stop)) break;
|
|
3154
|
+
const parent = dirname2(cur);
|
|
3155
|
+
if (parent === cur) break;
|
|
3156
|
+
cur = parent;
|
|
3157
|
+
}
|
|
3158
|
+
for (const dir of dirs2) {
|
|
3159
|
+
let lines = [];
|
|
3160
|
+
try {
|
|
3161
|
+
lines = readFileSync2(join3(dir, ".gitignore"), "utf8").split(/\r?\n/);
|
|
3162
|
+
} catch {
|
|
3163
|
+
continue;
|
|
3164
|
+
}
|
|
3165
|
+
for (const raw of lines) {
|
|
3166
|
+
const line = raw.trim();
|
|
3167
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
3168
|
+
const p = line.replace(/^\//, "").replace(/\/$/, "");
|
|
3169
|
+
patterns.push(`**/${p}`, `**/${p}/**`);
|
|
3170
|
+
}
|
|
2967
3171
|
}
|
|
2968
|
-
return patterns;
|
|
3172
|
+
return [...new Set(patterns)];
|
|
2969
3173
|
}
|
|
2970
3174
|
|
|
2971
3175
|
// src/tools/globSafety.ts
|
|
@@ -3007,19 +3211,30 @@ var globTool = defineTool({
|
|
|
3007
3211
|
path: z4.string().optional().describe("Base directory, default project root")
|
|
3008
3212
|
}),
|
|
3009
3213
|
kind: "read",
|
|
3010
|
-
target: (input) => input.pattern,
|
|
3214
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
3011
3215
|
async run(input, ctx) {
|
|
3012
3216
|
const invalid = validateGlobPattern(input.pattern);
|
|
3013
3217
|
if (invalid) return { output: invalid, isError: true };
|
|
3014
3218
|
const root = resolvePath(ctx.cwd, input.path ?? ".");
|
|
3015
3219
|
const files = [];
|
|
3016
3220
|
const finished = await collectUntilAbort(
|
|
3017
|
-
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root) }),
|
|
3221
|
+
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root, ctx.cwd) }),
|
|
3018
3222
|
ctx.signal,
|
|
3019
3223
|
(file) => files.push(normalizeGlobPath(file))
|
|
3020
3224
|
);
|
|
3021
3225
|
if (!finished) return { output: "Cancelled by the user.", isError: true };
|
|
3022
|
-
|
|
3226
|
+
const mtime = (rel) => {
|
|
3227
|
+
try {
|
|
3228
|
+
return statSync2(join4(root, rel)).mtimeMs;
|
|
3229
|
+
} catch {
|
|
3230
|
+
return 0;
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
const mtimes = new Map(files.map((f) => [f, mtime(f)]));
|
|
3234
|
+
files.sort((a, b) => {
|
|
3235
|
+
const diff = (mtimes.get(b) ?? 0) - (mtimes.get(a) ?? 0);
|
|
3236
|
+
return Math.abs(diff) >= 50 ? diff : a.localeCompare(b);
|
|
3237
|
+
});
|
|
3023
3238
|
if (!files.length) return { output: "No matching files." };
|
|
3024
3239
|
const first = files.slice(0, 500);
|
|
3025
3240
|
const shown = [];
|
|
@@ -3040,75 +3255,141 @@ var globTool = defineTool({
|
|
|
3040
3255
|
|
|
3041
3256
|
// src/tools/grep.ts
|
|
3042
3257
|
import { execFile } from "child_process";
|
|
3258
|
+
import { statSync as statSync3 } from "fs";
|
|
3043
3259
|
import { readFile as readFile2 } from "fs/promises";
|
|
3044
3260
|
import { glob as nativeGlob2 } from "fs/promises";
|
|
3261
|
+
import { basename, dirname as dirname3 } from "path";
|
|
3045
3262
|
import { z as z5 } from "zod";
|
|
3046
3263
|
var MAX_MATCHES = 200;
|
|
3047
3264
|
var MAX_OUTPUT_CHARS2 = 24e3;
|
|
3265
|
+
function escapeRegExp(s) {
|
|
3266
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3267
|
+
}
|
|
3268
|
+
function isSingleFile(p) {
|
|
3269
|
+
try {
|
|
3270
|
+
return statSync3(p).isFile();
|
|
3271
|
+
} catch {
|
|
3272
|
+
return false;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3048
3275
|
async function grepJs(pattern, opts) {
|
|
3049
|
-
const
|
|
3276
|
+
const source = opts.literal ? escapeRegExp(pattern) : pattern;
|
|
3277
|
+
const regex = new RegExp(source, opts.ignoreCase ? "i" : "");
|
|
3278
|
+
const single = isSingleFile(opts.root);
|
|
3050
3279
|
const files = [];
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3280
|
+
if (single) {
|
|
3281
|
+
files.push(basename(opts.root));
|
|
3282
|
+
} else {
|
|
3283
|
+
const finished = await collectUntilAbort(
|
|
3284
|
+
nativeGlob2(opts.glob ?? "**/*", { cwd: opts.root, exclude: ignorePatterns(opts.root, opts.projectRoot) }),
|
|
3285
|
+
opts.signal,
|
|
3286
|
+
(file) => files.push(normalizeGlobPath(file))
|
|
3287
|
+
);
|
|
3288
|
+
if (!finished) return [];
|
|
3289
|
+
}
|
|
3290
|
+
const baseDir = single ? dirname3(opts.root) : opts.root;
|
|
3291
|
+
const ctxLines = Math.max(0, Math.min(10, Math.floor(opts.context ?? 0)));
|
|
3292
|
+
const mode = opts.outputMode ?? "content";
|
|
3056
3293
|
const out = [];
|
|
3057
|
-
if (!finished) return out;
|
|
3058
3294
|
for (const file of files.sort()) {
|
|
3059
3295
|
if (opts.signal?.aborted) break;
|
|
3060
3296
|
let text;
|
|
3061
3297
|
try {
|
|
3062
|
-
const buffer = await readFile2(resolvePath(
|
|
3298
|
+
const buffer = await readFile2(resolvePath(baseDir, file));
|
|
3063
3299
|
if (buffer.subarray(0, 8e3).includes(0)) continue;
|
|
3064
3300
|
text = buffer.toString("utf8");
|
|
3065
3301
|
} catch {
|
|
3066
3302
|
continue;
|
|
3067
3303
|
}
|
|
3068
3304
|
const lines = text.split(/\r?\n/);
|
|
3305
|
+
let fileMatches = 0;
|
|
3069
3306
|
for (let i = 0; i < lines.length && out.length < MAX_MATCHES; i++) {
|
|
3070
|
-
if (regex.test(lines[i]))
|
|
3307
|
+
if (regex.test(lines[i])) {
|
|
3308
|
+
fileMatches++;
|
|
3309
|
+
if (mode === "files_with_matches") {
|
|
3310
|
+
out.push(file);
|
|
3311
|
+
break;
|
|
3312
|
+
}
|
|
3313
|
+
if (mode === "content") {
|
|
3314
|
+
const from = Math.max(0, i - ctxLines);
|
|
3315
|
+
const to = Math.min(lines.length - 1, i + ctxLines);
|
|
3316
|
+
for (let j = from; j <= to && out.length < MAX_MATCHES; j++) {
|
|
3317
|
+
const sep2 = j === i ? ":" : "-";
|
|
3318
|
+
out.push(`${file}${sep2}${j + 1}${sep2}${lines[j].slice(0, 300)}`);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3071
3322
|
}
|
|
3323
|
+
if (mode === "count" && fileMatches > 0) out.push(`${file}:${fileMatches}`);
|
|
3072
3324
|
if (out.length >= MAX_MATCHES) break;
|
|
3073
3325
|
}
|
|
3074
3326
|
return out;
|
|
3075
3327
|
}
|
|
3076
3328
|
function grepRg(pattern, opts) {
|
|
3077
|
-
const
|
|
3329
|
+
const single = isSingleFile(opts.root);
|
|
3330
|
+
const cwd = single ? dirname3(opts.root) : opts.root;
|
|
3331
|
+
const target = single ? basename(opts.root) : ".";
|
|
3332
|
+
const mode = opts.outputMode ?? "content";
|
|
3333
|
+
const args = ["--color", "never"];
|
|
3334
|
+
if (mode === "files_with_matches") {
|
|
3335
|
+
args.push("--files-with-matches");
|
|
3336
|
+
} else if (mode === "count") {
|
|
3337
|
+
args.push("--count");
|
|
3338
|
+
} else {
|
|
3339
|
+
args.push("--line-number", "--no-heading", "--max-columns", "300");
|
|
3340
|
+
if (opts.context && opts.context > 0) args.push("-C", String(Math.min(10, Math.floor(opts.context))));
|
|
3341
|
+
}
|
|
3342
|
+
if (opts.literal) args.push("-F");
|
|
3078
3343
|
if (opts.ignoreCase) args.push("-i");
|
|
3079
|
-
if (opts.glob) args.push("-g", opts.glob);
|
|
3080
|
-
args.push("
|
|
3081
|
-
return new Promise((
|
|
3082
|
-
execFile("rg", args, { cwd
|
|
3083
|
-
if (opts.signal?.aborted) return
|
|
3344
|
+
if (opts.glob && !single) args.push("-g", opts.glob);
|
|
3345
|
+
args.push("-e", pattern, target);
|
|
3346
|
+
return new Promise((resolve5) => {
|
|
3347
|
+
execFile("rg", args, { cwd, maxBuffer: 10 * 1024 * 1024, signal: opts.signal }, (error, stdout) => {
|
|
3348
|
+
if (opts.signal?.aborted) return resolve5([]);
|
|
3084
3349
|
const code = error?.code;
|
|
3085
|
-
if (code === "ENOENT") return
|
|
3086
|
-
if (error && error.code !== 1) return
|
|
3087
|
-
|
|
3350
|
+
if (code === "ENOENT") return resolve5(void 0);
|
|
3351
|
+
if (error && error.code !== 1) return resolve5(void 0);
|
|
3352
|
+
resolve5(
|
|
3353
|
+
stdout.split(/\r?\n/).filter(Boolean).map((l) => l.replace(/^\.[\\/]/, "").replace(/\\/g, "/")).slice(0, MAX_MATCHES)
|
|
3354
|
+
);
|
|
3088
3355
|
});
|
|
3089
3356
|
});
|
|
3090
3357
|
}
|
|
3091
3358
|
var grepTool = defineTool({
|
|
3092
3359
|
name: "grep",
|
|
3093
|
-
description: "Search file contents with a regular expression. Respects .gitignore. Returns path:line:text (max 200).",
|
|
3360
|
+
description: "Search file contents with a regular expression or literal text. Respects .gitignore. Returns path:line:text (max 200).",
|
|
3094
3361
|
schema: z5.object({
|
|
3095
|
-
pattern: z5.string().describe("Regular expression"),
|
|
3096
|
-
path: z5.string().optional().describe("
|
|
3362
|
+
pattern: z5.string().describe("Regular expression or literal string"),
|
|
3363
|
+
path: z5.string().optional().describe("File or directory to search, default project root"),
|
|
3097
3364
|
glob: z5.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
|
|
3098
|
-
ignore_case: z5.boolean().optional()
|
|
3365
|
+
ignore_case: z5.boolean().optional(),
|
|
3366
|
+
literal: z5.boolean().optional().describe("Treat pattern as literal text instead of regex"),
|
|
3367
|
+
context: z5.number().int().min(0).max(10).optional().describe("Number of context lines before and after each match"),
|
|
3368
|
+
output_mode: z5.enum(["content", "files_with_matches", "count"]).optional().describe("Output format, default content")
|
|
3099
3369
|
}),
|
|
3100
3370
|
kind: "read",
|
|
3101
|
-
target: (input) => input.pattern,
|
|
3371
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
3102
3372
|
async run(input, ctx) {
|
|
3103
3373
|
if (input.glob) {
|
|
3104
3374
|
const invalid = validateGlobPattern(input.glob);
|
|
3105
3375
|
if (invalid) return { output: invalid, isError: true };
|
|
3106
3376
|
}
|
|
3107
|
-
const opts = {
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3377
|
+
const opts = {
|
|
3378
|
+
root: resolvePath(ctx.cwd, input.path ?? "."),
|
|
3379
|
+
projectRoot: ctx.cwd,
|
|
3380
|
+
glob: input.glob,
|
|
3381
|
+
ignoreCase: input.ignore_case,
|
|
3382
|
+
literal: input.literal,
|
|
3383
|
+
context: input.context,
|
|
3384
|
+
outputMode: input.output_mode,
|
|
3385
|
+
signal: ctx.signal
|
|
3386
|
+
};
|
|
3387
|
+
if (!input.literal) {
|
|
3388
|
+
try {
|
|
3389
|
+
new RegExp(input.pattern);
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
return { output: `Invalid regex: ${error.message}`, isError: true };
|
|
3392
|
+
}
|
|
3112
3393
|
}
|
|
3113
3394
|
const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
|
|
3114
3395
|
if (ctx.signal.aborted) return { output: "Cancelled by the user.", isError: true };
|
|
@@ -3130,6 +3411,7 @@ var grepTool = defineTool({
|
|
|
3130
3411
|
});
|
|
3131
3412
|
|
|
3132
3413
|
// src/tools/read.ts
|
|
3414
|
+
import { readdirSync, statSync as statSync4 } from "fs";
|
|
3133
3415
|
import { readFile as readFile3 } from "fs/promises";
|
|
3134
3416
|
import { z as z6 } from "zod";
|
|
3135
3417
|
var MAX_LINE = 2e3;
|
|
@@ -3213,6 +3495,16 @@ function createReadTool(options = {}) {
|
|
|
3213
3495
|
target: (input) => input.path,
|
|
3214
3496
|
async run(input, ctx) {
|
|
3215
3497
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
3498
|
+
try {
|
|
3499
|
+
if (statSync4(abs).isDirectory()) {
|
|
3500
|
+
const entries = readdirSync(abs, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort((a, b) => a.localeCompare(b));
|
|
3501
|
+
const start = (input.offset ?? 1) - 1;
|
|
3502
|
+
const limit = input.limit ?? 2e3;
|
|
3503
|
+
const slice = entries.slice(start, start + limit);
|
|
3504
|
+
return { output: slice.join("\n") || "(empty directory)", display: t("{n} lines", { n: slice.length }) };
|
|
3505
|
+
}
|
|
3506
|
+
} catch {
|
|
3507
|
+
}
|
|
3216
3508
|
let buffer;
|
|
3217
3509
|
try {
|
|
3218
3510
|
buffer = await readFile3(abs);
|
|
@@ -3221,13 +3513,13 @@ function createReadTool(options = {}) {
|
|
|
3221
3513
|
}
|
|
3222
3514
|
const mediaType = vision ? imageMediaType(buffer) : void 0;
|
|
3223
3515
|
if (mediaType) {
|
|
3224
|
-
ctx.readFiles
|
|
3516
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3225
3517
|
return readImageResult(input, buffer, mediaType);
|
|
3226
3518
|
}
|
|
3227
3519
|
if (buffer.subarray(0, 8e3).includes(0)) {
|
|
3228
3520
|
return { output: `${input.path} is a binary file; not read.`, isError: true };
|
|
3229
3521
|
}
|
|
3230
|
-
ctx.readFiles
|
|
3522
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3231
3523
|
return readTextResult(input, buffer);
|
|
3232
3524
|
}
|
|
3233
3525
|
});
|
|
@@ -3237,7 +3529,7 @@ var readTool = createReadTool({ vision: true });
|
|
|
3237
3529
|
// src/tools/write.ts
|
|
3238
3530
|
import { existsSync as existsSync4 } from "fs";
|
|
3239
3531
|
import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
3240
|
-
import { dirname as
|
|
3532
|
+
import { dirname as dirname4 } from "path";
|
|
3241
3533
|
import { z as z7 } from "zod";
|
|
3242
3534
|
var writeTool = defineTool({
|
|
3243
3535
|
name: "write",
|
|
@@ -3264,12 +3556,12 @@ var writeTool = defineTool({
|
|
|
3264
3556
|
}
|
|
3265
3557
|
try {
|
|
3266
3558
|
await ctx.checkpoint?.(abs);
|
|
3267
|
-
await mkdir(
|
|
3559
|
+
await mkdir(dirname4(abs), { recursive: true });
|
|
3268
3560
|
await writeFile2(abs, input.content);
|
|
3269
3561
|
} catch (error) {
|
|
3270
3562
|
return { output: `Failed to write ${input.path}: ${errorCode(error)}`, isError: true };
|
|
3271
3563
|
}
|
|
3272
|
-
ctx.readFiles
|
|
3564
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3273
3565
|
const lines = input.content.split("\n").length;
|
|
3274
3566
|
return { output: `Wrote ${input.path} (${lines} lines).`, display: t("{lines} lines written", { lines }) };
|
|
3275
3567
|
}
|
|
@@ -3379,12 +3671,36 @@ function budgetStatus(usage, cap, model) {
|
|
|
3379
3671
|
}
|
|
3380
3672
|
|
|
3381
3673
|
// src/tokenBudget.ts
|
|
3674
|
+
var IMAGE_TOKEN_COST = 1e3;
|
|
3382
3675
|
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
3383
|
-
|
|
3676
|
+
function isImageAttachmentMessage(m) {
|
|
3677
|
+
if (m.role !== "user" || !Array.isArray(m.content)) return false;
|
|
3678
|
+
const hasImage = m.content.some((p) => p.type === "image_url");
|
|
3679
|
+
const firstText = m.content.find((p) => p.type === "text");
|
|
3680
|
+
return hasImage && !!firstText && firstText.type === "text" && firstText.text.startsWith("Image read from ");
|
|
3681
|
+
}
|
|
3682
|
+
function isUserTurnBoundary(m) {
|
|
3683
|
+
return m.role === "user" && !isImageAttachmentMessage(m);
|
|
3684
|
+
}
|
|
3685
|
+
function estimateMessages(messages) {
|
|
3686
|
+
let total = 0;
|
|
3687
|
+
for (const m of messages) {
|
|
3688
|
+
if (m.role === "user" && Array.isArray(m.content)) {
|
|
3689
|
+
for (const part of m.content) {
|
|
3690
|
+
if (part.type === "image_url") total += IMAGE_TOKEN_COST;
|
|
3691
|
+
else total += estimateTokens(part.text);
|
|
3692
|
+
}
|
|
3693
|
+
total += 8;
|
|
3694
|
+
} else {
|
|
3695
|
+
total += estimateTokens(JSON.stringify(m));
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
return total;
|
|
3699
|
+
}
|
|
3384
3700
|
function trimToFit(messages, maxTokens) {
|
|
3385
3701
|
let out = messages;
|
|
3386
3702
|
while (estimateMessages(out) > maxTokens) {
|
|
3387
|
-
const boundary = out.findIndex((m, i) => i > 0 && m
|
|
3703
|
+
const boundary = out.findIndex((m, i) => i > 0 && isUserTurnBoundary(m));
|
|
3388
3704
|
if (boundary === -1) return out;
|
|
3389
3705
|
out = out.slice(boundary);
|
|
3390
3706
|
}
|
|
@@ -3394,13 +3710,13 @@ function splitKeepRecent(messages, keepTokens) {
|
|
|
3394
3710
|
if (messages.length === 0) return [[], []];
|
|
3395
3711
|
let start = messages.length;
|
|
3396
3712
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3397
|
-
if (i > 0 && messages[i]
|
|
3713
|
+
if (i > 0 && !isUserTurnBoundary(messages[i])) continue;
|
|
3398
3714
|
if (estimateMessages(messages.slice(i)) <= keepTokens) start = i;
|
|
3399
3715
|
else break;
|
|
3400
3716
|
}
|
|
3401
3717
|
if (start === messages.length) {
|
|
3402
3718
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3403
|
-
if (messages[i]
|
|
3719
|
+
if (isUserTurnBoundary(messages[i])) {
|
|
3404
3720
|
start = i;
|
|
3405
3721
|
break;
|
|
3406
3722
|
}
|
|
@@ -3448,6 +3764,7 @@ var Agent2 = class {
|
|
|
3448
3764
|
this.messages = [...opts.history ?? []];
|
|
3449
3765
|
this.maxSteps = typeof opts.maxSteps === "number" && Number.isFinite(opts.maxSteps) && opts.maxSteps > 0 ? Math.max(1, Math.floor(opts.maxSteps)) : void 0;
|
|
3450
3766
|
this.contextWindow = opts.contextWindow ?? 128e3;
|
|
3767
|
+
this.budgetModel = opts.budgetModel ?? "";
|
|
3451
3768
|
this.systemPrompt = opts.systemPrompt;
|
|
3452
3769
|
this.reasoning = opts.reasoning ?? "auto";
|
|
3453
3770
|
}
|
|
@@ -3471,6 +3788,7 @@ var Agent2 = class {
|
|
|
3471
3788
|
maxSteps;
|
|
3472
3789
|
// One window for every model; resolved from contextWindowFor() (catalog + env override).
|
|
3473
3790
|
contextWindow;
|
|
3791
|
+
budgetModel;
|
|
3474
3792
|
systemPrompt;
|
|
3475
3793
|
setSystemPrompt(text) {
|
|
3476
3794
|
this.systemPrompt = text;
|
|
@@ -3478,6 +3796,15 @@ var Agent2 = class {
|
|
|
3478
3796
|
setContextWindow(window) {
|
|
3479
3797
|
this.contextWindow = window;
|
|
3480
3798
|
}
|
|
3799
|
+
getContextWindow() {
|
|
3800
|
+
return this.contextWindow;
|
|
3801
|
+
}
|
|
3802
|
+
setBudgetModel(model) {
|
|
3803
|
+
this.budgetModel = model;
|
|
3804
|
+
}
|
|
3805
|
+
getBudgetModel() {
|
|
3806
|
+
return this.budgetModel;
|
|
3807
|
+
}
|
|
3481
3808
|
setTools(tools) {
|
|
3482
3809
|
this.tools = tools;
|
|
3483
3810
|
this.definitions = toolDefinitions(tools);
|
|
@@ -3678,7 +4005,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
3678
4005
|
}
|
|
3679
4006
|
/** Current cap status; undefined means the budget is not exhausted. */
|
|
3680
4007
|
overBudget() {
|
|
3681
|
-
return budgetStatus(this.totalUsage, this.opts.usageCap, this.
|
|
4008
|
+
return budgetStatus(this.totalUsage, this.opts.usageCap, this.budgetModel);
|
|
3682
4009
|
}
|
|
3683
4010
|
/** Every tool call needs an answer in the history, or the next request to the model is rejected. */
|
|
3684
4011
|
cancelCalls(calls) {
|
|
@@ -3798,7 +4125,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
3798
4125
|
};
|
|
3799
4126
|
|
|
3800
4127
|
// src/checkpoints.ts
|
|
3801
|
-
import { existsSync as existsSync5, statSync } from "fs";
|
|
4128
|
+
import { existsSync as existsSync5, statSync as statSync5 } from "fs";
|
|
3802
4129
|
import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
|
|
3803
4130
|
var MAX_TURNS = 20;
|
|
3804
4131
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
@@ -3825,8 +4152,8 @@ var CheckpointStore = class {
|
|
|
3825
4152
|
history = [];
|
|
3826
4153
|
current;
|
|
3827
4154
|
pendingRedo;
|
|
4155
|
+
offset = 0;
|
|
3828
4156
|
beginTurn() {
|
|
3829
|
-
if (this.current?.size === 0) return;
|
|
3830
4157
|
this.current = /* @__PURE__ */ new Map();
|
|
3831
4158
|
this.history.push(this.current);
|
|
3832
4159
|
this.pendingRedo = void 0;
|
|
@@ -3837,16 +4164,19 @@ var CheckpointStore = class {
|
|
|
3837
4164
|
const turn = this.current;
|
|
3838
4165
|
if (turn.has(path)) return;
|
|
3839
4166
|
if (!existsSync5(path)) turn.set(path, null);
|
|
3840
|
-
else if (
|
|
4167
|
+
else if (statSync5(path).size > MAX_BYTES2) turn.set(path, "skipped");
|
|
3841
4168
|
else turn.set(path, await readFile5(path));
|
|
3842
|
-
while (this.turns() > MAX_TURNS)
|
|
4169
|
+
while (this.turns() > MAX_TURNS) {
|
|
4170
|
+
this.history.shift();
|
|
4171
|
+
this.offset++;
|
|
4172
|
+
}
|
|
3843
4173
|
}
|
|
3844
4174
|
turns() {
|
|
3845
4175
|
return this.history.filter((t2) => t2.size).length;
|
|
3846
4176
|
}
|
|
3847
4177
|
/** Total entries including empty ones; used to align file snapshots with conversation turns. */
|
|
3848
4178
|
entries() {
|
|
3849
|
-
return this.history.length;
|
|
4179
|
+
return this.offset + this.history.length;
|
|
3850
4180
|
}
|
|
3851
4181
|
async undo() {
|
|
3852
4182
|
while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
|
|
@@ -3866,7 +4196,7 @@ var CheckpointStore = class {
|
|
|
3866
4196
|
* pairs conversation turns with the entry count recorded when each turn started.
|
|
3867
4197
|
*/
|
|
3868
4198
|
async undoTo(keep) {
|
|
3869
|
-
const target = Math.max(0, Math.min(keep, this.history.length));
|
|
4199
|
+
const target = Math.max(0, Math.min(keep - this.offset, this.history.length));
|
|
3870
4200
|
const result = emptyResult();
|
|
3871
4201
|
const redo = /* @__PURE__ */ new Map();
|
|
3872
4202
|
let applied = false;
|
|
@@ -3891,8 +4221,11 @@ var CheckpointStore = class {
|
|
|
3891
4221
|
const turn = this.pendingRedo;
|
|
3892
4222
|
if (!turn) return void 0;
|
|
3893
4223
|
this.pendingRedo = void 0;
|
|
4224
|
+
const undoEntry = /* @__PURE__ */ new Map();
|
|
4225
|
+
await this.captureFor(turn, undoEntry);
|
|
3894
4226
|
const result = emptyResult();
|
|
3895
4227
|
await restoreTurn(turn, result);
|
|
4228
|
+
if (undoEntry.size) this.history.push(undoEntry);
|
|
3896
4229
|
return result;
|
|
3897
4230
|
}
|
|
3898
4231
|
/** Records the on-disk state of a turn's paths so redo() can put it back. */
|
|
@@ -3905,7 +4238,7 @@ var CheckpointStore = class {
|
|
|
3905
4238
|
}
|
|
3906
4239
|
try {
|
|
3907
4240
|
if (!existsSync5(path)) into.set(path, null);
|
|
3908
|
-
else if (
|
|
4241
|
+
else if (statSync5(path).size > MAX_BYTES2) into.set(path, "skipped");
|
|
3909
4242
|
else into.set(path, await readFile5(path));
|
|
3910
4243
|
} catch {
|
|
3911
4244
|
into.set(path, "skipped");
|
|
@@ -3916,6 +4249,7 @@ var CheckpointStore = class {
|
|
|
3916
4249
|
this.history.length = 0;
|
|
3917
4250
|
this.current = void 0;
|
|
3918
4251
|
this.pendingRedo = void 0;
|
|
4252
|
+
this.offset = 0;
|
|
3919
4253
|
}
|
|
3920
4254
|
};
|
|
3921
4255
|
|
|
@@ -3964,7 +4298,7 @@ import { z as z9 } from "zod";
|
|
|
3964
4298
|
// src/mcp/config.ts
|
|
3965
4299
|
import { createHash } from "crypto";
|
|
3966
4300
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
3967
|
-
import { dirname as
|
|
4301
|
+
import { dirname as dirname5, join as join5, resolve as resolve4 } from "path";
|
|
3968
4302
|
var ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
3969
4303
|
function expandEnvVars(config, env) {
|
|
3970
4304
|
const expand = (value) => value.replace(ENV_REF, (match, name, fallback) => {
|
|
@@ -3988,9 +4322,9 @@ function expandEnvVars(config, env) {
|
|
|
3988
4322
|
import { spawn as spawn3 } from "child_process";
|
|
3989
4323
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
3990
4324
|
import { createServer } from "http";
|
|
3991
|
-
import { dirname as
|
|
4325
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
3992
4326
|
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
3993
|
-
var oauthStorePath = (home) =>
|
|
4327
|
+
var oauthStorePath = (home) => join6(home, "mcp-oauth.json");
|
|
3994
4328
|
function readStore(home) {
|
|
3995
4329
|
const path = oauthStorePath(home);
|
|
3996
4330
|
if (!existsSync7(path)) return {};
|
|
@@ -4003,7 +4337,7 @@ function readStore(home) {
|
|
|
4003
4337
|
}
|
|
4004
4338
|
function writeStore(home, store) {
|
|
4005
4339
|
const path = oauthStorePath(home);
|
|
4006
|
-
mkdirSync3(
|
|
4340
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
4007
4341
|
writeFileAtomic(path, `${JSON.stringify(store, null, 2)}
|
|
4008
4342
|
`, 384);
|
|
4009
4343
|
}
|
|
@@ -4097,7 +4431,28 @@ var FileOAuthProvider = class {
|
|
|
4097
4431
|
};
|
|
4098
4432
|
|
|
4099
4433
|
// src/mcp/manager.ts
|
|
4100
|
-
|
|
4434
|
+
function formatMcpContent(content) {
|
|
4435
|
+
const parts = [];
|
|
4436
|
+
const images = [];
|
|
4437
|
+
for (const c of content ?? []) {
|
|
4438
|
+
if (c.type === "text") {
|
|
4439
|
+
parts.push(c.text ?? "");
|
|
4440
|
+
} else if (c.type === "image" && c.data) {
|
|
4441
|
+
const mediaType = c.mimeType || "image/png";
|
|
4442
|
+
images.push({ mediaType, data: c.data, path: "mcp:image" });
|
|
4443
|
+
parts.push(`[image attached: ${mediaType}]`);
|
|
4444
|
+
} else if (c.type === "resource" && c.resource?.text) {
|
|
4445
|
+
parts.push(c.resource.uri ? `[${c.resource.uri}]
|
|
4446
|
+
${c.resource.text}` : c.resource.text);
|
|
4447
|
+
} else {
|
|
4448
|
+
parts.push(`[${c.type}]`);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
return {
|
|
4452
|
+
output: parts.join("\n") || "(empty)",
|
|
4453
|
+
...images.length ? { images } : {}
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4101
4456
|
var CHILD_ENV_KEYS = [
|
|
4102
4457
|
// portable
|
|
4103
4458
|
"PATH",
|
|
@@ -4226,11 +4581,13 @@ var McpManager = class {
|
|
|
4226
4581
|
async run(input, ctx) {
|
|
4227
4582
|
try {
|
|
4228
4583
|
const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
|
|
4229
|
-
const
|
|
4584
|
+
const formatted = formatMcpContent(result.content);
|
|
4585
|
+
const output = formatted.output;
|
|
4230
4586
|
return {
|
|
4231
|
-
output: output.length >
|
|
4232
|
-
\u2026 [truncated at
|
|
4233
|
-
isError: !!result.isError
|
|
4587
|
+
output: output.length > 32e3 ? `${output.slice(0, 32e3)}
|
|
4588
|
+
\u2026 [truncated at 32000 characters; narrow the MCP request if possible]` : output,
|
|
4589
|
+
isError: !!result.isError,
|
|
4590
|
+
...formatted.images ? { images: formatted.images } : {}
|
|
4234
4591
|
};
|
|
4235
4592
|
} catch (error) {
|
|
4236
4593
|
return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
|
|
@@ -4264,7 +4621,7 @@ import { homedir as homedir2 } from "os";
|
|
|
4264
4621
|
|
|
4265
4622
|
// src/context.ts
|
|
4266
4623
|
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
4267
|
-
import { dirname as
|
|
4624
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
4268
4625
|
var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
|
|
4269
4626
|
function readIf(path) {
|
|
4270
4627
|
try {
|
|
@@ -4275,11 +4632,11 @@ function readIf(path) {
|
|
|
4275
4632
|
}
|
|
4276
4633
|
function instructionPaths(cwd, home) {
|
|
4277
4634
|
const dirs2 = [];
|
|
4278
|
-
for (let dir = cwd; ; dir =
|
|
4635
|
+
for (let dir = cwd; ; dir = dirname7(dir)) {
|
|
4279
4636
|
dirs2.unshift(dir);
|
|
4280
|
-
if (
|
|
4637
|
+
if (dirname7(dir) === dir) break;
|
|
4281
4638
|
}
|
|
4282
|
-
return [
|
|
4639
|
+
return [join7(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join7(dir, name)))];
|
|
4283
4640
|
}
|
|
4284
4641
|
function loadInstructions(cwd, home) {
|
|
4285
4642
|
const parts = [];
|
|
@@ -4291,9 +4648,9 @@ ${text}`);
|
|
|
4291
4648
|
return parts.join("\n\n");
|
|
4292
4649
|
}
|
|
4293
4650
|
function isGitRepo(cwd) {
|
|
4294
|
-
for (let dir = cwd; ; dir =
|
|
4295
|
-
if (existsSync8(
|
|
4296
|
-
if (
|
|
4651
|
+
for (let dir = cwd; ; dir = dirname7(dir)) {
|
|
4652
|
+
if (existsSync8(join7(dir, ".git"))) return true;
|
|
4653
|
+
if (dirname7(dir) === dir) return false;
|
|
4297
4654
|
}
|
|
4298
4655
|
}
|
|
4299
4656
|
function buildSystemPrompt(opts) {
|
|
@@ -4336,16 +4693,34 @@ ${instructions}` : ""}`;
|
|
|
4336
4693
|
}
|
|
4337
4694
|
|
|
4338
4695
|
// src/extensions.ts
|
|
4339
|
-
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync6, statSync as
|
|
4340
|
-
import { basename, join as
|
|
4696
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync6 } from "fs";
|
|
4697
|
+
import { basename as basename2, join as join8 } from "path";
|
|
4341
4698
|
function parseFrontmatter(text) {
|
|
4342
4699
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
4343
4700
|
if (!match) return { data: {}, body: text };
|
|
4344
4701
|
const data = {};
|
|
4702
|
+
let currentListKey;
|
|
4345
4703
|
for (const line of match[1].split(/\r?\n/)) {
|
|
4704
|
+
const listItem = /^\s*-\s+(.+)$/.exec(line);
|
|
4705
|
+
if (listItem && currentListKey) {
|
|
4706
|
+
const item = listItem[1].trim().replace(/^["']|["']$/g, "");
|
|
4707
|
+
const prev = data[currentListKey];
|
|
4708
|
+
if (Array.isArray(prev)) prev.push(item);
|
|
4709
|
+
else data[currentListKey] = [item];
|
|
4710
|
+
continue;
|
|
4711
|
+
}
|
|
4346
4712
|
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
4347
|
-
if (!kv)
|
|
4713
|
+
if (!kv) {
|
|
4714
|
+
if (line.trim()) currentListKey = void 0;
|
|
4715
|
+
continue;
|
|
4716
|
+
}
|
|
4348
4717
|
let value = kv[2].trim();
|
|
4718
|
+
if (!value) {
|
|
4719
|
+
currentListKey = kv[1];
|
|
4720
|
+
data[kv[1]] = "";
|
|
4721
|
+
continue;
|
|
4722
|
+
}
|
|
4723
|
+
currentListKey = void 0;
|
|
4349
4724
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
4350
4725
|
data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
4351
4726
|
continue;
|
|
@@ -4357,11 +4732,11 @@ function parseFrontmatter(text) {
|
|
|
4357
4732
|
}
|
|
4358
4733
|
var str = (v) => Array.isArray(v) ? v.join(", ") : v;
|
|
4359
4734
|
function dirs(r, kind) {
|
|
4360
|
-
return [
|
|
4735
|
+
return [join8(r.userHome, ".claude", kind), join8(r.home, kind), join8(r.cwd, ".claude", kind), join8(r.cwd, ".bccli", kind)];
|
|
4361
4736
|
}
|
|
4362
4737
|
function list(dir) {
|
|
4363
4738
|
try {
|
|
4364
|
-
return
|
|
4739
|
+
return readdirSync2(dir).sort();
|
|
4365
4740
|
} catch {
|
|
4366
4741
|
return [];
|
|
4367
4742
|
}
|
|
@@ -4370,11 +4745,11 @@ function loadSkills(r) {
|
|
|
4370
4745
|
const byName = /* @__PURE__ */ new Map();
|
|
4371
4746
|
for (const dir of dirs(r, "skills")) {
|
|
4372
4747
|
for (const entry of list(dir)) {
|
|
4373
|
-
const file =
|
|
4748
|
+
const file = join8(dir, entry, "SKILL.md");
|
|
4374
4749
|
if (!existsSync9(file)) continue;
|
|
4375
4750
|
const { data } = parseFrontmatter(readFileSync6(file, "utf8"));
|
|
4376
4751
|
const name = str(data.name) || entry;
|
|
4377
|
-
byName.set(name, { name, description: str(data.description) ?? "", dir:
|
|
4752
|
+
byName.set(name, { name, description: str(data.description) ?? "", dir: join8(dir, entry), file });
|
|
4378
4753
|
}
|
|
4379
4754
|
}
|
|
4380
4755
|
return [...byName.values()];
|
|
@@ -4383,10 +4758,10 @@ function loadCommands(r) {
|
|
|
4383
4758
|
const byName = /* @__PURE__ */ new Map();
|
|
4384
4759
|
for (const dir of dirs(r, "commands")) {
|
|
4385
4760
|
for (const entry of list(dir)) {
|
|
4386
|
-
const file =
|
|
4387
|
-
if (!entry.endsWith(".md") || !
|
|
4761
|
+
const file = join8(dir, entry);
|
|
4762
|
+
if (!entry.endsWith(".md") || !statSync6(file).isFile()) continue;
|
|
4388
4763
|
const { data, body } = parseFrontmatter(readFileSync6(file, "utf8"));
|
|
4389
|
-
const def = { name:
|
|
4764
|
+
const def = { name: basename2(entry, ".md"), body: body.trim() };
|
|
4390
4765
|
if (data.description) def.description = str(data.description);
|
|
4391
4766
|
if (data["argument-hint"]) def.argumentHint = str(data["argument-hint"]);
|
|
4392
4767
|
byName.set(def.name, def);
|
|
@@ -4399,8 +4774,8 @@ function loadAgentDefs(r) {
|
|
|
4399
4774
|
for (const dir of dirs(r, "agents")) {
|
|
4400
4775
|
for (const entry of list(dir)) {
|
|
4401
4776
|
if (!entry.endsWith(".md")) continue;
|
|
4402
|
-
const { data, body } = parseFrontmatter(readFileSync6(
|
|
4403
|
-
const name = str(data.name) ||
|
|
4777
|
+
const { data, body } = parseFrontmatter(readFileSync6(join8(dir, entry), "utf8"));
|
|
4778
|
+
const name = str(data.name) || basename2(entry, ".md");
|
|
4404
4779
|
const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
|
|
4405
4780
|
if (data.tools)
|
|
4406
4781
|
def.tools = Array.isArray(data.tools) ? data.tools : String(data.tools).split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -4418,8 +4793,7 @@ function loadAgentDefs(r) {
|
|
|
4418
4793
|
}
|
|
4419
4794
|
|
|
4420
4795
|
// src/providers.ts
|
|
4421
|
-
import {
|
|
4422
|
-
import { join as join7 } from "path";
|
|
4796
|
+
import { join as join9 } from "path";
|
|
4423
4797
|
function providerName(config, id) {
|
|
4424
4798
|
return PRESETS.find((p) => p.id === id)?.name ?? config.providers[id]?.name ?? id;
|
|
4425
4799
|
}
|
|
@@ -4478,11 +4852,11 @@ async function addProviderKey(providerId, key, deps) {
|
|
|
4478
4852
|
|
|
4479
4853
|
// src/session.ts
|
|
4480
4854
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
4481
|
-
import { appendFileSync, existsSync as existsSync10, mkdirSync as
|
|
4482
|
-
import { join as
|
|
4855
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync2, statSync as statSync7 } from "fs";
|
|
4856
|
+
import { basename as basename3, join as join10 } from "path";
|
|
4483
4857
|
function projectDir(home, cwd) {
|
|
4484
4858
|
const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-100) || "root";
|
|
4485
|
-
return
|
|
4859
|
+
return join10(home, "sessions", slug);
|
|
4486
4860
|
}
|
|
4487
4861
|
function repairTail(messages) {
|
|
4488
4862
|
const out = [...messages];
|
|
@@ -4500,19 +4874,22 @@ var Session = class _Session {
|
|
|
4500
4874
|
this.file = file;
|
|
4501
4875
|
}
|
|
4502
4876
|
file;
|
|
4877
|
+
get id() {
|
|
4878
|
+
return basename3(this.file, ".jsonl");
|
|
4879
|
+
}
|
|
4503
4880
|
static create(home, cwd, now = /* @__PURE__ */ new Date()) {
|
|
4504
4881
|
const dir = projectDir(home, cwd);
|
|
4505
|
-
|
|
4882
|
+
mkdirSync4(dir, { recursive: true });
|
|
4506
4883
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
4507
|
-
return new _Session(
|
|
4884
|
+
return new _Session(join10(dir, `${stamp}-${randomBytes2(3).toString("hex")}.jsonl`));
|
|
4508
4885
|
}
|
|
4509
4886
|
static list(home, cwd) {
|
|
4510
4887
|
const dir = projectDir(home, cwd);
|
|
4511
4888
|
if (!existsSync10(dir)) return [];
|
|
4512
|
-
return
|
|
4513
|
-
const session = new _Session(
|
|
4514
|
-
const first = session.
|
|
4515
|
-
return { session, mtime:
|
|
4889
|
+
return readdirSync3(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
4890
|
+
const session = new _Session(join10(dir, f));
|
|
4891
|
+
const first = session.loadHistory().find((m) => m.role === "user");
|
|
4892
|
+
return { session, mtime: statSync7(session.file).mtime, preview: first ? contentText(first.content).slice(0, 80) : "" };
|
|
4516
4893
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime() || b.session.file.localeCompare(a.session.file));
|
|
4517
4894
|
}
|
|
4518
4895
|
static latest(home, cwd) {
|
|
@@ -4561,6 +4938,35 @@ var Session = class _Session {
|
|
|
4561
4938
|
}
|
|
4562
4939
|
return void 0;
|
|
4563
4940
|
}
|
|
4941
|
+
/** Accumulated token usage since the last non-compaction reset. */
|
|
4942
|
+
totalUsage() {
|
|
4943
|
+
const segments = [{ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } }];
|
|
4944
|
+
for (const record of this.records()) {
|
|
4945
|
+
const cur = segments[segments.length - 1];
|
|
4946
|
+
if (record.t === "reset") {
|
|
4947
|
+
segments.push({ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } });
|
|
4948
|
+
} else if (record.t === "msg") {
|
|
4949
|
+
cur.msgs.push(record.m);
|
|
4950
|
+
} else if (record.t === "usage") {
|
|
4951
|
+
cur.usage.inputTokens += record.inputTokens;
|
|
4952
|
+
cur.usage.outputTokens += record.outputTokens;
|
|
4953
|
+
if (record.cachedInputTokens) cur.usage.cachedInputTokens = (cur.usage.cachedInputTokens ?? 0) + record.cachedInputTokens;
|
|
4954
|
+
if (record.cacheWriteTokens) cur.usage.cacheWriteTokens = (cur.usage.cacheWriteTokens ?? 0) + record.cacheWriteTokens;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
let total = { ...segments[0].usage };
|
|
4958
|
+
for (const seg of segments.slice(1)) {
|
|
4959
|
+
if (!isCompactionSummary(seg.msgs[0])) {
|
|
4960
|
+
total = { ...seg.usage };
|
|
4961
|
+
} else {
|
|
4962
|
+
total.inputTokens += seg.usage.inputTokens;
|
|
4963
|
+
total.outputTokens += seg.usage.outputTokens;
|
|
4964
|
+
if (seg.usage.cachedInputTokens) total.cachedInputTokens = (total.cachedInputTokens ?? 0) + seg.usage.cachedInputTokens;
|
|
4965
|
+
if (seg.usage.cacheWriteTokens) total.cacheWriteTokens = (total.cacheWriteTokens ?? 0) + seg.usage.cacheWriteTokens;
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
return total;
|
|
4969
|
+
}
|
|
4564
4970
|
/**
|
|
4565
4971
|
* What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
|
|
4566
4972
|
* its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
|
|
@@ -4573,12 +4979,22 @@ var Session = class _Session {
|
|
|
4573
4979
|
else if (record.t === "msg") segments[segments.length - 1].push(record.m);
|
|
4574
4980
|
}
|
|
4575
4981
|
let shown = segments[0];
|
|
4982
|
+
let lastSummary;
|
|
4576
4983
|
for (const segment of segments.slice(1)) {
|
|
4577
4984
|
if (!isCompactionSummary(segment[0])) {
|
|
4578
4985
|
shown = segment;
|
|
4986
|
+
lastSummary = void 0;
|
|
4579
4987
|
continue;
|
|
4580
4988
|
}
|
|
4581
4989
|
const kept = segment.slice(2);
|
|
4990
|
+
if (lastSummary && sameMessage2(segment[0], lastSummary)) {
|
|
4991
|
+
const subIdx = findContiguousSlice(shown, kept);
|
|
4992
|
+
if (subIdx !== -1) {
|
|
4993
|
+
shown = shown.slice(0, subIdx + kept.length);
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
lastSummary = segment[0];
|
|
4582
4998
|
let at = 0;
|
|
4583
4999
|
while (at <= shown.length && !endsWithPrefixOf(shown, at, kept)) at++;
|
|
4584
5000
|
shown = [...shown, ...kept.slice(shown.length - at)];
|
|
@@ -4605,6 +5021,20 @@ function endsWithPrefixOf(shown, at, kept) {
|
|
|
4605
5021
|
for (let j = 0; j < n; j++) if (!sameMessage2(shown[at + j], kept[j])) return false;
|
|
4606
5022
|
return true;
|
|
4607
5023
|
}
|
|
5024
|
+
function findContiguousSlice(shown, slice) {
|
|
5025
|
+
if (slice.length === 0) return shown.length;
|
|
5026
|
+
for (let i = 0; i + slice.length <= shown.length; i++) {
|
|
5027
|
+
let ok = true;
|
|
5028
|
+
for (let j = 0; j < slice.length; j++) {
|
|
5029
|
+
if (!sameMessage2(shown[i + j], slice[j])) {
|
|
5030
|
+
ok = false;
|
|
5031
|
+
break;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
if (ok) return i;
|
|
5035
|
+
}
|
|
5036
|
+
return -1;
|
|
5037
|
+
}
|
|
4608
5038
|
|
|
4609
5039
|
// src/tools/plan.ts
|
|
4610
5040
|
import { z as z10 } from "zod";
|
|
@@ -4714,6 +5144,7 @@ import { z as z13 } from "zod";
|
|
|
4714
5144
|
function createTaskTool(opts) {
|
|
4715
5145
|
const byName = new Map(opts.agents.map((a) => [a.name, a]));
|
|
4716
5146
|
const list2 = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
5147
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4717
5148
|
let askChain = Promise.resolve();
|
|
4718
5149
|
const enqueueAsk = (fn) => {
|
|
4719
5150
|
const next = askChain.then(
|
|
@@ -4733,7 +5164,8 @@ ${list2}`,
|
|
|
4733
5164
|
schema: z13.object({
|
|
4734
5165
|
agent: z13.string().describe("Agent name"),
|
|
4735
5166
|
description: z13.string().describe("3-6 word summary shown to the user"),
|
|
4736
|
-
prompt: z13.string().describe("Full instructions; the subagent sees nothing else")
|
|
5167
|
+
prompt: z13.string().describe("Full instructions; the subagent sees nothing else"),
|
|
5168
|
+
session_id: z13.string().optional().describe("Optional session id to continue a previous subagent conversation")
|
|
4737
5169
|
}),
|
|
4738
5170
|
kind: "read",
|
|
4739
5171
|
target: (input) => `[${input.agent}] ${input.description}`,
|
|
@@ -4760,6 +5192,7 @@ ${list2}`,
|
|
|
4760
5192
|
} catch (error) {
|
|
4761
5193
|
return { output: `Agent "${def.name}": ${error.message}`, isError: true };
|
|
4762
5194
|
}
|
|
5195
|
+
const priorHistory = input.session_id ? sessions.get(input.session_id) : void 0;
|
|
4763
5196
|
const child = new Agent2({
|
|
4764
5197
|
provider,
|
|
4765
5198
|
tools,
|
|
@@ -4769,6 +5202,7 @@ ${list2}`,
|
|
|
4769
5202
|
# Your role
|
|
4770
5203
|
${def.prompt}`,
|
|
4771
5204
|
cwd: opts.cwd,
|
|
5205
|
+
...priorHistory ? { history: priorHistory } : {},
|
|
4772
5206
|
// Every subagent runs with a step cap so a looping child cannot burn tokens forever.
|
|
4773
5207
|
maxSteps: def.maxSteps ?? 50,
|
|
4774
5208
|
label: def.name,
|
|
@@ -4789,12 +5223,29 @@ ${def.prompt}`,
|
|
|
4789
5223
|
const ask = ctx.ask;
|
|
4790
5224
|
if (ask) child.askPermission = (req) => enqueueAsk(() => ask(req));
|
|
4791
5225
|
await child.run(input.prompt, ctx.signal);
|
|
5226
|
+
if (input.session_id) sessions.set(input.session_id, [...child.messages]);
|
|
4792
5227
|
ctx.addUsage?.(child.totalUsage);
|
|
4793
5228
|
const tokens2 = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
|
|
4794
5229
|
const display = t("{steps} steps \xB7 {tokens} tokens", { steps, tokens: tokens2 >= 1e3 ? `${Math.round(tokens2 / 1e3)}k` : tokens2 });
|
|
4795
5230
|
if (outcome?.type === "error") return { output: `Subagent ${def.name} failed: ${outcome.message}`, isError: true, display };
|
|
4796
5231
|
if (outcome?.type === "aborted") return { output: `Subagent ${def.name} was cancelled.`, isError: true, display };
|
|
4797
|
-
|
|
5232
|
+
let last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
|
|
5233
|
+
if (!last && outcome?.type === "stepLimit" && !ctx.signal.aborted) {
|
|
5234
|
+
try {
|
|
5235
|
+
const synthesis = await provider.chat({
|
|
5236
|
+
messages: [
|
|
5237
|
+
...child.messages,
|
|
5238
|
+
{ role: "user", content: "Step limit reached. Summarize all findings gathered so far concisely." }
|
|
5239
|
+
],
|
|
5240
|
+
signal: ctx.signal,
|
|
5241
|
+
reasoning: opts.reasoning?.() ?? "auto"
|
|
5242
|
+
});
|
|
5243
|
+
if (synthesis.text.trim()) {
|
|
5244
|
+
last = { role: "assistant", content: synthesis.text.trim() };
|
|
5245
|
+
}
|
|
5246
|
+
} catch {
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
4798
5249
|
const report = last && typeof last.content === "string" ? last.content : "(no report)";
|
|
4799
5250
|
const suffix = outcome?.type === "stepLimit" ? `
|
|
4800
5251
|
|
|
@@ -4840,7 +5291,7 @@ function createTodoTool(store) {
|
|
|
4840
5291
|
|
|
4841
5292
|
// src/tools/memory.ts
|
|
4842
5293
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
4843
|
-
import { join as
|
|
5294
|
+
import { join as join11 } from "path";
|
|
4844
5295
|
import { z as z15 } from "zod";
|
|
4845
5296
|
|
|
4846
5297
|
// src/slash/memory.ts
|
|
@@ -4863,11 +5314,11 @@ function createSaveMemoryTool(opts) {
|
|
|
4863
5314
|
kind: "edit",
|
|
4864
5315
|
// The target is what the prompt shows and what the permission model judges: the
|
|
4865
5316
|
// global store really lives outside the project, so it must not look like one.
|
|
4866
|
-
target: (input) => input.scope === "global" ?
|
|
5317
|
+
target: (input) => input.scope === "global" ? join11(opts.home, "BCCLI.md") : "AGENTS.md",
|
|
4867
5318
|
async run(input, ctx) {
|
|
4868
5319
|
const content = input.content.trim();
|
|
4869
5320
|
if (!content) return { output: "Memory content is empty.", isError: true };
|
|
4870
|
-
const file = input.scope === "global" ?
|
|
5321
|
+
const file = input.scope === "global" ? join11(opts.home, "BCCLI.md") : join11(ctx.cwd, "AGENTS.md");
|
|
4871
5322
|
if (existsSync12(file)) {
|
|
4872
5323
|
const lines = readFileSync10(file, "utf8").split("\n");
|
|
4873
5324
|
if (lines.some((line) => line.trim() === `- ${content}`)) {
|
|
@@ -4885,18 +5336,21 @@ function createSaveMemoryTool(opts) {
|
|
|
4885
5336
|
import { z as z16 } from "zod";
|
|
4886
5337
|
|
|
4887
5338
|
// src/repomap.ts
|
|
4888
|
-
import { readdirSync as
|
|
4889
|
-
import { extname, join as
|
|
5339
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
5340
|
+
import { extname, join as join12, relative as relative2, sep } from "path";
|
|
4890
5341
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
4891
5342
|
"node_modules",
|
|
4892
5343
|
".git",
|
|
4893
5344
|
"dist",
|
|
4894
5345
|
"build",
|
|
5346
|
+
"out",
|
|
4895
5347
|
"coverage",
|
|
4896
5348
|
".next",
|
|
5349
|
+
".turbo",
|
|
4897
5350
|
"target",
|
|
4898
5351
|
"vendor",
|
|
4899
5352
|
"__pycache__",
|
|
5353
|
+
"venv",
|
|
4900
5354
|
".venv",
|
|
4901
5355
|
".cache"
|
|
4902
5356
|
]);
|
|
@@ -4922,21 +5376,22 @@ var CODE_EXTS = /* @__PURE__ */ new Set([
|
|
|
4922
5376
|
".ps1"
|
|
4923
5377
|
]);
|
|
4924
5378
|
var MAX_FILES = 400;
|
|
5379
|
+
var MAX_SCAN_FILES = 5e3;
|
|
4925
5380
|
var MAX_FILE_BYTES = 2e5;
|
|
4926
5381
|
var MAX_SYMBOLS_PER_FILE = 12;
|
|
4927
5382
|
var FAMILY = {
|
|
4928
5383
|
ts: [
|
|
4929
|
-
/^\s*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+([A-Za-z0-9_$]+)/,
|
|
5384
|
+
/^\s*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:abstract\s+|final\s+|sealed\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+([A-Za-z0-9_$]+)/,
|
|
4930
5385
|
/^\s*export\s+(?:const|let)\s+([A-Za-z0-9_$]+)/
|
|
4931
5386
|
],
|
|
4932
|
-
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*class\s+(\w+)/],
|
|
5387
|
+
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*(?:module|class)\s+(\w+)/],
|
|
4933
5388
|
go: [/^func\s+(?:\([^)]*\)\s*)?(\w+)/, /^type\s+(\w+)/],
|
|
4934
5389
|
rs: [/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?(?:async\s+)?(?:fn|struct|enum|trait|type)\s+(\w+)/],
|
|
4935
5390
|
c: [/^\s*(?:static\s+|inline\s+|extern\s+)*[A-Za-z_][A-Za-z0-9_\s*]*\b(\w+)\s*\([^;]*\)\s*\{?\s*$/]
|
|
4936
5391
|
};
|
|
4937
5392
|
function familyFor(ext) {
|
|
4938
|
-
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"
|
|
4939
|
-
if (ext === ".py" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
5393
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"].includes(ext)) return FAMILY.ts;
|
|
5394
|
+
if (ext === ".py" || ext === ".rb" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
4940
5395
|
if (ext === ".go") return FAMILY.go;
|
|
4941
5396
|
if (ext === ".rs") return FAMILY.rs;
|
|
4942
5397
|
return FAMILY.c;
|
|
@@ -4956,16 +5411,16 @@ function extractSymbols(text, ext) {
|
|
|
4956
5411
|
return out;
|
|
4957
5412
|
}
|
|
4958
5413
|
function walk(dir, root, files) {
|
|
4959
|
-
if (files.length >=
|
|
5414
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
4960
5415
|
let entries;
|
|
4961
5416
|
try {
|
|
4962
|
-
entries =
|
|
5417
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
4963
5418
|
} catch {
|
|
4964
5419
|
return;
|
|
4965
5420
|
}
|
|
4966
5421
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4967
|
-
if (files.length >=
|
|
4968
|
-
const full =
|
|
5422
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
5423
|
+
const full = join12(dir, e.name);
|
|
4969
5424
|
if (e.isDirectory()) {
|
|
4970
5425
|
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".") && full !== root) continue;
|
|
4971
5426
|
walk(full, root, files);
|
|
@@ -4976,16 +5431,17 @@ function walk(dir, root, files) {
|
|
|
4976
5431
|
}
|
|
4977
5432
|
function buildRepoMapSync(cwd, opts = {}) {
|
|
4978
5433
|
const maxTokens = opts.maxTokens ?? 2048;
|
|
4979
|
-
const
|
|
4980
|
-
walk(cwd, cwd,
|
|
4981
|
-
|
|
4982
|
-
(a, b) => relative2(cwd, a).split(
|
|
5434
|
+
const scanned = [];
|
|
5435
|
+
walk(cwd, cwd, scanned);
|
|
5436
|
+
scanned.sort(
|
|
5437
|
+
(a, b) => relative2(cwd, a).split(sep).length - relative2(cwd, b).split(sep).length || a.localeCompare(b)
|
|
4983
5438
|
);
|
|
5439
|
+
const files = scanned.slice(0, MAX_FILES);
|
|
4984
5440
|
const lines = [];
|
|
4985
5441
|
let used = 0;
|
|
4986
5442
|
let truncated = false;
|
|
4987
5443
|
for (const file of files) {
|
|
4988
|
-
const rel = relative2(cwd, file).split(
|
|
5444
|
+
const rel = relative2(cwd, file).split(sep).join("/");
|
|
4989
5445
|
let symbols = [];
|
|
4990
5446
|
try {
|
|
4991
5447
|
const text = readFileSync11(file, "utf8");
|
|
@@ -5020,13 +5476,50 @@ function createRepoMapTool() {
|
|
|
5020
5476
|
}
|
|
5021
5477
|
|
|
5022
5478
|
// src/tools/diagnostics.ts
|
|
5023
|
-
import { extname as
|
|
5479
|
+
import { extname as extname3 } from "path";
|
|
5024
5480
|
import { z as z17 } from "zod";
|
|
5025
5481
|
|
|
5026
5482
|
// src/lsp.ts
|
|
5027
5483
|
import { spawn as spawn4 } from "child_process";
|
|
5028
5484
|
import { readFileSync as readFileSync12 } from "fs";
|
|
5485
|
+
import { extname as extname2 } from "path";
|
|
5029
5486
|
import { pathToFileURL } from "url";
|
|
5487
|
+
var LANGUAGE_IDS = {
|
|
5488
|
+
".ts": "typescript",
|
|
5489
|
+
".tsx": "typescriptreact",
|
|
5490
|
+
".mts": "typescript",
|
|
5491
|
+
".cts": "typescript",
|
|
5492
|
+
".js": "javascript",
|
|
5493
|
+
".jsx": "javascriptreact",
|
|
5494
|
+
".mjs": "javascript",
|
|
5495
|
+
".cjs": "javascript",
|
|
5496
|
+
".py": "python",
|
|
5497
|
+
".rs": "rust",
|
|
5498
|
+
".go": "go",
|
|
5499
|
+
".java": "java",
|
|
5500
|
+
".c": "c",
|
|
5501
|
+
".h": "c",
|
|
5502
|
+
".cpp": "cpp",
|
|
5503
|
+
".hpp": "cpp",
|
|
5504
|
+
".cs": "csharp",
|
|
5505
|
+
".rb": "ruby",
|
|
5506
|
+
".php": "php",
|
|
5507
|
+
".json": "json",
|
|
5508
|
+
".md": "markdown",
|
|
5509
|
+
".sh": "shellscript",
|
|
5510
|
+
".ps1": "powershell",
|
|
5511
|
+
".yaml": "yaml",
|
|
5512
|
+
".yml": "yaml",
|
|
5513
|
+
".toml": "toml",
|
|
5514
|
+
".html": "html",
|
|
5515
|
+
".css": "css"
|
|
5516
|
+
};
|
|
5517
|
+
function languageIdFor(file) {
|
|
5518
|
+
return LANGUAGE_IDS[extname2(file).toLowerCase()] ?? "plaintext";
|
|
5519
|
+
}
|
|
5520
|
+
function useShellForLsp(command, platform = process.platform) {
|
|
5521
|
+
return platform === "win32" && !/\.(exe|mjs|cjs|js)$/i.test(command.trim());
|
|
5522
|
+
}
|
|
5030
5523
|
function encodeLsp(msg) {
|
|
5031
5524
|
const body = Buffer.from(JSON.stringify(msg), "utf8");
|
|
5032
5525
|
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r
|
|
@@ -5059,8 +5552,12 @@ function createLspParser(onMessage) {
|
|
|
5059
5552
|
};
|
|
5060
5553
|
}
|
|
5061
5554
|
function getDiagnostics(opts) {
|
|
5062
|
-
return new Promise((
|
|
5063
|
-
const child = spawn4(opts.server.command, opts.server.args ?? [], {
|
|
5555
|
+
return new Promise((resolve5) => {
|
|
5556
|
+
const child = spawn4(opts.server.command, opts.server.args ?? [], {
|
|
5557
|
+
cwd: opts.cwd,
|
|
5558
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
5559
|
+
shell: useShellForLsp(opts.server.command)
|
|
5560
|
+
});
|
|
5064
5561
|
const uri = pathToFileURL(opts.file).href;
|
|
5065
5562
|
let settled = false;
|
|
5066
5563
|
let nextId = 2;
|
|
@@ -5074,6 +5571,7 @@ function getDiagnostics(opts) {
|
|
|
5074
5571
|
if (settled) return;
|
|
5075
5572
|
settled = true;
|
|
5076
5573
|
clearTimeout(timer);
|
|
5574
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
5077
5575
|
send({ jsonrpc: "2.0", id: nextId++, method: "shutdown" });
|
|
5078
5576
|
send({ jsonrpc: "2.0", method: "exit" });
|
|
5079
5577
|
const killer = setTimeout(() => {
|
|
@@ -5083,8 +5581,11 @@ function getDiagnostics(opts) {
|
|
|
5083
5581
|
}
|
|
5084
5582
|
}, 500);
|
|
5085
5583
|
killer.unref?.();
|
|
5086
|
-
|
|
5584
|
+
resolve5(diags);
|
|
5087
5585
|
};
|
|
5586
|
+
const onAbort = () => finish([]);
|
|
5587
|
+
if (opts.signal?.aborted) return finish([]);
|
|
5588
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5088
5589
|
const timer = setTimeout(() => finish([]), opts.timeoutMs ?? 8e3);
|
|
5089
5590
|
const parser = createLspParser((msg) => {
|
|
5090
5591
|
if (msg.id === 1 && msg.result) {
|
|
@@ -5097,7 +5598,7 @@ function getDiagnostics(opts) {
|
|
|
5097
5598
|
send({
|
|
5098
5599
|
jsonrpc: "2.0",
|
|
5099
5600
|
method: "textDocument/didOpen",
|
|
5100
|
-
params: { textDocument: { uri, languageId:
|
|
5601
|
+
params: { textDocument: { uri, languageId: languageIdFor(opts.file), version: 1, text } }
|
|
5101
5602
|
});
|
|
5102
5603
|
}
|
|
5103
5604
|
if (msg.method === "textDocument/publishDiagnostics" && msg.params?.uri === uri) {
|
|
@@ -5135,7 +5636,7 @@ function createDiagnosticsTool(servers) {
|
|
|
5135
5636
|
target: (input) => input.path,
|
|
5136
5637
|
async run(input, ctx) {
|
|
5137
5638
|
const file = resolvePath(ctx.cwd, input.path);
|
|
5138
|
-
const ext =
|
|
5639
|
+
const ext = extname3(file).toLowerCase();
|
|
5139
5640
|
const server = servers.find((s) => s.extensions.includes(ext));
|
|
5140
5641
|
if (!server) {
|
|
5141
5642
|
return { output: t("No LSP server is configured for {ext} files.", { ext: ext || "(none)" }), isError: true };
|
|
@@ -5151,7 +5652,7 @@ function createDiagnosticsTool(servers) {
|
|
|
5151
5652
|
// src/plugins.ts
|
|
5152
5653
|
import { existsSync as existsSync13 } from "fs";
|
|
5153
5654
|
import { createRequire } from "module";
|
|
5154
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5655
|
+
import { isAbsolute as isAbsolute3, join as join13 } from "path";
|
|
5155
5656
|
import { z as z18 } from "zod";
|
|
5156
5657
|
var require_ = createRequire(import.meta.url);
|
|
5157
5658
|
function pluginLog(message) {
|
|
@@ -5176,7 +5677,7 @@ function loadPlugins(specs, opts) {
|
|
|
5176
5677
|
for (const spec of specs) {
|
|
5177
5678
|
let file;
|
|
5178
5679
|
try {
|
|
5179
|
-
file = isAbsolute3(spec) ? spec : spec.startsWith(".") ?
|
|
5680
|
+
file = isAbsolute3(spec) ? spec : spec.startsWith(".") ? join13(opts.home, spec) : require_.resolve(spec);
|
|
5180
5681
|
} catch {
|
|
5181
5682
|
throw new ConfigError(t("Plugin not found: {spec}", { spec }));
|
|
5182
5683
|
}
|
|
@@ -5253,7 +5754,7 @@ function createRuntime(opts) {
|
|
|
5253
5754
|
let config = loadConfig(opts.cwd, env);
|
|
5254
5755
|
let modelRef = opts.args.model ?? config.model;
|
|
5255
5756
|
let reasoning = opts.args.reasoning ?? config.reasoning;
|
|
5256
|
-
const
|
|
5757
|
+
const makeSingleProvider = (ref) => {
|
|
5257
5758
|
const resolved = resolveModel(config, ref, env);
|
|
5258
5759
|
return opts.provider ?? createProvider({
|
|
5259
5760
|
baseURL: resolved.baseURL,
|
|
@@ -5265,6 +5766,25 @@ function createRuntime(opts) {
|
|
|
5265
5766
|
enableProgrammaticToolCalling: lunaPtcAutoEnabled(env)
|
|
5266
5767
|
});
|
|
5267
5768
|
};
|
|
5769
|
+
const makeProvider = (ref) => {
|
|
5770
|
+
const primary = makeSingleProvider(ref);
|
|
5771
|
+
if (opts.provider) return primary;
|
|
5772
|
+
const fallbackRefs = [
|
|
5773
|
+
...opts.args.fallbackModel ? [opts.args.fallbackModel] : [],
|
|
5774
|
+
...config.fallbackModels ?? []
|
|
5775
|
+
].filter((r) => r && r !== ref);
|
|
5776
|
+
if (!fallbackRefs.length) return primary;
|
|
5777
|
+
return withFallbackProvider(
|
|
5778
|
+
primary,
|
|
5779
|
+
() => fallbackRefs.flatMap((fbRef) => {
|
|
5780
|
+
try {
|
|
5781
|
+
return [makeSingleProvider(fbRef)];
|
|
5782
|
+
} catch {
|
|
5783
|
+
return [];
|
|
5784
|
+
}
|
|
5785
|
+
})
|
|
5786
|
+
);
|
|
5787
|
+
};
|
|
5268
5788
|
const provider = makeProvider(modelRef);
|
|
5269
5789
|
const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
|
|
5270
5790
|
const rules = [...config.allow, ...opts.args.allowedTools.map((t2) => TOOL_RULES[t2] ?? t2)];
|
|
@@ -5286,6 +5806,10 @@ function createRuntime(opts) {
|
|
|
5286
5806
|
if (latest) {
|
|
5287
5807
|
session = latest;
|
|
5288
5808
|
history = latest.load();
|
|
5809
|
+
if (!opts.args.model) {
|
|
5810
|
+
const prevModel = latest.lastModel();
|
|
5811
|
+
if (prevModel) modelRef = prevModel;
|
|
5812
|
+
}
|
|
5289
5813
|
}
|
|
5290
5814
|
}
|
|
5291
5815
|
const repoMap = config.repoMap ? buildRepoMapSync(opts.cwd) : void 0;
|
|
@@ -5329,7 +5853,7 @@ function createRuntime(opts) {
|
|
|
5329
5853
|
// Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
|
|
5330
5854
|
baseTools: () => agent.tools.filter((t2) => t2.name !== "task"),
|
|
5331
5855
|
permissions,
|
|
5332
|
-
provider: () => agent.provider,
|
|
5856
|
+
provider: () => opts.provider ? agent.provider : makeProvider(modelRef),
|
|
5333
5857
|
providerFor: makeProvider,
|
|
5334
5858
|
systemPrompt: (childModelRef) => systemFor(childModelRef ?? modelRef),
|
|
5335
5859
|
cwd: opts.cwd,
|
|
@@ -5415,6 +5939,20 @@ function createRuntime(opts) {
|
|
|
5415
5939
|
session = previous;
|
|
5416
5940
|
agent.load(previous.load());
|
|
5417
5941
|
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
5942
|
+
if (!opts.args.model) {
|
|
5943
|
+
const prevModel = previous.lastModel();
|
|
5944
|
+
if (prevModel) {
|
|
5945
|
+
try {
|
|
5946
|
+
const nextProvider = makeProvider(prevModel);
|
|
5947
|
+
modelRef = prevModel;
|
|
5948
|
+
agent.provider = nextProvider;
|
|
5949
|
+
agent.setBudgetModel(prevModel);
|
|
5950
|
+
agent.setContextWindow(contextWindowFor(prevModel, env));
|
|
5951
|
+
agent.setSystemPrompt(systemFor(modelRef));
|
|
5952
|
+
} catch {
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5418
5956
|
todos.set([]);
|
|
5419
5957
|
checkpoints.clear();
|
|
5420
5958
|
turnMarks.length = 0;
|
|
@@ -5463,6 +6001,7 @@ function createRuntime(opts) {
|
|
|
5463
6001
|
const nextProvider = makeProvider(ref);
|
|
5464
6002
|
modelRef = ref;
|
|
5465
6003
|
agent.provider = nextProvider;
|
|
6004
|
+
agent.setBudgetModel(ref);
|
|
5466
6005
|
agent.setContextWindow(contextWindowFor(ref, env));
|
|
5467
6006
|
agent.setSystemPrompt(systemFor(modelRef));
|
|
5468
6007
|
},
|
|
@@ -5488,6 +6027,7 @@ async function* streamTask(opts) {
|
|
|
5488
6027
|
let finished = false;
|
|
5489
6028
|
let text = "";
|
|
5490
6029
|
let stopReason = "done";
|
|
6030
|
+
let errorMessage2;
|
|
5491
6031
|
const usage = { inputTokens: 0, outputTokens: 0 };
|
|
5492
6032
|
const toolCalls = [];
|
|
5493
6033
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -5537,28 +6077,45 @@ async function* streamTask(opts) {
|
|
|
5537
6077
|
case "aborted":
|
|
5538
6078
|
stopReason = "aborted";
|
|
5539
6079
|
break;
|
|
6080
|
+
case "error":
|
|
6081
|
+
stopReason = "error";
|
|
6082
|
+
errorMessage2 = event.message;
|
|
6083
|
+
push({ type: "error", message: event.message });
|
|
6084
|
+
break;
|
|
5540
6085
|
default:
|
|
5541
6086
|
break;
|
|
5542
6087
|
}
|
|
5543
6088
|
};
|
|
5544
|
-
const
|
|
5545
|
-
|
|
6089
|
+
const controller = new AbortController();
|
|
6090
|
+
const onParentAbort = () => controller.abort();
|
|
6091
|
+
if (opts.signal) {
|
|
6092
|
+
if (opts.signal.aborted) controller.abort();
|
|
6093
|
+
else opts.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
6094
|
+
}
|
|
6095
|
+
const running = rt.agent.run(opts.prompt, controller.signal).catch((err) => {
|
|
6096
|
+
stopReason = "error";
|
|
6097
|
+
errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
5546
6098
|
}).finally(() => {
|
|
5547
6099
|
finished = true;
|
|
5548
6100
|
wake?.();
|
|
5549
6101
|
});
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
6102
|
+
try {
|
|
6103
|
+
while (!finished || queue.length) {
|
|
6104
|
+
if (queue.length) {
|
|
6105
|
+
yield queue.shift();
|
|
6106
|
+
continue;
|
|
6107
|
+
}
|
|
6108
|
+
await new Promise((resolve5) => {
|
|
6109
|
+
wake = resolve5;
|
|
6110
|
+
});
|
|
6111
|
+
wake = null;
|
|
5554
6112
|
}
|
|
5555
|
-
await
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
6113
|
+
await running;
|
|
6114
|
+
yield { type: "result", result: { text, toolCalls, usage, stopReason, ...errorMessage2 ? { error: errorMessage2 } : {} } };
|
|
6115
|
+
} finally {
|
|
6116
|
+
opts.signal?.removeEventListener("abort", onParentAbort);
|
|
6117
|
+
if (!finished) controller.abort();
|
|
5559
6118
|
}
|
|
5560
|
-
await running;
|
|
5561
|
-
yield { type: "result", result: { text, toolCalls, usage, stopReason } };
|
|
5562
6119
|
}
|
|
5563
6120
|
async function runTask(opts) {
|
|
5564
6121
|
let out = { text: "", toolCalls: [], usage: { inputTokens: 0, outputTokens: 0 }, stopReason: "done" };
|