@bike4mind/cli 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as RechartsChartTypeList, A as ImageGenerationUsageTransaction, At as secureParameters, B as NotFoundError, C as FileEvents, Ct as getMcpProviderMetadata, D as GenericCreditAddTransaction, Dt as obfuscateApiKey, E as GenerateImageToolCallSchema, Et as isGPTImageModel, F as KnowledgeType, G as ProfileEvents, H as OpenAIImageGenerationInput, I as LLMEvents, J as PurchaseTransaction, K as ProjectEvents, L as MiscEvents, M as InboxEvents, N as InviteEvents, Nt as CollectionType, O as GenericCreditDeductTransaction, Ot as resolveNavigationIntents, P as InviteType, Q as ReceivedCreditTransaction, R as ModalEvents, S as FeedbackEvents, St as getDataLakeTags, T as GEMINI_IMAGE_MODELS, Tt as isGPTImage2Model, U as Permission, V as OpenAIEmbeddingModel, W as PermissionDeniedError, X as REASONING_SUPPORTED_MODELS, Y as QuestMasterParamsSchema, Z as RealtimeVoiceUsageTransaction, _ as CompletionApiUsageTransaction, _t as VideoModels, a as ApiKeyEvents, at as SessionEvents, b as FIXED_TEMPERATURE_MODELS, bt as b4mLLMTools, c as AppFileEvents, ct as SupportedFabFileMimeTypes, d as BFL_IMAGE_MODELS, dt as TextGenerationUsageTransaction, et as RegInviteEvents, f as BFL_SAFETY_TOLERANCE, ft as ToolUsageTransaction, g as ChatModels, gt as VideoGenerationUsageTransaction, h as ChatCompletionCreateInputSchema, ht as VIDEO_SIZE_CONSTRAINTS, i as AiEvents, it as ResearchTaskType, j as ImageModels, k as ImageEditUsageTransaction, kt as sanitizeTelemetryError, l as ArtifactTypeSchema, lt as TagType, mt as UiNavigationEvents, n as logger, nt as ResearchTaskExecutionType, o as ApiKeyScope, ot as SpeechToTextModels, p as BedrockEmbeddingModel, pt as TransferCreditTransaction, q as PromptMetaZodSchema, r as ALERT_THRESHOLDS, rt as ResearchTaskPeriodicFrequencyType, s as ApiKeyType, st as SubscriptionCreditTransaction, t as ConfigStore, tt as ResearchModeParamsSchema, u as AuthEvents, ut as TaskScheduleHandler, v as DashboardParamsSchema, vt as VoyageAIEmbeddingModel, w as FriendshipEvents, wt as getViewById, x as FavoriteDocumentType, xt as getAccessibleDataLakes, y as ElabsEvents, yt as XAI_IMAGE_MODELS, z as ModelBackend } from "./ConfigStore-DBUmvCfe.mjs";
2
+ import { $ as RechartsChartTypeList, A as ImageGenerationUsageTransaction, At as secureParameters, B as NotFoundError, C as FileEvents, Ct as getMcpProviderMetadata, D as GenericCreditAddTransaction, Dt as obfuscateApiKey, E as GenerateImageToolCallSchema, Et as isGPTImageModel, F as KnowledgeType, G as ProfileEvents, H as OpenAIImageGenerationInput, I as LLMEvents, J as PurchaseTransaction, K as ProjectEvents, L as MiscEvents, M as InboxEvents, N as InviteEvents, Nt as CollectionType, O as GenericCreditDeductTransaction, Ot as resolveNavigationIntents, P as InviteType, Q as ReceivedCreditTransaction, R as ModalEvents, S as FeedbackEvents, St as getDataLakeTags, T as GEMINI_IMAGE_MODELS, Tt as isGPTImage2Model, U as Permission, V as OpenAIEmbeddingModel, W as PermissionDeniedError, X as REASONING_SUPPORTED_MODELS, Y as QuestMasterParamsSchema, Z as RealtimeVoiceUsageTransaction, _ as CompletionApiUsageTransaction, _t as VideoModels, a as ApiKeyEvents, at as SessionEvents, b as FIXED_TEMPERATURE_MODELS, bt as b4mLLMTools, c as AppFileEvents, ct as SupportedFabFileMimeTypes, d as BFL_IMAGE_MODELS, dt as TextGenerationUsageTransaction, et as RegInviteEvents, f as BFL_SAFETY_TOLERANCE, ft as ToolUsageTransaction, g as ChatModels, gt as VideoGenerationUsageTransaction, h as ChatCompletionCreateInputSchema, ht as VIDEO_SIZE_CONSTRAINTS, i as AiEvents, it as ResearchTaskType, j as ImageModels, k as ImageEditUsageTransaction, kt as sanitizeTelemetryError, l as ArtifactTypeSchema, lt as TagType, mt as UiNavigationEvents, n as logger, nt as ResearchTaskExecutionType, o as ApiKeyScope, ot as SpeechToTextModels, p as BedrockEmbeddingModel, pt as TransferCreditTransaction, q as PromptMetaZodSchema, r as ALERT_THRESHOLDS, rt as ResearchTaskPeriodicFrequencyType, s as ApiKeyType, st as SubscriptionCreditTransaction, t as ConfigStore, tt as ResearchModeParamsSchema, u as AuthEvents, ut as TaskScheduleHandler, v as DashboardParamsSchema, vt as VoyageAIEmbeddingModel, w as FriendshipEvents, wt as getViewById, x as FavoriteDocumentType, xt as getAccessibleDataLakes, y as ElabsEvents, yt as XAI_IMAGE_MODELS, z as ModelBackend } from "./ConfigStore-Dt6utdSA.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";
@@ -522,6 +522,18 @@ const COMMANDS = [
522
522
  {
523
523
  name: "dirs",
524
524
  description: "List all accessible directories"
525
+ },
526
+ {
527
+ name: "decisions",
528
+ description: "Show decision log for current session"
529
+ },
530
+ {
531
+ name: "blockers",
532
+ description: "Show tracked blockers for current session"
533
+ },
534
+ {
535
+ name: "handoff",
536
+ description: "Show or generate the session handoff for cross-session continuity"
525
537
  }
526
538
  ];
