@botconnector/bccli 0.4.1 → 0.4.3
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-NU4HZHQO.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-MFHCQQEN.js → chunk-3I5BW3ZT.js} +3 -2
- package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
- package/dist/{chunk-BGXNR74O.js → chunk-4W5SKQDC.js} +152 -31
- package/dist/{chunk-BC7UXFE4.js → chunk-6AUROP2E.js} +64 -3
- package/dist/{chunk-24JN75FZ.js → chunk-6M55UPES.js} +2 -2
- package/dist/{chunk-XV7ZVL4J.js → chunk-6UO5GX55.js} +26 -12
- package/dist/{chunk-75BECX4F.js → chunk-7I2ZGPUS.js} +4 -4
- package/dist/{chunk-BKGFA2PT.js → chunk-FUJGHCWN.js} +1 -1
- package/dist/{chunk-GN4W3T6E.js → chunk-GWVLPN4B.js} +4 -4
- package/dist/{chunk-JHPGOAXU.js → chunk-HQW4BROS.js} +4 -5
- 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-QNUJHKEL.js} +659 -162
- package/dist/{chunk-FLQPE4S5.js → chunk-RX4UQX43.js} +4 -1
- package/dist/{chunk-R5RE46CW.js → chunk-WDX62DFH.js} +40 -6
- package/dist/{chunk-MHIYLXJC.js → chunk-WFZKJY3R.js} +8 -4
- package/dist/{chunk-WNACAUDK.js → chunk-XTOATEI6.js} +66 -9
- package/dist/{chunk-BXQQCUAC.js → chunk-Z5NI5FIK.js} +7 -7
- 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-GCIUUMKQ.js} +21 -20
- package/dist/{integrationCli-Q2YPDTPG.js → integrationCli-HXCSEWH2.js} +2 -2
- package/dist/{login-FP3BB44A.js → login-YXSSB2HQ.js} +4 -4
- package/dist/lsp.d.ts +26 -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-656AKYZD.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-V2576JXE.js +15 -0
- package/dist/sdk.d.ts +5 -1
- package/dist/sdk.js +1003 -230
- package/dist/{serve-KWOECK5W.js → serve-K5AH4MGO.js} +3 -3
- package/dist/{serve-X2ZV4S5U.js → serve-XPAGXIGS.js} +17 -17
- package/dist/session-V2VCAF7E.js +12 -0
- package/dist/session.d.ts +3 -0
- package/dist/setup-HVVHOIL4.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/diagnostics.d.ts +8 -0
- 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/tools/sandbox.d.ts +3 -0
- package/dist/{transferCli-YUFPI6AH.js → transferCli-C6XS6QYH.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-43C6Y57K.js} +199 -54
- package/dist/{update-TZ2G6KQ3.js → update-AVZO4XID.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
|
@@ -5,66 +5,71 @@ import {
|
|
|
5
5
|
pruneOldToolOutputs,
|
|
6
6
|
splitKeepRecent,
|
|
7
7
|
trimToFit
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OK2USAXX.js";
|
|
9
9
|
import {
|
|
10
10
|
BUILTIN_AGENTS,
|
|
11
11
|
appendMemory,
|
|
12
12
|
bashTool,
|
|
13
13
|
buildSystemPrompt,
|
|
14
14
|
createBashTool,
|
|
15
|
+
errorCode,
|
|
15
16
|
ignorePatterns,
|
|
17
|
+
isFileModifiedSinceRead,
|
|
16
18
|
loadAgentDefs,
|
|
17
19
|
loadCommands,
|
|
18
20
|
loadSkills,
|
|
19
21
|
normalizeGlobPath,
|
|
20
22
|
parseFrontmatter,
|
|
23
|
+
recordFileMtime,
|
|
24
|
+
resolvePath,
|
|
21
25
|
runHooks,
|
|
22
26
|
validateGlobPattern
|
|
23
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-4W5SKQDC.js";
|
|
24
28
|
import {
|
|
25
29
|
createWebSearchTool,
|
|
26
30
|
fetchTool
|
|
27
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JXM6REER.js";
|
|
28
32
|
import {
|
|
29
33
|
defineTool
|
|
30
34
|
} from "./chunk-PSO7R5AD.js";
|
|
31
35
|
import {
|
|
32
36
|
Permissions
|
|
33
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-WFZKJY3R.js";
|
|
34
38
|
import {
|
|
35
39
|
addProviderKey
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-GWVLPN4B.js";
|
|
37
41
|
import {
|
|
38
42
|
listAllModels
|
|
39
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-7I2ZGPUS.js";
|
|
40
44
|
import {
|
|
41
45
|
providerName
|
|
42
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-HQW4BROS.js";
|
|
43
47
|
import {
|
|
44
48
|
FileOAuthProvider,
|
|
45
49
|
hasStoredTokens
|
|
46
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-3I5BW3ZT.js";
|
|
47
51
|
import {
|
|
48
52
|
Session
|
|
49
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-6AUROP2E.js";
|
|
50
54
|
import {
|
|
51
55
|
completionAssistantMessage,
|
|
52
56
|
createProvider,
|
|
53
57
|
hasPendingProgrammaticReplay,
|
|
54
|
-
imageUserMessage
|
|
55
|
-
|
|
58
|
+
imageUserMessage,
|
|
59
|
+
withFallbackProvider
|
|
60
|
+
} from "./chunk-XTOATEI6.js";
|
|
56
61
|
import {
|
|
57
62
|
expandEnvVars
|
|
58
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-6M55UPES.js";
|
|
59
64
|
import {
|
|
60
65
|
ConfigError,
|
|
61
66
|
bccliHome,
|
|
62
67
|
loadConfig,
|
|
63
68
|
resolveModel
|
|
64
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-RX4UQX43.js";
|
|
65
70
|
import {
|
|
66
71
|
t
|
|
67
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-3WHI4LN3.js";
|
|
68
73
|
|
|
69
74
|
// src/textToolCalls.ts
|
|
70
75
|
var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
|
|
@@ -164,47 +169,108 @@ function formatDiff(lines) {
|
|
|
164
169
|
return lines.map((l) => `${String(l.line).padStart(5)} ${mark[l.kind]} ${l.text}`).join("\n");
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
// src/tools/paths.ts
|
|
168
|
-
import { isAbsolute, resolve } from "path";
|
|
169
|
-
function resolvePath(cwd, path) {
|
|
170
|
-
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
171
|
-
}
|
|
172
|
-
function errorCode(error) {
|
|
173
|
-
return error.code ?? error.message;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
172
|
// src/tools/edit.ts
|
|
177
|
-
var
|
|
178
|
-
path: z.string().describe("File path, relative to the project or absolute"),
|
|
173
|
+
var editEntrySchema = z.object({
|
|
179
174
|
old_string: z.string().describe("Exact text to replace; must be unique unless replace_all"),
|
|
180
175
|
new_string: z.string().describe("Replacement text"),
|
|
181
176
|
replace_all: z.boolean().optional().describe("Replace every occurrence")
|
|
182
177
|
});
|
|
178
|
+
var schema = z.object({
|
|
179
|
+
path: z.string().describe("File path, relative to the project or absolute"),
|
|
180
|
+
old_string: z.string().optional().describe("Exact text to replace; must be unique unless replace_all"),
|
|
181
|
+
new_string: z.string().optional().describe("Replacement text"),
|
|
182
|
+
replace_all: z.boolean().optional().describe("Replace every occurrence"),
|
|
183
|
+
edits: z.array(editEntrySchema).optional().describe("Optional list of sequential edits to apply atomically to the same file")
|
|
184
|
+
});
|
|
183
185
|
function countOccurrences(haystack, needle) {
|
|
184
186
|
let count = 0;
|
|
185
187
|
for (let i = haystack.indexOf(needle); i !== -1; i = haystack.indexOf(needle, i + needle.length)) count++;
|
|
186
188
|
return count;
|
|
187
189
|
}
|
|
188
|
-
function
|
|
190
|
+
function normalizeTypographic(s) {
|
|
191
|
+
return s.replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2013\u2014]/g, "-").replace(/\u00A0/g, " ");
|
|
192
|
+
}
|
|
193
|
+
function stripReadLinePrefixes(s) {
|
|
194
|
+
const lines = s.split(/\r?\n/);
|
|
195
|
+
if (lines.every((l) => !l.trim() || /^\s*\d+\t/.test(l))) {
|
|
196
|
+
return lines.map((l) => l.replace(/^\s*\d+\t/, "")).join("\n");
|
|
197
|
+
}
|
|
198
|
+
return s;
|
|
199
|
+
}
|
|
200
|
+
function matchByNormalizedLines(content, oldText, newText, replaceAll) {
|
|
201
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
202
|
+
const fileLines = content.split(/\r?\n/);
|
|
203
|
+
const oldLines = stripReadLinePrefixes(oldText).split(/\r?\n/);
|
|
204
|
+
if (!oldLines.length) return { count: 0 };
|
|
205
|
+
const normLine = (l) => normalizeTypographic(l).trimEnd();
|
|
206
|
+
const normOld = oldLines.map(normLine);
|
|
207
|
+
const matches = [];
|
|
208
|
+
for (let i = 0; i + oldLines.length <= fileLines.length; i++) {
|
|
209
|
+
let ok = true;
|
|
210
|
+
for (let j = 0; j < oldLines.length; j++) {
|
|
211
|
+
if (normLine(fileLines[i + j]) !== normOld[j]) {
|
|
212
|
+
ok = false;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (ok) {
|
|
217
|
+
matches.push(i);
|
|
218
|
+
if (replaceAll) i += oldLines.length - 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (matches.length === 0) return { count: 0 };
|
|
222
|
+
if (matches.length > 1 && !replaceAll) return { count: matches.length };
|
|
223
|
+
const replacementLines = stripReadLinePrefixes(newText).split(/\r?\n/);
|
|
224
|
+
const outLines = [...fileLines];
|
|
225
|
+
for (let m = matches.length - 1; m >= 0; m--) {
|
|
226
|
+
outLines.splice(matches[m], oldLines.length, ...replacementLines);
|
|
227
|
+
}
|
|
228
|
+
return { count: matches.length, result: outLines.join(eol) };
|
|
229
|
+
}
|
|
230
|
+
function applySingleEdit(content, path, input) {
|
|
189
231
|
if (input.old_string === input.new_string) {
|
|
190
232
|
return { output: "old_string and new_string are identical; nothing to change.", isError: true };
|
|
191
233
|
}
|
|
192
234
|
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
193
235
|
const toEol = (s) => s.replace(/\r?\n/g, eol);
|
|
236
|
+
const strippedOld = stripReadLinePrefixes(input.old_string);
|
|
237
|
+
const strippedNew = stripReadLinePrefixes(input.new_string);
|
|
194
238
|
const attempts = [[input.old_string, input.new_string]];
|
|
195
239
|
if (toEol(input.old_string) !== input.old_string) attempts.push([toEol(input.old_string), toEol(input.new_string)]);
|
|
240
|
+
if (strippedOld !== input.old_string) {
|
|
241
|
+
attempts.push([strippedOld, strippedNew], [toEol(strippedOld), toEol(strippedNew)]);
|
|
242
|
+
}
|
|
196
243
|
for (const [oldText, newText] of attempts) {
|
|
197
244
|
const count = oldText ? countOccurrences(content, oldText) : 0;
|
|
198
245
|
if (count === 0) continue;
|
|
199
246
|
if (count > 1 && !input.replace_all) {
|
|
200
247
|
return {
|
|
201
|
-
output: `old_string appears ${count} times in ${
|
|
248
|
+
output: `old_string appears ${count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
202
249
|
isError: true
|
|
203
250
|
};
|
|
204
251
|
}
|
|
205
252
|
return input.replace_all ? content.split(oldText).join(newText) : content.replace(oldText, () => newText);
|
|
206
253
|
}
|
|
207
|
-
|
|
254
|
+
const fuzzy = matchByNormalizedLines(content, input.old_string, input.new_string, input.replace_all);
|
|
255
|
+
if (fuzzy.count > 1 && !input.replace_all) {
|
|
256
|
+
return {
|
|
257
|
+
output: `old_string appears ${fuzzy.count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
258
|
+
isError: true
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (fuzzy.count > 0 && fuzzy.result !== void 0) return fuzzy.result;
|
|
262
|
+
return { output: `old_string not found in ${path}. Re-read the file.`, isError: true };
|
|
263
|
+
}
|
|
264
|
+
function applyEdit(content, input) {
|
|
265
|
+
const list = 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 }] : [];
|
|
266
|
+
if (!list.length) return { output: "Provide old_string and new_string (or edits array).", isError: true };
|
|
267
|
+
let current = content;
|
|
268
|
+
for (const item of list) {
|
|
269
|
+
const next = applySingleEdit(current, input.path, item);
|
|
270
|
+
if (typeof next !== "string") return next;
|
|
271
|
+
current = next;
|
|
272
|
+
}
|
|
273
|
+
return current;
|
|
208
274
|
}
|
|
209
275
|
var editTool = defineTool({
|
|
210
276
|
name: "edit",
|
|
@@ -215,6 +281,9 @@ var editTool = defineTool({
|
|
|
215
281
|
async validate(input, ctx) {
|
|
216
282
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
217
283
|
if (!ctx.readFiles.has(abs)) return `Read ${input.path} with read before editing it.`;
|
|
284
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
285
|
+
return `${input.path} has been modified since it was last read. Read it again before editing.`;
|
|
286
|
+
}
|
|
218
287
|
let content;
|
|
219
288
|
try {
|
|
220
289
|
content = await readFile(abs, "utf8");
|
|
@@ -235,6 +304,9 @@ var editTool = defineTool({
|
|
|
235
304
|
if (!ctx.readFiles.has(abs)) {
|
|
236
305
|
return { output: `Read ${input.path} with read before editing it.`, isError: true };
|
|
237
306
|
}
|
|
307
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
308
|
+
return { output: `${input.path} has been modified since it was last read. Read it again before editing.`, isError: true };
|
|
309
|
+
}
|
|
238
310
|
let content;
|
|
239
311
|
try {
|
|
240
312
|
content = await readFile(abs, "utf8");
|
|
@@ -245,25 +317,28 @@ var editTool = defineTool({
|
|
|
245
317
|
if (typeof next !== "string") return next;
|
|
246
318
|
await ctx.checkpoint?.(abs);
|
|
247
319
|
await writeFile(abs, next);
|
|
320
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
248
321
|
return { output: `Edited ${input.path}.`, display: formatDiff(diffLines(content, next)) };
|
|
249
322
|
}
|
|
250
323
|
});
|
|
251
324
|
|
|
252
325
|
// src/tools/glob.ts
|
|
326
|
+
import { statSync } from "fs";
|
|
253
327
|
import { glob as nativeGlob } from "fs/promises";
|
|
328
|
+
import { join } from "path";
|
|
254
329
|
import { z as z2 } from "zod";
|
|
255
330
|
|
|
256
331
|
// src/abort.ts
|
|
257
332
|
var ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
258
333
|
function raceAbort(promise, signal) {
|
|
259
334
|
if (signal.aborted) return Promise.resolve(ABORTED);
|
|
260
|
-
return new Promise((
|
|
261
|
-
const onAbort = () =>
|
|
335
|
+
return new Promise((resolve, reject) => {
|
|
336
|
+
const onAbort = () => resolve(ABORTED);
|
|
262
337
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
263
338
|
promise.then(
|
|
264
339
|
(value) => {
|
|
265
340
|
signal.removeEventListener("abort", onAbort);
|
|
266
|
-
|
|
341
|
+
resolve(value);
|
|
267
342
|
},
|
|
268
343
|
(error) => {
|
|
269
344
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -297,19 +372,30 @@ var globTool = defineTool({
|
|
|
297
372
|
path: z2.string().optional().describe("Base directory, default project root")
|
|
298
373
|
}),
|
|
299
374
|
kind: "read",
|
|
300
|
-
target: (input) => input.pattern,
|
|
375
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
301
376
|
async run(input, ctx) {
|
|
302
377
|
const invalid = validateGlobPattern(input.pattern);
|
|
303
378
|
if (invalid) return { output: invalid, isError: true };
|
|
304
379
|
const root = resolvePath(ctx.cwd, input.path ?? ".");
|
|
305
380
|
const files = [];
|
|
306
381
|
const finished = await collectUntilAbort(
|
|
307
|
-
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root) }),
|
|
382
|
+
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root, ctx.cwd) }),
|
|
308
383
|
ctx.signal,
|
|
309
384
|
(file) => files.push(normalizeGlobPath(file))
|
|
310
385
|
);
|
|
311
386
|
if (!finished) return { output: "Cancelled by the user.", isError: true };
|
|
312
|
-
|
|
387
|
+
const mtime = (rel) => {
|
|
388
|
+
try {
|
|
389
|
+
return statSync(join(root, rel)).mtimeMs;
|
|
390
|
+
} catch {
|
|
391
|
+
return 0;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const mtimes = new Map(files.map((f) => [f, mtime(f)]));
|
|
395
|
+
files.sort((a, b) => {
|
|
396
|
+
const diff = (mtimes.get(b) ?? 0) - (mtimes.get(a) ?? 0);
|
|
397
|
+
return Math.abs(diff) >= 50 ? diff : a.localeCompare(b);
|
|
398
|
+
});
|
|
313
399
|
if (!files.length) return { output: "No matching files." };
|
|
314
400
|
const first = files.slice(0, 500);
|
|
315
401
|
const shown = [];
|
|
@@ -330,75 +416,141 @@ var globTool = defineTool({
|
|
|
330
416
|
|
|
331
417
|
// src/tools/grep.ts
|
|
332
418
|
import { execFile } from "child_process";
|
|
419
|
+
import { statSync as statSync2 } from "fs";
|
|
333
420
|
import { readFile as readFile2 } from "fs/promises";
|
|
334
421
|
import { glob as nativeGlob2 } from "fs/promises";
|
|
422
|
+
import { basename, dirname } from "path";
|
|
335
423
|
import { z as z3 } from "zod";
|
|
336
424
|
var MAX_MATCHES = 200;
|
|
337
425
|
var MAX_OUTPUT_CHARS2 = 24e3;
|
|
426
|
+
function escapeRegExp(s) {
|
|
427
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
428
|
+
}
|
|
429
|
+
function isSingleFile(p) {
|
|
430
|
+
try {
|
|
431
|
+
return statSync2(p).isFile();
|
|
432
|
+
} catch {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
338
436
|
async function grepJs(pattern, opts) {
|
|
339
|
-
const
|
|
437
|
+
const source = opts.literal ? escapeRegExp(pattern) : pattern;
|
|
438
|
+
const regex = new RegExp(source, opts.ignoreCase ? "i" : "");
|
|
439
|
+
const single = isSingleFile(opts.root);
|
|
340
440
|
const files = [];
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
441
|
+
if (single) {
|
|
442
|
+
files.push(basename(opts.root));
|
|
443
|
+
} else {
|
|
444
|
+
const finished = await collectUntilAbort(
|
|
445
|
+
nativeGlob2(opts.glob ?? "**/*", { cwd: opts.root, exclude: ignorePatterns(opts.root, opts.projectRoot) }),
|
|
446
|
+
opts.signal,
|
|
447
|
+
(file) => files.push(normalizeGlobPath(file))
|
|
448
|
+
);
|
|
449
|
+
if (!finished) return [];
|
|
450
|
+
}
|
|
451
|
+
const baseDir = single ? dirname(opts.root) : opts.root;
|
|
452
|
+
const ctxLines = Math.max(0, Math.min(10, Math.floor(opts.context ?? 0)));
|
|
453
|
+
const mode = opts.outputMode ?? "content";
|
|
346
454
|
const out = [];
|
|
347
|
-
if (!finished) return out;
|
|
348
455
|
for (const file of files.sort()) {
|
|
349
456
|
if (opts.signal?.aborted) break;
|
|
350
457
|
let text;
|
|
351
458
|
try {
|
|
352
|
-
const buffer = await readFile2(resolvePath(
|
|
459
|
+
const buffer = await readFile2(resolvePath(baseDir, file));
|
|
353
460
|
if (buffer.subarray(0, 8e3).includes(0)) continue;
|
|
354
461
|
text = buffer.toString("utf8");
|
|
355
462
|
} catch {
|
|
356
463
|
continue;
|
|
357
464
|
}
|
|
358
465
|
const lines = text.split(/\r?\n/);
|
|
466
|
+
let fileMatches = 0;
|
|
359
467
|
for (let i = 0; i < lines.length && out.length < MAX_MATCHES; i++) {
|
|
360
|
-
if (regex.test(lines[i]))
|
|
468
|
+
if (regex.test(lines[i])) {
|
|
469
|
+
fileMatches++;
|
|
470
|
+
if (mode === "files_with_matches") {
|
|
471
|
+
out.push(file);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
if (mode === "content") {
|
|
475
|
+
const from = Math.max(0, i - ctxLines);
|
|
476
|
+
const to = Math.min(lines.length - 1, i + ctxLines);
|
|
477
|
+
for (let j = from; j <= to && out.length < MAX_MATCHES; j++) {
|
|
478
|
+
const sep2 = j === i ? ":" : "-";
|
|
479
|
+
out.push(`${file}${sep2}${j + 1}${sep2}${lines[j].slice(0, 300)}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
361
483
|
}
|
|
484
|
+
if (mode === "count" && fileMatches > 0) out.push(`${file}:${fileMatches}`);
|
|
362
485
|
if (out.length >= MAX_MATCHES) break;
|
|
363
486
|
}
|
|
364
487
|
return out;
|
|
365
488
|
}
|
|
366
489
|
function grepRg(pattern, opts) {
|
|
367
|
-
const
|
|
490
|
+
const single = isSingleFile(opts.root);
|
|
491
|
+
const cwd = single ? dirname(opts.root) : opts.root;
|
|
492
|
+
const target = single ? basename(opts.root) : ".";
|
|
493
|
+
const mode = opts.outputMode ?? "content";
|
|
494
|
+
const args = ["--color", "never"];
|
|
495
|
+
if (mode === "files_with_matches") {
|
|
496
|
+
args.push("--files-with-matches");
|
|
497
|
+
} else if (mode === "count") {
|
|
498
|
+
args.push("--count");
|
|
499
|
+
} else {
|
|
500
|
+
args.push("--line-number", "--no-heading", "--max-columns", "300");
|
|
501
|
+
if (opts.context && opts.context > 0) args.push("-C", String(Math.min(10, Math.floor(opts.context))));
|
|
502
|
+
}
|
|
503
|
+
if (opts.literal) args.push("-F");
|
|
368
504
|
if (opts.ignoreCase) args.push("-i");
|
|
369
|
-
if (opts.glob) args.push("-g", opts.glob);
|
|
370
|
-
args.push("
|
|
371
|
-
return new Promise((
|
|
372
|
-
execFile("rg", args, { cwd
|
|
373
|
-
if (opts.signal?.aborted) return
|
|
505
|
+
if (opts.glob && !single) args.push("-g", opts.glob);
|
|
506
|
+
args.push("-e", pattern, target);
|
|
507
|
+
return new Promise((resolve) => {
|
|
508
|
+
execFile("rg", args, { cwd, maxBuffer: 10 * 1024 * 1024, signal: opts.signal }, (error, stdout) => {
|
|
509
|
+
if (opts.signal?.aborted) return resolve([]);
|
|
374
510
|
const code = error?.code;
|
|
375
|
-
if (code === "ENOENT") return
|
|
376
|
-
if (error && error.code !== 1) return
|
|
377
|
-
|
|
511
|
+
if (code === "ENOENT") return resolve(void 0);
|
|
512
|
+
if (error && error.code !== 1) return resolve(void 0);
|
|
513
|
+
resolve(
|
|
514
|
+
stdout.split(/\r?\n/).filter(Boolean).map((l) => l.replace(/^\.[\\/]/, "").replace(/\\/g, "/")).slice(0, MAX_MATCHES)
|
|
515
|
+
);
|
|
378
516
|
});
|
|
379
517
|
});
|
|
380
518
|
}
|
|
381
519
|
var grepTool = defineTool({
|
|
382
520
|
name: "grep",
|
|
383
|
-
description: "Search file contents with a regular expression. Respects .gitignore. Returns path:line:text (max 200).",
|
|
521
|
+
description: "Search file contents with a regular expression or literal text. Respects .gitignore. Returns path:line:text (max 200).",
|
|
384
522
|
schema: z3.object({
|
|
385
|
-
pattern: z3.string().describe("Regular expression"),
|
|
386
|
-
path: z3.string().optional().describe("
|
|
523
|
+
pattern: z3.string().describe("Regular expression or literal string"),
|
|
524
|
+
path: z3.string().optional().describe("File or directory to search, default project root"),
|
|
387
525
|
glob: z3.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
|
|
388
|
-
ignore_case: z3.boolean().optional()
|
|
526
|
+
ignore_case: z3.boolean().optional(),
|
|
527
|
+
literal: z3.boolean().optional().describe("Treat pattern as literal text instead of regex"),
|
|
528
|
+
context: z3.number().int().min(0).max(10).optional().describe("Number of context lines before and after each match"),
|
|
529
|
+
output_mode: z3.enum(["content", "files_with_matches", "count"]).optional().describe("Output format, default content")
|
|
389
530
|
}),
|
|
390
531
|
kind: "read",
|
|
391
|
-
target: (input) => input.pattern,
|
|
532
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
392
533
|
async run(input, ctx) {
|
|
393
534
|
if (input.glob) {
|
|
394
535
|
const invalid = validateGlobPattern(input.glob);
|
|
395
536
|
if (invalid) return { output: invalid, isError: true };
|
|
396
537
|
}
|
|
397
|
-
const opts = {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
538
|
+
const opts = {
|
|
539
|
+
root: resolvePath(ctx.cwd, input.path ?? "."),
|
|
540
|
+
projectRoot: ctx.cwd,
|
|
541
|
+
glob: input.glob,
|
|
542
|
+
ignoreCase: input.ignore_case,
|
|
543
|
+
literal: input.literal,
|
|
544
|
+
context: input.context,
|
|
545
|
+
outputMode: input.output_mode,
|
|
546
|
+
signal: ctx.signal
|
|
547
|
+
};
|
|
548
|
+
if (!input.literal) {
|
|
549
|
+
try {
|
|
550
|
+
new RegExp(input.pattern);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
return { output: `Invalid regex: ${error.message}`, isError: true };
|
|
553
|
+
}
|
|
402
554
|
}
|
|
403
555
|
const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
|
|
404
556
|
if (ctx.signal.aborted) return { output: "Cancelled by the user.", isError: true };
|
|
@@ -420,6 +572,7 @@ var grepTool = defineTool({
|
|
|
420
572
|
});
|
|
421
573
|
|
|
422
574
|
// src/tools/read.ts
|
|
575
|
+
import { readdirSync, statSync as statSync3 } from "fs";
|
|
423
576
|
import { readFile as readFile3 } from "fs/promises";
|
|
424
577
|
import { z as z4 } from "zod";
|
|
425
578
|
var MAX_LINE = 2e3;
|
|
@@ -503,6 +656,16 @@ function createReadTool(options = {}) {
|
|
|
503
656
|
target: (input) => input.path,
|
|
504
657
|
async run(input, ctx) {
|
|
505
658
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
659
|
+
try {
|
|
660
|
+
if (statSync3(abs).isDirectory()) {
|
|
661
|
+
const entries = readdirSync(abs, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort((a, b) => a.localeCompare(b));
|
|
662
|
+
const start = (input.offset ?? 1) - 1;
|
|
663
|
+
const limit = input.limit ?? 2e3;
|
|
664
|
+
const slice = entries.slice(start, start + limit);
|
|
665
|
+
return { output: slice.join("\n") || "(empty directory)", display: t("{n} lines", { n: slice.length }) };
|
|
666
|
+
}
|
|
667
|
+
} catch {
|
|
668
|
+
}
|
|
506
669
|
let buffer;
|
|
507
670
|
try {
|
|
508
671
|
buffer = await readFile3(abs);
|
|
@@ -511,13 +674,13 @@ function createReadTool(options = {}) {
|
|
|
511
674
|
}
|
|
512
675
|
const mediaType = vision ? imageMediaType(buffer) : void 0;
|
|
513
676
|
if (mediaType) {
|
|
514
|
-
ctx.readFiles
|
|
677
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
515
678
|
return readImageResult(input, buffer, mediaType);
|
|
516
679
|
}
|
|
517
680
|
if (buffer.subarray(0, 8e3).includes(0)) {
|
|
518
681
|
return { output: `${input.path} is a binary file; not read.`, isError: true };
|
|
519
682
|
}
|
|
520
|
-
ctx.readFiles
|
|
683
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
521
684
|
return readTextResult(input, buffer);
|
|
522
685
|
}
|
|
523
686
|
});
|
|
@@ -527,7 +690,7 @@ var readTool = createReadTool({ vision: true });
|
|
|
527
690
|
// src/tools/write.ts
|
|
528
691
|
import { existsSync } from "fs";
|
|
529
692
|
import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
530
|
-
import { dirname } from "path";
|
|
693
|
+
import { dirname as dirname2 } from "path";
|
|
531
694
|
import { z as z5 } from "zod";
|
|
532
695
|
var writeTool = defineTool({
|
|
533
696
|
name: "write",
|
|
@@ -554,12 +717,12 @@ var writeTool = defineTool({
|
|
|
554
717
|
}
|
|
555
718
|
try {
|
|
556
719
|
await ctx.checkpoint?.(abs);
|
|
557
|
-
await mkdir(
|
|
720
|
+
await mkdir(dirname2(abs), { recursive: true });
|
|
558
721
|
await writeFile2(abs, input.content);
|
|
559
722
|
} catch (error) {
|
|
560
723
|
return { output: `Failed to write ${input.path}: ${errorCode(error)}`, isError: true };
|
|
561
724
|
}
|
|
562
|
-
ctx.readFiles
|
|
725
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
563
726
|
const lines = input.content.split("\n").length;
|
|
564
727
|
return { output: `Wrote ${input.path} (${lines} lines).`, display: t("{lines} lines written", { lines }) };
|
|
565
728
|
}
|
|
@@ -683,6 +846,7 @@ var Agent = class {
|
|
|
683
846
|
this.messages = [...opts.history ?? []];
|
|
684
847
|
this.maxSteps = typeof opts.maxSteps === "number" && Number.isFinite(opts.maxSteps) && opts.maxSteps > 0 ? Math.max(1, Math.floor(opts.maxSteps)) : void 0;
|
|
685
848
|
this.contextWindow = opts.contextWindow ?? 128e3;
|
|
849
|
+
this.budgetModel = opts.budgetModel ?? "";
|
|
686
850
|
this.systemPrompt = opts.systemPrompt;
|
|
687
851
|
this.reasoning = opts.reasoning ?? "auto";
|
|
688
852
|
}
|
|
@@ -706,6 +870,7 @@ var Agent = class {
|
|
|
706
870
|
maxSteps;
|
|
707
871
|
// One window for every model; resolved from contextWindowFor() (catalog + env override).
|
|
708
872
|
contextWindow;
|
|
873
|
+
budgetModel;
|
|
709
874
|
systemPrompt;
|
|
710
875
|
setSystemPrompt(text) {
|
|
711
876
|
this.systemPrompt = text;
|
|
@@ -713,6 +878,15 @@ var Agent = class {
|
|
|
713
878
|
setContextWindow(window) {
|
|
714
879
|
this.contextWindow = window;
|
|
715
880
|
}
|
|
881
|
+
getContextWindow() {
|
|
882
|
+
return this.contextWindow;
|
|
883
|
+
}
|
|
884
|
+
setBudgetModel(model) {
|
|
885
|
+
this.budgetModel = model;
|
|
886
|
+
}
|
|
887
|
+
getBudgetModel() {
|
|
888
|
+
return this.budgetModel;
|
|
889
|
+
}
|
|
716
890
|
setTools(tools) {
|
|
717
891
|
this.tools = tools;
|
|
718
892
|
this.definitions = toolDefinitions(tools);
|
|
@@ -913,7 +1087,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
913
1087
|
}
|
|
914
1088
|
/** Current cap status; undefined means the budget is not exhausted. */
|
|
915
1089
|
overBudget() {
|
|
916
|
-
return budgetStatus(this.totalUsage, this.opts.usageCap, this.
|
|
1090
|
+
return budgetStatus(this.totalUsage, this.opts.usageCap, this.budgetModel);
|
|
917
1091
|
}
|
|
918
1092
|
/** Every tool call needs an answer in the history, or the next request to the model is rejected. */
|
|
919
1093
|
cancelCalls(calls) {
|
|
@@ -1033,7 +1207,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1033
1207
|
};
|
|
1034
1208
|
|
|
1035
1209
|
// src/checkpoints.ts
|
|
1036
|
-
import { existsSync as existsSync2, statSync } from "fs";
|
|
1210
|
+
import { existsSync as existsSync2, statSync as statSync4 } from "fs";
|
|
1037
1211
|
import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
|
|
1038
1212
|
var MAX_TURNS = 20;
|
|
1039
1213
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
@@ -1060,8 +1234,8 @@ var CheckpointStore = class {
|
|
|
1060
1234
|
history = [];
|
|
1061
1235
|
current;
|
|
1062
1236
|
pendingRedo;
|
|
1237
|
+
offset = 0;
|
|
1063
1238
|
beginTurn() {
|
|
1064
|
-
if (this.current?.size === 0) return;
|
|
1065
1239
|
this.current = /* @__PURE__ */ new Map();
|
|
1066
1240
|
this.history.push(this.current);
|
|
1067
1241
|
this.pendingRedo = void 0;
|
|
@@ -1072,16 +1246,19 @@ var CheckpointStore = class {
|
|
|
1072
1246
|
const turn = this.current;
|
|
1073
1247
|
if (turn.has(path)) return;
|
|
1074
1248
|
if (!existsSync2(path)) turn.set(path, null);
|
|
1075
|
-
else if (
|
|
1249
|
+
else if (statSync4(path).size > MAX_BYTES) turn.set(path, "skipped");
|
|
1076
1250
|
else turn.set(path, await readFile5(path));
|
|
1077
|
-
while (this.turns() > MAX_TURNS)
|
|
1251
|
+
while (this.turns() > MAX_TURNS) {
|
|
1252
|
+
this.history.shift();
|
|
1253
|
+
this.offset++;
|
|
1254
|
+
}
|
|
1078
1255
|
}
|
|
1079
1256
|
turns() {
|
|
1080
1257
|
return this.history.filter((t2) => t2.size).length;
|
|
1081
1258
|
}
|
|
1082
1259
|
/** Total entries including empty ones; used to align file snapshots with conversation turns. */
|
|
1083
1260
|
entries() {
|
|
1084
|
-
return this.history.length;
|
|
1261
|
+
return this.offset + this.history.length;
|
|
1085
1262
|
}
|
|
1086
1263
|
async undo() {
|
|
1087
1264
|
while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
|
|
@@ -1101,7 +1278,7 @@ var CheckpointStore = class {
|
|
|
1101
1278
|
* pairs conversation turns with the entry count recorded when each turn started.
|
|
1102
1279
|
*/
|
|
1103
1280
|
async undoTo(keep) {
|
|
1104
|
-
const target = Math.max(0, Math.min(keep, this.history.length));
|
|
1281
|
+
const target = Math.max(0, Math.min(keep - this.offset, this.history.length));
|
|
1105
1282
|
const result = emptyResult();
|
|
1106
1283
|
const redo = /* @__PURE__ */ new Map();
|
|
1107
1284
|
let applied = false;
|
|
@@ -1126,8 +1303,11 @@ var CheckpointStore = class {
|
|
|
1126
1303
|
const turn = this.pendingRedo;
|
|
1127
1304
|
if (!turn) return void 0;
|
|
1128
1305
|
this.pendingRedo = void 0;
|
|
1306
|
+
const undoEntry = /* @__PURE__ */ new Map();
|
|
1307
|
+
await this.captureFor(turn, undoEntry);
|
|
1129
1308
|
const result = emptyResult();
|
|
1130
1309
|
await restoreTurn(turn, result);
|
|
1310
|
+
if (undoEntry.size) this.history.push(undoEntry);
|
|
1131
1311
|
return result;
|
|
1132
1312
|
}
|
|
1133
1313
|
/** Records the on-disk state of a turn's paths so redo() can put it back. */
|
|
@@ -1140,7 +1320,7 @@ var CheckpointStore = class {
|
|
|
1140
1320
|
}
|
|
1141
1321
|
try {
|
|
1142
1322
|
if (!existsSync2(path)) into.set(path, null);
|
|
1143
|
-
else if (
|
|
1323
|
+
else if (statSync4(path).size > MAX_BYTES) into.set(path, "skipped");
|
|
1144
1324
|
else into.set(path, await readFile5(path));
|
|
1145
1325
|
} catch {
|
|
1146
1326
|
into.set(path, "skipped");
|
|
@@ -1151,6 +1331,7 @@ var CheckpointStore = class {
|
|
|
1151
1331
|
this.history.length = 0;
|
|
1152
1332
|
this.current = void 0;
|
|
1153
1333
|
this.pendingRedo = void 0;
|
|
1334
|
+
this.offset = 0;
|
|
1154
1335
|
}
|
|
1155
1336
|
};
|
|
1156
1337
|
|
|
@@ -1176,7 +1357,28 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
|
1176
1357
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1177
1358
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1178
1359
|
import { z as z7 } from "zod";
|
|
1179
|
-
|
|
1360
|
+
function formatMcpContent(content) {
|
|
1361
|
+
const parts = [];
|
|
1362
|
+
const images = [];
|
|
1363
|
+
for (const c of content ?? []) {
|
|
1364
|
+
if (c.type === "text") {
|
|
1365
|
+
parts.push(c.text ?? "");
|
|
1366
|
+
} else if (c.type === "image" && c.data) {
|
|
1367
|
+
const mediaType = c.mimeType || "image/png";
|
|
1368
|
+
images.push({ mediaType, data: c.data, path: "mcp:image" });
|
|
1369
|
+
parts.push(`[image attached: ${mediaType}]`);
|
|
1370
|
+
} else if (c.type === "resource" && c.resource?.text) {
|
|
1371
|
+
parts.push(c.resource.uri ? `[${c.resource.uri}]
|
|
1372
|
+
${c.resource.text}` : c.resource.text);
|
|
1373
|
+
} else {
|
|
1374
|
+
parts.push(`[${c.type}]`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
output: parts.join("\n") || "(empty)",
|
|
1379
|
+
...images.length ? { images } : {}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1180
1382
|
var CHILD_ENV_KEYS = [
|
|
1181
1383
|
// portable
|
|
1182
1384
|
"PATH",
|
|
@@ -1305,11 +1507,13 @@ var McpManager = class {
|
|
|
1305
1507
|
async run(input, ctx) {
|
|
1306
1508
|
try {
|
|
1307
1509
|
const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
|
|
1308
|
-
const
|
|
1510
|
+
const formatted = formatMcpContent(result.content);
|
|
1511
|
+
const output = formatted.output;
|
|
1309
1512
|
return {
|
|
1310
|
-
output: output.length >
|
|
1311
|
-
\u2026 [truncated at
|
|
1312
|
-
isError: !!result.isError
|
|
1513
|
+
output: output.length > 32e3 ? `${output.slice(0, 32e3)}
|
|
1514
|
+
\u2026 [truncated at 32000 characters; narrow the MCP request if possible]` : output,
|
|
1515
|
+
isError: !!result.isError,
|
|
1516
|
+
...formatted.images ? { images: formatted.images } : {}
|
|
1313
1517
|
};
|
|
1314
1518
|
} catch (error) {
|
|
1315
1519
|
return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
|
|
@@ -1388,6 +1592,7 @@ import { z as z10 } from "zod";
|
|
|
1388
1592
|
function createTaskTool(opts) {
|
|
1389
1593
|
const byName = new Map(opts.agents.map((a) => [a.name, a]));
|
|
1390
1594
|
const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
1595
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1391
1596
|
let askChain = Promise.resolve();
|
|
1392
1597
|
const enqueueAsk = (fn) => {
|
|
1393
1598
|
const next = askChain.then(
|
|
@@ -1407,7 +1612,8 @@ ${list}`,
|
|
|
1407
1612
|
schema: z10.object({
|
|
1408
1613
|
agent: z10.string().describe("Agent name"),
|
|
1409
1614
|
description: z10.string().describe("3-6 word summary shown to the user"),
|
|
1410
|
-
prompt: z10.string().describe("Full instructions; the subagent sees nothing else")
|
|
1615
|
+
prompt: z10.string().describe("Full instructions; the subagent sees nothing else"),
|
|
1616
|
+
session_id: z10.string().optional().describe("Optional session id to continue a previous subagent conversation")
|
|
1411
1617
|
}),
|
|
1412
1618
|
kind: "read",
|
|
1413
1619
|
target: (input) => `[${input.agent}] ${input.description}`,
|
|
@@ -1434,6 +1640,7 @@ ${list}`,
|
|
|
1434
1640
|
} catch (error) {
|
|
1435
1641
|
return { output: `Agent "${def.name}": ${error.message}`, isError: true };
|
|
1436
1642
|
}
|
|
1643
|
+
const priorHistory = input.session_id ? sessions.get(input.session_id) : void 0;
|
|
1437
1644
|
const child = new Agent({
|
|
1438
1645
|
provider,
|
|
1439
1646
|
tools,
|
|
@@ -1443,6 +1650,7 @@ ${list}`,
|
|
|
1443
1650
|
# Your role
|
|
1444
1651
|
${def.prompt}`,
|
|
1445
1652
|
cwd: opts.cwd,
|
|
1653
|
+
...priorHistory ? { history: priorHistory } : {},
|
|
1446
1654
|
// Every subagent runs with a step cap so a looping child cannot burn tokens forever.
|
|
1447
1655
|
maxSteps: def.maxSteps ?? 50,
|
|
1448
1656
|
label: def.name,
|
|
@@ -1463,12 +1671,29 @@ ${def.prompt}`,
|
|
|
1463
1671
|
const ask = ctx.ask;
|
|
1464
1672
|
if (ask) child.askPermission = (req) => enqueueAsk(() => ask(req));
|
|
1465
1673
|
await child.run(input.prompt, ctx.signal);
|
|
1674
|
+
if (input.session_id) sessions.set(input.session_id, [...child.messages]);
|
|
1466
1675
|
ctx.addUsage?.(child.totalUsage);
|
|
1467
1676
|
const tokens2 = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
|
|
1468
1677
|
const display = t("{steps} steps \xB7 {tokens} tokens", { steps, tokens: tokens2 >= 1e3 ? `${Math.round(tokens2 / 1e3)}k` : tokens2 });
|
|
1469
1678
|
if (outcome?.type === "error") return { output: `Subagent ${def.name} failed: ${outcome.message}`, isError: true, display };
|
|
1470
1679
|
if (outcome?.type === "aborted") return { output: `Subagent ${def.name} was cancelled.`, isError: true, display };
|
|
1471
|
-
|
|
1680
|
+
let last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
|
|
1681
|
+
if (!last && outcome?.type === "stepLimit" && !ctx.signal.aborted) {
|
|
1682
|
+
try {
|
|
1683
|
+
const synthesis = await provider.chat({
|
|
1684
|
+
messages: [
|
|
1685
|
+
...child.messages,
|
|
1686
|
+
{ role: "user", content: "Step limit reached. Summarize all findings gathered so far concisely." }
|
|
1687
|
+
],
|
|
1688
|
+
signal: ctx.signal,
|
|
1689
|
+
reasoning: opts.reasoning?.() ?? "auto"
|
|
1690
|
+
});
|
|
1691
|
+
if (synthesis.text.trim()) {
|
|
1692
|
+
last = { role: "assistant", content: synthesis.text.trim() };
|
|
1693
|
+
}
|
|
1694
|
+
} catch {
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1472
1697
|
const report = last && typeof last.content === "string" ? last.content : "(no report)";
|
|
1473
1698
|
const suffix = outcome?.type === "stepLimit" ? `
|
|
1474
1699
|
|
|
@@ -1514,7 +1739,7 @@ function createTodoTool(store) {
|
|
|
1514
1739
|
|
|
1515
1740
|
// src/tools/memory.ts
|
|
1516
1741
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1517
|
-
import { join } from "path";
|
|
1742
|
+
import { join as join2 } from "path";
|
|
1518
1743
|
import { z as z12 } from "zod";
|
|
1519
1744
|
function createSaveMemoryTool(opts) {
|
|
1520
1745
|
return defineTool({
|
|
@@ -1527,11 +1752,11 @@ function createSaveMemoryTool(opts) {
|
|
|
1527
1752
|
kind: "edit",
|
|
1528
1753
|
// The target is what the prompt shows and what the permission model judges: the
|
|
1529
1754
|
// global store really lives outside the project, so it must not look like one.
|
|
1530
|
-
target: (input) => input.scope === "global" ?
|
|
1755
|
+
target: (input) => input.scope === "global" ? join2(opts.home, "BCCLI.md") : "AGENTS.md",
|
|
1531
1756
|
async run(input, ctx) {
|
|
1532
1757
|
const content = input.content.trim();
|
|
1533
1758
|
if (!content) return { output: "Memory content is empty.", isError: true };
|
|
1534
|
-
const file = input.scope === "global" ?
|
|
1759
|
+
const file = input.scope === "global" ? join2(opts.home, "BCCLI.md") : join2(ctx.cwd, "AGENTS.md");
|
|
1535
1760
|
if (existsSync3(file)) {
|
|
1536
1761
|
const lines = readFileSync2(file, "utf8").split("\n");
|
|
1537
1762
|
if (lines.some((line) => line.trim() === `- ${content}`)) {
|
|
@@ -1549,18 +1774,21 @@ function createSaveMemoryTool(opts) {
|
|
|
1549
1774
|
import { z as z13 } from "zod";
|
|
1550
1775
|
|
|
1551
1776
|
// src/repomap.ts
|
|
1552
|
-
import { readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
1553
|
-
import { extname, join as
|
|
1777
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
1778
|
+
import { extname, join as join3, relative, sep } from "path";
|
|
1554
1779
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1555
1780
|
"node_modules",
|
|
1556
1781
|
".git",
|
|
1557
1782
|
"dist",
|
|
1558
1783
|
"build",
|
|
1784
|
+
"out",
|
|
1559
1785
|
"coverage",
|
|
1560
1786
|
".next",
|
|
1787
|
+
".turbo",
|
|
1561
1788
|
"target",
|
|
1562
1789
|
"vendor",
|
|
1563
1790
|
"__pycache__",
|
|
1791
|
+
"venv",
|
|
1564
1792
|
".venv",
|
|
1565
1793
|
".cache"
|
|
1566
1794
|
]);
|
|
@@ -1586,21 +1814,22 @@ var CODE_EXTS = /* @__PURE__ */ new Set([
|
|
|
1586
1814
|
".ps1"
|
|
1587
1815
|
]);
|
|
1588
1816
|
var MAX_FILES = 400;
|
|
1817
|
+
var MAX_SCAN_FILES = 5e3;
|
|
1589
1818
|
var MAX_FILE_BYTES = 2e5;
|
|
1590
1819
|
var MAX_SYMBOLS_PER_FILE = 12;
|
|
1591
1820
|
var FAMILY = {
|
|
1592
1821
|
ts: [
|
|
1593
|
-
/^\s*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+([A-Za-z0-9_$]+)/,
|
|
1822
|
+
/^\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_$]+)/,
|
|
1594
1823
|
/^\s*export\s+(?:const|let)\s+([A-Za-z0-9_$]+)/
|
|
1595
1824
|
],
|
|
1596
|
-
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*class\s+(\w+)/],
|
|
1825
|
+
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*(?:module|class)\s+(\w+)/],
|
|
1597
1826
|
go: [/^func\s+(?:\([^)]*\)\s*)?(\w+)/, /^type\s+(\w+)/],
|
|
1598
1827
|
rs: [/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?(?:async\s+)?(?:fn|struct|enum|trait|type)\s+(\w+)/],
|
|
1599
1828
|
c: [/^\s*(?:static\s+|inline\s+|extern\s+)*[A-Za-z_][A-Za-z0-9_\s*]*\b(\w+)\s*\([^;]*\)\s*\{?\s*$/]
|
|
1600
1829
|
};
|
|
1601
1830
|
function familyFor(ext) {
|
|
1602
|
-
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"
|
|
1603
|
-
if (ext === ".py" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
1831
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"].includes(ext)) return FAMILY.ts;
|
|
1832
|
+
if (ext === ".py" || ext === ".rb" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
1604
1833
|
if (ext === ".go") return FAMILY.go;
|
|
1605
1834
|
if (ext === ".rs") return FAMILY.rs;
|
|
1606
1835
|
return FAMILY.c;
|
|
@@ -1620,16 +1849,16 @@ function extractSymbols(text, ext) {
|
|
|
1620
1849
|
return out;
|
|
1621
1850
|
}
|
|
1622
1851
|
function walk(dir, root, files) {
|
|
1623
|
-
if (files.length >=
|
|
1852
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
1624
1853
|
let entries;
|
|
1625
1854
|
try {
|
|
1626
|
-
entries =
|
|
1855
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1627
1856
|
} catch {
|
|
1628
1857
|
return;
|
|
1629
1858
|
}
|
|
1630
1859
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1631
|
-
if (files.length >=
|
|
1632
|
-
const full =
|
|
1860
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
1861
|
+
const full = join3(dir, e.name);
|
|
1633
1862
|
if (e.isDirectory()) {
|
|
1634
1863
|
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".") && full !== root) continue;
|
|
1635
1864
|
walk(full, root, files);
|
|
@@ -1640,11 +1869,12 @@ function walk(dir, root, files) {
|
|
|
1640
1869
|
}
|
|
1641
1870
|
function buildRepoMapSync(cwd, opts = {}) {
|
|
1642
1871
|
const maxTokens = opts.maxTokens ?? 2048;
|
|
1643
|
-
const
|
|
1644
|
-
walk(cwd, cwd,
|
|
1645
|
-
|
|
1872
|
+
const scanned = [];
|
|
1873
|
+
walk(cwd, cwd, scanned);
|
|
1874
|
+
scanned.sort(
|
|
1646
1875
|
(a, b) => relative(cwd, a).split(sep).length - relative(cwd, b).split(sep).length || a.localeCompare(b)
|
|
1647
1876
|
);
|
|
1877
|
+
const files = scanned.slice(0, MAX_FILES);
|
|
1648
1878
|
const lines = [];
|
|
1649
1879
|
let used = 0;
|
|
1650
1880
|
let truncated = false;
|
|
@@ -1684,13 +1914,50 @@ function createRepoMapTool() {
|
|
|
1684
1914
|
}
|
|
1685
1915
|
|
|
1686
1916
|
// src/tools/diagnostics.ts
|
|
1687
|
-
import { extname as
|
|
1917
|
+
import { extname as extname3, relative as relative2 } from "path";
|
|
1688
1918
|
import { z as z14 } from "zod";
|
|
1689
1919
|
|
|
1690
1920
|
// src/lsp.ts
|
|
1691
1921
|
import { spawn } from "child_process";
|
|
1692
1922
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1693
|
-
import {
|
|
1923
|
+
import { extname as extname2 } from "path";
|
|
1924
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
1925
|
+
var LANGUAGE_IDS = {
|
|
1926
|
+
".ts": "typescript",
|
|
1927
|
+
".tsx": "typescriptreact",
|
|
1928
|
+
".mts": "typescript",
|
|
1929
|
+
".cts": "typescript",
|
|
1930
|
+
".js": "javascript",
|
|
1931
|
+
".jsx": "javascriptreact",
|
|
1932
|
+
".mjs": "javascript",
|
|
1933
|
+
".cjs": "javascript",
|
|
1934
|
+
".py": "python",
|
|
1935
|
+
".rs": "rust",
|
|
1936
|
+
".go": "go",
|
|
1937
|
+
".java": "java",
|
|
1938
|
+
".c": "c",
|
|
1939
|
+
".h": "c",
|
|
1940
|
+
".cpp": "cpp",
|
|
1941
|
+
".hpp": "cpp",
|
|
1942
|
+
".cs": "csharp",
|
|
1943
|
+
".rb": "ruby",
|
|
1944
|
+
".php": "php",
|
|
1945
|
+
".json": "json",
|
|
1946
|
+
".md": "markdown",
|
|
1947
|
+
".sh": "shellscript",
|
|
1948
|
+
".ps1": "powershell",
|
|
1949
|
+
".yaml": "yaml",
|
|
1950
|
+
".yml": "yaml",
|
|
1951
|
+
".toml": "toml",
|
|
1952
|
+
".html": "html",
|
|
1953
|
+
".css": "css"
|
|
1954
|
+
};
|
|
1955
|
+
function languageIdFor(file) {
|
|
1956
|
+
return LANGUAGE_IDS[extname2(file).toLowerCase()] ?? "plaintext";
|
|
1957
|
+
}
|
|
1958
|
+
function useShellForLsp(command, platform = process.platform) {
|
|
1959
|
+
return platform === "win32" && !/\.(exe|mjs|cjs|js)$/i.test(command.trim());
|
|
1960
|
+
}
|
|
1694
1961
|
function encodeLsp(msg) {
|
|
1695
1962
|
const body = Buffer.from(JSON.stringify(msg), "utf8");
|
|
1696
1963
|
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r
|
|
@@ -1722,89 +1989,281 @@ function createLspParser(onMessage) {
|
|
|
1722
1989
|
}
|
|
1723
1990
|
};
|
|
1724
1991
|
}
|
|
1725
|
-
function
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1992
|
+
function extractHoverText(contents) {
|
|
1993
|
+
if (!contents) return "";
|
|
1994
|
+
if (typeof contents === "string") return contents.trim();
|
|
1995
|
+
if (Array.isArray(contents)) return contents.map(extractHoverText).filter(Boolean).join("\n\n");
|
|
1996
|
+
if (typeof contents === "object" && "value" in contents && typeof contents.value === "string") {
|
|
1997
|
+
return contents.value.trim();
|
|
1998
|
+
}
|
|
1999
|
+
return "";
|
|
2000
|
+
}
|
|
2001
|
+
function normalizeLocations(raw) {
|
|
2002
|
+
const items = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
2003
|
+
const out = [];
|
|
2004
|
+
for (const item of items) {
|
|
2005
|
+
if (!item || typeof item !== "object") continue;
|
|
2006
|
+
const rec = item;
|
|
2007
|
+
const uri = rec.uri ?? rec.targetUri;
|
|
2008
|
+
const start = rec.range?.start ?? rec.targetSelectionRange?.start ?? rec.targetRange?.start;
|
|
2009
|
+
if (!uri) continue;
|
|
2010
|
+
let path = uri;
|
|
2011
|
+
try {
|
|
2012
|
+
if (uri.startsWith("file:")) path = fileURLToPath(uri);
|
|
2013
|
+
} catch {
|
|
2014
|
+
}
|
|
2015
|
+
out.push({
|
|
2016
|
+
uri,
|
|
2017
|
+
path,
|
|
2018
|
+
line: start?.line ?? 0,
|
|
2019
|
+
character: start?.character ?? 0
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
return out;
|
|
2023
|
+
}
|
|
2024
|
+
function createWarmLspClient(opts) {
|
|
2025
|
+
let child;
|
|
2026
|
+
let initPromise;
|
|
2027
|
+
let nextId = 2;
|
|
2028
|
+
const opened = /* @__PURE__ */ new Map();
|
|
2029
|
+
const pendingRpc = /* @__PURE__ */ new Map();
|
|
2030
|
+
const diagWaiters = /* @__PURE__ */ new Map();
|
|
2031
|
+
const send = (msg) => {
|
|
2032
|
+
try {
|
|
2033
|
+
child?.stdin?.write(encodeLsp(msg));
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
const resetState = () => {
|
|
2038
|
+
child = void 0;
|
|
2039
|
+
initPromise = void 0;
|
|
2040
|
+
opened.clear();
|
|
2041
|
+
for (const [, resolve] of pendingRpc) resolve(void 0);
|
|
2042
|
+
pendingRpc.clear();
|
|
2043
|
+
for (const [, waiters] of diagWaiters) {
|
|
2044
|
+
for (const w of waiters) w([]);
|
|
2045
|
+
}
|
|
2046
|
+
diagWaiters.clear();
|
|
2047
|
+
};
|
|
2048
|
+
const ensureStarted = () => {
|
|
2049
|
+
if (initPromise && child && !child.killed) return initPromise;
|
|
2050
|
+
opened.clear();
|
|
2051
|
+
initPromise = new Promise((resolveInit) => {
|
|
2052
|
+
const proc = spawn(opts.server.command, opts.server.args ?? [], {
|
|
2053
|
+
cwd: opts.cwd,
|
|
2054
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
2055
|
+
shell: useShellForLsp(opts.server.command)
|
|
2056
|
+
});
|
|
2057
|
+
child = proc;
|
|
2058
|
+
let initSettled = false;
|
|
2059
|
+
const settleInit = () => {
|
|
2060
|
+
if (!initSettled) {
|
|
2061
|
+
initSettled = true;
|
|
2062
|
+
resolveInit();
|
|
1747
2063
|
}
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
if (msg.id === 1 && msg.result) {
|
|
1755
|
-
send({ jsonrpc: "2.0", method: "initialized", params: {} });
|
|
1756
|
-
let text = "";
|
|
1757
|
-
try {
|
|
1758
|
-
text = readFileSync4(opts.file, "utf8");
|
|
1759
|
-
} catch {
|
|
2064
|
+
};
|
|
2065
|
+
const parser = createLspParser((msg) => {
|
|
2066
|
+
if (msg.id === 1 && msg.result !== void 0) {
|
|
2067
|
+
send({ jsonrpc: "2.0", method: "initialized", params: {} });
|
|
2068
|
+
settleInit();
|
|
2069
|
+
return;
|
|
1760
2070
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
(msg.params.diagnostics ?? []).map((d) => ({
|
|
2071
|
+
if (msg.id !== void 0 && pendingRpc.has(msg.id)) {
|
|
2072
|
+
const fn = pendingRpc.get(msg.id);
|
|
2073
|
+
pendingRpc.delete(msg.id);
|
|
2074
|
+
fn?.(msg.result);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
if (msg.method === "textDocument/publishDiagnostics" && msg.params?.uri) {
|
|
2078
|
+
const uri = msg.params.uri;
|
|
2079
|
+
const diags = (msg.params.diagnostics ?? []).map((d) => ({
|
|
1770
2080
|
line: d.range?.start?.line ?? 0,
|
|
1771
2081
|
character: d.range?.start?.character ?? 0,
|
|
1772
2082
|
severity: d.severity ?? 1,
|
|
1773
2083
|
message: String(d.message ?? "")
|
|
1774
|
-
}))
|
|
1775
|
-
|
|
1776
|
-
|
|
2084
|
+
}));
|
|
2085
|
+
const list = diagWaiters.get(uri);
|
|
2086
|
+
if (list?.length) {
|
|
2087
|
+
diagWaiters.delete(uri);
|
|
2088
|
+
for (const w of list) w(diags);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
2092
|
+
proc.stdout?.on("data", (c) => parser.push(c));
|
|
2093
|
+
proc.on("error", () => {
|
|
2094
|
+
resetState();
|
|
2095
|
+
settleInit();
|
|
2096
|
+
});
|
|
2097
|
+
proc.on("close", () => {
|
|
2098
|
+
resetState();
|
|
2099
|
+
settleInit();
|
|
2100
|
+
});
|
|
2101
|
+
send({
|
|
2102
|
+
jsonrpc: "2.0",
|
|
2103
|
+
id: 1,
|
|
2104
|
+
method: "initialize",
|
|
2105
|
+
params: { processId: process.pid, capabilities: {}, rootUri: pathToFileURL(opts.cwd).href }
|
|
2106
|
+
});
|
|
1777
2107
|
});
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
2108
|
+
return initPromise;
|
|
2109
|
+
};
|
|
2110
|
+
const syncDocument = (file) => {
|
|
2111
|
+
const uri = pathToFileURL(file).href;
|
|
2112
|
+
let text = "";
|
|
2113
|
+
try {
|
|
2114
|
+
text = readFileSync4(file, "utf8");
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
const prevVersion = opened.get(uri);
|
|
2118
|
+
if (prevVersion === void 0) {
|
|
2119
|
+
opened.set(uri, 1);
|
|
2120
|
+
send({
|
|
2121
|
+
jsonrpc: "2.0",
|
|
2122
|
+
method: "textDocument/didOpen",
|
|
2123
|
+
params: { textDocument: { uri, languageId: languageIdFor(file), version: 1, text } }
|
|
2124
|
+
});
|
|
2125
|
+
} else {
|
|
2126
|
+
const version = prevVersion + 1;
|
|
2127
|
+
opened.set(uri, version);
|
|
2128
|
+
send({
|
|
2129
|
+
jsonrpc: "2.0",
|
|
2130
|
+
method: "textDocument/didChange",
|
|
2131
|
+
params: {
|
|
2132
|
+
textDocument: { uri, version },
|
|
2133
|
+
contentChanges: [{ text }]
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
return uri;
|
|
2138
|
+
};
|
|
2139
|
+
const request = (method, params, timeoutMs = opts.timeoutMs ?? 8e3) => new Promise((resolve) => {
|
|
2140
|
+
const id = nextId++;
|
|
2141
|
+
const timer = setTimeout(() => {
|
|
2142
|
+
pendingRpc.delete(id);
|
|
2143
|
+
resolve(void 0);
|
|
2144
|
+
}, timeoutMs);
|
|
2145
|
+
pendingRpc.set(id, (res) => {
|
|
2146
|
+
clearTimeout(timer);
|
|
2147
|
+
resolve(res);
|
|
1786
2148
|
});
|
|
2149
|
+
send({ jsonrpc: "2.0", id, method, params });
|
|
1787
2150
|
});
|
|
2151
|
+
return {
|
|
2152
|
+
async diagnostics(file, timeoutMs = opts.timeoutMs ?? 8e3) {
|
|
2153
|
+
await ensureStarted();
|
|
2154
|
+
if (!child) return [];
|
|
2155
|
+
const uri = pathToFileURL(file).href;
|
|
2156
|
+
return new Promise((resolve) => {
|
|
2157
|
+
const timer = setTimeout(() => {
|
|
2158
|
+
const list2 = diagWaiters.get(uri) ?? [];
|
|
2159
|
+
diagWaiters.set(
|
|
2160
|
+
uri,
|
|
2161
|
+
list2.filter((w) => w !== onDiag)
|
|
2162
|
+
);
|
|
2163
|
+
resolve([]);
|
|
2164
|
+
}, timeoutMs);
|
|
2165
|
+
const onDiag = (diags) => {
|
|
2166
|
+
clearTimeout(timer);
|
|
2167
|
+
resolve(diags);
|
|
2168
|
+
};
|
|
2169
|
+
const list = diagWaiters.get(uri) ?? [];
|
|
2170
|
+
list.push(onDiag);
|
|
2171
|
+
diagWaiters.set(uri, list);
|
|
2172
|
+
syncDocument(file);
|
|
2173
|
+
});
|
|
2174
|
+
},
|
|
2175
|
+
async hover(file, line, character, timeoutMs) {
|
|
2176
|
+
await ensureStarted();
|
|
2177
|
+
if (!child) return "";
|
|
2178
|
+
const uri = syncDocument(file);
|
|
2179
|
+
const res = await request("textDocument/hover", { textDocument: { uri }, position: { line, character } }, timeoutMs);
|
|
2180
|
+
return extractHoverText(res?.contents);
|
|
2181
|
+
},
|
|
2182
|
+
async definition(file, line, character, timeoutMs) {
|
|
2183
|
+
await ensureStarted();
|
|
2184
|
+
if (!child) return [];
|
|
2185
|
+
const uri = syncDocument(file);
|
|
2186
|
+
const res = await request("textDocument/definition", { textDocument: { uri }, position: { line, character } }, timeoutMs);
|
|
2187
|
+
return normalizeLocations(res);
|
|
2188
|
+
},
|
|
2189
|
+
async references(file, line, character, timeoutMs) {
|
|
2190
|
+
await ensureStarted();
|
|
2191
|
+
if (!child) return [];
|
|
2192
|
+
const uri = syncDocument(file);
|
|
2193
|
+
const res = await request(
|
|
2194
|
+
"textDocument/references",
|
|
2195
|
+
{ textDocument: { uri }, position: { line, character }, context: { includeDeclaration: true } },
|
|
2196
|
+
timeoutMs
|
|
2197
|
+
);
|
|
2198
|
+
return normalizeLocations(res);
|
|
2199
|
+
},
|
|
2200
|
+
async close() {
|
|
2201
|
+
if (!child) return;
|
|
2202
|
+
const proc = child;
|
|
2203
|
+
send({ jsonrpc: "2.0", id: nextId++, method: "shutdown" });
|
|
2204
|
+
send({ jsonrpc: "2.0", method: "exit" });
|
|
2205
|
+
resetState();
|
|
2206
|
+
const killer = setTimeout(() => {
|
|
2207
|
+
try {
|
|
2208
|
+
proc.kill();
|
|
2209
|
+
} catch {
|
|
2210
|
+
}
|
|
2211
|
+
}, 200);
|
|
2212
|
+
killer.unref?.();
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
1788
2215
|
}
|
|
1789
2216
|
|
|
1790
2217
|
// src/tools/diagnostics.ts
|
|
1791
2218
|
var SEVERITY = { 1: "error", 2: "warning", 3: "info", 4: "hint" };
|
|
1792
|
-
var SCHEMA = z14.object({
|
|
2219
|
+
var SCHEMA = z14.object({
|
|
2220
|
+
path: z14.string().describe("File to check (absolute or relative to the working directory)"),
|
|
2221
|
+
action: z14.enum(["diagnostics", "hover", "definition", "references"]).optional().describe("LSP query action, default diagnostics"),
|
|
2222
|
+
line: z14.number().int().min(1).optional().describe("1-based line number for hover, definition, or references"),
|
|
2223
|
+
character: z14.number().int().min(1).optional().describe("1-based column number for hover, definition, or references")
|
|
2224
|
+
});
|
|
1793
2225
|
function createDiagnosticsTool(servers) {
|
|
2226
|
+
const warmPool = /* @__PURE__ */ new Map();
|
|
2227
|
+
const clientFor = (server, cwd) => {
|
|
2228
|
+
const key = `${cwd}::${server.command}::${(server.args ?? []).join("\0")}`;
|
|
2229
|
+
let client = warmPool.get(key);
|
|
2230
|
+
if (!client) {
|
|
2231
|
+
client = createWarmLspClient({ server, cwd, timeoutMs: 15e3 });
|
|
2232
|
+
warmPool.set(key, client);
|
|
2233
|
+
}
|
|
2234
|
+
return client;
|
|
2235
|
+
};
|
|
1794
2236
|
return defineTool({
|
|
1795
2237
|
name: "diagnostics",
|
|
1796
|
-
description: '
|
|
2238
|
+
description: 'Query the configured LSP server(s) on a file for diagnostics (errors/warnings), hover docs, definition, or references. Configure servers under "lsp.servers" in ~/.bccli/config.json, e.g. { "extensions": [".ts"], "command": "typescript-language-server", "args": ["--stdio"] }.',
|
|
1797
2239
|
schema: SCHEMA,
|
|
1798
2240
|
kind: "read",
|
|
1799
2241
|
target: (input) => input.path,
|
|
1800
2242
|
async run(input, ctx) {
|
|
1801
2243
|
const file = resolvePath(ctx.cwd, input.path);
|
|
1802
|
-
const ext =
|
|
2244
|
+
const ext = extname3(file).toLowerCase();
|
|
1803
2245
|
const server = servers.find((s) => s.extensions.includes(ext));
|
|
1804
2246
|
if (!server) {
|
|
1805
2247
|
return { output: t("No LSP server is configured for {ext} files.", { ext: ext || "(none)" }), isError: true };
|
|
1806
2248
|
}
|
|
1807
|
-
const
|
|
2249
|
+
const client = clientFor(server, ctx.cwd);
|
|
2250
|
+
const action = input.action ?? "diagnostics";
|
|
2251
|
+
const line0 = Math.max(0, (input.line ?? 1) - 1);
|
|
2252
|
+
const char0 = Math.max(0, (input.character ?? 1) - 1);
|
|
2253
|
+
if (action === "hover") {
|
|
2254
|
+
const text = await client.hover(file, line0, char0, 15e3);
|
|
2255
|
+
return { output: text || "No hover information." };
|
|
2256
|
+
}
|
|
2257
|
+
if (action === "definition" || action === "references") {
|
|
2258
|
+
const locs = action === "definition" ? await client.definition(file, line0, char0, 15e3) : await client.references(file, line0, char0, 15e3);
|
|
2259
|
+
if (!locs.length) return { output: `No ${action} found.` };
|
|
2260
|
+
const formatted = locs.map((l) => {
|
|
2261
|
+
const rel = relative2(ctx.cwd, l.path).replace(/\\/g, "/") || input.path;
|
|
2262
|
+
return `${rel}:${l.line + 1}:${l.character + 1}`;
|
|
2263
|
+
});
|
|
2264
|
+
return { output: formatted.join("\n") };
|
|
2265
|
+
}
|
|
2266
|
+
const diags = await client.diagnostics(file, 15e3);
|
|
1808
2267
|
if (!diags.length) return { output: "No diagnostics." };
|
|
1809
2268
|
const lines = diags.map((d) => `${input.path}:${d.line + 1}:${d.character + 1} ${SEVERITY[d.severity] ?? "error"} ${d.message}`);
|
|
1810
2269
|
return { output: lines.join("\n"), isError: diags.some((d) => d.severity === 1) };
|
|
@@ -1815,7 +2274,7 @@ function createDiagnosticsTool(servers) {
|
|
|
1815
2274
|
// src/plugins.ts
|
|
1816
2275
|
import { existsSync as existsSync4 } from "fs";
|
|
1817
2276
|
import { createRequire } from "module";
|
|
1818
|
-
import { isAbsolute
|
|
2277
|
+
import { isAbsolute, join as join4 } from "path";
|
|
1819
2278
|
import { z as z15 } from "zod";
|
|
1820
2279
|
var require_ = createRequire(import.meta.url);
|
|
1821
2280
|
function pluginLog(message) {
|
|
@@ -1840,7 +2299,7 @@ function loadPlugins(specs, opts) {
|
|
|
1840
2299
|
for (const spec of specs) {
|
|
1841
2300
|
let file;
|
|
1842
2301
|
try {
|
|
1843
|
-
file =
|
|
2302
|
+
file = isAbsolute(spec) ? spec : spec.startsWith(".") ? join4(opts.home, spec) : require_.resolve(spec);
|
|
1844
2303
|
} catch {
|
|
1845
2304
|
throw new ConfigError(t("Plugin not found: {spec}", { spec }));
|
|
1846
2305
|
}
|
|
@@ -1917,7 +2376,7 @@ function createRuntime(opts) {
|
|
|
1917
2376
|
let config = loadConfig(opts.cwd, env);
|
|
1918
2377
|
let modelRef = opts.args.model ?? config.model;
|
|
1919
2378
|
let reasoning = opts.args.reasoning ?? config.reasoning;
|
|
1920
|
-
const
|
|
2379
|
+
const makeSingleProvider = (ref) => {
|
|
1921
2380
|
const resolved = resolveModel(config, ref, env);
|
|
1922
2381
|
return opts.provider ?? createProvider({
|
|
1923
2382
|
baseURL: resolved.baseURL,
|
|
@@ -1929,6 +2388,25 @@ function createRuntime(opts) {
|
|
|
1929
2388
|
enableProgrammaticToolCalling: lunaPtcAutoEnabled(env)
|
|
1930
2389
|
});
|
|
1931
2390
|
};
|
|
2391
|
+
const makeProvider = (ref) => {
|
|
2392
|
+
const primary = makeSingleProvider(ref);
|
|
2393
|
+
if (opts.provider) return primary;
|
|
2394
|
+
const fallbackRefs = [
|
|
2395
|
+
...opts.args.fallbackModel ? [opts.args.fallbackModel] : [],
|
|
2396
|
+
...config.fallbackModels ?? []
|
|
2397
|
+
].filter((r) => r && r !== ref);
|
|
2398
|
+
if (!fallbackRefs.length) return primary;
|
|
2399
|
+
return withFallbackProvider(
|
|
2400
|
+
primary,
|
|
2401
|
+
() => fallbackRefs.flatMap((fbRef) => {
|
|
2402
|
+
try {
|
|
2403
|
+
return [makeSingleProvider(fbRef)];
|
|
2404
|
+
} catch {
|
|
2405
|
+
return [];
|
|
2406
|
+
}
|
|
2407
|
+
})
|
|
2408
|
+
);
|
|
2409
|
+
};
|
|
1932
2410
|
const provider = makeProvider(modelRef);
|
|
1933
2411
|
const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
|
|
1934
2412
|
const rules = [...config.allow, ...opts.args.allowedTools.map((t2) => TOOL_RULES[t2] ?? t2)];
|
|
@@ -1950,6 +2428,10 @@ function createRuntime(opts) {
|
|
|
1950
2428
|
if (latest) {
|
|
1951
2429
|
session = latest;
|
|
1952
2430
|
history = latest.load();
|
|
2431
|
+
if (!opts.args.model) {
|
|
2432
|
+
const prevModel = latest.lastModel();
|
|
2433
|
+
if (prevModel) modelRef = prevModel;
|
|
2434
|
+
}
|
|
1953
2435
|
}
|
|
1954
2436
|
}
|
|
1955
2437
|
const repoMap = config.repoMap ? buildRepoMapSync(opts.cwd) : void 0;
|
|
@@ -1993,7 +2475,7 @@ function createRuntime(opts) {
|
|
|
1993
2475
|
// Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
|
|
1994
2476
|
baseTools: () => agent.tools.filter((t2) => t2.name !== "task"),
|
|
1995
2477
|
permissions,
|
|
1996
|
-
provider: () => agent.provider,
|
|
2478
|
+
provider: () => opts.provider ? agent.provider : makeProvider(modelRef),
|
|
1997
2479
|
providerFor: makeProvider,
|
|
1998
2480
|
systemPrompt: (childModelRef) => systemFor(childModelRef ?? modelRef),
|
|
1999
2481
|
cwd: opts.cwd,
|
|
@@ -2079,6 +2561,20 @@ function createRuntime(opts) {
|
|
|
2079
2561
|
session = previous;
|
|
2080
2562
|
agent.load(previous.load());
|
|
2081
2563
|
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
2564
|
+
if (!opts.args.model) {
|
|
2565
|
+
const prevModel = previous.lastModel();
|
|
2566
|
+
if (prevModel) {
|
|
2567
|
+
try {
|
|
2568
|
+
const nextProvider = makeProvider(prevModel);
|
|
2569
|
+
modelRef = prevModel;
|
|
2570
|
+
agent.provider = nextProvider;
|
|
2571
|
+
agent.setBudgetModel(prevModel);
|
|
2572
|
+
agent.setContextWindow(contextWindowFor(prevModel, env));
|
|
2573
|
+
agent.setSystemPrompt(systemFor(modelRef));
|
|
2574
|
+
} catch {
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2082
2578
|
todos.set([]);
|
|
2083
2579
|
checkpoints.clear();
|
|
2084
2580
|
turnMarks.length = 0;
|
|
@@ -2127,6 +2623,7 @@ function createRuntime(opts) {
|
|
|
2127
2623
|
const nextProvider = makeProvider(ref);
|
|
2128
2624
|
modelRef = ref;
|
|
2129
2625
|
agent.provider = nextProvider;
|
|
2626
|
+
agent.setBudgetModel(ref);
|
|
2130
2627
|
agent.setContextWindow(contextWindowFor(ref, env));
|
|
2131
2628
|
agent.setSystemPrompt(systemFor(modelRef));
|
|
2132
2629
|
},
|