@bike4mind/cli 0.2.82 → 0.3.0

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-CPr5OfMn.mjs";
2
+ import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-CEeSpbaK.mjs";
3
3
  import { execSync } from "child_process";
4
4
  import { constants, existsSync, promises } from "fs";
5
5
  import { homedir } from "os";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { B as ReActAgent, C as loadContextFiles, D as generateCliTools, E as PermissionManager, F as setWebSocketToolExecutor, H as CheckpointStore, L as buildCoreSystemPrompt, V as CustomCommandStore, W as SessionStore, _ as WebSocketLlmBackend, a as createCoordinateTaskTool, c as createAgentDelegateTool, d as createSkillTool, g as FallbackLlmBackend, h as WebSocketConnectionManager, i as createWriteTodosTool, l as AgentStore, m as WebSocketToolExecutor, n as createFindDefinitionTool, o as createBackgroundAgentTools, p as ApiClient, r as createTodoStore, s as BackgroundAgentManager, t as createGetFileStructureTool, u as SubagentOrchestrator, v as ServerLlmBackend, w as getApiUrl, y as McpManager, z as isReadOnlyTool } from "../tools-C0eJHV0Y.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-DTyUBb3A.mjs";
2
+ import { B as ReActAgent, C as loadContextFiles, D as generateCliTools, E as PermissionManager, F as setWebSocketToolExecutor, H as CheckpointStore, L as buildCoreSystemPrompt, V as CustomCommandStore, W as SessionStore, _ as WebSocketLlmBackend, a as createCoordinateTaskTool, c as createAgentDelegateTool, d as createSkillTool, g as FallbackLlmBackend, h as WebSocketConnectionManager, i as createWriteTodosTool, l as AgentStore, m as WebSocketToolExecutor, n as createFindDefinitionTool, o as createBackgroundAgentTools, p as ApiClient, r as createTodoStore, s as BackgroundAgentManager, t as createGetFileStructureTool, u as SubagentOrchestrator, v as ServerLlmBackend, w as getApiUrl, y as McpManager, z as isReadOnlyTool } from "../tools-BImjpPlR.mjs";
3
+ import { n as logger, t as ConfigStore } from "../ConfigStore-o5AblHqQ.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-DBEjF9YS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-C1B4t20N.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BEW3rqYi.mjs";
@@ -67,10 +67,12 @@ async function handleHeadlessCommand(options) {
67
67
  };
68
68
  let wsManager = null;
69
69
  let llm;
