@bike4mind/cli 0.2.29-feat-cli-sandbox.18843 → 0.2.29-feat-cli-websocket-streaming.18841
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/{artifactExtractor-EE753J4D.js → artifactExtractor-SNWW3EVH.js} +1 -1
- package/dist/{chunk-PKKJ44C5.js → chunk-BXCARBUQ.js} +2 -2
- package/dist/{chunk-ELTTBWAQ.js → chunk-EC2VEDD2.js} +2 -2
- package/dist/{chunk-5GSDPBTM.js → chunk-KPOK6V6E.js} +67 -2
- package/dist/{chunk-JWEG53NG.js → chunk-LBTTUQJM.js} +3 -65
- package/dist/{chunk-BCOGDPUB.js → chunk-QQL3OBKL.js} +2 -2
- package/dist/{chunk-S7BPPS33.js → chunk-TY6Z2UV2.js} +1 -1
- package/dist/commands/mcpCommand.js +1 -2
- package/dist/{create-O3PVSKFE.js → create-QOJPAIOR.js} +3 -3
- package/dist/index.js +492 -273
- package/dist/{llmMarkdownGenerator-IJKJYJ3U.js → llmMarkdownGenerator-JPDNDGE4.js} +1 -1
- package/dist/{markdownGenerator-SJT7AXLT.js → markdownGenerator-NHN4MNEA.js} +1 -1
- package/dist/{mementoService-PKCNMN4V.js → mementoService-EN7FZK7B.js} +3 -3
- package/dist/{src-GRO5NIC3.js → src-36AL3BJR.js} +13 -1
- package/dist/{src-C5CZFXVM.js → src-JZN5O6F3.js} +2 -2
- package/dist/{subtractCredits-WZZAP57N.js → subtractCredits-FCBMFPIP.js} +3 -3
- package/package.json +6 -6
- package/dist/BubblewrapRuntime-HXTR3NQG.js +0 -68
- package/dist/SandboxOrchestrator-Z36SFJ7X.js +0 -110
- package/dist/SandboxRuntimeAdapter-5ZMU25Q3.js +0 -13
- package/dist/SeatbeltRuntime-SL7HVS3J.js +0 -96
- package/dist/chunk-AVAD2PPK.js +0 -44
- package/dist/chunk-W4TCH4ZT.js +0 -27
- package/dist/types-SMQKWHQK.js +0 -7
package/dist/index.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-ELTTBWAQ.js";
|
|
3
2
|
import "./chunk-BPFEGDC7.js";
|
|
4
3
|
import "./chunk-BDQBOLYG.js";
|
|
5
4
|
import {
|
|
6
5
|
getEffectiveApiKey,
|
|
7
6
|
getOpenWeatherKey,
|
|
8
7
|
getSerperKey
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QQL3OBKL.js";
|
|
10
9
|
import {
|
|
11
10
|
ConfigStore,
|
|
12
11
|
logger
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LBTTUQJM.js";
|
|
14
13
|
import {
|
|
15
14
|
selectActiveBackgroundAgents,
|
|
16
15
|
useCliStore
|
|
17
16
|
} from "./chunk-TVW4ZESU.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-BXCARBUQ.js";
|
|
18
|
+
import "./chunk-EC2VEDD2.js";
|
|
20
19
|
import {
|
|
21
20
|
BFLImageService,
|
|
22
21
|
BaseStorage,
|
|
@@ -28,7 +27,7 @@ import {
|
|
|
28
27
|
OpenAIBackend,
|
|
29
28
|
OpenAIImageService,
|
|
30
29
|
XAIImageService
|
|
31
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-TY6Z2UV2.js";
|
|
32
31
|
import {
|
|
33
32
|
AiEvents,
|
|
34
33
|
ApiKeyEvents,
|
|
@@ -84,7 +83,7 @@ import {
|
|
|
84
83
|
XAI_IMAGE_MODELS,
|
|
85
84
|
b4mLLMTools,
|
|
86
85
|
getMcpProviderMetadata
|
|
87
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-KPOK6V6E.js";
|
|
88
87
|
import {
|
|
89
88
|
Logger
|
|
90
89
|
} from "./chunk-OCYRD7D6.js";
|
|
@@ -94,7 +93,7 @@ import React21, { useState as useState9, useEffect as useEffect6, useCallback as
|
|
|
94
93
|
import { render, Box as Box20, Text as Text20, useApp, useInput as useInput9 } from "ink";
|
|
95
94
|
import { execSync } from "child_process";
|
|
96
95
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
97
|
-
import { v4 as
|
|
96
|
+
import { v4 as uuidv413 } from "uuid";
|
|
98
97
|
|
|
99
98
|
// src/components/App.tsx
|
|
100
99
|
import React15, { useState as useState5, useEffect as useEffect4 } from "react";
|
|
@@ -646,24 +645,6 @@ var COMMANDS = [
|
|
|
646
645
|
name: "compact",
|
|
647
646
|
description: "Compact conversation into new session",
|
|
648
647
|
args: "[instructions]"
|
|
649
|
-
},
|
|
650
|
-
// Sandbox commands
|
|
651
|
-
{
|
|
652
|
-
name: "sandbox",
|
|
653
|
-
description: "Show sandbox status and configuration"
|
|
654
|
-
},
|
|
655
|
-
{
|
|
656
|
-
name: "sandbox:enable",
|
|
657
|
-
description: "Enable OS-level sandbox for bash commands"
|
|
658
|
-
},
|
|
659
|
-
{
|
|
660
|
-
name: "sandbox:disable",
|
|
661
|
-
description: "Disable sandbox mode"
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
name: "sandbox:mode",
|
|
665
|
-
description: "Set sandbox mode (auto-allow or permissions)",
|
|
666
|
-
args: "<auto-allow|permissions>"
|
|
667
648
|
}
|
|
668
649
|
];
|
|
669
650
|
function getAllCommandNames() {
|
|
@@ -3711,9 +3692,6 @@ EXAMPLES:
|
|
|
3711
3692
|
Remember: Use context from previous messages to understand follow-up questions.${contextSection}`;
|
|
3712
3693
|
}
|
|
3713
3694
|
|
|
3714
|
-
// src/utils/toolsAdapter.ts
|
|
3715
|
-
import { rmSync } from "fs";
|
|
3716
|
-
|
|
3717
3695
|
// ../../b4m-core/packages/services/dist/src/referService/generateCodes.js
|
|
3718
3696
|
import { randomBytes } from "crypto";
|
|
3719
3697
|
import range from "lodash/range.js";
|
|
@@ -11460,6 +11438,10 @@ var ServerToolExecutor = class {
|
|
|
11460
11438
|
};
|
|
11461
11439
|
|
|
11462
11440
|
// src/llm/ToolRouter.ts
|
|
11441
|
+
var wsToolExecutor = null;
|
|
11442
|
+
function setWebSocketToolExecutor(executor) {
|
|
11443
|
+
wsToolExecutor = executor;
|
|
11444
|
+
}
|
|
11463
11445
|
var SERVER_TOOLS = ["weather_info", "web_search", "web_fetch"];
|
|
11464
11446
|
var LOCAL_TOOLS = [
|
|
11465
11447
|
"file_read",
|
|
@@ -11482,7 +11464,15 @@ function isLocalTool(toolName) {
|
|
|
11482
11464
|
}
|
|
11483
11465
|
async function executeTool(toolName, input, apiClient, localToolFn) {
|
|
11484
11466
|
if (isServerTool(toolName)) {
|
|
11485
|
-
|
|
11467
|
+
if (wsToolExecutor) {
|
|
11468
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server via WebSocket`);
|
|
11469
|
+
const result = await wsToolExecutor.execute(toolName, input);
|
|
11470
|
+
if (!result.success) {
|
|
11471
|
+
return `Error executing ${toolName}: ${result.error || "Tool execution failed"}`;
|
|
11472
|
+
}
|
|
11473
|
+
return typeof result.content === "string" ? result.content : JSON.stringify(result.content ?? "");
|
|
11474
|
+
}
|
|
11475
|
+
logger.debug(`[ToolRouter] Routing ${toolName} to server via HTTP`);
|
|
11486
11476
|
const executor = new ServerToolExecutor(apiClient);
|
|
11487
11477
|
return await executor.executeTool(toolName, input);
|
|
11488
11478
|
} else if (isLocalTool(toolName)) {
|
|
@@ -11732,60 +11722,20 @@ var CliLogger = class extends Logger {
|
|
|
11732
11722
|
log(...args) {
|
|
11733
11723
|
}
|
|
11734
11724
|
};
|
|
11735
|
-
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient
|
|
11725
|
+
function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient) {
|
|
11736
11726
|
const originalFn = tool.toolFn;
|
|
11737
11727
|
const toolName = tool.toolSchema.name;
|
|
11738
11728
|
return {
|
|
11739
11729
|
...tool,
|
|
11740
11730
|
toolFn: async (args) => {
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
if (toolName === "bash_execute" && args?.command && sandboxOrchestrator) {
|
|
11744
|
-
const pathMod = await import("path");
|
|
11745
|
-
const cwd = args.cwd ? pathMod.resolve(process.cwd(), args.cwd) : process.cwd();
|
|
11746
|
-
const decision = sandboxOrchestrator.shouldSandbox(args.command, cwd);
|
|
11747
|
-
if (decision.type === "blocked") {
|
|
11748
|
-
return `Command blocked by sandbox: ${decision.reason}`;
|
|
11749
|
-
}
|
|
11750
|
-
if (decision.type === "sandbox") {
|
|
11751
|
-
isSandboxed = true;
|
|
11752
|
-
sandboxedArgs = {
|
|
11753
|
-
...args,
|
|
11754
|
-
command: decision.wrappedCommand.commandString,
|
|
11755
|
-
// Store cleanup paths for post-execution cleanup
|
|
11756
|
-
_sandboxCleanup: decision.wrappedCommand.cleanupPaths
|
|
11757
|
-
};
|
|
11758
|
-
}
|
|
11759
|
-
}
|
|
11760
|
-
const effectiveArgs = isSandboxed ? sandboxedArgs : args;
|
|
11761
|
-
if (!permissionManager.needsPermission(toolName, { isSandboxed })) {
|
|
11762
|
-
let result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
|
|
11763
|
-
cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
|
|
11764
|
-
result2 = await retrySandboxFailure(
|
|
11765
|
-
isSandboxed,
|
|
11766
|
-
result2,
|
|
11767
|
-
toolName,
|
|
11768
|
-
args,
|
|
11769
|
-
apiClient,
|
|
11770
|
-
originalFn,
|
|
11771
|
-
showPermissionPrompt
|
|
11772
|
-
);
|
|
11731
|
+
if (!permissionManager.needsPermission(toolName)) {
|
|
11732
|
+
const result2 = await executeTool(toolName, args, apiClient, originalFn);
|
|
11773
11733
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
11774
11734
|
return result2;
|
|
11775
11735
|
}
|
|
11776
11736
|
const { useCliStore: useCliStore2 } = await import("./store-FU6NDC2W.js");
|
|
11777
11737
|
if (useCliStore2.getState().autoAcceptEdits) {
|
|
11778
|
-
|
|
11779
|
-
cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
|
|
11780
|
-
result2 = await retrySandboxFailure(
|
|
11781
|
-
isSandboxed,
|
|
11782
|
-
result2,
|
|
11783
|
-
toolName,
|
|
11784
|
-
args,
|
|
11785
|
-
apiClient,
|
|
11786
|
-
originalFn,
|
|
11787
|
-
showPermissionPrompt
|
|
11788
|
-
);
|
|
11738
|
+
const result2 = await executeTool(toolName, args, apiClient, originalFn);
|
|
11789
11739
|
agentContext.observationQueue.push({ toolName, result: result2 });
|
|
11790
11740
|
return result2;
|
|
11791
11741
|
}
|
|
@@ -11820,10 +11770,9 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
11820
11770
|
} else if (toolName === "bash_execute" && args?.command) {
|
|
11821
11771
|
const cwd = args.cwd ? ` (in ${args.cwd})` : "";
|
|
11822
11772
|
const timeout = args.timeout ? ` [timeout: ${args.timeout}ms]` : "";
|
|
11823
|
-
|
|
11824
|
-
preview = `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
|
|
11773
|
+
preview = `$ ${args.command}${cwd}${timeout}`;
|
|
11825
11774
|
}
|
|
11826
|
-
const response = await showPermissionPrompt(toolName,
|
|
11775
|
+
const response = await showPermissionPrompt(toolName, args, preview);
|
|
11827
11776
|
if (response.action === "deny") {
|
|
11828
11777
|
throw new PermissionDeniedError(toolName, args);
|
|
11829
11778
|
}
|
|
@@ -11848,50 +11797,12 @@ function wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, a
|
|
|
11848
11797
|
}
|
|
11849
11798
|
}
|
|
11850
11799
|
}
|
|
11851
|
-
|
|
11852
|
-
cleanupSandboxFiles(effectiveArgs?._sandboxCleanup);
|
|
11853
|
-
result = await retrySandboxFailure(
|
|
11854
|
-
isSandboxed,
|
|
11855
|
-
result,
|
|
11856
|
-
toolName,
|
|
11857
|
-
args,
|
|
11858
|
-
apiClient,
|
|
11859
|
-
originalFn,
|
|
11860
|
-
showPermissionPrompt
|
|
11861
|
-
);
|
|
11800
|
+
const result = await executeTool(toolName, args, apiClient, originalFn);
|
|
11862
11801
|
agentContext.observationQueue.push({ toolName, result });
|
|
11863
11802
|
return result;
|
|
11864
11803
|
}
|
|
11865
11804
|
};
|
|
11866
11805
|
}
|
|
11867
|
-
function isSandboxFailure(isSandboxed, result) {
|
|
11868
|
-
if (!isSandboxed) return false;
|
|
11869
|
-
return result.includes("sandbox-exec:") || result.includes("bwrap:") || result.includes("Operation not permitted");
|
|
11870
|
-
}
|
|
11871
|
-
async function retrySandboxFailure(isSandboxed, result, toolName, originalArgs, apiClient, originalFn, showPermissionPrompt) {
|
|
11872
|
-
if (!isSandboxFailure(isSandboxed, result)) return result;
|
|
11873
|
-
const errorSnippet = result.slice(0, 200);
|
|
11874
|
-
const retryResponse = await showPermissionPrompt(
|
|
11875
|
-
toolName,
|
|
11876
|
-
originalArgs,
|
|
11877
|
-
`Sandbox execution failed. Retry without sandbox?
|
|
11878
|
-
|
|
11879
|
-
Error: ${errorSnippet}`
|
|
11880
|
-
);
|
|
11881
|
-
if (retryResponse.action !== "deny") {
|
|
11882
|
-
return executeTool(toolName, originalArgs, apiClient, originalFn);
|
|
11883
|
-
}
|
|
11884
|
-
return result;
|
|
11885
|
-
}
|
|
11886
|
-
function cleanupSandboxFiles(paths) {
|
|
11887
|
-
if (!paths || paths.length === 0) return;
|
|
11888
|
-
for (const p of paths) {
|
|
11889
|
-
try {
|
|
11890
|
-
rmSync(p, { recursive: true, force: true });
|
|
11891
|
-
} catch {
|
|
11892
|
-
}
|
|
11893
|
-
}
|
|
11894
|
-
}
|
|
11895
11806
|
function wrapToolWithHooks(tool, hooks, hookContext) {
|
|
11896
11807
|
const hasToolHooks = hooks?.PreToolUse || hooks?.PostToolUse || hooks?.PostToolUseFailure;
|
|
11897
11808
|
if (!hasToolHooks) {
|
|
@@ -11985,7 +11896,7 @@ function normalizeToolName(toolName) {
|
|
|
11985
11896
|
}
|
|
11986
11897
|
return TOOL_NAME_MAPPING[toolName] || toolName;
|
|
11987
11898
|
}
|
|
11988
|
-
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter
|
|
11899
|
+
function generateCliTools(userId, llm, model, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient, toolFilter) {
|
|
11989
11900
|
const logger2 = new CliLogger();
|
|
11990
11901
|
const storage = new NoOpStorage();
|
|
11991
11902
|
const user = {
|
|
@@ -12045,15 +11956,7 @@ function generateCliTools(userId, llm, model, permissionManager, showPermissionP
|
|
|
12045
11956
|
tools_to_generate
|
|
12046
11957
|
);
|
|
12047
11958
|
let tools = Object.entries(toolsMap).map(
|
|
12048
|
-
([_, tool]) => wrapToolWithPermission(
|
|
12049
|
-
tool,
|
|
12050
|
-
permissionManager,
|
|
12051
|
-
showPermissionPrompt,
|
|
12052
|
-
agentContext,
|
|
12053
|
-
configStore,
|
|
12054
|
-
apiClient,
|
|
12055
|
-
sandboxOrchestrator
|
|
12056
|
-
)
|
|
11959
|
+
([_, tool]) => wrapToolWithPermission(tool, permissionManager, showPermissionPrompt, agentContext, configStore, apiClient)
|
|
12057
11960
|
);
|
|
12058
11961
|
if (toolFilter) {
|
|
12059
11962
|
const { allowedTools, deniedTools } = toolFilter;
|
|
@@ -12078,21 +11981,10 @@ var PermissionManager = class {
|
|
|
12078
11981
|
constructor(trustedTools = [], customCategories, deniedTools) {
|
|
12079
11982
|
this.trustedTools = /* @__PURE__ */ new Set();
|
|
12080
11983
|
this.deniedTools = /* @__PURE__ */ new Set();
|
|
12081
|
-
this._sandboxMode = "disabled";
|
|
12082
|
-
this._sandboxActive = false;
|
|
12083
11984
|
this.trustedTools = new Set(trustedTools);
|
|
12084
11985
|
this.customCategories = new Map(Object.entries(customCategories || {}));
|
|
12085
11986
|
this.deniedTools = new Set(deniedTools || []);
|
|
12086
11987
|
}
|
|
12087
|
-
/** Update sandbox state from the orchestrator */
|
|
12088
|
-
setSandboxState(mode, active) {
|
|
12089
|
-
this._sandboxMode = mode;
|
|
12090
|
-
this._sandboxActive = active;
|
|
12091
|
-
}
|
|
12092
|
-
/** Check if sandbox auto-allow is active */
|
|
12093
|
-
isSandboxAutoAllow() {
|
|
12094
|
-
return this._sandboxMode === "auto-allow" && this._sandboxActive;
|
|
12095
|
-
}
|
|
12096
11988
|
/**
|
|
12097
11989
|
* Check if a tool needs permission before execution
|
|
12098
11990
|
*
|
|
@@ -12103,13 +11995,8 @@ var PermissionManager = class {
|
|
|
12103
11995
|
*
|
|
12104
11996
|
* Note: prompt_always tools CANNOT be trusted, so they always need permission
|
|
12105
11997
|
* Note: denied tools from project config ALWAYS need permission (cannot be overridden locally)
|
|
12106
|
-
*
|
|
12107
|
-
* @param toolName - The tool being checked
|
|
12108
|
-
* @param options.isSandboxed - If true, the command will run inside the OS-level sandbox.
|
|
12109
|
-
* In auto-allow mode, sandboxed bash_execute commands skip the permission prompt
|
|
12110
|
-
* because the sandbox provides the security boundary.
|
|
12111
11998
|
*/
|
|
12112
|
-
needsPermission(toolName
|
|
11999
|
+
needsPermission(toolName) {
|
|
12113
12000
|
const categoryMap = Object.fromEntries(this.customCategories);
|
|
12114
12001
|
const category = getToolCategory(toolName, categoryMap);
|
|
12115
12002
|
if (this.deniedTools.has(toolName)) {
|
|
@@ -12118,9 +12005,6 @@ var PermissionManager = class {
|
|
|
12118
12005
|
if (category === "auto_approve") {
|
|
12119
12006
|
return false;
|
|
12120
12007
|
}
|
|
12121
|
-
if (options?.isSandboxed && toolName === "bash_execute" && this.isSandboxAutoAllow()) {
|
|
12122
|
-
return false;
|
|
12123
|
-
}
|
|
12124
12008
|
if (category === "prompt_always") {
|
|
12125
12009
|
return true;
|
|
12126
12010
|
}
|
|
@@ -13518,6 +13402,186 @@ var ServerLlmBackend = class {
|
|
|
13518
13402
|
}
|
|
13519
13403
|
};
|
|
13520
13404
|
|
|
13405
|
+
// src/llm/WebSocketLlmBackend.ts
|
|
13406
|
+
import { v4 as uuidv411 } from "uuid";
|
|
13407
|
+
function stripThinkingBlocks2(text) {
|
|
13408
|
+
return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
13409
|
+
}
|
|
13410
|
+
var WebSocketLlmBackend = class {
|
|
13411
|
+
constructor(options) {
|
|
13412
|
+
this.wsManager = options.wsManager;
|
|
13413
|
+
this.apiClient = options.apiClient;
|
|
13414
|
+
this.currentModel = options.model;
|
|
13415
|
+
this.tokenGetter = options.tokenGetter;
|
|
13416
|
+
}
|
|
13417
|
+
/**
|
|
13418
|
+
* Make LLM completion request via WebSocket.
|
|
13419
|
+
* Mirrors ServerLlmBackend accumulation logic — collects all chunks,
|
|
13420
|
+
* calls callback once at completion with full accumulated content.
|
|
13421
|
+
*/
|
|
13422
|
+
async complete(model, messages, options, callback) {
|
|
13423
|
+
logger.debug(`[WebSocketLlmBackend] Starting complete() with model: ${model}`);
|
|
13424
|
+
if (options.abortSignal?.aborted) {
|
|
13425
|
+
logger.debug("[WebSocketLlmBackend] Request aborted before start");
|
|
13426
|
+
return;
|
|
13427
|
+
}
|
|
13428
|
+
if (!this.wsManager.isConnected) {
|
|
13429
|
+
throw new Error("WebSocket is not connected");
|
|
13430
|
+
}
|
|
13431
|
+
const requestId = uuidv411();
|
|
13432
|
+
const token = await this.tokenGetter();
|
|
13433
|
+
if (!token) {
|
|
13434
|
+
throw new Error("No access token available");
|
|
13435
|
+
}
|
|
13436
|
+
return new Promise((resolve3, reject) => {
|
|
13437
|
+
const isVerbose = process.env.B4M_VERBOSE === "1";
|
|
13438
|
+
const isUltraVerbose = process.env.B4M_DEBUG_STREAM === "1";
|
|
13439
|
+
const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", isVerbose, isUltraVerbose);
|
|
13440
|
+
streamLogger.streamStart();
|
|
13441
|
+
let eventCount = 0;
|
|
13442
|
+
let accumulatedText = "";
|
|
13443
|
+
let lastUsageInfo = {};
|
|
13444
|
+
let toolsUsed = [];
|
|
13445
|
+
let thinkingBlocks = [];
|
|
13446
|
+
let settled = false;
|
|
13447
|
+
const cleanup = () => {
|
|
13448
|
+
this.wsManager.offRequest(requestId);
|
|
13449
|
+
};
|
|
13450
|
+
const settleResolve = () => {
|
|
13451
|
+
if (settled) return;
|
|
13452
|
+
settled = true;
|
|
13453
|
+
cleanup();
|
|
13454
|
+
resolve3();
|
|
13455
|
+
};
|
|
13456
|
+
const settleReject = (err) => {
|
|
13457
|
+
if (settled) return;
|
|
13458
|
+
settled = true;
|
|
13459
|
+
cleanup();
|
|
13460
|
+
reject(err);
|
|
13461
|
+
};
|
|
13462
|
+
if (options.abortSignal) {
|
|
13463
|
+
const abortHandler = () => {
|
|
13464
|
+
logger.debug("[WebSocketLlmBackend] Abort signal received");
|
|
13465
|
+
settleResolve();
|
|
13466
|
+
};
|
|
13467
|
+
if (options.abortSignal.aborted) {
|
|
13468
|
+
settleResolve();
|
|
13469
|
+
return;
|
|
13470
|
+
}
|
|
13471
|
+
options.abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
13472
|
+
}
|
|
13473
|
+
this.wsManager.onRequest(requestId, (message) => {
|
|
13474
|
+
if (options.abortSignal?.aborted) return;
|
|
13475
|
+
const action = message.action;
|
|
13476
|
+
if (action === "cli_completion_chunk") {
|
|
13477
|
+
eventCount++;
|
|
13478
|
+
const chunk = message.chunk;
|
|
13479
|
+
streamLogger.onEvent(eventCount, JSON.stringify(chunk));
|
|
13480
|
+
if (chunk.type === "content") {
|
|
13481
|
+
const textChunk = chunk.text || "";
|
|
13482
|
+
accumulatedText += textChunk;
|
|
13483
|
+
streamLogger.onContent(eventCount, textChunk, accumulatedText);
|
|
13484
|
+
if (chunk.usage) {
|
|
13485
|
+
lastUsageInfo = {
|
|
13486
|
+
inputTokens: chunk.usage.inputTokens,
|
|
13487
|
+
outputTokens: chunk.usage.outputTokens
|
|
13488
|
+
};
|
|
13489
|
+
}
|
|
13490
|
+
} else if (chunk.type === "tool_use") {
|
|
13491
|
+
streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
|
|
13492
|
+
const textChunk = chunk.text || "";
|
|
13493
|
+
if (textChunk) accumulatedText += textChunk;
|
|
13494
|
+
if (chunk.tools && chunk.tools.length > 0) {
|
|
13495
|
+
toolsUsed = chunk.tools;
|
|
13496
|
+
}
|
|
13497
|
+
if (chunk.thinking && chunk.thinking.length > 0) {
|
|
13498
|
+
thinkingBlocks = chunk.thinking;
|
|
13499
|
+
}
|
|
13500
|
+
if (chunk.usage) {
|
|
13501
|
+
lastUsageInfo = {
|
|
13502
|
+
inputTokens: chunk.usage.inputTokens,
|
|
13503
|
+
outputTokens: chunk.usage.outputTokens
|
|
13504
|
+
};
|
|
13505
|
+
}
|
|
13506
|
+
}
|
|
13507
|
+
} else if (action === "cli_completion_done") {
|
|
13508
|
+
streamLogger.streamComplete(accumulatedText);
|
|
13509
|
+
const cleanedText = stripThinkingBlocks2(accumulatedText);
|
|
13510
|
+
if (toolsUsed.length > 0) {
|
|
13511
|
+
const info = {
|
|
13512
|
+
toolsUsed,
|
|
13513
|
+
thinking: thinkingBlocks.length > 0 ? thinkingBlocks : void 0,
|
|
13514
|
+
...lastUsageInfo
|
|
13515
|
+
};
|
|
13516
|
+
callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
|
|
13517
|
+
} else if (cleanedText) {
|
|
13518
|
+
callback([cleanedText], lastUsageInfo).then(() => settleResolve()).catch((err) => settleReject(err));
|
|
13519
|
+
} else {
|
|
13520
|
+
settleResolve();
|
|
13521
|
+
}
|
|
13522
|
+
} else if (action === "cli_completion_error") {
|
|
13523
|
+
const errorMsg = message.error || "Server error";
|
|
13524
|
+
streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
|
|
13525
|
+
settleReject(new Error(errorMsg));
|
|
13526
|
+
}
|
|
13527
|
+
});
|
|
13528
|
+
try {
|
|
13529
|
+
this.wsManager.send({
|
|
13530
|
+
action: "cli_completion_request",
|
|
13531
|
+
accessToken: token,
|
|
13532
|
+
requestId,
|
|
13533
|
+
model,
|
|
13534
|
+
messages,
|
|
13535
|
+
options: {
|
|
13536
|
+
temperature: options.temperature,
|
|
13537
|
+
maxTokens: options.maxTokens,
|
|
13538
|
+
stream: true,
|
|
13539
|
+
tools: options.tools || []
|
|
13540
|
+
}
|
|
13541
|
+
});
|
|
13542
|
+
} catch (err) {
|
|
13543
|
+
settleReject(err instanceof Error ? err : new Error(String(err)));
|
|
13544
|
+
}
|
|
13545
|
+
});
|
|
13546
|
+
}
|
|
13547
|
+
/**
|
|
13548
|
+
* Get available models from server (REST call, not streaming).
|
|
13549
|
+
* Delegates to ApiClient — same as ServerLlmBackend.
|
|
13550
|
+
*/
|
|
13551
|
+
async getModelInfo() {
|
|
13552
|
+
try {
|
|
13553
|
+
logger.debug("[WebSocketLlmBackend] Fetching models from /api/models");
|
|
13554
|
+
const response = await this.apiClient.get("/api/models");
|
|
13555
|
+
if (!response || typeof response !== "object" || !Array.isArray(response.models)) {
|
|
13556
|
+
logger.warn("[WebSocketLlmBackend] Invalid API response format, using fallback models");
|
|
13557
|
+
return this.getFallbackModels();
|
|
13558
|
+
}
|
|
13559
|
+
const filteredModels = response.models.filter(
|
|
13560
|
+
(model) => model.type === "text" && model.supportsTools === true
|
|
13561
|
+
);
|
|
13562
|
+
if (filteredModels.length === 0) {
|
|
13563
|
+
logger.warn("[WebSocketLlmBackend] No CLI-compatible models found, using fallback");
|
|
13564
|
+
return this.getFallbackModels();
|
|
13565
|
+
}
|
|
13566
|
+
logger.debug(`[WebSocketLlmBackend] Loaded ${filteredModels.length} models`);
|
|
13567
|
+
return filteredModels;
|
|
13568
|
+
} catch (error) {
|
|
13569
|
+
logger.warn(
|
|
13570
|
+
`[WebSocketLlmBackend] Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`
|
|
13571
|
+
);
|
|
13572
|
+
return this.getFallbackModels();
|
|
13573
|
+
}
|
|
13574
|
+
}
|
|
13575
|
+
getFallbackModels() {
|
|
13576
|
+
return [
|
|
13577
|
+
{ id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet" },
|
|
13578
|
+
{ id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
|
|
13579
|
+
{ id: "gpt-4o", name: "GPT-4o" },
|
|
13580
|
+
{ id: "gpt-4o-mini", name: "GPT-4o Mini" }
|
|
13581
|
+
];
|
|
13582
|
+
}
|
|
13583
|
+
};
|
|
13584
|
+
|
|
13521
13585
|
// src/llm/NotifyingLlmBackend.ts
|
|
13522
13586
|
var NotifyingLlmBackend = class {
|
|
13523
13587
|
constructor(inner, backgroundManager) {
|
|
@@ -13552,6 +13616,223 @@ Please acknowledge these background agent results and incorporate them into your
|
|
|
13552
13616
|
}
|
|
13553
13617
|
};
|
|
13554
13618
|
|
|
13619
|
+
// src/ws/WebSocketConnectionManager.ts
|
|
13620
|
+
var WebSocketConnectionManager = class {
|
|
13621
|
+
constructor(wsUrl, getToken) {
|
|
13622
|
+
this.ws = null;
|
|
13623
|
+
this.heartbeatInterval = null;
|
|
13624
|
+
this.reconnectAttempts = 0;
|
|
13625
|
+
this.maxReconnectDelay = 3e4;
|
|
13626
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
13627
|
+
this.connected = false;
|
|
13628
|
+
this.connecting = false;
|
|
13629
|
+
this.closed = false;
|
|
13630
|
+
this.wsUrl = wsUrl;
|
|
13631
|
+
this.getToken = getToken;
|
|
13632
|
+
}
|
|
13633
|
+
/**
|
|
13634
|
+
* Connect to the WebSocket server.
|
|
13635
|
+
* Resolves when connection is established, rejects on failure.
|
|
13636
|
+
*/
|
|
13637
|
+
async connect() {
|
|
13638
|
+
if (this.connected || this.connecting) return;
|
|
13639
|
+
this.connecting = true;
|
|
13640
|
+
const token = await this.getToken();
|
|
13641
|
+
if (!token) {
|
|
13642
|
+
this.connecting = false;
|
|
13643
|
+
throw new Error("No access token available for WebSocket connection");
|
|
13644
|
+
}
|
|
13645
|
+
return new Promise((resolve3, reject) => {
|
|
13646
|
+
const url = `${this.wsUrl}?token=${token}`;
|
|
13647
|
+
logger.debug(`[WS] Connecting to ${this.wsUrl}...`);
|
|
13648
|
+
this.ws = new WebSocket(url);
|
|
13649
|
+
this.ws.onopen = () => {
|
|
13650
|
+
logger.debug("[WS] Connected");
|
|
13651
|
+
this.connected = true;
|
|
13652
|
+
this.connecting = false;
|
|
13653
|
+
this.reconnectAttempts = 0;
|
|
13654
|
+
this.startHeartbeat();
|
|
13655
|
+
resolve3();
|
|
13656
|
+
};
|
|
13657
|
+
this.ws.onmessage = (event) => {
|
|
13658
|
+
try {
|
|
13659
|
+
const data = typeof event.data === "string" ? event.data : event.data.toString();
|
|
13660
|
+
const message = JSON.parse(data);
|
|
13661
|
+
const requestId = message.requestId;
|
|
13662
|
+
if (requestId && this.handlers.has(requestId)) {
|
|
13663
|
+
this.handlers.get(requestId)(message);
|
|
13664
|
+
} else {
|
|
13665
|
+
logger.debug(`[WS] Unhandled message: ${message.action || "unknown"}`);
|
|
13666
|
+
}
|
|
13667
|
+
} catch (err) {
|
|
13668
|
+
logger.debug(`[WS] Failed to parse message: ${err}`);
|
|
13669
|
+
}
|
|
13670
|
+
};
|
|
13671
|
+
this.ws.onclose = () => {
|
|
13672
|
+
logger.debug("[WS] Connection closed");
|
|
13673
|
+
this.cleanup();
|
|
13674
|
+
if (!this.closed) {
|
|
13675
|
+
this.scheduleReconnect();
|
|
13676
|
+
}
|
|
13677
|
+
};
|
|
13678
|
+
this.ws.onerror = (err) => {
|
|
13679
|
+
logger.debug(`[WS] Error: ${err}`);
|
|
13680
|
+
if (this.connecting) {
|
|
13681
|
+
this.connecting = false;
|
|
13682
|
+
this.connected = false;
|
|
13683
|
+
reject(new Error("WebSocket connection failed"));
|
|
13684
|
+
}
|
|
13685
|
+
};
|
|
13686
|
+
});
|
|
13687
|
+
}
|
|
13688
|
+
/** Whether the connection is currently established */
|
|
13689
|
+
get isConnected() {
|
|
13690
|
+
return this.connected;
|
|
13691
|
+
}
|
|
13692
|
+
/**
|
|
13693
|
+
* Send a JSON message over the WebSocket connection.
|
|
13694
|
+
*/
|
|
13695
|
+
send(data) {
|
|
13696
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
13697
|
+
throw new Error("WebSocket is not connected");
|
|
13698
|
+
}
|
|
13699
|
+
this.ws.send(JSON.stringify(data));
|
|
13700
|
+
}
|
|
13701
|
+
/**
|
|
13702
|
+
* Register a handler for messages matching a specific requestId.
|
|
13703
|
+
*/
|
|
13704
|
+
onRequest(requestId, handler) {
|
|
13705
|
+
this.handlers.set(requestId, handler);
|
|
13706
|
+
}
|
|
13707
|
+
/**
|
|
13708
|
+
* Remove a handler for a specific requestId.
|
|
13709
|
+
*/
|
|
13710
|
+
offRequest(requestId) {
|
|
13711
|
+
this.handlers.delete(requestId);
|
|
13712
|
+
}
|
|
13713
|
+
/**
|
|
13714
|
+
* Close the connection and stop all heartbeat/reconnect logic.
|
|
13715
|
+
*/
|
|
13716
|
+
disconnect() {
|
|
13717
|
+
this.closed = true;
|
|
13718
|
+
this.cleanup();
|
|
13719
|
+
if (this.ws) {
|
|
13720
|
+
this.ws.close();
|
|
13721
|
+
this.ws = null;
|
|
13722
|
+
}
|
|
13723
|
+
this.handlers.clear();
|
|
13724
|
+
}
|
|
13725
|
+
startHeartbeat() {
|
|
13726
|
+
this.stopHeartbeat();
|
|
13727
|
+
this.heartbeatInterval = setInterval(
|
|
13728
|
+
() => {
|
|
13729
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
13730
|
+
this.ws.send(JSON.stringify({ action: "heartbeat" }));
|
|
13731
|
+
logger.debug("[WS] Heartbeat sent");
|
|
13732
|
+
}
|
|
13733
|
+
},
|
|
13734
|
+
5 * 60 * 1e3
|
|
13735
|
+
);
|
|
13736
|
+
}
|
|
13737
|
+
stopHeartbeat() {
|
|
13738
|
+
if (this.heartbeatInterval) {
|
|
13739
|
+
clearInterval(this.heartbeatInterval);
|
|
13740
|
+
this.heartbeatInterval = null;
|
|
13741
|
+
}
|
|
13742
|
+
}
|
|
13743
|
+
cleanup() {
|
|
13744
|
+
this.connected = false;
|
|
13745
|
+
this.connecting = false;
|
|
13746
|
+
this.stopHeartbeat();
|
|
13747
|
+
}
|
|
13748
|
+
scheduleReconnect() {
|
|
13749
|
+
if (this.closed) return;
|
|
13750
|
+
this.reconnectAttempts++;
|
|
13751
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts - 1), this.maxReconnectDelay);
|
|
13752
|
+
logger.debug(`[WS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
13753
|
+
setTimeout(async () => {
|
|
13754
|
+
if (this.closed) return;
|
|
13755
|
+
try {
|
|
13756
|
+
await this.connect();
|
|
13757
|
+
} catch {
|
|
13758
|
+
logger.debug("[WS] Reconnection failed");
|
|
13759
|
+
}
|
|
13760
|
+
}, delay);
|
|
13761
|
+
}
|
|
13762
|
+
};
|
|
13763
|
+
|
|
13764
|
+
// src/ws/WebSocketToolExecutor.ts
|
|
13765
|
+
import { v4 as uuidv412 } from "uuid";
|
|
13766
|
+
var WebSocketToolExecutor = class {
|
|
13767
|
+
constructor(wsManager, tokenGetter) {
|
|
13768
|
+
this.wsManager = wsManager;
|
|
13769
|
+
this.tokenGetter = tokenGetter;
|
|
13770
|
+
}
|
|
13771
|
+
/**
|
|
13772
|
+
* Execute a server-side tool via WebSocket.
|
|
13773
|
+
* Returns the tool result or throws on error.
|
|
13774
|
+
*/
|
|
13775
|
+
async execute(toolName, input, abortSignal) {
|
|
13776
|
+
if (!this.wsManager.isConnected) {
|
|
13777
|
+
throw new Error("WebSocket is not connected");
|
|
13778
|
+
}
|
|
13779
|
+
const token = await this.tokenGetter();
|
|
13780
|
+
if (!token) {
|
|
13781
|
+
throw new Error("No access token available");
|
|
13782
|
+
}
|
|
13783
|
+
const requestId = uuidv412();
|
|
13784
|
+
return new Promise((resolve3, reject) => {
|
|
13785
|
+
let settled = false;
|
|
13786
|
+
const cleanup = () => {
|
|
13787
|
+
this.wsManager.offRequest(requestId);
|
|
13788
|
+
};
|
|
13789
|
+
const settleResolve = (result) => {
|
|
13790
|
+
if (settled) return;
|
|
13791
|
+
settled = true;
|
|
13792
|
+
cleanup();
|
|
13793
|
+
resolve3(result);
|
|
13794
|
+
};
|
|
13795
|
+
const settleReject = (err) => {
|
|
13796
|
+
if (settled) return;
|
|
13797
|
+
settled = true;
|
|
13798
|
+
cleanup();
|
|
13799
|
+
reject(err);
|
|
13800
|
+
};
|
|
13801
|
+
if (abortSignal) {
|
|
13802
|
+
const abortHandler = () => {
|
|
13803
|
+
settleReject(new Error("Tool execution aborted"));
|
|
13804
|
+
};
|
|
13805
|
+
if (abortSignal.aborted) {
|
|
13806
|
+
settleReject(new Error("Tool execution aborted"));
|
|
13807
|
+
return;
|
|
13808
|
+
}
|
|
13809
|
+
abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
13810
|
+
}
|
|
13811
|
+
this.wsManager.onRequest(requestId, (message) => {
|
|
13812
|
+
const action = message.action;
|
|
13813
|
+
if (action === "cli_tool_response") {
|
|
13814
|
+
settleResolve({
|
|
13815
|
+
success: message.success,
|
|
13816
|
+
content: message.content,
|
|
13817
|
+
error: message.error
|
|
13818
|
+
});
|
|
13819
|
+
}
|
|
13820
|
+
});
|
|
13821
|
+
try {
|
|
13822
|
+
this.wsManager.send({
|
|
13823
|
+
action: "cli_tool_request",
|
|
13824
|
+
accessToken: token,
|
|
13825
|
+
requestId,
|
|
13826
|
+
toolName,
|
|
13827
|
+
input
|
|
13828
|
+
});
|
|
13829
|
+
} catch (err) {
|
|
13830
|
+
settleReject(err instanceof Error ? err : new Error(String(err)));
|
|
13831
|
+
}
|
|
13832
|
+
});
|
|
13833
|
+
}
|
|
13834
|
+
};
|
|
13835
|
+
|
|
13555
13836
|
// src/auth/ApiClient.ts
|
|
13556
13837
|
import axios11 from "axios";
|
|
13557
13838
|
var ApiClient = class {
|
|
@@ -13689,7 +13970,7 @@ import { isAxiosError as isAxiosError2 } from "axios";
|
|
|
13689
13970
|
// package.json
|
|
13690
13971
|
var package_default = {
|
|
13691
13972
|
name: "@bike4mind/cli",
|
|
13692
|
-
version: "0.2.29-feat-cli-
|
|
13973
|
+
version: "0.2.29-feat-cli-websocket-streaming.18841+5c940d031",
|
|
13693
13974
|
type: "module",
|
|
13694
13975
|
description: "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
13695
13976
|
license: "UNLICENSED",
|
|
@@ -13799,10 +14080,10 @@ var package_default = {
|
|
|
13799
14080
|
},
|
|
13800
14081
|
devDependencies: {
|
|
13801
14082
|
"@bike4mind/agents": "0.1.0",
|
|
13802
|
-
"@bike4mind/common": "2.50.1-feat-cli-
|
|
13803
|
-
"@bike4mind/mcp": "1.29.1-feat-cli-
|
|
13804
|
-
"@bike4mind/services": "2.48.1-feat-cli-
|
|
13805
|
-
"@bike4mind/utils": "2.5.1-feat-cli-
|
|
14083
|
+
"@bike4mind/common": "2.50.1-feat-cli-websocket-streaming.18841+5c940d031",
|
|
14084
|
+
"@bike4mind/mcp": "1.29.1-feat-cli-websocket-streaming.18841+5c940d031",
|
|
14085
|
+
"@bike4mind/services": "2.48.1-feat-cli-websocket-streaming.18841+5c940d031",
|
|
14086
|
+
"@bike4mind/utils": "2.5.1-feat-cli-websocket-streaming.18841+5c940d031",
|
|
13806
14087
|
"@types/better-sqlite3": "^7.6.13",
|
|
13807
14088
|
"@types/diff": "^5.0.9",
|
|
13808
14089
|
"@types/jsonwebtoken": "^9.0.4",
|
|
@@ -13819,7 +14100,7 @@ var package_default = {
|
|
|
13819
14100
|
optionalDependencies: {
|
|
13820
14101
|
"@vscode/ripgrep": "^1.17.0"
|
|
13821
14102
|
},
|
|
13822
|
-
gitHead: "
|
|
14103
|
+
gitHead: "5c940d031610617c76d3c12cc611b71db8bb4caf"
|
|
13823
14104
|
};
|
|
13824
14105
|
|
|
13825
14106
|
// src/config/constants.ts
|
|
@@ -15628,7 +15909,7 @@ function CliApp() {
|
|
|
15628
15909
|
abortController: null,
|
|
15629
15910
|
contextContent: "",
|
|
15630
15911
|
backgroundManager: null,
|
|
15631
|
-
|
|
15912
|
+
wsManager: null
|
|
15632
15913
|
});
|
|
15633
15914
|
const [isInitialized, setIsInitialized] = useState9(false);
|
|
15634
15915
|
const [initError, setInitError] = useState9(null);
|
|
@@ -15656,6 +15937,10 @@ function CliApp() {
|
|
|
15656
15937
|
})
|
|
15657
15938
|
);
|
|
15658
15939
|
}
|
|
15940
|
+
if (state.wsManager) {
|
|
15941
|
+
state.wsManager.disconnect();
|
|
15942
|
+
setWebSocketToolExecutor(null);
|
|
15943
|
+
}
|
|
15659
15944
|
if (state.agent) {
|
|
15660
15945
|
state.agent.removeAllListeners();
|
|
15661
15946
|
}
|
|
@@ -15674,7 +15959,7 @@ function CliApp() {
|
|
|
15674
15959
|
setTimeout(() => {
|
|
15675
15960
|
process.exit(0);
|
|
15676
15961
|
}, 100);
|
|
15677
|
-
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore]);
|
|
15962
|
+
}, [state.session, state.sessionStore, state.mcpManager, state.agent, state.imageStore, state.wsManager]);
|
|
15678
15963
|
useInput9((input, key) => {
|
|
15679
15964
|
if (key.escape) {
|
|
15680
15965
|
if (state.abortController) {
|
|
@@ -15744,7 +16029,7 @@ function CliApp() {
|
|
|
15744
16029
|
if (!isAuthenticated) {
|
|
15745
16030
|
console.log("\u2139\uFE0F AI features disabled. Available commands: /login, /help, /config\n");
|
|
15746
16031
|
const minimalSession = {
|
|
15747
|
-
id:
|
|
16032
|
+
id: uuidv413(),
|
|
15748
16033
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
15749
16034
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15750
16035
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15772,10 +16057,41 @@ function CliApp() {
|
|
|
15772
16057
|
console.log(`\u{1F30D} API Environment: ${envName} (${apiBaseURL})`);
|
|
15773
16058
|
}
|
|
15774
16059
|
const apiClient = new ApiClient(apiBaseURL, state.configStore);
|
|
15775
|
-
const
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
}
|
|
16060
|
+
const tokenGetter = async () => {
|
|
16061
|
+
const tokens = await state.configStore.getAuthTokens();
|
|
16062
|
+
return tokens?.accessToken ?? null;
|
|
16063
|
+
};
|
|
16064
|
+
let wsManager = null;
|
|
16065
|
+
let llm;
|
|
16066
|
+
try {
|
|
16067
|
+
const serverConfig = await apiClient.get("/api/settings/serverConfig");
|
|
16068
|
+
const wsUrl = serverConfig?.websocketUrl;
|
|
16069
|
+
if (wsUrl) {
|
|
16070
|
+
wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
|
|
16071
|
+
await wsManager.connect();
|
|
16072
|
+
const wsToolExecutor2 = new WebSocketToolExecutor(wsManager, tokenGetter);
|
|
16073
|
+
setWebSocketToolExecutor(wsToolExecutor2);
|
|
16074
|
+
llm = new WebSocketLlmBackend({
|
|
16075
|
+
wsManager,
|
|
16076
|
+
apiClient,
|
|
16077
|
+
model: config.defaultModel,
|
|
16078
|
+
tokenGetter
|
|
16079
|
+
});
|
|
16080
|
+
logger.debug("\u{1F50C} Using WebSocket transport (bypasses CloudFront timeout)");
|
|
16081
|
+
} else {
|
|
16082
|
+
throw new Error("No websocketUrl in server config");
|
|
16083
|
+
}
|
|
16084
|
+
} catch (wsError) {
|
|
16085
|
+
logger.debug(
|
|
16086
|
+
`[WS] WebSocket unavailable, using SSE fallback: ${wsError instanceof Error ? wsError.message : String(wsError)}`
|
|
16087
|
+
);
|
|
16088
|
+
wsManager = null;
|
|
16089
|
+
setWebSocketToolExecutor(null);
|
|
16090
|
+
llm = new ServerLlmBackend({
|
|
16091
|
+
apiClient,
|
|
16092
|
+
model: config.defaultModel
|
|
16093
|
+
});
|
|
16094
|
+
}
|
|
15779
16095
|
const models = await llm.getModelInfo();
|
|
15780
16096
|
if (models.length === 0) {
|
|
15781
16097
|
throw new Error("No models available from server.");
|
|
@@ -15788,7 +16104,7 @@ function CliApp() {
|
|
|
15788
16104
|
}
|
|
15789
16105
|
llm.currentModel = modelInfo.id;
|
|
15790
16106
|
const newSession = {
|
|
15791
|
-
id:
|
|
16107
|
+
id: uuidv413(),
|
|
15792
16108
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
15793
16109
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15794
16110
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -15825,20 +16141,6 @@ function CliApp() {
|
|
|
15825
16141
|
config.tools.disabled || []
|
|
15826
16142
|
// denied tools from project config
|
|
15827
16143
|
);
|
|
15828
|
-
const { createSandboxRuntime } = await import("./SandboxRuntimeAdapter-5ZMU25Q3.js");
|
|
15829
|
-
const { SandboxOrchestrator } = await import("./SandboxOrchestrator-Z36SFJ7X.js");
|
|
15830
|
-
const { DEFAULT_SANDBOX_CONFIG } = await import("./types-SMQKWHQK.js");
|
|
15831
|
-
const sandboxRuntime = await createSandboxRuntime();
|
|
15832
|
-
const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
|
|
15833
|
-
const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime);
|
|
15834
|
-
permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
|
|
15835
|
-
if (sandboxConfig.enabled && sandboxConfig.mode !== "disabled") {
|
|
15836
|
-
if (sandboxRuntime) {
|
|
15837
|
-
console.log(`\u{1F512} Sandbox: ${sandboxConfig.mode} (${sandboxRuntime.name})`);
|
|
15838
|
-
} else {
|
|
15839
|
-
console.log("\u26A0\uFE0F Sandbox: enabled but runtime not available on this platform");
|
|
15840
|
-
}
|
|
15841
|
-
}
|
|
15842
16144
|
let permissionPromptCounter = 0;
|
|
15843
16145
|
const promptFn = (toolName, args, preview) => {
|
|
15844
16146
|
return new Promise((resolve3) => {
|
|
@@ -15864,10 +16166,7 @@ function CliApp() {
|
|
|
15864
16166
|
promptFn,
|
|
15865
16167
|
agentContext,
|
|
15866
16168
|
state.configStore,
|
|
15867
|
-
apiClient
|
|
15868
|
-
void 0,
|
|
15869
|
-
// toolFilter
|
|
15870
|
-
sandboxOrchestrator
|
|
16169
|
+
apiClient
|
|
15871
16170
|
);
|
|
15872
16171
|
startupLog.push(`\u{1F6E0}\uFE0F Loaded ${b4mTools2.length} B4M tool(s)`);
|
|
15873
16172
|
const mcpManager = new McpManager(config);
|
|
@@ -16027,8 +16326,8 @@ ${contextResult.mergedContent}` : "";
|
|
|
16027
16326
|
// Store raw context for compact instructions
|
|
16028
16327
|
backgroundManager,
|
|
16029
16328
|
// Store for grouped notification turn tracking
|
|
16030
|
-
|
|
16031
|
-
//
|
|
16329
|
+
wsManager
|
|
16330
|
+
// WebSocket connection manager (null if using SSE fallback)
|
|
16032
16331
|
}));
|
|
16033
16332
|
setStoreSession(newSession);
|
|
16034
16333
|
const bannerLines = [
|
|
@@ -16123,13 +16422,13 @@ ${contextResult.mergedContent}` : "";
|
|
|
16123
16422
|
messageContent = multimodalMessage.content;
|
|
16124
16423
|
}
|
|
16125
16424
|
const userMessage = {
|
|
16126
|
-
id:
|
|
16425
|
+
id: uuidv413(),
|
|
16127
16426
|
role: "user",
|
|
16128
16427
|
content: userMessageContent,
|
|
16129
16428
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16130
16429
|
};
|
|
16131
16430
|
const pendingAssistantMessage = {
|
|
16132
|
-
id:
|
|
16431
|
+
id: uuidv413(),
|
|
16133
16432
|
role: "assistant",
|
|
16134
16433
|
content: "...",
|
|
16135
16434
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16335,13 +16634,13 @@ ${contextResult.mergedContent}` : "";
|
|
|
16335
16634
|
userMessageContent = message;
|
|
16336
16635
|
}
|
|
16337
16636
|
const userMessage = {
|
|
16338
|
-
id:
|
|
16637
|
+
id: uuidv413(),
|
|
16339
16638
|
role: "user",
|
|
16340
16639
|
content: userMessageContent,
|
|
16341
16640
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16342
16641
|
};
|
|
16343
16642
|
const pendingAssistantMessage = {
|
|
16344
|
-
id:
|
|
16643
|
+
id: uuidv413(),
|
|
16345
16644
|
role: "assistant",
|
|
16346
16645
|
content: "...",
|
|
16347
16646
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16419,7 +16718,7 @@ ${contextResult.mergedContent}` : "";
|
|
|
16419
16718
|
const currentSession = useCliStore.getState().session;
|
|
16420
16719
|
if (currentSession) {
|
|
16421
16720
|
const cancelMessage = {
|
|
16422
|
-
id:
|
|
16721
|
+
id: uuidv413(),
|
|
16423
16722
|
role: "assistant",
|
|
16424
16723
|
content: "\u26A0\uFE0F Operation cancelled by user",
|
|
16425
16724
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16464,7 +16763,7 @@ ${contextResult.mergedContent}` : "";
|
|
|
16464
16763
|
setState((prev) => ({ ...prev, abortController }));
|
|
16465
16764
|
try {
|
|
16466
16765
|
const pendingAssistantMessage = {
|
|
16467
|
-
id:
|
|
16766
|
+
id: uuidv413(),
|
|
16468
16767
|
role: "assistant",
|
|
16469
16768
|
content: "...",
|
|
16470
16769
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16492,7 +16791,7 @@ ${contextResult.mergedContent}` : "";
|
|
|
16492
16791
|
const currentSession = useCliStore.getState().session;
|
|
16493
16792
|
if (!currentSession) return;
|
|
16494
16793
|
const continuationMessage = {
|
|
16495
|
-
id:
|
|
16794
|
+
id: uuidv413(),
|
|
16496
16795
|
role: "assistant",
|
|
16497
16796
|
content: "---\n\n**Background Agent Results:**\n\n" + result.finalAnswer,
|
|
16498
16797
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16551,13 +16850,13 @@ ${contextResult.mergedContent}` : "";
|
|
|
16551
16850
|
isError = true;
|
|
16552
16851
|
}
|
|
16553
16852
|
const userMessage = {
|
|
16554
|
-
id:
|
|
16853
|
+
id: uuidv413(),
|
|
16555
16854
|
role: "user",
|
|
16556
16855
|
content: `$ ${command}`,
|
|
16557
16856
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16558
16857
|
};
|
|
16559
16858
|
const assistantMessage = {
|
|
16560
|
-
id:
|
|
16859
|
+
id: uuidv413(),
|
|
16561
16860
|
role: "assistant",
|
|
16562
16861
|
content: isError ? `\u274C Error:
|
|
16563
16862
|
${output}` : output.trim() || "(no output)",
|
|
@@ -17026,7 +17325,7 @@ Keyboard Shortcuts:
|
|
|
17026
17325
|
console.clear();
|
|
17027
17326
|
const model = state.session?.model || state.config?.defaultModel || "claude-sonnet";
|
|
17028
17327
|
const newSession = {
|
|
17029
|
-
id:
|
|
17328
|
+
id: uuidv413(),
|
|
17030
17329
|
name: `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
17031
17330
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17032
17331
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -17497,86 +17796,6 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
17497
17796
|
}
|
|
17498
17797
|
break;
|
|
17499
17798
|
}
|
|
17500
|
-
// --- Sandbox commands ---
|
|
17501
|
-
case "sandbox": {
|
|
17502
|
-
if (!state.sandboxOrchestrator) {
|
|
17503
|
-
console.log("\nSandbox: Not initialized");
|
|
17504
|
-
break;
|
|
17505
|
-
}
|
|
17506
|
-
const status = state.sandboxOrchestrator.getStatus();
|
|
17507
|
-
console.log("\nSandbox Status:");
|
|
17508
|
-
console.log(` Enabled: ${status.enabled ? "Yes" : "No"}`);
|
|
17509
|
-
console.log(` Mode: ${status.mode}`);
|
|
17510
|
-
console.log(` Platform: ${status.platform ?? "N/A"}`);
|
|
17511
|
-
console.log(` Runtime: ${status.runtimeName ?? "N/A"}`);
|
|
17512
|
-
console.log(` Available: ${status.runtimeAvailable ? "Yes" : "No"}`);
|
|
17513
|
-
console.log("");
|
|
17514
|
-
console.log("Filesystem Config:");
|
|
17515
|
-
console.log(` Write only to CWD: ${status.config.filesystem.writeOnlyToWorkingDir}`);
|
|
17516
|
-
console.log(` Allowed reads: ${status.config.filesystem.allowedReadPaths.join(", ") || "(none)"}`);
|
|
17517
|
-
console.log(` Denied paths: ${status.config.filesystem.deniedPaths.join(", ") || "(none)"}`);
|
|
17518
|
-
console.log(` Excluded cmds: ${status.config.excludedCommands.join(", ") || "(none)"}`);
|
|
17519
|
-
console.log("");
|
|
17520
|
-
break;
|
|
17521
|
-
}
|
|
17522
|
-
case "sandbox:enable": {
|
|
17523
|
-
if (!state.sandboxOrchestrator) {
|
|
17524
|
-
console.log("Sandbox not initialized");
|
|
17525
|
-
break;
|
|
17526
|
-
}
|
|
17527
|
-
if (!state.sandboxOrchestrator.isAvailable()) {
|
|
17528
|
-
console.log("Sandbox runtime not available on this platform");
|
|
17529
|
-
break;
|
|
17530
|
-
}
|
|
17531
|
-
state.sandboxOrchestrator.setMode("auto-allow");
|
|
17532
|
-
state.permissionManager?.setSandboxState("auto-allow", true);
|
|
17533
|
-
const config = await state.configStore.get();
|
|
17534
|
-
await state.configStore.save({
|
|
17535
|
-
...config,
|
|
17536
|
-
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17537
|
-
});
|
|
17538
|
-
console.log("Sandbox enabled (auto-allow mode)");
|
|
17539
|
-
break;
|
|
17540
|
-
}
|
|
17541
|
-
case "sandbox:disable": {
|
|
17542
|
-
if (!state.sandboxOrchestrator) {
|
|
17543
|
-
console.log("Sandbox not initialized");
|
|
17544
|
-
break;
|
|
17545
|
-
}
|
|
17546
|
-
state.sandboxOrchestrator.setMode("disabled");
|
|
17547
|
-
state.permissionManager?.setSandboxState("disabled", false);
|
|
17548
|
-
const disableConfig = await state.configStore.get();
|
|
17549
|
-
await state.configStore.save({
|
|
17550
|
-
...disableConfig,
|
|
17551
|
-
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17552
|
-
});
|
|
17553
|
-
console.log("Sandbox disabled");
|
|
17554
|
-
break;
|
|
17555
|
-
}
|
|
17556
|
-
case "sandbox:mode": {
|
|
17557
|
-
if (!state.sandboxOrchestrator) {
|
|
17558
|
-
console.log("Sandbox not initialized");
|
|
17559
|
-
break;
|
|
17560
|
-
}
|
|
17561
|
-
const modeArg = args[0];
|
|
17562
|
-
if (modeArg !== "auto-allow" && modeArg !== "permissions") {
|
|
17563
|
-
console.log("Usage: /sandbox:mode <auto-allow|permissions>");
|
|
17564
|
-
break;
|
|
17565
|
-
}
|
|
17566
|
-
if (!state.sandboxOrchestrator.isAvailable()) {
|
|
17567
|
-
console.log("Sandbox runtime not available on this platform");
|
|
17568
|
-
break;
|
|
17569
|
-
}
|
|
17570
|
-
state.sandboxOrchestrator.setMode(modeArg);
|
|
17571
|
-
state.permissionManager?.setSandboxState(modeArg, state.sandboxOrchestrator.isActive());
|
|
17572
|
-
const modeConfig = await state.configStore.get();
|
|
17573
|
-
await state.configStore.save({
|
|
17574
|
-
...modeConfig,
|
|
17575
|
-
sandbox: { ...state.sandboxOrchestrator.getConfig() }
|
|
17576
|
-
});
|
|
17577
|
-
console.log(`Sandbox mode set to: ${modeArg}`);
|
|
17578
|
-
break;
|
|
17579
|
-
}
|
|
17580
17799
|
default:
|
|
17581
17800
|
console.log(`Unknown command: /${command}`);
|
|
17582
17801
|
console.log("Type /help for available commands");
|
|
@@ -17601,9 +17820,9 @@ No usage data available for the last ${USAGE_DAYS} days.`);
|
|
|
17601
17820
|
return { ...prev, config: updatedConfig };
|
|
17602
17821
|
});
|
|
17603
17822
|
if (modelChanged && state.agent) {
|
|
17604
|
-
const
|
|
17605
|
-
if (
|
|
17606
|
-
|
|
17823
|
+
const backend = state.agent.context.llm;
|
|
17824
|
+
if (backend) {
|
|
17825
|
+
backend.currentModel = updatedConfig.defaultModel;
|
|
17607
17826
|
}
|
|
17608
17827
|
}
|
|
17609
17828
|
};
|