@botconnector/bccli 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/{acp-3PHBAI64.js → acp-NU4HZHQO.js} +30 -6
  2. package/dist/acp.d.ts +1 -0
  3. package/dist/agent.d.ts +5 -1
  4. package/dist/args-NRTZWWEV.js +13 -0
  5. package/dist/args.d.ts +2 -0
  6. package/dist/checkpoints.d.ts +1 -0
  7. package/dist/{chunk-MFHCQQEN.js → chunk-3I5BW3ZT.js} +3 -2
  8. package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
  9. package/dist/{chunk-BGXNR74O.js → chunk-4W5SKQDC.js} +152 -31
  10. package/dist/{chunk-BC7UXFE4.js → chunk-6AUROP2E.js} +64 -3
  11. package/dist/{chunk-24JN75FZ.js → chunk-6M55UPES.js} +2 -2
  12. package/dist/{chunk-XV7ZVL4J.js → chunk-6UO5GX55.js} +26 -12
  13. package/dist/{chunk-75BECX4F.js → chunk-7I2ZGPUS.js} +4 -4
  14. package/dist/{chunk-BKGFA2PT.js → chunk-FUJGHCWN.js} +1 -1
  15. package/dist/{chunk-GN4W3T6E.js → chunk-GWVLPN4B.js} +4 -4
  16. package/dist/{chunk-JHPGOAXU.js → chunk-HQW4BROS.js} +4 -5
  17. package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
  18. package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
  19. package/dist/{chunk-WI22M2ID.js → chunk-NQOGVP3I.js} +2 -2
  20. package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
  21. package/dist/{chunk-PZRKU4UM.js → chunk-QNUJHKEL.js} +659 -162
  22. package/dist/{chunk-FLQPE4S5.js → chunk-RX4UQX43.js} +4 -1
  23. package/dist/{chunk-R5RE46CW.js → chunk-WDX62DFH.js} +40 -6
  24. package/dist/{chunk-MHIYLXJC.js → chunk-WFZKJY3R.js} +8 -4
  25. package/dist/{chunk-WNACAUDK.js → chunk-XTOATEI6.js} +66 -9
  26. package/dist/{chunk-BXQQCUAC.js → chunk-Z5NI5FIK.js} +7 -7
  27. package/dist/cli.js +60 -43
  28. package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
  29. package/dist/{config-DIDLDONU.js → config-ENDRMOTP.js} +3 -3
  30. package/dist/{config-IU76CPK2.js → config-LAINORU5.js} +2 -2
  31. package/dist/config.d.ts +2 -0
  32. package/dist/{ghBotCli-E3VPHNZY.js → ghBotCli-GCIUUMKQ.js} +21 -20
  33. package/dist/{integrationCli-Q2YPDTPG.js → integrationCli-HXCSEWH2.js} +2 -2
  34. package/dist/{login-FP3BB44A.js → login-YXSSB2HQ.js} +4 -4
  35. package/dist/lsp.d.ts +26 -0
  36. package/dist/mcp/manager.d.ts +18 -0
  37. package/dist/{mcpCli-SNBDUWJN.js → mcpCli-SETTCL3K.js} +6 -6
  38. package/dist/{modelsCli-CZ2HNGKR.js → modelsCli-656AKYZD.js} +6 -6
  39. package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
  40. package/dist/{pluginsCli-LPIUUDWB.js → pluginsCli-ZQNKWZHJ.js} +2 -2
  41. package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
  42. package/dist/prCli.d.ts +13 -0
  43. package/dist/print.d.ts +2 -0
  44. package/dist/provider.d.ts +5 -0
  45. package/dist/providerCli-V2576JXE.js +15 -0
  46. package/dist/sdk.d.ts +5 -1
  47. package/dist/sdk.js +1003 -230
  48. package/dist/{serve-KWOECK5W.js → serve-K5AH4MGO.js} +3 -3
  49. package/dist/{serve-X2ZV4S5U.js → serve-XPAGXIGS.js} +17 -17
  50. package/dist/session-V2VCAF7E.js +12 -0
  51. package/dist/session.d.ts +3 -0
  52. package/dist/setup-HVVHOIL4.js +24 -0
  53. package/dist/slash/info.d.ts +1 -0
  54. package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
  55. package/dist/tokenBudget.d.ts +1 -1
  56. package/dist/tools/diagnostics.d.ts +8 -0
  57. package/dist/tools/edit.d.ts +7 -2
  58. package/dist/tools/grep.d.ts +14 -2
  59. package/dist/tools/ignore.d.ts +2 -2
  60. package/dist/tools/paths.d.ts +2 -0
  61. package/dist/tools/sandbox.d.ts +3 -0
  62. package/dist/{transferCli-YUFPI6AH.js → transferCli-C6XS6QYH.js} +8 -6
  63. package/dist/ui/complete.d.ts +5 -0
  64. package/dist/ui/vim.d.ts +1 -1
  65. package/dist/{ui-IVIFNUCC.js → ui-43C6Y57K.js} +199 -54
  66. package/dist/{update-TZ2G6KQ3.js → update-AVZO4XID.js} +2 -2
  67. package/package.json +1 -1
  68. package/dist/args-W6BNKY7K.js +0 -13
  69. package/dist/providerCli-NMUFXAAS.js +0 -15
  70. package/dist/session-GKEXDJKZ.js +0 -12
  71. package/dist/setup-OV3NWQN5.js +0 -24
