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

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,33 +2,35 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-RDQ7HPWS.js";
5
+ } from "./chunk-M7INIL2C.js";
6
6
  import {
7
7
  addProviderKey,
8
8
  createProvider,
9
9
  listAllModels
10
- } from "./chunk-MANM7HY6.js";
10
+ } from "./chunk-5ISYVAXI.js";
11
11
  import {
12
12
  runLogin
13
- } from "./chunk-E4BWUMQ2.js";
14
- import {
15
- Session,
16
- pruneSessions
17
- } from "./chunk-SJH3YLW4.js";
13
+ } from "./chunk-QH7XXNHU.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
29
  } from "./chunk-5C6NQFSI.js";
30
+ import {
31
+ Session,
32
+ pruneSessions
33
+ } from "./chunk-SJH3YLW4.js";
32
34
  import {
33
35
  ConfigError,
34
36
  bccliHome,
@@ -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;
@@ -624,6 +640,14 @@ ${completion.text}` });
624
640
  }))
625
641
  } : {}
626
642
  });
643
+ if (completion.finishReason === "repetition") {
644
+ this.onEvent({ type: "textReplace", text: completion.text });
645
+ this.onEvent({
646
+ type: "error",
647
+ message: "Model terjebak mengulang teks, jawaban dihentikan. Coba ulangi atau ganti model dengan /model."
648
+ });
649
+ return;
650
+ }
627
651
  if (!completion.toolCalls.length) {
628
652
  this.onEvent({ type: "done" });
629
653
  return;
@@ -692,6 +716,7 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
692
716
  callId: call.id,
693
717
  emit: (event) => this.onEvent(event),
694
718
  ask: (req) => this.askPermission(req),
719
+ checkpoint: this.opts.checkpoint,
695
720
  addUsage: (u) => {
696
721
  this.totalUsage.inputTokens += u.inputTokens;
697
722
  this.totalUsage.outputTokens += u.outputTokens;
@@ -734,24 +759,59 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
734
759
  }
735
760
  };
736
761
 
737
- // src/agents.ts
738
- 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.";
739
- 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.";
740
- var BUILTIN_AGENTS = [
741
- {
742
- name: "explore",
743
- description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
744
- tools: ["read", "grep", "glob"],
745
- prompt: EXPLORE_PROMPT,
746
- maxSteps: 30
747
- },
748
- {
749
- name: "general",
750
- description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
751
- prompt: GENERAL_PROMPT,
752
- 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;
753
809
  }
754
- ];
810
+ clear() {
811
+ this.history.length = 0;
812
+ this.current = void 0;
813
+ }
814
+ };
755
815
 
756
816
  // src/mcp/manager.ts
757
817
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -863,73 +923,6 @@ var McpManager = class {
863
923
  // src/setup.ts
864
924
  import { homedir } from "os";
865
925
 
866
- // src/context.ts
867
- import { existsSync as existsSync2, readFileSync } from "fs";
868
- import { dirname as dirname2, join } from "path";
869
- var FILES = ["AGENTS.md", "BCCLI.md"];
870
- function readIf(path) {
871
- try {
872
- return existsSync2(path) ? readFileSync(path, "utf8").trim() : void 0;
873
- } catch {
874
- return void 0;
875
- }
876
- }
877
- function loadInstructions(cwd, home) {
878
- const dirs = [];
879
- for (let dir = cwd; ; dir = dirname2(dir)) {
880
- dirs.unshift(dir);
881
- if (dirname2(dir) === dir) break;
882
- }
883
- const parts = [];
884
- const global = readIf(join(home, "BCCLI.md"));
885
- if (global) parts.push(`# ${join(home, "BCCLI.md")}
886
- ${global}`);
887
- for (const dir of dirs) {
888
- for (const name of FILES) {
889
- const text = readIf(join(dir, name));
890
- if (text) parts.push(`# ${join(dir, name)}
891
- ${text}`);
892
- }
893
- }
894
- return parts.join("\n\n");
895
- }
896
- function isGitRepo(cwd) {
897
- for (let dir = cwd; ; dir = dirname2(dir)) {
898
- if (existsSync2(join(dir, ".git"))) return true;
899
- if (dirname2(dir) === dir) return false;
900
- }
901
- }
902
- function buildSystemPrompt(opts) {
903
- const instructions = loadInstructions(opts.cwd, opts.home);
904
- const skills = (opts.skills ?? []).slice(0, 50);
905
- const skillText = skills.length ? `
906
-
907
- Skills (load one with the skill tool when it matches the task):
908
- ${skills.map((s) => `- ${s.name}: ${s.description.slice(0, 200)}`).join("\n")}` : "";
909
- 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.
910
-
911
- How to work:
912
- - Understand before changing: read the relevant files and search the codebase first. Never guess file contents.
913
- - Always read a file before editing it. Make the smallest correct change; match the existing style.
914
- - For tasks with 3 or more steps, keep a todo list with todo_write and update it as you go.
915
- - In plan mode you can only read and search. When the plan is ready, call exit_plan with it and wait for approval.
916
- - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
917
- - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
918
- - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
919
- - Never expose secrets, never run destructive commands (rm -rf, force push, dropping data) unless the user explicitly asks.
920
-
921
- Environment:
922
- - Working directory: ${opts.cwd}
923
- - Git repository: ${isGitRepo(opts.cwd) ? "yes" : "no"}
924
- - Platform: ${opts.platform ?? process.platform}
925
- - Date: ${opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
926
- - Model: ${opts.model}${skillText}${instructions ? `
927
-
928
- Project and user instructions (follow them):
929
-
930
- ${instructions}` : ""}`;
931
- }
932
-
933
926
  // src/tools/plan.ts
