@botconnector/bccli 0.4.0-beta.35 → 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/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-LRMC4AOV.js";
19
+ } from "./chunk-HXSREA7S.js";
14
20
  import {
15
21
  helpText,
16
22
  parseCliArgs
17
- } from "./chunk-NM3C75BT.js";
23
+ } from "./chunk-ASTNW3X2.js";
18
24
  import {
19
25
  Permissions
20
- } from "./chunk-ELML4KVQ.js";
26
+ } from "./chunk-R3ND76LW.js";
21
27
  import {
22
28
  addProviderKey,
23
- completionAssistantMessage,
24
- createProvider,
25
- hasPendingProgrammaticReplay,
26
29
  listAllModels
27
- } from "./chunk-GJAX4NCM.js";
30
+ } from "./chunk-BB4CVWHS.js";
28
31
  import {
29
32
  providerName,
30
33
  readGlobalConfigFile
31
- } from "./chunk-AOVIQXXO.js";
32
- import "./chunk-UJH2JT6H.js";
34
+ } from "./chunk-U66JJ6RN.js";
33
35
  import {
34
36
  runLogin
35
- } from "./chunk-FNBTY56Z.js";
37
+ } from "./chunk-FCQCZCKO.js";
36
38
  import {
37
39
  Session,
38
40
  pruneSessions
39
- } from "./chunk-SJH3YLW4.js";
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-IJGTX7RC.js";
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 z8 } from "zod";
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 as z2 } from "zod";
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 = z2.object({
279
- path: z2.string().describe("File path, relative to the project or absolute"),
280
- old_string: z2.string().describe("Exact text to replace; must be unique unless replace_all"),
281
- new_string: z2.string().describe("Replacement text"),
282
- replace_all: z2.boolean().optional().describe("Replace every occurrence")
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 { z as z3 } from "zod";
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: z3.object({
466
- url: z3.string().describe("Absolute http(s) URL"),
467
- prompt: z3.string().max(2e3).optional().describe("What to look for in the page; returns only the relevant excerpt"),
468
- offset: z3.number().int().min(0).optional().describe("Start reading at this character (to continue a truncated page)")
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
- let res;
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 fetch(url, { headers: { accept: ACCEPT }, signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)]), redirect: "manual" });
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 z4 } from "zod";
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: z4.object({
618
- pattern: z4.string().describe("Glob pattern"),
619
- path: z4.string().optional().describe("Base directory, default project root")
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 z5 } from "zod";
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: z5.object({
707
- pattern: z5.string().describe("Regular expression"),
708
- path: z5.string().optional().describe("Directory to search, default project root"),
709
- glob: z5.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
710
- ignore_case: z5.boolean().optional()
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 z6 } from "zod";
689
+ import { z as z5 } from "zod";
747
690
  var MAX_LINE = 2e3;
748
691
  var MAX_OUTPUT_CHARS3 = 4e4;
749
- var readTool = defineTool({
750
- name: "read",
751
- description: "Read a text file. Returns numbered lines (default first 2000). Use offset/limit for large files. Always read a file before editing it.",
752
- schema: z6.object({
753
- path: z6.string().describe("File path, relative to the project or absolute"),
754
- offset: z6.number().int().min(1).optional().describe("First line to read, 1-based"),
755
- limit: z6.number().int().min(1).optional().describe("Number of lines, default 2000")
756
- }),
757
- kind: "read",
758
- target: (input) => input.path,
759
- async run(input, ctx) {
760
- const abs = resolvePath(ctx.cwd, input.path);
761
- let buffer;
762
- try {
763
- buffer = await readFile3(abs);
764
- } catch (error) {
765
- return { output: `Cannot read ${input.path}: ${errorCode(error)}`, isError: true };
766
- }
767
- if (buffer.subarray(0, 8e3).includes(0)) {
768
- return { output: `${input.path} is a binary file; not read.`, isError: true };
769
- }
770
- const lines = buffer.toString("utf8").split(/\r?\n/);
771
- const start = (input.offset ?? 1) - 1;
772
- const limit = input.limit ?? 2e3;
773
- const requested = lines.slice(start, start + limit);
774
- const shown = [];
775
- let size = 0;
776
- for (let i = 0; i < requested.length; i++) {
777
- const line = requested[i];
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: remaining > 0 ? tokenBudgetHit ? `${body}
793
- \u2026 ${remaining} more lines (token budget reached; continue with offset=${nextOffset})` : `${body}
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 z7 } from "zod";
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: z7.object({
809
- path: z7.string().describe("File path, relative to the project or absolute"),
810
- content: z7.string().describe("Full new file 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 ?? z8.toJSONSchema(tool.schema);
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 Agent = class {
932
+ var Agent2 = class {
929
933
  constructor(opts) {
930
934
  this.opts = opts;
931
935
  this.provider = opts.provider;
@@ -935,7 +939,7 @@ var Agent = class {
935
939
  this.turnTools = this.tools;
936
940
  this.turnDefinitions = this.definitions;
937
941
  this.messages = [...opts.history ?? []];
938
- this.maxSteps = opts.maxSteps ?? 50;
942
+ this.maxSteps = typeof opts.maxSteps === "number" && Number.isFinite(opts.maxSteps) && opts.maxSteps > 0 ? Math.max(1, Math.floor(opts.maxSteps)) : void 0;
939
943
  this.contextWindow = opts.contextWindow ?? 128e3;
940
944
  this.systemPrompt = opts.systemPrompt;
941
945
  this.reasoning = opts.reasoning ?? "auto";
@@ -958,12 +962,15 @@ var Agent = class {
958
962
  readFiles = /* @__PURE__ */ new Set();
959
963
  webBudget = new WebBudget();
960
964
  maxSteps;
961
- // ponytail: one window for every model; read it from /v1/models metadata when providers expose it.
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 = 0;
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 }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
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,
@@ -1028,7 +1036,7 @@ ${completion.text}` });
1028
1036
  try {
1029
1037
  if (this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) await this.compact(signal);
1030
1038
  this.push({ role: "user", content: text });
1031
- for (let step = 0; step < this.maxSteps; step++) {
1039
+ for (let step = 0; this.maxSteps === void 0 || step < this.maxSteps; step++) {
1032
1040
  if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) {
1033
1041
  await this.compact(signal);
1034
1042
  this.push({ role: "user", content: `Continue this task using the summary above: ${text}` });
@@ -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,21 +1110,23 @@ ${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;
1116
1127
  }
1117
1128
  }
1118
- this.onEvent({ type: "stepLimit" });
1129
+ if (this.maxSteps !== void 0) this.onEvent({ type: "stepLimit", maxSteps: this.maxSteps });
1119
1130
  } catch (error) {
1120
1131
  if (signal.aborted) {
1121
1132
  this.onEvent({ type: "aborted" });
@@ -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.output;
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 = { restored: [], deleted: [], skipped: [], failed: [] };
1252
- for (const [path, original] of turn) {
1253
- try {
1254
- if (original === "skipped") result.skipped.push(path);
1255
- else if (original === null) {
1256
- await rm(path, { force: true });
1257
- result.deleted.push(path);
1258
- } else {
1259
- await writeFile3(path, original);
1260
- result.restored.push(path);
1261
- }
1262
- } catch {
1263
- result.failed.push(path);
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
- return result;
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 z9 } from "zod";
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
- const transport = config.type === "http" ? new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers ?? {} } }) : new StdioClientTransport({
1316
- command: config.command,
1317
- args: config.args ?? [],
1318
- env: { ...process.env, ...config.env },
1319
- stderr: "ignore"
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
- entry.state = { ...entry.state, status: "error", error: error.message };
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: z9.record(z9.string(), z9.unknown()),
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 z10 } from "zod";
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: z10.object({ plan: z10.string().describe("The plan, in markdown") }),
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 z11 } from "zod";
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: z11.object({ name: z11.string().describe("Skill name from the list in the system prompt") }),
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 z12 } from "zod";
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: z12.object({
1455
- query: z12.string().min(1).max(500).describe("Search query"),
1456
- max_results: z12.number().int().min(1).max(10).optional().describe("Number of results (default 6)")
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 z13 } from "zod";
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: z13.object({
1503
- agent: z13.string().describe("Agent name"),
1504
- description: z13.string().describe("3-6 word summary shown to the user"),
1505
- prompt: z13.string().describe("Full instructions; the subagent sees nothing else")
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
- // Parallel only when the resolved agent can do nothing but read (a project file may redefine "explore").
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 allowed = byName.get(input.agent)?.tools;
1512
- if (!allowed) return false;
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 allowed.every((name) => base.find((t2) => t2.name === name)?.kind === "read");
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 Agent({
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,
1693
+ // Every subagent runs with a step cap so a looping child cannot burn tokens forever.
1539
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
- if (ctx.ask) child.askPermission = ctx.ask;
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;
@@ -1560,14 +1719,14 @@ ${def.prompt}`,
1560
1719
  const report = last && typeof last.content === "string" ? last.content : "(no report)";
1561
1720
  const suffix = outcome?.type === "stepLimit" ? `
1562
1721
 
1563
- (The subagent stopped at its ${def.maxSteps ?? 50}-step limit.)` : "";
1722
+ (The subagent stopped at its ${outcome.maxSteps}-step limit.)` : "";
1564
1723
  return { output: report + suffix, display };
1565
1724
  }
1566
1725
  });
1567
1726
  }
1568
1727
 
1569
1728
  // src/tools/todo.ts
1570
- import { z as z14 } from "zod";
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: z14.object({
1588
- todos: z14.array(z14.object({ content: z14.string(), status: z14.enum(["pending", "in_progress", "completed"]) }))
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 baseTools = [...ALL_TOOLS, webSearch, createSkillTool(skills), createTodoTool(todos), createExitPlanTool({ permissions, interaction })];
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 Agent({
1971
+ const agent = new Agent2({
1670
1972
  provider,
1671
1973
  tools: [...baseTools, task],
1672
1974
  permissions,
@@ -1674,12 +1976,26 @@ function createRuntime(opts) {
1674
1976
  cwd: opts.cwd,
1675
1977
  history,
1676
1978
  reasoning,
1979
+ maxSteps: config.maxSteps,
1980
+ hooks: config.hooks,
1981
+ pluginEmit,
1982
+ env,
1983
+ contextWindow: contextWindowFor(modelRef, env),
1677
1984
  onMessage: (m) => session.append(m),
1678
1985
  onReset: () => session.reset(),
1679
- onTurnStart: () => checkpoints.beginTurn(),
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
+ },
1680
1995
  checkpoint: (path) => checkpoints.snapshot(path)
1681
1996
  });
1682
1997
  const mcp = new McpManager({
1998
+ home,
1683
1999
  onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
1684
2000
  // A later server may reuse a sanitized name; it must not inherit this one's "[a]" grants.
1685
2001
  onToolsRemoved: (names) => {
@@ -1728,6 +2044,8 @@ function createRuntime(opts) {
1728
2044
  agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1729
2045
  todos.set([]);
1730
2046
  checkpoints.clear();
2047
+ turnMarks.length = 0;
2048
+ checkpointMarks.length = 0;
1731
2049
  },
1732
2050
  newSession() {
1733
2051
  session = Session.create(home, opts.cwd);
@@ -1735,12 +2053,36 @@ function createRuntime(opts) {
1735
2053
  agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1736
2054
  todos.set([]);
1737
2055
  checkpoints.clear();
2056
+ turnMarks.length = 0;
2057
+ checkpointMarks.length = 0;
1738
2058
  startedAt = /* @__PURE__ */ new Date();
1739
2059
  },
1740
2060
  rebuildSystemPrompt() {
1741
- agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
2061
+ rebuildPrompt();
1742
2062
  },
2063
+ emitPlugins: (event, payload) => plugins.emit(event, payload),
1743
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
+ },
1744
2086
  get startedAt() {
1745
2087
  return startedAt;
1746
2088
  },
@@ -1748,6 +2090,7 @@ function createRuntime(opts) {
1748
2090
  const nextProvider = makeProvider(ref);
1749
2091
  modelRef = ref;
1750
2092
  agent.provider = nextProvider;
2093
+ agent.setContextWindow(contextWindowFor(ref, env));
1751
2094
  agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
1752
2095
  },
1753
2096
  setReasoning(level) {
@@ -1758,7 +2101,7 @@ function createRuntime(opts) {
1758
2101
  }
1759
2102
 
1760
2103
  // src/version.ts
1761
- var VERSION = true ? "0.4.0-beta.35" : "0.0.0-dev";
2104
+ var VERSION = true ? "0.4.0-beta.37" : "0.0.0-dev";
1762
2105
 
1763
2106
  // src/cli.ts
1764
2107
  function chooseLanguage(argv) {
@@ -1780,20 +2123,21 @@ async function main() {
1780
2123
  console.log(helpText());
1781
2124
  return 0;
1782
2125
  }
1783
- const cwd = process.cwd();
2126
+ let cwd = process.cwd();
2127
+ if (args.worktree) cwd = (await resolveWorktree(cwd, args.worktree)).path;
1784
2128
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1785
2129
  if (args.command === "mcp") {
1786
- const { runMcpCommand } = await import("./mcpCli-UGNEDBMU.js");
1787
- const { readSecret } = await import("./login-7IARC5WX.js");
2130
+ const { runMcpCommand } = await import("./mcpCli-5ZSAMBJS.js");
2131
+ const { readSecret } = await import("./login-MHVCON5R.js");
1788
2132
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1789
2133
  }
1790
2134
  if (args.command === "provider") {
1791
- const { runProviderCommand } = await import("./providerCli-4JTIW3YA.js");
1792
- const { readSecret } = await import("./login-7IARC5WX.js");
2135
+ const { runProviderCommand } = await import("./providerCli-Z3TZ5XSZ.js");
2136
+ const { readSecret } = await import("./login-MHVCON5R.js");
1793
2137
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1794
2138
  }
1795
2139
  if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
1796
- const { runIntegrationCommand } = await import("./integrationCli-W5LJQJ4J.js");
2140
+ const { runIntegrationCommand } = await import("./integrationCli-ZTZTQD2I.js");
1797
2141
  return runIntegrationCommand(args, {
1798
2142
  env: process.env,
1799
2143
  cwd,
@@ -1816,20 +2160,20 @@ async function main() {
1816
2160
  }
1817
2161
  if (args.print) {
1818
2162
  if (!args.prompt) throw new ConfigError(t('-p mode needs a task, e.g. bccli -p "explain this repo"'));
1819
- const { serversToStart } = await import("./config-DMF4AO7E.js");
2163
+ const { serversToStart } = await import("./config-JRO5EOKZ.js");
1820
2164
  const plan = serversToStart(rt.home, cwd);
1821
2165
  for (const s of plan.needTrust) {
1822
2166
  console.error(t('Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).', { name: s.name }));
1823
2167
  }
1824
2168
  await rt.startMcp(plan.start);
1825
2169
  try {
1826
- 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 });
1827
2171
  } finally {
1828
2172
  await rt.mcp.stop();
1829
2173
  }
1830
2174
  }
1831
2175
  if (!process.stdin.isTTY) throw new ConfigError(t('Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"'));
1832
- const { startInteractive } = await import("./ui-KK6ARPCL.js");
2176
+ const { startInteractive } = await import("./ui-YLIKFLGN.js");
1833
2177
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1834
2178
  }
1835
2179
  main().then(