@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.
package/cjs/index.cjs CHANGED
@@ -1543,7 +1543,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1543
1543
  console.error(`[DELEGATE] Using binary at: ${binaryPath}`);
1544
1544
  console.error(`[DELEGATE] Command args: ${args.join(" ")}`);
1545
1545
  }
1546
- return new Promise((resolve, reject) => {
1546
+ return new Promise((resolve2, reject) => {
1547
1547
  const delegationSpan = tracer ? tracer.createDelegationSpan(sessionId, task) : null;
1548
1548
  const process2 = (0, import_child_process5.spawn)(binaryPath, args, {
1549
1549
  stdio: ["pipe", "pipe", "pipe"],
@@ -1608,7 +1608,7 @@ async function delegate({ task, timeout = 300, debug = false, currentIteration =
1608
1608
  delegationSpan.end();
1609
1609
  }
1610
1610
  }
1611
- resolve(response);
1611
+ resolve2(response);
1612
1612
  } else {
1613
1613
  const errorMessage = stderr.trim() || `Delegate process failed with exit code ${code}`;
1614
1614
  if (debug) {
@@ -2040,13 +2040,19 @@ For GitHub-compatible mermaid diagrams, avoid single quotes and parentheses in n
2040
2040
 
2041
2041
  **Rules:**
2042
2042
  - NO single quotes in any node labels: 'text' \u2192 "text" or text
2043
- - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
2043
+ - NO parentheses in square brackets: [Text (detail)] \u2192 [Text - detail]
2044
2044
  - NO complex expressions in diamonds: {a && b} \u2192 {condition}
2045
+ - NO HTML tags in node labels: [<pre>code</pre>] \u2192 ["code block"] or [Code Block]
2045
2046
  - USE single quotes for styles and classes: classDef highlight fill:'#ff9999'
2047
+ - CRITICAL: When using quotes in node labels, place them INSIDE the brackets: ["quoted text"], NOT [quoted text"]
2046
2048
 
2047
2049
  **Examples:**
2048
2050
  - \u2705 [Load Config] ["Run command"] {Valid?}
2051
+ - \u2705 ["depends_on: [generate-items]"] (correct quote placement)
2052
+ - \u2705 ["Code Block"] (clean text instead of HTML)
2049
2053
  - \u274C [Load (config)] [Run 'command'] {isValid('x')}
2054
+ - \u274C [depends_on: [generate-items"] (incorrect quote placement - quote ends inside bracket)
2055
+ - \u274C [<pre>depends_on: [generate-items]</pre>] (HTML tags in node labels)
2050
2056
 
2051
2057
  **Diagram Type Selection:**
2052
2058
 
@@ -2862,7 +2868,7 @@ function createMockProvider() {
2862
2868
  provider: "mock",
2863
2869
  // Mock the doGenerate method used by Vercel AI SDK
2864
2870
  doGenerate: async ({ messages, tools: tools2 }) => {
2865
- await new Promise((resolve) => setTimeout(resolve, 10));
2871
+ await new Promise((resolve2) => setTimeout(resolve2, 10));
2866
2872
  return {
2867
2873
  text: "This is a mock response for testing",
2868
2874
  toolCalls: [],
@@ -4687,7 +4693,7 @@ var ProbeAgent_exports = {};
4687
4693
  __export(ProbeAgent_exports, {
4688
4694
  ProbeAgent: () => ProbeAgent
4689
4695
  });
4690
- var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, ProbeAgent;
4696
+ var import_anthropic, import_openai, import_google, import_ai2, import_crypto5, import_events2, import_fs5, import_promises, import_path7, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, SUPPORTED_IMAGE_EXTENSIONS, MAX_IMAGE_FILE_SIZE, ProbeAgent;
4691
4697
  var init_ProbeAgent = __esm({
4692
4698
  "src/agent/ProbeAgent.js"() {
4693
4699
  "use strict";
@@ -4697,6 +4703,9 @@ var init_ProbeAgent = __esm({
4697
4703
  import_ai2 = require("ai");
4698
4704
  import_crypto5 = require("crypto");
4699
4705
  import_events2 = require("events");
4706
+ import_fs5 = require("fs");
4707
+ import_promises = require("fs/promises");
4708
+ import_path7 = require("path");
4700
4709
  init_tokenCounter();
4701
4710
  init_tools2();
4702
4711
  init_common();
@@ -4707,6 +4716,8 @@ var init_ProbeAgent = __esm({
4707
4716
  init_mcp();
4708
4717
  MAX_TOOL_ITERATIONS = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
4709
4718
  MAX_HISTORY_MESSAGES = 100;
4719
+ SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"];
4720
+ MAX_IMAGE_FILE_SIZE = 20 * 1024 * 1024;
4710
4721
  ProbeAgent = class {
4711
4722
  /**
4712
4723
  * Create a new ProbeAgent instance
@@ -4750,6 +4761,8 @@ var init_ProbeAgent = __esm({
4750
4761
  }
4751
4762
  this.initializeTools();
4752
4763
  this.history = [];
4764
+ this.pendingImages = /* @__PURE__ */ new Map();
4765
+ this.currentImages = [];
4753
4766
  this.events = new import_events2.EventEmitter();
4754
4767
  this.enableMcp = !!options.enableMcp || process.env.ENABLE_MCP === "1";
4755
4768
  this.mcpConfigPath = options.mcpConfigPath || null;
@@ -4875,6 +4888,175 @@ var init_ProbeAgent = __esm({
4875
4888
  console.log(`Using Google API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
4876
4889
  }
4877
4890
  }
4891
+ /**
4892
+ * Process assistant response content and detect/load image references
4893
+ * @param {string} content - The assistant's response content
4894
+ * @returns {Promise<void>}
4895
+ */
4896
+ async processImageReferences(content) {
4897
+ if (!content) return;
4898
+ const extensionsPattern = `(?:${SUPPORTED_IMAGE_EXTENSIONS.join("|")})`;
4899
+ const imagePatterns = [
4900
+ // Direct file path mentions: "./screenshot.png", "/path/to/image.jpg", etc.
4901
+ new RegExp(`(?:\\.?\\.\\/)?[^\\s"'<>\\[\\]]+\\.${extensionsPattern}(?!\\w)`, "gi"),
4902
+ // Contextual mentions: "look at image.png", "the file screenshot.jpg shows"
4903
+ new RegExp(`(?:image|file|screenshot|diagram|photo|picture|graphic)\\s*:?\\s*([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi"),
4904
+ // Tool result mentions: often contain file paths
4905
+ new RegExp(`(?:found|saved|created|generated).*?([^\\s"'<>\\[\\]]+\\.${extensionsPattern})(?!\\w)`, "gi")
4906
+ ];
4907
+ const foundPaths = /* @__PURE__ */ new Set();
4908
+ for (const pattern of imagePatterns) {
4909
+ let match;
4910
+ while ((match = pattern.exec(content)) !== null) {
4911
+ const imagePath = match[1] || match[0];
4912
+ if (imagePath && imagePath.length > 0) {
4913
+ foundPaths.add(imagePath.trim());
4914
+ }
4915
+ }
4916
+ }
4917
+ if (foundPaths.size === 0) return;
4918
+ if (this.debug) {
4919
+ console.log(`[DEBUG] Found ${foundPaths.size} potential image references:`, Array.from(foundPaths));
4920
+ }
4921
+ for (const imagePath of foundPaths) {
4922
+ await this.loadImageIfValid(imagePath);
4923
+ }
4924
+ }
4925
+ /**
4926
+ * Load and cache an image if it's valid and accessible
4927
+ * @param {string} imagePath - Path to the image file
4928
+ * @returns {Promise<boolean>} - True if image was loaded successfully
4929
+ */
4930
+ async loadImageIfValid(imagePath) {
4931
+ try {
4932
+ if (this.pendingImages.has(imagePath)) {
4933
+ if (this.debug) {
4934
+ console.log(`[DEBUG] Image already loaded: ${imagePath}`);
4935
+ }
4936
+ return true;
4937
+ }
4938
+ const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
4939
+ let absolutePath;
4940
+ let isPathAllowed = false;
4941
+ if ((0, import_path7.isAbsolute)(imagePath)) {
4942
+ absolutePath = imagePath;
4943
+ isPathAllowed = allowedDirs.some((dir) => absolutePath.startsWith((0, import_path7.resolve)(dir)));
4944
+ } else {
4945
+ for (const dir of allowedDirs) {
4946
+ const resolvedPath = (0, import_path7.resolve)(dir, imagePath);
4947
+ if (resolvedPath.startsWith((0, import_path7.resolve)(dir))) {
4948
+ absolutePath = resolvedPath;
4949
+ isPathAllowed = true;
4950
+ break;
4951
+ }
4952
+ }
4953
+ }
4954
+ if (!isPathAllowed) {
4955
+ if (this.debug) {
4956
+ console.log(`[DEBUG] Image path outside allowed directories: ${imagePath}`);
4957
+ }
4958
+ return false;
4959
+ }
4960
+ let fileStats;
4961
+ try {
4962
+ fileStats = await (0, import_promises.stat)(absolutePath);
4963
+ } catch (error) {
4964
+ if (this.debug) {
4965
+ console.log(`[DEBUG] Image file not found: ${absolutePath}`);
4966
+ }
4967
+ return false;
4968
+ }
4969
+ if (fileStats.size > MAX_IMAGE_FILE_SIZE) {
4970
+ if (this.debug) {
4971
+ console.log(`[DEBUG] Image file too large: ${absolutePath} (${fileStats.size} bytes, max: ${MAX_IMAGE_FILE_SIZE})`);
4972
+ }
4973
+ return false;
4974
+ }
4975
+ const extension = absolutePath.toLowerCase().split(".").pop();
4976
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
4977
+ if (this.debug) {
4978
+ console.log(`[DEBUG] Unsupported image format: ${extension}`);
4979
+ }
4980
+ return false;
4981
+ }
4982
+ const mimeTypes = {
4983
+ "png": "image/png",
4984
+ "jpg": "image/jpeg",
4985
+ "jpeg": "image/jpeg",
4986
+ "webp": "image/webp",
4987
+ "gif": "image/gif",
4988
+ "bmp": "image/bmp",
4989
+ "svg": "image/svg+xml"
4990
+ };
4991
+ const mimeType = mimeTypes[extension];
4992
+ const fileBuffer = await (0, import_promises.readFile)(absolutePath);
4993
+ const base64Data = fileBuffer.toString("base64");
4994
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
4995
+ this.pendingImages.set(imagePath, dataUrl);
4996
+ if (this.debug) {
4997
+ console.log(`[DEBUG] Successfully loaded image: ${imagePath} (${fileBuffer.length} bytes)`);
4998
+ }
4999
+ return true;
5000
+ } catch (error) {
5001
+ if (this.debug) {
5002
+ console.log(`[DEBUG] Failed to load image ${imagePath}: ${error.message}`);
5003
+ }
5004
+ return false;
5005
+ }
5006
+ }
5007
+ /**
5008
+ * Get all currently loaded images as an array for AI model consumption
5009
+ * @returns {Array<string>} - Array of base64 data URLs
5010
+ */
5011
+ getCurrentImages() {
5012
+ return Array.from(this.pendingImages.values());
5013
+ }
5014
+ /**
5015
+ * Clear loaded images (useful for new conversations)
5016
+ */
5017
+ clearLoadedImages() {
5018
+ this.pendingImages.clear();
5019
+ this.currentImages = [];
5020
+ if (this.debug) {
5021
+ console.log("[DEBUG] Cleared all loaded images");
5022
+ }
5023
+ }
5024
+ /**
5025
+ * Prepare messages for AI consumption, adding images to the latest user message if available
5026
+ * @param {Array} messages - Current conversation messages
5027
+ * @returns {Array} - Messages formatted for AI SDK with potential image content
5028
+ */
5029
+ prepareMessagesWithImages(messages) {
5030
+ const loadedImages = this.getCurrentImages();
5031
+ if (loadedImages.length === 0) {
5032
+ return messages;
5033
+ }
5034
+ const messagesWithImages = [...messages];
5035
+ const lastUserMessageIndex = messagesWithImages.map((m) => m.role).lastIndexOf("user");
5036
+ if (lastUserMessageIndex === -1) {
5037
+ if (this.debug) {
5038
+ console.log("[DEBUG] No user messages found to attach images to");
5039
+ }
5040
+ return messages;
5041
+ }
5042
+ const lastUserMessage = messagesWithImages[lastUserMessageIndex];
5043
+ if (typeof lastUserMessage.content === "string") {
5044
+ messagesWithImages[lastUserMessageIndex] = {
5045
+ ...lastUserMessage,
5046
+ content: [
5047
+ { type: "text", text: lastUserMessage.content },
5048
+ ...loadedImages.map((imageData) => ({
5049
+ type: "image",
5050
+ image: imageData
5051
+ }))
5052
+ ]
5053
+ };
5054
+ if (this.debug) {
5055
+ console.log(`[DEBUG] Added ${loadedImages.length} images to the latest user message`);
5056
+ }
5057
+ }
5058
+ return messagesWithImages;
5059
+ }
4878
5060
  /**
4879
5061
  * Initialize mock model for testing
4880
5062
  */
@@ -4974,7 +5156,7 @@ Examples:
4974
5156
  </extract>
4975
5157
 
4976
5158
  <attempt_completion>
4977
- <result>The configuration is loaded from src/config.js lines 15-25 which contains the database settings.</result>
5159
+ The configuration is loaded from src/config.js lines 15-25 which contains the database settings.
4978
5160
  </attempt_completion>
4979
5161
 
4980
5162
  # Special Case: Quick Completion
@@ -5001,7 +5183,7 @@ I need to find code related to error handling in the search module. The most app
5001
5183
  6. Wait for the tool execution result, which will be provided in the next message (within a <tool_result> block).
5002
5184
  7. Analyze the tool result and decide the next step. If more tool calls are needed, repeat steps 2-6.
5003
5185
  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.
5004
- 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.
5186
+ 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.
5005
5187
  10. If your previous response was already correct and complete, you may use \`<attempt_complete>\` as a shorthand.
5006
5188
 
5007
5189
  Available Tools:
@@ -5256,9 +5438,10 @@ You are working with a repository located at: ${searchDirectory}
5256
5438
  let assistantResponseContent = "";
5257
5439
  try {
5258
5440
  const executeAIRequest = async () => {
5441
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
5259
5442
  const result = await (0, import_ai2.streamText)({
5260
5443
  model: this.provider(this.model),
5261
- messages: currentMessages,
5444
+ messages: messagesForAI,
5262
5445
  maxTokens: maxResponseTokens,
5263
5446
  temperature: 0.3
5264
5447
  });
@@ -5292,6 +5475,9 @@ You are working with a repository located at: ${searchDirectory}
5292
5475
  const assistantPreview = createMessagePreview(assistantResponseContent);
5293
5476
  console.log(`[DEBUG] Assistant response (${assistantResponseContent.length} chars): ${assistantPreview}`);
5294
5477
  }
5478
+ if (assistantResponseContent) {
5479
+ await this.processImageReferences(assistantResponseContent);
5480
+ }
5295
5481
  const validTools = [
5296
5482
  "search",
5297
5483
  "query",
@@ -5416,12 +5602,17 @@ ${toolResultContent}
5416
5602
  throw toolError;
5417
5603
  }
5418
5604
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
5605
+ const toolResultContent = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2);
5606
+ const toolResultMessage = `<tool_result>
5607
+ ${toolResultContent}
5608
+ </tool_result>`;
5419
5609
  currentMessages.push({
5420
5610
  role: "user",
5421
- content: `<tool_result>
5422
- ${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult, null, 2)}
5423
- </tool_result>`
5611
+ content: toolResultMessage
5424
5612
  });
5613
+ if (toolResultContent) {
5614
+ await this.processImageReferences(toolResultContent);
5615
+ }
5425
5616
  if (this.debug) {
5426
5617
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
5427
5618
  }
@@ -5919,12 +6110,12 @@ function initializeSimpleTelemetryFromOptions(options) {
5919
6110
  });
5920
6111
  return telemetry;
5921
6112
  }
5922
- var import_fs5, import_path7, SimpleTelemetry, SimpleAppTracer;
6113
+ var import_fs6, import_path8, SimpleTelemetry, SimpleAppTracer;
5923
6114
  var init_simpleTelemetry = __esm({
5924
6115
  "src/agent/simpleTelemetry.js"() {
5925
6116
  "use strict";
5926
- import_fs5 = require("fs");
5927
- import_path7 = require("path");
6117
+ import_fs6 = require("fs");
6118
+ import_path8 = require("path");
5928
6119
  SimpleTelemetry = class {
5929
6120
  constructor(options = {}) {
5930
6121
  this.serviceName = options.serviceName || "probe-agent";
@@ -5938,11 +6129,11 @@ var init_simpleTelemetry = __esm({
5938
6129
  }
5939
6130
  initializeFileExporter() {
5940
6131
  try {
5941
- const dir = (0, import_path7.dirname)(this.filePath);
5942
- if (!(0, import_fs5.existsSync)(dir)) {
5943
- (0, import_fs5.mkdirSync)(dir, { recursive: true });
6132
+ const dir = (0, import_path8.dirname)(this.filePath);
6133
+ if (!(0, import_fs6.existsSync)(dir)) {
6134
+ (0, import_fs6.mkdirSync)(dir, { recursive: true });
5944
6135
  }
5945
- this.stream = (0, import_fs5.createWriteStream)(this.filePath, { flags: "a" });
6136
+ this.stream = (0, import_fs6.createWriteStream)(this.filePath, { flags: "a" });
5946
6137
  this.stream.on("error", (error) => {
5947
6138
  console.error(`[SimpleTelemetry] Stream error: ${error.message}`);
5948
6139
  });
@@ -6003,20 +6194,20 @@ var init_simpleTelemetry = __esm({
6003
6194
  }
6004
6195
  async flush() {
6005
6196
  if (this.stream) {
6006
- return new Promise((resolve) => {
6007
- this.stream.once("drain", resolve);
6197
+ return new Promise((resolve2) => {
6198
+ this.stream.once("drain", resolve2);
6008
6199
  if (!this.stream.writableNeedDrain) {
6009
- resolve();
6200
+ resolve2();
6010
6201
  }
6011
6202
  });
6012
6203
  }
6013
6204
  }
6014
6205
  async shutdown() {
6015
6206
  if (this.stream) {
6016
- return new Promise((resolve) => {
6207
+ return new Promise((resolve2) => {
6017
6208
  this.stream.end(() => {
6018
6209
  console.log(`[SimpleTelemetry] File stream closed: ${this.filePath}`);
6019
- resolve();
6210
+ resolve2();
6020
6211
  });
6021
6212
  });
6022
6213
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc101",
3
+ "version": "0.6.0-rc103",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",