@probelabs/probe 0.6.0-rc235 → 0.6.0-rc236

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.
@@ -839,7 +839,12 @@ export class ProbeAgent {
839
839
  delegationManager: this.delegationManager, // Per-instance delegation limits
840
840
  outputBuffer: this._outputBuffer,
841
841
  concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
842
- isToolAllowed
842
+ isToolAllowed,
843
+ // Lazy MCP getters — MCP is initialized after tools are created, so we use
844
+ // getter functions that resolve at call-time to get the current MCP state
845
+ getMcpBridge: () => this.mcpBridge,
846
+ getMcpTools: () => this.mcpBridge?.mcpTools || {},
847
+ isMcpToolAllowed: (toolName) => this._isMcpToolAllowed(toolName),
843
848
  };
844
849
 
845
850
  // Create base tools
@@ -10508,7 +10508,12 @@ var init_vercel = __esm({
10508
10508
  if (outline) {
10509
10509
  extractOptions.format = "xml";
10510
10510
  }
10511
- return await extract(extractOptions);
10511
+ const extractResult = await extract(extractOptions);
10512
+ if (resolutionBase && typeof extractResult === "string") {
10513
+ const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
10514
+ return extractResult.split(wsPrefix).join("");
10515
+ }
10516
+ return extractResult;
10512
10517
  } catch (error) {
10513
10518
  console.error("Delegated search failed, falling back to raw search:", error);
10514
10519
  try {
@@ -28898,25 +28903,56 @@ function createExecutePlanTool(options) {
28898
28903
  const tracer = options.tracer || null;
28899
28904
  const sessionStore = options.sessionStore || {};
28900
28905
  const outputBuffer = options.outputBuffer || null;
28901
- if (options.toolImplementations) {
28902
- runtimeOptions = { ...options, tracer, sessionStore, outputBuffer };
28903
- llmCallFn = options.llmCall;
28904
- } else {
28905
- llmCallFn = buildLLMCall(options);
28906
- runtimeOptions = {
28907
- toolImplementations: buildToolImplementations(options),
28908
- llmCall: llmCallFn,
28909
- mcpBridge: options.mcpBridge || null,
28910
- mcpTools: options.mcpTools || {},
28911
- mapConcurrency: options.mapConcurrency || 5,
28912
- timeoutMs: options.timeoutMs || 3e5,
28913
- maxLoopIterations: options.maxLoopIterations || 5e3,
28914
- tracer,
28915
- sessionStore,
28916
- outputBuffer
28917
- };
28906
+ const getMcpBridge = options.getMcpBridge || (() => options.mcpBridge || null);
28907
+ const getMcpTools = options.getMcpTools || (() => options.mcpTools || {});
28908
+ const isMcpToolAllowed = options.isMcpToolAllowed || (() => true);
28909
+ let cachedMcpBridge = null;
28910
+ let runtime = null;
28911
+ function buildRuntime() {
28912
+ const currentMcpBridge = getMcpBridge();
28913
+ const currentMcpTools = getMcpTools();
28914
+ const filteredMcpTools = {};
28915
+ for (const [name, tool5] of Object.entries(currentMcpTools)) {
28916
+ if (isMcpToolAllowed(name)) {
28917
+ filteredMcpTools[name] = tool5;
28918
+ }
28919
+ }
28920
+ if (options.toolImplementations) {
28921
+ runtimeOptions = {
28922
+ ...options,
28923
+ tracer,
28924
+ sessionStore,
28925
+ outputBuffer,
28926
+ mcpBridge: currentMcpBridge,
28927
+ mcpTools: filteredMcpTools
28928
+ };
28929
+ llmCallFn = options.llmCall;
28930
+ } else {
28931
+ llmCallFn = llmCallFn || buildLLMCall(options);
28932
+ runtimeOptions = {
28933
+ toolImplementations: buildToolImplementations(options),
28934
+ llmCall: llmCallFn,
28935
+ mcpBridge: currentMcpBridge,
28936
+ mcpTools: filteredMcpTools,
28937
+ mapConcurrency: options.mapConcurrency || 5,
28938
+ timeoutMs: options.timeoutMs || 3e5,
28939
+ maxLoopIterations: options.maxLoopIterations || 5e3,
28940
+ tracer,
28941
+ sessionStore,
28942
+ outputBuffer
28943
+ };
28944
+ }
28945
+ cachedMcpBridge = currentMcpBridge;
28946
+ runtime = createDSLRuntime(runtimeOptions);
28947
+ return runtime;
28948
+ }
28949
+ function getRuntime() {
28950
+ const currentMcpBridge = getMcpBridge();
28951
+ if (!runtime || cachedMcpBridge !== currentMcpBridge) {
28952
+ buildRuntime();
28953
+ }
28954
+ return runtime;
28918
28955
  }
28919
- const runtime = createDSLRuntime(runtimeOptions);
28920
28956
  const maxRetries = options.maxRetries ?? 2;
28921
28957
  return tool4({
28922
28958
  description: "Execute a JavaScript DSL program to orchestrate tool calls. Use for batch processing, paginated APIs, multi-step workflows where intermediate data is large. Write simple synchronous-looking code \u2014 do NOT use async/await.",
@@ -28982,7 +29018,7 @@ Original error: ${lastError}`;
28982
29018
  return finalOutput;
28983
29019
  }
28984
29020
  }
28985
- const result = await runtime.execute(currentCode, description);
29021
+ const result = await getRuntime().execute(currentCode, description);
28986
29022
  if (result.status === "success") {
28987
29023
  finalOutput = formatSuccess(result, description, attempt, outputBuffer);
28988
29024
  planSpan?.setAttributes?.({
@@ -81422,7 +81458,12 @@ var init_ProbeAgent = __esm({
81422
81458
  outputBuffer: this._outputBuffer,
81423
81459
  concurrencyLimiter: this.concurrencyLimiter,
81424
81460
  // Global AI concurrency limiter
81425
- isToolAllowed
81461
+ isToolAllowed,
81462
+ // Lazy MCP getters — MCP is initialized after tools are created, so we use
81463
+ // getter functions that resolve at call-time to get the current MCP state
81464
+ getMcpBridge: () => this.mcpBridge,
81465
+ getMcpTools: () => this.mcpBridge?.mcpTools || {},
81466
+ isMcpToolAllowed: (toolName) => this._isMcpToolAllowed(toolName)
81426
81467
  };
81427
81468
  const baseTools = createTools(configOptions);
81428
81469
  const wrappedTools = createWrappedTools(baseTools);
@@ -175,28 +175,77 @@ export function createExecutePlanTool(options) {
175
175
  // Output buffer for direct-to-user content (bypasses LLM context window)
176
176
  const outputBuffer = options.outputBuffer || null;
177
177
 
178
- if (options.toolImplementations) {
179
- // Direct DSL options used by tests and manual scripts
180
- runtimeOptions = { ...options, tracer, sessionStore, outputBuffer };
181
- llmCallFn = options.llmCall;
182
- } else {
183
- // Agent configOptions — build everything from the agent's config
184
- llmCallFn = buildLLMCall(options);
185
- runtimeOptions = {
186
- toolImplementations: buildToolImplementations(options),
187
- llmCall: llmCallFn,
188
- mcpBridge: options.mcpBridge || null,
189
- mcpTools: options.mcpTools || {},
190
- mapConcurrency: options.mapConcurrency || 5,
191
- timeoutMs: options.timeoutMs || 300000,
192
- maxLoopIterations: options.maxLoopIterations || 5000,
193
- tracer,
194
- sessionStore,
195
- outputBuffer,
196
- };
178
+ // Lazy MCP getters — when using agent configOptions, MCP may be initialized after
179
+ // this tool is created. We use getters to resolve MCP state at execution time.
180
+ const getMcpBridge = options.getMcpBridge || (() => options.mcpBridge || null);
181
+ const getMcpTools = options.getMcpTools || (() => options.mcpTools || {});
182
+ const isMcpToolAllowed = options.isMcpToolAllowed || (() => true);
183
+
184
+ // Track which MCP bridge the current runtime was built with
185
+ let cachedMcpBridge = null;
186
+ let runtime = null;
187
+
188
+ /**
189
+ * Build or rebuild the DSL runtime.
190
+ * Called lazily on first execute() and when MCP bridge changes.
191
+ */
192
+ function buildRuntime() {
193
+ const currentMcpBridge = getMcpBridge();
194
+ const currentMcpTools = getMcpTools();
195
+
196
+ // Filter MCP tools through allowedTools
197
+ const filteredMcpTools = {};
198
+ for (const [name, tool] of Object.entries(currentMcpTools)) {
199
+ if (isMcpToolAllowed(name)) {
200
+ filteredMcpTools[name] = tool;
201
+ }
202
+ }
203
+
204
+ if (options.toolImplementations) {
205
+ // Direct DSL options — used by tests and manual scripts
206
+ runtimeOptions = {
207
+ ...options,
208
+ tracer,
209
+ sessionStore,
210
+ outputBuffer,
211
+ mcpBridge: currentMcpBridge,
212
+ mcpTools: filteredMcpTools,
213
+ };
214
+ llmCallFn = options.llmCall;
215
+ } else {
216
+ // Agent configOptions — build everything from the agent's config
217
+ llmCallFn = llmCallFn || buildLLMCall(options);
218
+ runtimeOptions = {
219
+ toolImplementations: buildToolImplementations(options),
220
+ llmCall: llmCallFn,
221
+ mcpBridge: currentMcpBridge,
222
+ mcpTools: filteredMcpTools,
223
+ mapConcurrency: options.mapConcurrency || 5,
224
+ timeoutMs: options.timeoutMs || 300000,
225
+ maxLoopIterations: options.maxLoopIterations || 5000,
226
+ tracer,
227
+ sessionStore,
228
+ outputBuffer,
229
+ };
230
+ }
231
+
232
+ cachedMcpBridge = currentMcpBridge;
233
+ runtime = createDSLRuntime(runtimeOptions);
234
+ return runtime;
235
+ }
236
+
237
+ /**
238
+ * Get or rebuild the runtime if MCP state has changed.
239
+ */
240
+ function getRuntime() {
241
+ const currentMcpBridge = getMcpBridge();
242
+ // Rebuild runtime if MCP bridge changed (null -> bridge, or different bridge)
243
+ if (!runtime || cachedMcpBridge !== currentMcpBridge) {
244
+ buildRuntime();
245
+ }
246
+ return runtime;
197
247
  }
198
248
 
199
- const runtime = createDSLRuntime(runtimeOptions);
200
249
  const maxRetries = options.maxRetries ?? 2;
201
250
 
202
251
  return tool({
@@ -272,7 +321,7 @@ RULES REMINDER:
272
321
  }
273
322
  }
274
323
 
275
- const result = await runtime.execute(currentCode, description);
324
+ const result = await getRuntime().execute(currentCode, description);
276
325
 
277
326
  if (result.status === 'success') {
278
327
  finalOutput = formatSuccess(result, description, attempt, outputBuffer);
@@ -283,7 +283,15 @@ export const searchTool = (options = {}) => {
283
283
  extractOptions.format = 'xml';
284
284
  }
285
285
 
286
- return await extract(extractOptions);
286
+ const extractResult = await extract(extractOptions);
287
+
288
+ // Strip workspace root prefix from extract output so paths are relative
289
+ if (resolutionBase && typeof extractResult === 'string') {
290
+ const wsPrefix = resolutionBase.endsWith('/') ? resolutionBase : resolutionBase + '/';
291
+ return extractResult.split(wsPrefix).join('');
292
+ }
293
+
294
+ return extractResult;
287
295
  } catch (error) {
288
296
  console.error('Delegated search failed, falling back to raw search:', error);
289
297
  try {
@@ -37619,7 +37619,12 @@ var init_vercel = __esm({
37619
37619
  if (outline) {
37620
37620
  extractOptions.format = "xml";
37621
37621
  }
37622
- return await extract(extractOptions);
37622
+ const extractResult = await extract(extractOptions);
37623
+ if (resolutionBase && typeof extractResult === "string") {
37624
+ const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
37625
+ return extractResult.split(wsPrefix).join("");
37626
+ }
37627
+ return extractResult;
37623
37628
  } catch (error2) {
37624
37629
  console.error("Delegated search failed, falling back to raw search:", error2);
37625
37630
  try {
@@ -56009,25 +56014,56 @@ function createExecutePlanTool(options) {
56009
56014
  const tracer = options.tracer || null;
56010
56015
  const sessionStore = options.sessionStore || {};
56011
56016
  const outputBuffer = options.outputBuffer || null;
56012
- if (options.toolImplementations) {
56013
- runtimeOptions = { ...options, tracer, sessionStore, outputBuffer };
56014
- llmCallFn = options.llmCall;
56015
- } else {
56016
- llmCallFn = buildLLMCall(options);
56017
- runtimeOptions = {
56018
- toolImplementations: buildToolImplementations(options),
56019
- llmCall: llmCallFn,
56020
- mcpBridge: options.mcpBridge || null,
56021
- mcpTools: options.mcpTools || {},
56022
- mapConcurrency: options.mapConcurrency || 5,
56023
- timeoutMs: options.timeoutMs || 3e5,
56024
- maxLoopIterations: options.maxLoopIterations || 5e3,
56025
- tracer,
56026
- sessionStore,
56027
- outputBuffer
56028
- };
56017
+ const getMcpBridge = options.getMcpBridge || (() => options.mcpBridge || null);
56018
+ const getMcpTools = options.getMcpTools || (() => options.mcpTools || {});
56019
+ const isMcpToolAllowed = options.isMcpToolAllowed || (() => true);
56020
+ let cachedMcpBridge = null;
56021
+ let runtime = null;
56022
+ function buildRuntime() {
56023
+ const currentMcpBridge = getMcpBridge();
56024
+ const currentMcpTools = getMcpTools();
56025
+ const filteredMcpTools = {};
56026
+ for (const [name14, tool5] of Object.entries(currentMcpTools)) {
56027
+ if (isMcpToolAllowed(name14)) {
56028
+ filteredMcpTools[name14] = tool5;
56029
+ }
56030
+ }
56031
+ if (options.toolImplementations) {
56032
+ runtimeOptions = {
56033
+ ...options,
56034
+ tracer,
56035
+ sessionStore,
56036
+ outputBuffer,
56037
+ mcpBridge: currentMcpBridge,
56038
+ mcpTools: filteredMcpTools
56039
+ };
56040
+ llmCallFn = options.llmCall;
56041
+ } else {
56042
+ llmCallFn = llmCallFn || buildLLMCall(options);
56043
+ runtimeOptions = {
56044
+ toolImplementations: buildToolImplementations(options),
56045
+ llmCall: llmCallFn,
56046
+ mcpBridge: currentMcpBridge,
56047
+ mcpTools: filteredMcpTools,
56048
+ mapConcurrency: options.mapConcurrency || 5,
56049
+ timeoutMs: options.timeoutMs || 3e5,
56050
+ maxLoopIterations: options.maxLoopIterations || 5e3,
56051
+ tracer,
56052
+ sessionStore,
56053
+ outputBuffer
56054
+ };
56055
+ }
56056
+ cachedMcpBridge = currentMcpBridge;
56057
+ runtime = createDSLRuntime(runtimeOptions);
56058
+ return runtime;
56059
+ }
56060
+ function getRuntime() {
56061
+ const currentMcpBridge = getMcpBridge();
56062
+ if (!runtime || cachedMcpBridge !== currentMcpBridge) {
56063
+ buildRuntime();
56064
+ }
56065
+ return runtime;
56029
56066
  }
56030
- const runtime = createDSLRuntime(runtimeOptions);
56031
56067
  const maxRetries = options.maxRetries ?? 2;
56032
56068
  return (0, import_ai4.tool)({
56033
56069
  description: "Execute a JavaScript DSL program to orchestrate tool calls. Use for batch processing, paginated APIs, multi-step workflows where intermediate data is large. Write simple synchronous-looking code \u2014 do NOT use async/await.",
@@ -56093,7 +56129,7 @@ Original error: ${lastError}`;
56093
56129
  return finalOutput;
56094
56130
  }
56095
56131
  }
56096
- const result = await runtime.execute(currentCode, description);
56132
+ const result = await getRuntime().execute(currentCode, description);
56097
56133
  if (result.status === "success") {
56098
56134
  finalOutput = formatSuccess(result, description, attempt, outputBuffer);
56099
56135
  planSpan?.setAttributes?.({
@@ -108100,7 +108136,12 @@ var init_ProbeAgent = __esm({
108100
108136
  outputBuffer: this._outputBuffer,
108101
108137
  concurrencyLimiter: this.concurrencyLimiter,
108102
108138
  // Global AI concurrency limiter
108103
- isToolAllowed
108139
+ isToolAllowed,
108140
+ // Lazy MCP getters — MCP is initialized after tools are created, so we use
108141
+ // getter functions that resolve at call-time to get the current MCP state
108142
+ getMcpBridge: () => this.mcpBridge,
108143
+ getMcpTools: () => this.mcpBridge?.mcpTools || {},
108144
+ isMcpToolAllowed: (toolName) => this._isMcpToolAllowed(toolName)
108104
108145
  };
108105
108146
  const baseTools = createTools(configOptions);
108106
108147
  const wrappedTools = createWrappedTools(baseTools);
package/cjs/index.cjs CHANGED
@@ -94428,7 +94428,12 @@ var init_ProbeAgent = __esm({
94428
94428
  outputBuffer: this._outputBuffer,
94429
94429
  concurrencyLimiter: this.concurrencyLimiter,
94430
94430
  // Global AI concurrency limiter
94431
- isToolAllowed
94431
+ isToolAllowed,
94432
+ // Lazy MCP getters — MCP is initialized after tools are created, so we use
94433
+ // getter functions that resolve at call-time to get the current MCP state
94434
+ getMcpBridge: () => this.mcpBridge,
94435
+ getMcpTools: () => this.mcpBridge?.mcpTools || {},
94436
+ isMcpToolAllowed: (toolName) => this._isMcpToolAllowed(toolName)
94432
94437
  };
94433
94438
  const baseTools = createTools(configOptions);
94434
94439
  const wrappedTools = createWrappedTools(baseTools);
@@ -98909,7 +98914,12 @@ var init_vercel = __esm({
98909
98914
  if (outline) {
98910
98915
  extractOptions.format = "xml";
98911
98916
  }
98912
- return await extract(extractOptions);
98917
+ const extractResult = await extract(extractOptions);
98918
+ if (resolutionBase && typeof extractResult === "string") {
98919
+ const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
98920
+ return extractResult.split(wsPrefix).join("");
98921
+ }
98922
+ return extractResult;
98913
98923
  } catch (error2) {
98914
98924
  console.error("Delegated search failed, falling back to raw search:", error2);
98915
98925
  try {
@@ -110696,25 +110706,56 @@ function createExecutePlanTool(options) {
110696
110706
  const tracer = options.tracer || null;
110697
110707
  const sessionStore = options.sessionStore || {};
110698
110708
  const outputBuffer = options.outputBuffer || null;
110699
- if (options.toolImplementations) {
110700
- runtimeOptions = { ...options, tracer, sessionStore, outputBuffer };
110701
- llmCallFn = options.llmCall;
110702
- } else {
110703
- llmCallFn = buildLLMCall(options);
110704
- runtimeOptions = {
110705
- toolImplementations: buildToolImplementations(options),
110706
- llmCall: llmCallFn,
110707
- mcpBridge: options.mcpBridge || null,
110708
- mcpTools: options.mcpTools || {},
110709
- mapConcurrency: options.mapConcurrency || 5,
110710
- timeoutMs: options.timeoutMs || 3e5,
110711
- maxLoopIterations: options.maxLoopIterations || 5e3,
110712
- tracer,
110713
- sessionStore,
110714
- outputBuffer
110715
- };
110709
+ const getMcpBridge = options.getMcpBridge || (() => options.mcpBridge || null);
110710
+ const getMcpTools = options.getMcpTools || (() => options.mcpTools || {});
110711
+ const isMcpToolAllowed = options.isMcpToolAllowed || (() => true);
110712
+ let cachedMcpBridge = null;
110713
+ let runtime = null;
110714
+ function buildRuntime() {
110715
+ const currentMcpBridge = getMcpBridge();
110716
+ const currentMcpTools = getMcpTools();
110717
+ const filteredMcpTools = {};
110718
+ for (const [name14, tool5] of Object.entries(currentMcpTools)) {
110719
+ if (isMcpToolAllowed(name14)) {
110720
+ filteredMcpTools[name14] = tool5;
110721
+ }
110722
+ }
110723
+ if (options.toolImplementations) {
110724
+ runtimeOptions = {
110725
+ ...options,
110726
+ tracer,
110727
+ sessionStore,
110728
+ outputBuffer,
110729
+ mcpBridge: currentMcpBridge,
110730
+ mcpTools: filteredMcpTools
110731
+ };
110732
+ llmCallFn = options.llmCall;
110733
+ } else {
110734
+ llmCallFn = llmCallFn || buildLLMCall(options);
110735
+ runtimeOptions = {
110736
+ toolImplementations: buildToolImplementations(options),
110737
+ llmCall: llmCallFn,
110738
+ mcpBridge: currentMcpBridge,
110739
+ mcpTools: filteredMcpTools,
110740
+ mapConcurrency: options.mapConcurrency || 5,
110741
+ timeoutMs: options.timeoutMs || 3e5,
110742
+ maxLoopIterations: options.maxLoopIterations || 5e3,
110743
+ tracer,
110744
+ sessionStore,
110745
+ outputBuffer
110746
+ };
110747
+ }
110748
+ cachedMcpBridge = currentMcpBridge;
110749
+ runtime = createDSLRuntime(runtimeOptions);
110750
+ return runtime;
110751
+ }
110752
+ function getRuntime() {
110753
+ const currentMcpBridge = getMcpBridge();
110754
+ if (!runtime || cachedMcpBridge !== currentMcpBridge) {
110755
+ buildRuntime();
110756
+ }
110757
+ return runtime;
110716
110758
  }
110717
- const runtime = createDSLRuntime(runtimeOptions);
110718
110759
  const maxRetries = options.maxRetries ?? 2;
110719
110760
  return (0, import_ai6.tool)({
110720
110761
  description: "Execute a JavaScript DSL program to orchestrate tool calls. Use for batch processing, paginated APIs, multi-step workflows where intermediate data is large. Write simple synchronous-looking code \u2014 do NOT use async/await.",
@@ -110780,7 +110821,7 @@ Original error: ${lastError}`;
110780
110821
  return finalOutput;
110781
110822
  }
110782
110823
  }
110783
- const result = await runtime.execute(currentCode, description);
110824
+ const result = await getRuntime().execute(currentCode, description);
110784
110825
  if (result.status === "success") {
110785
110826
  finalOutput = formatSuccess(result, description, attempt, outputBuffer);
110786
110827
  planSpan?.setAttributes?.({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probelabs/probe",
3
- "version": "0.6.0-rc235",
3
+ "version": "0.6.0-rc236",
4
4
  "description": "Node.js wrapper for the probe code search tool",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -839,7 +839,12 @@ export class ProbeAgent {
839
839
  delegationManager: this.delegationManager, // Per-instance delegation limits
840
840
  outputBuffer: this._outputBuffer,
841
841
  concurrencyLimiter: this.concurrencyLimiter, // Global AI concurrency limiter
842
- isToolAllowed
842
+ isToolAllowed,
843
+ // Lazy MCP getters — MCP is initialized after tools are created, so we use
844
+ // getter functions that resolve at call-time to get the current MCP state
845
+ getMcpBridge: () => this.mcpBridge,
846
+ getMcpTools: () => this.mcpBridge?.mcpTools || {},
847
+ isMcpToolAllowed: (toolName) => this._isMcpToolAllowed(toolName),
843
848
  };
844
849
 
845
850
  // Create base tools
@@ -175,28 +175,77 @@ export function createExecutePlanTool(options) {
175
175
  // Output buffer for direct-to-user content (bypasses LLM context window)
176
176
  const outputBuffer = options.outputBuffer || null;
177
177
 
178
- if (options.toolImplementations) {
179
- // Direct DSL options used by tests and manual scripts
180
- runtimeOptions = { ...options, tracer, sessionStore, outputBuffer };
181
- llmCallFn = options.llmCall;
182
- } else {
183
- // Agent configOptions — build everything from the agent's config
184
- llmCallFn = buildLLMCall(options);
185
- runtimeOptions = {
186
- toolImplementations: buildToolImplementations(options),
187
- llmCall: llmCallFn,
188
- mcpBridge: options.mcpBridge || null,
189
- mcpTools: options.mcpTools || {},
190
- mapConcurrency: options.mapConcurrency || 5,
191
- timeoutMs: options.timeoutMs || 300000,
192
- maxLoopIterations: options.maxLoopIterations || 5000,
193
- tracer,
194
- sessionStore,
195
- outputBuffer,
196
- };
178
+ // Lazy MCP getters — when using agent configOptions, MCP may be initialized after
179
+ // this tool is created. We use getters to resolve MCP state at execution time.
180
+ const getMcpBridge = options.getMcpBridge || (() => options.mcpBridge || null);
181
+ const getMcpTools = options.getMcpTools || (() => options.mcpTools || {});
182
+ const isMcpToolAllowed = options.isMcpToolAllowed || (() => true);
183
+
184
+ // Track which MCP bridge the current runtime was built with
185
+ let cachedMcpBridge = null;
186
+ let runtime = null;
187
+
188
+ /**
189
+ * Build or rebuild the DSL runtime.
190
+ * Called lazily on first execute() and when MCP bridge changes.
191
+ */
192
+ function buildRuntime() {
193
+ const currentMcpBridge = getMcpBridge();
194
+ const currentMcpTools = getMcpTools();
195
+
196
+ // Filter MCP tools through allowedTools
197
+ const filteredMcpTools = {};
198
+ for (const [name, tool] of Object.entries(currentMcpTools)) {
199
+ if (isMcpToolAllowed(name)) {
200
+ filteredMcpTools[name] = tool;
201
+ }
202
+ }
203
+
204
+ if (options.toolImplementations) {
205
+ // Direct DSL options — used by tests and manual scripts
206
+ runtimeOptions = {
207
+ ...options,
208
+ tracer,
209
+ sessionStore,
210
+ outputBuffer,
211
+ mcpBridge: currentMcpBridge,
212
+ mcpTools: filteredMcpTools,
213
+ };
214
+ llmCallFn = options.llmCall;
215
+ } else {
216
+ // Agent configOptions — build everything from the agent's config
217
+ llmCallFn = llmCallFn || buildLLMCall(options);
218
+ runtimeOptions = {
219
+ toolImplementations: buildToolImplementations(options),
220
+ llmCall: llmCallFn,
221
+ mcpBridge: currentMcpBridge,
222
+ mcpTools: filteredMcpTools,
223
+ mapConcurrency: options.mapConcurrency || 5,
224
+ timeoutMs: options.timeoutMs || 300000,
225
+ maxLoopIterations: options.maxLoopIterations || 5000,
226
+ tracer,
227
+ sessionStore,
228
+ outputBuffer,
229
+ };
230
+ }
231
+
232
+ cachedMcpBridge = currentMcpBridge;
233
+ runtime = createDSLRuntime(runtimeOptions);
234
+ return runtime;
235
+ }
236
+
237
+ /**
238
+ * Get or rebuild the runtime if MCP state has changed.
239
+ */
240
+ function getRuntime() {
241
+ const currentMcpBridge = getMcpBridge();
242
+ // Rebuild runtime if MCP bridge changed (null -> bridge, or different bridge)
243
+ if (!runtime || cachedMcpBridge !== currentMcpBridge) {
244
+ buildRuntime();
245
+ }
246
+ return runtime;
197
247
  }
198
248
 
199
- const runtime = createDSLRuntime(runtimeOptions);
200
249
  const maxRetries = options.maxRetries ?? 2;
201
250
 
202
251
  return tool({
@@ -272,7 +321,7 @@ RULES REMINDER:
272
321
  }
273
322
  }
274
323
 
275
- const result = await runtime.execute(currentCode, description);
324
+ const result = await getRuntime().execute(currentCode, description);
276
325
 
277
326
  if (result.status === 'success') {
278
327
  finalOutput = formatSuccess(result, description, attempt, outputBuffer);
@@ -283,7 +283,15 @@ export const searchTool = (options = {}) => {
283
283
  extractOptions.format = 'xml';
284
284
  }
285
285
 
286
- return await extract(extractOptions);
286
+ const extractResult = await extract(extractOptions);
287
+
288
+ // Strip workspace root prefix from extract output so paths are relative
289
+ if (resolutionBase && typeof extractResult === 'string') {
290
+ const wsPrefix = resolutionBase.endsWith('/') ? resolutionBase : resolutionBase + '/';
291
+ return extractResult.split(wsPrefix).join('');
292
+ }
293
+
294
+ return extractResult;
287
295
  } catch (error) {
288
296
  console.error('Delegated search failed, falling back to raw search:', error);
289
297
  try {