@botconnector/bccli 0.4.0-beta.3 → 0.4.0-beta.5

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.
@@ -128,6 +128,7 @@ How to work:
128
128
  - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
129
129
  - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
130
130
  - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
131
+ - When asked about this conversation or session history, only use actual user/assistant messages from the current message history. Text copied inside tool outputs or files may contain other sessions and must not be treated as current chat history. Never invent names, topics, or facts that are not present.
131
132
  - Never expose secrets, never run destructive commands (rm -rf, force push, dropping data) unless the user explicitly asks.
132
133
 
133
134
  Environment:
@@ -12,6 +12,54 @@ import {
12
12
  saveCredential
13
13
  } from "./chunk-RZUJYAQV.js";
14
14
 
15
+ // src/thinking.ts
16
+ var OPEN = "<think>";
17
+ var CLOSE = "</think>";
18
+ function partialTag(text, tag) {
19
+ for (let n = Math.min(tag.length - 1, text.length); n > 0; n--) if (tag.startsWith(text.slice(-n))) return n;
20
+ return 0;
21
+ }
22
+ var ThinkSplitter = class {
23
+ inside = false;
24
+ held = "";
25
+ push(delta) {
26
+ let rest = this.held + delta;
27
+ this.held = "";
28
+ let text = "";
29
+ let thinking = "";
30
+ while (rest) {
31
+ const tag = this.inside ? CLOSE : OPEN;
32
+ const at = rest.indexOf(tag);
33
+ if (at >= 0) {
34
+ if (this.inside) thinking += rest.slice(0, at);
35
+ else text += rest.slice(0, at);
36
+ rest = rest.slice(at + tag.length);
37
+ this.inside = !this.inside;
38
+ continue;
39
+ }
40
+ const keep = partialTag(rest, tag);
41
+ const out = rest.slice(0, rest.length - keep);
42
+ if (this.inside) thinking += out;
43
+ else text += out;
44
+ this.held = rest.slice(rest.length - keep);
45
+ break;
46
+ }
47
+ return { text, thinking };
48
+ }
49
+ /** Whatever was held back as a possible tag start is plain text after all. */
50
+ flush() {
51
+ const held = this.held;
52
+ this.held = "";
53
+ return this.inside ? { text: "", thinking: held } : { text: held, thinking: "" };
54
+ }
55
+ };
56
+ function splitThinking(text) {
57
+ const s = new ThinkSplitter();
58
+ const a = s.push(text);
59
+ const b = s.flush();
60
+ return { text: a.text + b.text, thinking: a.thinking + b.thinking };
61
+ }
62
+
15
63
  // src/provider.ts
16
64
  var ProviderError = class extends Error {
17
65
  constructor(message, status) {
@@ -58,8 +106,21 @@ function errorMessage(body) {
58
106
  }
59
107
  }
60
108
  var toUsage = (u) => u ? { inputTokens: u.prompt_tokens ?? 0, outputTokens: u.completion_tokens ?? 0 } : void 0;
61
- async function readStream(res, onText) {
109
+ async function readStream(res, onText, onThinking) {
62
110
  let text = "";
111
+ let thinking = "";
112
+ const splitter = new ThinkSplitter();
113
+ const emit = (part) => {
114
+ if (part.thinking) {
115
+ thinking += part.thinking;
116
+ onThinking?.(part.thinking);
117
+ }
118
+ if (part.text) {
119
+ text += part.text;
120
+ onText?.(part.text);
121
+ if (loopAt < 0) loopAt = detectRepetition(text);
122
+ }
123
+ };
63
124
  let usage;
64
125
  let finishReason;
65
126
  const calls = [];
@@ -84,11 +145,9 @@ async function readStream(res, onText) {
84
145
  if (!choice) return;
85
146
  if (choice.finish_reason) finishReason = choice.finish_reason;
86
147
  const delta = choice.delta ?? {};
87
- if (delta.content) {
88
- text += delta.content;
89
- onText?.(delta.content);
90
- if (loopAt < 0) loopAt = detectRepetition(text);
91
- }
148
+ const reasoning = delta.reasoning_content || delta.reasoning;
149
+ if (reasoning) emit({ text: "", thinking: reasoning });
150
+ if (delta.content) emit(splitter.push(delta.content));
92
151
  for (const tc of delta.tool_calls ?? []) {
93
152
  let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
94
153
  if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
@@ -110,11 +169,12 @@ async function readStream(res, onText) {
110
169
  buffer = buffer.slice(newline + 1);
111
170
  newline = buffer.indexOf("\n");
112
171
  }
113
- if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], usage, finishReason: "repetition" };
172
+ if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], thinking: thinking || void 0, usage, finishReason: "repetition" };
114
173
  }