527
539
  /**
@@ -3015,7 +3027,18 @@ EXAMPLES:
3015
3027
  - "what packages installed?" → ${TOOL_GLOB_FILES} "**/package.json" → ${TOOL_FILE_READ}
3016
3028
  - "find all components using React hooks" → ${TOOL_SUBAGENT_DELEGATE}(task="find all components using React hooks", type="explore")
3017
3029
 
3018
- Remember: Use context from previous messages to understand follow-up questions.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}`;
3030
+ Remember: Use context from previous messages to understand follow-up questions.
3031
+
3032
+ DURABLE WORKFLOW TRACKING:
3033
+ You have tools for tracking decisions and blockers during your work. These create an audit trail that persists across sessions, enabling anyone to understand why things were done and what's still outstanding.
3034
+
3035
+ - log_decision: When you make a significant decision (architecture choice, scope narrowing, interpretation of ambiguous requirements, trade-off between alternatives), log it with rationale. Do NOT log trivial decisions. Log decisions that would matter if someone needed to understand WHY you did something or if they needed to resume this work.
3036
+
3037
+ - track_blocker: When you encounter something blocking progress (missing information, unclear requirements, external dependencies, ambiguous specs that need human clarification), track it. This makes blockers visible so they can be addressed.
3038
+
3039
+ - resolve_blocker: When a blocker is cleared, record how it was resolved. Use the blocker ID from the track_blocker output.
3040
+
3041
+ These tools are lightweight — use them naturally as part of your work, not as a ceremony.${directoriesSection}${projectContextSection}${skillsSection}${featureModulesSection}`;
3019
3042
  }
3020
3043
  /**
3021
3044
  * Build the dynamic agent creation prompt section
@@ -3080,8 +3103,7 @@ var AIImageService = class {
3080
3103
  }
3081
3104
  };
3082
3105
  //#endregion
3083
- //#region ../../b4m-core/utils/dist/index.mjs
3084
- var BaseStorage = class {};
3106
+ //#region ../../b4m-core/observability/dist/index.mjs
3085
3107
  var Logger = class Logger {
3086
3108
  static globalInstance = new Logger();
3087
3109
  metadata = {};
@@ -3285,6 +3307,9 @@ var Logger = class Logger {
3285
3307
  blue: `\x1b[34m${args.join(" ")}\x1b[0m`
3286
3308
  });
3287
3309
  };
3310
+ //#endregion
3311
+ //#region ../../b4m-core/utils/dist/index.mjs
3312
+ var BaseStorage = class {};
3288
3313
  dotenv.config();
3289
3314
  /**
3290
3315
  * Execute an array of async tasks either in parallel (with concurrency limiting)
@@ -4741,6 +4766,8 @@ var OpenAIBackend = class {
4741
4766
  ...options
4742
4767
  };
4743
4768
  const toolCallCount = options._internal?.toolCallCount ?? 0;
4769
+ const accumInputTokens = options._internal?.accumInputTokens ?? 0;
4770
+ const accumOutputTokens = options._internal?.accumOutputTokens ?? 0;
4744
4771
  if (toolCallCount >= 10 && options.tools?.length) {
4745
4772
  this.logger.warn(`⚠️ Max tool calls limit (10) reached. Disabling tools to prevent infinite loops.`);
4746
4773
  await this.complete(model, messages, {
@@ -4923,7 +4950,9 @@ var OpenAIBackend = class {
4923
4950
  tools: anyMcpTool ? options.tools : void 0,
4924
4951
  _internal: {
4925
4952
  ...options._internal,
4926
- toolCallCount: toolCallCount + 1
4953
+ toolCallCount: toolCallCount + 1,
4954
+ accumInputTokens: accumInputTokens + (response.usage?.prompt_tokens || 0),
4955
+ accumOutputTokens: accumOutputTokens + (response.usage?.completion_tokens || 0)
4927
4956
  }
4928
4957
  }, recursiveCallback, toolsUsed);
4929
4958
  if (anyArtifactWasStreamed && recursiveBuffer) {
@@ -4934,8 +4963,8 @@ var OpenAIBackend = class {
4934
4963
  } else {
4935
4964
  this.logger.debug(`[Tool Execution] executeTools=false, passing tool calls to callback`);
4936
4965
  await callback([null], {
4937
- inputTokens: response.usage?.prompt_tokens || 0,
4938
- outputTokens: response.usage?.completion_tokens || 0,
4966
+ inputTokens: accumInputTokens + (response.usage?.prompt_tokens || 0),
4967
+ outputTokens: accumOutputTokens + (response.usage?.completion_tokens || 0),
4939
4968
  toolsUsed: toolsUsed.length > 0 ? toolsUsed : void 0
4940
4969
  });
4941
4970
  return;
@@ -4949,8 +4978,8 @@ var OpenAIBackend = class {
4949
4978
  if (cacheStats) logCacheStats(this.logger, cacheStats, { streaming: false });
4950
4979
  }
4951
4980
  await callback(streamedText, {
4952
- inputTokens: response.usage?.prompt_tokens || 0,
4953
- outputTokens: response.usage?.completion_tokens || 0,
4981
+ inputTokens: accumInputTokens + (response.usage?.prompt_tokens || 0),
4982
+ outputTokens: accumOutputTokens + (response.usage?.completion_tokens || 0),
4954
4983
  toolsUsed: toolsUsed.length > 0 ? toolsUsed : void 0,
4955
4984
  cacheStats
4956
4985
  });
@@ -4989,8 +5018,8 @@ var OpenAIBackend = class {
4989
5018
  if (c.delta.content) streamedText[c.index] = (streamedText[c.index] || "") + c.delta.content;
4990
5019
  });
4991
5020
  await callback(streamedText, {
4992
- inputTokens,
4993
- outputTokens,
5021
+ inputTokens: accumInputTokens + inputTokens,
5022
+ outputTokens: accumOutputTokens + outputTokens,
4994
5023
  toolsUsed: toolsUsed.length > 0 ? toolsUsed : void 0
4995
5024
  });
4996
5025
  }
@@ -5095,8 +5124,8 @@ var OpenAIBackend = class {
5095
5124
  thisToolHadArtifact = true;
5096
5125
  anyArtifactWasStreamed = true;
5097
5126
  await callback(results, {
5098
- inputTokens,
5099
- outputTokens,
5127
+ inputTokens: accumInputTokens + inputTokens,
5128
+ outputTokens: accumOutputTokens + outputTokens,
5100
5129
  toolsUsed: toolsUsed.length > 0 ? toolsUsed : void 0,
5101
5130
  cacheStats
5102
5131
  });
@@ -5119,7 +5148,9 @@ var OpenAIBackend = class {
5119
5148
  tools: anyMcpTool ? options.tools : void 0,
5120
5149
  _internal: {
5121
5150
  ...options._internal,
5122
- toolCallCount: toolCallCount + 1
5151
+ toolCallCount: toolCallCount + 1,
5152
+ accumInputTokens: accumInputTokens + inputTokens,
5153
+ accumOutputTokens: accumOutputTokens + outputTokens
5123
5154
  }
5124
5155
  }, async (results, meta) => {
5125
5156
  for (const r of results) if (r != null) recursiveBuffer += r;
@@ -5132,14 +5163,16 @@ var OpenAIBackend = class {
5132
5163
  tools: anyMcpTool ? options.tools : void 0,
5133
5164
  _internal: {
5134
5165
  ...options._internal,
5135
- toolCallCount: toolCallCount + 1
5166
+ toolCallCount: toolCallCount + 1,
5167
+ accumInputTokens: accumInputTokens + inputTokens,
5168
+ accumOutputTokens: accumOutputTokens + outputTokens
5136
5169
  }
5137
5170
  }, callback, toolsUsed);
5138
5171
  } else {
5139
5172
  this.logger.debug(`[Tool Execution] executeTools=false, passing tool calls to callback`);
5140
5173
  await callback([null], {
5141
- inputTokens,
5142
- outputTokens,
5174
+ inputTokens: accumInputTokens + inputTokens,
5175
+ outputTokens: accumOutputTokens + outputTokens,
5143
5176
  toolsUsed: toolsUsed.length > 0 ? toolsUsed : void 0,
5144
5177
  cacheStats
5145
5178
  });
@@ -6675,15 +6708,26 @@ const webSearchTool = {
6675
6708
  };
6676
6709
  //#endregion
6677
6710
  //#region ../../b4m-core/services/dist/llm/tools/implementation/webfetch/index.mjs
6711
+ const DEFAULT_TIMEOUT_MS = 6e4;
6712
+ const PDF_TIMEOUT_MS = 9e4;
6713
+ function isPdfUrl(url) {
6714
+ try {
6715
+ const { pathname } = new URL(url);
6716
+ return pathname.toLowerCase().endsWith(".pdf");
6717
+ } catch {
6718
+ return false;
6719
+ }
6720
+ }
6678
6721
  /**
6679
6722
  * Fetch URL content using Firecrawl (shared function, no ToolContext)
6680
6723
  * Pattern follows serpApiSearch from websearch tool
6681
6724
  *
6682
6725
  * @param adapters - Database adapters for fetching Firecrawl API key
6683
6726
  * @param url - URL to fetch
6727
+ * @param options - Optional configuration (e.g. maxTimeoutMs for Lambda-constrained callers)
6684
6728
  * @returns Markdown content and title
6685
6729
  */
