@probelabs/probe 0.6.0-rc101 → 0.6.0-rc103

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.
@@ -1840,7 +1840,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1840
1840
  console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
1841
1841
  console.error(`[DELEGATE] Command args: ${args.join(" ")}`);
1842
1842
  }
1843
- return new Promise((resolve, reject) => {
1843
+ return new Promise((resolve2, reject) => {
1844
1844
  const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
1845
1845
  const process2 = (0, import_child_process5.spawn)(binaryPath, args, {
1846
1846
  stdio: ["pipe", "pipe", "pipe"],
@@ -1905,7 +1905,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1905
1905
  delegationSpan.end();
1906
1906
  }
1907
1907
  }
1908
- resolve(response);
1908
+ resolve2(response);
1909
1909
  } else {
1910
1910
  const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
1911
1911
  if (debug) {
@@ -2268,13 +2268,19 @@ For GitHub-compatible mermaid diagrams, avoid single quotes and parentheses in n
2268
2268
 
2269
2269
  **Rules:**
2270
2270
  - NO single quotes in any node labels: 'text' \u2192 "text" or text
2271
- - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
2271
+ - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
2272
2272
  - NO complex expressions in diamonds: {a && b} \u2192 {condition}
2273
+ - NO HTML tags in node labels: [<pre>code</pre>] \u2192 ["code block"] or [Code Block]
2273
2274
  - USE single quotes for styles and classes: classDef highlight fill:'#ff9999'
2275
+ - CRITICAL: When using quotes in node labels, place them INSIDE the brackets: ["quoted text"], NOT [quoted text"]
2274
2276
 
2275
2277
  **Examples:**
2276
2278
  - \u2705 [Load Config] ["Run command"] {Valid?}
2279
+ - \u2705 ["depends_on: [generate-items]"] (correct quote placement)
2280
+ - \u2705 ["Code Block"] (clean text instead of HTML)
2277
2281
  - \u274C [Load (config)] [Run 'command'] {isValid('x')}
2282
+ - \u274C [depends_on: [generate-items"] (incorrect quote placement - quote ends inside bracket)
2283
+ - \u274C [<pre>depends_on: [generate-items]</pre>] (HTML tags in node labels)
2278
2284
 
2279
2285
  **Diagram Type Selection:**
2280
2286
 
@@ -2776,7 +2782,7 @@ function createMockProvider() {
2776
2782
  provider: "mock",
2777
2783
  // Mock the doGenerate method used by Vercel AI SDK
2778
2784
  doGenerate: async ({ messages, tools: tools2 }) => {
2779
- await new Promise((resolve) => setTimeout(resolve, 10));
2785
+ await new Promise((resolve2) => setTimeout(resolve2, 10));
2780
2786
  return {
2781
2787
  text: "This is a mock response for testing",
2782
2788
  toolCalls: [],
@@ -4602,7 +4608,7 @@ __export(ProbeAgent_exports, {
4602
4608
  ProbeAgent: () => ProbeAgent
4603
4609
  });
4604
4610
  module.exports = __toCommonJS(ProbeAgent_exports);
4605
- var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, ProbeAgent;
4611
+ var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, import_fs6, import_promises, import_path8, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
4606
4612
  var init_ProbeAgent = __esm({
4607
4613
  "src/agent/ProbeAgent.js"() {
4608
4614
  import_anthropic = require("@ai-sdk/anthropic");
@@ -4611,6 +4617,9 @@ var init_ProbeAgent = __esm({
4611
4617
  import_ai2 = require("ai");
4612
4618
  import_crypto5 = require("crypto");
4613
4619
  import_events2 = require("events");
4620
+ import_fs6 = require("fs");
4621
+ import_promises = require("fs/promises");
4622
+ import_path8 = require("path");
4614
4623
  init_tokenCounter();
4615
4624
  init_tools2();
4616
4625
  init_common();
@@ -4621,6 +4630,8 @@ var init_ProbeAgent = __esm({
4621
4630
  init_mcp();
4622
4631
  MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
4623
4632
  MAX_HISTORY_MESSAGES = 100;
4633
+ SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
4634
+ MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
4624
4635
  ProbeAgent = class {
4625
4636
  /**
4626
4637
  * Create a new ProbeAgent instance
@@ -4664,6 +4675,8 @@ var init_ProbeAgent = __esm({
4664
4675
  }
4665
4676
  this.initializeTools();
4666
4677
  this.history = [];
4678
+ this.pendingImages = /* @__PURE__ */ new Map();
4679
+ this.currentImages = [];
4667
4680
  this.events = new import_events2.EventEmitter();
4668
4681
  this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === "1";
4669
4682
  this.mcpConfigPath = options.mcpConfigPath || null;
@@ -4789,6 +4802,175 @@ var init_ProbeAgent = __esm({
4789
4802
  console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
4790
4803
  }
4791
4804
  }
4805
+ /**
4806
+ * Process assistant response content and detect/load image references
4807
+ * @param {string} content - The assistant's response content
4808
+ * @returns {Promise<void>}
4809
+ */
4810
+ async processImageReferences(content) {
4811
+ if (!content) return;
4812
+ const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join("|")})`;
4813
+ const imagePatterns = [
4814
+ // Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
4815
+ new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\.${extensionsPattern}(?!\\w)`, "gi"),
4816
+ // Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
4817
+ new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi"),
4818
+ // Tool result mentions: often contain file paths
4819
+ new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi")
4820
+ ];
4821
+ const foundPaths = /* @__PURE__ */ new Set();
4822
+ for (const pattern of imagePatterns) {
4823
+ let match;
4824
+ while ((match = pattern.exec(content)) !== null) {
4825
+ const imagePath = match[1] || match[0];
4826
+ if (imagePath && imagePath.length > 0) {
4827
+ foundPaths.add(imagePath.trim());
4828
+ }
4829
+ }
4830
+ }
4831
+ if (foundPaths.size === 0) return;
4832
+ if (this.debug) {
4833
+ console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
4834
+ }
4835
+ for (const imagePath of foundPaths) {
4836
+ await this.loadImageIfValid(imagePath);
4837
+ }
4838
+ }
4839
+ /**
4840
+ * Load and cache an image if it's valid and accessible
4841
+ * @param {string} imagePath - Path to the image file
4842
+ * @returns {Promise<boolean>} - True if image was loaded successfully
4843
+ */
4844
+ async loadImageIfValid(imagePath) {
4845
+ try {
4846
+ if (this.pendingImages.has(imagePath)) {
4847
+ if (this.debug) {
4848
+ console.log(`[DEBUG] Image already loaded: ${imagePath}`);
4849
+ }
4850
+ return true;
4851
+ }
4852
+ const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
4853
+ let absolutePath;
4854
+ let isPathAllowed = false;
4855
+ if ((0, import_path8.isAbsolute)(imagePath)) {
4856
+ absolutePath = imagePath;
4857
+ isPathAllowed = allowedDirs.some((dir) => absolutePath.startsWith((0, import_path8.resolve)(dir)));
4858
+ } else {
4859
+ for (const dir of allowedDirs) {
4860
+ const resolvedPath = (0, import_path8.resolve)(dir, imagePath);
4861
+ if (resolvedPath.startsWith((0, import_path8.resolve)(dir))) {
4862
+ absolutePath = resolvedPath;
4863
+ isPathAllowed = true;
4864
+ break;
4865
+ }
4866
+ }
4867
+ }
4868
+ if (!isPathAllowed) {
4869
+ if (this.debug) {
4870
+ console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
4871
+ }
4872
+ return false;
4873
+ }
4874
+ let fileStats;
4875
+ try {
4876
+ fileStats = await (0, import_promises.stat)(absolutePath);
4877
+ } catch (error) {
4878
+ if (this.debug) {
4879
+ console.log(`[DEBUG] Image file not found: ${absolutePath}`);
4880
+ }
4881
+ return false;
4882
+ }
4883
+ if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
4884
+ if (this.debug) {
4885
+ console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
4886
+ }
4887
+ return false;
4888
+ }
4889
+ const extension = absolutePath.toLowerCase().split(".").pop();
4890
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
4891
+ if (this.debug) {
4892
+ console.log(`[DEBUG] Unsupported image format: ${extension}`);
4893
+ }
4894
+ return false;
4895
+ }
4896
+ const mimeTypes = {
4897
+ "png": "image/png",
4898
+ "jpg": "image/jpeg",
4899
+ "jpeg": "image/jpeg",
4900
+ "webp": "image/webp",
4901
+ "gif": "image/gif",
4902
+ "bmp": "image/bmp",
4903
+ "svg": "image/svg+xml"
4904
+ };
4905
+ const mimeType = mimeTypes[extension];
4906
+ const fileBuffer = await (0, import_promises.readFile)(absolutePath);
4907
+ const base64Data = fileBuffer.toString("base64");
4908
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
4909
+ this.pendingImages.set(imagePath, dataUrl);
4910
+ if (this.debug) {
4911
+ console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
4912
+ }
4913
+ return true;
4914
+ } catch (error) {
4915
+ if (this.debug) {
4916
+ console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
4917
+ }
4918
+ return false;
4919
+ }
4920
+ }
4921
+ /**
4922
+ * Get all currently loaded images as an array for AI model consumption
4923
+ * @returns {Array<string>} - Array of base64 data URLs
4924
+ */
4925
+ getCurrentImages() {
4926
+ return Array.from(this.pendingImages.values());
4927
+ }
4928
+ /**
4929
+ * Clear loaded images (useful for new conversations)
4930
+ */
4931
+ clearLoadedImages() {
4932
+ this.pendingImages.clear();
4933
+ this.currentImages = [];
4934
+ if (this.debug) {
4935
+ console.log("[DEBUG] Cleared all loaded images");
4936
+ }
4937
+ }
4938
+ /**
4939
+ * Prepare messages for AI consumption, adding images to the latest user message if available
4940
+ * @param {Array} messages - Current conversation messages
4941
+ * @returns {Array} - Messages formatted for AI SDK with potential image content
4942
+ */
4943
+ prepareMessagesWithImages(messages) {
4944
+ const loadedImages = this.getCurrentImages();
4945
+ if (loadedImages.length === 0) {
4946
+ return messages;
4947
+ }
4948
+ const messagesWithImages = [...messages];
4949
+ const lastUserMessageIndex = messagesWithImages.map((m) => m.role).lastIndexOf("user");
4950
+ if (lastUserMessageIndex === -1) {
4951
+ if (this.debug) {
4952
+ console.log("[DEBUG] No user messages found to attach images to");
4953
+ }
4954
+ return messages;
4955
+ }
4956
+ const lastUserMessage = messagesWithImages[lastUserMessageIndex];
4957
+ if (typeof lastUserMessage.content === "string") {
4958
+ messagesWithImages[lastUserMessageIndex] = {
4959
+ ...lastUserMessage,
4960
+ content: [
4961
+ { type: "text", text: lastUserMessage.content },
4962
+ ...loadedImages.map((imageData) => ({
4963
+ type: "image",
4964
+ image: imageData
4965
+ }))
4966
+ ]
4967
+ };
4968
+ if (this.debug) {
4969
+ console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
4970
+ }
4971
+ }
4972
+ return messagesWithImages;
4973
+ }
4792
4974
  /**
4793
4975
  * Initialize mock model for testing
4794
4976
  */
@@ -4888,7 +5070,7 @@ Examples:
4888
5070
  </extract>
4889
5071
 
4890
5072
  <attempt_completion>
4891
- <result>The configuration is loaded from src/config.js lines 15-25 which contains the database settings.</result>
5073
+ The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
4892
5074
  </attempt_completion>
4893
5075
 
4894
5076
  # Special Case: Quick Completion
@@ -4915,7 +5097,7 @@ I need to find code related to error handling in the search module. The most app
4915
5097
  6. Wait for the tool execution result, which will be provided in the next message (within a <tool_result> block).
4916
5098
  7. Analyze the tool result and decide the next step. If more tool calls are needed, repeat steps 2-6.
4917
5099
  8. If the task is fully complete and all previous steps were successful, use the \`<attempt_completion>\` tool to provide the final answer. This is the ONLY way to finish the task.
4918
- 9. If you cannot proceed (e.g., missing information, invalid request), explain the issue clearly before using \`<attempt_completion>\` with an appropriate message in the \`<result>\` tag.
5100
+ 9. If you cannot proceed (e.g., missing information, invalid request), use \`<attempt_completion>\` to explain the issue clearly with an appropriate message directly inside the tags.
4919
5101
  10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
4920
5102
 
4921
5103
  Available Tools:
@@ -5170,9 +5352,10 @@ You are working with a repository located at: ${searchDirectory}
5170
5352
  let assistantResponseContent = "";
5171
5353
  try {
5172
5354
  const executeAIRequest = async () => {
5355
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
5173
5356
  const result = await (0, import_ai2.streamText)({
5174
5357
  model: this.provider(this.model),
5175
- messages: currentMessages,
5358
+ messages: messagesForAI,
5176
5359
  maxTokens: maxResponseTokens,
5177
5360
  temperature: 0.3
5178
5361
  });
@@ -5206,6 +5389,9 @@ You are working with a repository located at: ${searchDirectory}
5206
5389
  const assistantPreview = createMessagePreview(assistantResponseContent);
5207
5390
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
5208
5391
  }
5392
+ if (assistantResponseContent) {
5393
+ await this.processImageReferences(assistantResponseContent);
5394
+ }
5209
5395
  const validTools = [
5210
5396
  "search",
5211
5397
  "query",
@@ -5330,12 +5516,17 @@ ${toolResultContent}
5330
5516
  throw toolError;
5331
5517
  }
5332
5518
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
5519
+ const toolResultContent = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2);
5520
+ const toolResultMessage = `<tool_result>
5521
+ ${toolResultContent}
5522
+ </tool_result>`;
5333
5523
  currentMessages.push({
5334
5524
  role: "user",
5335
- content: `<tool_result>
5336
- ${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2)}
5337
- </tool_result>`
5525
+ content: toolResultMessage
5338
5526
  });
5527
+ if (toolResultContent) {
5528
+ await this.processImageReferences(toolResultContent);
5529
+ }
5339
5530
  if (this.debug) {
5340
5531
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
5341
5532
  }