934
927
  import { z as z10 } from "zod";
935
928
  function createExitPlanTool(opts) {
@@ -952,7 +945,7 @@ function createExitPlanTool(opts) {
952
945
  }
953
946
 
954
947
  // src/tools/skill.ts
955
- import { readFileSync as readFileSync2 } from "fs";
948
+ import { readFileSync } from "fs";
956
949
  import { z as z11 } from "zod";
957
950
  function createSkillTool(skills) {
958
951
  return defineTool({
@@ -964,7 +957,7 @@ function createSkillTool(skills) {
964
957
  async run(input) {
965
958
  const skill = skills.find((s) => s.name === input.name);
966
959
  if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
967
- const { body } = parseFrontmatter(readFileSync2(skill.file, "utf8"));
960
+ const { body } = parseFrontmatter(readFileSync(skill.file, "utf8"));
968
961
  return { output: `Base directory for this skill: ${skill.dir}
969
962
 
970
963
  ${body}`, display: skill.description.slice(0, 60) };
@@ -1019,7 +1012,9 @@ ${list}`,
1019
1012
  ${def.prompt}`,
1020
1013
  cwd: opts.cwd,
1021
1014
  maxSteps: def.maxSteps ?? 50,
1022
- 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
1023
1018
  });
1024
1019
  let outcome;
1025
1020
  let steps = 0;
@@ -1099,6 +1094,8 @@ function createRuntime(opts) {
1099
1094
  const commands = loadCommands(roots);
1100
1095
  const agentDefs = loadAgentDefs(roots);
1101
1096
  const todos = new TodoStore();
1097
+ const checkpoints = new CheckpointStore();
1098
+ let startedAt = /* @__PURE__ */ new Date();
1102
1099
  const interaction = { approvePlan: async () => "no" };
1103
1100
  let session = Session.create(home, opts.cwd);
1104
1101
  let history = opts.history;
@@ -1129,7 +1126,9 @@ function createRuntime(opts) {
1129
1126
  cwd: opts.cwd,
1130
1127
  history,
1131
1128
  onMessage: (m) => session.append(m),
1132
- onReset: () => session.reset()
1129
+ onReset: () => session.reset(),
1130
+ onTurnStart: () => checkpoints.beginTurn(),
1131
+ checkpoint: (path) => checkpoints.snapshot(path)
1133
1132
  });
1134
1133
  const mcp = new McpManager({
1135
1134
  onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
@@ -1173,7 +1172,23 @@ function createRuntime(opts) {
1173
1172
  },
1174
1173
  resume(previous) {
1175
1174
  session = previous;
1176
- agent.messages = previous.load();
1175
+ agent.load(previous.load());
1176
+ checkpoints.clear();
1177
+ },
1178
+ newSession() {
1179
+ session = Session.create(home, opts.cwd);
1180
+ agent.load([]);
1181
+ agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1182
+ todos.set([]);
1183
+ checkpoints.clear();
1184
+ startedAt = /* @__PURE__ */ new Date();
1185
+ },
1186
+ rebuildSystemPrompt() {
1187
+ agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
1188
+ },
1189
+ checkpoints,
1190
+ get startedAt() {
1191
+ return startedAt;
1177
1192
  },
1178
1193
  setModel(ref) {
1179
1194
  agent.provider = makeProvider(ref);
@@ -1183,7 +1198,7 @@ function createRuntime(opts) {
1183
1198
  }
1184
1199
 
1185
1200
  // src/version.ts
1186
- var VERSION = true ? "0.3.0-beta.2" : "0.0.0-dev";
1201
+ var VERSION = true ? "0.4.0-beta.1" : "0.0.0-dev";
1187
1202
 
1188
1203
  // src/cli.ts
1189
1204
  async function main() {
@@ -1200,12 +1215,12 @@ async function main() {
1200
1215
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1201
1216
  if (args.command === "mcp") {
1202
1217
  const { runMcpCommand } = await import("./mcpCli-BV3IGR47.js");
1203
- const { readSecret } = await import("./login-IXJD5O7Z.js");
1218
+ const { readSecret } = await import("./login-FTMR64IA.js");
1204
1219
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1205
1220
  }
1206
1221
  if (args.command === "provider") {
1207
- const { runProviderCommand } = await import("./providerCli-7QNAYIFT.js");
1208
- const { readSecret } = await import("./login-IXJD5O7Z.js");
1222
+ const { runProviderCommand } = await import("./providerCli-SJRCBSRE.js");
1223
+ const { readSecret } = await import("./login-FTMR64IA.js");
1209
1224
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1210
1225
  }
1211
1226
  if (args.command === "models") {
@@ -1235,7 +1250,7 @@ async function main() {
1235
1250
  }
1236
1251
  }
1237
1252
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1238
- const { startInteractive } = await import("./ui-HNIC4DQ6.js");
1253
+ const { startInteractive } = await import("./ui-LWZ75D7H.js");
1239
1254
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1240
1255
  }
1241
1256
  main().then(
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-E4BWUMQ2.js";
5
+ } from "./chunk-QH7XXNHU.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  addProviderKey,
4
4
  runProviderCommand
5
- } from "./chunk-MANM7HY6.js";
5
+ } from "./chunk-5ISYVAXI.js";
6
6
  import "./chunk-5C6NQFSI.js";
7
7
  import "./chunk-Z5JM76EL.js";
8
8
  export {