@botconnector/bccli 0.3.0-beta.3 → 0.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,39 +2,41 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-RDQ7HPWS.js";
5
+ } from "./chunk-UN7QXPCB.js";
6
6
  import {
7
7
  addProviderKey,
8
8
  createProvider,
9
9
  listAllModels
10
- } from "./chunk-5ISYVAXI.js";
10
+ } from "./chunk-2JBRLGKD.js";
11
11
  import {
12
12
  runLogin
13
- } from "./chunk-R37QWDTL.js";
14
- import {
15
- Session,
16
- pruneSessions
17
- } from "./chunk-SJH3YLW4.js";
13
+ } from "./chunk-L7L4RGKG.js";
18
14
  import {
15
+ BUILTIN_AGENTS,
16
+ buildSystemPrompt,
19
17
  ignorePatterns,
20
18
  loadAgentDefs,
21
19
  loadCommands,
22
20
  loadSkills,
23
21
  parseFrontmatter,
24
22
  runPrint
25
- } from "./chunk-TBPPXOOZ.js";
23
+ } from "./chunk-OSWQPSA4.js";
26
24
  import {
27
25
  Permissions
28
- } from "./chunk-5XN44Q55.js";
26
+ } from "./chunk-ELML4KVQ.js";
29
27
  import {
30
28
  providerName
31
- } from "./chunk-5C6NQFSI.js";
29
+ } from "./chunk-U7DLMVPZ.js";
30
+ import {
31
+ Session,
32
+ pruneSessions
33
+ } from "./chunk-SJH3YLW4.js";
32
34
  import {
33
35
  ConfigError,
34
36
  bccliHome,
35
37
  loadConfig,
36
38
  resolveModel
37
- } from "./chunk-Z5JM76EL.js";
39
+ } from "./chunk-RZUJYAQV.js";
38
40
 
39
41
  // src/tools/index.ts
40
42
  import { z as z8 } from "zod";
@@ -268,6 +270,7 @@ var editTool = defineTool({
268
270
  }
269
271
  const next = applyEdit(content, input);
270
272
  if (typeof next !== "string") return next;
273
+ await ctx.checkpoint?.(abs);
271
274
  await writeFile(abs, next);
272
275
  return { output: `Mengedit ${input.path}.`, display: formatDiff(diffLines(content, next)) };
273
276
  }
