@botconnector/bccli 0.4.0-beta.36 → 0.4.0-beta.37
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/README.md +1 -0
- package/dist/args-MNVI2OJI.js +13 -0
- package/dist/chunk-3HCOAJEQ.js +190 -0
- package/dist/{chunk-SJH3YLW4.js → chunk-3JRBXJVB.js} +4 -1
- package/dist/{chunk-CZMTWXCE.js → chunk-4RMP4CNT.js} +38 -124
- package/dist/{chunk-QKWSDZJQ.js → chunk-7TABTC32.js} +44 -118
- package/dist/{chunk-BTCJNE7R.js → chunk-ASTNW3X2.js} +17 -5
- package/dist/chunk-BB4CVWHS.js +115 -0
- package/dist/{chunk-NSCC5VMS.js → chunk-FCQCZCKO.js} +2 -2
- package/dist/chunk-H5N4ST6B.js +149 -0
- package/dist/{chunk-5LSTEXSG.js → chunk-HPZSXX4W.js} +6 -2
- package/dist/chunk-HXSREA7S.js +785 -0
- package/dist/{chunk-ELML4KVQ.js → chunk-R3ND76LW.js} +16 -2
- package/dist/{chunk-SZRM2VOB.js → chunk-TASYQW7B.js} +23 -2
- package/dist/{chunk-3DHJO7NC.js → chunk-U66JJ6RN.js} +1 -1
- package/dist/{chunk-3NTTXK4Y.js → chunk-YURAIW7A.js} +1 -1
- package/dist/cli.js +633 -290
- package/dist/{config-53MC3AH2.js → config-JRO5EOKZ.js} +5 -2
- package/dist/{integrationCli-JWLY6E7E.js → integrationCli-ZTZTQD2I.js} +4 -2
- package/dist/{login-LKAO5PVR.js → login-MHVCON5R.js} +1 -1
- package/dist/{mcpCli-THHBIX7I.js → mcpCli-5ZSAMBJS.js} +33 -5
- package/dist/providerCli-Z3TZ5XSZ.js +14 -0
- package/dist/session-IYVTCUBC.js +12 -0
- package/dist/{ui-I5BTZATT.js → ui-YLIKFLGN.js} +87 -38
- package/package.json +2 -1
- package/dist/args-GJUGK4F4.js +0 -12
- package/dist/chunk-HKHKYFZI.js +0 -297
- package/dist/providerCli-WJXJW3W2.js +0 -12
- package/dist/session-7UB77CEG.js +0 -9
package/dist/cli.js
CHANGED
|
@@ -1,51 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BUILTIN_AGENTS,
|
|
4
|
+
appendMemory,
|
|
5
|
+
bashTool,
|
|
4
6
|
buildSystemPrompt,
|
|
7
|
+
createBashTool,
|
|
8
|
+
defineTool,
|
|
5
9
|
ignorePatterns,
|
|
6
10
|
loadAgentDefs,
|
|
7
11
|
loadCommands,
|
|
8
12
|
loadSkills,
|
|
9
13
|
normalizeGlobPath,
|
|
10
14
|
parseFrontmatter,
|
|
15
|
+
resolveWorktree,
|
|
16
|
+
runHooks,
|
|
11
17
|
runPrint,
|
|
12
18
|
validateGlobPattern
|
|
13
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HXSREA7S.js";
|
|
14
20
|
import {
|
|
15
21
|
helpText,
|
|
16
22
|
parseCliArgs
|
|
17
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-ASTNW3X2.js";
|
|
18
24
|
import {
|
|
19
25
|
Permissions
|
|
20
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-R3ND76LW.js";
|
|
21
27
|
import {
|
|
22
28
|
addProviderKey,
|
|
23
|
-
completionAssistantMessage,
|
|
24
|
-
createProvider,
|
|
25
|
-
hasPendingProgrammaticReplay,
|
|
26
29
|
listAllModels
|
|
27
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-BB4CVWHS.js";
|
|
28
31
|
import {
|
|
29
32
|
providerName,
|
|
30
33
|
readGlobalConfigFile
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-3NTTXK4Y.js";
|
|
34
|
+
} from "./chunk-U66JJ6RN.js";
|
|
33
35
|
import {
|
|
34
36
|
runLogin
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-FCQCZCKO.js";
|
|
36
38
|
import {
|
|
37
39
|
Session,
|
|
38
40
|
pruneSessions
|
|
39
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3JRBXJVB.js";
|
|
42
|
+
import {
|
|
43
|
+
completionAssistantMessage,
|
|
44
|
+
createProvider,
|
|
45
|
+
hasPendingProgrammaticReplay,
|
|
46
|
+
imageUserMessage
|
|
47
|
+
} from "./chunk-7TABTC32.js";
|
|
48
|
+
import "./chunk-YURAIW7A.js";
|
|
49
|
+
import {
|
|
50
|
+
FileOAuthProvider,
|
|
51
|
+
hasStoredTokens
|
|
52
|
+
} from "./chunk-3HCOAJEQ.js";
|
|
53
|
+
import {
|
|
54
|
+
expandEnvVars
|
|
55
|
+
} from "./chunk-TASYQW7B.js";
|
|
40
56
|
import {
|
|
41
57
|
ConfigError,
|
|
42
58
|
bccliHome,
|
|
43
59
|
loadConfig,
|
|
60
|
+
resolveModel
|
|
61
|
+
} from "./chunk-H5N4ST6B.js";
|
|
62
|
+
import {
|
|
44
63
|
resolveLanguage,
|
|
45
|
-
resolveModel,
|
|
46
64
|
setLanguage,
|
|
47
65
|
t
|
|
48
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-4RMP4CNT.js";
|
|
49
67
|
|
|
50
68
|
// src/textToolCalls.ts
|
|
51
69
|
var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
|
|
@@ -113,131 +131,11 @@ function recoverTextToolCalls(text, tools) {
|
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
// src/tools/index.ts
|
|
116
|
-
import { z as
|
|
117
|
-
|
|
118
|
-
// src/tools/bash.ts
|
|
119
|
-
import { spawn } from "child_process";
|
|
120
|
-
import { z } from "zod";
|
|
121
|
-
|
|
122
|
-
// src/tools/types.ts
|
|
123
|
-
function defineTool(tool) {
|
|
124
|
-
return tool;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// src/tools/bash.ts
|
|
128
|
-
var HALF = 15e3;
|
|
129
|
-
function killProcessTree(pid, platform = process.platform, spawnFn = spawn) {
|
|
130
|
-
if (platform === "win32") {
|
|
131
|
-
const killer = spawnFn("taskkill", ["/pid", String(pid), "/T", "/F"]);
|
|
132
|
-
killer.on("error", () => {
|
|
133
|
-
try {
|
|
134
|
-
process.kill(pid);
|
|
135
|
-
} catch {
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
process.kill(-pid, "SIGKILL");
|
|
142
|
-
} catch {
|
|
143
|
-
try {
|
|
144
|
-
process.kill(pid, "SIGKILL");
|
|
145
|
-
} catch {
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
var active = /* @__PURE__ */ new Set();
|
|
150
|
-
var exitHookInstalled = false;
|
|
151
|
-
function killAllCommands() {
|
|
152
|
-
for (const kill of active) kill();
|
|
153
|
-
}
|
|
154
|
-
function runCommand(command, opts) {
|
|
155
|
-
return new Promise((resolve2) => {
|
|
156
|
-
const child = spawn(command, {
|
|
157
|
-
cwd: opts.cwd,
|
|
158
|
-
shell: true,
|
|
159
|
-
detached: process.platform !== "win32",
|
|
160
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
161
|
-
});
|
|
162
|
-
if (!exitHookInstalled) {
|
|
163
|
-
process.on("exit", killAllCommands);
|
|
164
|
-
exitHookInstalled = true;
|
|
165
|
-
}
|
|
166
|
-
child.stdout.setEncoding("utf8");
|
|
167
|
-
child.stderr.setEncoding("utf8");
|
|
168
|
-
let head = "";
|
|
169
|
-
let tail = "";
|
|
170
|
-
let dropped = 0;
|
|
171
|
-
let timedOut = false;
|
|
172
|
-
let aborted = false;
|
|
173
|
-
const onData = (data) => {
|
|
174
|
-
let s = data;
|
|
175
|
-
if (head.length < HALF) {
|
|
176
|
-
const take = s.slice(0, HALF - head.length);
|
|
177
|
-
head += take;
|
|
178
|
-
s = s.slice(take.length);
|
|
179
|
-
}
|
|
180
|
-
if (s) {
|
|
181
|
-
tail += s;
|
|
182
|
-
if (tail.length > HALF) {
|
|
183
|
-
dropped += tail.length - HALF;
|
|
184
|
-
tail = tail.slice(-HALF);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
child.stdout.on("data", onData);
|
|
189
|
-
child.stderr.on("data", onData);
|
|
190
|
-
const kill = () => {
|
|
191
|
-
if (child.pid !== void 0) killProcessTree(child.pid);
|
|
192
|
-
};
|
|
193
|
-
const killAsAborted = () => {
|
|
194
|
-
aborted = true;
|
|
195
|
-
kill();
|
|
196
|
-
};
|
|
197
|
-
active.add(killAsAborted);
|
|
198
|
-
const timer = setTimeout(() => {
|
|
199
|
-
timedOut = true;
|
|
200
|
-
kill();
|
|
201
|
-
}, opts.timeoutMs);
|
|
202
|
-
const onAbort = () => {
|
|
203
|
-
aborted = true;
|
|
204
|
-
kill();
|
|
205
|
-
};
|
|
206
|
-
if (opts.signal.aborted) onAbort();
|
|
207
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
208
|
-
const finish = (exitCode, extra = "") => {
|
|
209
|
-
active.delete(killAsAborted);
|
|
210
|
-
clearTimeout(timer);
|
|
211
|
-
opts.signal.removeEventListener("abort", onAbort);
|
|
212
|
-
const output = dropped ? `${head}
|
|
213
|
-
\u2026 [${dropped} characters truncated] \u2026
|
|
214
|
-
${tail}` : head + tail;
|
|
215
|
-
resolve2({ output: output + extra, exitCode, timedOut, aborted });
|
|
216
|
-
};
|
|
217
|
-
child.on("error", (error) => finish(null, error.message));
|
|
218
|
-
child.on("close", (code) => finish(code));
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
var bashTool = defineTool({
|
|
222
|
-
name: "bash",
|
|
223
|
-
description: "Run a shell command in the project directory. No interactive input. Default timeout 2 minutes (max 10). Use for tests, builds, git, and other CLI tasks.",
|
|
224
|
-
schema: z.object({
|
|
225
|
-
command: z.string().describe("The shell command to run"),
|
|
226
|
-
timeout_ms: z.number().int().min(1e3).max(6e5).optional().describe("Timeout in milliseconds")
|
|
227
|
-
}),
|
|
228
|
-
kind: "bash",
|
|
229
|
-
target: (input) => input.command,
|
|
230
|
-
async run(input, ctx) {
|
|
231
|
-
const r = await runCommand(input.command, { cwd: ctx.cwd, timeoutMs: input.timeout_ms ?? 12e4, signal: ctx.signal });
|
|
232
|
-
const status = r.timedOut ? `[stopped: timeout after ${Math.round((input.timeout_ms ?? 12e4) / 1e3)} seconds]` : r.aborted ? "[cancelled by the user]" : `[exit code ${r.exitCode}]`;
|
|
233
|
-
return { output: `${r.output.trimEnd()}
|
|
234
|
-
${status}`.trimStart(), isError: r.exitCode !== 0 };
|
|
235
|
-
}
|
|
236
|
-
});
|
|
134
|
+
import { z as z7 } from "zod";
|
|
237
135
|
|
|
238
136
|
// src/tools/edit.ts
|
|
239
137
|
import { readFile, writeFile } from "fs/promises";
|
|
240
|
-
import { z
|
|
138
|
+
import { z } from "zod";
|
|
241
139
|
|
|
242
140
|
// src/diff.ts
|
|
243
141
|
import { structuredPatch } from "diff";
|
|
@@ -275,11 +173,11 @@ function errorCode(error) {
|
|
|
275
173
|
}
|
|
276
174
|
|
|
277
175
|
// src/tools/edit.ts
|
|
278
|
-
var schema =
|
|
279
|
-
path:
|
|
280
|
-
old_string:
|
|
281
|
-
new_string:
|
|
282
|
-
replace_all:
|
|
176
|
+
var schema = z.object({
|
|
177
|
+
path: z.string().describe("File path, relative to the project or absolute"),
|
|
178
|
+
old_string: z.string().describe("Exact text to replace; must be unique unless replace_all"),
|
|
179
|
+
new_string: z.string().describe("Replacement text"),
|
|
180
|
+
replace_all: z.boolean().optional().describe("Replace every occurrence")
|
|
283
181
|
});
|
|
284
182
|
function countOccurrences(haystack, needle) {
|
|
285
183
|
let count = 0;
|
|
@@ -351,7 +249,11 @@ var editTool = defineTool({
|
|
|
351
249
|
});
|
|
352
250
|
|
|
353
251
|
// src/tools/fetch.ts
|
|
354
|
-
import {
|
|
252
|
+
import { lookup as dnsCbLookup } from "dns";
|
|
253
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
254
|
+
import { isIP } from "net";
|
|
255
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
256
|
+
import { z as z2 } from "zod";
|
|
355
257
|
var MAX_CHARS = 12e3;
|
|
356
258
|
var FOCUS_CHUNKS = 3;
|
|
357
259
|
var FOCUS_SIZE = 800;
|
|
@@ -406,6 +308,21 @@ function isPrivateHost(host) {
|
|
|
406
308
|
return !h.includes(".");
|
|
407
309
|
}
|
|
408
310
|
var words = (s) => s.toLowerCase().match(/[\p{L}\p{N}]{2,}/gu) ?? [];
|
|
311
|
+
function pinLookup(pinned) {
|
|
312
|
+
return (hostname, options, callback) => {
|
|
313
|
+
const opts = typeof options === "object" && options !== null ? options : {};
|
|
314
|
+
const family = typeof options === "number" ? options : opts.family;
|
|
315
|
+
const realDns = () => dnsCbLookup(hostname, typeof options === "number" ? { family: options } : opts, callback);
|
|
316
|
+
if (!pinned.length) return realDns();
|
|
317
|
+
const list = family ? pinned.filter((a) => a.family === family) : pinned;
|
|
318
|
+
if (!list.length) {
|
|
319
|
+
callback(Object.assign(new Error(`no pinned address for family ${family}`), { code: "ENOTFOUND" }), "");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (opts.all) callback(null, list);
|
|
323
|
+
else callback(null, list[0].address, list[0].family);
|
|
324
|
+
};
|
|
325
|
+
}
|
|
409
326
|
function pickRelevant(text, query, count = FOCUS_CHUNKS, size = FOCUS_SIZE) {
|
|
410
327
|
const chunks = [];
|
|
411
328
|
let cur = "";
|
|
@@ -456,22 +373,38 @@ var withCost = (text, what) => `${what} \xB7 ${t("~{n} tokens", { n: tokensOf(te
|
|
|
456
373
|
function createFetchTool(opts = {}) {
|
|
457
374
|
const keenableURL = opts.keenableURL === void 0 ? "https://api.keenable.ai/v1/fetch/public" : opts.keenableURL;
|
|
458
375
|
const isPrivate = opts.isPrivate ?? isPrivateHost;
|
|
376
|
+
const lookup = opts.lookup ?? ((hostname) => dnsLookup(hostname, { all: true }));
|
|
459
377
|
return defineTool({
|
|
460
378
|
name: "fetch",
|
|
461
379
|
description: `Fetch a URL (http/https) as text/markdown. Costs the user tokens, so ask for as little as you need:
|
|
462
380
|
- Pass \`prompt\` (what you want to know from the page) to get only the relevant excerpt instead of the whole page. Prefer this whenever you are looking for specific facts.
|
|
463
381
|
- Without \`prompt\` you get the first ${MAX_CHARS} characters; the reply tells you the \`offset\` to continue from.
|
|
464
382
|
- Fetching a page that is already in this conversation is wasteful; reuse what you have.`,
|
|
465
|
-
schema:
|
|
466
|
-
url:
|
|
467
|
-
prompt:
|
|
468
|
-
offset:
|
|
383
|
+
schema: z2.object({
|
|
384
|
+
url: z2.string().describe("Absolute http(s) URL"),
|
|
385
|
+
prompt: z2.string().max(2e3).optional().describe("What to look for in the page; returns only the relevant excerpt"),
|
|
386
|
+
offset: z2.number().int().min(0).optional().describe("Start reading at this character (to continue a truncated page)")
|
|
469
387
|
}),
|
|
470
388
|
kind: "fetch",
|
|
471
389
|
target: (input) => input.url,
|
|
472
390
|
async run(input, ctx) {
|
|
473
391
|
if (!/^https?:\/\//i.test(input.url)) return { output: "URL must start with http:// or https://", isError: true };
|
|
474
392
|
const target = new URL(input.url);
|
|
393
|
+
let pinned;
|
|
394
|
+
if (!isPrivate(target.hostname) && isIP(target.hostname) === 0) {
|
|
395
|
+
try {
|
|
396
|
+
const addresses = await lookup(target.hostname);
|
|
397
|
+
const internal = addresses.find((a) => isPrivate(a.address));
|
|
398
|
+
if (internal) {
|
|
399
|
+
return {
|
|
400
|
+
output: t("Blocked: {host} resolves to a private address ({addr}). Fetching an internal host through a public DNS name is not allowed.", { host: target.hostname, addr: internal.address }),
|
|
401
|
+
isError: true
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
pinned = addresses;
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
475
408
|
const prompt = input.prompt?.trim();
|
|
476
409
|
const askKeenable = !!prompt && !input.offset && !!keenableURL && !target.username && !target.password && !isPrivate(target.hostname);
|
|
477
410
|
if (askKeenable) {
|
|
@@ -496,7 +429,7 @@ ${input.url}`);
|
|
|
496
429
|
const cached = !!page;
|
|
497
430
|
if (cached) ctx.refundFetch?.();
|
|
498
431
|
if (!page) {
|
|
499
|
-
const loaded = await loadPage(input.url, ctx.signal);
|
|
432
|
+
const loaded = await loadPage(input.url, ctx.signal, pinned, opts.fetchPage);
|
|
500
433
|
if ("error" in loaded) return { output: loaded.error, isError: true };
|
|
501
434
|
page = loaded;
|
|
502
435
|
remember(`p
|
|
@@ -543,12 +476,19 @@ async function extractWithKeenable(endpoint, url, prompt, signal) {
|
|
|
543
476
|
return void 0;
|
|
544
477
|
}
|
|
545
478
|
}
|
|
546
|
-
async function loadPage(inputUrl, signal) {
|
|
479
|
+
async function loadPage(inputUrl, signal, pinned, fetchPage) {
|
|
547
480
|
let url = new URL(inputUrl);
|
|
548
|
-
|
|
481
|
+
const dispatcher = pinned?.length ? new Agent({ connect: { lookup: pinLookup(pinned) } }) : void 0;
|
|
482
|
+
const doFetch = fetchPage ?? (dispatcher ? undiciFetch : fetch);
|
|
549
483
|
try {
|
|
484
|
+
let res;
|
|
550
485
|
for (let hop = 0; hop < 5; hop++) {
|
|
551
|
-
res = await
|
|
486
|
+
res = await doFetch(url, {
|
|
487
|
+
headers: { accept: ACCEPT },
|
|
488
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)]),
|
|
489
|
+
redirect: "manual",
|
|
490
|
+
...dispatcher ? { dispatcher } : {}
|
|
491
|
+
});
|
|
552
492
|
const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
|
|
553
493
|
if (!location) break;
|
|
554
494
|
const next = new URL(location, url);
|
|
@@ -559,21 +499,24 @@ async function loadPage(inputUrl, signal) {
|
|
|
559
499
|
await res.body?.cancel();
|
|
560
500
|
url = next;
|
|
561
501
|
}
|
|
502
|
+
if (!res) return { error: `Failed to fetch ${inputUrl}` };
|
|
503
|
+
const { text: body, truncated } = await readCapped(res);
|
|
504
|
+
const type = res.headers.get("content-type") ?? "";
|
|
505
|
+
const text = type.includes("html") ? htmlToText(body) : body;
|
|
506
|
+
if (!res.ok) return { error: `HTTP ${res.status} from ${inputUrl}: ${text.slice(0, 500)}` };
|
|
507
|
+
return { text, truncated, at: Date.now() };
|
|
562
508
|
} catch (error) {
|
|
563
509
|
return { error: `Failed to fetch ${inputUrl}: ${describeFetchError(error)}` };
|
|
510
|
+
} finally {
|
|
511
|
+
await dispatcher?.close().catch(() => {
|
|
512
|
+
});
|
|
564
513
|
}
|
|
565
|
-
if (!res) return { error: `Failed to fetch ${inputUrl}` };
|
|
566
|
-
const { text: body, truncated } = await readCapped(res);
|
|
567
|
-
const type = res.headers.get("content-type") ?? "";
|
|
568
|
-
const text = type.includes("html") ? htmlToText(body) : body;
|
|
569
|
-
if (!res.ok) return { error: `HTTP ${res.status} from ${inputUrl}: ${text.slice(0, 500)}` };
|
|
570
|
-
return { text, truncated, at: Date.now() };
|
|
571
514
|
}
|
|
572
515
|
var fetchTool = createFetchTool();
|
|
573
516
|
|
|
574
517
|
// src/tools/glob.ts
|
|
575
518
|
import { glob as nativeGlob } from "fs/promises";
|
|
576
|
-
import { z as
|
|
519
|
+
import { z as z3 } from "zod";
|
|
577
520
|
|
|
578
521
|
// src/abort.ts
|
|
579
522
|
var ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
@@ -614,9 +557,9 @@ var MAX_OUTPUT_CHARS = 24e3;
|
|
|
614
557
|
var globTool = defineTool({
|
|
615
558
|
name: "glob",
|
|
616
559
|
description: 'Find files by glob pattern, e.g. "src/**/*.ts". Respects .gitignore. Max 500 results.',
|
|
617
|
-
schema:
|
|
618
|
-
pattern:
|
|
619
|
-
path:
|
|
560
|
+
schema: z3.object({
|
|
561
|
+
pattern: z3.string().describe("Glob pattern"),
|
|
562
|
+
path: z3.string().optional().describe("Base directory, default project root")
|
|
620
563
|
}),
|
|
621
564
|
kind: "read",
|
|
622
565
|
target: (input) => input.pattern,
|
|
@@ -654,7 +597,7 @@ var globTool = defineTool({
|
|
|
654
597
|
import { execFile } from "child_process";
|
|
655
598
|
import { readFile as readFile2 } from "fs/promises";
|
|
656
599
|
import { glob as nativeGlob2 } from "fs/promises";
|
|
657
|
-
import { z as
|
|
600
|
+
import { z as z4 } from "zod";
|
|
658
601
|
var MAX_MATCHES = 200;
|
|
659
602
|
var MAX_OUTPUT_CHARS2 = 24e3;
|
|
660
603
|
async function grepJs(pattern, opts) {
|
|
@@ -703,11 +646,11 @@ function grepRg(pattern, opts) {
|
|
|
703
646
|
var grepTool = defineTool({
|
|
704
647
|
name: "grep",
|
|
705
648
|
description: "Search file contents with a regular expression. Respects .gitignore. Returns path:line:text (max 200).",
|
|
706
|
-
schema:
|
|
707
|
-
pattern:
|
|
708
|
-
path:
|
|
709
|
-
glob:
|
|
710
|
-
ignore_case:
|
|
649
|
+
schema: z4.object({
|
|
650
|
+
pattern: z4.string().describe("Regular expression"),
|
|
651
|
+
path: z4.string().optional().describe("Directory to search, default project root"),
|
|
652
|
+
glob: z4.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
|
|
653
|
+
ignore_case: z4.boolean().optional()
|
|
711
654
|
}),
|
|
712
655
|
kind: "read",
|
|
713
656
|
target: (input) => input.pattern,
|
|
@@ -743,71 +686,120 @@ var grepTool = defineTool({
|
|
|
743
686
|
|
|
744
687
|
// src/tools/read.ts
|
|
745
688
|
import { readFile as readFile3 } from "fs/promises";
|
|
746
|
-
import { z as
|
|
689
|
+
import { z as z5 } from "zod";
|
|
747
690
|
var MAX_LINE = 2e3;
|
|
748
691
|
var MAX_OUTPUT_CHARS3 = 4e4;
|
|
749
|
-
var
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [line truncated]` : line;
|
|
779
|
-
const rendered = `${String(start + i + 1).padStart(6)} ${text}`;
|
|
780
|
-
const extra = rendered.length + (shown.length ? 1 : 0);
|
|
781
|
-
if (shown.length && size + extra > MAX_OUTPUT_CHARS3) break;
|
|
782
|
-
shown.push(rendered);
|
|
783
|
-
size += extra;
|
|
784
|
-
if (size >= MAX_OUTPUT_CHARS3) break;
|
|
785
|
-
}
|
|
786
|
-
const body = shown.join("\n");
|
|
787
|
-
const remaining = lines.length - start - shown.length;
|
|
788
|
-
const tokenBudgetHit = shown.length < requested.length;
|
|
789
|
-
const nextOffset = start + shown.length + 1;
|
|
790
|
-
ctx.readFiles.add(abs);
|
|
692
|
+
var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
693
|
+
var IMAGE_HEADERS = [
|
|
694
|
+
{
|
|
695
|
+
mediaType: "image/png",
|
|
696
|
+
match: (h) => h.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
|
|
697
|
+
},
|
|
698
|
+
{ mediaType: "image/jpeg", match: (h) => h[0] === 255 && h[1] === 216 && h[2] === 255 },
|
|
699
|
+
{ mediaType: "image/gif", match: (h) => h.subarray(0, 6).toString("ascii").startsWith("GIF8") },
|
|
700
|
+
{
|
|
701
|
+
mediaType: "image/webp",
|
|
702
|
+
match: (h) => h.subarray(0, 4).toString("ascii") === "RIFF" && h.subarray(8, 12).toString("ascii") === "WEBP"
|
|
703
|
+
}
|
|
704
|
+
];
|
|
705
|
+
var MEDIA_LABEL = {
|
|
706
|
+
"image/png": "png",
|
|
707
|
+
"image/jpeg": "jpeg",
|
|
708
|
+
"image/gif": "gif",
|
|
709
|
+
"image/webp": "webp"
|
|
710
|
+
};
|
|
711
|
+
function imageMediaType(buffer) {
|
|
712
|
+
return IMAGE_HEADERS.find((entry) => entry.match(buffer))?.mediaType;
|
|
713
|
+
}
|
|
714
|
+
var schema2 = z5.object({
|
|
715
|
+
path: z5.string().describe("File path, relative to the project or absolute"),
|
|
716
|
+
offset: z5.number().int().min(1).optional().describe("First line to read, 1-based"),
|
|
717
|
+
limit: z5.number().int().min(1).optional().describe("Number of lines, default 2000")
|
|
718
|
+
});
|
|
719
|
+
async function readImageResult(input, buffer, mediaType) {
|
|
720
|
+
if (buffer.length > MAX_IMAGE_BYTES) {
|
|
791
721
|
return {
|
|
792
|
-
output:
|
|
793
|
-
|
|
794
|
-
\u2026 ${remaining} more lines (use offset)` : body,
|
|
795
|
-
display: t("{n} lines", { n: shown.length })
|
|
722
|
+
output: `Cannot attach ${input.path}: image is ${(buffer.length / (1024 * 1024)).toFixed(1)} MB (limit 4 MB).`,
|
|
723
|
+
isError: true
|
|
796
724
|
};
|
|
797
725
|
}
|
|
798
|
-
|
|
726
|
+
const label = MEDIA_LABEL[mediaType] ?? mediaType;
|
|
727
|
+
return {
|
|
728
|
+
output: `[image attached] ${input.path} (${label}, ${(buffer.length / 1024).toFixed(1)} KB).`,
|
|
729
|
+
display: `${input.path} \xB7 image`,
|
|
730
|
+
images: [{ mediaType, data: buffer.toString("base64"), path: input.path }]
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
async function readTextResult(input, buffer) {
|
|
734
|
+
const lines = buffer.toString("utf8").split(/\r?\n/);
|
|
735
|
+
const start = (input.offset ?? 1) - 1;
|
|
736
|
+
const limit = input.limit ?? 2e3;
|
|
737
|
+
const requested = lines.slice(start, start + limit);
|
|
738
|
+
const shown = [];
|
|
739
|
+
let size = 0;
|
|
740
|
+
for (let i = 0; i < requested.length; i++) {
|
|
741
|
+
const line = requested[i];
|
|
742
|
+
const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [line truncated]` : line;
|
|
743
|
+
const rendered = `${String(start + i + 1).padStart(6)} ${text}`;
|
|
744
|
+
const extra = rendered.length + (shown.length ? 1 : 0);
|
|
745
|
+
if (shown.length && size + extra > MAX_OUTPUT_CHARS3) break;
|
|
746
|
+
shown.push(rendered);
|
|
747
|
+
size += extra;
|
|
748
|
+
if (size >= MAX_OUTPUT_CHARS3) break;
|
|
749
|
+
}
|
|
750
|
+
const body = shown.join("\n");
|
|
751
|
+
const remaining = lines.length - start - shown.length;
|
|
752
|
+
const tokenBudgetHit = shown.length < requested.length;
|
|
753
|
+
const nextOffset = start + shown.length + 1;
|
|
754
|
+
return {
|
|
755
|
+
output: remaining > 0 ? tokenBudgetHit ? `${body}
|
|
756
|
+
\u2026 ${remaining} more lines (token budget reached; continue with offset=${nextOffset})` : `${body}
|
|
757
|
+
\u2026 ${remaining} more lines (use offset)` : body,
|
|
758
|
+
display: t("{n} lines", { n: shown.length })
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function createReadTool(options = {}) {
|
|
762
|
+
const vision = options.vision !== false;
|
|
763
|
+
return defineTool({
|
|
764
|
+
name: "read",
|
|
765
|
+
description: vision ? "Read a text file, or an image (png/jpeg/gif/webp) which is attached for the model to see. Returns numbered lines (default first 2000). Use offset/limit for large files. Always read a file before editing it." : "Read a text file. Returns numbered lines (default first 2000). Use offset/limit for large files. Always read a file before editing it.",
|
|
766
|
+
schema: schema2,
|
|
767
|
+
kind: "read",
|
|
768
|
+
target: (input) => input.path,
|
|
769
|
+
async run(input, ctx) {
|
|
770
|
+
const abs = resolvePath(ctx.cwd, input.path);
|
|
771
|
+
let buffer;
|
|
772
|
+
try {
|
|
773
|
+
buffer = await readFile3(abs);
|
|
774
|
+
} catch (error) {
|
|
775
|
+
return { output: `Cannot read ${input.path}: ${errorCode(error)}`, isError: true };
|
|
776
|
+
}
|
|
777
|
+
const mediaType = vision ? imageMediaType(buffer) : void 0;
|
|
778
|
+
if (mediaType) {
|
|
779
|
+
ctx.readFiles.add(abs);
|
|
780
|
+
return readImageResult(input, buffer, mediaType);
|
|
781
|
+
}
|
|
782
|
+
if (buffer.subarray(0, 8e3).includes(0)) {
|
|
783
|
+
return { output: `${input.path} is a binary file; not read.`, isError: true };
|
|
784
|
+
}
|
|
785
|
+
ctx.readFiles.add(abs);
|
|
786
|
+
return readTextResult(input, buffer);
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
var readTool = createReadTool({ vision: true });
|
|
799
791
|
|
|
800
792
|
// src/tools/write.ts
|
|
801
793
|
import { existsSync } from "fs";
|
|
802
794
|
import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
803
795
|
import { dirname } from "path";
|
|
804
|
-
import { z as
|
|
796
|
+
import { z as z6 } from "zod";
|
|
805
797
|
var writeTool = defineTool({
|
|
806
798
|
name: "write",
|
|
807
799
|
description: "Create or overwrite a file with the given content. Read an existing file first. Prefer edit for changes.",
|
|
808
|
-
schema:
|
|
809
|
-
path:
|
|
810
|
-
content:
|
|
800
|
+
schema: z6.object({
|
|
801
|
+
path: z6.string().describe("File path, relative to the project or absolute"),
|
|
802
|
+
content: z6.string().describe("Full new file content")
|
|
811
803
|
}),
|
|
812
804
|
kind: "edit",
|
|
813
805
|
target: (input) => input.path,
|
|
@@ -842,7 +834,7 @@ var writeTool = defineTool({
|
|
|
842
834
|
var ALL_TOOLS = [readTool, writeTool, editTool, bashTool, grepTool, globTool, fetchTool];
|
|
843
835
|
function toolDefinitions(tools) {
|
|
844
836
|
return tools.map((tool) => {
|
|
845
|
-
const { $schema: _ignored, ...parameters } = tool.jsonSchema ??
|
|
837
|
+
const { $schema: _ignored, ...parameters } = tool.jsonSchema ?? z7.toJSONSchema(tool.schema);
|
|
846
838
|
return { type: "function", function: { name: tool.name, description: tool.description, parameters, ...tool.outputJsonSchema ? { output_schema: tool.outputJsonSchema } : {} } };
|
|
847
839
|
});
|
|
848
840
|
}
|
|
@@ -921,11 +913,23 @@ var WebBudget = class {
|
|
|
921
913
|
}
|
|
922
914
|
};
|
|
923
915
|
|
|
916
|
+
// src/tokenBudget.ts
|
|
917
|
+
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
918
|
+
var estimateMessages = (messages) => estimateTokens(JSON.stringify(messages));
|
|
919
|
+
function trimToFit(messages, maxTokens) {
|
|
920
|
+
let out = messages;
|
|
921
|
+
while (estimateMessages(out) > maxTokens) {
|
|
922
|
+
const boundary = out.findIndex((m, i) => i > 0 && m.role === "user");
|
|
923
|
+
if (boundary === -1) return out;
|
|
924
|
+
out = out.slice(boundary);
|
|
925
|
+
}
|
|
926
|
+
return out;
|
|
927
|
+
}
|
|
928
|
+
|
|
924
929
|
// src/agent.ts
|
|
925
930
|
var CANCELLED = "Cancelled by the user.";
|
|
926
|
-
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
927
931
|
var COMPACT_PROMPT = "Summarize the conversation so far for yourself so you can continue the work with no other context. Include: the user goals, decisions made, files touched and their current state, commands run and results, the task in progress and the next step. Be concise but complete.";
|
|
928
|
-
var
|
|
932
|
+
var Agent2 = class {
|
|
929
933
|
constructor(opts) {
|
|
930
934
|
this.opts = opts;
|
|
931
935
|
this.provider = opts.provider;
|
|
@@ -958,12 +962,15 @@ var Agent = class {
|
|
|
958
962
|
readFiles = /* @__PURE__ */ new Set();
|
|
959
963
|
webBudget = new WebBudget();
|
|
960
964
|
maxSteps;
|
|
961
|
-
//
|
|
965
|
+
// One window for every model; resolved from contextWindowFor() (catalog + env override).
|
|
962
966
|
contextWindow;
|
|
963
967
|
systemPrompt;
|
|
964
968
|
setSystemPrompt(text) {
|
|
965
969
|
this.systemPrompt = text;
|
|
966
970
|
}
|
|
971
|
+
setContextWindow(window) {
|
|
972
|
+
this.contextWindow = window;
|
|
973
|
+
}
|
|
967
974
|
setTools(tools) {
|
|
968
975
|
this.tools = tools;
|
|
969
976
|
this.definitions = toolDefinitions(tools);
|
|
@@ -987,7 +994,7 @@ var Agent = class {
|
|
|
987
994
|
this.messages = messages;
|
|
988
995
|
this.readFiles.clear();
|
|
989
996
|
this.webBudget.reset();
|
|
990
|
-
this.lastInputTokens =
|
|
997
|
+
this.lastInputTokens = estimateMessages(messages) + estimateTokens(this.systemPrompt);
|
|
991
998
|
}
|
|
992
999
|
clear() {
|
|
993
1000
|
this.messages = [];
|
|
@@ -997,8 +1004,9 @@ var Agent = class {
|
|
|
997
1004
|
this.opts.onReset?.();
|
|
998
1005
|
}
|
|
999
1006
|
async compact(signal) {
|
|
1007
|
+
const history = trimToFit(this.messages, Math.floor(this.contextWindow * 0.8));
|
|
1000
1008
|
const completion = await this.provider.chat({
|
|
1001
|
-
messages: [{ role: "system", content: this.systemPrompt }, ...
|
|
1009
|
+
messages: [{ role: "system", content: this.systemPrompt }, ...history, { role: "user", content: COMPACT_PROMPT }],
|
|
1002
1010
|
// Some gateways reject tool_calls in history when no tools are declared.
|
|
1003
1011
|
tools: this.definitions,
|
|
1004
1012
|
signal,
|
|
@@ -1076,6 +1084,7 @@ ${completion.text}` });
|
|
|
1076
1084
|
}
|
|
1077
1085
|
const calls = completion.toolCalls;
|
|
1078
1086
|
let i = 0;
|
|
1087
|
+
const roundImages = [];
|
|
1079
1088
|
while (i < calls.length) {
|
|
1080
1089
|
if (signal.aborted) {
|
|
1081
1090
|
this.cancelCalls(calls.slice(i));
|
|
@@ -1101,15 +1110,17 @@ ${completion.text}` });
|
|
|
1101
1110
|
return;
|
|
1102
1111
|
}
|
|
1103
1112
|
batch.forEach((c, k) => {
|
|
1113
|
+
roundImages.push(...outcome[k].images ?? []);
|
|
1104
1114
|
this.push({
|
|
1105
1115
|
role: "tool",
|
|
1106
1116
|
tool_call_id: c.id,
|
|
1107
|
-
content: outcome[k],
|
|
1117
|
+
content: outcome[k].output,
|
|
1108
1118
|
...c.caller ? { responses_caller: c.caller } : {}
|
|
1109
1119
|
});
|
|
1110
1120
|
});
|
|
1111
1121
|
i = j;
|
|
1112
1122
|
}
|
|
1123
|
+
if (roundImages.length) this.push(imageUserMessage(roundImages));
|
|
1113
1124
|
if (signal.aborted) {
|
|
1114
1125
|
this.onEvent({ type: "aborted" });
|
|
1115
1126
|
return;
|
|
@@ -1140,12 +1151,31 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1140
1151
|
...pending.caller ? { responses_caller: pending.caller } : {}
|
|
1141
1152
|
});
|
|
1142
1153
|
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Every tool call must resolve with text: a rejected one would leave the assistant tool_calls
|
|
1156
|
+
* without answers, and the next request to the model would be rejected until a restart.
|
|
1157
|
+
*/
|
|
1143
1158
|
async runTool(call, signal, ticket) {
|
|
1159
|
+
const emit = (event) => this.report(ticket, event);
|
|
1160
|
+
try {
|
|
1161
|
+
return await this.executeTool(call, signal, ticket);
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
const message = `Error: ${error?.message || String(error)}`;
|
|
1164
|
+
if (ticket.open) {
|
|
1165
|
+
emit({ type: "toolEnd", id: call.id, tool: call.name, output: message, isError: true });
|
|
1166
|
+
} else {
|
|
1167
|
+
emit({ type: "toolStart", id: call.id, tool: call.name, target: "" });
|
|
1168
|
+
emit({ type: "toolEnd", id: call.id, tool: call.name, output: message, isError: true });
|
|
1169
|
+
}
|
|
1170
|
+
return { output: message };
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async executeTool(call, signal, ticket) {
|
|
1144
1174
|
const emit = (event) => this.report(ticket, event);
|
|
1145
1175
|
const fail = (output, target2 = "") => {
|
|
1146
1176
|
emit({ type: "toolStart", id: call.id, tool: call.name, target: target2 });
|
|
1147
1177
|
emit({ type: "toolEnd", id: call.id, tool: call.name, output, isError: true });
|
|
1148
|
-
return output;
|
|
1178
|
+
return { output };
|
|
1149
1179
|
};
|
|
1150
1180
|
const tool = this.turnTools.find((t2) => t2.name === call.name);
|
|
1151
1181
|
if (!tool) return fail(`Tool "${call.name}" does not exist. Available tools: ${this.turnTools.map((t2) => t2.name).join(", ")}`);
|
|
@@ -1163,6 +1193,9 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1163
1193
|
const target = tool.target(input);
|
|
1164
1194
|
const overBudget = tool.name === "fetch" ? this.webBudget.take() : void 0;
|
|
1165
1195
|
if (overBudget) return fail(overBudget, target);
|
|
1196
|
+
await this.opts.pluginEmit?.("PreToolUse", { tool: tool.name, input });
|
|
1197
|
+
const pre = await runHooks(this.opts.hooks, "PreToolUse", { tool: tool.name, input }, { env: this.opts.env, cwd: this.opts.cwd });
|
|
1198
|
+
if (pre.blocked) return fail(pre.blocked, target);
|
|
1166
1199
|
const ctx = {
|
|
1167
1200
|
cwd: this.opts.cwd,
|
|
1168
1201
|
signal,
|
|
@@ -1184,7 +1217,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1184
1217
|
const invalid = await tool.validate?.(input, ctx).catch((error) => error.message);
|
|
1185
1218
|
if (invalid) {
|
|
1186
1219
|
emit({ type: "toolEnd", id: call.id, tool: tool.name, output: invalid, isError: true });
|
|
1187
|
-
return invalid;
|
|
1220
|
+
return { output: invalid };
|
|
1188
1221
|
}
|
|
1189
1222
|
const request = { tool: tool.name, kind: tool.kind, target };
|
|
1190
1223
|
let decision = this.permissions.check(request);
|
|
@@ -1203,7 +1236,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1203
1236
|
if (decision === "deny") {
|
|
1204
1237
|
const output = this.permissions.mode === "plan" ? "Denied: plan mode only allows reading and searching. Write the plan for the user without changing anything." : "The user declined to run this tool. Ask the user what they want, or try another approach.";
|
|
1205
1238
|
emit({ type: "toolEnd", id: call.id, tool: tool.name, output, isError: true });
|
|
1206
|
-
return output;
|
|
1239
|
+
return { output };
|
|
1207
1240
|
}
|
|
1208
1241
|
let result;
|
|
1209
1242
|
try {
|
|
@@ -1212,8 +1245,15 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
1212
1245
|
result = { output: `Error: ${error.message}`, isError: true };
|
|
1213
1246
|
}
|
|
1214
1247
|
if (tool.name === "fetch" && !result.isError) this.webBudget.record(result.output.length);
|
|
1248
|
+
await runHooks(
|
|
1249
|
+
this.opts.hooks,
|
|
1250
|
+
"PostToolUse",
|
|
1251
|
+
{ tool: tool.name, input, output: result.output },
|
|
1252
|
+
{ env: this.opts.env, cwd: this.opts.cwd }
|
|
1253
|
+
);
|
|
1254
|
+
await this.opts.pluginEmit?.("PostToolUse", { tool: tool.name, input, output: result.output });
|
|
1215
1255
|
emit({ type: "toolEnd", id: call.id, tool: tool.name, output: result.output, display: result.display, isError: !!result.isError });
|
|
1216
|
-
return result
|
|
1256
|
+
return result;
|
|
1217
1257
|
}
|
|
1218
1258
|
};
|
|
1219
1259
|
|
|
@@ -1222,6 +1262,25 @@ import { existsSync as existsSync2, statSync } from "fs";
|
|
|
1222
1262
|
import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
|
|
1223
1263
|
var MAX_TURNS = 20;
|
|
1224
1264
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
1265
|
+
function emptyResult() {
|
|
1266
|
+
return { restored: [], deleted: [], skipped: [], failed: [] };
|
|
1267
|
+
}
|
|
1268
|
+
async function restoreTurn(turn, result) {
|
|
1269
|
+
for (const [path, original] of turn) {
|
|
1270
|
+
try {
|
|
1271
|
+
if (original === "skipped") result.skipped.push(path);
|
|
1272
|
+
else if (original === null) {
|
|
1273
|
+
await rm(path, { force: true });
|
|
1274
|
+
result.deleted.push(path);
|
|
1275
|
+
} else {
|
|
1276
|
+
await writeFile3(path, original);
|
|
1277
|
+
result.restored.push(path);
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
result.failed.push(path);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1225
1284
|
var CheckpointStore = class {
|
|
1226
1285
|
history = [];
|
|
1227
1286
|
current;
|
|
@@ -1243,27 +1302,37 @@ var CheckpointStore = class {
|
|
|
1243
1302
|
turns() {
|
|
1244
1303
|
return this.history.filter((t2) => t2.size).length;
|
|
1245
1304
|
}
|
|
1305
|
+
/** Total entries including empty ones; used to align file snapshots with conversation turns. */
|
|
1306
|
+
entries() {
|
|
1307
|
+
return this.history.length;
|
|
1308
|
+
}
|
|
1246
1309
|
async undo() {
|
|
1247
1310
|
while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
|
|
1248
1311
|
const turn = this.history.pop();
|
|
1249
1312
|
this.current = void 0;
|
|
1250
1313
|
if (!turn) return void 0;
|
|
1251
|
-
const result =
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1314
|
+
const result = emptyResult();
|
|
1315
|
+
await restoreTurn(turn, result);
|
|
1316
|
+
return result;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Revert every snapshot taken at or after the `keep`-th entry, newest first, so the files
|
|
1320
|
+
* end in the state from before the turn that created entry `keep`. Used by /rewind, which
|
|
1321
|
+
* pairs conversation turns with the entry count recorded when each turn started.
|
|
1322
|
+
*/
|
|
1323
|
+
async undoTo(keep) {
|
|
1324
|
+
const target = Math.max(0, Math.min(keep, this.history.length));
|
|
1325
|
+
const result = emptyResult();
|
|
1326
|
+
let applied = false;
|
|
1327
|
+
while (this.history.length > target) {
|
|
1328
|
+
const turn = this.history.pop();
|
|
1329
|
+
if (turn?.size) {
|
|
1330
|
+
await restoreTurn(turn, result);
|
|
1331
|
+
applied = true;
|
|
1264
1332
|
}
|
|
1265
1333
|
}
|
|
1266
|
-
|
|
1334
|
+
this.current = void 0;
|
|
1335
|
+
return applied ? result : void 0;
|
|
1267
1336
|
}
|
|
1268
1337
|
clear() {
|
|
1269
1338
|
this.history.length = 0;
|
|
@@ -1271,12 +1340,66 @@ var CheckpointStore = class {
|
|
|
1271
1340
|
}
|
|
1272
1341
|
};
|
|
1273
1342
|
|
|
1343
|
+
// src/contextWindow.ts
|
|
1344
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
1345
|
+
var WINDOWS = [
|
|
1346
|
+
// BotConnector's Luna request cap (see LUNA_AUTO_COMPACT_THRESHOLD in provider.ts).
|
|
1347
|
+
[/gpt-6-luna/i, 272e3],
|
|
1348
|
+
[/claude-(opus|sonnet|haiku)-\d/i, 2e5],
|
|
1349
|
+
[/gemini-\d/i, 1048576]
|
|
1350
|
+
];
|
|
1351
|
+
function contextWindowFor(modelRef, env = process.env) {
|
|
1352
|
+
const override = Number(env.BCCLI_CONTEXT_WINDOW);
|
|
1353
|
+
if (Number.isInteger(override) && override > 0) return override;
|
|
1354
|
+
const model = modelRef.slice(modelRef.indexOf("/") + 1);
|
|
1355
|
+
for (const [pattern, window] of WINDOWS) if (pattern.test(model)) return window;
|
|
1356
|
+
return DEFAULT_CONTEXT_WINDOW;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1274
1359
|
// src/mcp/manager.ts
|
|
1275
1360
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1361
|
+
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
1276
1362
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1277
1363
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1278
|
-
import { z as
|
|
1364
|
+
import { z as z8 } from "zod";
|
|
1279
1365
|
var MCP_OUTPUT_MAX_CHARS = 32e3;
|
|
1366
|
+
var CHILD_ENV_KEYS = [
|
|
1367
|
+
// portable
|
|
1368
|
+
"PATH",
|
|
1369
|
+
"HOME",
|
|
1370
|
+
"USERPROFILE",
|
|
1371
|
+
"LANG",
|
|
1372
|
+
"LC_ALL",
|
|
1373
|
+
"TZ",
|
|
1374
|
+
"TMP",
|
|
1375
|
+
"TEMP",
|
|
1376
|
+
"TMPDIR",
|
|
1377
|
+
// windows
|
|
1378
|
+
"SYSTEMROOT",
|
|
1379
|
+
"SYSTEMDRIVE",
|
|
1380
|
+
"COMSPEC",
|
|
1381
|
+
"PATHEXT",
|
|
1382
|
+
"APPDATA",
|
|
1383
|
+
"LOCALAPPDATA",
|
|
1384
|
+
"HOMEDRIVE",
|
|
1385
|
+
"HOMEPATH",
|
|
1386
|
+
"USERNAME",
|
|
1387
|
+
"PROGRAMFILES",
|
|
1388
|
+
"PROCESSOR_ARCHITECTURE",
|
|
1389
|
+
// posix
|
|
1390
|
+
"LOGNAME",
|
|
1391
|
+
"SHELL",
|
|
1392
|
+
"TERM",
|
|
1393
|
+
"USER"
|
|
1394
|
+
];
|
|
1395
|
+
function mcpChildEnv(extra) {
|
|
1396
|
+
const env = {};
|
|
1397
|
+
for (const key of CHILD_ENV_KEYS) {
|
|
1398
|
+
const value = process.env[key];
|
|
1399
|
+
if (value !== void 0) env[key] = value;
|
|
1400
|
+
}
|
|
1401
|
+
return { ...env, ...extra };
|
|
1402
|
+
}
|
|
1280
1403
|
function mcpToolName(server, tool, taken) {
|
|
1281
1404
|
const clean2 = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
|
|
1282
1405
|
const base = `mcp__${clean2(server)}__${clean2(tool)}`.slice(0, 64);
|
|
@@ -1311,13 +1434,22 @@ var McpManager = class {
|
|
|
1311
1434
|
entry.client = client;
|
|
1312
1435
|
const timeoutMs = this.opts.connectTimeoutMs ?? 3e4;
|
|
1313
1436
|
try {
|
|
1314
|
-
const config = spec.config;
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1437
|
+
const config = expandEnvVars(spec.config, process.env);
|
|
1438
|
+
let transport;
|
|
1439
|
+
if (config.type === "http") {
|
|
1440
|
+
const oauth = this.opts.home && (config.auth === "oauth" || hasStoredTokens(this.opts.home, config.url)) ? new FileOAuthProvider({ home: this.opts.home, serverUrl: config.url, open: this.opts.open }) : void 0;
|
|
1441
|
+
transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
1442
|
+
requestInit: { headers: config.headers ?? {} },
|
|
1443
|
+
...oauth ? { authProvider: oauth } : {}
|
|
1444
|
+
});
|
|
1445
|
+
} else {
|
|
1446
|
+
transport = new StdioClientTransport({
|
|
1447
|
+
command: config.command,
|
|
1448
|
+
args: config.args ?? [],
|
|
1449
|
+
env: mcpChildEnv(config.env),
|
|
1450
|
+
stderr: "ignore"
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1321
1453
|
await withTimeout(client.connect(transport), timeoutMs);
|
|
1322
1454
|
const { tools } = await withTimeout(client.listTools(), timeoutMs);
|
|
1323
1455
|
if (this.servers.get(spec.name) !== entry) {
|
|
@@ -1328,7 +1460,15 @@ var McpManager = class {
|
|
|
1328
1460
|
entry.tools = tools.map((t2) => this.wrap(spec.name, client, t2));
|
|
1329
1461
|
entry.state = { ...entry.state, status: "ready", tools: entry.tools.length };
|
|
1330
1462
|
} catch (error) {
|
|
1331
|
-
|
|
1463
|
+
if (error instanceof UnauthorizedError) {
|
|
1464
|
+
entry.state = {
|
|
1465
|
+
...entry.state,
|
|
1466
|
+
status: "error",
|
|
1467
|
+
error: t("OAuth required for {name}: run bccli mcp auth {name}", { name: spec.name })
|
|
1468
|
+
};
|
|
1469
|
+
} else {
|
|
1470
|
+
entry.state = { ...entry.state, status: "error", error: error.message };
|
|
1471
|
+
}
|
|
1332
1472
|
await client.close().catch(() => {
|
|
1333
1473
|
});
|
|
1334
1474
|
if (this.servers.get(spec.name) !== entry) return;
|
|
@@ -1339,7 +1479,7 @@ var McpManager = class {
|
|
|
1339
1479
|
return {
|
|
1340
1480
|
name: mcpToolName(server, tool.name, this.taken),
|
|
1341
1481
|
description: `[MCP ${server}] ${tool.description ?? tool.name}`.slice(0, 1024),
|
|
1342
|
-
schema:
|
|
1482
|
+
schema: z8.record(z8.string(), z8.unknown()),
|
|
1343
1483
|
jsonSchema: tool.inputSchema,
|
|
1344
1484
|
...tool.outputSchema ? { outputJsonSchema: tool.outputSchema } : {},
|
|
1345
1485
|
kind: "mcp",
|
|
@@ -1388,12 +1528,12 @@ var McpManager = class {
|
|
|
1388
1528
|
import { homedir } from "os";
|
|
1389
1529
|
|
|
1390
1530
|
// src/tools/plan.ts
|
|
1391
|
-
import { z as
|
|
1531
|
+
import { z as z9 } from "zod";
|
|
1392
1532
|
function createExitPlanTool(opts) {
|
|
1393
1533
|
return defineTool({
|
|
1394
1534
|
name: "exit_plan",
|
|
1395
1535
|
description: "Plan mode only: present the finished plan to the user for approval. If approved you may start implementing it.",
|
|
1396
|
-
schema:
|
|
1536
|
+
schema: z9.object({ plan: z9.string().describe("The plan, in markdown") }),
|
|
1397
1537
|
kind: "read",
|
|
1398
1538
|
target: () => t("plan"),
|
|
1399
1539
|
async run(input) {
|
|
@@ -1410,12 +1550,12 @@ function createExitPlanTool(opts) {
|
|
|
1410
1550
|
|
|
1411
1551
|
// src/tools/skill.ts
|
|
1412
1552
|
import { readFileSync } from "fs";
|
|
1413
|
-
import { z as
|
|
1553
|
+
import { z as z10 } from "zod";
|
|
1414
1554
|
function createSkillTool(skills) {
|
|
1415
1555
|
return defineTool({
|
|
1416
1556
|
name: "skill",
|
|
1417
1557
|
description: "Load a skill (instructions for a kind of task) by name. Use it when a listed skill matches the task, then follow it.",
|
|
1418
|
-
schema:
|
|
1558
|
+
schema: z10.object({ name: z10.string().describe("Skill name from the list in the system prompt") }),
|
|
1419
1559
|
kind: "read",
|
|
1420
1560
|
target: (input) => input.name,
|
|
1421
1561
|
async run(input) {
|
|
@@ -1430,7 +1570,7 @@ ${body}`, display: skill.description.slice(0, 60) };
|
|
|
1430
1570
|
}
|
|
1431
1571
|
|
|
1432
1572
|
// src/tools/websearch.ts
|
|
1433
|
-
import { z as
|
|
1573
|
+
import { z as z11 } from "zod";
|
|
1434
1574
|
var clean = (items, max) => (Array.isArray(items) ? items : []).filter((r) => typeof r?.url === "string" && /^https?:\/\//.test(r.url)).slice(0, max).map((r) => ({
|
|
1435
1575
|
title: String(r.title || r.url).slice(0, 200),
|
|
1436
1576
|
url: r.url,
|
|
@@ -1451,9 +1591,9 @@ function createWebSearchTool(opts) {
|
|
|
1451
1591
|
return defineTool({
|
|
1452
1592
|
name: "web_search",
|
|
1453
1593
|
description: "Search the web. Returns titles, URLs and short snippets. Use it to find the right pages instead of guessing URLs; then fetch a result URL when you need the full page.",
|
|
1454
|
-
schema:
|
|
1455
|
-
query:
|
|
1456
|
-
max_results:
|
|
1594
|
+
schema: z11.object({
|
|
1595
|
+
query: z11.string().min(1).max(500).describe("Search query"),
|
|
1596
|
+
max_results: z11.number().int().min(1).max(10).optional().describe("Number of results (default 6)")
|
|
1457
1597
|
}),
|
|
1458
1598
|
kind: "read",
|
|
1459
1599
|
target: (input) => input.query,
|
|
@@ -1491,27 +1631,41 @@ function createWebSearchTool(opts) {
|
|
|
1491
1631
|
}
|
|
1492
1632
|
|
|
1493
1633
|
// src/tools/task.ts
|
|
1494
|
-
import { z as
|
|
1634
|
+
import { z as z12 } from "zod";
|
|
1495
1635
|
function createTaskTool(opts) {
|
|
1496
1636
|
const byName = new Map(opts.agents.map((a) => [a.name, a]));
|
|
1497
1637
|
const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
1638
|
+
let askChain = Promise.resolve();
|
|
1639
|
+
const enqueueAsk = (fn) => {
|
|
1640
|
+
const next = askChain.then(
|
|
1641
|
+
() => fn(),
|
|
1642
|
+
() => fn()
|
|
1643
|
+
);
|
|
1644
|
+
askChain = next.then(
|
|
1645
|
+
() => void 0,
|
|
1646
|
+
() => void 0
|
|
1647
|
+
);
|
|
1648
|
+
return next;
|
|
1649
|
+
};
|
|
1498
1650
|
return defineTool({
|
|
1499
1651
|
name: "task",
|
|
1500
1652
|
description: `Delegate a self-contained sub-task to a subagent. It starts with an empty context and returns only its final report. Available agents:
|
|
1501
1653
|
${list}`,
|
|
1502
|
-
schema:
|
|
1503
|
-
agent:
|
|
1504
|
-
description:
|
|
1505
|
-
prompt:
|
|
1654
|
+
schema: z12.object({
|
|
1655
|
+
agent: z12.string().describe("Agent name"),
|
|
1656
|
+
description: z12.string().describe("3-6 word summary shown to the user"),
|
|
1657
|
+
prompt: z12.string().describe("Full instructions; the subagent sees nothing else")
|
|
1506
1658
|
}),
|
|
1507
1659
|
kind: "read",
|
|
1508
1660
|
target: (input) => `[${input.agent}] ${input.description}`,
|
|
1509
|
-
//
|
|
1661
|
+
// parallel: true opts a write-capable agent in; otherwise only all-read agents run concurrently
|
|
1662
|
+
// (a project file may redefine "explore").
|
|
1510
1663
|
parallelSafe: (input) => {
|
|
1511
|
-
const
|
|
1512
|
-
if (
|
|
1664
|
+
const def = byName.get(input.agent);
|
|
1665
|
+
if (def?.parallel) return true;
|
|
1666
|
+
if (!def?.tools) return false;
|
|
1513
1667
|
const base = opts.baseTools();
|
|
1514
|
-
return
|
|
1668
|
+
return def.tools.every((name) => base.find((t2) => t2.name === name)?.kind === "read");
|
|
1515
1669
|
},
|
|
1516
1670
|
async run(input, ctx) {
|
|
1517
1671
|
const def = byName.get(input.agent);
|
|
@@ -1527,7 +1681,7 @@ ${list}`,
|
|
|
1527
1681
|
} catch (error) {
|
|
1528
1682
|
return { output: `Agent "${def.name}": ${error.message}`, isError: true };
|
|
1529
1683
|
}
|
|
1530
|
-
const child = new
|
|
1684
|
+
const child = new Agent2({
|
|
1531
1685
|
provider,
|
|
1532
1686
|
tools,
|
|
1533
1687
|
permissions: opts.permissions,
|
|
@@ -1536,9 +1690,13 @@ ${list}`,
|
|
|
1536
1690
|
# Your role
|
|
1537
1691
|
${def.prompt}`,
|
|
1538
1692
|
cwd: opts.cwd,
|
|
1539
|
-
|
|
1693
|
+
// Every subagent runs with a step cap so a looping child cannot burn tokens forever.
|
|
1694
|
+
maxSteps: def.maxSteps ?? 50,
|
|
1540
1695
|
label: def.name,
|
|
1541
1696
|
reasoning: opts.reasoning?.() ?? "auto",
|
|
1697
|
+
hooks: opts.hooks,
|
|
1698
|
+
pluginEmit: opts.pluginEmit,
|
|
1699
|
+
env: opts.env,
|
|
1542
1700
|
// Subagent edits belong to the parent's turn so /undo reverts them too.
|
|
1543
1701
|
checkpoint: ctx.checkpoint
|
|
1544
1702
|
});
|
|
@@ -1549,7 +1707,8 @@ ${def.prompt}`,
|
|
|
1549
1707
|
if (event.type === "error" || event.type === "stepLimit" || event.type === "aborted" || event.type === "done") outcome = event;
|
|
1550
1708
|
if (event.type !== "usage" && ctx.callId) ctx.emit?.({ type: "subagent", parentId: ctx.callId, agent: def.name, event });
|
|
1551
1709
|
};
|
|
1552
|
-
|
|
1710
|
+
const ask = ctx.ask;
|
|
1711
|
+
if (ask) child.askPermission = (req) => enqueueAsk(() => ask(req));
|
|
1553
1712
|
await child.run(input.prompt, ctx.signal);
|
|
1554
1713
|
ctx.addUsage?.(child.totalUsage);
|
|
1555
1714
|
const tokens2 = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
|
|
@@ -1567,7 +1726,7 @@ ${def.prompt}`,
|
|
|
1567
1726
|
}
|
|
1568
1727
|
|
|
1569
1728
|
// src/tools/todo.ts
|
|
1570
|
-
import { z as
|
|
1729
|
+
import { z as z13 } from "zod";
|
|
1571
1730
|
var TodoStore = class {
|
|
1572
1731
|
items = [];
|
|
1573
1732
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -1584,8 +1743,8 @@ function createTodoTool(store) {
|
|
|
1584
1743
|
return defineTool({
|
|
1585
1744
|
name: "todo_write",
|
|
1586
1745
|
description: "Write the task list for multi-step work (3+ steps). Send the whole list every time; exactly one item in_progress while working. Mark items completed as soon as they are done.",
|
|
1587
|
-
schema:
|
|
1588
|
-
todos:
|
|
1746
|
+
schema: z13.object({
|
|
1747
|
+
todos: z13.array(z13.object({ content: z13.string(), status: z13.enum(["pending", "in_progress", "completed"]) }))
|
|
1589
1748
|
}),
|
|
1590
1749
|
kind: "read",
|
|
1591
1750
|
target: (input) => `${input.todos.length} item`,
|
|
@@ -1600,6 +1759,124 @@ function createTodoTool(store) {
|
|
|
1600
1759
|
});
|
|
1601
1760
|
}
|
|
1602
1761
|
|
|
1762
|
+
// src/tools/memory.ts
|
|
1763
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1764
|
+
import { join } from "path";
|
|
1765
|
+
import { z as z14 } from "zod";
|
|
1766
|
+
function createSaveMemoryTool(opts) {
|
|
1767
|
+
return defineTool({
|
|
1768
|
+
name: "save_memory",
|
|
1769
|
+
description: 'Save a durable fact, decision, or convention to the instruction files so future sessions remember it. Scope "project" (default) appends to AGENTS.md in the project; "global" appends to your personal BCCLI.md. Use for things worth remembering across sessions, not temporary task notes.',
|
|
1770
|
+
schema: z14.object({
|
|
1771
|
+
content: z14.string().min(1).max(500).describe("One short line, without a leading bullet (it is added for you)"),
|
|
1772
|
+
scope: z14.enum(["project", "global"]).optional().describe("project (default) or global")
|
|
1773
|
+
}),
|
|
1774
|
+
kind: "edit",
|
|
1775
|
+
target: (input) => `${input.scope ?? "project"} memory`,
|
|
1776
|
+
async run(input, ctx) {
|
|
1777
|
+
const content = input.content.trim();
|
|
1778
|
+
if (!content) return { output: "Memory content is empty.", isError: true };
|
|
1779
|
+
const file = input.scope === "global" ? join(opts.home, "BCCLI.md") : join(ctx.cwd, "AGENTS.md");
|
|
1780
|
+
if (existsSync3(file)) {
|
|
1781
|
+
const lines = readFileSync2(file, "utf8").split("\n");
|
|
1782
|
+
if (lines.some((line) => line.trim() === `- ${content}`)) {
|
|
1783
|
+
return { output: `Already saved to ${file}.` };
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
appendMemory(file, content);
|
|
1787
|
+
opts.onSaved?.();
|
|
1788
|
+
return { output: `Saved to ${file}.` };
|
|
1789
|
+
}
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// src/plugins.ts
|
|
1794
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1795
|
+
import { createRequire } from "module";
|
|
1796
|
+
import { isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
1797
|
+
import { z as z15 } from "zod";
|
|
1798
|
+
var require_ = createRequire(import.meta.url);
|
|
1799
|
+
function pluginLog(message) {
|
|
1800
|
+
process.stderr.write(`[plugin] ${message}
|
|
1801
|
+
`);
|
|
1802
|
+
}
|
|
1803
|
+
function normalizeTool(def) {
|
|
1804
|
+
return {
|
|
1805
|
+
name: def.name,
|
|
1806
|
+
description: def.description,
|
|
1807
|
+
// Raw-args passthrough: a plugin without its own zod still receives what the model sent.
|
|
1808
|
+
schema: def.schema ?? z15.record(z15.string(), z15.unknown()),
|
|
1809
|
+
kind: def.kind ?? "read",
|
|
1810
|
+
target: def.target ?? (() => def.name),
|
|
1811
|
+
run: def.run
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
function loadPlugins(specs, opts) {
|
|
1815
|
+
const tools = [];
|
|
1816
|
+
const taken = new Set(opts.builtinNames);
|
|
1817
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1818
|
+
for (const spec of specs) {
|
|
1819
|
+
let file;
|
|
1820
|
+
try {
|
|
1821
|
+
file = isAbsolute2(spec) ? spec : spec.startsWith(".") ? join2(opts.home, spec) : require_.resolve(spec);
|
|
1822
|
+
} catch {
|
|
1823
|
+
throw new ConfigError(t("Plugin not found: {spec}", { spec }));
|
|
1824
|
+
}
|
|
1825
|
+
if (!existsSync4(file)) throw new ConfigError(t("Plugin not found: {spec}", { spec }));
|
|
1826
|
+
let mod;
|
|
1827
|
+
try {
|
|
1828
|
+
mod = require_(file);
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
throw new ConfigError(t("Plugin failed to load: {spec}: {reason}", { spec, reason: error.message }));
|
|
1831
|
+
}
|
|
1832
|
+
const activate = typeof mod.default === "function" ? mod.default : typeof mod.activate === "function" ? mod.activate : void 0;
|
|
1833
|
+
if (!activate) {
|
|
1834
|
+
throw new ConfigError(t("Plugin failed to load: {spec}: {reason}", { spec, reason: "no default or activate export" }));
|
|
1835
|
+
}
|
|
1836
|
+
const ctx = {
|
|
1837
|
+
cwd: opts.cwd,
|
|
1838
|
+
env: opts.env,
|
|
1839
|
+
config: opts.config,
|
|
1840
|
+
registerTool(tool) {
|
|
1841
|
+
if (taken.has(tool.name)) {
|
|
1842
|
+
throw new ConfigError(
|
|
1843
|
+
t("Plugin {spec} registered a tool name that is already in use: {name}", { spec, name: tool.name })
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
taken.add(tool.name);
|
|
1847
|
+
tools.push(normalizeTool(tool));
|
|
1848
|
+
},
|
|
1849
|
+
on(event, handler) {
|
|
1850
|
+
const list = listeners.get(event) ?? [];
|
|
1851
|
+
list.push(handler);
|
|
1852
|
+
listeners.set(event, list);
|
|
1853
|
+
},
|
|
1854
|
+
log: pluginLog
|
|
1855
|
+
};
|
|
1856
|
+
try {
|
|
1857
|
+
const result = activate(ctx);
|
|
1858
|
+
if (result && typeof result.catch === "function") {
|
|
1859
|
+
;
|
|
1860
|
+
result.catch((error) => pluginLog(`activation error in ${spec}: ${String(error)}`));
|
|
1861
|
+
}
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
throw new ConfigError(t("Plugin failed to load: {spec}: {reason}", { spec, reason: error.message }));
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
return {
|
|
1867
|
+
tools,
|
|
1868
|
+
async emit(event, payload = {}) {
|
|
1869
|
+
for (const handler of listeners.get(event) ?? []) {
|
|
1870
|
+
try {
|
|
1871
|
+
await handler(payload);
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
pluginLog(`handler for ${event} failed: ${String(error)}`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1603
1880
|
// src/setup.ts
|
|
1604
1881
|
var TOOL_RULES = { bash: "bash", edit: "edit", write: "edit", fetch: "fetch" };
|
|
1605
1882
|
function lunaPtcAutoEnabled(env) {
|
|
@@ -1632,6 +1909,8 @@ function createRuntime(opts) {
|
|
|
1632
1909
|
const agentDefs = loadAgentDefs(roots);
|
|
1633
1910
|
const todos = new TodoStore();
|
|
1634
1911
|
const checkpoints = new CheckpointStore();
|
|
1912
|
+
const turnMarks = [];
|
|
1913
|
+
const checkpointMarks = [];
|
|
1635
1914
|
let startedAt = /* @__PURE__ */ new Date();
|
|
1636
1915
|
const interaction = { approvePlan: async () => "no" };
|
|
1637
1916
|
let session = Session.create(home, opts.cwd);
|
|
@@ -1654,7 +1933,27 @@ function createRuntime(opts) {
|
|
|
1654
1933
|
}
|
|
1655
1934
|
}
|
|
1656
1935
|
});
|
|
1657
|
-
const
|
|
1936
|
+
const rebuildPrompt = () => agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
|
|
1937
|
+
const saveMemory = createSaveMemoryTool({ home, onSaved: rebuildPrompt });
|
|
1938
|
+
const baseTools = [
|
|
1939
|
+
createReadTool({ vision: config.vision }),
|
|
1940
|
+
createBashTool({ networkPolicy: config.networkPolicy }),
|
|
1941
|
+
...ALL_TOOLS.filter((tool) => tool.name !== "read" && tool.name !== "bash"),
|
|
1942
|
+
webSearch,
|
|
1943
|
+
createSkillTool(skills),
|
|
1944
|
+
createTodoTool(todos),
|
|
1945
|
+
createExitPlanTool({ permissions, interaction }),
|
|
1946
|
+
saveMemory
|
|
1947
|
+
];
|
|
1948
|
+
const plugins = loadPlugins(config.plugins ?? [], {
|
|
1949
|
+
home,
|
|
1950
|
+
cwd: opts.cwd,
|
|
1951
|
+
env,
|
|
1952
|
+
config,
|
|
1953
|
+
builtinNames: [...baseTools.map((tool) => tool.name), "task"]
|
|
1954
|
+
});
|
|
1955
|
+
baseTools.push(...plugins.tools);
|
|
1956
|
+
const pluginEmit = (event, payload) => plugins.emit(event, payload);
|
|
1658
1957
|
const task = createTaskTool({
|
|
1659
1958
|
agents: [...BUILTIN_AGENTS, ...agentDefs],
|
|
1660
1959
|
// Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
|
|
@@ -1664,9 +1963,12 @@ function createRuntime(opts) {
|
|
|
1664
1963
|
providerFor: makeProvider,
|
|
1665
1964
|
systemPrompt: (childModelRef) => buildSystemPrompt({ cwd: opts.cwd, home, model: childModelRef ?? modelRef, skills }),
|
|
1666
1965
|
cwd: opts.cwd,
|
|
1667
|
-
reasoning: () => reasoning
|
|
1966
|
+
reasoning: () => reasoning,
|
|
1967
|
+
hooks: config.hooks,
|
|
1968
|
+
pluginEmit,
|
|
1969
|
+
env
|
|
1668
1970
|
});
|
|
1669
|
-
const agent = new
|
|
1971
|
+
const agent = new Agent2({
|
|
1670
1972
|
provider,
|
|
1671
1973
|
tools: [...baseTools, task],
|
|
1672
1974
|
permissions,
|
|
@@ -1675,12 +1977,25 @@ function createRuntime(opts) {
|
|
|
1675
1977
|
history,
|
|
1676
1978
|
reasoning,
|
|
1677
1979
|
maxSteps: config.maxSteps,
|
|
1980
|
+
hooks: config.hooks,
|
|
1981
|
+
pluginEmit,
|
|
1982
|
+
env,
|
|
1983
|
+
contextWindow: contextWindowFor(modelRef, env),
|
|
1678
1984
|
onMessage: (m) => session.append(m),
|
|
1679
1985
|
onReset: () => session.reset(),
|
|
1680
|
-
onTurnStart: () =>
|
|
1986
|
+
onTurnStart: () => {
|
|
1987
|
+
for (let i = turnMarks.length - 1; i >= 0 && turnMarks[i] > agent.messages.length; i--) {
|
|
1988
|
+
turnMarks.splice(i, 1);
|
|
1989
|
+
checkpointMarks.splice(i, 1);
|
|
1990
|
+
}
|
|
1991
|
+
turnMarks.push(agent.messages.length);
|
|
1992
|
+
checkpointMarks.push(checkpoints.entries());
|
|
1993
|
+
checkpoints.beginTurn();
|
|
1994
|
+
},
|
|
1681
1995
|
checkpoint: (path) => checkpoints.snapshot(path)
|
|
1682
1996
|
});
|
|
1683
1997
|
const mcp = new McpManager({
|
|
1998
|
+
home,
|
|
1684
1999
|
onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
|
|
1685
2000
|
// A later server may reuse a sanitized name; it must not inherit this one's "[a]" grants.
|
|
1686
2001
|
onToolsRemoved: (names) => {
|
|
@@ -1729,6 +2044,8 @@ function createRuntime(opts) {
|
|
|
1729
2044
|
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1730
2045
|
todos.set([]);
|
|
1731
2046
|
checkpoints.clear();
|
|
2047
|
+
turnMarks.length = 0;
|
|
2048
|
+
checkpointMarks.length = 0;
|
|
1732
2049
|
},
|
|
1733
2050
|
newSession() {
|
|
1734
2051
|
session = Session.create(home, opts.cwd);
|
|
@@ -1736,12 +2053,36 @@ function createRuntime(opts) {
|
|
|
1736
2053
|
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1737
2054
|
todos.set([]);
|
|
1738
2055
|
checkpoints.clear();
|
|
2056
|
+
turnMarks.length = 0;
|
|
2057
|
+
checkpointMarks.length = 0;
|
|
1739
2058
|
startedAt = /* @__PURE__ */ new Date();
|
|
1740
2059
|
},
|
|
1741
2060
|
rebuildSystemPrompt() {
|
|
1742
|
-
|
|
2061
|
+
rebuildPrompt();
|
|
1743
2062
|
},
|
|
2063
|
+
emitPlugins: (event, payload) => plugins.emit(event, payload),
|
|
1744
2064
|
checkpoints,
|
|
2065
|
+
async rewindTurns(n) {
|
|
2066
|
+
if (turnMarks.length === 0) return void 0;
|
|
2067
|
+
const count = Math.min(Math.max(1, Math.floor(n) || 1), turnMarks.length);
|
|
2068
|
+
const from = turnMarks.length - count;
|
|
2069
|
+
const messageTarget = turnMarks[from];
|
|
2070
|
+
const checkpointTarget = checkpointMarks[from];
|
|
2071
|
+
turnMarks.splice(from);
|
|
2072
|
+
checkpointMarks.splice(from);
|
|
2073
|
+
const files = await checkpoints.undoTo(checkpointTarget);
|
|
2074
|
+
const kept = agent.messages.slice(0, Math.min(messageTarget, agent.messages.length));
|
|
2075
|
+
agent.load(kept);
|
|
2076
|
+
session.reset();
|
|
2077
|
+
for (const message of kept) session.append(message);
|
|
2078
|
+
return {
|
|
2079
|
+
turns: count,
|
|
2080
|
+
restored: files?.restored ?? [],
|
|
2081
|
+
deleted: files?.deleted ?? [],
|
|
2082
|
+
skipped: files?.skipped ?? [],
|
|
2083
|
+
failed: files?.failed ?? []
|
|
2084
|
+
};
|
|
2085
|
+
},
|
|
1745
2086
|
get startedAt() {
|
|
1746
2087
|
return startedAt;
|
|
1747
2088
|
},
|
|
@@ -1749,6 +2090,7 @@ function createRuntime(opts) {
|
|
|
1749
2090
|
const nextProvider = makeProvider(ref);
|
|
1750
2091
|
modelRef = ref;
|
|
1751
2092
|
agent.provider = nextProvider;
|
|
2093
|
+
agent.setContextWindow(contextWindowFor(ref, env));
|
|
1752
2094
|
agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
|
|
1753
2095
|
},
|
|
1754
2096
|
setReasoning(level) {
|
|
@@ -1759,7 +2101,7 @@ function createRuntime(opts) {
|
|
|
1759
2101
|
}
|
|
1760
2102
|
|
|
1761
2103
|
// src/version.ts
|
|
1762
|
-
var VERSION = true ? "0.4.0-beta.
|
|
2104
|
+
var VERSION = true ? "0.4.0-beta.37" : "0.0.0-dev";
|
|
1763
2105
|
|
|
1764
2106
|
// src/cli.ts
|
|
1765
2107
|
function chooseLanguage(argv) {
|
|
@@ -1781,20 +2123,21 @@ async function main() {
|
|
|
1781
2123
|
console.log(helpText());
|
|
1782
2124
|
return 0;
|
|
1783
2125
|
}
|
|
1784
|
-
|
|
2126
|
+
let cwd = process.cwd();
|
|
2127
|
+
if (args.worktree) cwd = (await resolveWorktree(cwd, args.worktree)).path;
|
|
1785
2128
|
if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
|
|
1786
2129
|
if (args.command === "mcp") {
|
|
1787
|
-
const { runMcpCommand } = await import("./mcpCli-
|
|
1788
|
-
const { readSecret } = await import("./login-
|
|
2130
|
+
const { runMcpCommand } = await import("./mcpCli-5ZSAMBJS.js");
|
|
2131
|
+
const { readSecret } = await import("./login-MHVCON5R.js");
|
|
1789
2132
|
return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
|
|
1790
2133
|
}
|
|
1791
2134
|
if (args.command === "provider") {
|
|
1792
|
-
const { runProviderCommand } = await import("./providerCli-
|
|
1793
|
-
const { readSecret } = await import("./login-
|
|
2135
|
+
const { runProviderCommand } = await import("./providerCli-Z3TZ5XSZ.js");
|
|
2136
|
+
const { readSecret } = await import("./login-MHVCON5R.js");
|
|
1794
2137
|
return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
|
|
1795
2138
|
}
|
|
1796
2139
|
if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
|
|
1797
|
-
const { runIntegrationCommand } = await import("./integrationCli-
|
|
2140
|
+
const { runIntegrationCommand } = await import("./integrationCli-ZTZTQD2I.js");
|
|
1798
2141
|
return runIntegrationCommand(args, {
|
|
1799
2142
|
env: process.env,
|
|
1800
2143
|
cwd,
|
|
@@ -1817,20 +2160,20 @@ async function main() {
|
|
|
1817
2160
|
}
|
|
1818
2161
|
if (args.print) {
|
|
1819
2162
|
if (!args.prompt) throw new ConfigError(t('-p mode needs a task, e.g. bccli -p "explain this repo"'));
|
|
1820
|
-
const { serversToStart } = await import("./config-
|
|
2163
|
+
const { serversToStart } = await import("./config-JRO5EOKZ.js");
|
|
1821
2164
|
const plan = serversToStart(rt.home, cwd);
|
|
1822
2165
|
for (const s of plan.needTrust) {
|
|
1823
2166
|
console.error(t('Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).', { name: s.name }));
|
|
1824
2167
|
}
|
|
1825
2168
|
await rt.startMcp(plan.start);
|
|
1826
2169
|
try {
|
|
1827
|
-
return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll });
|
|
2170
|
+
return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll, outputFormat: args.outputFormat });
|
|
1828
2171
|
} finally {
|
|
1829
2172
|
await rt.mcp.stop();
|
|
1830
2173
|
}
|
|
1831
2174
|
}
|
|
1832
2175
|
if (!process.stdin.isTTY) throw new ConfigError(t('Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"'));
|
|
1833
|
-
const { startInteractive } = await import("./ui-
|
|
2176
|
+
const { startInteractive } = await import("./ui-YLIKFLGN.js");
|
|
1834
2177
|
return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
|
|
1835
2178
|
}
|
|
1836
2179
|
main().then(
|