6686
- async function firecrawlFetch(adapters, url) {
6730
+ async function firecrawlFetch(adapters, url, options) {
6687
6731
  if (!/^https?:\/\/.+/i.test(url)) throw new Error(`Invalid URL format: ${url}. URL must start with http:// or https://`);
6688
6732
  const apiKeySetting = await adapters.db.adminSettings.findBySettingName("FirecrawlApiKey");
6689
6733
  if (!apiKeySetting?.settingValue) {
@@ -6691,45 +6735,45 @@ async function firecrawlFetch(adapters, url) {
6691
6735
  throw new Error("Firecrawl API key not configured");
6692
6736
  }
6693
6737
  const app = new FirecrawlApp({ apiKey: apiKeySetting.settingValue });
6694
- const controller = new AbortController();
6695
- const timeoutId = setTimeout(() => controller.abort(), 6e4);
6696
- try {
6697
- console.log("📥 WebFetch Tool: Scraping URL with Firecrawl...");
6698
- let result;
6699
- try {
6700
- result = await app.scrapeUrl(url, {
6701
- formats: ["markdown"],
6702
- actions: [{
6703
- type: "wait",
6704
- milliseconds: 1e3
6705
- }]
6706
- });
6707
- } catch (scrapeError) {
6708
- if ((scrapeError instanceof Error ? scrapeError.message : "").includes("Actions are not supported")) {
6709
- console.log("📥 WebFetch Tool: Actions not supported, retrying without actions...");
6710
- result = await app.scrapeUrl(url, { formats: ["markdown"] });
6711
- } else throw scrapeError;
6712
- }
6713
- clearTimeout(timeoutId);
6714
- if (!result || result.error) {
6715
- const errorMessage = result?.error || "Unknown error";
6716
- console.error("❌ WebFetch Tool: Firecrawl error:", errorMessage);
6717
- throw new Error(`Failed to fetch content from URL: ${errorMessage}`);
6718
- }
6719
- if (!("markdown" in result) || !result.markdown) {
6720
- console.error("❌ WebFetch Tool: No markdown content returned");
6721
- throw new Error("No content could be extracted from the URL");
6722
- }
6723
- const markdown = result.markdown.slice(0, 5e4);
6724
- console.log(`📄 WebFetch Tool: Extracted ${markdown.length} characters of content`);
6725
- return {
6726
- markdown,
6727
- title: result.metadata?.title
6728
- };
6729
- } catch (error) {
6730
- clearTimeout(timeoutId);
6731
- throw error;
6732
- }
6738
+ const isPdf = isPdfUrl(url);
6739
+ const desiredTimeout = isPdf ? PDF_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
6740
+ const timeoutMs = options?.maxTimeoutMs ? Math.min(desiredTimeout, options.maxTimeoutMs) : desiredTimeout;
6741
+ console.log(`📥 WebFetch Tool: Scraping URL with Firecrawl${isPdf ? " (PDF mode, extended timeout)" : ""}...`);
6742
+ const baseParams = {
6743
+ formats: ["markdown"],
6744
+ timeout: timeoutMs
6745
+ };
6746
+ let result;
6747
+ if (isPdf) result = await app.scrapeUrl(url, baseParams);
6748
+ else try {
6749
+ result = await app.scrapeUrl(url, {
6750
+ ...baseParams,
6751
+ actions: [{
6752
+ type: "wait",
6753
+ milliseconds: 1e3
6754
+ }]
6755
+ });
6756
+ } catch (scrapeError) {
6757
+ if ((scrapeError instanceof Error ? scrapeError.message : "").includes("Actions are not supported")) {
6758
+ console.log("📥 WebFetch Tool: Actions not supported, retrying without actions...");
6759
+ result = await app.scrapeUrl(url, baseParams);
6760
+ } else throw scrapeError;
6761
+ }
6762
+ if (!result || result.error) {
6763
+ const errorMessage = result?.error || "Unknown error";
6764
+ console.error("❌ WebFetch Tool: Firecrawl error:", errorMessage);
6765
+ throw new Error(`Failed to fetch content from URL: ${errorMessage}`);
6766
+ }
6767
+ if (!("markdown" in result) || !result.markdown) {
6768
+ console.error("❌ WebFetch Tool: No markdown content returned");
6769
+ throw new Error("No content could be extracted from the URL");
6770
+ }
6771
+ const markdown = result.markdown.slice(0, 5e4);
6772
+ console.log(`📄 WebFetch Tool: Extracted ${markdown.length} characters of content`);
6773
+ return {
6774
+ markdown,
6775
+ title: result.metadata?.title
6776
+ };
6733
6777
  }
6734
6778
  const webFetchTool = {
6735
6779
  name: "web_fetch",
@@ -6785,6 +6829,14 @@ const webFetchTool = {
6785
6829
  await context.statusUpdate({}, `WebFetch: ${statusLabel}`);
6786
6830
  return userMessage;
6787
6831
  }
6832
+ if (error instanceof FirecrawlError && error.statusCode === 500 && errorMessage.includes("PDF")) {
6833
+ context.logger.warn("WebFetch PDF too large for Firecrawl", {
6834
+ url: params.url,
6835
+ error: errorMessage
6836
+ });
6837
+ await context.statusUpdate({}, "WebFetch: PDF too large");
6838
+ return `This PDF is too large to process via URL. Please download the file and upload it directly instead.`;
6839
+ }
6788
6840
  if ([
6789
6841
  "no response received",
6790
6842
  "econnrefused",
@@ -6821,17 +6873,49 @@ const webFetchTool = {
6821
6873
  })
6822
6874
  };
6823
6875
  //#endregion
6824
- //#region ../../b4m-core/services/dist/jsonSanitize-NGoqEfny.mjs
6876
+ //#region ../../b4m-core/services/dist/jsonSanitize-DPYPbara.mjs
6825
6877
  /**
6826
6878
  * Sanitize a JSON string by escaping control characters inside string literals.
6827
6879
  * LLMs sometimes return JSON with unescaped newlines/tabs inside string values,
6828
6880
  * which causes JSON.parse to fail with "Bad control character in string literal".
6829
6881
  *
6830
6882
  * Covers all U+0000–U+001F control characters per JSON RFC 8259.
6883
+ *
6884
+ * Also applies a lookahead heuristic to escape unescaped double-quotes inside
6885
+ * string values — the most common LLM JSON failure mode (e.g., code snippets
6886
+ * containing logger.error("msg") inside a JSON string).
6887
+ *
6888
+ * Returns the sanitized string and the number of quotes repaired.
6889
+ * If the JSON is already valid, it is returned unchanged (zero repairs).
6831
6890
  */
6832
6891
  function sanitizeJsonString(jsonStr) {
6892
+ const { result } = sanitizeJsonStringWithMeta(jsonStr);
6893
+ return result;
6894
+ }
6895
+ function sanitizeJsonStringWithMeta(jsonStr, options) {
6896
+ try {
6897
+ JSON.parse(jsonStr);
6898
+ return {
6899
+ result: jsonStr,
6900
+ repairedQuotes: 0
6901
+ };
6902
+ } catch {}
6833
6903
  let result = "";
6834
6904
  let inString = false;
6905
+ /**
6906
+ * Tracks whether we are in a key position (true) or value position (false).
6907
+ * Starts as true — the first string in valid JSON must be a key.
6908
+ * Flips to false after a key-closing quote + colon pair.
6909
+ * Flips back to true after a value-closing quote (or structural char) + comma/`{`/`[`.
6910
+ *
6911
+ * This is used to disambiguate `:` after a `"` inside a string:
6912
+ * - In key position: `"` + `:` → structural (closes the key, colon follows)
6913
+ * - In value position: `"` + `:` → embedded (e.g., `"status": it was null"`)
6914
+ */
6915
+ let inKey = true;
6916
+ /** Tracks nested structure context for truncation repair. */
6917
+ const structureStack = [];
6918
+ let repairedQuotes = 0;
6835
6919
  let i = 0;
6836
6920
  while (i < jsonStr.length) {
6837
6921
  const char = jsonStr[i];
@@ -6841,8 +6925,25 @@ function sanitizeJsonString(jsonStr) {
6841
6925
  continue;
6842
6926
  }
6843
6927
  if (char === "\"") {
6844
- inString = !inString;
6845
- result += char;
6928
+ if (!inString) {
6929
+ inString = true;
6930
+ result += char;
6931
+ i++;
6932
+ continue;
6933
+ }
6934
+ let j = i + 1;
6935
+ while (j < jsonStr.length && (jsonStr[j] === " " || jsonStr[j] === " " || jsonStr[j] === "\r" || jsonStr[j] === "\n")) j++;
6936
+ const nextNonWs = j < jsonStr.length ? jsonStr[j] : "";
6937
+ if (nextNonWs === "," || nextNonWs === "}" || nextNonWs === "]" || nextNonWs === "" || nextNonWs === ":" && inKey) {
6938
+ inString = false;
6939
+ result += char;
6940
+ i++;
6941
+ if (nextNonWs === ":") inKey = false;
6942
+ else if (nextNonWs === "," || nextNonWs === "}" || nextNonWs === "]") inKey = true;
6943
+ continue;
6944
+ }
6945
+ result += "\\\"";
6946
+ repairedQuotes++;
6846
6947
  i++;
6847
6948
  continue;
6848
6949
  }
@@ -6869,10 +6970,45 @@ function sanitizeJsonString(jsonStr) {
6869
6970
  break;
6870
6971
  }
6871
6972
  else result += char;
6872
- } else result += char;
6973
+ } else {
6974
+ result += char;
6975
+ if (char === "{") {
6976
+ structureStack.push("{");
6977
+ inKey = true;
6978
+ } else if (char === "[") {
6979
+ structureStack.push("[");
6980
+ inKey = false;
6981
+ } else if (char === "}" || char === "]") {
6982
+ if (structureStack.length > 0) structureStack.pop();
6983
+ inKey = (structureStack.length > 0 ? structureStack[structureStack.length - 1] : null) !== "[";
6984
+ } else if (char === ",") {
6985
+ if ((structureStack.length > 0 ? structureStack[structureStack.length - 1] : null) === "{") inKey = true;
6986
+ }
6987
+ }
6873
6988
  i++;
6874
6989
  }
6875
- return result;
6990
+ if (inString) {
6991
+ if (options?.attemptTruncationRepair && structureStack.length > 0) {
6992
+ const closing = [...structureStack].reverse().map((c) => c === "{" ? "}" : "]").join("");
6993
+ const salvage = result + "\"" + closing;
6994
+ try {
6995
+ JSON.parse(salvage);
6996
+ return {
6997
+ result: salvage,
6998
+ repairedQuotes,
6999
+ truncationRepaired: true
7000
+ };
7001
+ } catch {}
7002
+ }
7003
+ return {
7004
+ result: jsonStr,
7005
+ repairedQuotes: 0
7006
+ };
7007
+ }
7008
+ return {
7009
+ result,
7010
+ repairedQuotes
7011
+ };
6876
7012
  }
6877
7013
  //#endregion
6878
7014
  //#region ../../b4m-core/quantum/dist/index.mjs
@@ -8720,7 +8856,7 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
8720
8856
  "Minimize: sum(error^2)"
8721
8857
  ].join("\n");