@@ -36,6 +36,9 @@ function runAcp(input, output, opts) {
36
36
  const replyError = (id, code, message) => write({ jsonrpc: "2.0", id, error: { code, message } });
37
37
  const update = (sessionId, body) => write({ jsonrpc: "2.0", method: "session/update", params: { sessionId, update: body } });
38
38
  const ioFor = (sid, session) => ({
39
+ onThinking(delta) {
40
+ update(sid, { sessionUpdate: "agent_thought_chunk", content: { type: "text", text: delta } });
41
+ },
39
42
  toolStart(id, tool, target) {
40
43
  session.open.push({ id, tool, target });
41
44
  update(sid, { sessionUpdate: "tool_call", toolCallId: id, title: titleOf(tool, target), kind: kindOf(tool), status: "in_progress", rawInput: { target } });
@@ -89,7 +92,7 @@ function runAcp(input, output, opts) {
89
92
  protocolVersion: ACP_PROTOCOL_VERSION,
90
93
  agentCapabilities: {
91
94
  loadSession: false,
92
- promptCapabilities: { image: false, audio: false, embeddedContext: false }
95
+ promptCapabilities: { image: false, audio: false, embeddedContext: true }
93
96
  }
94
97
  });
95
98
  }
@@ -108,7 +111,17 @@ function runAcp(input, output, opts) {
108
111
  const sid = params?.sessionId ?? "";
109
112
  const session = sessions.get(sid);
110
113
  if (!session) return replyError(id, -32602, "Unknown session");
111
- const text = (params?.prompt ?? []).filter((p) => p?.type === "text").map((p) => String(p.text ?? "")).join("\n");
114
+ if (session.controller) return replyError(id, -32e3, "Session is busy");
115
+ const text = (params?.prompt ?? []).map((p) => {
116
+ if (p?.type === "text") return String(p.text ?? "");
117
+ if ((p?.type === "resource" || p?.type === "embeddedContext") && p.resource?.text) {
118
+ const label = p.resource.uri ? `
119
+ [${p.resource.uri}]
120
+ ` : "\n";
121
+ return `${label}${p.resource.text}`;
122
+ }
123
+ return "";
124
+ }).filter(Boolean).join("\n");
112
125
  const controller = new AbortController();
113
126
  session.controller = controller;
114
127
  let cancelled = false;
@@ -122,8 +135,10 @@ function runAcp(input, output, opts) {
122
135
  if (cancelled) return reply(id, { stopReason: "cancelled" });
123
136
  return replyError(id, -32603, e.message);
124
137
  } finally {
125
- session.controller = null;
126
- session.open = [];
138
+ if (session.controller === controller) {
139
+ session.controller = null;
140
+ session.open = [];
141
+ }
127
142
  }
128
143
  return reply(id, { stopReason: cancelled ? "cancelled" : "end_turn" });
129
144
  }
@@ -150,6 +165,10 @@ function runAcp(input, output, opts) {
150
165
  };
151
166
  const onEnd = () => {
152
167
  input.removeListener("data", onData);
168
+ for (const session of sessions.values()) {
169
+ session.controller?.abort();
170
+ for (const settle of [...session.pending]) settle("no");
171
+ }
153
172
  resolve();
154
173
  };
155
174
  input.on("data", onData);
@@ -161,17 +180,22 @@ function runtimeAgent(cwd, allowAll = false) {
161
180
  return {
162
181
  async run(text, onChunk, signal, io) {
163
182
  if (!rt) {
164
- const { parseCliArgs } = await import("./args-W6BNKY7K.js");
165
- const { createRuntime } = await import("./setup-OV3NWQN5.js");
183
+ const { parseCliArgs } = await import("./args-NRTZWWEV.js");
184
+ const { createRuntime } = await import("./setup-HVVHOIL4.js");
166
185
  rt = createRuntime({ cwd, args: { ...parseCliArgs([]), allowAll } });
167
186
  }
168
187
  if (io && !allowAll) rt.agent.askPermission = (req) => io.askPermission(req);
188
+ let turnError;
169
189
  rt.agent.onEvent = (event) => {
170
190
  if (event.type === "text") onChunk(event.delta);
191
+ else if (event.type === "thinking") io?.onThinking?.(event.delta);
171
192
  else if (event.type === "toolStart") io?.toolStart(event.id, event.tool, event.target);
172
193
  else if (event.type === "toolEnd") io?.toolEnd(event.id, event.output, event.isError);
194
+ else if (event.type === "error") turnError = event.message;
195
+ else if (event.type === "budgetExceeded") turnError = `Budget exceeded (${event.kind})`;
173
196
  };
174
197
  await rt.agent.run(text, signal);
198
+ if (turnError) throw new Error(turnError);
175
199
  }
176
200
  };
177
201
  }
package/dist/acp.d.ts CHANGED
@@ -5,6 +5,7 @@ export interface AcpIo {
5
5
  toolStart(id: string, tool: string, target: string): void;
6
6
  toolEnd(id: string, output: string, isError: boolean): void;
7
7
  askPermission(req: PermissionAsk): Promise<PermissionAnswer>;
8
+ onThinking?(delta: string): void;
8
9
  }
9
10
  /** The minimum an ACP client needs: run one prompt, stream text chunks, allow cancellation. */
10
11
  export interface AcpAgent {
package/dist/agent.d.ts CHANGED
@@ -109,14 +109,18 @@ export declare class Agent {
109
109
  private definitions;
110
110
  private turnTools;
111
111
  private turnDefinitions;
112
- private readonly readFiles;
112
+ readonly readFiles: Set<string>;
113
113
  private readonly webBudget;
114
114
  private readonly maxSteps?;
115
115
  private contextWindow;
116
+ private budgetModel;
116
117
  private systemPrompt;
117
118
  constructor(opts: AgentOptions);
118
119
  setSystemPrompt(text: string): void;
119
120
  setContextWindow(window: number): void;
121
+ getContextWindow(): number;
122
+ setBudgetModel(model: string): void;
123
+ getBudgetModel(): string;
120
124
  setTools(tools: Tool[]): void;
121
125
  private isParallelSafe;
122
126
  private push;
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ helpText,
4
+ parseCliArgs
5
+ } from "./chunk-6UO5GX55.js";
6
+ import "./chunk-WFZKJY3R.js";
7
+ import "./chunk-KPTML72Q.js";
8
+ import "./chunk-RX4UQX43.js";
9
+ import "./chunk-3WHI4LN3.js";
10
+ export {
11
+ helpText,
12
+ parseCliArgs
13
+ };
package/dist/args.d.ts CHANGED
@@ -45,6 +45,8 @@ export interface CliArgs {
45
45
  project?: string;
46
46
  /** `bccli pr` — explicit owner/name repo when cwd is not a git checkout of the PR. */
47
47
  repo?: string;
48
+ /** Automatic fallback model ref when the primary model fails with 429 or 5xx. */
49
+ fallbackModel?: string;
48
50
  }
49
51
  /** Help text in the current language (call it after the language is set). */
50
52
  export declare const helpText: () => string;
@@ -8,6 +8,7 @@ export declare class CheckpointStore {
8
8
  private readonly history;
9
9
  private current;
10
10
  private pendingRedo;
11
+ private offset;
11
12
  beginTurn(): void;
12
13
  /** Call before writing a file; only the first snapshot per file per turn is kept. */
13
14
  snapshot(path: string): Promise<void>;
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  quarantineCorruptFile,
4
4
  writeFileAtomic
5
- } from "./chunk-FLQPE4S5.js";
5
+ } from "./chunk-RX4UQX43.js";
6
6
 
7
7
  // src/mcp/oauth.ts
8
8
  import { spawn } from "child_process";
@@ -156,8 +156,9 @@ async function authorizeMcpServer(spec, deps) {
156
156
  res.end("<html><body><p>BCCLI is connected. You can close this tab.</p></body></html>");
157
157
  settle.resolve(code);
158
158
  } catch (error) {
159
+ const escaped = error.message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
159
160
  res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
160
- res.end(`<html><body><p>${error.message}</p></body></html>`);
161
+ res.end(`<html><body><p>${escaped}</p></body></html>`);
161
162
  settle.reject(error);
162
163
  }
163
164
  });
