@probelabs/probe 0.6.0-rc168 → 0.6.0-rc170

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.
@@ -13,11 +13,11 @@ import { randomUUID } from 'crypto';
13
13
  import { EventEmitter } from 'events';
14
14
  import { existsSync } from 'fs';
15
15
  import { readFile, stat } from 'fs/promises';
16
- import { resolve, isAbsolute, dirname } from 'path';
16
+ import { resolve, isAbsolute, dirname, basename, normalize, sep } from 'path';
17
17
  import { TokenCounter } from './tokenCounter.js';
18
18
  import { InMemoryStorageAdapter } from './storage/InMemoryStorageAdapter.js';
19
19
  import { HookManager, HOOK_TYPES } from './hooks/HookManager.js';
20
- import { SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES } from './imageConfig.js';
20
+ import { SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES, isFormatSupportedByProvider } from './imageConfig.js';
21
21
  import {
22
22
  createTools,
23
23
  searchToolDefinition,
@@ -420,14 +420,20 @@ export class ProbeAgent {
420
420
  * Initialize tools with configuration
421
421
  */
422
422
  initializeTools() {
423
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
424
+
423
425
  const configOptions = {
424
426
  sessionId: this.sessionId,
425
427
  debug: this.debug,
426
428
  defaultPath: this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd(),
427
429
  allowedFolders: this.allowedFolders,
428
430
  outline: this.outline,
431
+ allowEdit: this.allowEdit,
432
+ enableDelegate: this.enableDelegate,
429
433
  enableBash: this.enableBash,
430
- bashConfig: this.bashConfig
434
+ bashConfig: this.bashConfig,
435
+ allowedTools: this.allowedTools,
436
+ isToolAllowed
431
437
  };
432
438
 
433
439
  // Create base tools
@@ -436,21 +442,54 @@ export class ProbeAgent {
436
442
  // Create wrapped tools with event emission
437
443
  const wrappedTools = createWrappedTools(baseTools);
438
444
 
439
- // Store tool instances for execution
440
- this.toolImplementations = {
441
- search: wrappedTools.searchToolInstance,
442
- query: wrappedTools.queryToolInstance,
443
- extract: wrappedTools.extractToolInstance,
444
- delegate: wrappedTools.delegateToolInstance,
445
- listFiles: listFilesToolInstance,
446
- searchFiles: searchFilesToolInstance,
447
- readImage: {
445
+ // Store tool instances for execution (respect allowedTools + feature flags)
446
+ this.toolImplementations = {};
447
+
448
+ if (wrappedTools.searchToolInstance && isToolAllowed('search')) {
449
+ this.toolImplementations.search = wrappedTools.searchToolInstance;
450
+ }
451
+ if (wrappedTools.queryToolInstance && isToolAllowed('query')) {
452
+ this.toolImplementations.query = wrappedTools.queryToolInstance;
453
+ }
454
+ if (wrappedTools.extractToolInstance && isToolAllowed('extract')) {
455
+ this.toolImplementations.extract = wrappedTools.extractToolInstance;
456
+ }
457
+ if (this.enableDelegate && wrappedTools.delegateToolInstance && isToolAllowed('delegate')) {
458
+ this.toolImplementations.delegate = wrappedTools.delegateToolInstance;
459
+ }
460
+
461
+ // File browsing tools
462
+ if (isToolAllowed('listFiles')) {
463
+ this.toolImplementations.listFiles = listFilesToolInstance;
464
+ }
465
+ if (isToolAllowed('searchFiles')) {
466
+ this.toolImplementations.searchFiles = searchFilesToolInstance;
467
+ }
468
+
469
+ // Image loading tool
470
+ if (isToolAllowed('readImage')) {
471
+ this.toolImplementations.readImage = {
448
472
  execute: async (params) => {
449
473
  const imagePath = params.path;
450
474
  if (!imagePath) {
451
475
  throw new Error('Image path is required');
452
476
  }
453
477
 
478
+ // Validate extension before attempting to load
479
+ // Use basename to prevent path traversal attacks (e.g., 'malicious.jpg/../../../etc/passwd')
480
+ const filename = basename(imagePath);
481
+ const extension = filename.toLowerCase().split('.').pop();
482
+
483
+ // Always validate extension is in allowed list (defense-in-depth)
484
+ if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
485
+ throw new Error(`Invalid or unsupported image extension: ${extension}. Supported formats: ${SUPPORTED_IMAGE_EXTENSIONS.join(', ')}`);
486
+ }
487
+
488
+ // Check provider-specific format restrictions (e.g., SVG not supported by Google Gemini)
489
+ if (this.apiType && !isFormatSupportedByProvider(extension, this.apiType)) {
490
+ throw new Error(`Image format '${extension}' is not supported by the current AI provider (${this.apiType}). Try using a different image format like PNG or JPEG.`);
491
+ }
492
+
454
493
  // Load the image using the existing loadImageIfValid method
455
494
  const loaded = await this.loadImageIfValid(imagePath);
456
495
 
@@ -460,20 +499,20 @@ export class ProbeAgent {
460
499
 
461
500
  return `Image loaded successfully: ${imagePath}. The image is now available for analysis in the conversation.`;
462
501
  }
463
- }
464
- };
502
+ };
503
+ }
465
504
 
466
- // Add bash tool if enabled
467
- if (this.enableBash && wrappedTools.bashToolInstance) {
505
+ // Add bash tool if enabled and allowed
506
+ if (this.enableBash && wrappedTools.bashToolInstance && isToolAllowed('bash')) {
468
507
  this.toolImplementations.bash = wrappedTools.bashToolInstance;
469
508
  }
470
509
 
471
- // Add edit and create tools if enabled
510
+ // Add edit and create tools if enabled and allowed
472
511
  if (this.allowEdit) {
473
- if (wrappedTools.editToolInstance) {
512
+ if (wrappedTools.editToolInstance && isToolAllowed('edit')) {
474
513
  this.toolImplementations.edit = wrappedTools.editToolInstance;
475
514
  }
476
- if (wrappedTools.createToolInstance) {
515
+ if (wrappedTools.createToolInstance && isToolAllowed('create')) {
477
516
  this.toolImplementations.create = wrappedTools.createToolInstance;
478
517
  }
479
518
  }
@@ -1238,20 +1277,28 @@ export class ProbeAgent {
1238
1277
  }
1239
1278
 
1240
1279
  // Security validation: check if path is within any allowed directory
1280
+ // Use normalize() after resolve() to handle path traversal attempts (e.g., '/allowed/../etc/passwd')
1241
1281
  const allowedDirs = this.allowedFolders && this.allowedFolders.length > 0 ? this.allowedFolders : [process.cwd()];
1242
-
1282
+
1243
1283
  let absolutePath;
1244
1284
  let isPathAllowed = false;
1245
-
1285
+
1246
1286
  // If absolute path, check if it's within any allowed directory
1247
1287
  if (isAbsolute(imagePath)) {
1248
- absolutePath = imagePath;
1249
- isPathAllowed = allowedDirs.some(dir => absolutePath.startsWith(resolve(dir)));
1288
+ // Normalize to resolve any '..' sequences
1289
+ absolutePath = normalize(resolve(imagePath));
1290
+ isPathAllowed = allowedDirs.some(dir => {
1291
+ const normalizedDir = normalize(resolve(dir));
1292
+ // Ensure the path is within the allowed directory (add separator to prevent prefix attacks)
1293
+ return absolutePath === normalizedDir || absolutePath.startsWith(normalizedDir + sep);
1294
+ });
1250
1295
  } else {
1251
1296
  // For relative paths, try resolving against each allowed directory
1252
1297
  for (const dir of allowedDirs) {
1253
- const resolvedPath = resolve(dir, imagePath);
1254
- if (resolvedPath.startsWith(resolve(dir))) {
1298
+ const normalizedDir = normalize(resolve(dir));
1299
+ const resolvedPath = normalize(resolve(dir, imagePath));
1300
+ // Ensure the resolved path is within the allowed directory
1301
+ if (resolvedPath === normalizedDir || resolvedPath.startsWith(normalizedDir + sep)) {
1255
1302
  absolutePath = resolvedPath;
1256
1303
  isPathAllowed = true;
1257
1304
  break;
@@ -1295,6 +1342,10 @@ export class ProbeAgent {
1295
1342
  return false;
1296
1343
  }
1297
1344
 
1345
+ // Note: Provider-specific format validation (e.g., SVG not supported by Google Gemini)
1346
+ // is handled by the readImage tool which provides explicit error messages.
1347
+ // loadImageIfValid is a lower-level method that only checks general format support.
1348
+
1298
1349
  // Determine MIME type (from shared config)
1299
1350
  const mimeType = IMAGE_MIME_TYPES[extension];
1300
1351
 
@@ -2480,7 +2531,7 @@ When troubleshooting:
2480
2531
  maxIterations,
2481
2532
  parentSessionId: this.sessionId, // Pass parent session ID for tracking
2482
2533
  path: this.searchPath, // Inherit search path
2483
- provider: this.provider, // Inherit AI provider
2534
+ provider: this.apiType, // Inherit AI provider (string identifier)
2484
2535
  model: this.model, // Inherit model
2485
2536
  debug: this.debug,
2486
2537
  tracer: this.tracer
@@ -2489,7 +2540,7 @@ When troubleshooting:
2489
2540
  if (this.debug) {
2490
2541
  console.log(`[DEBUG] Executing delegate tool at iteration ${currentIteration}/${maxIterations}`);
2491
2542
  console.log(`[DEBUG] Parent session: ${this.sessionId}`);
2492
- console.log(`[DEBUG] Inherited config: path=${this.searchPath}, provider=${this.provider}, model=${this.model}`);
2543
+ console.log(`[DEBUG] Inherited config: path=${this.searchPath}, provider=${this.apiType}, model=${this.model}`);
2493
2544
  console.log(`[DEBUG] Delegate task: ${toolParams.task?.substring(0, 100)}...`);
2494
2545
  }
2495
2546
 
@@ -2573,10 +2624,10 @@ When troubleshooting:
2573
2624
  content: toolResultMessage
2574
2625
  });
2575
2626
 
2576
- // Process tool result for image references
2577
- if (toolResultContent) {
2578
- await this.processImageReferences(toolResultContent);
2579
- }
2627
+ // NOTE: Automatic image processing removed (GitHub issue #305)
2628
+ // Images are now only loaded when the AI explicitly calls the readImage tool
2629
+ // This prevents: 1) implicit behavior that users don't expect
2630
+ // 2) crashes with unsupported MIME types (e.g., SVG on Gemini)
2580
2631
 
2581
2632
  if (this.debug) {
2582
2633
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === 'string' ? toolResult.length : JSON.stringify(toolResult).length}`);
@@ -21,6 +21,12 @@ export const IMAGE_MIME_TYPES = {
21
21
  'svg': 'image/svg+xml'
22
22
  };
23
23
 
24
+ // Provider-specific unsupported image formats
25
+ // These providers do not support certain MIME types and will crash if they receive them
26
+ export const PROVIDER_UNSUPPORTED_FORMATS = {
27
+ 'google': ['svg'], // Google Gemini doesn't support image/svg+xml
28
+ };
29
+
24
30
  /**
25
31
  * Generate a regex pattern string for matching image file extensions
26
32
  * @param {string[]} extensions - Array of extensions (without dots)
@@ -38,3 +44,54 @@ export function getExtensionPattern(extensions = SUPPORTED_IMAGE_EXTENSIONS) {
38
44
  export function getMimeType(extension) {
39
45
  return IMAGE_MIME_TYPES[extension.toLowerCase()];
40
46
  }
47
+
48
+ /**
49
+ * Check if an image extension is supported by a specific provider
50
+ * @param {string} extension - File extension (without dot)
51
+ * @param {string} provider - Provider name (e.g., 'google', 'anthropic', 'openai')
52
+ * @returns {boolean} True if the format is supported by the provider
53
+ */
54
+ export function isFormatSupportedByProvider(extension, provider) {
55
+ // Validate extension parameter - must be a non-empty string without path separators
56
+ if (!extension || typeof extension !== 'string') {
57
+ return false;
58
+ }
59
+ // Sanitize: reject extensions containing path traversal characters
60
+ if (extension.includes('/') || extension.includes('\\') || extension.includes('..')) {
61
+ return false;
62
+ }
63
+
64
+ const ext = extension.toLowerCase();
65
+
66
+ // First check if it's a generally supported format
67
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(ext)) {
68
+ return false;
69
+ }
70
+
71
+ // Handle null/undefined provider gracefully (treat as no restrictions)
72
+ if (!provider || typeof provider !== 'string') {
73
+ return true;
74
+ }
75
+
76
+ // Check provider-specific restrictions
77
+ const unsupportedFormats = PROVIDER_UNSUPPORTED_FORMATS[provider];
78
+ if (unsupportedFormats && unsupportedFormats.includes(ext)) {
79
+ return false;
80
+ }
81
+
82
+ return true;
83
+ }
84
+
85
+ /**
86
+ * Get supported image extensions for a specific provider
87
+ * @param {string} provider - Provider name (e.g., 'google', 'anthropic', 'openai')
88
+ * @returns {string[]} Array of supported extensions for this provider
89
+ */
90
+ export function getSupportedExtensionsForProvider(provider) {
91
+ // Handle null/undefined/non-string provider gracefully (return all extensions)
92
+ if (!provider || typeof provider !== 'string') {
93
+ return [...SUPPORTED_IMAGE_EXTENSIONS];
94
+ }
95
+ const unsupportedFormats = PROVIDER_UNSUPPORTED_FORMATS[provider] || [];
96
+ return SUPPORTED_IMAGE_EXTENSIONS.filter(ext => !unsupportedFormats.includes(ext));
97
+ }
@@ -1998,7 +1998,27 @@ var init_HookManager = __esm({
1998
1998
  });
1999
1999
 
2000
2000
  // src/agent/imageConfig.js
2001
- var SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES;
2001
+ function isFormatSupportedByProvider(extension, provider) {
2002
+ if (!extension || typeof extension !== "string") {
2003
+ return false;
2004
+ }
2005
+ if (extension.includes("/") || extension.includes("\\") || extension.includes("..")) {
2006
+ return false;
2007
+ }
2008
+ const ext2 = extension.toLowerCase();
2009
+ if (!SUPPORTED_IMAGE_EXTENSIONS.includes(ext2)) {
2010
+ return false;
2011
+ }
2012
+ if (!provider || typeof provider !== "string") {
2013
+ return true;
2014
+ }
2015
+ const unsupportedFormats = PROVIDER_UNSUPPORTED_FORMATS[provider];
2016
+ if (unsupportedFormats && unsupportedFormats.includes(ext2)) {
2017
+ return false;
2018
+ }
2019
+ return true;
2020
+ }
2021
+ var SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES, PROVIDER_UNSUPPORTED_FORMATS;
2002
2022
  var init_imageConfig = __esm({
2003
2023
  "src/agent/imageConfig.js"() {
2004
2024
  "use strict";
@@ -2011,6 +2031,10 @@ var init_imageConfig = __esm({
2011
2031
  "bmp": "image/bmp",
2012
2032
  "svg": "image/svg+xml"
2013
2033
  };
2034
+ PROVIDER_UNSUPPORTED_FORMATS = {
2035
+ "google": ["svg"]
2036
+ // Google Gemini doesn't support image/svg+xml
2037
+ };
2014
2038
  }
2015
2039
  });
2016
2040
 
@@ -15373,7 +15397,7 @@ var init_esm4 = __esm({
15373
15397
  *
15374
15398
  * @internal
15375
15399
  */
15376
- constructor(cwd = process.cwd(), pathImpl, sep3, { nocase, childrenCacheSize = 16 * 1024, fs: fs8 = defaultFS } = {}) {
15400
+ constructor(cwd = process.cwd(), pathImpl, sep4, { nocase, childrenCacheSize = 16 * 1024, fs: fs8 = defaultFS } = {}) {
15377
15401
  this.#fs = fsFromOption(fs8);
15378
15402
  if (cwd instanceof URL || cwd.startsWith("file://")) {
15379
15403
  cwd = fileURLToPath4(cwd);
@@ -15384,7 +15408,7 @@ var init_esm4 = __esm({
15384
15408
  this.#resolveCache = new ResolveCache();
15385
15409
  this.#resolvePosixCache = new ResolveCache();
15386
15410
  this.#children = new ChildrenCache(childrenCacheSize);
15387
- const split = cwdPath.substring(this.rootPath.length).split(sep3);
15411
+ const split = cwdPath.substring(this.rootPath.length).split(sep4);
15388
15412
  if (split.length === 1 && !split[0]) {
15389
15413
  split.pop();
15390
15414
  }
@@ -17550,17 +17574,30 @@ var init_xmlParsingUtils = __esm({
17550
17574
  // src/agent/tools.js
17551
17575
  import { randomUUID as randomUUID3 } from "crypto";
17552
17576
  function createTools(configOptions) {
17553
- const tools2 = {
17554
- searchTool: searchTool(configOptions),
17555
- queryTool: queryTool(configOptions),
17556
- extractTool: extractTool(configOptions),
17557
- delegateTool: delegateTool(configOptions)
17558
- };
17559
- if (configOptions.enableBash) {
17577
+ const tools2 = {};
17578
+ const isToolAllowed = configOptions.isToolAllowed || ((toolName) => {
17579
+ if (!configOptions.allowedTools) return true;
17580
+ return configOptions.allowedTools.isEnabled(toolName);
17581
+ });
17582
+ if (isToolAllowed("search")) {
17583
+ tools2.searchTool = searchTool(configOptions);
17584
+ }
17585
+ if (isToolAllowed("query")) {
17586
+ tools2.queryTool = queryTool(configOptions);
17587
+ }
17588
+ if (isToolAllowed("extract")) {
17589
+ tools2.extractTool = extractTool(configOptions);
17590
+ }
17591
+ if (configOptions.enableDelegate && isToolAllowed("delegate")) {
17592
+ tools2.delegateTool = delegateTool(configOptions);
17593
+ }
17594
+ if (configOptions.enableBash && isToolAllowed("bash")) {
17560
17595
  tools2.bashTool = bashTool(configOptions);
17561
17596
  }
17562
- if (configOptions.allowEdit) {
17597
+ if (configOptions.allowEdit && isToolAllowed("edit")) {
17563
17598
  tools2.editTool = editTool(configOptions);
17599
+ }
17600
+ if (configOptions.allowEdit && isToolAllowed("create")) {
17564
17601
  tools2.createTool = createTool(configOptions);
17565
17602
  }
17566
17603
  return tools2;
@@ -36287,7 +36324,7 @@ var init_graph_builder = __esm({
36287
36324
  applyLinkStyles() {
36288
36325
  if (!this.pendingLinkStyles.length || !this.edges.length)
36289
36326
  return;
36290
- const normalize2 = (s) => {
36327
+ const normalize3 = (s) => {
36291
36328
  const out = {};
36292
36329
  for (const [kRaw, vRaw] of Object.entries(s)) {
36293
36330
  const k = kRaw.trim().toLowerCase();
@@ -36308,7 +36345,7 @@ var init_graph_builder = __esm({
36308
36345
  return out;
36309
36346
  };
36310
36347
  for (const cmd of this.pendingLinkStyles) {
36311
- const style = normalize2(cmd.props);
36348
+ const style = normalize3(cmd.props);
36312
36349
  for (const idx of cmd.indices) {
36313
36350
  if (idx >= 0 && idx < this.edges.length) {
36314
36351
  const e = this.edges[idx];
@@ -43400,7 +43437,7 @@ var require_bk = __commonJS({
43400
43437
  return xs;
43401
43438
  }
43402
43439
  function buildBlockGraph(g, layering, root2, reverseSep) {
43403
- var blockGraph = new Graph(), graphLabel = g.graph(), sepFn = sep3(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
43440
+ var blockGraph = new Graph(), graphLabel = g.graph(), sepFn = sep4(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
43404
43441
  _.forEach(layering, function(layer) {
43405
43442
  var u;
43406
43443
  _.forEach(layer, function(v) {
@@ -43490,7 +43527,7 @@ var require_bk = __commonJS({
43490
43527
  alignCoordinates(xss, smallestWidth);
43491
43528
  return balance(xss, g.graph().align);
43492
43529
  }
43493
- function sep3(nodeSep, edgeSep, reverseSep) {
43530
+ function sep4(nodeSep, edgeSep, reverseSep) {
43494
43531
  return function(g, v, w) {
43495
43532
  var vLabel = g.node(v);
43496
43533
  var wLabel = g.node(w);
@@ -43575,7 +43612,7 @@ var require_layout = __commonJS({
43575
43612
  "use strict";
43576
43613
  var _ = require_lodash2();
43577
43614
  var acyclic = require_acyclic();
43578
- var normalize2 = require_normalize();
43615
+ var normalize3 = require_normalize();
43579
43616
  var rank = require_rank();
43580
43617
  var normalizeRanks = require_util().normalizeRanks;
43581
43618
  var parentDummyChains = require_parent_dummy_chains();
@@ -43637,7 +43674,7 @@ var require_layout = __commonJS({
43637
43674
  removeEdgeLabelProxies(g);
43638
43675
  });
43639
43676
  time(" normalize.run", function() {
43640
- normalize2.run(g);
43677
+ normalize3.run(g);
43641
43678
  });
43642
43679
  time(" parentDummyChains", function() {
43643
43680
  parentDummyChains(g);
@@ -43664,7 +43701,7 @@ var require_layout = __commonJS({
43664
43701
  removeBorderNodes(g);
43665
43702
  });
43666
43703
  time(" normalize.undo", function() {
43667
- normalize2.undo(g);
43704
+ normalize3.undo(g);
43668
43705
  });
43669
43706
  time(" fixupEdgeLabelCoords", function() {
43670
43707
  fixupEdgeLabelCoords(g);
@@ -50035,8 +50072,8 @@ var require_resolve = __commonJS({
50035
50072
  }
50036
50073
  return count;
50037
50074
  }
50038
- function getFullPath(resolver, id = "", normalize2) {
50039
- if (normalize2 !== false)
50075
+ function getFullPath(resolver, id = "", normalize3) {
50076
+ if (normalize3 !== false)
50040
50077
  id = normalizeId(id);
50041
50078
  const p = resolver.parse(id);
50042
50079
  return _getFullPath(resolver, p);
@@ -51376,7 +51413,7 @@ var require_fast_uri = __commonJS({
51376
51413
  "use strict";
51377
51414
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
51378
51415
  var { SCHEMES, getSchemeHandler } = require_schemes();
51379
- function normalize2(uri, options) {
51416
+ function normalize3(uri, options) {
51380
51417
  if (typeof uri === "string") {
51381
51418
  uri = /** @type {T} */
51382
51419
  serialize(parse6(uri, options), options);
@@ -51612,7 +51649,7 @@ var require_fast_uri = __commonJS({
51612
51649
  }
51613
51650
  var fastUri = {
51614
51651
  SCHEMES,
51615
- normalize: normalize2,
51652
+ normalize: normalize3,
51616
51653
  resolve: resolve6,
51617
51654
  resolveComponent,
51618
51655
  equal,
@@ -58980,7 +59017,7 @@ import { randomUUID as randomUUID5 } from "crypto";
58980
59017
  import { EventEmitter as EventEmitter5 } from "events";
58981
59018
  import { existsSync as existsSync5 } from "fs";
58982
59019
  import { readFile, stat } from "fs/promises";
58983
- import { resolve as resolve4, isAbsolute as isAbsolute2, dirname as dirname4 } from "path";
59020
+ import { resolve as resolve4, isAbsolute as isAbsolute2, dirname as dirname4, basename, normalize as normalize2, sep as sep3 } from "path";
58984
59021
  var MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
58985
59022
  var init_ProbeAgent = __esm({
58986
59023
  "src/agent/ProbeAgent.js"() {
@@ -59264,46 +59301,72 @@ var init_ProbeAgent = __esm({
59264
59301
  * Initialize tools with configuration
59265
59302
  */
59266
59303
  initializeTools() {
59304
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
59267
59305
  const configOptions = {
59268
59306
  sessionId: this.sessionId,
59269
59307
  debug: this.debug,
59270
59308
  defaultPath: this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd(),
59271
59309
  allowedFolders: this.allowedFolders,
59272
59310
  outline: this.outline,
59311
+ allowEdit: this.allowEdit,
59312
+ enableDelegate: this.enableDelegate,
59273
59313
  enableBash: this.enableBash,
59274
- bashConfig: this.bashConfig
59314
+ bashConfig: this.bashConfig,
59315
+ allowedTools: this.allowedTools,
59316
+ isToolAllowed
59275
59317
  };
59276
59318
  const baseTools = createTools(configOptions);
59277
59319
  const wrappedTools = createWrappedTools(baseTools);
59278
- this.toolImplementations = {
59279
- search: wrappedTools.searchToolInstance,
59280
- query: wrappedTools.queryToolInstance,
59281
- extract: wrappedTools.extractToolInstance,
59282
- delegate: wrappedTools.delegateToolInstance,
59283
- listFiles: listFilesToolInstance,
59284
- searchFiles: searchFilesToolInstance,
59285
- readImage: {
59320
+ this.toolImplementations = {};
59321
+ if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
59322
+ this.toolImplementations.search = wrappedTools.searchToolInstance;
59323
+ }
59324
+ if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
59325
+ this.toolImplementations.query = wrappedTools.queryToolInstance;
59326
+ }
59327
+ if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
59328
+ this.toolImplementations.extract = wrappedTools.extractToolInstance;
59329
+ }
59330
+ if (this.enableDelegate && wrappedTools.delegateToolInstance && isToolAllowed("delegate")) {
59331
+ this.toolImplementations.delegate = wrappedTools.delegateToolInstance;
59332
+ }
59333
+ if (isToolAllowed("listFiles")) {
59334
+ this.toolImplementations.listFiles = listFilesToolInstance;
59335
+ }
59336
+ if (isToolAllowed("searchFiles")) {
59337
+ this.toolImplementations.searchFiles = searchFilesToolInstance;
59338
+ }
59339
+ if (isToolAllowed("readImage")) {
59340
+ this.toolImplementations.readImage = {
59286
59341
  execute: async (params) => {
59287
59342
  const imagePath = params.path;
59288
59343
  if (!imagePath) {
59289
59344
  throw new Error("Image path is required");
59290
59345
  }
59346
+ const filename = basename(imagePath);
59347
+ const extension = filename.toLowerCase().split(".").pop();
59348
+ if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
59349
+ throw new Error(`Invalid or unsupported image extension: ${extension}. Supported formats: ${SUPPORTED_IMAGE_EXTENSIONS.join(", ")}`);
59350
+ }
59351
+ if (this.apiType && !isFormatSupportedByProvider(extension, this.apiType)) {
59352
+ throw new Error(`Image format '${extension}' is not supported by the current AI provider (${this.apiType}). Try using a different image format like PNG or JPEG.`);
59353
+ }
59291
59354
  const loaded = await this.loadImageIfValid(imagePath);
59292
59355
  if (!loaded) {
59293
59356
  throw new Error(`Failed to load image: ${imagePath}. The file may not exist, be too large, have an unsupported format, or be outside allowed directories.`);
59294
59357
  }
59295
59358
  return `Image loaded successfully: ${imagePath}. The image is now available for analysis in the conversation.`;
59296
59359
  }
59297
- }
59298
- };
59299
- if (this.enableBash && wrappedTools.bashToolInstance) {
59360
+ };
59361
+ }
59362
+ if (this.enableBash && wrappedTools.bashToolInstance && isToolAllowed("bash")) {
59300
59363
  this.toolImplementations.bash = wrappedTools.bashToolInstance;
59301
59364
  }
59302
59365
  if (this.allowEdit) {
59303
- if (wrappedTools.editToolInstance) {
59366
+ if (wrappedTools.editToolInstance && isToolAllowed("edit")) {
59304
59367
  this.toolImplementations.edit = wrappedTools.editToolInstance;
59305
59368
  }
59306
- if (wrappedTools.createToolInstance) {
59369
+ if (wrappedTools.createToolInstance && isToolAllowed("create")) {
59307
59370
  this.toolImplementations.create = wrappedTools.createToolInstance;
59308
59371
  }
59309
59372
  }
@@ -59906,12 +59969,16 @@ var init_ProbeAgent = __esm({
59906
59969
  let absolutePath;
59907
59970
  let isPathAllowed2 = false;
59908
59971
  if (isAbsolute2(imagePath)) {
59909
- absolutePath = imagePath;
59910
- isPathAllowed2 = allowedDirs.some((dir) => absolutePath.startsWith(resolve4(dir)));
59972
+ absolutePath = normalize2(resolve4(imagePath));
59973
+ isPathAllowed2 = allowedDirs.some((dir) => {
59974
+ const normalizedDir = normalize2(resolve4(dir));
59975
+ return absolutePath === normalizedDir || absolutePath.startsWith(normalizedDir + sep3);
59976
+ });
59911
59977
  } else {
59912
59978
  for (const dir of allowedDirs) {
59913
- const resolvedPath = resolve4(dir, imagePath);
59914
- if (resolvedPath.startsWith(resolve4(dir))) {
59979
+ const normalizedDir = normalize2(resolve4(dir));
59980
+ const resolvedPath = normalize2(resolve4(dir, imagePath));
59981
+ if (resolvedPath === normalizedDir || resolvedPath.startsWith(normalizedDir + sep3)) {
59915
59982
  absolutePath = resolvedPath;
59916
59983
  isPathAllowed2 = true;
59917
59984
  break;
@@ -60919,8 +60986,8 @@ ${toolResultContent}
60919
60986
  // Pass parent session ID for tracking
60920
60987
  path: this.searchPath,
60921
60988
  // Inherit search path
60922
- provider: this.provider,
60923
- // Inherit AI provider
60989
+ provider: this.apiType,
60990
+ // Inherit AI provider (string identifier)
60924
60991
  model: this.model,
60925
60992
  // Inherit model
60926
60993
  debug: this.debug,
@@ -60929,7 +60996,7 @@ ${toolResultContent}
60929
60996
  if (this.debug) {
60930
60997
  console.log(`[DEBUG] Executing delegate tool at iteration ${currentIteration}/${maxIterations}`);
60931
60998
  console.log(`[DEBUG] Parent session: ${this.sessionId}`);
60932
- console.log(`[DEBUG] Inherited config: path=${this.searchPath}, provider=${this.provider}, model=${this.model}`);
60999
+ console.log(`[DEBUG] Inherited config: path=${this.searchPath}, provider=${this.apiType}, model=${this.model}`);
60933
61000
  console.log(`[DEBUG] Delegate task: ${toolParams.task?.substring(0, 100)}...`);
60934
61001
  }
60935
61002
  if (this.tracer) {
@@ -60996,9 +61063,6 @@ ${toolResultContent}
60996
61063
  role: "user",
60997
61064
  content: toolResultMessage
60998
61065
  });
60999
- if (toolResultContent) {
61000
- await this.processImageReferences(toolResultContent);
61001
- }
61002
61066
  if (this.debug) {
61003
61067
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
61004
61068
  }
@@ -31,21 +31,39 @@ import { processXmlWithThinkingAndRecovery } from './xmlParsingUtils.js';
31
31
 
32
32
  // Create configured tool instances
33
33
  export function createTools(configOptions) {
34
- const tools = {
35
- searchTool: searchTool(configOptions),
36
- queryTool: queryTool(configOptions),
37
- extractTool: extractTool(configOptions),
38
- delegateTool: delegateTool(configOptions)
39
- };
34
+ const tools = {};
35
+
36
+ const isToolAllowed =
37
+ configOptions.isToolAllowed ||
38
+ ((toolName) => {
39
+ if (!configOptions.allowedTools) return true;
40
+ return configOptions.allowedTools.isEnabled(toolName);
41
+ });
42
+
43
+ // Core tools
44
+ if (isToolAllowed('search')) {
45
+ tools.searchTool = searchTool(configOptions);
46
+ }
47
+ if (isToolAllowed('query')) {
48
+ tools.queryTool = queryTool(configOptions);
49
+ }
50
+ if (isToolAllowed('extract')) {
51
+ tools.extractTool = extractTool(configOptions);
52
+ }
53
+ if (configOptions.enableDelegate && isToolAllowed('delegate')) {
54
+ tools.delegateTool = delegateTool(configOptions);
55
+ }
40
56
 
41
57
  // Add bash tool if enabled
42
- if (configOptions.enableBash) {
58
+ if (configOptions.enableBash && isToolAllowed('bash')) {
43
59
  tools.bashTool = bashTool(configOptions);
44
60
  }
45
61
 
46
62
  // Add edit and create tools if enabled
47
- if (configOptions.allowEdit) {
63
+ if (configOptions.allowEdit && isToolAllowed('edit')) {
48
64
  tools.editTool = editTool(configOptions);
65
+ }
66
+ if (configOptions.allowEdit && isToolAllowed('create')) {
49
67
  tools.createTool = createTool(configOptions);
50
68
  }
51
69
 
@@ -199,4 +217,3 @@ export function parseXmlToolCallWithThinking(xmlString, validTools) {
199
217
  // Otherwise, use the original parseXmlToolCall function to parse the cleaned XML string
200
218
  return parseXmlToolCall(cleanedXmlString, validTools);
201
219
  }
202
-