@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/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-BCOGDPUB.js";
8
+ } from "./chunk-QQL3OBKL.js";
10
9
  import {
11
10
  ConfigStore,
12
11
  logger
13
- } from "./chunk-JWEG53NG.js";
12
+ } from "./chunk-LBTTUQJM.js";
14
13
  import {
15
14
  selectActiveBackgroundAgents,
16
15
  useCliStore
17
16
  } from "./chunk-TVW4ZESU.js";
18
- import "./chunk-W4TCH4ZT.js";
19
- import "./chunk-PKKJ44C5.js";
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-S7BPPS33.js";
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-5GSDPBTM.js";
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 uuidv411 } from "uuid";
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
- logger.debug(`[ToolRouter] Routing ${toolName} to server`);
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, sandboxOrchestrator) {
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
- let isSandboxed = false;
11742
- let sandboxedArgs = args;
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
- let result2 = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
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
- const sandboxLabel = isSandboxed ? " [sandboxed]" : "";
11824
- preview = `$ ${args.command}${cwd}${timeout}${sandboxLabel}`;
11773
+ preview = `$ ${args.command}${cwd}${timeout}`;
11825
11774
  }
11826
- const response = await showPermissionPrompt(toolName, effectiveArgs, preview);
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
- let result = await executeTool(toolName, effectiveArgs, apiClient, originalFn);
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, sandboxOrchestrator) {
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, options) {
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-sandbox.18843+467f137bc",
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-sandbox.18843+467f137bc",
13803
- "@bike4mind/mcp": "1.29.1-feat-cli-sandbox.18843+467f137bc",
13804
- "@bike4mind/services": "2.48.1-feat-cli-sandbox.18843+467f137bc",
13805
- "@bike4mind/utils": "2.5.1-feat-cli-sandbox.18843+467f137bc",
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: "467f137bcb738020d1665b78bc7d34de7d846df5"
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
- sandboxOrchestrator: null
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: uuidv411(),
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 llm = new ServerLlmBackend({
15776
- apiClient,
15777
- model: config.defaultModel
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: uuidv411(),
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
- sandboxOrchestrator
16031
- // Store sandbox orchestrator for /sandbox commands
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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: uuidv411(),
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 llm = state.agent.context.llm;
17605
- if (llm) {
17606
- llm.currentModel = updatedConfig.defaultModel;
17823
+ const backend = state.agent.context.llm;
17824
+ if (backend) {
17825
+ backend.currentModel = updatedConfig.defaultModel;
17607
17826
  }
17608
17827
  }
17609
17828
  };