8722
8858
  //#endregion
8723
- //#region ../../b4m-core/services/dist/tools-s4H4po60.mjs
8859
+ //#region ../../b4m-core/services/dist/tools-B55E_Q3K.mjs
8724
8860
  async function performDeepResearch(context, params, config) {
8725
8861
  const maxDepth = config.maxDepth || 7;
8726
8862
  const duration = config.duration || 4.5;
@@ -15760,56 +15896,6 @@ z.object({
15760
15896
  embargoDetected: z.boolean().prefault(false),
15761
15897
  suggestedTags: z.array(z.string()).prefault([])
15762
15898
  });
15763
- z.object({
15764
- userId: z.string(),
15765
- sessionId: z.string(),
15766
- questId: z.string(),
15767
- message: z.string().min(1, "Message cannot be empty"),
15768
- messageFileIds: z.array(z.string()),
15769
- historyCount: z.number(),
15770
- fabFileIds: z.array(z.string()),
15771
- params: ChatCompletionCreateInputSchema,
15772
- dashboardParams: DashboardParamsSchema.optional(),
15773
- enableQuestMaster: z.boolean().optional(),
15774
- enableMementos: z.boolean().optional(),
15775
- enableArtifacts: z.boolean().optional(),
15776
- enableAgents: z.boolean().optional(),
15777
- enableLattice: z.boolean().optional(),
15778
- promptMeta: PromptMetaZodSchema,
15779
- tools: z.array(z.union([b4mLLMTools, z.string()])).optional(),
15780
- mcpServers: z.array(z.string()).optional(),
15781
- projectId: z.string().optional(),
15782
- organizationId: z.string().nullable().optional(),
15783
- questMaster: QuestMasterParamsSchema.optional(),
15784
- toolPromptId: z.string().optional(),
15785
- researchMode: ResearchModeParamsSchema.optional(),
15786
- fallbackModel: z.string().optional(),
15787
- embeddingModel: z.string().optional(),
15788
- queryComplexity: z.string(),
15789
- imageConfig: GenerateImageToolCallSchema.optional(),
15790
- deepResearchConfig: z.object({
15791
- maxDepth: z.number().optional(),
15792
- duration: z.number().optional(),
15793
- searchers: z.array(z.any()).optional()
15794
- }).optional(),
15795
- extraContextMessages: z.array(z.object({
15796
- role: z.enum([
15797
- "user",
15798
- "assistant",
15799
- "system",
15800
- "function",
15801
- "tool"
15802
- ]),
15803
- content: z.union([z.string(), z.array(z.any())]),
15804
- fabFileIds: z.array(z.string()).optional()
15805
- })).optional(),
15806
- /** User's timezone (IANA format, e.g., "America/New_York") */
15807
- timezone: z.string().optional(),
15808
- /** Persona-based sub-agent filter — only these agent names are available for delegation */
15809
- allowedAgents: z.array(z.string()).optional(),
15810
- /** When true, Quest Processor injects Slack-specific tool configs (help, notebooks, curated files) */
15811
- enableSlackTools: z.boolean().optional()
15812
- });
15813
15899
  /**
15814
15900
  * Regex patterns for extracting GitHub entities from text
15815
15901
  */
@@ -16307,6 +16393,56 @@ var CombinedExtractor = class {
16307
16393
  }
16308
16394
  };
