@bike4mind/cli 0.2.71-fix-opti-hashi-data-lake-explorer.22040 → 0.2.71

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.
@@ -982,7 +982,8 @@ const SreAgentConfigSchema = z.object({
982
982
  enabled: true,
983
983
  minConfidence: 80
984
984
  }),
985
- dryRun: z.boolean().default(false)
985
+ dryRun: z.boolean().default(false),
986
+ maxRevisions: z.number().min(0).max(5).default(2)
986
987
  });
987
988
  /**
988
989
  * Configuration schema for the SecOps Triage feature.
@@ -1568,7 +1569,8 @@ const KeepCommandType = z.enum([
1568
1569
  "jupyter_start_kernel",
1569
1570
  "jupyter_execute_cell",
1570
1571
  "jupyter_stop_kernel",
1571
- "jupyter_get_kernelspecs"
1572
+ "jupyter_get_kernelspecs",
1573
+ "jupyter_execute_notebook"
1572
1574
  ]);
1573
1575
  /** Web HUD → Server: request a command be sent to the Keep (CLI) */
1574
1576
  const KeepCommandRequestAction = z.object({
@@ -1740,7 +1742,8 @@ const SceneCommandSchema = z.discriminatedUnion("type", [
1740
1742
  col: z.number(),
1741
1743
  row: z.number(),
1742
1744
  gid: z.number()
1743
- }))
1745
+ })),
1746
+ worldVersion: z.number().optional()
1744
1747
  })
1745
1748
  ]);
1746
1749
  /** Client → Server: send scene commands to broadcast to all HUD clients */
@@ -3051,6 +3054,11 @@ const CONTEXT_TELEMETRY_VALIDATION_LIMITS = {
3051
3054
  max: 18e4,
3052
3055
  default: 6e4
3053
3056
  },
