@botconnector/bccli 0.3.0-beta.3 → 0.4.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -2
- package/dist/args-AWLROI65.js +12 -0
- package/dist/{chunk-Z5JM76EL.js → chunk-2MGINW4O.js} +4 -1
- package/dist/{chunk-5C6NQFSI.js → chunk-7S6JXQCH.js} +1 -1
- package/dist/{chunk-XQLXE666.js → chunk-BFZXRKW5.js} +1 -1
- package/dist/{chunk-RDQ7HPWS.js → chunk-BJ337ABK.js} +18 -4
- package/dist/chunk-CLN4Z5E3.js +60 -0
- package/dist/{chunk-TV6LEHOH.js → chunk-DX7CZTBB.js} +1 -1
- package/dist/{chunk-5XN44Q55.js → chunk-ELML4KVQ.js} +7 -1
- package/dist/{chunk-R37QWDTL.js → chunk-IOWDWOXS.js} +2 -2
- package/dist/chunk-KV27KTOX.js +254 -0
- package/dist/{chunk-5ISYVAXI.js → chunk-U7GK73CK.js} +90 -15
- package/dist/cli.js +328 -135
- package/dist/{config-XPWPGNZJ.js → config-YUNKPCNB.js} +2 -2
- package/dist/integrationCli-GEIMUL3M.js +347 -0
- package/dist/{login-WJMGVDRM.js → login-4C262MEI.js} +1 -1
- package/dist/{mcpCli-BV3IGR47.js → mcpCli-DZWQPTOS.js} +3 -3
- package/dist/providerCli-Q7LRM2NP.js +12 -0
- package/dist/ui-6GTQUJOE.js +1415 -0
- package/package.json +2 -1
- package/dist/args-EVIFNGPQ.js +0 -11
- package/dist/chunk-TBPPXOOZ.js +0 -165
- package/dist/providerCli-SJRCBSRE.js +0 -11
- package/dist/ui-HAW5NMFA.js +0 -867
package/dist/cli.js
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILTIN_AGENTS,
|
|
4
|
+
buildSystemPrompt,
|
|
5
|
+
ignorePatterns,
|
|
6
|
+
loadAgentDefs,
|
|
7
|
+
loadCommands,
|
|
8
|
+
loadSkills,
|
|
9
|
+
parseFrontmatter,
|
|
10
|
+
runPrint
|
|
11
|
+
} from "./chunk-KV27KTOX.js";
|
|
2
12
|
import {
|
|
3
13
|
HELP_TEXT,
|
|
4
14
|
parseCliArgs
|
|
5
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-BJ337ABK.js";
|
|
16
|
+
import {
|
|
17
|
+
Permissions
|
|
18
|
+
} from "./chunk-ELML4KVQ.js";
|
|
6
19
|
import {
|
|
7
20
|
addProviderKey,
|
|
8
21
|
createProvider,
|
|
9
22
|
listAllModels
|
|
10
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-U7GK73CK.js";
|
|
24
|
+
import {
|
|
25
|
+
providerName
|
|
26
|
+
} from "./chunk-7S6JXQCH.js";
|
|
27
|
+
import "./chunk-CLN4Z5E3.js";
|
|
11
28
|
import {
|
|
12
29
|
runLogin
|
|
13
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-IOWDWOXS.js";
|
|
14
31
|
import {
|
|
15
32
|
Session,
|
|
16
33
|
pruneSessions
|
|
17
34
|
} from "./chunk-SJH3YLW4.js";
|
|
18
|
-
import {
|
|
19
|
-
ignorePatterns,
|
|
20
|
-
loadAgentDefs,
|
|
21
|
-
loadCommands,
|
|
22
|
-
loadSkills,
|
|
23
|
-
parseFrontmatter,
|
|
24
|
-
runPrint
|
|
25
|
-
} from "./chunk-TBPPXOOZ.js";
|
|
26
|
-
import {
|
|
27
|
-
Permissions
|
|
28
|
-
} from "./chunk-5XN44Q55.js";
|
|
29
|
-
import {
|
|
30
|
-
providerName
|
|
31
|
-
} from "./chunk-5C6NQFSI.js";
|
|
32
35
|
import {
|
|
33
36
|
ConfigError,
|
|
34
37
|
bccliHome,
|
|
35
38
|
loadConfig,
|
|
36
39
|
resolveModel
|
|
37
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-2MGINW4O.js";
|
|
38
41
|
|
|
39
42
|
// src/tools/index.ts
|
|
40
43
|
import { z as z8 } from "zod";
|
|
@@ -268,6 +271,7 @@ var editTool = defineTool({
|
|
|
268
271
|
}
|
|
269
272
|
const next = applyEdit(content, input);
|
|
270
273
|
if (typeof next !== "string") return next;
|
|
274
|
+
await ctx.checkpoint?.(abs);
|
|
271
275
|
await writeFile(abs, next);
|
|
272
276
|
return { output: `Mengedit ${input.path}.`, display: formatDiff(diffLines(content, next)) };
|
|
273
277
|
}
|
|
@@ -297,6 +301,20 @@ async function readCapped(res) {
|
|
|
297
301
|
function htmlToText(html) {
|
|
298
302
|
return html.replace(/<(script|style|noscript)[\s\S]*?<\/\1>/gi, "").replace(/<(br|\/p|\/div|\/h[1-6]|\/li|\/tr)\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&").split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
|
|
299
303
|
}
|
|
304
|
+
function describeFetchError(error) {
|
|
305
|
+
const cause = error.cause;
|
|
306
|
+
const code = cause?.code ?? "";
|
|
307
|
+
const detail = cause?.message ?? error.message;
|
|
308
|
+
if (error.name === "TimeoutError") return "tidak ada jawaban dalam 30 detik";
|
|
309
|
+
if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `${detail} \u2014 DNS tidak menemukan host (salah ketik, offline, atau diblokir DNS/ISP)`;
|
|
310
|
+
if (/CERT|SELF_SIGNED|UNABLE_TO/.test(code)) {
|
|
311
|
+
return `${detail} \u2014 sertifikat TLS ditolak; sering karena antivirus/proxy yang memeriksa HTTPS. Coba jalankan dengan NODE_OPTIONS=--use-system-ca`;
|
|
312
|
+
}
|
|
313
|
+
if (/ECONNRESET|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|UND_ERR_CONNECT_TIMEOUT|UND_ERR_SOCKET/.test(code)) {
|
|
314
|
+
return `${detail} \u2014 koneksi gagal (firewall, proxy, atau situs diblokir jaringan)`;
|
|
315
|
+
}
|
|
316
|
+
return code ? `${detail} (${code})` : detail;
|
|
317
|
+
}
|
|
300
318
|
var fetchTool = defineTool({
|
|
301
319
|
name: "fetch",
|
|
302
320
|
description: "Fetch a URL (http/https). HTML is converted to plain text. Max 50k characters.",
|
|
@@ -324,12 +342,12 @@ var fetchTool = defineTool({
|
|
|
324
342
|
url = next;
|
|
325
343
|
}
|
|
326
344
|
} catch (error) {
|
|
327
|
-
return { output: `Gagal mengambil ${input.url}: ${error
|
|
345
|
+
return { output: `Gagal mengambil ${input.url}: ${describeFetchError(error)}`, isError: true };
|
|
328
346
|
}
|
|
329
347
|
if (!res) return { output: `Gagal mengambil ${input.url}`, isError: true };
|
|
330
348
|
const { text: body, truncated } = await readCapped(res);
|
|
331
|
-
if (!res.ok) return { output: `HTTP ${res.status} dari ${input.url}: ${body.slice(0, 500)}`, isError: true };
|
|
332
349
|
const text = (res.headers.get("content-type") ?? "").includes("html") ? htmlToText(body) : body;
|
|
350
|
+
if (!res.ok) return { output: `HTTP ${res.status} dari ${input.url}: ${text.slice(0, 500)}`, isError: true };
|
|
333
351
|
const clipped = text.length > MAX_CHARS || truncated ? `${text.slice(0, MAX_CHARS)}
|
|
334
352
|
\u2026 [dipotong]` : text;
|
|
335
353
|
const display = truncated ? "lebih dari 5 MB, dipotong" : `${text.length} karakter`;
|
|
@@ -497,6 +515,7 @@ var writeTool = defineTool({
|
|
|
497
515
|
return { output: `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.`, isError: true };
|
|
498
516
|
}
|
|
499
517
|
try {
|
|
518
|
+
await ctx.checkpoint?.(abs);
|
|
500
519
|
await mkdir(dirname(abs), { recursive: true });
|
|
501
520
|
await writeFile2(abs, input.content);
|
|
502
521
|
} catch (error) {
|
|
@@ -517,6 +536,71 @@ function toolDefinitions(tools) {
|
|
|
517
536
|
});
|
|
518
537
|
}
|
|
519
538
|
|
|
539
|
+
// src/textToolCalls.ts
|
|
540
|
+
var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
|
|
541
|
+
var CALL_START = /<tool_call>|\n?[^\n]*\bTool call list\b[^\n]*|\n\s*\d+\.\s*Tool:/g;
|
|
542
|
+
function coerce(value, type) {
|
|
543
|
+
const types = Array.isArray(type) ? type : [type];
|
|
544
|
+
const v = value.trim();
|
|
545
|
+
if ((types.includes("integer") || types.includes("number")) && v !== "" && !Number.isNaN(Number(v))) return Number(v);
|
|
546
|
+
if (types.includes("boolean") && /^(true|false)$/i.test(v)) return v.toLowerCase() === "true";
|
|
547
|
+
if ((types.includes("object") || types.includes("array")) && /^[[{]/.test(v)) {
|
|
548
|
+
try {
|
|
549
|
+
return JSON.parse(v);
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return value.replace(/^\n+|\n+$/g, "");
|
|
554
|
+
}
|
|
555
|
+
function pickTool(name, keys, tools) {
|
|
556
|
+
const named = name && tools.find((t) => t.function.name === name);
|
|
557
|
+
if (named) return named;
|
|
558
|
+
let best;
|
|
559
|
+
let bestScore = 0;
|
|
560
|
+
let tie = false;
|
|
561
|
+
for (const t of tools) {
|
|
562
|
+
const params = t.function.parameters;
|
|
563
|
+
const props = Object.keys(params.properties ?? {});
|
|
564
|
+
if (!(params.required ?? []).every((r) => keys.includes(r))) continue;
|
|
565
|
+
const score = keys.filter((k) => props.includes(k)).length;
|
|
566
|
+
if (score > bestScore) [best, bestScore, tie] = [t, score, false];
|
|
567
|
+
else if (score === bestScore && score > 0) tie = true;
|
|
568
|
+
}
|
|
569
|
+
return tie ? void 0 : best;
|
|
570
|
+
}
|
|
571
|
+
function recoverTextToolCalls(text, tools) {
|
|
572
|
+
if (!/<\/arg_value>|<tool_call>/.test(text)) return null;
|
|
573
|
+
const starts = [...text.matchAll(CALL_START)].map((m) => m.index ?? 0);
|
|
574
|
+
const firstPair = text.search(PAIR);
|
|
575
|
+
const cut = Math.min(...[...starts, firstPair].filter((i) => i >= 0));
|
|
576
|
+
const bounds = starts.filter((i) => i >= cut);
|
|
577
|
+
if (!bounds.length || bounds[0] > cut) bounds.unshift(cut);
|
|
578
|
+
const toolCalls = [];
|
|
579
|
+
bounds.forEach((start, n) => {
|
|
580
|
+
const segment = text.slice(start, bounds[n + 1] ?? text.length);
|
|
581
|
+
const name = /<tool_call>\s*([A-Za-z_][\w.-]*)/.exec(segment)?.[1] ?? /Tool:\s*`?([A-Za-z_][\w.-]*)`?/.exec(segment)?.[1];
|
|
582
|
+
let args = [];
|
|
583
|
+
const flush = () => {
|
|
584
|
+
if (!args.length) return;
|
|
585
|
+
const keys = args.map(([k]) => k);
|
|
586
|
+
const tool = pickTool(name, keys, tools);
|
|
587
|
+
if (tool) {
|
|
588
|
+
const props = tool.function.parameters.properties ?? {};
|
|
589
|
+
const input = Object.fromEntries(args.filter(([k]) => k in props).map(([k, v]) => [k, coerce(v, props[k].type)]));
|
|
590
|
+
toolCalls.push({ id: `text_call_${toolCalls.length}`, name: tool.function.name, arguments: JSON.stringify(input) });
|
|
591
|
+
}
|
|
592
|
+
args = [];
|
|
593
|
+
};
|
|
594
|
+
for (const m of segment.matchAll(PAIR)) {
|
|
595
|
+
if (args.some(([k]) => k === m[1])) flush();
|
|
596
|
+
args.push([m[1], m[2]]);
|
|
597
|
+
}
|
|
598
|
+
flush();
|
|
599
|
+
});
|
|
600
|
+
if (!toolCalls.length) return null;
|
|
601
|
+
return { text: text.slice(0, cut).trim(), toolCalls };
|
|
602
|
+
}
|
|
603
|
+
|
|
520
604
|
// src/agent.ts
|
|
521
605
|
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
522
606
|
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.";
|
|
@@ -532,6 +616,8 @@ var Agent = class {
|
|
|
532
616
|
this.messages = [...opts.history ?? []];
|
|
533
617
|
this.maxSteps = opts.maxSteps ?? 50;
|
|
534
618
|
this.contextWindow = opts.contextWindow ?? 128e3;
|
|
619
|
+
this.systemPrompt = opts.systemPrompt;
|
|
620
|
+
this.reasoning = opts.reasoning ?? "auto";
|
|
535
621
|
}
|
|
536
622
|
opts;
|
|
537
623
|
provider;
|
|
@@ -541,6 +627,7 @@ var Agent = class {
|
|
|
541
627
|
lastInputTokens = 0;
|
|
542
628
|
onEvent = () => {
|
|
543
629
|
};
|
|
630
|
+
reasoning;
|
|
544
631
|
askPermission = async () => "no";
|
|
545
632
|
tools;
|
|
546
633
|
definitions;
|
|
@@ -551,6 +638,10 @@ var Agent = class {
|
|
|
551
638
|
maxSteps;
|
|
552
639
|
// ponytail: one window for every model; read it from /v1/models metadata when providers expose it.
|
|
553
640
|
contextWindow;
|
|
641
|
+
systemPrompt;
|
|
642
|
+
setSystemPrompt(text) {
|
|
643
|
+
this.systemPrompt = text;
|
|
644
|
+
}
|
|
554
645
|
setTools(tools) {
|
|
555
646
|
this.tools = tools;
|
|
556
647
|
this.definitions = toolDefinitions(tools);
|
|
@@ -569,6 +660,12 @@ var Agent = class {
|
|
|
569
660
|
this.messages.push(message);
|
|
570
661
|
this.opts.onMessage?.(message);
|
|
571
662
|
}
|
|
663
|
+
/** Swaps in another conversation (resume, new session) without writing to the session log. */
|
|
664
|
+
load(messages) {
|
|
665
|
+
this.messages = messages;
|
|
666
|
+
this.readFiles.clear();
|
|
667
|
+
this.lastInputTokens = 0;
|
|
668
|
+
}
|
|
572
669
|
clear() {
|
|
573
670
|
this.messages = [];
|
|
574
671
|
this.readFiles.clear();
|
|
@@ -577,10 +674,11 @@ var Agent = class {
|
|
|
577
674
|
}
|
|
578
675
|
async compact(signal) {
|
|
579
676
|
const completion = await this.provider.chat({
|
|
580
|
-
messages: [{ role: "system", content: this.
|
|
677
|
+
messages: [{ role: "system", content: this.systemPrompt }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
|
|
581
678
|
// Some gateways reject tool_calls in history when no tools are declared.
|
|
582
679
|
tools: this.definitions,
|
|
583
|
-
signal
|
|
680
|
+
signal,
|
|
681
|
+
reasoning: this.reasoning
|
|
584
682
|
});
|
|
585
683
|
this.clear();
|
|
586
684
|
this.push({ role: "user", content: `Ringkasan percakapan sebelumnya:
|
|
@@ -591,6 +689,7 @@ ${completion.text}` });
|
|
|
591
689
|
async run(text, signal) {
|
|
592
690
|
this.turnTools = this.tools;
|
|
593
691
|
this.turnDefinitions = this.definitions;
|
|
692
|
+
this.opts.onTurnStart?.();
|
|
594
693
|
try {
|
|
595
694
|
if (this.lastInputTokens > this.contextWindow * 0.8) await this.compact(signal);
|
|
596
695
|
this.push({ role: "user", content: text });
|
|
@@ -599,14 +698,23 @@ ${completion.text}` });
|
|
|
599
698
|
await this.compact(signal);
|
|
600
699
|
this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
|
|
601
700
|
}
|
|
602
|
-
|
|
603
|
-
messages: [{ role: "system", content: this.
|
|
701
|
+
let completion = await this.provider.chat({
|
|
702
|
+
messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
|
|
604
703
|
tools: this.turnDefinitions,
|
|
605
704
|
signal,
|
|
606
|
-
onText: (delta) => this.onEvent({ type: "text", delta })
|
|
705
|
+
onText: (delta) => this.onEvent({ type: "text", delta }),
|
|
706
|
+
onThinking: (delta) => this.onEvent({ type: "thinking", delta }),
|
|
707
|
+
reasoning: this.reasoning
|
|
607
708
|
});
|
|
709
|
+
if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
|
|
710
|
+
const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
|
|
711
|
+
if (recovered) {
|
|
712
|
+
completion = { ...completion, ...recovered };
|
|
713
|
+
this.onEvent({ type: "textReplace", text: recovered.text });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
608
716
|
const usage = completion.usage ?? {
|
|
609
|
-
inputTokens: estimateTokens(this.
|
|
717
|
+
inputTokens: estimateTokens(this.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
|
|
610
718
|
outputTokens: estimateTokens(completion.text) + (completion.toolCalls.length ? estimateTokens(JSON.stringify(completion.toolCalls)) : 0)
|
|
611
719
|
};
|
|
612
720
|
this.totalUsage.inputTokens += usage.inputTokens;
|
|
@@ -700,6 +808,7 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
|
|
|
700
808
|
callId: call.id,
|
|
701
809
|
emit: (event) => this.onEvent(event),
|
|
702
810
|
ask: (req) => this.askPermission(req),
|
|
811
|
+
checkpoint: this.opts.checkpoint,
|
|
703
812
|
addUsage: (u) => {
|
|
704
813
|
this.totalUsage.inputTokens += u.inputTokens;
|
|
705
814
|
this.totalUsage.outputTokens += u.outputTokens;
|
|
@@ -742,24 +851,59 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
|
|
|
742
851
|
}
|
|
743
852
|
};
|
|
744
853
|
|
|
745
|
-
// src/
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
var
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
854
|
+
// src/checkpoints.ts
|
|
855
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
856
|
+
import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
|
|
857
|
+
var MAX_TURNS = 20;
|
|
858
|
+
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
859
|
+
var CheckpointStore = class {
|
|
860
|
+
history = [];
|
|
861
|
+
current;
|
|
862
|
+
beginTurn() {
|
|
863
|
+
if (this.current?.size === 0) return;
|
|
864
|
+
this.current = /* @__PURE__ */ new Map();
|
|
865
|
+
this.history.push(this.current);
|
|
866
|
+
}
|
|
867
|
+
/** Call before writing a file; only the first snapshot per file per turn is kept. */
|
|
868
|
+
async snapshot(path) {
|
|
869
|
+
if (!this.current) this.beginTurn();
|
|
870
|
+
const turn = this.current;
|
|
871
|
+
if (turn.has(path)) return;
|
|
872
|
+
if (!existsSync2(path)) turn.set(path, null);
|
|
873
|
+
else if (statSync(path).size > MAX_BYTES2) turn.set(path, "skipped");
|
|
874
|
+
else turn.set(path, await readFile5(path));
|
|
875
|
+
while (this.turns() > MAX_TURNS) this.history.shift();
|
|
876
|
+
}
|
|
877
|
+
turns() {
|
|
878
|
+
return this.history.filter((t) => t.size).length;
|
|
879
|
+
}
|
|
880
|
+
async undo() {
|
|
881
|
+
while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
|
|
882
|
+
const turn = this.history.pop();
|
|
883
|
+
this.current = void 0;
|
|
884
|
+
if (!turn) return void 0;
|
|
885
|
+
const result = { restored: [], deleted: [], skipped: [], failed: [] };
|
|
886
|
+
for (const [path, original] of turn) {
|
|
887
|
+
try {
|
|
888
|
+
if (original === "skipped") result.skipped.push(path);
|
|
889
|
+
else if (original === null) {
|
|
890
|
+
await rm(path, { force: true });
|
|
891
|
+
result.deleted.push(path);
|
|
892
|
+
} else {
|
|
893
|
+
await writeFile3(path, original);
|
|
894
|
+
result.restored.push(path);
|
|
895
|
+
}
|
|
896
|
+
} catch {
|
|
897
|
+
result.failed.push(path);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return result;
|
|
761
901
|
}
|
|
762
|
-
|
|
902
|
+
clear() {
|
|
903
|
+
this.history.length = 0;
|
|
904
|
+
this.current = void 0;
|
|
905
|
+
}
|
|
906
|
+
};
|
|
763
907
|
|
|
764
908
|
// src/mcp/manager.ts
|
|
765
909
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -767,8 +911,8 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
|
767
911
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
768
912
|
import { z as z9 } from "zod";
|
|
769
913
|
function mcpToolName(server, tool, taken) {
|
|
770
|
-
const
|
|
771
|
-
const base = `mcp__${
|
|
914
|
+
const clean2 = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
|
|
915
|
+
const base = `mcp__${clean2(server)}__${clean2(tool)}`.slice(0, 64);
|
|
772
916
|
let name = base;
|
|
773
917
|
for (let n = 2; taken.has(name); n++) name = `${base.slice(0, 64 - String(n).length - 1)}_${n}`;
|
|
774
918
|
taken.add(name);
|
|
@@ -871,73 +1015,6 @@ var McpManager = class {
|
|
|
871
1015
|
// src/setup.ts
|
|
872
1016
|
import { homedir } from "os";
|
|
873
1017
|
|
|
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
1018
|
// src/tools/plan.ts
|
|
942
1019
|
import { z as z10 } from "zod";
|
|
943
1020
|
function createExitPlanTool(opts) {
|
|
@@ -960,7 +1037,7 @@ function createExitPlanTool(opts) {
|
|
|
960
1037
|
}
|
|
961
1038
|
|
|
962
1039
|
// src/tools/skill.ts
|
|
963
|
-
import { readFileSync
|
|
1040
|
+
import { readFileSync } from "fs";
|
|
964
1041
|
import { z as z11 } from "zod";
|
|
965
1042
|
function createSkillTool(skills) {
|
|
966
1043
|
return defineTool({
|
|
@@ -972,7 +1049,7 @@ function createSkillTool(skills) {
|
|
|
972
1049
|
async run(input) {
|
|
973
1050
|
const skill = skills.find((s) => s.name === input.name);
|
|
974
1051
|
if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
|
|
975
|
-
const { body } = parseFrontmatter(
|
|
1052
|
+
const { body } = parseFrontmatter(readFileSync(skill.file, "utf8"));
|
|
976
1053
|
return { output: `Base directory for this skill: ${skill.dir}
|
|
977
1054
|
|
|
978
1055
|
${body}`, display: skill.description.slice(0, 60) };
|
|
@@ -980,8 +1057,69 @@ ${body}`, display: skill.description.slice(0, 60) };
|
|
|
980
1057
|
});
|
|
981
1058
|
}
|
|
982
1059
|
|
|
983
|
-
// src/tools/
|
|
1060
|
+
// src/tools/websearch.ts
|
|
984
1061
|
import { z as z12 } from "zod";
|
|
1062
|
+
var clean = (items, max) => (Array.isArray(items) ? items : []).filter((r) => typeof r?.url === "string" && /^https?:\/\//.test(r.url)).slice(0, max).map((r) => ({
|
|
1063
|
+
title: String(r.title || r.url).slice(0, 200),
|
|
1064
|
+
url: r.url,
|
|
1065
|
+
snippet: String(r.snippet || r.description || "").replace(/\s+/g, " ").trim().slice(0, 600)
|
|
1066
|
+
}));
|
|
1067
|
+
async function postJson(url, body, headers, signal) {
|
|
1068
|
+
const res = await fetch(url, {
|
|
1069
|
+
method: "POST",
|
|
1070
|
+
headers: { "content-type": "application/json", ...headers },
|
|
1071
|
+
body: JSON.stringify(body),
|
|
1072
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
|
|
1073
|
+
});
|
|
1074
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1075
|
+
return res.json();
|
|
1076
|
+
}
|
|
1077
|
+
function createWebSearchTool(opts) {
|
|
1078
|
+
const keenableURL = opts.keenableURL ?? "https://api.keenable.ai/v1/search/public";
|
|
1079
|
+
return defineTool({
|
|
1080
|
+
name: "web_search",
|
|
1081
|
+
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.",
|
|
1082
|
+
schema: z12.object({
|
|
1083
|
+
query: z12.string().min(1).max(500).describe("Search query"),
|
|
1084
|
+
max_results: z12.number().int().min(1).max(10).optional().describe("Number of results (default 6)")
|
|
1085
|
+
}),
|
|
1086
|
+
kind: "read",
|
|
1087
|
+
target: (input) => input.query,
|
|
1088
|
+
parallelSafe: () => true,
|
|
1089
|
+
async run(input, ctx) {
|
|
1090
|
+
const max = input.max_results ?? 6;
|
|
1091
|
+
const errors = [];
|
|
1092
|
+
let results = [];
|
|
1093
|
+
const bc = opts.botconnector();
|
|
1094
|
+
if (bc) {
|
|
1095
|
+
try {
|
|
1096
|
+
const data = await postJson(`${bc.baseURL.replace(/\/+$/, "")}/web/search`, { query: input.query, max_results: max }, { authorization: `Bearer ${bc.apiKey}` }, ctx.signal);
|
|
1097
|
+
results = clean(data.results, max);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
errors.push(`BotConnector: ${error.message}`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (!results.length) {
|
|
1103
|
+
try {
|
|
1104
|
+
const data = await postJson(keenableURL, { query: input.query }, { "x-keenable-title": "BotConnector" }, ctx.signal);
|
|
1105
|
+
results = clean(data.results, max);
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
errors.push(`Keenable: ${error.message}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
if (!results.length) {
|
|
1111
|
+
return { output: errors.length ? `Web search gagal (${errors.join("; ")}).` : "Tidak ada hasil.", isError: errors.length > 0 };
|
|
1112
|
+
}
|
|
1113
|
+
const output = results.map((r, i) => `${i + 1}. ${r.title}
|
|
1114
|
+
${r.url}${r.snippet ? `
|
|
1115
|
+
${r.snippet}` : ""}`).join("\n");
|
|
1116
|
+
return { output, display: `${results.length} hasil` };
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/tools/task.ts
|
|
1122
|
+
import { z as z13 } from "zod";
|
|
985
1123
|
function createTaskTool(opts) {
|
|
986
1124
|
const byName = new Map(opts.agents.map((a) => [a.name, a]));
|
|
987
1125
|
const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
@@ -989,10 +1127,10 @@ function createTaskTool(opts) {
|
|
|
989
1127
|
name: "task",
|
|
990
1128
|
description: `Delegate a self-contained sub-task to a subagent. It starts with an empty context and returns only its final report. Available agents:
|
|
991
1129
|
${list}`,
|
|
992
|
-
schema:
|
|
993
|
-
agent:
|
|
994
|
-
description:
|
|
995
|
-
prompt:
|
|
1130
|
+
schema: z13.object({
|
|
1131
|
+
agent: z13.string().describe("Agent name"),
|
|
1132
|
+
description: z13.string().describe("3-6 word summary shown to the user"),
|
|
1133
|
+
prompt: z13.string().describe("Full instructions; the subagent sees nothing else")
|
|
996
1134
|
}),
|
|
997
1135
|
kind: "read",
|
|
998
1136
|
target: (input) => `[${input.agent}] ${input.description}`,
|
|
@@ -1027,7 +1165,10 @@ ${list}`,
|
|
|
1027
1165
|
${def.prompt}`,
|
|
1028
1166
|
cwd: opts.cwd,
|
|
1029
1167
|
maxSteps: def.maxSteps ?? 50,
|
|
1030
|
-
label: def.name
|
|
1168
|
+
label: def.name,
|
|
1169
|
+
reasoning: opts.reasoning?.() ?? "auto",
|
|
1170
|
+
// Subagent edits belong to the parent's turn so /undo reverts them too.
|
|
1171
|
+
checkpoint: ctx.checkpoint
|
|
1031
1172
|
});
|
|
1032
1173
|
let outcome;
|
|
1033
1174
|
let steps = 0;
|
|
@@ -1054,7 +1195,7 @@ ${def.prompt}`,
|
|
|
1054
1195
|
}
|
|
1055
1196
|
|
|
1056
1197
|
// src/tools/todo.ts
|
|
1057
|
-
import { z as
|
|
1198
|
+
import { z as z14 } from "zod";
|
|
1058
1199
|
var TodoStore = class {
|
|
1059
1200
|
items = [];
|
|
1060
1201
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -1071,8 +1212,8 @@ function createTodoTool(store) {
|
|
|
1071
1212
|
return defineTool({
|
|
1072
1213
|
name: "todo_write",
|
|
1073
1214
|
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.",
|
|
1074
|
-
schema:
|
|
1075
|
-
todos:
|
|
1215
|
+
schema: z14.object({
|
|
1216
|
+
todos: z14.array(z14.object({ content: z14.string(), status: z14.enum(["pending", "in_progress", "completed"]) }))
|
|
1076
1217
|
}),
|
|
1077
1218
|
kind: "read",
|
|
1078
1219
|
target: (input) => `${input.todos.length} item`,
|
|
@@ -1094,9 +1235,10 @@ function createRuntime(opts) {
|
|
|
1094
1235
|
const home = bccliHome(env);
|
|
1095
1236
|
let config = loadConfig(opts.cwd, env);
|
|
1096
1237
|
let modelRef = opts.args.model ?? config.model;
|
|
1238
|
+
let reasoning = opts.args.reasoning ?? config.reasoning;
|
|
1097
1239
|
const makeProvider = (ref) => {
|
|
1098
1240
|
const resolved = resolveModel(config, ref, env);
|
|
1099
|
-
return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model });
|
|
1241
|
+
return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model, providerId: resolved.providerId });
|
|
1100
1242
|
};
|
|
1101
1243
|
const provider = makeProvider(modelRef);
|
|
1102
1244
|
const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
|
|
@@ -1107,6 +1249,8 @@ function createRuntime(opts) {
|
|
|
1107
1249
|
const commands = loadCommands(roots);
|
|
1108
1250
|
const agentDefs = loadAgentDefs(roots);
|
|
1109
1251
|
const todos = new TodoStore();
|
|
1252
|
+
const checkpoints = new CheckpointStore();
|
|
1253
|
+
let startedAt = /* @__PURE__ */ new Date();
|
|
1110
1254
|
const interaction = { approvePlan: async () => "no" };
|
|
1111
1255
|
let session = Session.create(home, opts.cwd);
|
|
1112
1256
|
let history = opts.history;
|
|
@@ -1118,7 +1262,17 @@ function createRuntime(opts) {
|
|
|
1118
1262
|
}
|
|
1119
1263
|
}
|
|
1120
1264
|
const systemPrompt = buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills });
|
|
1121
|
-
const
|
|
1265
|
+
const webSearch = createWebSearchTool({
|
|
1266
|
+
botconnector: () => {
|
|
1267
|
+
try {
|
|
1268
|
+
const bc = resolveModel(config, "bc-cloud/search", env);
|
|
1269
|
+
return bc.apiKey ? { baseURL: bc.baseURL, apiKey: bc.apiKey } : void 0;
|
|
1270
|
+
} catch {
|
|
1271
|
+
return void 0;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
const baseTools = [...ALL_TOOLS, webSearch, createSkillTool(skills), createTodoTool(todos), createExitPlanTool({ permissions, interaction })];
|
|
1122
1276
|
const task = createTaskTool({
|
|
1123
1277
|
agents: [...BUILTIN_AGENTS, ...agentDefs],
|
|
1124
1278
|
// Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
|
|
@@ -1127,7 +1281,8 @@ function createRuntime(opts) {
|
|
|
1127
1281
|
provider: () => agent.provider,
|
|
1128
1282
|
providerFor: makeProvider,
|
|
1129
1283
|
systemPrompt,
|
|
1130
|
-
cwd: opts.cwd
|
|
1284
|
+
cwd: opts.cwd,
|
|
1285
|
+
reasoning: () => reasoning
|
|
1131
1286
|
});
|
|
1132
1287
|
const agent = new Agent({
|
|
1133
1288
|
provider,
|
|
@@ -1136,8 +1291,11 @@ function createRuntime(opts) {
|
|
|
1136
1291
|
systemPrompt,
|
|
1137
1292
|
cwd: opts.cwd,
|
|
1138
1293
|
history,
|
|
1294
|
+
reasoning,
|
|
1139
1295
|
onMessage: (m) => session.append(m),
|
|
1140
|
-
onReset: () => session.reset()
|
|
1296
|
+
onReset: () => session.reset(),
|
|
1297
|
+
onTurnStart: () => checkpoints.beginTurn(),
|
|
1298
|
+
checkpoint: (path) => checkpoints.snapshot(path)
|
|
1141
1299
|
});
|
|
1142
1300
|
const mcp = new McpManager({
|
|
1143
1301
|
onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
|
|
@@ -1175,23 +1333,48 @@ function createRuntime(opts) {
|
|
|
1175
1333
|
get modelRef() {
|
|
1176
1334
|
return modelRef;
|
|
1177
1335
|
},
|
|
1336
|
+
get reasoning() {
|
|
1337
|
+
return reasoning;
|
|
1338
|
+
},
|
|
1178
1339
|
agent,
|
|
1179
1340
|
get session() {
|
|
1180
1341
|
return session;
|
|
1181
1342
|
},
|
|
1182
1343
|
resume(previous) {
|
|
1183
1344
|
session = previous;
|
|
1184
|
-
agent.
|
|
1345
|
+
agent.load(previous.load());
|
|
1346
|
+
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1347
|
+
todos.set([]);
|
|
1348
|
+
checkpoints.clear();
|
|
1349
|
+
},
|
|
1350
|
+
newSession() {
|
|
1351
|
+
session = Session.create(home, opts.cwd);
|
|
1352
|
+
agent.load([]);
|
|
1353
|
+
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1354
|
+
todos.set([]);
|
|
1355
|
+
checkpoints.clear();
|
|
1356
|
+
startedAt = /* @__PURE__ */ new Date();
|
|
1357
|
+
},
|
|
1358
|
+
rebuildSystemPrompt() {
|
|
1359
|
+
agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
|
|
1360
|
+
},
|
|
1361
|
+
checkpoints,
|
|
1362
|
+
get startedAt() {
|
|
1363
|
+
return startedAt;
|
|
1185
1364
|
},
|
|
1186
1365
|
setModel(ref) {
|
|
1187
1366
|
agent.provider = makeProvider(ref);
|
|
1188
1367
|
modelRef = ref;
|
|
1368
|
+
},
|
|
1369
|
+
setReasoning(level) {
|
|
1370
|
+
reasoning = level;
|
|
1371
|
+
agent.reasoning = level;
|
|
1189
1372
|
}
|
|
1190
1373
|
};
|
|
1191
1374
|
}
|
|
1192
1375
|
|
|
1193
1376
|
// src/version.ts
|
|
1194
|
-
var VERSION = true ? "0.
|
|
1377
|
+
var VERSION = true ? "0.4.0-beta.10" : "0.0.0-dev";
|
|
1195
1378
|
|
|
1196
1379
|
// src/cli.ts
|
|
1197
1380
|
async function main() {
|
|
@@ -1207,15 +1390,25 @@ async function main() {
|
|
|
1207
1390
|
const cwd = process.cwd();
|
|
1208
1391
|
if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
|
|
1209
1392
|
if (args.command === "mcp") {
|
|
1210
|
-
const { runMcpCommand } = await import("./mcpCli-
|
|
1211
|
-
const { readSecret } = await import("./login-
|
|
1393
|
+
const { runMcpCommand } = await import("./mcpCli-DZWQPTOS.js");
|
|
1394
|
+
const { readSecret } = await import("./login-4C262MEI.js");
|
|
1212
1395
|
return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
|
|
1213
1396
|
}
|
|
1214
1397
|
if (args.command === "provider") {
|
|
1215
|
-
const { runProviderCommand } = await import("./providerCli-
|
|
1216
|
-
const { readSecret } = await import("./login-
|
|
1398
|
+
const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
|
|
1399
|
+
const { readSecret } = await import("./login-4C262MEI.js");
|
|
1217
1400
|
return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
|
|
1218
1401
|
}
|
|
1402
|
+
if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
|
|
1403
|
+
const { runIntegrationCommand } = await import("./integrationCli-GEIMUL3M.js");
|
|
1404
|
+
return runIntegrationCommand(args, {
|
|
1405
|
+
env: process.env,
|
|
1406
|
+
cwd,
|
|
1407
|
+
out: (s) => console.log(s),
|
|
1408
|
+
err: (s) => console.error(s),
|
|
1409
|
+
fetch: globalThis.fetch
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1219
1412
|
if (args.command === "models") {
|
|
1220
1413
|
const config = loadConfig(cwd);
|
|
1221
1414
|
const resolved = resolveModel(config, args.model ?? config.model);
|
|
@@ -1230,7 +1423,7 @@ async function main() {
|
|
|
1230
1423
|
}
|
|
1231
1424
|
if (args.print) {
|
|
1232
1425
|
if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
|
|
1233
|
-
const { serversToStart } = await import("./config-
|
|
1426
|
+
const { serversToStart } = await import("./config-YUNKPCNB.js");
|
|
1234
1427
|
const plan = serversToStart(rt.home, cwd);
|
|
1235
1428
|
for (const s of plan.needTrust) {
|
|
1236
1429
|
console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
|
|
@@ -1243,7 +1436,7 @@ async function main() {
|
|
|
1243
1436
|
}
|
|
1244
1437
|
}
|
|
1245
1438
|
if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
|
|
1246
|
-
const { startInteractive } = await import("./ui-
|
|
1439
|
+
const { startInteractive } = await import("./ui-6GTQUJOE.js");
|
|
1247
1440
|
return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
|
|
1248
1441
|
}
|
|
1249
1442
|
main().then(
|