@probelabs/probe 0.6.0-rc223 → 0.6.0-rc225

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.
@@ -264,6 +264,204 @@ export class SimpleAppTracer {
264
264
  }
265
265
  }
266
266
 
267
+ /**
268
+ * Hash content for deduplication/comparison purposes
269
+ * @param {string} content - The content to hash
270
+ * @returns {string} - Hex string hash
271
+ */
272
+ hashContent(content) {
273
+ let hash = 0;
274
+ const len = Math.min(content.length, 1000);
275
+ for (let i = 0; i < len; i++) {
276
+ hash = ((hash << 5) - hash) + content.charCodeAt(i);
277
+ hash |= 0; // Convert to 32-bit integer
278
+ }
279
+ return hash.toString(16);
280
+ }
281
+
282
+ /**
283
+ * Record a conversation turn (assistant response or tool result)
284
+ * @param {string} role - The role (assistant, tool_result)
285
+ * @param {string} content - The turn content
286
+ * @param {Object} metadata - Additional metadata
287
+ */
288
+ recordConversationTurn(role, content, metadata = {}) {
289
+ if (!this.isEnabled()) return;
290
+
291
+ this.addEvent(`conversation.turn.${role}`, {
292
+ 'session.id': this.sessionId,
293
+ 'conversation.role': role,
294
+ 'conversation.content': content.substring(0, 10000),
295
+ 'conversation.content.length': content.length,
296
+ 'conversation.content.hash': this.hashContent(content),
297
+ ...metadata
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Record error events with classification
303
+ * @param {string} errorType - The type of error (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker, etc.)
304
+ * @param {Object} errorDetails - Error details including message, stack, context
305
+ */
306
+ recordErrorEvent(errorType, errorDetails = {}) {
307
+ if (!this.isEnabled()) return;
308
+
309
+ this.addEvent(`error.${errorType}`, {
310
+ 'session.id': this.sessionId,
311
+ 'error.type': errorType,
312
+ 'error.message': errorDetails.message?.substring(0, 1000) || null,
313
+ 'error.stack': errorDetails.stack?.substring(0, 2000) || null,
314
+ 'error.recoverable': errorDetails.recoverable ?? true,
315
+ 'error.context': JSON.stringify(errorDetails.context || {}).substring(0, 1000),
316
+ ...Object.fromEntries(
317
+ Object.entries(errorDetails)
318
+ .filter(([k]) => !['message', 'stack', 'context', 'recoverable'].includes(k))
319
+ .map(([k, v]) => [`error.${k}`, v])
320
+ )
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Record AI thinking/reasoning content
326
+ * @param {string} thinkingContent - The thinking content from AI response
327
+ * @param {Object} metadata - Additional metadata
328
+ */
329
+ recordThinkingContent(thinkingContent, metadata = {}) {
330
+ if (!this.isEnabled() || !thinkingContent) return;
331
+
332
+ this.addEvent('ai.thinking', {
333
+ 'session.id': this.sessionId,
334
+ 'ai.thinking.content': thinkingContent.substring(0, 50000),
335
+ 'ai.thinking.length': thinkingContent.length,
336
+ 'ai.thinking.hash': this.hashContent(thinkingContent),
337
+ ...metadata
338
+ });
339
+ }
340
+
341
+ /**
342
+ * Record AI tool call decision
343
+ * @param {string} toolName - The tool name AI decided to call
344
+ * @param {Object} params - The parameters AI provided
345
+ * @param {Object} metadata - Additional metadata
346
+ */
347
+ recordToolDecision(toolName, params, metadata = {}) {
348
+ if (!this.isEnabled()) return;
349
+
350
+ this.addEvent('ai.tool_decision', {
351
+ 'session.id': this.sessionId,
352
+ 'ai.tool_decision.name': toolName,
353
+ 'ai.tool_decision.params': JSON.stringify(params || {}).substring(0, 2000),
354
+ ...metadata
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Record tool result after execution
360
+ * @param {string} toolName - The tool that was executed
361
+ * @param {string|Object} result - The tool result
362
+ * @param {boolean} success - Whether the tool succeeded
363
+ * @param {number} durationMs - Execution duration in milliseconds
364
+ * @param {Object} metadata - Additional metadata
365
+ */
366
+ recordToolResult(toolName, result, success, durationMs, metadata = {}) {
367
+ if (!this.isEnabled()) return;
368
+
369
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
370
+ this.addEvent('tool.result', {
371
+ 'session.id': this.sessionId,
372
+ 'tool.name': toolName,
373
+ 'tool.result': resultStr.substring(0, 10000),
374
+ 'tool.result.length': resultStr.length,
375
+ 'tool.result.hash': this.hashContent(resultStr),
376
+ 'tool.duration_ms': durationMs,
377
+ 'tool.success': success,
378
+ ...metadata
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Record MCP tool execution start
384
+ * @param {string} toolName - MCP tool name
385
+ * @param {string} serverName - MCP server name
386
+ * @param {Object} params - Tool parameters
387
+ * @param {Object} metadata - Additional metadata
388
+ */
389
+ recordMcpToolStart(toolName, serverName, params, metadata = {}) {
390
+ if (!this.isEnabled()) return;
391
+
392
+ this.addEvent('mcp.tool.start', {
393
+ 'session.id': this.sessionId,
394
+ 'mcp.tool.name': toolName,
395
+ 'mcp.tool.server': serverName || 'unknown',
396
+ 'mcp.tool.params': JSON.stringify(params || {}).substring(0, 2000),
397
+ ...metadata
398
+ });
399
+ }
400
+
401
+ /**
402
+ * Record MCP tool execution end
403
+ * @param {string} toolName - MCP tool name
404
+ * @param {string} serverName - MCP server name
405
+ * @param {string|Object} result - Tool result
406
+ * @param {boolean} success - Whether succeeded
407
+ * @param {number} durationMs - Execution duration
408
+ * @param {string} errorMessage - Error message if failed
409
+ * @param {Object} metadata - Additional metadata
410
+ */
411
+ recordMcpToolEnd(toolName, serverName, result, success, durationMs, errorMessage = null, metadata = {}) {
412
+ if (!this.isEnabled()) return;
413
+
414
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result || '');
415
+ this.addEvent('mcp.tool.end', {
416
+ 'session.id': this.sessionId,
417
+ 'mcp.tool.name': toolName,
418
+ 'mcp.tool.server': serverName || 'unknown',
419
+ 'mcp.tool.result': resultStr.substring(0, 10000),
420
+ 'mcp.tool.result.length': resultStr.length,
421
+ 'mcp.tool.duration_ms': durationMs,
422
+ 'mcp.tool.success': success,
423
+ 'mcp.tool.error': errorMessage,
424
+ ...metadata
425
+ });
426
+ }
427
+
428
+ /**
429
+ * Record iteration lifecycle event
430
+ * @param {string} eventType - start or end
431
+ * @param {number} iteration - Iteration number
432
+ * @param {Object} data - Additional data
433
+ */
434
+ recordIterationEvent(eventType, iteration, data = {}) {
435
+ if (!this.isEnabled()) return;
436
+
437
+ this.addEvent(`iteration.${eventType}`, {
438
+ 'session.id': this.sessionId,
439
+ 'iteration': iteration,
440
+ ...data
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Record per-turn token breakdown
446
+ * @param {number} iteration - Iteration number
447
+ * @param {Object} tokenData - Token metrics
448
+ */
449
+ recordTokenTurn(iteration, tokenData = {}) {
450
+ if (!this.isEnabled()) return;
451
+
452
+ this.addEvent('tokens.turn', {
453
+ 'session.id': this.sessionId,
454
+ 'iteration': iteration,
455
+ 'tokens.input': tokenData.inputTokens || 0,
456
+ 'tokens.output': tokenData.outputTokens || 0,
457
+ 'tokens.total': (tokenData.inputTokens || 0) + (tokenData.outputTokens || 0),
458
+ 'tokens.cache_read': tokenData.cacheReadTokens || 0,
459
+ 'tokens.cache_write': tokenData.cacheWriteTokens || 0,
460
+ 'tokens.context_used': tokenData.contextTokens || 0,
461
+ 'tokens.context_remaining': tokenData.maxContextTokens ? (tokenData.maxContextTokens - (tokenData.contextTokens || 0)) : null
462
+ });
463
+ }
464
+
267
465
  async withSpan(spanName, fn, attributes = {}) {
268
466
  if (!this.isEnabled()) {
269
467
  return fn();
@@ -270,13 +270,16 @@ User: Analyze the diagram in docs/architecture.svg
270
270
  */
271
271
  export function parseXmlToolCallWithThinking(xmlString, validTools) {
272
272
  // Use the shared processing logic
273
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
274
-
275
- // If recovery found an attempt_complete pattern, return it
273
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
274
+
275
+ // If recovery found an attempt_complete pattern, return it with thinking content
276
276
  if (recoveryResult) {
277
- return recoveryResult;
277
+ return { ...recoveryResult, thinkingContent };
278
278
  }
279
279
 
280
280
  // Otherwise, use the original parseXmlToolCall function to parse the cleaned XML string
281
- return parseXmlToolCall(cleanedXmlString, validTools);
281
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
282
+
283
+ // Return tool call with thinking content attached
284
+ return toolCall ? { ...toolCall, thinkingContent } : null;
282
285
  }
package/build/delegate.js CHANGED
@@ -385,6 +385,23 @@ export async function delegate({
385
385
  throw new Error('Task parameter is required and must be a string');
386
386
  }
387
387
 
388
+ // Support runtime timeout override via environment variables when timeout not explicitly passed
389
+ // This allows operators to configure delegation timeouts without code changes
390
+ // Priority: DELEGATION_TIMEOUT_MS (milliseconds) > DELEGATION_TIMEOUT_SECONDS > DELEGATION_TIMEOUT (seconds)
391
+ const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, 'timeout');
392
+ if (!hasExplicitTimeout) {
393
+ const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || '', 10);
394
+ const envTimeoutSeconds = parseInt(
395
+ process.env.DELEGATION_TIMEOUT_SECONDS || process.env.DELEGATION_TIMEOUT || '',
396
+ 10
397
+ );
398
+ if (!Number.isNaN(envTimeoutMs) && envTimeoutMs > 0) {
399
+ timeout = Math.max(1, Math.ceil(envTimeoutMs / 1000));
400
+ } else if (!Number.isNaN(envTimeoutSeconds) && envTimeoutSeconds > 0) {
401
+ timeout = Math.max(1, envTimeoutSeconds);
402
+ }
403
+ }
404
+
388
405
  // Use provided manager or fall back to default singleton
389
406
  const manager = delegationManager || defaultDelegationManager;
390
407
 
@@ -33164,6 +33164,19 @@ async function delegate({
33164
33164
  if (!task || typeof task !== "string") {
33165
33165
  throw new Error("Task parameter is required and must be a string");
33166
33166
  }
33167
+ const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, "timeout");
33168
+ if (!hasExplicitTimeout) {
33169
+ const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || "", 10);
33170
+ const envTimeoutSeconds = parseInt(
33171
+ process.env.DELEGATION_TIMEOUT_SECONDS || process.env.DELEGATION_TIMEOUT || "",
33172
+ 10
33173
+ );
33174
+ if (!Number.isNaN(envTimeoutMs) && envTimeoutMs > 0) {
33175
+ timeout = Math.max(1, Math.ceil(envTimeoutMs / 1e3));
33176
+ } else if (!Number.isNaN(envTimeoutSeconds) && envTimeoutSeconds > 0) {
33177
+ timeout = Math.max(1, envTimeoutSeconds);
33178
+ }
33179
+ }
33167
33180
  const manager = delegationManager || defaultDelegationManager;
33168
33181
  const sessionId = (0, import_crypto2.randomUUID)();
33169
33182
  const startTime = Date.now();
@@ -49538,11 +49551,12 @@ function createTools(configOptions) {
49538
49551
  return tools2;
49539
49552
  }
49540
49553
  function parseXmlToolCallWithThinking(xmlString, validTools) {
49541
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49554
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
49542
49555
  if (recoveryResult) {
49543
- return recoveryResult;
49556
+ return { ...recoveryResult, thinkingContent };
49544
49557
  }
49545
- return parseXmlToolCall(cleanedXmlString, validTools);
49558
+ const toolCall = parseXmlToolCall(cleanedXmlString, validTools);
49559
+ return toolCall ? { ...toolCall, thinkingContent } : null;
49546
49560
  }
49547
49561
  var import_crypto4, implementToolDefinition, listFilesToolDefinition, searchFilesToolDefinition, listSkillsToolDefinition, useSkillToolDefinition, readImageToolDefinition;
49548
49562
  var init_tools2 = __esm({
@@ -88837,25 +88851,27 @@ function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
88837
88851
  function parseHybridXmlToolCall(xmlString, nativeTools = [], mcpBridge = null) {
88838
88852
  const nativeResult = parseNativeXmlToolWithThinking(xmlString, nativeTools);
88839
88853
  if (nativeResult) {
88840
- return { ...nativeResult, type: "native" };
88854
+ const { thinkingContent, ...rest } = nativeResult;
88855
+ return { ...rest, type: "native", thinkingContent };
88841
88856
  }
88842
88857
  if (mcpBridge) {
88843
88858
  const mcpResult = parseXmlMcpToolCall(xmlString, mcpBridge.getToolNames());
88844
88859
  if (mcpResult) {
88845
- return { ...mcpResult, type: "mcp" };
88860
+ const { thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, []);
88861
+ return { ...mcpResult, type: "mcp", thinkingContent };
88846
88862
  }
88847
88863
  }
88848
88864
  return null;
88849
88865
  }
88850
88866
  function parseNativeXmlToolWithThinking(xmlString, validTools) {
88851
- const { cleanedXmlString, recoveryResult } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88867
+ const { cleanedXmlString, recoveryResult, thinkingContent } = processXmlWithThinkingAndRecovery(xmlString, validTools);
88852
88868
  if (recoveryResult) {
88853
- return recoveryResult;
88869
+ return { ...recoveryResult, thinkingContent };
88854
88870
  }
88855
88871
  for (const toolName of validTools) {
88856
88872
  const result = parseNativeXmlTool(cleanedXmlString, toolName);
88857
88873
  if (result) {
88858
- return result;
88874
+ return { ...result, thinkingContent };
88859
88875
  }
88860
88876
  }
88861
88877
  return null;
@@ -99316,6 +99332,181 @@ var init_ProbeAgent = __esm({
99316
99332
  _filterMcpTools(mcpToolNames) {
99317
99333
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
99318
99334
  }
99335
+ /**
99336
+ * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
99337
+ * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
99338
+ * @private
99339
+ */
99340
+ _isAppTracerStyle() {
99341
+ return this.tracer && typeof this.tracer.sessionSpans !== "undefined";
99342
+ }
99343
+ /**
99344
+ * Record an error classification event for telemetry
99345
+ * Provides unified error recording across all error types
99346
+ * @param {string} errorType - Error type (wrapped_tool, unrecognized_tool, no_tool_call, circuit_breaker)
99347
+ * @param {string} message - Error message
99348
+ * @param {Object} context - Additional context data
99349
+ * @param {number} iteration - Current iteration number
99350
+ * @private
99351
+ */
99352
+ _recordErrorTelemetry(errorType, message, context, iteration) {
99353
+ if (!this.tracer) return;
99354
+ if (this._isAppTracerStyle() && typeof this.tracer.recordErrorClassification === "function") {
99355
+ this.tracer.recordErrorClassification(this.sessionId, iteration, errorType, {
99356
+ message,
99357
+ context
99358
+ });
99359
+ } else if (typeof this.tracer.recordErrorEvent === "function") {
99360
+ this.tracer.recordErrorEvent(errorType, {
99361
+ message,
99362
+ context: { ...context, iteration }
99363
+ });
99364
+ } else {
99365
+ this.tracer.addEvent(`error.${errorType}`, {
99366
+ "error.type": errorType,
99367
+ "error.message": message,
99368
+ "error.recoverable": errorType !== "circuit_breaker",
99369
+ "error.context": JSON.stringify(context).substring(0, 1e3),
99370
+ "iteration": iteration
99371
+ });
99372
+ }
99373
+ }
99374
+ /**
99375
+ * Record AI thinking content for telemetry
99376
+ * @param {string} thinkingContent - The thinking content
99377
+ * @param {number} iteration - Current iteration number
99378
+ * @private
99379
+ */
99380
+ _recordThinkingTelemetry(thinkingContent, iteration) {
99381
+ if (!this.tracer || !thinkingContent) return;
99382
+ if (this._isAppTracerStyle() && typeof this.tracer.recordThinkingContent === "function") {
99383
+ this.tracer.recordThinkingContent(this.sessionId, iteration, thinkingContent);
99384
+ } else if (typeof this.tracer.recordThinkingContent === "function") {
99385
+ this.tracer.recordThinkingContent(thinkingContent, { iteration });
99386
+ } else {
99387
+ this.tracer.addEvent("ai.thinking", {
99388
+ "ai.thinking.content": thinkingContent.substring(0, 5e4),
99389
+ "ai.thinking.length": thinkingContent.length,
99390
+ "iteration": iteration
99391
+ });
99392
+ }
99393
+ }
99394
+ /**
99395
+ * Record AI tool decision for telemetry
99396
+ * @param {string} toolName - The tool name
99397
+ * @param {Object} params - Tool parameters
99398
+ * @param {number} responseLength - Length of AI response
99399
+ * @param {number} iteration - Current iteration number
99400
+ * @private
99401
+ */
99402
+ _recordToolDecisionTelemetry(toolName, params, responseLength, iteration) {
99403
+ if (!this.tracer) return;
99404
+ if (this._isAppTracerStyle() && typeof this.tracer.recordAIToolDecision === "function") {
99405
+ this.tracer.recordAIToolDecision(this.sessionId, iteration, toolName, params);
99406
+ } else if (typeof this.tracer.recordToolDecision === "function") {
99407
+ this.tracer.recordToolDecision(toolName, params, {
99408
+ iteration,
99409
+ "ai.tool_decision.raw_response_length": responseLength
99410
+ });
99411
+ } else {
99412
+ this.tracer.addEvent("ai.tool_decision", {
99413
+ "ai.tool_decision.name": toolName,
99414
+ "ai.tool_decision.params": JSON.stringify(params || {}).substring(0, 2e3),
99415
+ "ai.tool_decision.raw_response_length": responseLength,
99416
+ "iteration": iteration
99417
+ });
99418
+ }
99419
+ }
99420
+ /**
99421
+ * Record tool result for telemetry
99422
+ * @param {string} toolName - The tool name
99423
+ * @param {string|Object} result - Tool result
99424
+ * @param {boolean} success - Whether tool succeeded
99425
+ * @param {number} durationMs - Execution duration in milliseconds
99426
+ * @param {number} iteration - Current iteration number
99427
+ * @private
99428
+ */
99429
+ _recordToolResultTelemetry(toolName, result, success, durationMs, iteration) {
99430
+ if (!this.tracer) return;
99431
+ if (this._isAppTracerStyle() && typeof this.tracer.recordToolResult === "function") {
99432
+ this.tracer.recordToolResult(this.sessionId, iteration, toolName, result, success, durationMs);
99433
+ } else if (typeof this.tracer.recordToolResult === "function") {
99434
+ this.tracer.recordToolResult(toolName, result, success, durationMs, { iteration });
99435
+ } else {
99436
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99437
+ this.tracer.addEvent("tool.result", {
99438
+ "tool.name": toolName,
99439
+ "tool.result": resultStr.substring(0, 1e4),
99440
+ "tool.result.length": resultStr.length,
99441
+ "tool.duration_ms": durationMs,
99442
+ "tool.success": success,
99443
+ "iteration": iteration
99444
+ });
99445
+ }
99446
+ }
99447
+ /**
99448
+ * Record MCP tool lifecycle event for telemetry
99449
+ * @param {string} phase - 'start' or 'end'
99450
+ * @param {string} toolName - MCP tool name
99451
+ * @param {Object} params - Tool parameters (for start) or null (for end)
99452
+ * @param {number} iteration - Current iteration number
99453
+ * @param {Object} [endData] - Additional data for end phase (result, success, durationMs, error)
99454
+ * @private
99455
+ */
99456
+ _recordMcpToolTelemetry(phase, toolName, params, iteration, endData = null) {
99457
+ if (!this.tracer) return;
99458
+ if (phase === "start") {
99459
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolStart === "function") {
99460
+ this.tracer.recordMcpToolStart(this.sessionId, iteration, toolName, "mcp", params);
99461
+ } else if (typeof this.tracer.recordMcpToolStart === "function") {
99462
+ this.tracer.recordMcpToolStart(toolName, "mcp", params, { iteration });
99463
+ } else {
99464
+ this.tracer.addEvent("mcp.tool.start", {
99465
+ "mcp.tool.name": toolName,
99466
+ "mcp.tool.server": "mcp",
99467
+ "mcp.tool.params": JSON.stringify(params || {}).substring(0, 2e3),
99468
+ "iteration": iteration
99469
+ });
99470
+ }
99471
+ } else if (phase === "end" && endData) {
99472
+ const { result, success, durationMs, error: error2 } = endData;
99473
+ if (this._isAppTracerStyle() && typeof this.tracer.recordMcpToolEnd === "function") {
99474
+ this.tracer.recordMcpToolEnd(this.sessionId, iteration, toolName, "mcp", result, success, durationMs, error2);
99475
+ } else if (typeof this.tracer.recordMcpToolEnd === "function") {
99476
+ this.tracer.recordMcpToolEnd(toolName, "mcp", result, success, durationMs, error2, { iteration });
99477
+ } else {
99478
+ const resultStr = typeof result === "string" ? result : JSON.stringify(result || "");
99479
+ this.tracer.addEvent("mcp.tool.end", {
99480
+ "mcp.tool.name": toolName,
99481
+ "mcp.tool.server": "mcp",
99482
+ "mcp.tool.result": resultStr.substring(0, 1e4),
99483
+ "mcp.tool.result.length": resultStr.length,
99484
+ "mcp.tool.duration_ms": durationMs,
99485
+ "mcp.tool.success": success,
99486
+ "mcp.tool.error": error2,
99487
+ "iteration": iteration
99488
+ });
99489
+ }
99490
+ }
99491
+ }
99492
+ /**
99493
+ * Record iteration lifecycle event for telemetry
99494
+ * @param {string} phase - 'end' (start is already handled elsewhere)
99495
+ * @param {number} iteration - Current iteration number
99496
+ * @param {Object} data - Additional iteration data
99497
+ * @private
99498
+ */
99499
+ _recordIterationTelemetry(phase, iteration, data2 = {}) {
99500
+ if (!this.tracer) return;
99501
+ if (typeof this.tracer.recordIterationEvent === "function") {
99502
+ this.tracer.recordIterationEvent(phase, iteration, data2);
99503
+ } else {
99504
+ this.tracer.addEvent(`iteration.${phase}`, {
99505
+ "iteration": iteration,
99506
+ ...data2
99507
+ });
99508
+ }
99509
+ }
99319
99510
  /**
99320
99511
  * Initialize the agent asynchronously (must be called after constructor)
99321
99512
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -101270,8 +101461,12 @@ You are working with a repository located at: ${searchDirectory}
101270
101461
  }
101271
101462
  const nativeTools = validTools;
101272
101463
  const parsedTool = this.mcpBridge && !options._disableTools ? parseHybridXmlToolCall(assistantResponseContent, nativeTools, this.mcpBridge) : parseXmlToolCallWithThinking(assistantResponseContent, validTools);
101464
+ if (parsedTool?.thinkingContent) {
101465
+ this._recordThinkingTelemetry(parsedTool.thinkingContent, currentIteration);
101466
+ }
101273
101467
  if (parsedTool) {
101274
101468
  const { toolName, params } = parsedTool;
101469
+ this._recordToolDecisionTelemetry(toolName, params, assistantResponseContent.length, currentIteration);
101275
101470
  if (this.debug) console.log(`[DEBUG] Parsed tool call: ${toolName} with params:`, params);
101276
101471
  if (toolName === "attempt_completion") {
101277
101472
  completionAttempted = true;
@@ -101348,6 +101543,8 @@ You are working with a repository located at: ${searchDirectory}
101348
101543
  } else {
101349
101544
  const { type } = parsedTool;
101350
101545
  if (type === "mcp" && this.mcpBridge && this.mcpBridge.isMcpTool(toolName)) {
101546
+ const mcpStartTime = Date.now();
101547
+ this._recordMcpToolTelemetry("start", toolName, params, currentIteration);
101351
101548
  try {
101352
101549
  if (this.debug) {
101353
101550
  console.error(`
@@ -101377,6 +101574,13 @@ You are working with a repository located at: ${searchDirectory}
101377
101574
  } catch (truncateError) {
101378
101575
  console.error(`[WARN] Tool output truncation failed: ${truncateError.message}`);
101379
101576
  }
101577
+ const mcpDurationMs = Date.now() - mcpStartTime;
101578
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101579
+ result: toolResultContent,
101580
+ success: true,
101581
+ durationMs: mcpDurationMs,
101582
+ error: null
101583
+ });
101380
101584
  if (this.debug) {
101381
101585
  const preview = toolResultContent.length > 500 ? toolResultContent.substring(0, 500) + "..." : toolResultContent;
101382
101586
  console.error(`[DEBUG] ========================================`);
@@ -101390,6 +101594,13 @@ You are working with a repository located at: ${searchDirectory}
101390
101594
  ${toolResultContent}
101391
101595
  </tool_result>` });
101392
101596
  } catch (error2) {
101597
+ const mcpDurationMs = Date.now() - mcpStartTime;
101598
+ this._recordMcpToolTelemetry("end", toolName, null, currentIteration, {
101599
+ result: null,
101600
+ success: false,
101601
+ durationMs: mcpDurationMs,
101602
+ error: error2.message
101603
+ });
101393
101604
  console.error(`Error executing MCP tool ${toolName}:`, error2);
101394
101605
  if (this.debug) {
101395
101606
  console.error(`[DEBUG] ========================================`);
@@ -101481,6 +101692,7 @@ ${errorXml}
101481
101692
  return await this.toolImplementations[toolName].execute(toolParams);
101482
101693
  };
101483
101694
  let toolResult;
101695
+ const toolStartTime = Date.now();
101484
101696
  try {
101485
101697
  if (this.tracer) {
101486
101698
  toolResult = await this.tracer.withSpan("tool.call", executeToolCall, {
@@ -101491,6 +101703,8 @@ ${errorXml}
101491
101703
  } else {
101492
101704
  toolResult = await executeToolCall();
101493
101705
  }
101706
+ const toolDurationMs = Date.now() - toolStartTime;
101707
+ this._recordToolResultTelemetry(toolName, toolResult, true, toolDurationMs, currentIteration);
101494
101708
  if (this.debug) {
101495
101709
  const resultPreview = typeof toolResult === "string" ? toolResult.length > 500 ? toolResult.substring(0, 500) + "..." : toolResult : toolResult ? JSON.stringify(toolResult, null, 2).substring(0, 500) + "..." : "No Result";
101496
101710
  console.error(`[DEBUG] ========================================`);
@@ -101547,6 +101761,20 @@ ${toolResultContent}
101547
101761
  role: "user",
101548
101762
  content: toolResultMessage
101549
101763
  });
101764
+ if (this.tracer) {
101765
+ if (typeof this.tracer.recordConversationTurn === "function") {
101766
+ this.tracer.recordConversationTurn("assistant", assistantResponseContent, {
101767
+ iteration: currentIteration,
101768
+ has_tool_call: true,
101769
+ tool_name: toolName
101770
+ });
101771
+ this.tracer.recordConversationTurn("tool_result", toolResultContent, {
101772
+ iteration: currentIteration,
101773
+ tool_name: toolName,
101774
+ tool_success: true
101775
+ });
101776
+ }
101777
+ }
101550
101778
  if (this.debug) {
101551
101779
  console.log(`[DEBUG] Tool ${toolName} executed successfully. Result length: ${typeof toolResult === "string" ? toolResult.length : JSON.stringify(toolResult).length}`);
101552
101780
  }
@@ -101617,6 +101845,7 @@ ${errorXml}
101617
101845
  if (this.debug) {
101618
101846
  console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
101619
101847
  }
101848
+ this._recordErrorTelemetry("wrapped_tool", "Tool call wrapped in markdown", { toolName: wrappedToolName }, currentIteration);
101620
101849
  const toolError = new ParameterError(
101621
101850
  `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
101622
101851
  {
@@ -101642,6 +101871,7 @@ ${formatErrorForAI(toolError)}
101642
101871
  if (this.debug) {
101643
101872
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
101644
101873
  }
101874
+ this._recordErrorTelemetry("unrecognized_tool", `Unknown tool: ${unrecognizedTool}`, { toolName: unrecognizedTool, validTools }, currentIteration);
101645
101875
  const toolError = new ParameterError(`Tool '${unrecognizedTool}' is not available in this context.`, {
101646
101876
  suggestion: `Available tools: ${validTools.join(", ")}. Please use one of these tools instead.`
101647
101877
  });
@@ -101649,6 +101879,7 @@ ${formatErrorForAI(toolError)}
101649
101879
  ${formatErrorForAI(toolError)}
101650
101880
  </tool_result>`;
101651
101881
  } else {
101882
+ this._recordErrorTelemetry("no_tool_call", "AI response did not contain tool call", { responsePreview: assistantResponseContent.substring(0, 500) }, currentIteration);
101652
101883
  if (currentIteration >= maxIterations) {
101653
101884
  let cleanedResponse = assistantResponseContent;
101654
101885
  cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
@@ -101724,6 +101955,7 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101724
101955
  sameFormatErrorCount++;
101725
101956
  if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
101726
101957
  const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
101958
+ this._recordErrorTelemetry("circuit_breaker", "Format error limit exceeded", { formatErrorCount: sameFormatErrorCount, errorCategory }, currentIteration);
101727
101959
  console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
101728
101960
  finalResult = `Error: Unable to complete request. The AI model repeatedly used incorrect tool call format (${errorDesc}). Please try rephrasing your question or using a different model.`;
101729
101961
  break;
@@ -101737,6 +101969,10 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
101737
101969
  sameFormatErrorCount = 0;
101738
101970
  }
101739
101971
  }
101972
+ this._recordIterationTelemetry("end", currentIteration, {
101973
+ "iteration.completed": completionAttempted,
101974
+ "iteration.message_count": currentMessages.length
101975
+ });
101740
101976
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
101741
101977
  const messagesBefore = currentMessages.length;
101742
101978
  const systemMsg = currentMessages[0];