3057
+ llmAnalysisThreshold: {
3058
+ min: 0,
3059
+ max: 100,
3060
+ default: 30
3061
+ },
3054
3062
  alertThreshold: {
3055
3063
  min: 0,
3056
3064
  max: 100,
@@ -3081,6 +3089,31 @@ const CONTEXT_TELEMETRY_VALIDATION_LIMITS = {
3081
3089
  max: 168,
3082
3090
  default: 24
3083
3091
  },
3092
+ baselineWindowDays: {
3093
+ min: 3,
3094
+ max: 30,
3095
+ default: 7
3096
+ },
3097
+ sloResponseTimeP95Ms: {
3098
+ min: 500,
3099
+ max: 3e5,
3100
+ default: 6e4
3101
+ },
3102
+ sloFirstTokenTimeMs: {
3103
+ min: 500,
3104
+ max: 6e4,
3105
+ default: 5e3
3106
+ },
3107
+ sloErrorRatePercent: {
3108
+ min: 0,
3109
+ max: 100,
3110
+ default: 2
3111
+ },
3112
+ sloContextUtilizationPercent: {
3113
+ min: 50,
3114
+ max: 100,
3115
+ default: 85
3116
+ },
3084
3117
  maxIssuesPerHour: {
3085
3118
  min: 1,
3086
3119
  max: 200,
@@ -3103,6 +3136,7 @@ const ContextTelemetryAlertsSchema = z.object({
3103
3136
  temperature: z.number().min(CT.temperature.min).max(CT.temperature.max).default(CT.temperature.default),
3104
3137
  maxTokens: z.number().min(CT.maxTokens.min).max(CT.maxTokens.max).default(CT.maxTokens.default),
3105
3138
  timeoutMs: z.number().min(CT.timeoutMs.min).max(CT.timeoutMs.max).default(CT.timeoutMs.default),
3139
+ llmAnalysisThreshold: z.number().min(CT.llmAnalysisThreshold.min).max(CT.llmAnalysisThreshold.max).default(CT.llmAnalysisThreshold.default),
3106
3140
  alertThreshold: z.number().min(CT.alertThreshold.min).max(CT.alertThreshold.max).default(CT.alertThreshold.default),
3107
3141
  criticalThreshold: z.number().min(CT.criticalThreshold.min).max(CT.criticalThreshold.max).default(CT.criticalThreshold.default),
3108
3142
  dedupWindowMinutes: z.number().min(CT.dedupWindowMinutes.min).max(CT.dedupWindowMinutes.max).default(CT.dedupWindowMinutes.default),
@@ -3111,6 +3145,11 @@ const ContextTelemetryAlertsSchema = z.object({
3111
3145
  duplicateAlertCooldownHours: z.number().min(CT.duplicateAlertCooldownHours.min).max(CT.duplicateAlertCooldownHours.max).default(CT.duplicateAlertCooldownHours.default),
3112
3146
  enableLlmPriority: z.boolean().default(false),
3113
3147
  promptTemplate: z.string().min(CT.promptTemplate.min, `Prompt template must be at least ${CT.promptTemplate.min} characters`).max(CT.promptTemplate.max, `Prompt template cannot exceed ${CT.promptTemplate.max.toLocaleString()} characters`).trim().optional(),
3148
+ baselineWindowDays: z.number().min(CT.baselineWindowDays.min).max(CT.baselineWindowDays.max).default(CT.baselineWindowDays.default),
3149
+ sloResponseTimeP95Ms: z.number().min(CT.sloResponseTimeP95Ms.min).max(CT.sloResponseTimeP95Ms.max).default(CT.sloResponseTimeP95Ms.default),
3150
+ sloFirstTokenTimeMs: z.number().min(CT.sloFirstTokenTimeMs.min).max(CT.sloFirstTokenTimeMs.max).default(CT.sloFirstTokenTimeMs.default),
3151
+ sloErrorRatePercent: z.number().min(CT.sloErrorRatePercent.min).max(CT.sloErrorRatePercent.max).default(CT.sloErrorRatePercent.default),
3152
+ sloContextUtilizationPercent: z.number().min(CT.sloContextUtilizationPercent.min).max(CT.sloContextUtilizationPercent.max).default(CT.sloContextUtilizationPercent.default),
3114
3153
  maxIssuesPerHour: z.number().min(CT.maxIssuesPerHour.min).max(CT.maxIssuesPerHour.max).default(CT.maxIssuesPerHour.default),
3115
3154
  dryRun: z.boolean().default(false)
3116
3155
  });
@@ -4921,6 +4960,7 @@ makeStringSetting({
4921
4960
  temperature: CONTEXT_TELEMETRY_VALIDATION_LIMITS.temperature.default,
4922
4961
  maxTokens: CONTEXT_TELEMETRY_VALIDATION_LIMITS.maxTokens.default,
4923
4962
  timeoutMs: CONTEXT_TELEMETRY_VALIDATION_LIMITS.timeoutMs.default,
4963
+ llmAnalysisThreshold: CONTEXT_TELEMETRY_VALIDATION_LIMITS.llmAnalysisThreshold.default,
4924
4964
  alertThreshold: CONTEXT_TELEMETRY_VALIDATION_LIMITS.alertThreshold.default,
4925
4965
  criticalThreshold: CONTEXT_TELEMETRY_VALIDATION_LIMITS.criticalThreshold.default,
4926
4966
  dedupWindowMinutes: CONTEXT_TELEMETRY_VALIDATION_LIMITS.dedupWindowMinutes.default,
@@ -4928,6 +4968,11 @@ makeStringSetting({
4928
4968
  regressionGracePeriodHours: CONTEXT_TELEMETRY_VALIDATION_LIMITS.regressionGracePeriodHours.default,
4929
4969
  duplicateAlertCooldownHours: CONTEXT_TELEMETRY_VALIDATION_LIMITS.duplicateAlertCooldownHours.default,
4930
4970
  enableLlmPriority: false,
4971
+ baselineWindowDays: CONTEXT_TELEMETRY_VALIDATION_LIMITS.baselineWindowDays.default,
4972
+ sloResponseTimeP95Ms: CONTEXT_TELEMETRY_VALIDATION_LIMITS.sloResponseTimeP95Ms.default,
4973
+ sloFirstTokenTimeMs: CONTEXT_TELEMETRY_VALIDATION_LIMITS.sloFirstTokenTimeMs.default,
4974
+ sloErrorRatePercent: CONTEXT_TELEMETRY_VALIDATION_LIMITS.sloErrorRatePercent.default,
4975
+ sloContextUtilizationPercent: CONTEXT_TELEMETRY_VALIDATION_LIMITS.sloContextUtilizationPercent.default,
4931
4976
  maxIssuesPerHour: CONTEXT_TELEMETRY_VALIDATION_LIMITS.maxIssuesPerHour.default,
4932
4977
  dryRun: false
4933
4978
  },
@@ -5192,6 +5237,23 @@ const ALERT_THRESHOLDS = {
5192
5237
  warning: 30,
5193
5238
  critical: 50
5194
5239
  };
5240
+ z.object({
5241
+ avgResponseTimeMs: z.number(),
5242
+ p95ResponseTimeMs: z.number(),
5243
+ avgUtilization: z.number(),
5244
+ utilizationRange: z.object({
5245
+ low: z.number(),
5246
+ high: z.number()
5247
+ }),
5248
+ sampleCount: z.number(),
5249
+ windowDays: z.number()
5250
+ });
5251
+ z.enum([
5252
+ "no_action",
5253
+ "monitor",
5254
+ "investigate_soon",
5255
+ "immediate_action"
5256
+ ]);
5195
5257
  const PromptMetaModelParametersSchema = z.object({
5196
5258
  temperature: z.number().optional(),
5197
5259
  topP: z.number().optional(),
@@ -8091,7 +8153,8 @@ const CliConfigSchema = z.object({
8091
8153
  features: z.object({ tavern: z.boolean().optional() }).optional().prefault({}),
8092
8154
  trustedTools: z.array(z.string()).optional().prefault([]),
8093
8155
  sandbox: SandboxConfigSchema.optional(),
8094
- additionalDirectories: z.array(z.string()).optional().prefault([])
8156
+ additionalDirectories: z.array(z.string()).optional().prefault([]),
8157
+ fallbackModels: z.array(z.string()).optional()
8095
8158
  });
8096
8159
  /**
8097
8160
  * Zod schema for ProjectConfig validation
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-bL3tE6X7.mjs";
2
+ import { i as version, n as fetchLatestVersion, r as forceCheckForUpdate } from "../updateChecker-Cxb7mnxO.mjs";
3
3
  import { execSync } from "child_process";
4
4
  import { constants, existsSync, promises } from "fs";
5
5
  import { homedir } from "os";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { I as isReadOnlyTool, L as ReActAgent, M as setWebSocketToolExecutor, P as buildCoreSystemPrompt, R as CustomCommandStore, S as getApiUrl, T as generateCliTools, V as SessionStore, _ as McpManager, a as createBackgroundAgentTools, c as AgentStore, f as ApiClient, g as ServerLlmBackend, h as WebSocketLlmBackend, i as createWriteTodosTool, l as SubagentOrchestrator, m as WebSocketConnectionManager, n as createFindDefinitionTool, o as BackgroundAgentManager, p as WebSocketToolExecutor, r as createTodoStore, s as createAgentDelegateTool, t as createGetFileStructureTool, u as createSkillTool, w as PermissionManager, x as loadContextFiles, z as CheckpointStore } from "../tools-CcSulyLi.mjs";
3
- import { n as logger, t as ConfigStore } from "../ConfigStore-DH64GYfC.mjs";
2
+ import { B as CustomCommandStore, C as getApiUrl, E as generateCliTools, I as buildCoreSystemPrompt, P as setWebSocketToolExecutor, R as isReadOnlyTool, S as loadContextFiles, T as PermissionManager, U as SessionStore, V as CheckpointStore, _ as ServerLlmBackend, a as createBackgroundAgentTools, c as AgentStore, f as ApiClient, g as WebSocketLlmBackend, h as FallbackLlmBackend, i as createWriteTodosTool, l as SubagentOrchestrator, m as WebSocketConnectionManager, n as createFindDefinitionTool, o as BackgroundAgentManager, p as WebSocketToolExecutor, r as createTodoStore, s as createAgentDelegateTool, t as createGetFileStructureTool, u as createSkillTool, v as McpManager, z as ReActAgent } from "../tools-DmUrRjzs.mjs";
3
+ import { n as logger, t as ConfigStore } from "../ConfigStore-CJrPFsAE.mjs";
4
4
  import { t as DEFAULT_SANDBOX_CONFIG } from "../types-DBEjF9YS.mjs";
5
5
  import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-C1B4t20N.mjs";
6
6
  import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BEW3rqYi.mjs";
@@ -97,6 +97,9 @@ async function handleHeadlessCommand(options) {
97
97
  if (models.length === 0) throw new Error("No models available from server.");
98
98
  const modelInfo = models.find((m) => m.id === config.defaultModel) ?? models[0];
99
99
  llm.currentModel = modelInfo.id;
100
+ const effectiveLlm = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(llm, config.fallbackModels, (fromModel, toModel, error) => {
101
+ process.stderr.write(`⚠️ Model "${fromModel}" failed (${error.message}). Falling back to "${toModel}"...\n`);
102
+ }) : llm;
100
103
  const session = {
101
104
  id: v4(),
102
105
  name: `Headless ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -195,7 +198,7 @@ async function handleHeadlessCommand(options) {
195
198
  const agent = new ReActAgent({
196
199
  userId: config.userId,
197
200
  logger: silentLogger,
198
- llm,
201
+ llm: effectiveLlm,
199
202
  model: modelInfo.id,
200
203
  tools: allTools,
201
204
  maxIterations,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as ConfigStore } from "../ConfigStore-DH64GYfC.mjs";
2
+ import { t as ConfigStore } from "../ConfigStore-CJrPFsAE.mjs";
3
3
  //#region src/commands/mcpCommand.ts
4
4
  /**
5
5
  * External MCP commands (b4m mcp list, b4m mcp add, etc.)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as version, r as forceCheckForUpdate } from "../updateChecker-bL3tE6X7.mjs";
2
+ import { i as version, r as forceCheckForUpdate } from "../updateChecker-Cxb7mnxO.mjs";
3
3
  import { execSync } from "child_process";
4
4
  //#region src/commands/updateCommand.ts
5
5
  /**
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-Dw1nZX2Y.mjs";
3
- import { A as clearFeatureModuleTools, B as CommandHistoryStore, C as getEnvironmentName, D as DEFAULT_AGENT_MODEL, E as ALWAYS_DENIED_FOR_AGENTS, F as buildSkillsPromptSection, G as searchCommands, H as OAuthClient, I as isReadOnlyTool, J as searchFiles, K as mergeCommands, L as ReActAgent, M as setWebSocketToolExecutor, N as OllamaBackend, O as DEFAULT_MAX_ITERATIONS, P as buildCoreSystemPrompt, R as CustomCommandStore, S as getApiUrl, T as generateCliTools, U as hasFileReferences, V as SessionStore, W as processFileReferences, Y as warmFileCache, _ as McpManager, a as createBackgroundAgentTools, b as extractCompactInstructions, c as AgentStore, d as parseAgentConfig, f as ApiClient, g as ServerLlmBackend, h as WebSocketLlmBackend, i as createWriteTodosTool, j as registerFeatureModuleTools, k as DEFAULT_THOROUGHNESS, l as SubagentOrchestrator, m as WebSocketConnectionManager, n as createFindDefinitionTool, o as BackgroundAgentManager, p as WebSocketToolExecutor, q as formatFileSize, r as createTodoStore, s as createAgentDelegateTool, t as createGetFileStructureTool, u as createSkillTool, v as substituteArguments, w as PermissionManager, x as loadContextFiles, y as formatStep, z as CheckpointStore } from "./tools-CcSulyLi.mjs";
4
- import { Dt as validateNotebookPath$1, Et as validateJupyterKernelName, g as ChatModels, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-DH64GYfC.mjs";
5
- import { i as version, t as checkForUpdate } from "./updateChecker-bL3tE6X7.mjs";
3
+ import { A as DEFAULT_RETRY_CONFIG, B as CustomCommandStore, C as getApiUrl, D as ALWAYS_DENIED_FOR_AGENTS, E as generateCliTools, F as OllamaBackend, G as hasFileReferences, H as CommandHistoryStore, I as buildCoreSystemPrompt, J as mergeCommands, K as processFileReferences, L as buildSkillsPromptSection, M as clearFeatureModuleTools, N as registerFeatureModuleTools, O as DEFAULT_AGENT_MODEL, P as setWebSocketToolExecutor, R as isReadOnlyTool, S as loadContextFiles, T as PermissionManager, U as SessionStore, V as CheckpointStore, W as OAuthClient, X as searchFiles, Y as formatFileSize, Z as warmFileCache, _ as ServerLlmBackend, a as createBackgroundAgentTools, b as formatStep, c as AgentStore, d as parseAgentConfig, f as ApiClient, g as WebSocketLlmBackend, h as FallbackLlmBackend, i as createWriteTodosTool, j as DEFAULT_THOROUGHNESS, k as DEFAULT_MAX_ITERATIONS, l as SubagentOrchestrator, m as WebSocketConnectionManager, n as createFindDefinitionTool, o as BackgroundAgentManager, p as WebSocketToolExecutor, q as searchCommands, r as createTodoStore, s as createAgentDelegateTool, t as createGetFileStructureTool, u as createSkillTool, v as McpManager, w as getEnvironmentName, x as extractCompactInstructions, y as substituteArguments, z as ReActAgent } from "./tools-DmUrRjzs.mjs";
4
+ import { Dt as validateNotebookPath$1, Et as validateJupyterKernelName, g as ChatModels, m as CREDIT_DEDUCT_TRANSACTION_TYPES, n as logger, t as ConfigStore } from "./ConfigStore-CJrPFsAE.mjs";
5
+ import { i as version, t as checkForUpdate } from "./updateChecker-Cxb7mnxO.mjs";
6
6
  import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
7
7
  import { Box, Static, Text, render, useApp, useInput } from "ink";
8
8
  import { execSync } from "child_process";
9
- import { randomBytes } from "crypto";
9
+ import { randomBytes, randomUUID } from "crypto";
10
10
  import { existsSync, promises, readFileSync, statSync } from "fs";
11
11
  import { homedir } from "os";
12
12
  import path, { extname } from "path";
@@ -2931,6 +2931,171 @@ function createJupyterClientFromEnv() {
2931
2931
  });
2932
2932
  }
2933
2933
  //#endregion
2934
+ //#region src/commands/executeNotebookHandler.ts
2935
+ /**
2936
+ * Jupyter Notebook Execution Handler
2937
+ *
2938
+ * Handles the jupyter_execute_notebook Keep command.
2939
+ * Orchestrates cell-by-cell execution of a notebook via local Jupyter server.
2940
+ */
2941
+ /**
2942
+ * Zod schema for validating execute notebook parameters.
2943
+ * Replaces unsafe `as` casts with proper runtime validation.
2944
+ */
2945
+ const ExecuteNotebookParams = z.object({
2946
+ notebookJson: z.string().min(1, "notebookJson is required"),
2947
+ sessionId: z.string().min(1, "sessionId is required"),
2948
+ kernelName: z.string().default("python3"),
2949
+ timeoutPerCell: z.number().default(3e4)
2950
+ });
2951
+ /**
2952
+ * Get cell source as string (handles both string and string[] formats).
2953
+ */
2954
+ function getCellSource(cell) {
2955
+ return Array.isArray(cell.source) ? cell.source.join("") : cell.source;
2956
+ }
2957
+ /**
2958
+ * Execute a Jupyter notebook cell by cell.
2959
+ *
2960
+ * @param params - Validated notebook execution parameters
2961
+ * @param deps - Dependencies (jupyter client, websocket manager, logger)
2962
+ * @returns Execution result with success status and executed notebook
2963
+ */
2964
+ async function executeNotebook(params, deps) {
2965
+ const { jupyterClient, wsManager, logger, requestId } = deps;
2966
+ const { notebookJson, sessionId, kernelName, timeoutPerCell } = ExecuteNotebookParams.parse(params);
2967
+ let notebook;
2968
+ try {
2969
+ notebook = JSON.parse(notebookJson);
2970
+ if (!notebook.cells || !Array.isArray(notebook.cells)) throw new Error("Invalid notebook: missing cells array");
2971
+ } catch (parseErr) {
2972
+ throw new Error(`Failed to parse notebook JSON: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
2973
+ }
2974
+ const totalCodeCells = notebook.cells.filter((c) => c.cell_type === "code").length;
2975
+ logger.info(`[Keep] Starting notebook execution: ${totalCodeCells} code cells, kernel: ${kernelName}`);
2976
+ const tempNotebookPath = `/tmp/b4m-notebook-${randomUUID()}.ipynb`;
2977
+ await promises.writeFile(tempNotebookPath, notebookJson, "utf-8");
2978
+ let jupyterSession = null;
2979
+ let cellsExecuted = 0;
2980
+ let cellsFailed = 0;
2981
+ try {
2982
+ logger.info(`[Keep] Starting Jupyter kernel: ${kernelName}`);
2983
+ jupyterSession = await jupyterClient.startSession(tempNotebookPath, kernelName);
2984
+ const kernelId = jupyterSession.kernel.id;
2985
+ const jupyterSessionId = jupyterSession.id;
2986
+ logger.info(`[Keep] Kernel started: session=${jupyterSessionId}, kernel=${kernelId}`);
2987
+ let codeCellIndex = 0;
2988
+ for (let i = 0; i < notebook.cells.length; i++) {
2989
+ const cell = notebook.cells[i];
2990
+ if (cell.cell_type !== "code") continue;
2991
+ const cellCode = getCellSource(cell);
2992
+ if (!cellCode.trim()) {
2993
+ codeCellIndex++;
2994
+ continue;
2995
+ }
2996
+ logger.info(`[Keep] Executing cell ${codeCellIndex + 1}/${totalCodeCells}`);
2997
+ wsManager?.send({
2998
+ action: "jupyter_cell_output",
2999
+ requestId,
3000
+ sessionId,
3001
+ jupyterSessionId,
3002
+ cellIndex: codeCellIndex,
3003
+ outputType: "stream",
3004
+ content: {
3005
+ text: `Executing cell ${codeCellIndex + 1}/${totalCodeCells}...`,
3006
+ name: "stdout"
3007
+ },
3008
+ executionCount: null,
3009
+ isComplete: false
3010
+ });
3011
+ try {
3012
+ const cellResult = await jupyterClient.executeCell(kernelId, cellCode, timeoutPerCell);
3013
+ cell.outputs = cellResult.outputs;
3014
+ cell.execution_count = cellResult.executionCount;
3015
+ if (cellResult.success) {
3016
+ cellsExecuted++;
3017
+ wsManager?.send({
3018
+ action: "jupyter_cell_output",
3019
+ requestId,
3020
+ sessionId,
3021
+ jupyterSessionId,
3022
+ cellIndex: codeCellIndex,
3023
+ outputType: "execute_result",
3024
+ content: {
3025
+ text: cellResult.outputs.map((o) => {
3026
+ if (o.text) return Array.isArray(o.text) ? o.text.join("") : o.text;
3027
+ if (o.data && typeof o.data === "object" && "text/plain" in o.data) {
3028
+ const textPlain = o.data["text/plain"];
3029
+ return Array.isArray(textPlain) ? textPlain.join("") : String(textPlain);
3030
+ }
3031
+ return "";
3032
+ }).join(""),
3033
+ data: cellResult.outputs.find((o) => o.data)?.data
3034
+ },
3035
+ executionCount: cellResult.executionCount,
3036
+ isComplete: true
3037
+ });
3038
+ } else {
3039
+ cellsFailed++;
3040
+ wsManager?.send({
3041
+ action: "jupyter_cell_output",
3042
+ requestId,
3043
+ sessionId,
3044
+ jupyterSessionId,
3045
+ cellIndex: codeCellIndex,
3046
+ outputType: "error",
3047
+ content: {
3048
+ ename: cellResult.error?.ename || "ExecutionError",
3049
+ evalue: cellResult.error?.evalue || "Cell execution failed",
3050
+ traceback: cellResult.error?.traceback || []
3051
+ },
3052
+ executionCount: cellResult.executionCount,
3053
+ isComplete: true
3054
+ });
3055
+ logger.warn(`[Keep] Cell ${codeCellIndex + 1} failed: ${cellResult.error?.ename}: ${cellResult.error?.evalue}`);
3056
+ }
3057
+ } catch (cellErr) {
3058
+ cellsFailed++;
3059
+ const errMsg = cellErr instanceof Error ? cellErr.message : String(cellErr);
3060
+ wsManager?.send({
3061
+ action: "jupyter_cell_output",
3062
+ requestId,
3063
+ sessionId,
3064
+ jupyterSessionId,
3065
+ cellIndex: codeCellIndex,
3066
+ outputType: "error",
3067
+ content: {
3068
+ ename: "ExecutionError",
3069
+ evalue: errMsg,
3070
+ traceback: []
3071
+ },
3072
+ executionCount: null,
3073
+ isComplete: true
3074
+ });
3075
+ logger.error(`[Keep] Cell ${codeCellIndex + 1} threw error: ${errMsg}`);
3076
+ }
3077
+ codeCellIndex++;
3078
+ }
3079
+ return {
3080
+ success: cellsFailed === 0,
3081
+ cellsExecuted,
3082
+ cellsFailed,
3083
+ totalCodeCells,
3084
+ executedNotebook: JSON.stringify(notebook)
3085
+ };
3086
+ } finally {
3087
+ if (jupyterSession) try {
3088
+ logger.info(`[Keep] Stopping Jupyter session: ${jupyterSession.id}`);
3089
+ await jupyterClient.stopSession(jupyterSession.id);
3090
+ } catch (stopErr) {
3091
+ logger.warn(`[Keep] Failed to stop session: ${stopErr instanceof Error ? stopErr.message : String(stopErr)}`);
3092
+ }
3093
+ try {
3094
+ await promises.unlink(tempNotebookPath);
3095
+ } catch {}
3096
+ }
3097
+ }
3098
+ //#endregion
2934
3099
  //#region src/llm/NotifyingLlmBackend.ts
2935
3100
  /**
2936
3101
  * LLM backend wrapper that injects background agent notifications
@@ -3030,7 +3195,8 @@ function createDynamicAgentTool(orchestrator, parentSessionId, backgroundManager
3030
3195
  deniedTools,
3031
3196
  maxIterations: { ...DEFAULT_MAX_ITERATIONS },
3032
3197
  defaultThoroughness: DEFAULT_THOROUGHNESS,
3033
- defaultVariables: params.variables
3198
+ defaultVariables: params.variables,
3199
+ retry: { ...DEFAULT_RETRY_CONFIG }
3034
3200
  };
3035
3201
  const spawnOptions = {
3036
3202
  task: params.task,
@@ -4408,6 +4574,14 @@ function CliApp() {
4408
4574
  };
4409
4575
  break;
4410
4576
  }
4577
+ case "jupyter_execute_notebook":
4578
+ result = await executeNotebook(params, {
4579
+ jupyterClient: getRequiredJupyterClient(),
4580
+ wsManager,
4581
+ logger,
4582
+ requestId
4583
+ });
4584
+ break;
4411
4585
  default: throw new Error(`Unknown command type: ${commandType}`);
4412
4586
  }
4413
4587
  } catch (err) {
@@ -4606,7 +4780,9 @@ function CliApp() {
4606
4780
  const agentDelegateTool = createAgentDelegateTool(orchestrator, agentStore, newSession.id, backgroundManager);
4607
4781
  const dynamicAgentTool = config.preferences.enableDynamicAgentCreation === true ? createDynamicAgentTool(orchestrator, newSession.id, backgroundManager) : null;
4608
4782
  const backgroundTools = createBackgroundAgentTools(backgroundManager);
4609
- const notifyingLlm = new NotifyingLlmBackend(llm, backgroundManager);
4783
+ const notifyingLlm = new NotifyingLlmBackend(config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(llm, config.fallbackModels, (fromModel, toModel, error) => {
4784
+ logger.warn(`⚠️ Model "${fromModel}" failed (${error.message}) — falling back to "${toModel}"`);
4785
+ }) : llm, backgroundManager);
4610
4786
  const writeTodosTool = createWriteTodosTool(createTodoStore());
4611
4787
  const enableSkillTool = config.preferences.enableSkillTool !== false;
4612
4788
  const skillTool = enableSkillTool ? createSkillTool({
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as RegInviteEvents, A as ImageGenerationUsageTransaction, B as OpenAIEmbeddingModel, C as FileEvents, Ct as isGPTImageModel, D as GenericCreditAddTransaction, E as GenerateImageToolCallSchema, F as KnowledgeType, G as ProjectEvents, H as Permission, I as LLMEvents, J as QuestMasterParamsSchema, K as PromptMetaZodSchema, L as MiscEvents, M as InboxEvents, N as InviteEvents, O as GenericCreditDeductTransaction, Ot as CollectionType, P as InviteType, Q as RechartsChartTypeList, R as ModalEvents, S as FeedbackEvents, St as getViewById, T as GEMINI_IMAGE_MODELS, Tt as sanitizeTelemetryError, U as PermissionDeniedError, V as OpenAIImageGenerationInput, W as ProfileEvents, X as RealtimeVoiceUsageTransaction, Y as REASONING_SUPPORTED_MODELS, Z as ReceivedCreditTransaction, _ as CompletionApiUsageTransaction, _t as VoyageAIEmbeddingModel, a as ApiKeyEvents, at as SpeechToTextModels, b as FIXED_TEMPERATURE_MODELS, bt as getDataLakeTags, c as AppFileEvents, ct as TagType, d as BFL_IMAGE_MODELS, dt as ToolUsageTransaction, et as ResearchModeParamsSchema, f as BFL_SAFETY_TOLERANCE, ft as TransferCreditTransaction, g as ChatModels, gt as VideoModels, h as ChatCompletionCreateInputSchema, ht as VideoGenerationUsageTransaction, i as AiEvents, it as SessionEvents, j as ImageModels, k as ImageEditUsageTransaction, l as ArtifactTypeSchema, lt as TaskScheduleHandler, mt as VIDEO_SIZE_CONSTRAINTS, n as logger, nt as ResearchTaskPeriodicFrequencyType, o as ApiKeyScope, ot as SubscriptionCreditTransaction, p as BedrockEmbeddingModel, pt as UiNavigationEvents, q as PurchaseTransaction, r as ALERT_THRESHOLDS, rt as ResearchTaskType, s as ApiKeyType, st as SupportedFabFileMimeTypes, t as ConfigStore, tt as ResearchTaskExecutionType, u as AuthEvents, ut as TextGenerationUsageTransaction, v as DashboardParamsSchema, vt as XAI_IMAGE_MODELS, w as FriendshipEvents, wt as resolveNavigationIntents, x as FavoriteDocumentType, xt as getMcpProviderMetadata, y as ElabsEvents, yt as b4mLLMTools, z as ModelBackend } from "./ConfigStore-DH64GYfC.mjs";
2
+ import { $ as RegInviteEvents, A as ImageGenerationUsageTransaction, B as OpenAIEmbeddingModel, C as FileEvents, Ct as isGPTImageModel, D as GenericCreditAddTransaction, E as GenerateImageToolCallSchema, F as KnowledgeType, G as ProjectEvents, H as Permission, I as LLMEvents, J as QuestMasterParamsSchema, K as PromptMetaZodSchema, L as MiscEvents, M as InboxEvents, N as InviteEvents, O as GenericCreditDeductTransaction, Ot as CollectionType, P as InviteType, Q as RechartsChartTypeList, R as ModalEvents, S as FeedbackEvents, St as getViewById, T as GEMINI_IMAGE_MODELS, Tt as sanitizeTelemetryError, U as PermissionDeniedError, V as OpenAIImageGenerationInput, W as ProfileEvents, X as RealtimeVoiceUsageTransaction, Y as REASONING_SUPPORTED_MODELS, Z as ReceivedCreditTransaction, _ as CompletionApiUsageTransaction, _t as VoyageAIEmbeddingModel, a as ApiKeyEvents, at as SpeechToTextModels, b as FIXED_TEMPERATURE_MODELS, bt as getDataLakeTags, c as AppFileEvents, ct as TagType, d as BFL_IMAGE_MODELS, dt as ToolUsageTransaction, et as ResearchModeParamsSchema, f as BFL_SAFETY_TOLERANCE, ft as TransferCreditTransaction, g as ChatModels, gt as VideoModels, h as ChatCompletionCreateInputSchema, ht as VideoGenerationUsageTransaction, i as AiEvents, it as SessionEvents, j as ImageModels, k as ImageEditUsageTransaction, l as ArtifactTypeSchema, lt as TaskScheduleHandler, mt as VIDEO_SIZE_CONSTRAINTS, n as logger, nt as ResearchTaskPeriodicFrequencyType, o as ApiKeyScope, ot as SubscriptionCreditTransaction, p as BedrockEmbeddingModel, pt as UiNavigationEvents, q as PurchaseTransaction, r as ALERT_THRESHOLDS, rt as ResearchTaskType, s as ApiKeyType, st as SupportedFabFileMimeTypes, t as ConfigStore, tt as ResearchTaskExecutionType, u as AuthEvents, ut as TextGenerationUsageTransaction, v as DashboardParamsSchema, vt as XAI_IMAGE_MODELS, w as FriendshipEvents, wt as resolveNavigationIntents, x as FavoriteDocumentType, xt as getMcpProviderMetadata, y as ElabsEvents, yt as b4mLLMTools, z as ModelBackend } from "./ConfigStore-CJrPFsAE.mjs";
3
3
  import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-CIytuhr3-Dt5dntLx.mjs";
4
4
  import { execFile, execFileSync, spawn } from "child_process";
5
5
  import { createHash, randomBytes } from "crypto";
@@ -16812,6 +16812,13 @@ var HookBlockedError = class extends Error {
16812
16812
  */
16813
16813
  const ALWAYS_DENIED_FOR_AGENTS = ["agent_delegate", "create_dynamic_agent"];
16814
16814
  /**
16815
+ * Default retry configuration for agent execution
16816
+ */
16817
+ const DEFAULT_RETRY_CONFIG = {
16818
+ maxRetries: 2,
16819
+ initialDelayMs: 1e3
16820
+ };
16821
+ /**
16815
16822
  * Schema for a command hook definition
16816
16823
  */
16817
16824
  const CommandHookSchema = z.object({
@@ -16868,7 +16875,11 @@ const AgentFrontmatterSchema = z.object({
16868
16875
  "very_thorough"
16869
16876
  ]).optional(),
16870
16877
  variables: z.record(z.string(), z.string()).optional(),
16871
- hooks: AgentHooksSchema
16878
+ hooks: AgentHooksSchema,
16879
+ retry: z.object({
16880
+ maxRetries: z.int().nonnegative().optional(),
16881
+ initialDelay: z.number().positive().optional()
16882
+ }).optional()
16872
16883
  });
16873
16884
  /**
16874
16885
  * Default iteration limits for agents
@@ -18189,16 +18200,16 @@ var StreamLogger = class StreamLogger {
18189
18200
  }
18190
18201
  };
18191
18202
  //#endregion
18192
- //#region src/llm/ServerLlmBackend.ts
18203
+ //#region src/llm/streamAccumulator.ts
18193
18204
  /**
18194
- * Strip <think>...</think> blocks from text
18195
- * Claude's extended thinking should not be shown in final output
18205
+ * Strip <think>...</think> blocks from text.
18206
+ * Claude's extended thinking should not be shown in final output.
18196
18207
  */
18197
- function stripThinkingBlocks$1(text) {
18208
+ function stripThinkingBlocks(text) {
18198
18209
  return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
18199
18210
  }
18200
18211
  /**
18201
- * Extract usage and credit information from SSE event
18212
+ * Extract usage and credit information into CompletionInfo shape.
18202
18213
  */
18203
18214
  function extractUsageInfo(parsed) {
18204
18215
  return {
@@ -18209,6 +18220,68 @@ function extractUsageInfo(parsed) {
18209
18220
  };
18210
18221
  }
18211
18222
  /**
18223
+ * Accumulates streaming LLM response chunks (text, tool calls, thinking blocks, usage)
18224
+ * and fires the completion callback once at the end.
18225
+ *
18226
+ * Shared between ServerLlmBackend (SSE) and WebSocketLlmBackend (WebSocket frames)
18227
+ * so accumulation logic lives in exactly one place.
18228
+ */
18229
+ var StreamAccumulator = class {
18230
+ constructor() {
18231
+ this.accumulatedText = "";
18232
+ this.toolsUsed = [];
18233
+ this.thinkingBlocks = [];
18234
+ this.lastUsageInfo = {};
18235
+ }
18236
+ onContent(text, usage, credits) {
18237
+ this.accumulatedText += text;
18238
+ if (usage || credits) this.lastUsageInfo = extractUsageInfo({
18239
+ usage,
18240
+ credits
18241
+ });
18242
+ }
18243
+ onToolUse(text, tools, thinking, usage, credits) {
18244
+ if (text) this.accumulatedText += text;
18245
+ if (tools && tools.length > 0) this.toolsUsed = tools;
18246
+ if (thinking && thinking.length > 0) this.thinkingBlocks = thinking;
18247
+ if (usage || credits) this.lastUsageInfo = extractUsageInfo({
18248
+ usage,
18249
+ credits
18250
+ });
18251
+ }
18252
+ /** True when neither text nor tools have been accumulated (stream produced nothing useful). */
18253
+ isEmpty() {
18254
+ return this.accumulatedText.trim().length === 0 && this.toolsUsed.length === 0;
18255
+ }
18256
+ get accumulatedLength() {
18257
+ return this.accumulatedText.length;
18258
+ }
18259
+ get toolCount() {
18260
+ return this.toolsUsed.length;
18261
+ }
18262
+ /** Raw accumulated text before thinking-block stripping (for debug logging). */
18263
+ get rawText() {
18264
+ return this.accumulatedText;
18265
+ }
18266
+ /**
18267
+ * Calls the completion callback with all accumulated content.
18268
+ * Strips thinking blocks from text before delivering.
18269
+ */
18270
+ async finalize(callback) {
18271
+ const cleanedText = stripThinkingBlocks(this.accumulatedText);
18272
+ if (this.toolsUsed.length > 0) {
18273
+ const info = {
18274
+ toolsUsed: this.toolsUsed,
18275
+ thinking: this.thinkingBlocks.length > 0 ? this.thinkingBlocks : void 0,
18276
+ ...this.lastUsageInfo
18277
+ };
18278
+ await callback([cleanedText], info);
18279
+ } else if (cleanedText) await callback([cleanedText], this.lastUsageInfo);
18280
+ }
18281
+ };
18282
+ //#endregion
18283
+ //#region src/llm/ServerLlmBackend.ts
18284
+ /**
18212
18285
  * Server-side LLM backend that proxies requests through Bike4Mind API
18213
18286
  * Uses Server-Sent Events (SSE) for streaming responses
18214
18287
  * API keys remain secure on server - never exposed to CLI
@@ -18311,10 +18384,7 @@ var ServerLlmBackend = class ServerLlmBackend {
18311
18384
  const streamLogger = new StreamLogger(logger, "ServerLlmBackend", process.env.B4M_VERBOSE === "1", process.env.B4M_DEBUG_STREAM === "1");
18312
18385
  streamLogger.streamStart();
18313
18386
  let eventCount = 0;
18314
- let accumulatedText = "";
18315
- let lastUsageInfo = {};
18316
- let toolsUsed = [];
18317
- let thinkingBlocks = [];
18387
+ const accumulator = new StreamAccumulator();
18318
18388
  let receivedDone = false;
18319
18389
  const parser = createParser({ onEvent: (event) => {
18320
18390
  eventCount++;
@@ -18322,28 +18392,15 @@ var ServerLlmBackend = class ServerLlmBackend {
18322
18392
  const data = event.data;
18323
18393
  if (data === "[DONE]") {
18324
18394
  receivedDone = true;
18325
- streamLogger.onCriticalEvent(eventCount, "[DONE]", `accumulated text length: ${accumulatedText.length}`);
18326
- const cleanedText = stripThinkingBlocks$1(accumulatedText);
18327
- streamLogger.streamComplete(accumulatedText);
18328
- if (toolsUsed.length > 0) {
18329
- const info = {
18330
- toolsUsed,
18331
- thinking: thinkingBlocks.length > 0 ? thinkingBlocks : void 0,
18332
- ...lastUsageInfo
18333
- };
18334
- logger.debug(`[ServerLlmBackend] Calling callback with tools, thinking blocks: ${thinkingBlocks.length}`);
18335
- callback([cleanedText], info).catch((err) => {
18336
- logger.error("[ServerLlmBackend] Callback error:", err);
18337
- reject(err);
18338
- }).then(() => {
18339
- logger.debug("[ServerLlmBackend] Callback completed, resolving");
18340
- resolve();
18341
- });
18342
- } else if (cleanedText) callback([cleanedText], lastUsageInfo).catch((err) => {
18395
+ streamLogger.onCriticalEvent(eventCount, "[DONE]", `accumulated text length: ${accumulator.accumulatedLength}`);
18396
+ streamLogger.streamComplete(accumulator.rawText);
18397
+ accumulator.finalize(callback).catch((err) => {
18343
18398
  logger.error("[ServerLlmBackend] Callback error:", err);
18344
18399
  reject(err);
18345
- }).then(() => resolve());
18346
- else resolve();
18400
+ }).then(() => {
18401
+ logger.debug("[ServerLlmBackend] Callback completed, resolving");
18402
+ resolve();
18403
+ });
18347
18404
  return;
18348
18405
  }
18349
18406
  try {
@@ -18355,9 +18412,8 @@ var ServerLlmBackend = class ServerLlmBackend {
18355
18412
  }
18356
18413
  if (parsed.type === "content") {
18357
18414
  const textChunk = parsed.text || "";
18358
- accumulatedText += textChunk;
18359
- if (parsed.usage || parsed.credits) lastUsageInfo = extractUsageInfo(parsed);
18360
- streamLogger.onContent(eventCount, textChunk, accumulatedText);
18415
+ accumulator.onContent(textChunk, parsed.usage, parsed.credits);
18416
+ streamLogger.onContent(eventCount, textChunk, accumulator.rawText);
18361
18417
  } else if (parsed.type === "tool_use") {
18362
18418
  streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${parsed.tools?.length}`);
18363
18419
  if (parsed.tools && parsed.tools.length > 0) for (const tool of parsed.tools) {
@@ -18369,14 +18425,8 @@ var ServerLlmBackend = class ServerLlmBackend {
18369
18425
  logger.debug(` Params: [Unable to stringify]`);
18370
18426
  }
18371
18427
  }
18372
- const textChunk = parsed.text || "";
18373
- if (textChunk) accumulatedText += textChunk;
18374
- if (parsed.tools && parsed.tools.length > 0) toolsUsed = parsed.tools;
18375
- if (parsed.thinking && parsed.thinking.length > 0) {
18376
- thinkingBlocks = parsed.thinking;
18377
- streamLogger.onCriticalEvent(eventCount, "THINKING", `${thinkingBlocks.length} thinking blocks`);
18378
- }
18379
- if (parsed.usage || parsed.credits) lastUsageInfo = extractUsageInfo(parsed);
18428
+ accumulator.onToolUse(parsed.text || "", parsed.tools, parsed.thinking, parsed.usage, parsed.credits);
18429
+ if (parsed.thinking && parsed.thinking.length > 0) streamLogger.onCriticalEvent(eventCount, "THINKING", `${parsed.thinking.length} thinking blocks`);
18380
18430
  }
18381
18431
  } catch (parseError) {
18382
18432
  streamLogger.streamError(parseError);
@@ -18403,20 +18453,10 @@ var ServerLlmBackend = class ServerLlmBackend {
18403
18453
  });
18404
18454
  response.data.on("end", () => {
18405
18455
  if (!receivedDone) {
18406
- const hasAccumulatedData = accumulatedText.trim().length > 0 || toolsUsed.length > 0;
18407
- logger.warn(`[ServerLlmBackend] Stream ended without [DONE] signal. Accumulated text: ${accumulatedText.length} chars, tools: ${toolsUsed.length}`);
18408
- if (hasAccumulatedData) {
18409
- const cleanedText = stripThinkingBlocks$1(accumulatedText);
18410
- streamLogger.streamComplete(accumulatedText);
18411
- if (toolsUsed.length > 0) {
18412
- const info = {
18413
- toolsUsed,
18414
- thinking: thinkingBlocks.length > 0 ? thinkingBlocks : void 0,
18415
- ...lastUsageInfo
18416
- };
18417
- callback([cleanedText], info).then(() => resolve(), reject);
18418
- } else if (cleanedText) callback([cleanedText], lastUsageInfo).then(() => resolve(), reject);
18419
- else resolve();
18456
+ logger.warn(`[ServerLlmBackend] Stream ended without [DONE] signal. Accumulated text: ${accumulator.accumulatedLength} chars, tools: ${accumulator.toolCount}`);
18457
+ if (!accumulator.isEmpty()) {
18458
+ streamLogger.streamComplete(accumulator.rawText);
18459
+ accumulator.finalize(callback).then(() => resolve(), reject);
18420
18460
  } else reject(/* @__PURE__ */ new Error("Stream ended prematurely without receiving any data. The server may be experiencing issues."));
18421
18461
  } else logger.debug("[ServerLlmBackend] Stream ended, [DONE] handler will resolve");
18422
18462
  });
@@ -18537,9 +18577,6 @@ var ServerLlmBackend = class ServerLlmBackend {
18537
18577
  };
18538
18578
  //#endregion
18539
18579
  //#region src/llm/WebSocketLlmBackend.ts
18540
- function stripThinkingBlocks(text) {
18541
- return text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
18542
- }
18543
18580
  /**
18544
18581
  * Hybrid HTTP + WebSocket LLM backend for CLI completions.
18545
18582
  *
@@ -18573,10 +18610,7 @@ var WebSocketLlmBackend = class {
18573
18610
  const streamLogger = new StreamLogger(logger, "WebSocketLlmBackend", process.env.B4M_VERBOSE === "1", process.env.B4M_DEBUG_STREAM === "1");
18574
18611
  streamLogger.streamStart();
18575
18612
  let eventCount = 0;
18576
- let accumulatedText = "";
18577
- let lastUsageInfo = {};
18578
- let toolsUsed = [];
18579
- let thinkingBlocks = [];
18613
+ const accumulator = new StreamAccumulator();
18580
18614
  let settled = false;
18581
18615
  const settle = (action) => {
18582
18616
  if (settled) return;
@@ -18604,12 +18638,6 @@ var WebSocketLlmBackend = class {
18604
18638
  }
18605
18639
  options.abortSignal.addEventListener("abort", abortHandler, { once: true });
18606
18640
  }
18607
- const updateUsage = (usage) => {
18608
- if (usage) lastUsageInfo = {
18609
- inputTokens: usage.inputTokens,
18610
- outputTokens: usage.outputTokens
18611
- };
18612
- };
18613
18641
  this.wsManager.onRequest(requestId, (message) => {
18614
18642
  if (options.abortSignal?.aborted) return;
18615
18643
  const action = message.action;
@@ -18617,28 +18645,20 @@ var WebSocketLlmBackend = class {
18617
18645
  eventCount++;
18618
18646
  const chunk = message.chunk;
18619
18647
  streamLogger.onEvent(eventCount, JSON.stringify(chunk));
18620
- const textChunk = chunk.text || "";
18621
- if (textChunk) accumulatedText += textChunk;
18622
- updateUsage(chunk.usage);
18623
- if (chunk.type === "content") streamLogger.onContent(eventCount, textChunk, accumulatedText);
18624
- else if (chunk.type === "tool_use") {
18648
+ if (chunk.type === "content") {
18649
+ accumulator.onContent(chunk.text || "", chunk.usage);
18650
+ streamLogger.onContent(eventCount, chunk.text || "", accumulator.rawText);
18651
+ } else if (chunk.type === "tool_use") {
18625
18652
  streamLogger.onCriticalEvent(eventCount, "TOOL_USE", `tools: ${chunk.tools?.length}`);
18626
- if (chunk.tools && chunk.tools.length > 0) toolsUsed = chunk.tools;
18627
- if (chunk.thinking && chunk.thinking.length > 0) thinkingBlocks = chunk.thinking;
18653
+ accumulator.onToolUse(chunk.text || "", chunk.tools, chunk.thinking, chunk.usage);
18628
18654
  }
18629
18655
  } else if (action === "cli_completion_done") {
18630
- streamLogger.streamComplete(accumulatedText);
18631
- const cleanedText = stripThinkingBlocks(accumulatedText);
18632
- if (!cleanedText && toolsUsed.length === 0) {
18656
+ streamLogger.streamComplete(accumulator.rawText);
18657
+ if (accumulator.isEmpty()) {
18633
18658
  settleResolve();
18634
18659
  return;
18635
18660
  }
18636
- const info = {
18637
- ...lastUsageInfo,
18638
- ...toolsUsed.length > 0 && { toolsUsed },
18639
- ...thinkingBlocks.length > 0 && { thinking: thinkingBlocks }
18640
- };
18641
- callback([cleanedText], info).then(() => settleResolve()).catch((err) => settleReject(err));
18661
+ accumulator.finalize(callback).then(() => settleResolve()).catch((err) => settleReject(err));
18642
18662
  } else if (action === "cli_completion_error") {
18643
18663
  const errorMsg = message.error || "Server error";
18644
18664
  streamLogger.onCriticalEvent(eventCount, "ERROR", errorMsg);
@@ -18739,6 +18759,65 @@ var WebSocketLlmBackend = class {
18739
18759
  }
18740
18760
  };
18741
18761
  //#endregion
18762
+ //#region src/llm/FallbackLlmBackend.ts
18763
+ /**
18764
+ * LLM backend decorator that provides model-level fallback routing.
18765
+ *
18766
+ * When the primary model fails (after the inner backend's own retries are exhausted),
18767
+ * FallbackLlmBackend tries the next model in the configured fallback chain.
18768
+ *
18769
+ * Example chain: Opus → Sonnet → Haiku (graceful degradation under rate limits)
18770
+ *
18771
+ * Configured via `CliConfig.fallbackModels`. Wraps any `ICompletionBackend`,
18772
+ * fitting cleanly into the existing decorator pattern (NotifyingLlmBackend, etc.).
18773
+ */
18774
+ var FallbackLlmBackend = class {
18775
+ constructor(inner, fallbackModels, onFallback) {
18776
+ this.inner = inner;
18777
+ this.fallbackModels = fallbackModels;
18778
+ this.onFallback = onFallback;
18779
+ }
18780
+ get currentModel() {
18781
+ return this.inner.currentModel;
18782
+ }
18783
+ set currentModel(model) {
18784
+ this.inner.currentModel = model;
18785
+ }
18786
+ async complete(model, messages, options, callback) {
18787
+ if (options.abortSignal?.aborted) return this.inner.complete(model, messages, options, callback);
18788
+ const modelsToTry = [model, ...this.fallbackModels.filter((m) => m !== model)];
18789
+ let callbackInvoked = false;
18790
+ const guardedCallback = async (text, completionInfo) => {
18791
+ callbackInvoked = true;
18792
+ return callback(text, completionInfo);
18793
+ };
18794
+ let lastError;
18795
+ for (let i = 0; i < modelsToTry.length; i++) {
18796
+ const modelToTry = modelsToTry[i];
18797
+ try {
18798
+ await this.inner.complete(modelToTry, messages, options, guardedCallback);
18799
+ return;
18800
+ } catch (error) {
18801
+ if (options.abortSignal?.aborted) throw error;
18802
+ if (callbackInvoked) throw error;
18803
+ lastError = error instanceof Error ? error : new Error(String(error));
18804
+ const nextModel = modelsToTry[i + 1];
18805
+ if (nextModel) {
18806
+ logger.warn(`[FallbackLlmBackend] Model "${modelToTry}" failed: ${lastError.message}`);
18807
+ this.onFallback(modelToTry, nextModel, lastError);
18808
+ }
18809
+ }
18810
+ }
18811
+ throw lastError ?? /* @__PURE__ */ new Error("All fallback models exhausted");
18812
+ }
18813
+ pushToolMessages(messages, tool, result, thinkingBlocks) {
18814
+ this.inner.pushToolMessages(messages, tool, result, thinkingBlocks);
18815
+ }
18816
+ async getModelInfo() {
18817
+ return this.inner.getModelInfo();
18818
+ }
18819
+ };
18820
+ //#endregion
18742
18821
  //#region src/ws/WebSocketConnectionManager.ts
18743
18822
  const useWsPolyfill = typeof globalThis.WebSocket === "undefined";
18744
18823
  const WS = useWsPolyfill ? WsWebSocket : globalThis.WebSocket;
@@ -19423,7 +19502,7 @@ var SubagentOrchestrator = class {
19423
19502
  * @returns Agent result with summary
19424
19503
  */
19425
19504
  async delegateToAgent(options) {
19426
- const { task, agentName, thoroughness, variables, parentSessionId, model, allowedTools } = options;
19505
+ const { task, agentName, thoroughness, variables, parentSessionId, model, allowedTools, abortSignal } = options;
19427
19506
  let agentDef;
19428
19507
  if (options.agentDefinition) agentDef = {
19429
19508
  ...options.agentDefinition,
@@ -19498,11 +19577,22 @@ var SubagentOrchestrator = class {
19498
19577
  const startTime = Date.now();
19499
19578
  let result;
19500
19579
  try {
19501
- result = await agent.run(task, {
19580
+ const { result: agentResult, attempts } = await withRetry(() => agent.run(task, {
19502
19581
  maxIterations,
19503
19582
  parallelExecution: this.deps.enableParallelToolExecution === true,
19504
19583
  isReadOnlyTool
19584
+ }), {
19585
+ maxRetries: agentDef.retry.maxRetries,
19586
+ initialDelayMs: agentDef.retry.initialDelayMs,
19587
+ isRetryable: isRetryableError,
19588
+ abortSignal,
19589
+ logger: {
19590
+ info: (msg, meta) => this.deps.logger.info(`[${agentName}] ${msg}`, meta),
19591
+ warn: (msg, meta) => this.deps.logger.warn(`[${agentName}] ${msg}`, meta)
19592
+ }
19505
19593
  });
19594
+ if (attempts > 0) this.deps.logger.info(`[${agentName}] Recovered after ${attempts} retry attempt${attempts === 1 ? "" : "s"}`);
19595
+ result = agentResult;
19506
19596
  } catch (error) {
19507
19597
  if (error instanceof HookBlockedError) {
19508
19598
  if (this.afterRunCallback) this.afterRunCallback(agent, agentName);
@@ -19844,7 +19934,11 @@ var AgentStore = class {
19844
19934
  hooks: parsed.hooks,
19845
19935
  source,
19846
19936
  filePath,
19847
- modelResolved: resolution.resolved
19937
+ modelResolved: resolution.resolved,
19938
+ retry: {
19939
+ maxRetries: parsed.retry?.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries,
19940
+ initialDelayMs: parsed.retry?.initialDelay ?? DEFAULT_RETRY_CONFIG.initialDelayMs
19941
+ }
19848
19942
  };
19849
19943
  }
19850
19944
  /**
@@ -20297,7 +20391,10 @@ var BackgroundAgentManager = class {
20297
20391
  this.runningCount++;
20298
20392
  this.updateJob(internal.job.id, { status: "running" });
20299
20393
  const { job, options } = internal;
20300
- internal.promise = this.orchestrator.delegateToAgent(options).then((result) => {
20394
+ internal.promise = this.orchestrator.delegateToAgent({
20395
+ ...options,
20396
+ abortSignal: internal.abortController.signal
20397
+ }).then((result) => {
20301
20398
  this.updateJob(job.id, {
20302
20399
  status: "completed",
20303
20400
  endTime: Date.now(),
@@ -20920,4 +21017,4 @@ function createGetFileStructureTool() {
20920
21017
  };
20921
21018
  }
20922
21019
  //#endregion
20923
- export { clearFeatureModuleTools as A, CommandHistoryStore as B, getEnvironmentName as C, DEFAULT_AGENT_MODEL as D, ALWAYS_DENIED_FOR_AGENTS as E, buildSkillsPromptSection as F, searchCommands as G, OAuthClient as H, isReadOnlyTool as I, searchFiles as J, mergeCommands as K, ReActAgent as L, setWebSocketToolExecutor as M, OllamaBackend as N, DEFAULT_MAX_ITERATIONS as O, buildCoreSystemPrompt as P, CustomCommandStore as R, getApiUrl as S, generateCliTools as T, hasFileReferences as U, SessionStore as V, processFileReferences as W, warmFileCache as Y, McpManager as _, createBackgroundAgentTools as a, extractCompactInstructions as b, AgentStore as c, parseAgentConfig as d, ApiClient as f, ServerLlmBackend as g, WebSocketLlmBackend as h, createWriteTodosTool as i, registerFeatureModuleTools as j, DEFAULT_THOROUGHNESS as k, SubagentOrchestrator as l, WebSocketConnectionManager as m, createFindDefinitionTool as n, BackgroundAgentManager as o, WebSocketToolExecutor as p, formatFileSize$1 as q, createTodoStore as r, createAgentDelegateTool as s, createGetFileStructureTool as t, createSkillTool as u, substituteArguments as v, PermissionManager as w, loadContextFiles as x, formatStep as y, CheckpointStore as z };
21020
+ export { DEFAULT_RETRY_CONFIG as A, CustomCommandStore as B, getApiUrl as C, ALWAYS_DENIED_FOR_AGENTS as D, generateCliTools as E, OllamaBackend as F, hasFileReferences as G, CommandHistoryStore as H, buildCoreSystemPrompt as I, mergeCommands as J, processFileReferences as K, buildSkillsPromptSection as L, clearFeatureModuleTools as M, registerFeatureModuleTools as N, DEFAULT_AGENT_MODEL as O, setWebSocketToolExecutor as P, isReadOnlyTool as R, loadContextFiles as S, PermissionManager as T, SessionStore as U, CheckpointStore as V, OAuthClient as W, searchFiles as X, formatFileSize$1 as Y, warmFileCache as Z, ServerLlmBackend as _, createBackgroundAgentTools as a, formatStep as b, AgentStore as c, parseAgentConfig as d, ApiClient as f, WebSocketLlmBackend as g, FallbackLlmBackend as h, createWriteTodosTool as i, DEFAULT_THOROUGHNESS as j, DEFAULT_MAX_ITERATIONS as k, SubagentOrchestrator as l, WebSocketConnectionManager as m, createFindDefinitionTool as n, BackgroundAgentManager as o, WebSocketToolExecutor as p, searchCommands as q, createTodoStore as r, createAgentDelegateTool as s, createGetFileStructureTool as t, createSkillTool as u, McpManager as v, getEnvironmentName as w, extractCompactInstructions as x, substituteArguments as y, ReActAgent as z };
@@ -4,7 +4,7 @@ import { homedir } from "os";
4
4
  import path from "path";
5
5
  import axios from "axios";
6
6
  //#region package.json
7
- var version = "0.2.71-fix-opti-hashi-data-lake-explorer.22040+12bd59509";
7
+ var version = "0.2.71";
8
8
  //#endregion
9
9
  //#region src/utils/updateChecker.ts
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bike4mind/cli",
3
- "version": "0.2.71-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
3
+ "version": "0.2.71",
4
4
  "type": "module",
5
5
  "description": "Interactive CLI tool for Bike4Mind with ReAct agents",
6
6
  "license": "UNLICENSED",
@@ -115,11 +115,11 @@
115
115
  "zustand": "^4.5.4"
116
116
  },
117
117
  "devDependencies": {
118
- "@bike4mind/agents": "0.4.8-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
119
- "@bike4mind/common": "2.78.1-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
120
- "@bike4mind/mcp": "1.33.27-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
121
- "@bike4mind/services": "2.71.2-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
122
- "@bike4mind/utils": "2.16.9-fix-opti-hashi-data-lake-explorer.22040+12bd59509",
118
+ "@bike4mind/agents": "0.4.8",
119
+ "@bike4mind/common": "2.79.0",
120
+ "@bike4mind/mcp": "1.33.27",
121
+ "@bike4mind/services": "2.72.0",
122
+ "@bike4mind/utils": "2.16.9",
123
123
  "@types/better-sqlite3": "^7.6.13",
124
124
  "@types/jsonwebtoken": "^9.0.4",
125
125
  "@types/node": "^22.9.0",
@@ -136,5 +136,5 @@
136
136
  "optionalDependencies": {
137
137
  "@vscode/ripgrep": "^1.17.1"
138
138
  },
139
- "gitHead": "12bd59509be441d15195d7abbb0079298356065d"
139
+ "gitHead": "1d7b05a3d32a0bda3e9b32aab784a26775eab784"
140
140
  }