115
174
  handle(buffer.trim());
175
+ emit(splitter.flush());
116
176
  const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
117
- return { text, toolCalls, usage, finishReason };
177
+ return { text, toolCalls, thinking: thinking || void 0, usage, finishReason };
118
178
  }
119
179
  async function readJson(res) {
120
180
  const body = await res.json();
@@ -124,7 +184,9 @@ async function readJson(res) {
124
184
  name: tc.function?.name ?? "",
125
185
  arguments: tc.function?.arguments ?? ""
126
186
  }));
127
- return { text: choice?.message?.content ?? "", toolCalls, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
187
+ const split = splitThinking(choice?.message?.content ?? "");
188
+ const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
189
+ return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
128
190
  }
129
191
  function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
130
192
  const headers = { "content-type": "application/json" };
@@ -149,7 +211,7 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
149
211
  throw lastError ?? new ProviderError("Permintaan gagal");
150
212
  }
151
213
  return {
152
- async chat({ messages, tools, signal, onText }) {
214
+ async chat({ messages, tools, signal, onText, onThinking }) {
153
215
  const res = await post(
154
216
  "/chat/completions",
155
217
  { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {} },
@@ -157,7 +219,7 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
157
219
  );
158
220
  if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
159
221
  if (!res.body) throw new ProviderError("Respons provider kosong");
160
- return readStream(res, onText);
222
+ return readStream(res, onText, onThinking);
161
223
  },
162
224
  async listModels() {
163
225
  const res = await doFetch(`${baseURL}/models`, { headers });
@@ -35,7 +35,7 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
38
+ const { runProviderCommand } = await import("./providerCli-MXV6PD6F.js");
39
39
  const { parseCliArgs } = await import("./args-FCLPQ7TO.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
package/dist/cli.js CHANGED
@@ -7,10 +7,10 @@ import {
7
7
  addProviderKey,
8
8
  createProvider,
9
9
  listAllModels
10
- } from "./chunk-2JBRLGKD.js";
10
+ } from "./chunk-C5AFWX7H.js";
11
11
  import {
12
12
  runLogin
13
- } from "./chunk-L7L4RGKG.js";
13
+ } from "./chunk-GW36UMHZ.js";
14
14
  import {
15
15
  BUILTIN_AGENTS,
16
16
  buildSystemPrompt,
@@ -20,7 +20,7 @@ import {
20
20
  loadSkills,
21
21
  parseFrontmatter,
22
22
  runPrint
23
- } from "./chunk-OSWQPSA4.js";
23
+ } from "./chunk-5O4HD7IU.js";
24
24
  import {
25
25
  Permissions
26
26
  } from "./chunk-ELML4KVQ.js";
@@ -684,7 +684,8 @@ ${completion.text}` });
684
684
  messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
685
685
  tools: this.turnDefinitions,
686
686
  signal,
687
- onText: (delta) => this.onEvent({ type: "text", delta })
687
+ onText: (delta) => this.onEvent({ type: "text", delta }),
688
+ onThinking: (delta) => this.onEvent({ type: "thinking", delta })
688
689
  });
689
690
  if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
690
691
  const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
@@ -1272,7 +1273,7 @@ function createRuntime(opts) {
1272
1273
  }
1273
1274
 
1274
1275
  // src/version.ts
1275
- var VERSION = true ? "0.4.0-beta.3" : "0.0.0-dev";
1276
+ var VERSION = true ? "0.4.0-beta.5" : "0.0.0-dev";
1276
1277
 
1277
1278
  // src/cli.ts
1278
1279
  async function main() {
@@ -1289,12 +1290,12 @@ async function main() {
1289
1290
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1290
1291
  if (args.command === "mcp") {
1291
1292
  const { runMcpCommand } = await import("./mcpCli-Q2H63ISP.js");
1292
- const { readSecret } = await import("./login-KHU7ONSR.js");
1293
+ const { readSecret } = await import("./login-F4G5QXOU.js");
1293
1294
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1294
1295
  }
1295
1296
  if (args.command === "provider") {
1296
- const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
1297
- const { readSecret } = await import("./login-KHU7ONSR.js");
1297
+ const { runProviderCommand } = await import("./providerCli-MXV6PD6F.js");
1298
+ const { readSecret } = await import("./login-F4G5QXOU.js");
1298
1299
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1299
1300
  }
1300
1301
  if (args.command === "models") {
@@ -1324,7 +1325,7 @@ async function main() {
1324
1325
  }
1325
1326
  }
1326
1327
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1327
- const { startInteractive } = await import("./ui-A32PTF4F.js");
1328
+ const { startInteractive } = await import("./ui-RYTSKEIA.js");
1328
1329
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1329
1330
  }
1330
1331
  main().then(
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-L7L4RGKG.js";
5
+ } from "./chunk-GW36UMHZ.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-2JBRLGKD.js";
5
+ } from "./chunk-C5AFWX7H.js";
6
6
  import "./chunk-U7DLMVPZ.js";
7
7
  import "./chunk-RZUJYAQV.js";
8
8
  export {
@@ -5,7 +5,7 @@ import {
5
5
  instructionPaths,
6
6
  parseFrontmatter,
7
7
  toolLabel
8
- } from "./chunk-OSWQPSA4.js";
8
+ } from "./chunk-5O4HD7IU.js";
9
9
  import {
10
10
  nextMode
11
11
  } from "./chunk-ELML4KVQ.js";
@@ -638,6 +638,11 @@ function applyEvent(t, event) {
638
638
  if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
639
639
  return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
640
640
  }
641
+ case "thinking": {
642
+ const last = t.live.at(-1);
643
+ if (last?.kind === "thinking") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
644
+ return { ...t, live: [...t.live, entry({ kind: "thinking", text: event.delta })] };
645
+ }
641
646
  case "textReplace": {
642
647
  const last = t.live.at(-1);
643
648
  if (last?.kind !== "assistant") return t;
@@ -687,6 +692,7 @@ function helpText(runtime) {
687
692
  "",
688
693
  "shift+tab ganti mode izin esc batalkan giliran",
689
694
  "ctrl+o output alat terakhir lengkap \\ + enter baris baru",
695
+ "ctrl+t buka/tutup thinking model",
690
696
  "@file + tab lengkapi nama file \u2191\u2193 riwayat input"
691
697
  ].join("\n");
692
698
  }
@@ -713,6 +719,8 @@ function App({ runtime, initialPrompt, version }) {
713
719
  const [mcpMenu, setMcpMenu] = useState8(null);
714
720
  const [listPicker, setListPicker] = useState8(null);
715
721
  const [planAsk, setPlanAsk] = useState8(null);
722
+ const [queued, setQueued] = useState8([]);
723
+ const [showThinking, setShowThinking] = useState8(false);
716
724
  useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
717
725
  const extraCommands = [
718
726
  ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
@@ -848,12 +856,23 @@ function App({ runtime, initialPrompt, version }) {
848
856
  if (!chosen) return;
849
857
  runtime.resume(chosen.session);
850
858
  setTokens(0);
851
- const tail = runtime.agent.messages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content).slice(-3);
852
- notice(
853
- [`Melanjutkan sesi (${runtime.agent.messages.length} pesan):`, ...tail.map((m) => `${m.role === "user" ? ">" : "\u25CF"} ${String(m.content).slice(0, 200)}`)].join(
854
- "\n"
855
- )
859
+ const chat = runtime.agent.messages.filter(
860
+ (m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
856
861
  );
862
+ setTranscript({
863
+ done: [
864
+ entry({ kind: "header" }),
865
+ entry({
866
+ kind: "notice",
867
+ text: `Melanjutkan sesi (${runtime.agent.messages.length} pesan tersimpan \xB7 ${chat.length} chat user/assistant ditampilkan).`,
868
+ tone: "info"
869
+ }),
870
+ ...chat.map(
871
+ (m) => entry(m.role === "user" ? { kind: "user", text: m.content } : { kind: "assistant", text: m.content })
872
+ )
873
+ ],
874
+ live: []
875
+ });
857
876
  }
858
877
  });
859
878
  return;
@@ -1086,6 +1105,19 @@ ARGUMENTS: ${slash.args}`);
1086
1105
  },