@@ -458,7 +458,8 @@ Opsi:
458
458
  "gh is not available ({error}); install GitHub CLI first": "gh tidak tersedia ({error}); pasang GitHub CLI dulu",
459
459
  "gh pr checkout {n} failed (exit {code})": "gh pr checkout {n} gagal (exit {code})",
460
460
  "checked out PR #{n}": "checkout PR #{n} selesai",
461
- "--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya"
461
+ "--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya",
462
+ "view or change configuration settings": "lihat atau ubah pengaturan konfigurasi"
462
463
  };
463
464
 
464
465
  // src/i18n.ts
@@ -4,14 +4,21 @@ import {
4
4
  } from "./chunk-PSO7R5AD.js";
5
5
  import {
6
6
  t
7
- } from "./chunk-UZWJQF67.js";
7
+ } from "./chunk-3WHI4LN3.js";
8
8
 
9
9
  // src/tools/bash.ts
10
10
  import { spawn } from "child_process";
11
- import { existsSync as existsSync2 } from "fs";
11
+ import { existsSync as existsSync2, writeFileSync } from "fs";
12
+ import { tmpdir } from "os";
13
+ import { join } from "path";
12
14
  import { z } from "zod";
13
15
 
14
16
  // src/tools/sandbox.ts
17
+ function winPathToWsl(winPath) {
18
+ const normalized = winPath.replace(/\\/g, "/");
19
+ const m = /^([A-Za-z]):\/(.*)$/.exec(normalized);
20
+ return m ? `/mnt/${m[1].toLowerCase()}/${m[2]}` : normalized;
21
+ }
15
22
  function darwinProfile(cwd, network) {
16
23
  const writable = [cwd, "/tmp", "/private/tmp", "/dev", "/var/folders"].map((p) => `(subpath "${p}")`).join(" ");
17
24
  return [
@@ -55,6 +62,33 @@ function sandboxArgv(command, opts) {
55
62
  command
56
63
  ];
57
64
  }
65
+ if (opts.platform === "win32" && (opts.wslBwrap || process.env.BCCLI_WIN32_WSL_SANDBOX === "1")) {
66
+ const wslCwd = winPathToWsl(opts.cwd);
67
+ return [
68
+ "wsl.exe",
69
+ "--",
70
+ "bwrap",
71
+ "--ro-bind",
72
+ "/",
73
+ "/",
74
+ "--dev-bind",
75
+ "/dev",
76
+ "/dev",
77
+ "--proc",
78
+ "/proc",
79
+ "--bind",
80
+ wslCwd,
81
+ wslCwd,
82
+ "--tmpfs",
83
+ "/tmp",
84
+ "--die-with-parent",
85
+ ...opts.network ? [] : ["--unshare-net"],
86
+ "--",
87
+ "/bin/bash",
88
+ "-c",
89
+ command
90
+ ];
91
+ }
58
92
  return void 0;
59
93
  }
60
94
 
@@ -236,7 +270,7 @@ function offlineEnv(env = process.env) {
236
270
  };
237
271
  }
238
272
  function runCommand(command, opts) {
239
- return new Promise((resolve) => {
273
+ return new Promise((resolve3) => {
240
274
  const child = spawnChild(command, opts.cwd, opts.env, opts.shell ?? defaultShell());
241
275
  if (!exitHookInstalled) {
242
276
  process.on("exit", killAllCommands);
@@ -249,7 +283,14 @@ function runCommand(command, opts) {
249
283
  let dropped = 0;
250
284
  let timedOut = false;
251
285
  let aborted = false;
286
+ const fullChunks = [];
287
+ let fullBytes = 0;
288
+ const MAX_SPILL_BYTES = 10 * 1024 * 1024;
252
289
  const onData = (data) => {
290
+ if (fullBytes < MAX_SPILL_BYTES) {
291
+ fullChunks.push(data);
292
+ fullBytes += data.length;
293
+ }
253
294
  let s = data;
254
295
  if (head.length < HALF) {
255
296
  const take = s.slice(0, HALF - head.length);
@@ -288,10 +329,19 @@ function runCommand(command, opts) {
288
329
  active.delete(killAsAborted);
289
330
  clearTimeout(timer);
290
331
  opts.signal.removeEventListener("abort", onAbort);
332
+ let spillNote = "";
333
+ if (dropped && fullChunks.length) {
334
+ try {
335
+ const spillPath = join(tmpdir(), `bccli-bash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
336
+ writeFileSync(spillPath, fullChunks.join(""), "utf8");
337
+ spillNote = `; full output saved to ${spillPath}`;
338
+ } catch {
339
+ }
340
+ }
291
341
  const output = dropped ? `${head}
292
- \u2026 [${dropped} characters truncated] \u2026
342
+ \u2026 [${dropped} characters truncated${spillNote}] \u2026
293
343
  ${tail}` : head + tail;
294
- resolve({ output: output + extra, exitCode, timedOut, aborted });
344
+ resolve3({ output: output + extra, exitCode, timedOut, aborted });
295
345
  };
296
346
  child.on("error", (error) => finish(null, error.message));
297
347
  child.on("close", (code) => finish(code));
@@ -403,7 +453,7 @@ var BUILTIN_AGENTS = [
403
453
 
404
454
  // src/context.ts
405
455
  import { existsSync as existsSync3, readFileSync } from "fs";
406
- import { dirname, join } from "path";
456
+ import { dirname, join as join2 } from "path";
407
457
  var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
408
458
  function readIf(path) {
409
459
  try {
@@ -418,7 +468,7 @@ function instructionPaths(cwd, home) {
418
468
  dirs2.unshift(dir);
419
469
  if (dirname(dir) === dir) break;
420
470
  }
421
- return [join(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join(dir, name)))];
471
+ return [join2(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join2(dir, name)))];
422
472
  }
423
473
  function loadInstructions(cwd, home) {
424
474
  const parts = [];
@@ -431,7 +481,7 @@ ${text}`);
431
481
  }
432
482
  function isGitRepo(cwd) {
433
483
  for (let dir = cwd; ; dir = dirname(dir)) {
434
- if (existsSync3(join(dir, ".git"))) return true;
484
+ if (existsSync3(join2(dir, ".git"))) return true;
435
485
  if (dirname(dir) === dir) return false;
436
486
  }
437
487
  }
@@ -476,15 +526,33 @@ ${instructions}` : ""}`;
476
526
 
477
527
  // src/extensions.ts
478
528
  import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
479
- import { basename, join as join2 } from "path";
529
+ import { basename, join as join3 } from "path";
480
530
  function parseFrontmatter(text) {
481
531
  const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
482
532
  if (!match) return { data: {}, body: text };
483
533
  const data = {};
534
+ let currentListKey;
484
535
  for (const line of match[1].split(/\r?\n/)) {
536
+ const listItem = /^\s*-\s+(.+)$/.exec(line);
537
+ if (listItem && currentListKey) {
538
+ const item = listItem[1].trim().replace(/^["']|["']$/g, "");
539
+ const prev = data[currentListKey];
540
+ if (Array.isArray(prev)) prev.push(item);
541
+ else data[currentListKey] = [item];
542
+ continue;
543
+ }
485
544
  const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
486
- if (!kv) continue;
545
+ if (!kv) {
546
+ if (line.trim()) currentListKey = void 0;
547
+ continue;
548
+ }
487
549
  let value = kv[2].trim();
550
+ if (!value) {
551
+ currentListKey = kv[1];
552
+ data[kv[1]] = "";
553
+ continue;
554
+ }
555
+ currentListKey = void 0;
488
556
  if (value.startsWith("[") && value.endsWith("]")) {
489
557
  data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
490
558
  continue;
@@ -496,7 +564,7 @@ function parseFrontmatter(text) {
496
564
  }
497
565
  var str = (v) => Array.isArray(v) ? v.join(", ") : v;
498
566
  function dirs(r, kind) {
499
- return [join2(r.userHome, ".claude", kind), join2(r.home, kind), join2(r.cwd, ".claude", kind), join2(r.cwd, ".bccli", kind)];
567
+ return [join3(r.userHome, ".claude", kind), join3(r.home, kind), join3(r.cwd, ".claude", kind), join3(r.cwd, ".bccli", kind)];
500
568
  }
501
569
  function list(dir) {
502
570
  try {
@@ -509,11 +577,11 @@ function loadSkills(r) {
509
577
  const byName = /* @__PURE__ */ new Map();
510
578
  for (const dir of dirs(r, "skills")) {
511
579
  for (const entry of list(dir)) {
512
- const file = join2(dir, entry, "SKILL.md");
580
+ const file = join3(dir, entry, "SKILL.md");
513
581
  if (!existsSync4(file)) continue;
514
582
  const { data } = parseFrontmatter(readFileSync2(file, "utf8"));
515
583
  const name = str(data.name) || entry;
516
- byName.set(name, { name, description: str(data.description) ?? "", dir: join2(dir, entry), file });
584
+ byName.set(name, { name, description: str(data.description) ?? "", dir: join3(dir, entry), file });
517
585
  }
518
586
  }
519
587
  return [...byName.values()];
@@ -522,7 +590,7 @@ function loadCommands(r) {
522
590
  const byName = /* @__PURE__ */ new Map();
523
591
  for (const dir of dirs(r, "commands")) {
524
592
  for (const entry of list(dir)) {
525
- const file = join2(dir, entry);
593
+ const file = join3(dir, entry);
526
594
  if (!entry.endsWith(".md") || !statSync(file).isFile()) continue;
527
595
  const { data, body } = parseFrontmatter(readFileSync2(file, "utf8"));
528
596
  const def = { name: basename(entry, ".md"), body: body.trim() };
@@ -538,7 +606,7 @@ function loadAgentDefs(r) {
538
606
  for (const dir of dirs(r, "agents")) {
539
607
  for (const entry of list(dir)) {
540
608
  if (!entry.endsWith(".md")) continue;
541
- const { data, body } = parseFrontmatter(readFileSync2(join2(dir, entry), "utf8"));
609
+ const { data, body } = parseFrontmatter(readFileSync2(join3(dir, entry), "utf8"));
542
610
  const name = str(data.name) || basename(entry, ".md");
543
611
  const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
544
612
  if (data.tools)
@@ -573,7 +641,7 @@ function runOne(command, event, payload, opts) {
573
641
  ...payload.input !== void 0 ? { BCCLI_HOOK_INPUT: JSON.stringify(payload.input) } : {},
574
642
  ...payload.output !== void 0 ? { BCCLI_HOOK_OUTPUT: payload.output } : {}
575
643
  };
576
- return new Promise((resolve) => {
644
+ return new Promise((resolve3) => {
577
645
  let stdout = "";
578
646
  let stderr = "";
579
647
  let timedOut = false;
@@ -582,7 +650,7 @@ function runOne(command, event, payload, opts) {
582
650
  if (settled) return;
583
651
  settled = true;
584
652
  clearTimeout(timer);
585
- resolve(result);
653
+ resolve3(result);
586
654
  };
587
655
  let child;
588
656
  const shell = defaultShell();
@@ -598,7 +666,7 @@ function runOne(command, event, payload, opts) {
598
666
  child = spawn2(command, { shell: true, env, cwd: opts.cwd ?? process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
599
667
  }
600
668
  } catch (error) {
601
- resolve({ failure: error.message });
669
+ resolve3({ failure: error.message });
602
670
  return;
603
671
  }
604
672
  const timer = setTimeout(() => {
@@ -648,6 +716,38 @@ async function runHooks(hooks, event, payload = {}, opts = {}) {
648
716
  return { blocked, warnings };
649
717
  }
650
718
 
719
+ // src/tools/paths.ts
720
+ import { statSync as statSync2 } from "fs";
721
+ import { isAbsolute, resolve } from "path";
722
+ var readMtimes = /* @__PURE__ */ new WeakMap();
723
+ function resolvePath(cwd, path) {
724
+ return isAbsolute(path) ? path : resolve(cwd, path);
725
+ }
726
+ function errorCode(error) {
727
+ return error.code ?? error.message;
728
+ }
729
+ function recordFileMtime(readFiles, abs) {
730
+ readFiles.add(abs);
731
+ try {
732
+ let map = readMtimes.get(readFiles);
733
+ if (!map) {
734
+ map = /* @__PURE__ */ new Map();
735
+ readMtimes.set(readFiles, map);
736
+ }
737
+ map.set(abs, statSync2(abs).mtimeMs);
738
+ } catch {
739
+ }
740
+ }
741
+ function isFileModifiedSinceRead(readFiles, abs) {
742
+ const recorded = readMtimes.get(readFiles)?.get(abs);
743
+ if (recorded === void 0) return false;
744
+ try {
745
+ return statSync2(abs).mtimeMs > recorded + 50;
746
+ } catch {
747
+ return false;
748
+ }
749
+ }
750
+
651
751
  // src/slash/memory.ts
652
752
  import { appendFileSync, existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
653
753
  function instructionFiles(cwd, home) {
@@ -665,22 +765,38 @@ function parseMemoryArgs(args) {
665
765
 
666
766
  // src/tools/ignore.ts
667
767
  import { readFileSync as readFileSync4 } from "fs";
668
- import { join as join3 } from "path";
669
- function ignorePatterns(root) {
768
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
769
+ function ignorePatterns(root, projectRoot) {
670
770
  const patterns = ["**/node_modules/**", "**/.git/**"];
671
- let lines = [];
672
- try {
673
- lines = readFileSync4(join3(root, ".gitignore"), "utf8").split(/\r?\n/);
674
- } catch {
675
- return patterns;
771
+ const seen = /* @__PURE__ */ new Set();
772
+ const dirs2 = [];
773
+ let cur = resolve2(root);
774
+ const stop = projectRoot ? resolve2(projectRoot) : cur;
775
+ while (true) {
776
+ if (!seen.has(cur)) {
777
+ seen.add(cur);
778
+ dirs2.push(cur);
779
+ }
780
+ if (cur === stop || !cur.startsWith(stop)) break;
781
+ const parent = dirname2(cur);
782
+ if (parent === cur) break;
783
+ cur = parent;
676
784
  }
677
- for (const raw of lines) {
678
- const line = raw.trim();
679
- if (!line || line.startsWith("#") || line.startsWith("!")) continue;
680
- const p = line.replace(/^\//, "").replace(/\/$/, "");
681
- patterns.push(`**/${p}`, `**/${p}/**`);
785
+ for (const dir of dirs2) {
786
+ let lines = [];
787
+ try {
788
+ lines = readFileSync4(join4(dir, ".gitignore"), "utf8").split(/\r?\n/);
789
+ } catch {
790
+ continue;
791
+ }
792
+ for (const raw of lines) {
793
+ const line = raw.trim();
794
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
795
+ const p = line.replace(/^\//, "").replace(/\/$/, "");
796
+ patterns.push(`**/${p}`, `**/${p}/**`);
797
+ }
682
798
  }
683
- return patterns;
799
+ return [...new Set(patterns)];
684
800
  }
685
801
 
686
802
  // src/tools/globSafety.ts
@@ -713,11 +829,16 @@ function normalizeGlobPath(path) {
713
829
  }
714
830
 
715
831
  export {
832
+ resolveShell,
716
833
  runHooks,
717
834
  renderBackgroundTasks,
718
835
  runCommand,
719
836
  createBashTool,
720
837
  bashTool,
838
+ resolvePath,
839
+ errorCode,
840
+ recordFileMtime,
841
+ isFileModifiedSinceRead,
721
842
  ignorePatterns,
722
843
  validateGlobPattern,
723
844
  normalizeGlobPath,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  contentText
4
- } from "./chunk-WNACAUDK.js";
4
+ } from "./chunk-XTOATEI6.js";
5
5
 
6
6
  // src/session.ts
7
7
  import { randomBytes } from "crypto";
8
8
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
9
- import { join } from "path";
9
+ import { basename, join } from "path";
10
10
  function projectDir(home, cwd) {
11
11
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-100) || "root";
12
12
  return join(home, "sessions", slug);
@@ -27,6 +27,9 @@ var Session = class _Session {
27
27
  this.file = file;
28
28
  }
29
29
  file;
30
+ get id() {
31
+ return basename(this.file, ".jsonl");
32
+ }
30
33
  static create(home, cwd, now = /* @__PURE__ */ new Date()) {
31
34
  const dir = projectDir(home, cwd);
32
35
  mkdirSync(dir, { recursive: true });
@@ -38,7 +41,7 @@ var Session = class _Session {
38
41
  if (!existsSync(dir)) return [];
39
42
  return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
40
43
  const session = new _Session(join(dir, f));
41
- const first = session.load().find((m) => m.role === "user");
44
+ const first = session.loadHistory().find((m) => m.role === "user");
42
45
  return { session, mtime: statSync(session.file).mtime, preview: first ? contentText(first.content).slice(0, 80) : "" };
43
46
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime() || b.session.file.localeCompare(a.session.file));
44
47
  }
@@ -88,6 +91,35 @@ var Session = class _Session {
88
91
  }
89
92
  return void 0;
90
93
  }
94
+ /** Accumulated token usage since the last non-compaction reset. */
95
+ totalUsage() {
96
+ const segments = [{ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } }];
97
+ for (const record of this.records()) {
98
+ const cur = segments[segments.length - 1];
99
+ if (record.t === "reset") {
100
+ segments.push({ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } });
101
+ } else if (record.t === "msg") {
102
+ cur.msgs.push(record.m);
103
+ } else if (record.t === "usage") {
104
+ cur.usage.inputTokens += record.inputTokens;
105
+ cur.usage.outputTokens += record.outputTokens;
106
+ if (record.cachedInputTokens) cur.usage.cachedInputTokens = (cur.usage.cachedInputTokens ?? 0) + record.cachedInputTokens;
107
+ if (record.cacheWriteTokens) cur.usage.cacheWriteTokens = (cur.usage.cacheWriteTokens ?? 0) + record.cacheWriteTokens;
108
+ }
109
+ }
110
+ let total = { ...segments[0].usage };
111
+ for (const seg of segments.slice(1)) {
112
+ if (!isCompactionSummary(seg.msgs[0])) {
113
+ total = { ...seg.usage };
114
+ } else {
115
+ total.inputTokens += seg.usage.inputTokens;
116
+ total.outputTokens += seg.usage.outputTokens;
117
+ if (seg.usage.cachedInputTokens) total.cachedInputTokens = (total.cachedInputTokens ?? 0) + seg.usage.cachedInputTokens;
118
+ if (seg.usage.cacheWriteTokens) total.cacheWriteTokens = (total.cacheWriteTokens ?? 0) + seg.usage.cacheWriteTokens;
119
+ }
120
+ }
121
+ return total;
122
+ }
91
123
  /**
92
124
  * What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
93
125
  * its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
@@ -100,12 +132,22 @@ var Session = class _Session {
100
132
  else if (record.t === "msg") segments[segments.length - 1].push(record.m);
101
133
  }
102
134
  let shown = segments[0];
135
+ let lastSummary;
103
136
  for (const segment of segments.slice(1)) {
104
137
  if (!isCompactionSummary(segment[0])) {
105
138
  shown = segment;
139
+ lastSummary = void 0;
106
140
  continue;
107
141
  }
108
142
  const kept = segment.slice(2);
143
+ if (lastSummary && sameMessage(segment[0], lastSummary)) {
144
+ const subIdx = findContiguousSlice(shown, kept);
145
+ if (subIdx !== -1) {
146
+ shown = shown.slice(0, subIdx + kept.length);
147
+ continue;
148
+ }
149
+ }
150
+ lastSummary = segment[0];
109
151
  let at = 0;
110
152
  while (at <= shown.length && !endsWithPrefixOf(shown, at, kept)) at++;
111
153
  shown = [...shown, ...kept.slice(shown.length - at)];
@@ -132,12 +174,31 @@ function endsWithPrefixOf(shown, at, kept) {
132
174
  for (let j = 0; j < n; j++) if (!sameMessage(shown[at + j], kept[j])) return false;
133
175
  return true;
134
176
  }
177
+ function findContiguousSlice(shown, slice) {
178
+ if (slice.length === 0) return shown.length;
179
+ for (let i = 0; i + slice.length <= shown.length; i++) {
180
+ let ok = true;
181
+ for (let j = 0; j < slice.length; j++) {
182
+ if (!sameMessage(shown[i + j], slice[j])) {
183
+ ok = false;
184
+ break;
185
+ }
186
+ }
187
+ if (ok) return i;
188
+ }
189
+ return -1;
190
+ }
135
191
  function pruneSessions(home, now = Date.now(), maxAgeDays = 30) {
136
192
  const root = join(home, "sessions");
137
193
  if (!existsSync(root)) return 0;
138
194
  let removed = 0;
139
195
  for (const project of readdirSync(root)) {
140
196
  const dir = join(root, project);
197
+ try {
198
+ if (!statSync(dir).isDirectory()) continue;
199
+ } catch {
200
+ continue;
201
+ }
141
202
  for (const file of readdirSync(dir)) {
142
203
  const path = join(dir, file);
143
204
  if (now - statSync(path).mtimeMs > maxAgeDays * 864e5) {
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  ConfigError,
4
4
  writeFileAtomic
5
- } from "./chunk-FLQPE4S5.js";
5
+ } from "./chunk-RX4UQX43.js";
6
6
  import {
7
7
  t
8
- } from "./chunk-UZWJQF67.js";
8
+ } from "./chunk-3WHI4LN3.js";
9
9
 
10
10
  // src/mcp/config.ts
11
11
  import { createHash } from "crypto";