@probelabs/probe 0.6.0-rc124 → 0.6.0-rc126

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.
@@ -556,7 +556,7 @@ var init_BytePairEncodingCore = __esm({
556
556
  var chatEnabledModels;
557
557
  var init_modelsChatEnabled_gen = __esm({
558
558
  "node_modules/gpt-tokenizer/esm/modelsChatEnabled.gen.js"() {
559
- chatEnabledModels = ["chatgpt-4o-latest", "codex-mini-latest", "computer-use-preview", "computer-use-preview-2025-03-11", "gpt-3.5", "gpt-3.5-0301", "gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0125-preview", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-1106-vision-preview", "gpt-4-32k", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview", "gpt-4.1", "gpt-4.1-2025-04-14", "gpt-4.1-mini", "gpt-4.1-mini-2025-04-14", "gpt-4.1-nano", "gpt-4.1-nano-2025-04-14", "gpt-4.5-preview", "gpt-4.5-preview-2025-02-27", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20", "gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01", "gpt-4o-audio-preview-2024-12-17", "gpt-4o-audio-preview-2025-06-03", "gpt-4o-mini", "gpt-4o-mini-2024-07-18", "gpt-4o-mini-audio-preview", "gpt-4o-mini-audio-preview-2024-12-17", "gpt-4o-mini-search-preview", "gpt-4o-mini-search-preview-2025-03-11", "gpt-4o-search-preview", "gpt-4o-search-preview-2025-03-11", "o1", "o1-2024-12-17", "o1-mini", "o1-mini-2024-09-12", "o1-preview", "o1-preview-2024-09-12", "o1-pro", "o1-pro-2025-03-19", "o3", "o3-2025-04-16", "o3-mini", "o3-mini-2025-01-31", "o3-pro", "o3-pro-2025-06-10", "o4-mini", "o4-mini-2025-04-16"];
559
+ chatEnabledModels = ["chatgpt-4o-latest", "codex-mini-latest", "computer-use-preview", "computer-use-preview-2025-03-11", "gpt-3.5", "gpt-3.5-0301", "gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0125-preview", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-1106-vision-preview", "gpt-4-32k", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview", "gpt-4.1", "gpt-4.1-2025-04-14", "gpt-4.1-mini", "gpt-4.1-mini-2025-04-14", "gpt-4.1-nano", "gpt-4.1-nano-2025-04-14", "gpt-4.5-preview", "gpt-4.5-preview-2025-02-27", "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20", "gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01", "gpt-4o-audio-preview-2024-12-17", "gpt-4o-audio-preview-2025-06-03", "gpt-4o-mini", "gpt-4o-mini-2024-07-18", "gpt-4o-mini-audio-preview", "gpt-4o-mini-audio-preview-2024-12-17", "gpt-4o-mini-search-preview", "gpt-4o-mini-search-preview-2025-03-11", "gpt-4o-search-preview", "gpt-4o-search-preview-2025-03-11", "gpt-5", "gpt-5-2025-08-07", "gpt-5-chat-latest", "gpt-5-codex", "gpt-5-mini", "gpt-5-mini-2025-08-07", "gpt-5-nano", "gpt-5-nano-2025-08-07", "gpt-5-pro", "gpt-5-pro-2025-10-06", "gpt-audio", "gpt-audio-2025-08-28", "gpt-audio-mini", "gpt-audio-mini-2025-10-06", "gpt-oss-120b", "gpt-oss-20b", "o1", "o1-2024-12-17", "o1-mini", "o1-mini-2024-09-12", "o1-preview", "o1-preview-2024-09-12", "o1-pro", "o1-pro-2025-03-19", "o3", "o3-2025-04-16", "o3-deep-research", "o3-deep-research-2025-06-26", "o3-mini", "o3-mini-2025-01-31", "o3-pro", "o3-pro-2025-06-10", "o4-mini", "o4-mini-2025-04-16", "o4-mini-deep-research", "o4-mini-deep-research-2025-06-26"];
560
560
  }
561
561
  });
562
562
 
@@ -952,8 +952,9 @@ var init_GptEncoding = __esm({
952
952
  * Returns tokens assuming the 'gpt-3.5-turbo-0301' / 'gpt-4-0314' format.
953
953
  * Based on OpenAI's guidelines: https://github.com/openai/openai-python/blob/main/chatml.md
954
954
  * Also mentioned in section 6 of this document: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
955
+ * @param encodeOptions Options controlling how special tokens are handled.
955
956
  */
956
- *encodeChatGenerator(chat, model = this.modelName) {
957
+ *encodeChatGenerator(chat, model = this.modelName, encodeOptions) {
957
958
  if (!model) {
958
959
  throw new Error("Model name must be provided either during initialization or passed in to the method.");
959
960
  }
@@ -979,12 +980,12 @@ var init_GptEncoding = __esm({
979
980
  if (encodedRoleSeparator.length > 0) {
980
981
  yield encodedRoleSeparator;
981
982
  }
982
- yield* this.encodeGenerator(content);
983
+ yield* this.encodeGenerator(content, encodeOptions);
983
984
  yield [chatEndToken];
984
985
  yield encodedMessageSeparator;
985
986
  }
986
987
  yield [chatStartToken];
987
- yield* this.encodeGenerator("assistant");
988
+ yield* this.encodeGenerator("assistant", encodeOptions);
988
989
  if (encodedRoleSeparator.length > 0) {
989
990
  yield encodedRoleSeparator;
990
991
  }
@@ -995,15 +996,20 @@ var init_GptEncoding = __esm({
995
996
  * Returns tokens assuming the 'gpt-3.5-turbo-0301' / 'gpt-4-0314' format.
996
997
  * Based on OpenAI's guidelines: https://github.com/openai/openai-python/blob/main/chatml.md
997
998
  * Also mentioned in section 6 of this document: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
999
+ * @param encodeOptions Options controlling how special tokens are handled.
998
1000
  */
999
- encodeChat(chat, model = this.modelName) {
1000
- return [...this.encodeChatGenerator(chat, model)].flat();
1001
+ encodeChat(chat, model = this.modelName, encodeOptions) {
1002
+ return [...this.encodeChatGenerator(chat, model, encodeOptions)].flat();
1001
1003
  }
1002
1004
  /**
1005
+ * Checks whether the provided input stays within the provided token limit.
1006
+ * @param input The string or chat messages to evaluate.
1007
+ * @param tokenLimit The maximum allowed number of tokens.
1008
+ * @param encodeOptions Options controlling how special tokens are handled.
1003
1009
  * @returns {false | number} false if token limit is exceeded, otherwise the number of tokens
1004
1010
  */
1005
- isWithinTokenLimit(input, tokenLimit) {
1006
- const tokenGenerator = typeof input === "string" ? this.encodeGenerator(input) : this.encodeChatGenerator(input);
1011
+ isWithinTokenLimit(input, tokenLimit, encodeOptions) {
1012
+ const tokenGenerator = typeof input === "string" ? this.encodeGenerator(input, encodeOptions) : this.encodeChatGenerator(input, void 0, encodeOptions);
1007
1013
  let count = 0;
1008
1014
  for (const tokens of tokenGenerator) {
1009
1015
  count += tokens.length;
@@ -1015,6 +1021,8 @@ var init_GptEncoding = __esm({
1015
1021
  }
1016
1022
  /**
1017
1023
  * Counts the number of tokens in the input.
1024
+ * @param input The string or chat messages to evaluate.
1025
+ * @param encodeOptions Options controlling how special tokens are handled.
1018
1026
  * @returns {number} The number of tokens.
1019
1027
  */
1020
1028
  countTokens(input, encodeOptions) {
@@ -1028,7 +1036,7 @@ var init_GptEncoding = __esm({
1028
1036
  }
1029
1037
  return this.bytePairEncodingCoreProcessor.countNative(input, specialTokenConfig.allowedSpecial);
1030
1038
  }
1031
- const tokenGenerator = this.encodeChatGenerator(input);
1039
+ const tokenGenerator = this.encodeChatGenerator(input, void 0, encodeOptions);
1032
1040
  let count = 0;
1033
1041
  for (const tokens of tokenGenerator) {
1034
1042
  count += tokens.length;
@@ -1466,6 +1474,226 @@ var init_tokenCounter = __esm({
1466
1474
  }
1467
1475
  });
1468
1476
 
1477
+ // src/agent/storage/StorageAdapter.js
1478
+ var StorageAdapter;
1479
+ var init_StorageAdapter = __esm({
1480
+ "src/agent/storage/StorageAdapter.js"() {
1481
+ "use strict";
1482
+ StorageAdapter = class {
1483
+ /**
1484
+ * Load conversation history for a session
1485
+ * @param {string} sessionId - Session identifier
1486
+ * @returns {Promise<Array<Object>>} Array of message objects with {role, content, ...}
1487
+ */
1488
+ async loadHistory(sessionId) {
1489
+ throw new Error("StorageAdapter.loadHistory() must be implemented by subclass");
1490
+ }
1491
+ /**
1492
+ * Save a message to storage
1493
+ * @param {string} sessionId - Session identifier
1494
+ * @param {Object} message - Message object { role, content, ... }
1495
+ * @returns {Promise<void>}
1496
+ */
1497
+ async saveMessage(sessionId, message) {
1498
+ throw new Error("StorageAdapter.saveMessage() must be implemented by subclass");
1499
+ }
1500
+ /**
1501
+ * Clear history for a session
1502
+ * @param {string} sessionId - Session identifier
1503
+ * @returns {Promise<void>}
1504
+ */
1505
+ async clearHistory(sessionId) {
1506
+ throw new Error("StorageAdapter.clearHistory() must be implemented by subclass");
1507
+ }
1508
+ /**
1509
+ * Get session metadata (optional)
1510
+ * @param {string} sessionId - Session identifier
1511
+ * @returns {Promise<Object|null>} Session metadata or null
1512
+ */
1513
+ async getSessionMetadata(sessionId) {
1514
+ return null;
1515
+ }
1516
+ /**
1517
+ * Update session activity timestamp (optional)
1518
+ * @param {string} sessionId - Session identifier
1519
+ * @returns {Promise<void>}
1520
+ */
1521
+ async updateSessionActivity(sessionId) {
1522
+ }
1523
+ };
1524
+ }
1525
+ });
1526
+
1527
+ // src/agent/storage/InMemoryStorageAdapter.js
1528
+ var InMemoryStorageAdapter;
1529
+ var init_InMemoryStorageAdapter = __esm({
1530
+ "src/agent/storage/InMemoryStorageAdapter.js"() {
1531
+ "use strict";
1532
+ init_StorageAdapter();
1533
+ InMemoryStorageAdapter = class extends StorageAdapter {
1534
+ constructor() {
1535
+ super();
1536
+ this.sessions = /* @__PURE__ */ new Map();
1537
+ }
1538
+ async loadHistory(sessionId) {
1539
+ const session = this.sessions.get(sessionId);
1540
+ return session ? session.messages : [];
1541
+ }
1542
+ async saveMessage(sessionId, message) {
1543
+ if (!this.sessions.has(sessionId)) {
1544
+ this.sessions.set(sessionId, {
1545
+ messages: [],
1546
+ metadata: {
1547
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1548
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
1549
+ }
1550
+ });
1551
+ }
1552
+ const session = this.sessions.get(sessionId);
1553
+ session.messages.push(message);
1554
+ session.metadata.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1555
+ }
1556
+ async clearHistory(sessionId) {
1557
+ this.sessions.delete(sessionId);
1558
+ }
1559
+ async getSessionMetadata(sessionId) {
1560
+ const session = this.sessions.get(sessionId);
1561
+ return session ? session.metadata : null;
1562
+ }
1563
+ async updateSessionActivity(sessionId) {
1564
+ const session = this.sessions.get(sessionId);
1565
+ if (session) {
1566
+ session.metadata.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1567
+ }
1568
+ }
1569
+ };
1570
+ }
1571
+ });
1572
+
1573
+ // src/agent/hooks/HookManager.js
1574
+ var HookManager, HOOK_TYPES;
1575
+ var init_HookManager = __esm({
1576
+ "src/agent/hooks/HookManager.js"() {
1577
+ "use strict";
1578
+ HookManager = class {
1579
+ constructor() {
1580
+ this.hooks = /* @__PURE__ */ new Map();
1581
+ }
1582
+ /**
1583
+ * Register a hook callback
1584
+ * @param {string} hookName - Name of the hook
1585
+ * @param {Function} callback - Callback function
1586
+ * @returns {Function} Unregister function
1587
+ */
1588
+ on(hookName, callback) {
1589
+ if (!this.hooks.has(hookName)) {
1590
+ this.hooks.set(hookName, /* @__PURE__ */ new Set());
1591
+ }
1592
+ this.hooks.get(hookName).add(callback);
1593
+ return () => this.off(hookName, callback);
1594
+ }
1595
+ /**
1596
+ * Register a one-time hook callback
1597
+ * @param {string} hookName - Name of the hook
1598
+ * @param {Function} callback - Callback function
1599
+ * @returns {Function} Unregister function
1600
+ */
1601
+ once(hookName, callback) {
1602
+ const wrappedCallback = async (data) => {
1603
+ this.off(hookName, wrappedCallback);
1604
+ await callback(data);
1605
+ };
1606
+ return this.on(hookName, wrappedCallback);
1607
+ }
1608
+ /**
1609
+ * Unregister a hook callback
1610
+ * @param {string} hookName - Name of the hook
1611
+ * @param {Function} callback - Callback function
1612
+ */
1613
+ off(hookName, callback) {
1614
+ const callbacks = this.hooks.get(hookName);
1615
+ if (callbacks) {
1616
+ callbacks.delete(callback);
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Emit a hook event
1621
+ * @param {string} hookName - Name of the hook
1622
+ * @param {any} data - Data to pass to callbacks
1623
+ * @returns {Promise<void>}
1624
+ */
1625
+ async emit(hookName, data) {
1626
+ const callbacks = this.hooks.get(hookName);
1627
+ if (!callbacks || callbacks.size === 0) return;
1628
+ const promises = Array.from(callbacks).map((callback) => {
1629
+ try {
1630
+ return Promise.resolve(callback(data));
1631
+ } catch (error) {
1632
+ return Promise.reject(error);
1633
+ }
1634
+ });
1635
+ const results = await Promise.allSettled(promises);
1636
+ results.forEach((result, index) => {
1637
+ if (result.status === "rejected") {
1638
+ console.error(`[HookManager] Error in hook "${hookName}" (callback ${index + 1}):`, result.reason);
1639
+ }
1640
+ });
1641
+ }
1642
+ /**
1643
+ * Clear all hooks or hooks for a specific event
1644
+ * @param {string} [hookName] - Optional hook name to clear
1645
+ */
1646
+ clear(hookName) {
1647
+ if (hookName) {
1648
+ this.hooks.delete(hookName);
1649
+ } else {
1650
+ this.hooks.clear();
1651
+ }
1652
+ }
1653
+ /**
1654
+ * Get list of registered hook names
1655
+ * @returns {string[]} Array of hook names
1656
+ */
1657
+ getHookNames() {
1658
+ return Array.from(this.hooks.keys());
1659
+ }
1660
+ /**
1661
+ * Get number of callbacks for a hook
1662
+ * @param {string} hookName - Name of the hook
1663
+ * @returns {number} Number of callbacks
1664
+ */
1665
+ getCallbackCount(hookName) {
1666
+ const callbacks = this.hooks.get(hookName);
1667
+ return callbacks ? callbacks.size : 0;
1668
+ }
1669
+ };
1670
+ HOOK_TYPES = {
1671
+ // Lifecycle hooks
1672
+ AGENT_INITIALIZED: "agent:initialized",
1673
+ AGENT_CLEANUP: "agent:cleanup",
1674
+ // Message hooks
1675
+ MESSAGE_USER: "message:user",
1676
+ MESSAGE_ASSISTANT: "message:assistant",
1677
+ MESSAGE_SYSTEM: "message:system",
1678
+ // Tool execution hooks
1679
+ TOOL_START: "tool:start",
1680
+ TOOL_END: "tool:end",
1681
+ TOOL_ERROR: "tool:error",
1682
+ // AI streaming hooks
1683
+ AI_STREAM_START: "ai:stream:start",
1684
+ AI_STREAM_DELTA: "ai:stream:delta",
1685
+ AI_STREAM_END: "ai:stream:end",
1686
+ // Storage hooks
1687
+ STORAGE_LOAD: "storage:load",
1688
+ STORAGE_SAVE: "storage:save",
1689
+ STORAGE_CLEAR: "storage:clear",
1690
+ // Iteration hooks
1691
+ ITERATION_START: "iteration:start",
1692
+ ITERATION_END: "iteration:end"
1693
+ };
1694
+ }
1695
+ });
1696
+
1469
1697
  // src/directory-resolver.js
1470
1698
  import path from "path";
1471
1699
  import os from "os";
@@ -16001,15 +16229,16 @@ var init_probeTool = __esm({
16001
16229
  const { directory = ".", workingDirectory } = params;
16002
16230
  const baseCwd = workingDirectory || process.cwd();
16003
16231
  const secureBaseDir = path6.resolve(baseCwd);
16232
+ const isDependencyPath = directory.startsWith("/dep/") || directory.startsWith("go:") || directory.startsWith("js:") || directory.startsWith("rust:");
16004
16233
  let targetDir;
16005
16234
  if (path6.isAbsolute(directory)) {
16006
16235
  targetDir = path6.resolve(directory);
16007
- if (!targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16236
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16008
16237
  throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
16009
16238
  }
16010
16239
  } else {
16011
16240
  targetDir = path6.resolve(secureBaseDir, directory);
16012
- if (!targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16241
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16013
16242
  throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
16014
16243
  }
16015
16244
  }
@@ -16073,15 +16302,16 @@ var init_probeTool = __esm({
16073
16302
  }
16074
16303
  const baseCwd = workingDirectory || process.cwd();
16075
16304
  const secureBaseDir = path6.resolve(baseCwd);
16305
+ const isDependencyPath = directory.startsWith("/dep/") || directory.startsWith("go:") || directory.startsWith("js:") || directory.startsWith("rust:");
16076
16306
  let targetDir;
16077
16307
  if (path6.isAbsolute(directory)) {
16078
16308
  targetDir = path6.resolve(directory);
16079
- if (!targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16309
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16080
16310
  throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
16081
16311
  }
16082
16312
  } else {
16083
16313
  targetDir = path6.resolve(secureBaseDir, directory);
16084
- if (!targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16314
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path6.sep) && targetDir !== secureBaseDir) {
16085
16315
  throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
16086
16316
  }
16087
16317
  }
@@ -16119,6 +16349,23 @@ var init_probeTool = __esm({
16119
16349
  }
16120
16350
  });
16121
16351
 
16352
+ // src/agent/storage/index.js
16353
+ var init_storage = __esm({
16354
+ "src/agent/storage/index.js"() {
16355
+ "use strict";
16356
+ init_StorageAdapter();
16357
+ init_InMemoryStorageAdapter();
16358
+ }
16359
+ });
16360
+
16361
+ // src/agent/hooks/index.js
16362
+ var init_hooks = __esm({
16363
+ "src/agent/hooks/index.js"() {
16364
+ "use strict";
16365
+ init_HookManager();
16366
+ }
16367
+ });
16368
+
16122
16369
  // src/index.js
16123
16370
  var init_index = __esm({
16124
16371
  "src/index.js"() {
@@ -16138,6 +16385,8 @@ var init_index = __esm({
16138
16385
  init_ProbeAgent();
16139
16386
  init_simpleTelemetry();
16140
16387
  init_probeTool();
16388
+ init_storage();
16389
+ init_hooks();
16141
16390
  }
16142
16391
  });
16143
16392
 
@@ -47311,6 +47560,8 @@ var init_ProbeAgent = __esm({
47311
47560
  "src/agent/ProbeAgent.js"() {
47312
47561
  "use strict";
47313
47562
  init_tokenCounter();
47563
+ init_InMemoryStorageAdapter();
47564
+ init_HookManager();
47314
47565
  init_tools2();
47315
47566
  init_common();
47316
47567
  init_probeTool();
@@ -47343,6 +47594,8 @@ var init_ProbeAgent = __esm({
47343
47594
  * @param {string} [options.mcpConfigPath] - Path to MCP configuration file
47344
47595
  * @param {Object} [options.mcpConfig] - MCP configuration object (overrides mcpConfigPath)
47345
47596
  * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
47597
+ * @param {Object} [options.storageAdapter] - Custom storage adapter for history management
47598
+ * @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
47346
47599
  */
47347
47600
  constructor(options = {}) {
47348
47601
  this.sessionId = options.sessionId || randomUUID4();
@@ -47356,6 +47609,13 @@ var init_ProbeAgent = __esm({
47356
47609
  this.maxResponseTokens = options.maxResponseTokens || parseInt(process.env.MAX_RESPONSE_TOKENS || "0", 10) || null;
47357
47610
  this.maxIterations = options.maxIterations || null;
47358
47611
  this.disableMermaidValidation = !!options.disableMermaidValidation;
47612
+ this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
47613
+ this.hooks = new HookManager();
47614
+ if (options.hooks) {
47615
+ for (const [hookName, callback] of Object.entries(options.hooks)) {
47616
+ this.hooks.on(hookName, callback);
47617
+ }
47618
+ }
47359
47619
  this.enableBash = !!options.enableBash;
47360
47620
  this.bashConfig = options.bashConfig || {};
47361
47621
  if (options.allowedFolders && Array.isArray(options.allowedFolders)) {
@@ -47389,9 +47649,23 @@ var init_ProbeAgent = __esm({
47389
47649
  }
47390
47650
  /**
47391
47651
  * Initialize the agent asynchronously (must be called after constructor)
47392
- * This method initializes MCP and merges MCP tools into the tool list
47652
+ * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
47393
47653
  */
47394
47654
  async initialize() {
47655
+ try {
47656
+ const history = await this.storageAdapter.loadHistory(this.sessionId);
47657
+ this.history = history;
47658
+ if (this.debug && history.length > 0) {
47659
+ console.log(`[DEBUG] Loaded ${history.length} messages from storage for session ${this.sessionId}`);
47660
+ }
47661
+ await this.hooks.emit(HOOK_TYPES.STORAGE_LOAD, {
47662
+ sessionId: this.sessionId,
47663
+ messages: history
47664
+ });
47665
+ } catch (error) {
47666
+ console.error(`[ERROR] Failed to load history from storage:`, error);
47667
+ this.history = [];
47668
+ }
47395
47669
  if (this.enableMcp && !this._mcpInitialized) {
47396
47670
  this._mcpInitialized = true;
47397
47671
  try {
@@ -47424,6 +47698,10 @@ var init_ProbeAgent = __esm({
47424
47698
  this.mcpBridge = null;
47425
47699
  }
47426
47700
  }
47701
+ await this.hooks.emit(HOOK_TYPES.AGENT_INITIALIZED, {
47702
+ sessionId: this.sessionId,
47703
+ agent: this
47704
+ });
47427
47705
  }
47428
47706
  /**
47429
47707
  * Initialize tools with configuration
@@ -47475,7 +47753,7 @@ var init_ProbeAgent = __esm({
47475
47753
  }
47476
47754
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
47477
47755
  const openaiApiKey = process.env.OPENAI_API_KEY;
47478
- const googleApiKey = process.env.GOOGLE_API_KEY;
47756
+ const googleApiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_API_KEY;
47479
47757
  const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
47480
47758
  const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
47481
47759
  const awsRegion = process.env.AWS_REGION;
@@ -47523,7 +47801,7 @@ var init_ProbeAgent = __esm({
47523
47801
  } else if (awsAccessKeyId && awsSecretAccessKey && awsRegion || awsApiKey) {
47524
47802
  this.initializeBedrockModel(awsAccessKeyId, awsSecretAccessKey, awsRegion, awsSessionToken, awsApiKey, awsBedrockBaseUrl, modelName);
47525
47803
  } else {
47526
- throw new Error("No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.");
47804
+ throw new Error("No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY (or GOOGLE_API_KEY), AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION), or AWS_BEDROCK_API_KEY environment variables.");
47527
47805
  }
47528
47806
  }
47529
47807
  /**
@@ -48161,6 +48439,12 @@ You are working with a repository located at: ${searchDirectory}
48161
48439
  options = schemaOrOptions || {};
48162
48440
  }
48163
48441
  try {
48442
+ const oldHistoryLength = this.history.length;
48443
+ await this.hooks.emit(HOOK_TYPES.MESSAGE_USER, {
48444
+ sessionId: this.sessionId,
48445
+ message,
48446
+ images
48447
+ });
48164
48448
  const systemMessage = await this.getSystemMessage();
48165
48449
  let userMessage = { role: "user", content: message.trim() };
48166
48450
  if (images && images.length > 0) {
@@ -48246,6 +48530,9 @@ You are working with a repository located at: ${searchDirectory}
48246
48530
  });
48247
48531
  for await (const delta of result.textStream) {
48248
48532
  assistantResponseContent += delta;
48533
+ if (options.onStream) {
48534
+ options.onStream(delta);
48535
+ }
48249
48536
  }
48250
48537
  const usage = await result.usage;
48251
48538
  if (usage) {
@@ -48310,6 +48597,13 @@ You are working with a repository located at: ${searchDirectory}
48310
48597
  const validation = attemptCompletionSchema.safeParse(params);
48311
48598
  if (validation.success) {
48312
48599
  finalResult = validation.data.result;
48600
+ if (options.onStream && finalResult) {
48601
+ const chunkSize = 50;
48602
+ for (let i = 0; i < finalResult.length; i += chunkSize) {
48603
+ const chunk = finalResult.slice(i, Math.min(i + chunkSize, finalResult.length));
48604
+ options.onStream(chunk);
48605
+ }
48606
+ }
48313
48607
  if (this.debug) console.log(`[DEBUG] Task completed successfully with result: ${finalResult.substring(0, 100)}...`);
48314
48608
  } else {
48315
48609
  console.error(`[ERROR] Invalid attempt_completion parameters:`, validation.error);
@@ -48384,7 +48678,9 @@ ${toolResultContent}
48384
48678
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
48385
48679
  name: toolName,
48386
48680
  args: toolParams,
48387
- status: "started"
48681
+ status: "started",
48682
+ pauseStream: true
48683
+ // Signal to pause text streaming
48388
48684
  });
48389
48685
  const executeToolCall = async () => {
48390
48686
  if (toolName === "delegate") {
@@ -48568,6 +48864,18 @@ IMPORTANT: When using <attempt_complete>, this must be the ONLY content in your
48568
48864
  }
48569
48865
  }
48570
48866
  this.tokenCounter.updateHistory(this.history);
48867
+ try {
48868
+ const messagesToSave = currentMessages.slice(oldHistoryLength);
48869
+ for (const message2 of messagesToSave) {
48870
+ await this.storageAdapter.saveMessage(this.sessionId, message2);
48871
+ await this.hooks.emit(HOOK_TYPES.STORAGE_SAVE, {
48872
+ sessionId: this.sessionId,
48873
+ message: message2
48874
+ });
48875
+ }
48876
+ } catch (error) {
48877
+ console.error(`[ERROR] Failed to save messages to storage:`, error);
48878
+ }
48571
48879
  const reachedMaxIterations = currentIteration >= maxIterations && !completionAttempted;
48572
48880
  if (options.schema && !options._schemaFormatted && !completionAttempted && !reachedMaxIterations) {
48573
48881
  if (this.debug) {
@@ -48932,10 +49240,18 @@ Convert your previous response content into actual JSON data that follows this s
48932
49240
  /**
48933
49241
  * Clear conversation history and reset counters
48934
49242
  */
48935
- clearHistory() {
49243
+ async clearHistory() {
49244
+ try {
49245
+ await this.storageAdapter.clearHistory(this.sessionId);
49246
+ } catch (error) {
49247
+ console.error(`[ERROR] Failed to clear history in storage:`, error);
49248
+ }
48936
49249
  this.history = [];
48937
49250
  this.tokenCounter.clear();
48938
49251
  clearToolExecutionData(this.sessionId);
49252
+ await this.hooks.emit(HOOK_TYPES.STORAGE_CLEAR, {
49253
+ sessionId: this.sessionId
49254
+ });
48939
49255
  if (this.debug) {
48940
49256
  console.log(`[DEBUG] Cleared conversation history and reset counters for session ${this.sessionId}`);
48941
49257
  }
@@ -224,19 +224,27 @@ export const listFilesTool = {
224
224
  // Security: Validate path to prevent traversal attacks
225
225
  const secureBaseDir = path.resolve(baseCwd);
226
226
 
227
+ // Check if this is a dependency path that should bypass workspace restrictions
228
+ const isDependencyPath = directory.startsWith('/dep/') ||
229
+ directory.startsWith('go:') ||
230
+ directory.startsWith('js:') ||
231
+ directory.startsWith('rust:');
232
+
227
233
  // If directory is absolute, check if it's within the secure base directory
228
234
  // If it's relative, resolve it against the secure base directory
229
235
  let targetDir;
230
236
  if (path.isAbsolute(directory)) {
231
237
  targetDir = path.resolve(directory);
232
238
  // Check if the absolute path is within the secure base directory
233
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
239
+ // Allow dependency paths to bypass this restriction
240
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
234
241
  throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
235
242
  }
236
243
  } else {
237
244
  targetDir = path.resolve(secureBaseDir, directory);
238
245
  // Double-check the resolved path is still within the secure base directory
239
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
246
+ // Allow dependency paths to bypass this restriction
247
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
240
248
  throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
241
249
  }
242
250
  }
@@ -323,19 +331,27 @@ export const searchFilesTool = {
323
331
  const baseCwd = workingDirectory || process.cwd();
324
332
  const secureBaseDir = path.resolve(baseCwd);
325
333
 
334
+ // Check if this is a dependency path that should bypass workspace restrictions
335
+ const isDependencyPath = directory.startsWith('/dep/') ||
336
+ directory.startsWith('go:') ||
337
+ directory.startsWith('js:') ||
338
+ directory.startsWith('rust:');
339
+
326
340
  // If directory is absolute, check if it's within the secure base directory
327
341
  // If it's relative, resolve it against the secure base directory
328
342
  let targetDir;
329
343
  if (path.isAbsolute(directory)) {
330
344
  targetDir = path.resolve(directory);
331
345
  // Check if the absolute path is within the secure base directory
332
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
346
+ // Allow dependency paths to bypass this restriction
347
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
333
348
  throw new Error(`Path traversal attempt detected. Cannot access directory outside workspace: ${directory}`);
334
349
  }
335
350
  } else {
336
351
  targetDir = path.resolve(secureBaseDir, directory);
337
352
  // Double-check the resolved path is still within the secure base directory
338
- if (!targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
353
+ // Allow dependency paths to bypass this restriction
354
+ if (!isDependencyPath && !targetDir.startsWith(secureBaseDir + path.sep) && targetDir !== secureBaseDir) {
339
355
  throw new Error(`Path traversal attempt detected. Access denied: ${directory}`);
340
356
  }
341
357
  }
@@ -0,0 +1,49 @@
1
+ import { StorageAdapter } from './StorageAdapter.js';
2
+
3
+ /**
4
+ * Default in-memory storage adapter
5
+ * This is the default behavior - stores history in a Map in memory
6
+ */
7
+ export class InMemoryStorageAdapter extends StorageAdapter {
8
+ constructor() {
9
+ super();
10
+ this.sessions = new Map(); // sessionId -> {messages: [], metadata: {}}
11
+ }
12
+
13
+ async loadHistory(sessionId) {
14
+ const session = this.sessions.get(sessionId);
15
+ return session ? session.messages : [];
16
+ }
17
+
18
+ async saveMessage(sessionId, message) {
19
+ if (!this.sessions.has(sessionId)) {
20
+ this.sessions.set(sessionId, {
21
+ messages: [],
22
+ metadata: {
23
+ createdAt: new Date().toISOString(),
24
+ lastActivity: new Date().toISOString()
25
+ }
26
+ });
27
+ }
28
+
29
+ const session = this.sessions.get(sessionId);
30
+ session.messages.push(message);
31
+ session.metadata.lastActivity = new Date().toISOString();
32
+ }
33
+
34
+ async clearHistory(sessionId) {
35
+ this.sessions.delete(sessionId);
36
+ }
37
+
38
+ async getSessionMetadata(sessionId) {
39
+ const session = this.sessions.get(sessionId);
40
+ return session ? session.metadata : null;
41
+ }
42
+
43
+ async updateSessionActivity(sessionId) {
44
+ const session = this.sessions.get(sessionId);
45
+ if (session) {
46
+ session.metadata.lastActivity = new Date().toISOString();
47
+ }
48
+ }
49
+ }