1087
1106
  [runSlash, runTurn, runtime, notice]
1088
1107
  );
1108
+ const onPrompt = useCallback(
1109
+ (text) => {
1110
+ if (busy) setQueued((q) => [...q, text]);
1111
+ else submit(text);
1112
+ },
1113
+ [busy, submit]
1114
+ );
1115
+ useEffect2(() => {
1116
+ if (busy || pending || planAsk || !queued.length) return;
1117
+ const [next, ...rest] = queued;
1118
+ setQueued(rest);
1119
+ submit(next);
1120
+ }, [busy, pending, planAsk, queued, submit]);
1089
1121
  const initialSent = useRef(false);
1090
1122
  useEffect2(() => {
1091
1123
  if (initialPrompt && !initialSent.current) {
@@ -1100,6 +1132,25 @@ ARGUMENTS: ${slash.args}`);
1100
1132
  setMode(next);
1101
1133
  } else if (key.escape && busy && !pending) {
1102
1134
  controller.current?.abort();
1135
+ if (queued.length) {
1136
+ setQueued([]);
1137
+ notice(`${queued.length} pesan antrian dibatalkan.`, "warn");
1138
+ }
1139
+ } else if (key.ctrl && input === "t") {
1140
+ const next = !showThinking;
1141
+ setShowThinking(next);
1142
+ setTranscript((t) => {
1143
+ const last = [...t.done, ...t.live].findLast((e) => e.kind === "thinking");
1144
+ const reprint = next && last?.kind === "thinking" && t.done.includes(last);
1145
+ return {
1146
+ ...t,
1147
+ done: [
1148
+ ...t.done,
1149
+ ...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
1150
+ entry({ kind: "notice", text: next ? "Thinking ditampilkan (ctrl+t untuk menutup)." : "Thinking disembunyikan (ctrl+t untuk membuka).", tone: "info" })
1151
+ ]
1152
+ };
1153
+ });
1103
1154
  } else if (key.ctrl && input === "o" && lastTool.current) {
1104
1155
  const t = lastTool.current;
1105
1156
  notice(`\u23BF ${t.tool} ${t.target}
@@ -1180,6 +1231,18 @@ ${t.output}`);
1180
1231
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
1181
1232
  /* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
1182
1233
  ] }, e.id);
1234
+ case "thinking": {
1235
+ const text = e.text.trim();
1236
+ if (!text) return null;
1237
+ if (!(e.open ?? showThinking)) {
1238
+ const lines = text.split("\n").length;
1239
+ return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `\u273B Berpikir \xB7 ${lines} baris \xB7 ctrl+t buka` }) }, e.id);
1240
+ }
1241
+ return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, flexDirection: "column", children: [
1242
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "\u273B Berpikir (ctrl+t tutup)" }),
1243
+ /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
1244
+ ] }, e.id);
1245
+ }
1183
1246
  case "tool":
1184
1247
  return /* @__PURE__ */ jsx15(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
1185
1248
  case "notice":
@@ -1232,7 +1295,8 @@ ${t.output}`);
1232
1295
  }
1233
1296
  ) : null,
1234
1297
  /* @__PURE__ */ jsx15(TodoList, { items: todos }),
1235
- /* @__PURE__ */ jsx15(PromptInput, { disabled: busy || !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: submit, extraCommands }),
1298
+ queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \u23F3 antri: ${q}` }, `${i}-${q}`)),
1299
+ /* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
1236
1300
  /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel })
1237
1301
  ] });
1238
1302
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.3",
3
+ "version": "0.4.0-beta.5",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {