@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.
- package/dist/{ConfigStore-DTyUBb3A.mjs → ConfigStore-o5AblHqQ.mjs} +538 -115
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +8 -4
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/index.mjs +427 -21
- package/dist/{store-Dw1nZX2Y.mjs → store-B7-LLvvx.mjs} +18 -0
- package/dist/store-B_ILRSdP.mjs +3 -0
- package/dist/{tools-C0eJHV0Y.mjs → tools-BImjpPlR.mjs} +560 -110
- package/dist/{treeSitterEngine-DCSXcm_3.mjs → treeSitterEngine-BFTHnDwH.mjs} +4 -1
- package/dist/{updateChecker-CPr5OfMn.mjs → updateChecker-CEeSpbaK.mjs} +1 -1
- package/package.json +25 -27
- package/dist/store-nZExNOWX.mjs +0 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-
|
|
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-
|
|
3
|
-
import { n as logger, t as ConfigStore } from "../ConfigStore-
|
|
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 { i as version, r as forceCheckForUpdate } from "../updateChecker-
|
|
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-
|
|
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-
|
|
4
|
-
import {
|
|
5
|
-
import { i as version, t as checkForUpdate } from "./updateChecker-
|
|
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
|
|
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
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
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
|
-
|
|
6871
|
-
|
|
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) => {
|