70
+ let completionsUrl;
70
71
  try {
71
72
  const serverConfig = await apiClient.get("/api/settings/serverConfig");
72
73
  const wsUrl = serverConfig?.websocketUrl;
73
74
  const wsCompletionUrl = serverConfig?.wsCompletionUrl;
75
+ completionsUrl = serverConfig?.completionsUrl;
74
76
  if (wsUrl && wsCompletionUrl) {
75
77
  wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
76
78
  await wsManager.connect();
@@ -89,7 +91,8 @@ async function handleHeadlessCommand(options) {
89
91
  setWebSocketToolExecutor(null);
90
92
  llm = new ServerLlmBackend({
91
93
  apiClient,
92
- model: config.defaultModel
94
+ model: config.defaultModel,
95
+ completionsUrl
93
96
  });
94
97
  logger.debug("[headless] Using SSE transport fallback");
95
98
  }
@@ -242,7 +245,8 @@ async function handleHeadlessCommand(options) {
242
245
  try {
243
246
  result = await agent.run(fullPrompt, {
244
247
  parallelExecution: config.preferences.enableParallelToolExecution === true,
245
- isReadOnlyTool
248
+ isReadOnlyTool,
249
+ maxHistoryIterations: 4
246
250
  });
247
251
  } finally {
248
252
  backgroundManager.setCurrentTurn(null);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-DTyUBb3A.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-o5AblHqQ.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as version, r as forceCheckForUpdate } from "../updateChecker-CPr5OfMn.mjs";
2
+ import { i as version, r as forceCheckForUpdate } from "../updateChecker-CEeSpbaK.mjs";
3
3
  import { execSync } from "child_process";
4
4
  //#region src/commands/updateCommand.ts
5
5
  /**
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-Dw1nZX2Y.mjs";
3
- import { A as DEFAULT_MAX_ITERATIONS, B as ReActAgent, C as loadContextFiles, D as generateCliTools, E as PermissionManager, F as setWebSocketToolExecutor, G as OAuthClient, H as CheckpointStore, I as OllamaBackend, J as searchCommands, K as hasFileReferences, L as buildCoreSystemPrompt, M as DEFAULT_THOROUGHNESS, N as clearFeatureModuleTools, O as ALWAYS_DENIED_FOR_AGENTS, P as registerFeatureModuleTools, Q as warmFileCache, R as buildSkillsPromptSection, S as extractCompactInstructions, T as getEnvironmentName, U as CommandHistoryStore, V as CustomCommandStore, W as SessionStore, X as formatFileSize, Y as mergeCommands, Z as searchFiles, _ as WebSocketLlmBackend, a as createCoordinateTaskTool, b as substituteArguments, c as createAgentDelegateTool, d as createSkillTool, f as parseAgentConfig, g as FallbackLlmBackend, h as WebSocketConnectionManager, i as createWriteTodosTool, j as DEFAULT_RETRY_CONFIG, k as DEFAULT_AGENT_MODEL, l as AgentStore, m as WebSocketToolExecutor, n as createFindDefinitionTool, o as createBackgroundAgentTools, p as ApiClient, q as processFileReferences, r as createTodoStore, s as BackgroundAgentManager, t as createGetFileStructureTool, u as SubagentOrchestrator, v as ServerLlmBackend, w as getApiUrl, x as formatStep, y as McpManager, z as isReadOnlyTool } from "./tools-C0eJHV0Y.mjs";
4
- import { Dt as validateJupyterKernelName, Ot as validateNotebookPath$1, g as ChatModels, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-DTyUBb3A.mjs";
5
- import { i as version, t as checkForUpdate } from "./updateChecker-CPr5OfMn.mjs";
2
+ import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-B7-LLvvx.mjs";
3
+ import { A as DEFAULT_MAX_ITERATIONS, B as ReActAgent, C as loadContextFiles, D as generateCliTools, E as PermissionManager, F as setWebSocketToolExecutor, G as OAuthClient, H as CheckpointStore, I as OllamaBackend, J as searchCommands, K as hasFileReferences, L as buildCoreSystemPrompt, M as DEFAULT_THOROUGHNESS, N as clearFeatureModuleTools, O as ALWAYS_DENIED_FOR_AGENTS, P as registerFeatureModuleTools, Q as warmFileCache, R as buildSkillsPromptSection, S as extractCompactInstructions, T as getEnvironmentName, U as CommandHistoryStore, V as CustomCommandStore, W as SessionStore, X as formatFileSize, Y as mergeCommands, Z as searchFiles, _ as WebSocketLlmBackend, a as createCoordinateTaskTool, b as substituteArguments, c as createAgentDelegateTool, d as createSkillTool, f as parseAgentConfig, g as FallbackLlmBackend, h as WebSocketConnectionManager, i as createWriteTodosTool, j as DEFAULT_RETRY_CONFIG, k as DEFAULT_AGENT_MODEL, l as AgentStore, m as WebSocketToolExecutor, n as createFindDefinitionTool, o as createBackgroundAgentTools, p as ApiClient, q as processFileReferences, r as createTodoStore, s as BackgroundAgentManager, t as createGetFileStructureTool, u as SubagentOrchestrator, v as ServerLlmBackend, w as getApiUrl, x as formatStep, y as McpManager, z as isReadOnlyTool } from "./tools-BImjpPlR.mjs";
4
+ import { Mt as validateNotebookPath$1, g as ChatModels, jt as validateJupyterKernelName, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-o5AblHqQ.mjs";
5
+ import { i as version, t as checkForUpdate } from "./updateChecker-CEeSpbaK.mjs";
6
6
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
7
  import { Box, Static, Text, render, useApp, useInput } from "ink";
8
8
  import { execSync } from "child_process";
9
9
  import { randomBytes, randomUUID } from "crypto";
10
10
  import { existsSync, promises, readFileSync, statSync } from "fs";
11
11
  import { homedir } from "os";
12
- import path, { extname } from "path";
12
+ import path, { basename, extname, join } from "path";
13
13
  import { v4 } from "uuid";
14
14
  import * as path$1 from "node:path";
15
15
  import Spinner from "ink-spinner";
@@ -1980,14 +1980,14 @@ function App({ onMessage, onBackgroundCompletion, onCommand, onBashCommand, onPe
1980
1980
  args: permissionPrompt.args,
1981
1981
  preview: permissionPrompt.preview,
1982
1982
  canBeTrusted: permissionPrompt.canBeTrusted,
1983
- onResponse: onPermissionResponse
1983
+ onResponse: (response) => onPermissionResponse(response, permissionPrompt.id)
1984
1984
  })), userQuestionPrompt && /* @__PURE__ */ React.createElement(Box, {
1985
1985
  key: userQuestionPrompt.id,
1986
1986
  flexDirection: "column",
1987
1987
  paddingX: 1
1988
1988
  }, /* @__PURE__ */ React.createElement(UserQuestionPrompt, {
1989
1989
  payload: userQuestionPrompt.payload,
1990
- onResponse: onUserQuestionResponse
1990
+ onResponse: (response) => onUserQuestionResponse(response, userQuestionPrompt.id)
1991
1991
  })), !permissionPrompt && !userQuestionPrompt && /* @__PURE__ */ React.createElement(AgentThinking, null), /* @__PURE__ */ React.createElement(BackgroundAgentStatus, null), /* @__PURE__ */ React.createElement(CompletedGroupNotification, null), exitRequested && /* @__PURE__ */ React.createElement(Box, {
1992
1992
  paddingX: 1,
1993
1993
  marginBottom: 1
@@ -4311,6 +4311,224 @@ Agents have personalities, moods, quests, and memories — they are autonomous e
4311
4311
  }
4312
4312
  };
4313
4313
  //#endregion
4314
+ //#region src/features/bridgePresence/BridgePresence.ts
4315
+ const DEFAULT_PORT = Number(process.env.CC_BRIDGE_PORT ?? 48732);
4316
+ const CONFIG_PATH = join(homedir(), ".b4m", "cc-bridge.json");
4317
+ const ANNOUNCE_TIMEOUT_MS = 2e3;
4318
+ async function readBridgeConfig() {
4319
+ try {
4320
+ const raw = await promises.readFile(CONFIG_PATH, "utf8");
4321
+ const parsed = JSON.parse(raw);
4322
+ if (typeof parsed.hookSecret !== "string" || !parsed.hookSecret) return null;
4323
+ return {
4324
+ port: typeof parsed.port === "number" ? parsed.port : DEFAULT_PORT,
4325
+ hookSecret: parsed.hookSecret
4326
+ };
4327
+ } catch {
4328
+ return null;
4329
+ }
4330
+ }
4331
+ var BridgePresence = class {
4332
+ constructor() {
4333
+ this.config = null;
4334
+ this.instanceId = null;
4335
+ this.ws = null;
4336
+ this.callbacks = {};
4337
+ this.started = false;
4338
+ this.stopped = false;
4339
+ this.reconnectTimer = null;
4340
+ this.reconnectAttempts = 0;
4341
+ this.announceRetryTimer = null;
4342
+ this.announceAttempts = 0;
4343
+ this.startOpts = null;
4344
+ this.pendingWorkspaceName = null;
4345
+ this.pendingCapabilities = null;
4346
+ this.pendingSource = null;
4347
+ this.emitQueue = Promise.resolve();
4348
+ }
4349
+ setCallbacks(cbs) {
4350
+ this.callbacks = cbs;
4351
+ }
4352
+ /**
4353
+ * Probe the local bridge and, if present, announce this CLI session.
4354
+ * Returns true iff the announce succeeded (tavern presence is active).
4355
+ * Safe to call multiple times — second call no-ops.
4356
+ *
4357
+ * If announce fails (bridge absent or not yet up), a background retry
4358
+ * loop keeps trying with bounded backoff so the sprite appears when the
4359
+ * bridge comes online later in the CLI's lifetime.
4360
+ */
4361
+ async start(opts) {
4362
+ if (this.started) return this.instanceId !== null;
4363
+ this.started = true;
4364
+ const config = await readBridgeConfig();
4365
+ if (!config) {
4366
+ logger.debug("[tavern] cc-bridge not configured; CLI runs without tavern presence");
4367
+ return false;
4368
+ }
4369
+ this.config = config;
4370
+ this.startOpts = opts;
4371
+ this.pendingWorkspaceName = opts.workspaceName ?? (basename(opts.workspacePath) || "workspace");
4372
+ this.pendingCapabilities = opts.capabilities ?? ["interactive"];
4373
+ this.pendingSource = opts.source ?? "b4m-cli";
4374
+ return this.attemptAnnounce();
4375
+ }
4376
+ /** One announce attempt. Schedules a retry on failure; wires up the
4377
+ * command WS + initial status on success. Idempotent: re-entering after
4378
+ * a successful announce short-circuits at the instanceId guard. */
4379
+ async attemptAnnounce() {
4380
+ if (this.stopped || !this.config || !this.startOpts) return false;
4381
+ if (this.instanceId) return true;
4382
+ const instanceId = v4();
4383
+ const workspaceName = this.pendingWorkspaceName;
4384
+ const capabilities = this.pendingCapabilities;
4385
+ const source = this.pendingSource;
4386
+ if (!await this.announce({
4387
+ instanceId,
4388
+ source,
4389
+ workspaceName,
4390
+ workspacePath: this.startOpts.workspacePath,
4391
+ capabilities
4392
+ })) {
4393
+ this.scheduleAnnounceRetry();
4394
+ return false;
4395
+ }
4396
+ this.instanceId = instanceId;
4397
+ this.announceAttempts = 0;
4398
+ logger.info(`[tavern] announced ${workspaceName} to cc-bridge on 127.0.0.1:${this.config.port ?? DEFAULT_PORT}`);
4399
+ this.connectCommandWs();
4400
+ this.emitEvent({
4401
+ type: "status",
4402
+ status: "idle"
4403
+ });
4404
+ return true;
4405
+ }
4406
+ scheduleAnnounceRetry() {
4407
+ if (this.stopped || this.announceRetryTimer) return;
4408
+ this.announceAttempts += 1;
4409
+ const delay = Math.min(1e3 * 2 ** (this.announceAttempts - 1), 3e4);
4410
+ this.announceRetryTimer = setTimeout(() => {
4411
+ this.announceRetryTimer = null;
4412
+ this.attemptAnnounce();
4413
+ }, delay);
4414
+ }
4415
+ /** Emit an event for this session. No-op if the bridge isn't up. Events
4416
+ * leave in strict order — see `emitQueue` comment. */
4417
+ async emitEvent(event) {
4418
+ if (!this.config || !this.instanceId) return;
4419
+ const task = () => this.post("/event", {
4420
+ instanceId: this.instanceId,
4421
+ event
4422
+ }).catch((err) => logger.info(`[tavern] emitEvent ${event.type} failed: ${err.message}`));
4423
+ this.emitQueue = this.emitQueue.then(task, task);
4424
+ return this.emitQueue;
4425
+ }
4426
+ /** Tear down the tavern presence cleanly. */
4427
+ async stop(reason = "cli_exit") {
4428
+ if (this.stopped) return;
4429
+ this.stopped = true;
4430
+ if (this.reconnectTimer) {
4431
+ clearTimeout(this.reconnectTimer);
4432
+ this.reconnectTimer = null;
4433
+ }
4434
+ if (this.announceRetryTimer) {
4435
+ clearTimeout(this.announceRetryTimer);
4436
+ this.announceRetryTimer = null;
4437
+ }
4438
+ if (this.ws) {
4439
+ try {
4440
+ this.ws.close();
4441
+ } catch {}
4442
+ this.ws = null;
4443
+ }
4444
+ if (this.config && this.instanceId) await this.post("/disconnect", {
4445
+ instanceId: this.instanceId,
4446
+ reason
4447
+ }).catch(() => {});
4448
+ }
4449
+ async announce(body) {
4450
+ try {
4451
+ await this.post("/announce", body);
4452
+ return true;
4453
+ } catch (err) {
4454
+ logger.info(`[tavern] bridge announce failed: ${err.message}`);
4455
+ return false;
4456
+ }
4457
+ }
4458
+ async post(path, body) {
4459
+ if (!this.config) throw new Error("bridge config not loaded");
4460
+ const url = `http://127.0.0.1:${this.config.port ?? DEFAULT_PORT}${path}?secret=${encodeURIComponent(this.config.hookSecret)}`;
4461
+ const res = await fetch(url, {
4462
+ method: "POST",
4463
+ headers: { "Content-Type": "application/json" },
4464
+ body: JSON.stringify(body),
4465
+ signal: AbortSignal.timeout(ANNOUNCE_TIMEOUT_MS)
4466
+ });
4467
+ if (!res.ok) throw new Error(`bridge ${path} -> ${res.status}`);
4468
+ }
4469
+ connectCommandWs() {
4470
+ if (this.stopped || !this.config || !this.instanceId) return;
4471
+ const url = `ws://127.0.0.1:${this.config.port ?? DEFAULT_PORT}/commands?instanceId=${encodeURIComponent(this.instanceId)}&secret=${encodeURIComponent(this.config.hookSecret)}`;
4472
+ let ws;
4473
+ try {
4474
+ ws = new WsWebSocket(url);
4475
+ } catch (err) {
4476
+ logger.debug(`[tavern] command WS construct failed: ${err.message}`);
4477
+ this.scheduleReconnect();
4478
+ return;
4479
+ }
4480
+ this.ws = ws;
4481
+ ws.on("open", () => {
4482
+ this.reconnectAttempts = 0;
4483
+ logger.debug("[tavern] command WS open");
4484
+ });
4485
+ ws.on("message", (raw) => {
4486
+ let frame = null;
4487
+ try {
4488
+ frame = JSON.parse(raw.toString());
4489
+ } catch {
4490
+ logger.debug("[tavern] malformed command frame; ignored");
4491
+ return;
4492
+ }
4493
+ if (!frame?.command) return;
4494
+ this.dispatchCommand(frame.command).catch((err) => logger.warn(`[tavern] command dispatch threw: ${err.message}`));
4495
+ });
4496
+ ws.on("close", () => {
4497
+ this.ws = null;
4498
+ if (this.stopped) return;
4499
+ logger.debug("[tavern] command WS closed; reconnecting");
4500
+ this.scheduleReconnect();
4501
+ });
4502
+ ws.on("error", (err) => {
4503
+ logger.debug(`[tavern] command WS error: ${err.message}`);
4504
+ });
4505
+ }
4506
+ scheduleReconnect() {
4507
+ if (this.stopped || this.reconnectTimer) return;
4508
+ this.reconnectAttempts += 1;
4509
+ const delay = Math.min(500 * 2 ** (this.reconnectAttempts - 1), 1e4);
4510
+ this.reconnectTimer = setTimeout(() => {
4511
+ this.reconnectTimer = null;
4512
+ this.connectCommandWs();
4513
+ }, delay);
4514
+ }
4515
+ async dispatchCommand(command) {
4516
+ switch (command.type) {
4517
+ case "send_prompt":
4518
+ if (this.callbacks.onSendPrompt) await this.callbacks.onSendPrompt(command.text);
4519
+ break;
4520
+ case "resolve_permission":
4521
+ if (this.callbacks.onResolvePermission) await this.callbacks.onResolvePermission(command.requestId, command.allow);
4522
+ break;
4523
+ case "abort":
4524
+ if (this.callbacks.onAbort) await this.callbacks.onAbort();
4525
+ break;
4526
+ }
4527
+ }
4528
+ };
4529
+ /** Process-wide singleton — the CLI only ever has one tavern presence per run. */
4530
+ const bridgePresence = new BridgePresence();
4531
+ //#endregion
4314
4532
  //#region src/index.tsx
4315
4533
  process.removeAllListeners("warning");
4316
4534
  process.on("warning", (warning) => {
@@ -4326,6 +4544,16 @@ function getRequiredJupyterClient() {
4326
4544
  if (!client) throw new Error("Jupyter not configured. Set JUPYTER_SERVER_URL and optionally JUPYTER_TOKEN environment variables.");
4327
4545
  return client;
4328
4546
  }
4547
+ /**
4548
+ * Render the first question from a UserQuestion payload as a 240-char-capped
4549
+ * summary for tavern status events. Returns undefined if the payload has no
4550
+ * questions (defensive: payload.questions[0]?.question is typed string but
4551
+ * the array itself can be empty).
4552
+ */
4553
+ function summarizeUserQuestion(payload) {
4554
+ const first = payload.questions?.[0]?.question;
4555
+ return first ? first.slice(0, 240) : void 0;
4556
+ }
4329
4557
  let exitTimestamp = null;
4330
4558
  const EXIT_TIMEOUT_MS = 2e3;
4331
4559
  let usageCache = null;
@@ -4365,7 +4593,6 @@ function CliApp() {
4365
4593
  const imageStoreInitPromise = useRef(null);
4366
4594
  const setStoreSession = useCliStore((state) => state.setSession);
4367
4595
  const enqueuePermissionPrompt = useCliStore((state) => state.enqueuePermissionPrompt);
4368
- const dequeuePermissionPrompt = useCliStore((state) => state.dequeuePermissionPrompt);
4369
4596
  const enqueueUserQuestionPrompt = useCliStore((state) => state.enqueueUserQuestionPrompt);
4370
4597
  const dequeueUserQuestionPrompt = useCliStore((state) => state.dequeueUserQuestionPrompt);
4371
4598
  const setShowConfigEditor = useCliStore((state) => state.setShowConfigEditor);
@@ -4384,6 +4611,9 @@ function CliApp() {
4384
4611
  state.wsManager.disconnect();
4385
4612
  setWebSocketToolExecutor(null);
4386
4613
  }
4614
+ cleanupTasks.push(bridgePresence.stop("cli_exit").catch((err) => {
4615
+ logger.debug(`[CLEANUP] Bridge presence stop error: ${err.message}`);
4616
+ }));
4387
4617
  if (state.agent) state.agent.removeAllListeners();
4388
4618
  if (state.imageStore) try {
4389
4619
  state.imageStore.close();
@@ -4486,10 +4716,12 @@ function CliApp() {
4486
4716
  };
4487
4717
  let wsManager = null;
4488
4718
  let llm;
4719
+ let completionsUrl;
4489
4720
  try {
4490
4721
  const serverConfig = await apiClient.get("/api/settings/serverConfig");
4491
4722
  const wsUrl = serverConfig?.websocketUrl;
4492
4723
  const wsCompletionUrl = serverConfig?.wsCompletionUrl;
4724
+ completionsUrl = serverConfig?.completionsUrl;
4493
4725
  if (wsUrl && wsCompletionUrl) {
4494
4726
  wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
4495
4727
  await wsManager.connect();
@@ -4605,7 +4837,8 @@ function CliApp() {
4605
4837
  setWebSocketToolExecutor(null);
4606
4838
  llm = new ServerLlmBackend({
4607
4839
  apiClient,
4608
- model: config.defaultModel
4840
+ model: config.defaultModel,
4841
+ completionsUrl
4609
4842
  });
4610
4843
  }
4611
4844
  const ollamaHost = process.env.B4M_OLLAMA_HOST;
@@ -4700,8 +4933,9 @@ function CliApp() {
4700
4933
  const promptFn = (toolName, args, preview) => {
4701
4934
  return new Promise((resolve) => {
4702
4935
  const canBeTrusted = permissionManager.canBeTrusted(toolName);
4936
+ const id = `perm-${++permissionPromptCounter}`;
4703
4937
  const prompt = {
4704
- id: `perm-${++permissionPromptCounter}`,
4938
+ id,
4705
4939
  toolName,
4706
4940
  args,
4707
4941
  preview,
@@ -4713,6 +4947,24 @@ function CliApp() {
4713
4947
  permissionManager
4714
4948
  }));
4715
4949
  enqueuePermissionPrompt(prompt);
4950
+ let summary;
4951
+ if (preview) summary = preview.slice(0, 4e3);
4952
+ else try {
4953
+ summary = JSON.stringify(args).slice(0, 4e3);
4954
+ } catch {
4955
+ summary = void 0;
4956
+ }
4957
+ bridgePresence.emitEvent({
4958
+ type: "permission_request",
4959
+ requestId: id,
4960
+ toolName,
4961
+ input: summary
4962
+ });
4963
+ bridgePresence.emitEvent({
4964
+ type: "status",
4965
+ status: "awaiting_permission",
4966
+ text: `${toolName} permission requested`.slice(0, 240)
4967
+ });
4716
4968
  });
4717
4969
  };
4718
4970
  let userQuestionCounter = 0;
@@ -4723,6 +4975,11 @@ function CliApp() {
4723
4975
  payload,
4724
4976
  resolve
4725
4977
  });
4978
+ bridgePresence.emitEvent({
4979
+ type: "status",
4980
+ status: "awaiting_input",
4981
+ text: summarizeUserQuestion(payload)
4982
+ });
4726
4983
  });
4727
4984
  };
4728
4985
  const agentContext = {
@@ -4878,6 +5135,82 @@ function CliApp() {
4878
5135
  subagent.off("observation", stepHandler);
4879
5136
  subagent.off("action", stepHandler);
4880
5137
  });
5138
+ const pendingToolUseIds = /* @__PURE__ */ new Map();
5139
+ const summarizeToolInput = (input) => {
5140
+ if (input == null) return void 0;
5141
+ if (typeof input === "string") return input.slice(0, 240);
5142
+ try {
5143
+ return JSON.stringify(input).slice(0, 240);
5144
+ } catch {
5145
+ return;
5146
+ }
5147
+ };
5148
+ const tavernActionHandler = (step) => {
5149
+ const toolName = step.metadata?.toolName ?? "tool";
5150
+ const toolUseId = `${toolName}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5151
+ const queue = pendingToolUseIds.get(toolName) ?? [];
5152
+ queue.push(toolUseId);
5153
+ pendingToolUseIds.set(toolName, queue);
5154
+ bridgePresence.emitEvent({
5155
+ type: "tool_use",
5156
+ tool: toolName,
5157
+ toolUseId,
5158
+ text: summarizeToolInput(step.metadata?.toolInput)
5159
+ });
5160
+ };
5161
+ const tavernObservationHandler = (step) => {
5162
+ const toolName = step.metadata?.toolName;
5163
+ let toolUseId;
5164
+ if (toolName) {
5165
+ const queue = pendingToolUseIds.get(toolName);
5166
+ if (queue && queue.length > 0) {
5167
+ toolUseId = queue.shift();
5168
+ if (queue.length === 0) pendingToolUseIds.delete(toolName);
5169
+ }
5170
+ }
5171
+ if (!toolUseId) {
5172
+ for (const [name, queue] of pendingToolUseIds.entries()) if (queue.length > 0) {
5173
+ toolUseId = queue.shift();
5174
+ if (queue.length === 0) pendingToolUseIds.delete(name);
5175
+ break;
5176
+ }
5177
+ }
5178
+ if (!toolUseId) toolUseId = `orphan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5179
+ bridgePresence.emitEvent({
5180
+ type: "tool_result",
5181
+ tool: toolName,
5182
+ toolUseId,
5183
+ text: typeof step.content === "string" ? step.content.slice(0, 4e3) : void 0
5184
+ });
5185
+ };
5186
+ const tavernFinalAnswerHandler = (step) => {
5187
+ const text = typeof step.content === "string" ? step.content : "";
5188
+ if (!text) return;
5189
+ bridgePresence.emitEvent({
5190
+ type: "message",
5191
+ role: "assistant",
5192
+ text: text.slice(0, 4e3)
5193
+ });
5194
+ };
5195
+ agent.on("action", tavernActionHandler);
5196
+ agent.on("observation", tavernObservationHandler);
5197
+ agent.on("final_answer", tavernFinalAnswerHandler);
5198
+ orchestrator.setBeforeRunCallback((subagent, _subagentType) => {
5199
+ subagent.on("thought", stepHandler);
5200
+ subagent.on("observation", stepHandler);
5201
+ subagent.on("action", stepHandler);
5202
+ subagent.on("action", tavernActionHandler);
5203
+ subagent.on("observation", tavernObservationHandler);
5204
+ subagent.on("final_answer", tavernFinalAnswerHandler);
5205
+ });
5206
+ orchestrator.setAfterRunCallback((subagent, _subagentType) => {
5207
+ subagent.off("thought", stepHandler);
5208
+ subagent.off("observation", stepHandler);
5209
+ subagent.off("action", stepHandler);
5210
+ subagent.off("action", tavernActionHandler);
5211
+ subagent.off("observation", tavernObservationHandler);
5212
+ subagent.off("final_answer", tavernFinalAnswerHandler);
5213
+ });
4881
5214
  setState((prev) => ({
4882
5215
  ...prev,
4883
5216
  session: newSession,
@@ -4969,6 +5302,60 @@ function CliApp() {
4969
5302
  useEffect(() => {
4970
5303
  init();
4971
5304
  }, [init]);
5305
+ const handleMessageRef = useRef(null);
5306
+ const abortControllerRef = useRef(state.abortController);
5307
+ abortControllerRef.current = state.abortController;
5308
+ const emitNextAwaitingStatus = () => {
5309
+ const s = useCliStore.getState();
5310
+ if (s.permissionPrompt) {
5311
+ bridgePresence.emitEvent({
5312
+ type: "status",
5313
+ status: "awaiting_permission",
5314
+ text: `${s.permissionPrompt.toolName} permission requested`.slice(0, 240)
5315
+ });
5316
+ return;
5317
+ }
5318
+ if (s.userQuestionPrompt) {
5319
+ bridgePresence.emitEvent({
5320
+ type: "status",
5321
+ status: "awaiting_input",
5322
+ text: summarizeUserQuestion(s.userQuestionPrompt.payload)
5323
+ });
5324
+ return;
5325
+ }
5326
+ const abort = abortControllerRef.current;
5327
+ if (!abort || abort.signal.aborted) return;
5328
+ bridgePresence.emitEvent({
5329
+ type: "status",
5330
+ status: "running"
5331
+ });
5332
+ };
5333
+ useEffect(() => {
5334
+ if (!isInitialized) return;
5335
+ let cancelled = false;
5336
+ bridgePresence.setCallbacks({
5337
+ onSendPrompt: (text) => handleMessageRef.current?.(text),
5338
+ onAbort: () => abortControllerRef.current?.abort(),
5339
+ onResolvePermission: (requestId, allow) => {
5340
+ const action = allow ? "allow-once" : "deny";
5341
+ if (!useCliStore.getState().resolvePermissionPromptById(requestId, action)) return;
5342
+ bridgePresence.emitEvent({
5343
+ type: "permission_resolved",
5344
+ requestId,
5345
+ allow,
5346
+ resolvedBy: "user"
5347
+ });
5348
+ emitNextAwaitingStatus();
5349
+ }
5350
+ });
5351
+ bridgePresence.start({ workspacePath: process.cwd() }).then((live) => {
5352
+ if (cancelled) return;
5353
+ if (live) logger.debug("[tavern] presence active");
5354
+ }).catch((err) => logger.debug(`[tavern] start threw: ${err.message}`));
5355
+ return () => {
5356
+ cancelled = true;
5357
+ };
5358
+ }, [isInitialized]);
4972
5359
  /**
4973
5360
  * Handle custom command execution with proper display
4974
5361
  * Shows concise user message but sends full template to agent
@@ -5188,6 +5575,16 @@ function CliApp() {
5188
5575
  console.error("❌ CLI failed to initialize. Try restarting b4m.\n");
5189
5576
  return;
5190
5577
  }
5578
+ bridgePresence.emitEvent({
5579
+ type: "message",
5580
+ role: "user",
5581
+ text: message.slice(0, 4e3)
5582
+ });
5583
+ bridgePresence.emitEvent({
5584
+ type: "status",
5585
+ status: "running",
5586
+ text: message.slice(0, 240)
5587
+ });
5191
5588
  await state.commandHistoryStore.add(message);
5192
5589
  setCommandHistory(await state.commandHistoryStore.list());
5193
5590
  const config = state.config;
@@ -5358,13 +5755,19 @@ function CliApp() {
5358
5755
  console.error(`\n❌ ${errorMessage}\n`);
5359
5756
  logger.debug(`Full error details: ${error instanceof Error ? error.stack || error.message : String(error)}`);
5360
5757
  } finally {
5758
+ const wasAborted = abortController.signal.aborted;
5361
5759
  setState((prev) => ({
5362
5760
  ...prev,
5363
5761
  abortController: null
5364
5762
  }));
5365
5763
  useCliStore.getState().setIsThinking(false);
5764
+ bridgePresence.emitEvent({
5765
+ type: "status",
5766
+ status: wasAborted ? "idle" : "awaiting_input"
5767
+ });
5366
5768
  }
5367
5769
  };
5770
+ handleMessageRef.current = handleMessage;
5368
5771
  /**
5369
5772
  * Handle background agent completion - runs agent to process results silently
5370
5773
  * without adding a user message to the conversation.
@@ -6857,19 +7260,22 @@ Multi-line Input:
6857
7260
  }));
6858
7261
  },
6859
7262
  mcpManager: state.mcpManager ?? void 0,
6860
- onPermissionResponse: (response) => {
6861
- const currentPrompt = useCliStore.getState().permissionPrompt;
6862
- if (currentPrompt) {
6863
- currentPrompt.resolve({ action: response });
6864
- dequeuePermissionPrompt();
6865
- }
7263
+ onPermissionResponse: (response, promptId) => {
7264
+ if (!useCliStore.getState().resolvePermissionPromptById(promptId, response)) return;
7265
+ bridgePresence.emitEvent({
7266
+ type: "permission_resolved",
7267
+ requestId: promptId,
7268
+ allow: response !== "deny",
7269
+ resolvedBy: "user"
7270
+ });
7271
+ emitNextAwaitingStatus();
6866
7272
  },
6867
- onUserQuestionResponse: (response) => {
7273
+ onUserQuestionResponse: (response, promptId) => {
6868
7274
  const currentPrompt = useCliStore.getState().userQuestionPrompt;
6869
- if (currentPrompt) {
6870
- currentPrompt.resolve(response);
6871
- dequeueUserQuestionPrompt();
6872
- }
7275
+ if (currentPrompt?.id !== promptId) return;
7276
+ currentPrompt.resolve(response);
7277
+ dequeueUserQuestionPrompt();
7278
+ emitNextAwaitingStatus();
6873
7279
  }
6874
7280
  });
6875
7281
  }
@@ -74,6 +74,24 @@ const useCliStore = create((set) => ({
74
74
  permissionQueue: rest
75
75
  };
76
76
  }),
77
+ resolvePermissionPromptById: (id, response) => {
78
+ const state = useCliStore.getState();
79
+ if (state.permissionPrompt?.id === id) {
80
+ const target = state.permissionPrompt;
81
+ state.dequeuePermissionPrompt();
82
+ target.resolve({ action: response });
83
+ return true;
84
+ }
85
+ const queueIdx = state.permissionQueue.findIndex((p) => p.id === id);
86
+ if (queueIdx >= 0) {
87
+ const target = state.permissionQueue[queueIdx];
88
+ const nextQueue = [...state.permissionQueue.slice(0, queueIdx), ...state.permissionQueue.slice(queueIdx + 1)];
89
+ useCliStore.setState({ permissionQueue: nextQueue });
90
+ target.resolve({ action: response });
91
+ return true;
92
+ }
93
+ return false;
94
+ },
77
95
  userQuestionPrompt: null,
78
96
  userQuestionQueue: [],
79
97
  enqueueUserQuestionPrompt: (prompt) => set((state) => {
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as useCliStore } from "./store-B7-LLvvx.mjs";
3
+ export { useCliStore };