@probelabs/probe 0.6.0-rc299 → 0.6.0-rc301

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.
@@ -38,6 +38,7 @@ import { existsSync } from 'fs';
38
38
  import { readFile, stat, readdir } from 'fs/promises';
39
39
  import { resolve, isAbsolute, dirname, basename, normalize, sep } from 'path';
40
40
  import { TokenCounter } from './tokenCounter.js';
41
+ import { truncateForSpan } from './simpleTelemetry.js';
41
42
  import { InMemoryStorageAdapter } from './storage/InMemoryStorageAdapter.js';
42
43
  import { HookManager, HOOK_TYPES } from './hooks/HookManager.js';
43
44
  import { SUPPORTED_IMAGE_EXTENSIONS, IMAGE_MIME_TYPES, isFormatSupportedByProvider } from './imageConfig.js';
@@ -4327,9 +4328,7 @@ Double-check your response based on the criteria above. If everything looks good
4327
4328
 
4328
4329
  let aiResult;
4329
4330
  if (this.tracer) {
4330
- const inputPreview = message.length > 1000
4331
- ? message.substring(0, 1000) + '... [truncated]'
4332
- : message;
4331
+ const inputPreview = truncateForSpan(message, 4096);
4333
4332
 
4334
4333
  aiResult = await this.tracer.withSpan('ai.request', executeAIRequest, {
4335
4334
  'ai.model': this.model,
@@ -4340,6 +4339,12 @@ Double-check your response based on the criteria above. If everything looks good
4340
4339
  'max_tokens': maxResponseTokens,
4341
4340
  'temperature': 0.3,
4342
4341
  'message_count': currentMessages.length
4342
+ }, (span, result) => {
4343
+ const text = result?.finalText || '';
4344
+ span.setAttributes({
4345
+ 'ai.output': truncateForSpan(text),
4346
+ 'ai.output_length': text.length
4347
+ });
4343
4348
  });
4344
4349
  } else {
4345
4350
  aiResult = await executeAIRequest();
@@ -4367,6 +4372,14 @@ Double-check your response based on the criteria above. If everything looks good
4367
4372
  finalResult = aiResult.finalText;
4368
4373
  }
4369
4374
 
4375
+ // Ensure finalResult is always a string (#533).
4376
+ // When both structured output and finalText are empty (e.g., timeout,
4377
+ // model returns nothing), downstream callers like delegate() expect a
4378
+ // string and would otherwise throw "not a string".
4379
+ if (finalResult === null || finalResult === undefined) {
4380
+ finalResult = '';
4381
+ }
4382
+
4370
4383
  // Graceful timeout handling: ensure the response clearly indicates
4371
4384
  // the research was interrupted and may be incomplete.
4372
4385
  if (gracefulTimeoutState.triggered) {
@@ -2,6 +2,21 @@ import { existsSync, mkdirSync, createWriteStream } from 'fs';
2
2
  import { dirname } from 'path';
3
3
  import { patchConsole } from './otelLogBridge.js';
4
4
 
5
+ /**
6
+ * Truncate text for span attributes, preserving head and tail for context.
7
+ * For text <= maxLen, returns as-is. For longer text, shows first half and
8
+ * last half of the budget with a separator indicating omitted chars.
9
+ * @param {string} text - The text to truncate
10
+ * @param {number} [maxLen=4096] - Maximum output length
11
+ * @returns {string} The truncated text
12
+ */
13
+ export function truncateForSpan(text, maxLen = 4096) {
14
+ if (!text || text.length <= maxLen) return text || '';
15
+ const half = Math.floor((maxLen - 40) / 2); // 40 chars reserved for separator
16
+ const omitted = text.length - half * 2;
17
+ return text.substring(0, half) + `\n... [${omitted} chars omitted] ...\n` + text.substring(text.length - half);
18
+ }
19
+
5
20
  /**
6
21
  * Simple telemetry implementation for probe-agent
7
22
  * This provides basic tracing functionality without complex OpenTelemetry dependencies
@@ -463,7 +478,7 @@ export class SimpleAppTracer {
463
478
  });
464
479
  }
465
480
 
466
- async withSpan(spanName, fn, attributes = {}) {
481
+ async withSpan(spanName, fn, attributes = {}, onResult = null) {
467
482
  if (!this.isEnabled()) {
468
483
  return fn();
469
484
  }
@@ -476,12 +491,19 @@ export class SimpleAppTracer {
476
491
  try {
477
492
  const result = await fn();
478
493
  span.setStatus('OK');
494
+ if (onResult) {
495
+ try {
496
+ onResult(span, result);
497
+ } catch (_) {
498
+ // Don't let span enrichment errors break the flow
499
+ }
500
+ }
479
501
  return result;
480
502
  } catch (error) {
481
503
  span.setStatus('ERROR');
482
- span.addEvent('exception', {
504
+ span.addEvent('exception', {
483
505
  'exception.message': error.message,
484
- 'exception.stack': error.stack
506
+ 'exception.stack': error.stack
485
507
  });
486
508
  throw error;
487
509
  } finally {
@@ -13,6 +13,7 @@ import { searchSchema, querySchema, extractSchema, delegateSchema, analyzeAllSch
13
13
  import { existsSync } from 'fs';
14
14
  import { formatErrorForAI } from '../utils/error-types.js';
15
15
  import { annotateOutputWithHashes } from './hashline.js';
16
+ import { truncateForSpan } from '../agent/simpleTelemetry.js';
16
17
 
17
18
  /**
18
19
  * Auto-quote search query terms that contain mixed case or underscores.
@@ -539,6 +540,7 @@ export const searchTool = (options = {}) => {
539
540
  tracer: options.tracer || null,
540
541
  enableBash: false,
541
542
  bashConfig: null,
543
+ allowEdit: options.allowEdit || false,
542
544
  architectureFileName: options.architectureFileName || null,
543
545
  promptType: 'code-searcher',
544
546
  allowedTools: ['search', 'extract', 'listFiles'],
@@ -551,6 +553,12 @@ export const searchTool = (options = {}) => {
551
553
  ? await options.tracer.withSpan('search.delegate', runDelegation, {
552
554
  'search.query': searchQuery,
553
555
  'search.path': searchPath
556
+ }, (span, result) => {
557
+ const text = typeof result === 'string' ? result : '';
558
+ span.setAttributes({
559
+ 'search.delegate.output': truncateForSpan(text),
560
+ 'search.delegate.output_length': text.length
561
+ });
554
562
  })
555
563
  : await runDelegation();
556
564
 
@@ -888,7 +896,7 @@ export const extractTool = (options = {}) => {
888
896
  * @returns {Object} Configured delegate tool
889
897
  */
890
898
  export const delegateTool = (options = {}) => {
891
- const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
899
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, allowEdit = false, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
892
900
  // Timeout settings inherited from parent agent
893
901
  timeoutBehavior, maxOperationTimeout, requestTimeout, gracefulTimeoutBonusSteps,
894
902
  negotiatedTimeoutBudget, negotiatedTimeoutMaxRequests, negotiatedTimeoutMaxPerRequest,
@@ -1007,6 +1015,7 @@ export const delegateTool = (options = {}) => {
1007
1015
  model,
1008
1016
  tracer,
1009
1017
  enableBash,
1018
+ allowEdit,
1010
1019
  bashConfig,
1011
1020
  architectureFileName,
1012
1021
  searchDelegate,