16309
16395
  new CombinedExtractor();
16396
+ z.object({
16397
+ userId: z.string(),
16398
+ sessionId: z.string(),
16399
+ questId: z.string(),
16400
+ message: z.string().min(1, "Message cannot be empty"),
16401
+ messageFileIds: z.array(z.string()),
16402
+ historyCount: z.number(),
16403
+ fabFileIds: z.array(z.string()),
16404
+ params: ChatCompletionCreateInputSchema,
16405
+ dashboardParams: DashboardParamsSchema.optional(),
16406
+ enableQuestMaster: z.boolean().optional(),
16407
+ enableMementos: z.boolean().optional(),
16408
+ enableArtifacts: z.boolean().optional(),
16409
+ enableAgents: z.boolean().optional(),
16410
+ enableLattice: z.boolean().optional(),
16411
+ promptMeta: PromptMetaZodSchema,
16412
+ tools: z.array(z.union([b4mLLMTools, z.string()])).optional(),
16413
+ mcpServers: z.array(z.string()).optional(),
16414
+ projectId: z.string().optional(),
16415
+ organizationId: z.string().nullable().optional(),
16416
+ questMaster: QuestMasterParamsSchema.optional(),
16417
+ toolPromptId: z.string().optional(),
16418
+ researchMode: ResearchModeParamsSchema.optional(),
16419
+ fallbackModel: z.string().optional(),
16420
+ embeddingModel: z.string().optional(),
16421
+ queryComplexity: z.string(),
16422
+ imageConfig: GenerateImageToolCallSchema.optional(),
16423
+ deepResearchConfig: z.object({
16424
+ maxDepth: z.number().optional(),
16425
+ duration: z.number().optional(),
16426
+ searchers: z.array(z.any()).optional()
16427
+ }).optional(),
16428
+ extraContextMessages: z.array(z.object({
16429
+ role: z.enum([
16430
+ "user",
16431
+ "assistant",
16432
+ "system",
16433
+ "function",
16434
+ "tool"
16435
+ ]),
16436
+ content: z.union([z.string(), z.array(z.any())]),
16437
+ fabFileIds: z.array(z.string()).optional()
16438
+ })).optional(),
16439
+ /** User's timezone (IANA format, e.g., "America/New_York") */
16440
+ timezone: z.string().optional(),
16441
+ /** Persona-based sub-agent filter — only these agent names are available for delegation */
16442
+ allowedAgents: z.array(z.string()).optional(),
16443
+ /** When true, Quest Processor injects Slack-specific tool configs (help, notebooks, curated files) */
16444
+ enableSlackTools: z.boolean().optional()
16445
+ });
16310
16446
  (class AnomalyAlertService {
16311
16447
  static {
16312
16448
  this.MAX_CACHE_ENTRIES = 1e3;
@@ -23479,4 +23615,234 @@ function createGetFileStructureTool() {
23479
23615
  };
23480
23616
  }
23481
23617
  //#endregion
23482
- export { DEFAULT_MAX_ITERATIONS as A, ReActAgent as B, loadContextFiles as C, generateCliTools as D, PermissionManager as E, setWebSocketToolExecutor as F, OAuthClient as G, CheckpointStore as H, OllamaBackend as I, searchCommands as J, hasFileReferences as K, buildCoreSystemPrompt as L, DEFAULT_THOROUGHNESS as M, clearFeatureModuleTools as N, ALWAYS_DENIED_FOR_AGENTS as O, registerFeatureModuleTools as P, warmFileCache as Q, buildSkillsPromptSection as R, extractCompactInstructions as S, getEnvironmentName as T, CommandHistoryStore as U, CustomCommandStore as V, SessionStore as W, formatFileSize$1 as X, mergeCommands as Y, searchFiles as Z, WebSocketLlmBackend as _, createCoordinateTaskTool as a, substituteArguments as b, createAgentDelegateTool as c, createSkillTool as d, parseAgentConfig as f, FallbackLlmBackend as g, WebSocketConnectionManager as h, createWriteTodosTool as i, DEFAULT_RETRY_CONFIG as j, DEFAULT_AGENT_MODEL as k, AgentStore as l, WebSocketToolExecutor as m, createFindDefinitionTool as n, createBackgroundAgentTools as o, ApiClient as p, processFileReferences as q, createTodoStore as r, BackgroundAgentManager as s, createGetFileStructureTool as t, SubagentOrchestrator as u, ServerLlmBackend as v, getApiUrl as w, formatStep as x, McpManager as y, isReadOnlyTool as z };
23618
+ //#region src/tools/decisionLogTool.ts
23619
+ /**
23620
+ * Validate log_decision parameters
23621
+ * @throws Error if validation fails
23622
+ */
23623
+ function validateParams(args) {
23624
+ const params = args;
23625
+ if (typeof params.summary !== "string" || params.summary.trim() === "") throw new Error("log_decision: summary must be a non-empty string");
23626
+ if (typeof params.rationale !== "string" || params.rationale.trim() === "") throw new Error("log_decision: rationale must be a non-empty string");
23627
+ if (params.alternatives !== void 0) {
23628
+ if (!Array.isArray(params.alternatives)) throw new Error("log_decision: alternatives must be an array of strings");
23629
+ for (const alt of params.alternatives) if (typeof alt !== "string") throw new Error("log_decision: each alternative must be a string");
23630
+ }
23631
+ if (params.context !== void 0 && typeof params.context !== "string") throw new Error("log_decision: context must be a string");
23632
+ return {
23633
+ summary: params.summary.trim(),
23634
+ rationale: params.rationale.trim(),
23635
+ alternatives: params.alternatives,
23636
+ context: typeof params.context === "string" ? params.context.trim() : void 0
23637
+ };
23638
+ }
23639
+ /**
23640
+ * Format decisions for display output
23641
+ */
23642
+ function formatDecisionsOutput(decisions) {
23643
+ if (decisions.length === 0) return "No decisions logged in this session.";
23644
+ return decisions.map((decision, index) => {
23645
+ const lines = [`${index + 1}. **${decision.summary}**`, ` Rationale: ${decision.rationale}`];
23646
+ if (decision.alternatives && decision.alternatives.length > 0) lines.push(` Alternatives considered: ${decision.alternatives.join(", ")}`);
23647
+ if (decision.context) lines.push(` Context: ${decision.context}`);
23648
+ const timestamp = new Date(decision.timestamp).toLocaleTimeString();
23649
+ lines.push(` Logged at: ${timestamp}`);
23650
+ return lines.join("\n");
23651
+ }).join("\n\n");
23652
+ }
23653
+ /**
23654
+ * Create the log_decision tool.
23655
+ *
23656
+ * Allows the AI to record significant decisions with rationale during a session.
23657
+ * Decisions are persisted in the session's workflow state for audit trail
23658
+ * and cross-session continuity.
23659
+ */
23660
+ function createDecisionLogTool(store) {
23661
+ return {
23662
+ toolFn: async (args) => {
23663
+ const params = validateParams(args);
23664
+ const decision = {
23665
+ id: v4(),
23666
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
23667
+ summary: params.summary,
23668
+ rationale: params.rationale,
23669
+ alternatives: params.alternatives,
23670
+ context: params.context
23671
+ };
23672
+ store.decisions.push(decision);
23673
+ if (store.onUpdate) store.onUpdate(store.decisions);
23674
+ return `Decision logged (#${store.decisions.length}): ${decision.summary}`;
23675
+ },
23676
+ toolSchema: {
23677
+ name: "log_decision",
23678
+ description: `Record a significant decision with its rationale for audit trail and session continuity.
23679
+
23680
+ **When to use:**
23681
+ - Architecture or design choices (e.g., "chose Zustand over Redux because...")
23682
+ - Scope narrowing or direction changes in research
23683
+ - Trade-off decisions between viable alternatives
23684
+ - Interpretation of ambiguous requirements
23685
+
23686
+ **When NOT to use:**
23687
+ - Routine operations (reading files, running tests)
23688
+ - Trivial choices that wouldn't matter to someone resuming this work
23689
+ - Implementation details that are obvious from the code
23690
+
23691
+ Log decisions that would matter if someone needed to understand WHY you did something.`,
23692
+ parameters: {
23693
+ type: "object",
23694
+ properties: {
23695
+ summary: {
23696
+ type: "string",
23697
+ description: "What was decided — a clear, concise statement"
23698
+ },
23699
+ rationale: {
23700
+ type: "string",
23701
+ description: "Why this decision was made — the reasoning behind it"
23702
+ },
23703
+ alternatives: {
23704
+ type: "array",
23705
+ items: { type: "string" },
23706
+ description: "What alternatives were considered (optional)"
23707
+ },
23708
+ context: {
23709
+ type: "string",
23710
+ description: "What triggered this decision — the situation or constraint (optional)"
23711
+ }
23712
+ },
23713
+ required: ["summary", "rationale"]
23714
+ }
23715
+ }
23716
+ };
23717
+ }
23718
+ /**
23719
+ * Create a new empty DecisionStore
23720
+ */
23721
+ function createDecisionStore(onUpdate) {
23722
+ return {
23723
+ decisions: [],
23724
+ onUpdate
23725
+ };
23726
+ }
23727
+ //#endregion
23728
+ //#region src/tools/blockerTool.ts
23729
+ /**
23730
+ * Format blockers for display output
23731
+ */
23732
+ function formatBlockersOutput(blockers) {
23733
+ if (blockers.length === 0) return "No blockers tracked in this session.";
23734
+ const open = blockers.filter((b) => b.status === "open");
23735
+ const resolved = blockers.filter((b) => b.status === "resolved");
23736
+ const lines = [];
23737
+ if (open.length > 0) {
23738
+ lines.push(`**Open blockers (${open.length}):**`);
23739
+ for (const blocker of open) lines.push(` - [${blocker.id.slice(0, 8)}] ${blocker.description}`);
23740
+ }
23741
+ if (resolved.length > 0) {
23742
+ if (open.length > 0) lines.push("");
23743
+ lines.push(`**Resolved blockers (${resolved.length}):**`);
23744
+ for (const blocker of resolved) {
23745
+ lines.push(` - [${blocker.id.slice(0, 8)}] ${blocker.description}`);
23746
+ lines.push(` Resolution: ${blocker.resolution ?? "(no resolution recorded)"}`);
23747
+ }
23748
+ }
23749
+ return lines.join("\n");
23750
+ }
23751
+ /**
23752
+ * Create the track_blocker and resolve_blocker tools.
23753
+ *
23754
+ * Allows the AI to track what's blocking progress and record resolutions.
23755
+ * Blockers are persisted in the session's workflow state for audit trail
23756
+ * and cross-session continuity.
23757
+ */
23758
+ function createBlockerTools(store) {
23759
+ return [{
23760
+ toolFn: async (args) => {
23761
+ const params = args;
23762
+ if (typeof params.description !== "string" || params.description.trim() === "") throw new Error("track_blocker: description must be a non-empty string");
23763
+ const blocker = {
23764
+ id: v4(),
23765
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
23766
+ description: params.description.trim(),
23767
+ status: "open"
23768
+ };
23769
+ store.blockers.push(blocker);
23770
+ if (store.onUpdate) store.onUpdate(store.blockers);
23771
+ const openCount = store.blockers.filter((b) => b.status === "open").length;
23772
+ return `Blocker tracked [${blocker.id.slice(0, 8)}]: ${blocker.description}\n(${openCount} open blocker${openCount === 1 ? "" : "s"})`;
23773
+ },
23774
+ toolSchema: {
23775
+ name: "track_blocker",
23776
+ description: `Track something that is blocking progress.
23777
+
23778
+ **When to use:**
23779
+ - Missing information or unclear requirements
23780
+ - External dependencies (waiting on API access, credentials, data)
23781
+ - Technical constraints discovered during work
23782
+ - Ambiguous requirements that need human clarification
23783
+
23784
+ **When NOT to use:**
23785
+ - Normal challenges that are part of the work
23786
+ - Things you can resolve immediately`,
23787
+ parameters: {
23788
+ type: "object",
23789
+ properties: { description: {
23790
+ type: "string",
23791
+ description: "What is blocking progress — be specific about what is needed to unblock"
23792
+ } },
23793
+ required: ["description"]
23794
+ }
23795
+ }
23796
+ }, {
23797
+ toolFn: async (args) => {
23798
+ const params = args;
23799
+ if (typeof params.blocker_id !== "string" || params.blocker_id.trim() === "") throw new Error("resolve_blocker: blocker_id must be a non-empty string");
23800
+ if (typeof params.resolution !== "string" || params.resolution.trim() === "") throw new Error("resolve_blocker: resolution must be a non-empty string");
23801
+ const blockerId = params.blocker_id.trim();
23802
+ const blocker = store.blockers.find((b) => b.id === blockerId || b.id.startsWith(blockerId));
23803
+ if (!blocker) {
23804
+ const openBlockers = store.blockers.filter((b) => b.status === "open");
23805
+ if (openBlockers.length === 0) return "No open blockers to resolve.";
23806
+ return `Blocker not found. Open blockers:\n${openBlockers.map((b) => ` [${b.id.slice(0, 8)}] ${b.description}`).join("\n")}`;
23807
+ }
23808
+ if (blocker.status === "resolved") return `Blocker [${blocker.id.slice(0, 8)}] is already resolved.`;
23809
+ blocker.status = "resolved";
23810
+ blocker.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
23811
+ blocker.resolution = params.resolution.trim();
23812
+ if (store.onUpdate) store.onUpdate(store.blockers);
23813
+ const openCount = store.blockers.filter((b) => b.status === "open").length;
23814
+ return `Blocker resolved [${blocker.id.slice(0, 8)}]: ${blocker.resolution}\n(${openCount} open blocker${openCount === 1 ? "" : "s"} remaining)`;
23815
+ },
23816
+ toolSchema: {
23817
+ name: "resolve_blocker",
23818
+ description: `Mark a blocker as resolved with a description of how it was resolved.
23819
+
23820
+ Use the blocker ID (or its first 8 characters) from the track_blocker output.`,
23821
+ parameters: {
23822
+ type: "object",
23823
+ properties: {
23824
+ blocker_id: {
23825
+ type: "string",
23826
+ description: "The ID of the blocker to resolve (full ID or first 8 characters)"
23827
+ },
23828
+ resolution: {
23829
+ type: "string",
23830
+ description: "How the blocker was resolved"
23831
+ }
23832
+ },
23833
+ required: ["blocker_id", "resolution"]
23834
+ }
23835
+ }
23836
+ }];
23837
+ }
23838
+ /**
23839
+ * Create a new empty BlockerStore
23840
+ */
23841
+ function createBlockerStore(onUpdate) {
23842
+ return {
23843
+ blockers: [],
23844
+ onUpdate
23845
+ };
23846
+ }
23847
+ //#endregion
23848
+ export { processFileReferences as $, getApiUrl as A, registerFeatureModuleTools as B, WebSocketLlmBackend as C, formatStep as D, substituteArguments as E, DEFAULT_AGENT_MODEL as F, isReadOnlyTool as G, OllamaBackend as H, DEFAULT_MAX_ITERATIONS as I, CheckpointStore as J, ReActAgent as K, DEFAULT_RETRY_CONFIG as L, PermissionManager as M, generateCliTools as N, extractCompactInstructions as O, ALWAYS_DENIED_FOR_AGENTS as P, hasFileReferences as Q, DEFAULT_THOROUGHNESS as R, FallbackLlmBackend as S, McpManager as T, buildCoreSystemPrompt as U, setWebSocketToolExecutor as V, buildSkillsPromptSection as W, SessionStore as X, CommandHistoryStore as Y, OAuthClient as Z, createSkillTool as _, createDecisionStore as a, WebSocketToolExecutor as b, createFindDefinitionTool as c, createCoordinateTaskTool as d, searchCommands as et, createBackgroundAgentTools as f, SubagentOrchestrator as g, AgentStore as h, createDecisionLogTool as i, warmFileCache as it, getEnvironmentName as j, loadContextFiles as k, createTodoStore as l, createAgentDelegateTool as m, createBlockerTools as n, formatFileSize$1 as nt, formatDecisionsOutput as o, BackgroundAgentManager as p, CustomCommandStore as q, formatBlockersOutput as r, searchFiles as rt, createGetFileStructureTool as s, createBlockerStore as t, mergeCommands as tt, createWriteTodosTool as u, parseAgentConfig as v, ServerLlmBackend as w, WebSocketConnectionManager as x, ApiClient as y, clearFeatureModuleTools as z };