@@ -497,6 +500,7 @@ var writeTool = defineTool({
497
500
  return { output: `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.`, isError: true };
498
501
  }
499
502
  try {
503
+ await ctx.checkpoint?.(abs);
500
504
  await mkdir(dirname(abs), { recursive: true });
501
505
  await writeFile2(abs, input.content);
502
506
  } catch (error) {
@@ -532,6 +536,7 @@ var Agent = class {
532
536
  this.messages = [...opts.history ?? []];
533
537
  this.maxSteps = opts.maxSteps ?? 50;
534
538
  this.contextWindow = opts.contextWindow ?? 128e3;
539
+ this.systemPrompt = opts.systemPrompt;
535
540
  }
536
541
  opts;
537
542
  provider;
@@ -551,6 +556,10 @@ var Agent = class {
551
556
  maxSteps;
552
557
  // ponytail: one window for every model; read it from /v1/models metadata when providers expose it.
553
558
  contextWindow;
559
+ systemPrompt;
560
+ setSystemPrompt(text) {
561
+ this.systemPrompt = text;
562
+ }
554
563
  setTools(tools) {
555
564
  this.tools = tools;
556
565
  this.definitions = toolDefinitions(tools);
@@ -569,6 +578,12 @@ var Agent = class {
569
578
  this.messages.push(message);
570
579
  this.opts.onMessage?.(message);
571
580
  }
581
+ /** Swaps in another conversation (resume, new session) without writing to the session log. */
582
+ load(messages) {
583
+ this.messages = messages;
584
+ this.readFiles.clear();
585
+ this.lastInputTokens = 0;
586
+ }
572
587
  clear() {
573
588
  this.messages = [];
574
589
  this.readFiles.clear();
@@ -577,7 +592,7 @@ var Agent = class {
577
592
  }
578
593
  async compact(signal) {
579
594
  const completion = await this.provider.chat({
580
- messages: [{ role: "system", content: this.opts.systemPrompt }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
595
+ messages: [{ role: "system", content: this.systemPrompt }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
581
596
  // Some gateways reject tool_calls in history when no tools are declared.
582
597
  tools: this.definitions,
583
598
  signal
@@ -591,6 +606,7 @@ ${completion.text}` });
591
606
  async run(text, signal) {
592
607
  this.turnTools = this.tools;
593
608
  this.turnDefinitions = this.definitions;
609
+ this.opts.onTurnStart?.();
594
610
  try {
595
611
  if (this.lastInputTokens > this.contextWindow * 0.8) await this.compact(signal);
596
612
  this.push({ role: "user", content: text });
@@ -600,13 +616,13 @@ ${completion.text}` });
600
616
  this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
601
617
  }
602
618
  const completion = await this.provider.chat({
603
- messages: [{ role: "system", content: this.opts.systemPrompt }, ...this.messages],
619
+ messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
604
620
  tools: this.turnDefinitions,
605
621
  signal,
606
622
  onText: (delta) => this.onEvent({ type: "text", delta })
607
623
  });
608
624
  const usage = completion.usage ?? {
609
- inputTokens: estimateTokens(this.opts.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
625
+ inputTokens: estimateTokens(this.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
610
626
  outputTokens: estimateTokens(completion.text) + (completion.toolCalls.length ? estimateTokens(JSON.stringify(completion.toolCalls)) : 0)
611
627
  };
612
628
  this.totalUsage.inputTokens += usage.inputTokens;
@@ -700,6 +716,7 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
700
716
  callId: call.id,
701
717
  emit: (event) => this.onEvent(event),
702
718
  ask: (req) => this.askPermission(req),
719
+ checkpoint: this.opts.checkpoint,
703
720
  addUsage: (u) => {
704
721
  this.totalUsage.inputTokens += u.inputTokens;
705
722
  this.totalUsage.outputTokens += u.outputTokens;
@@ -742,24 +759,59 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
742
759
  }
743
760
  };
744
761
 
745
- // src/agents.ts
746
- var EXPLORE_PROMPT = "You are a read-only research agent working for another agent. Search and read the codebase to answer the request. You cannot edit files or run commands. Reply with a concise report: the answer, the relevant file paths with line numbers, and nothing else.";
747
- var GENERAL_PROMPT = "You are an agent handling a delegated task for another agent. Complete the task end to end with the tools you have, verify the result, and reply with a short report of what you did and what you found.";
748
- var BUILTIN_AGENTS = [
749
- {
750
- name: "explore",
751
- description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
752
- tools: ["read", "grep", "glob"],
753
- prompt: EXPLORE_PROMPT,
754
- maxSteps: 30
755
- },
756
- {
757
- name: "general",
758
- description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
759
- prompt: GENERAL_PROMPT,
760
- maxSteps: 50
762
+ // src/checkpoints.ts
763
+ import { existsSync as existsSync2, statSync } from "fs";
764
+ import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
765
+ var MAX_TURNS = 20;
766
+ var MAX_BYTES2 = 2 * 1024 * 1024;
767
+ var CheckpointStore = class {
768
+ history = [];
769
+ current;
770
+ beginTurn() {
771
+ if (this.current?.size === 0) return;
772
+ this.current = /* @__PURE__ */ new Map();
773
+ this.history.push(this.current);
774
+ }
775
+ /** Call before writing a file; only the first snapshot per file per turn is kept. */
776
+ async snapshot(path) {
777
+ if (!this.current) this.beginTurn();
778
+ const turn = this.current;
779
+ if (turn.has(path)) return;
780
+ if (!existsSync2(path)) turn.set(path, null);
781
+ else if (statSync(path).size > MAX_BYTES2) turn.set(path, "skipped");
782
+ else turn.set(path, await readFile5(path));
783
+ while (this.turns() > MAX_TURNS) this.history.shift();
784
+ }
785
+ turns() {
786
+ return this.history.filter((t) => t.size).length;
787
+ }
788
+ async undo() {
789
+ while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
790
+ const turn = this.history.pop();
791
+ this.current = void 0;
792
+ if (!turn) return void 0;
793
+ const result = { restored: [], deleted: [], skipped: [], failed: [] };
794
+ for (const [path, original] of turn) {
795
+ try {
796
+ if (original === "skipped") result.skipped.push(path);
797
+ else if (original === null) {
798
+ await rm(path, { force: true });
799
+ result.deleted.push(path);
800
+ } else {
801
+ await writeFile3(path, original);
802
+ result.restored.push(path);
803
+ }
804
+ } catch {
805
+ result.failed.push(path);
806
+ }
807
+ }
808
+ return result;
809
+ }
810
+ clear() {
811
+ this.history.length = 0;
812
+ this.current = void 0;
761
813
  }
762
- ];
814
+ };
763
815
 
764
816
  // src/mcp/manager.ts
765
817
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -871,73 +923,6 @@ var McpManager = class {
871
923
  // src/setup.ts
872
924
  import { homedir } from "os";
873
925
 
874
- // src/context.ts
875
- import { existsSync as existsSync2, readFileSync } from "fs";
876
- import { dirname as dirname2, join } from "path";
877
- var FILES = ["AGENTS.md", "BCCLI.md"];
878
- function readIf(path) {
879
- try {
880
- return existsSync2(path) ? readFileSync(path, "utf8").trim() : void 0;
881
- } catch {
882
- return void 0;
883
- }
884
- }
885
- function loadInstructions(cwd, home) {
886
- const dirs = [];
887
- for (let dir = cwd; ; dir = dirname2(dir)) {
888
- dirs.unshift(dir);
889
- if (dirname2(dir) === dir) break;
890
- }
891
- const parts = [];
892
- const global = readIf(join(home, "BCCLI.md"));
893
- if (global) parts.push(`# ${join(home, "BCCLI.md")}
894
- ${global}`);
895
- for (const dir of dirs) {
896
- for (const name of FILES) {
897
- const text = readIf(join(dir, name));
898
- if (text) parts.push(`# ${join(dir, name)}
899
- ${text}`);
900
- }
901
- }
902
- return parts.join("\n\n");
903
- }
904
- function isGitRepo(cwd) {
905
- for (let dir = cwd; ; dir = dirname2(dir)) {
906
- if (existsSync2(join(dir, ".git"))) return true;
907
- if (dirname2(dir) === dir) return false;
908
- }
909
- }
910
- function buildSystemPrompt(opts) {
911
- const instructions = loadInstructions(opts.cwd, opts.home);
912
- const skills = (opts.skills ?? []).slice(0, 50);
913
- const skillText = skills.length ? `
914
-
915
- Skills (load one with the skill tool when it matches the task):
916
- ${skills.map((s) => `- ${s.name}: ${s.description.slice(0, 200)}`).join("\n")}` : "";
917
- return `You are BCCLI, a coding agent by BotConnector running in the user's terminal. You help with software engineering tasks by reading code, editing files, and running commands with the tools provided.
918
-
919
- How to work:
920
- - Understand before changing: read the relevant files and search the codebase first. Never guess file contents.
921
- - Always read a file before editing it. Make the smallest correct change; match the existing style.
922
- - For tasks with 3 or more steps, keep a todo list with todo_write and update it as you go.
923
- - In plan mode you can only read and search. When the plan is ready, call exit_plan with it and wait for approval.
924
- - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
925
- - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
926
- - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
927
- - Never expose secrets, never run destructive commands (rm -rf, force push, dropping data) unless the user explicitly asks.
928
-
929
- Environment:
930
- - Working directory: ${opts.cwd}
931
- - Git repository: ${isGitRepo(opts.cwd) ? "yes" : "no"}
932
- - Platform: ${opts.platform ?? process.platform}
933
- - Date: ${opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
934
- - Model: ${opts.model}${skillText}${instructions ? `
935
-
936
- Project and user instructions (follow them):
937
-
938
- ${instructions}` : ""}`;
939
- }
940
-
941
926
  // src/tools/plan.ts
942
927
  import { z as z10 } from "zod";
943
928
  function createExitPlanTool(opts) {
@@ -960,7 +945,7 @@ function createExitPlanTool(opts) {
960
945
  }
961
946
 
962
947
  // src/tools/skill.ts
963
- import { readFileSync as readFileSync2 } from "fs";
948
+ import { readFileSync } from "fs";
964
949
  import { z as z11 } from "zod";
965
950
  function createSkillTool(skills) {
966
951
  return defineTool({
@@ -972,7 +957,7 @@ function createSkillTool(skills) {
972
957
  async run(input) {
973
958
  const skill = skills.find((s) => s.name === input.name);
974
959
  if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
975
- const { body } = parseFrontmatter(readFileSync2(skill.file, "utf8"));
960
+ const { body } = parseFrontmatter(readFileSync(skill.file, "utf8"));
976
961
  return { output: `Base directory for this skill: ${skill.dir}
977
962
 
978
963
  ${body}`, display: skill.description.slice(0, 60) };
@@ -1027,7 +1012,9 @@ ${list}`,
1027
1012
  ${def.prompt}`,
1028
1013
  cwd: opts.cwd,
1029
1014
  maxSteps: def.maxSteps ?? 50,
1030
- label: def.name
1015
+ label: def.name,
1016
+ // Subagent edits belong to the parent's turn so /undo reverts them too.
1017
+ checkpoint: ctx.checkpoint
1031
1018
  });
1032
1019
  let outcome;
1033
1020
  let steps = 0;
@@ -1107,6 +1094,8 @@ function createRuntime(opts) {
1107
1094
  const commands = loadCommands(roots);
1108
1095
  const agentDefs = loadAgentDefs(roots);
1109
1096
  const todos = new TodoStore();
1097
+ const checkpoints = new CheckpointStore();
1098
+ let startedAt = /* @__PURE__ */ new Date();
1110
1099
  const interaction = { approvePlan: async () => "no" };
1111
1100
  let session = Session.create(home, opts.cwd);
1112
1101
  let history = opts.history;
@@ -1137,7 +1126,9 @@ function createRuntime(opts) {
1137
1126
  cwd: opts.cwd,
1138
1127
  history,
1139
1128
  onMessage: (m) => session.append(m),
1140
- onReset: () => session.reset()
1129
+ onReset: () => session.reset(),
1130
+ onTurnStart: () => checkpoints.beginTurn(),
1131
+ checkpoint: (path) => checkpoints.snapshot(path)
1141
1132
  });
1142
1133
  const mcp = new McpManager({
1143
1134
  onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
@@ -1181,7 +1172,25 @@ function createRuntime(opts) {
1181
1172
  },
1182
1173
  resume(previous) {
1183
1174
  session = previous;
1184
- agent.messages = previous.load();
1175
+ agent.load(previous.load());
1176
+ agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1177
+ todos.set([]);
1178
+ checkpoints.clear();
1179
+ },
1180
+ newSession() {
1181
+ session = Session.create(home, opts.cwd);
1182
+ agent.load([]);
1183
+ agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1184
+ todos.set([]);
1185
+ checkpoints.clear();
1186
+ startedAt = /* @__PURE__ */ new Date();
1187
+ },
1188
+ rebuildSystemPrompt() {
1189
+ agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
1190
+ },
1191
+ checkpoints,
1192
+ get startedAt() {
1193
+ return startedAt;
1185
1194
  },
1186
1195
  setModel(ref) {
1187
1196
  agent.provider = makeProvider(ref);
@@ -1191,7 +1200,7 @@ function createRuntime(opts) {
1191
1200
  }
1192
1201
 
1193
1202
  // src/version.ts
1194
- var VERSION = true ? "0.3.0-beta.3" : "0.0.0-dev";
1203
+ var VERSION = true ? "0.4.0-beta.2" : "0.0.0-dev";
1195
1204
 
1196
1205
  // src/cli.ts
1197
1206
  async function main() {
@@ -1207,13 +1216,13 @@ async function main() {
1207
1216
  const cwd = process.cwd();
1208
1217
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1209
1218
  if (args.command === "mcp") {
1210
- const { runMcpCommand } = await import("./mcpCli-BV3IGR47.js");
1211
- const { readSecret } = await import("./login-WJMGVDRM.js");
1219
+ const { runMcpCommand } = await import("./mcpCli-Q2H63ISP.js");
1220
+ const { readSecret } = await import("./login-KHU7ONSR.js");
1212
1221
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1213
1222
  }
1214
1223
  if (args.command === "provider") {
1215
- const { runProviderCommand } = await import("./providerCli-SJRCBSRE.js");
1216
- const { readSecret } = await import("./login-WJMGVDRM.js");
1224
+ const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
1225
+ const { readSecret } = await import("./login-KHU7ONSR.js");
1217
1226
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1218
1227
  }
1219
1228
  if (args.command === "models") {
@@ -1230,7 +1239,7 @@ async function main() {
1230
1239
  }
1231
1240
  if (args.print) {
1232
1241
  if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1233
- const { serversToStart } = await import("./config-XPWPGNZJ.js");
1242
+ const { serversToStart } = await import("./config-NPSYFAFD.js");
1234
1243
  const plan = serversToStart(rt.home, cwd);
1235
1244
  for (const s of plan.needTrust) {
1236
1245
  console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
@@ -1243,7 +1252,7 @@ async function main() {
1243
1252
  }
1244
1253
  }
1245
1254
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1246
- const { startInteractive } = await import("./ui-HAW5NMFA.js");
1255
+ const { startInteractive } = await import("./ui-A32PTF4F.js");
1247
1256
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1248
1257
  }
1249
1258
  main().then(
@@ -8,8 +8,8 @@ import {
8
8
  removeGlobalServer,
9
9
  serversToStart,
10
10
  setProjectTrust
11
- } from "./chunk-XQLXE666.js";
12
- import "./chunk-Z5JM76EL.js";
11
+ } from "./chunk-NGLKANMD.js";
12
+ import "./chunk-RZUJYAQV.js";
13
13
  export {
14
14
  addGlobalServer,
15
15
  globalMcpPath,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-R37QWDTL.js";
5
+ } from "./chunk-L7L4RGKG.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  CATALOG,
4
4
  fillTemplate
5
- } from "./chunk-TV6LEHOH.js";
5
+ } from "./chunk-UESQNU4A.js";
6
6
  import {
7
7
  addGlobalServer,
8
8
  globalMcpPath,
9
9
  readMcpFile,
10
10
  removeGlobalServer
11
- } from "./chunk-XQLXE666.js";
11
+ } from "./chunk-NGLKANMD.js";
12
12
  import {
13
13
  bccliHome
14
- } from "./chunk-Z5JM76EL.js";
14
+ } from "./chunk-RZUJYAQV.js";
15
15
 
16
16
  // src/mcpCli.ts
17
17
  async function runMcpCommand(args, deps) {
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  addProviderKey,
4
4
  runProviderCommand
5
- } from "./chunk-5ISYVAXI.js";
6
- import "./chunk-5C6NQFSI.js";
7
- import "./chunk-Z5JM76EL.js";
5
+ } from "./chunk-2JBRLGKD.js";
6
+ import "./chunk-U7DLMVPZ.js";
7
+ import "./chunk-RZUJYAQV.js";
8
8
  export {
9
9
  addProviderKey,
10
10
  runProviderCommand