@probelabs/probe 0.6.0-rc100

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.
Files changed (115) hide show
  1. package/README.md +583 -0
  2. package/bin/.gitkeep +0 -0
  3. package/bin/probe +158 -0
  4. package/bin/probe-binary +0 -0
  5. package/build/agent/ProbeAgent.d.ts +199 -0
  6. package/build/agent/ProbeAgent.js +1486 -0
  7. package/build/agent/acp/README.md +347 -0
  8. package/build/agent/acp/connection.js +237 -0
  9. package/build/agent/acp/connection.test.js +311 -0
  10. package/build/agent/acp/examples/simple-client.js +212 -0
  11. package/build/agent/acp/examples/tool-lifecycle.js +230 -0
  12. package/build/agent/acp/final-test.js +173 -0
  13. package/build/agent/acp/index.js +5 -0
  14. package/build/agent/acp/integration.test.js +385 -0
  15. package/build/agent/acp/manual-test.js +410 -0
  16. package/build/agent/acp/protocol-test.js +190 -0
  17. package/build/agent/acp/server.js +448 -0
  18. package/build/agent/acp/server.test.js +371 -0
  19. package/build/agent/acp/test-runner.js +216 -0
  20. package/build/agent/acp/test-utils/README.md +315 -0
  21. package/build/agent/acp/test-utils/acp-tester.js +484 -0
  22. package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
  23. package/build/agent/acp/tools.js +368 -0
  24. package/build/agent/acp/tools.test.js +334 -0
  25. package/build/agent/acp/types.js +218 -0
  26. package/build/agent/acp/types.test.js +327 -0
  27. package/build/agent/appTracer.js +360 -0
  28. package/build/agent/fileSpanExporter.js +169 -0
  29. package/build/agent/index.js +7426 -0
  30. package/build/agent/mcp/client.js +338 -0
  31. package/build/agent/mcp/config.js +313 -0
  32. package/build/agent/mcp/index.js +64 -0
  33. package/build/agent/mcp/xmlBridge.js +371 -0
  34. package/build/agent/mockProvider.js +53 -0
  35. package/build/agent/probeTool.js +257 -0
  36. package/build/agent/schemaUtils.js +1726 -0
  37. package/build/agent/simpleTelemetry.js +267 -0
  38. package/build/agent/telemetry.js +225 -0
  39. package/build/agent/tokenCounter.js +395 -0
  40. package/build/agent/tools.js +163 -0
  41. package/build/cli.js +49 -0
  42. package/build/delegate.js +267 -0
  43. package/build/directory-resolver.js +237 -0
  44. package/build/downloader.js +750 -0
  45. package/build/extract.js +149 -0
  46. package/build/index.js +70 -0
  47. package/build/mcp/index.js +514 -0
  48. package/build/mcp/index.ts +608 -0
  49. package/build/query.js +116 -0
  50. package/build/search.js +247 -0
  51. package/build/tools/common.js +410 -0
  52. package/build/tools/index.js +40 -0
  53. package/build/tools/langchain.js +88 -0
  54. package/build/tools/system-message.js +121 -0
  55. package/build/tools/vercel.js +271 -0
  56. package/build/utils/file-lister.js +193 -0
  57. package/build/utils.js +128 -0
  58. package/cjs/agent/ProbeAgent.cjs +5829 -0
  59. package/cjs/index.cjs +6217 -0
  60. package/cjs/package.json +3 -0
  61. package/index.d.ts +401 -0
  62. package/package.json +114 -0
  63. package/scripts/postinstall.js +172 -0
  64. package/src/agent/ProbeAgent.d.ts +199 -0
  65. package/src/agent/ProbeAgent.js +1486 -0
  66. package/src/agent/acp/README.md +347 -0
  67. package/src/agent/acp/connection.js +237 -0
  68. package/src/agent/acp/connection.test.js +311 -0
  69. package/src/agent/acp/examples/simple-client.js +212 -0
  70. package/src/agent/acp/examples/tool-lifecycle.js +230 -0
  71. package/src/agent/acp/final-test.js +173 -0
  72. package/src/agent/acp/index.js +5 -0
  73. package/src/agent/acp/integration.test.js +385 -0
  74. package/src/agent/acp/manual-test.js +410 -0
  75. package/src/agent/acp/protocol-test.js +190 -0
  76. package/src/agent/acp/server.js +448 -0
  77. package/src/agent/acp/server.test.js +371 -0
  78. package/src/agent/acp/test-runner.js +216 -0
  79. package/src/agent/acp/test-utils/README.md +315 -0
  80. package/src/agent/acp/test-utils/acp-tester.js +484 -0
  81. package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
  82. package/src/agent/acp/tools.js +368 -0
  83. package/src/agent/acp/tools.test.js +334 -0
  84. package/src/agent/acp/types.js +218 -0
  85. package/src/agent/acp/types.test.js +327 -0
  86. package/src/agent/appTracer.js +360 -0
  87. package/src/agent/fileSpanExporter.js +169 -0
  88. package/src/agent/index.js +813 -0
  89. package/src/agent/mcp/client.js +338 -0
  90. package/src/agent/mcp/config.js +313 -0
  91. package/src/agent/mcp/index.js +64 -0
  92. package/src/agent/mcp/xmlBridge.js +371 -0
  93. package/src/agent/mockProvider.js +53 -0
  94. package/src/agent/probeTool.js +257 -0
  95. package/src/agent/schemaUtils.js +1726 -0
  96. package/src/agent/simpleTelemetry.js +267 -0
  97. package/src/agent/telemetry.js +225 -0
  98. package/src/agent/tokenCounter.js +395 -0
  99. package/src/agent/tools.js +163 -0
  100. package/src/cli.js +49 -0
  101. package/src/delegate.js +267 -0
  102. package/src/directory-resolver.js +237 -0
  103. package/src/downloader.js +750 -0
  104. package/src/extract.js +149 -0
  105. package/src/index.js +70 -0
  106. package/src/mcp/index.ts +608 -0
  107. package/src/query.js +116 -0
  108. package/src/search.js +247 -0
  109. package/src/tools/common.js +410 -0
  110. package/src/tools/index.js +40 -0
  111. package/src/tools/langchain.js +88 -0
  112. package/src/tools/system-message.js +121 -0
  113. package/src/tools/vercel.js +271 -0
  114. package/src/utils/file-lister.js +193 -0
  115. package/src/utils.js +128 -0
@@ -0,0 +1,813 @@
1
+ import { ProbeAgent } from './ProbeAgent.js';
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ ErrorCode,
7
+ ListToolsRequestSchema,
8
+ McpError,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { readFileSync, existsSync } from 'fs';
11
+ import { resolve } from 'path';
12
+ import { initializeSimpleTelemetryFromOptions, SimpleAppTracer } from './simpleTelemetry.js';
13
+ import {
14
+ cleanSchemaResponse,
15
+ processSchemaResponse,
16
+ isJsonSchema,
17
+ validateJsonResponse,
18
+ createJsonCorrectionPrompt,
19
+ isMermaidSchema,
20
+ validateMermaidResponse,
21
+ createMermaidCorrectionPrompt,
22
+ validateAndFixMermaidResponse
23
+ } from './schemaUtils.js';
24
+ import { ACPServer } from './acp/index.js';
25
+
26
+ // Helper function to detect if input is a file path and read it
27
+ function readInputContent(input) {
28
+ if (!input) return null;
29
+
30
+ // Check if the input looks like a file path and exists
31
+ try {
32
+ const resolvedPath = resolve(input);
33
+ if (existsSync(resolvedPath)) {
34
+ return readFileSync(resolvedPath, 'utf-8').trim();
35
+ }
36
+ } catch (error) {
37
+ // If file reading fails, treat as literal string
38
+ }
39
+
40
+ // Return as literal string if not a valid file
41
+ return input;
42
+ }
43
+
44
+ // Function to check if stdin has data available
45
+ function isStdinAvailable() {
46
+ // Check if stdin is not a TTY (indicates piped input)
47
+ // Also ensure we're not in an interactive terminal session
48
+ return !process.stdin.isTTY && process.stdin.readable;
49
+ }
50
+
51
+ // Function to read from stdin with timeout detection for interactive vs piped usage
52
+ function readFromStdin() {
53
+ return new Promise((resolve, reject) => {
54
+ let data = '';
55
+ let hasReceivedData = false;
56
+ let dataChunks = [];
57
+
58
+ // Short timeout to detect if this is interactive usage (no immediate data)
59
+ const timeout = setTimeout(() => {
60
+ if (!hasReceivedData) {
61
+ reject(new Error('INTERACTIVE_MODE'));
62
+ }
63
+ }, 100); // Very short timeout - piped input should arrive immediately
64
+
65
+ process.stdin.setEncoding('utf8');
66
+
67
+ // Try to read immediately to see if data is available
68
+ process.stdin.on('readable', () => {
69
+ let chunk;
70
+ while ((chunk = process.stdin.read()) !== null) {
71
+ hasReceivedData = true;
72
+ clearTimeout(timeout);
73
+ dataChunks.push(chunk);
74
+ data += chunk;
75
+ }
76
+ });
77
+
78
+ process.stdin.on('end', () => {
79
+ clearTimeout(timeout);
80
+ const trimmed = data.trim();
81
+ if (!trimmed && dataChunks.length === 0) {
82
+ reject(new Error('No input received from stdin'));
83
+ } else {
84
+ resolve(trimmed);
85
+ }
86
+ });
87
+
88
+ process.stdin.on('error', (error) => {
89
+ clearTimeout(timeout);
90
+ reject(error);
91
+ });
92
+
93
+ // Force a read attempt to trigger readable event if data is available
94
+ process.nextTick(() => {
95
+ const chunk = process.stdin.read();
96
+ if (chunk !== null) {
97
+ hasReceivedData = true;
98
+ clearTimeout(timeout);
99
+ data += chunk;
100
+ dataChunks.push(chunk);
101
+ }
102
+ });
103
+ });
104
+ }
105
+
106
+ // Parse command line arguments
107
+ function parseArgs() {
108
+ const args = process.argv.slice(2);
109
+ const config = {
110
+ mcp: false,
111
+ acp: false,
112
+ question: null,
113
+ path: null,
114
+ prompt: null,
115
+ systemPrompt: null,
116
+ schema: null,
117
+ provider: null,
118
+ model: null,
119
+ allowEdit: false,
120
+ verbose: false,
121
+ help: false,
122
+ maxIterations: null,
123
+ maxResponseTokens: null,
124
+ traceFile: undefined,
125
+ traceRemote: undefined,
126
+ traceConsole: false,
127
+ useStdin: false, // New flag to indicate stdin should be used
128
+ outline: false, // New flag to enable outline format
129
+ noMermaidValidation: false // New flag to disable mermaid validation
130
+ };
131
+
132
+ for (let i = 0; i < args.length; i++) {
133
+ const arg = args[i];
134
+
135
+ if (arg === '--mcp') {
136
+ config.mcp = true;
137
+ } else if (arg === '--acp') {
138
+ config.acp = true;
139
+ } else if (arg === '--help' || arg === '-h') {
140
+ config.help = true;
141
+ } else if (arg === '--verbose') {
142
+ config.verbose = true;
143
+ } else if (arg === '--allow-edit') {
144
+ config.allowEdit = true;
145
+ } else if (arg === '--path' && i + 1 < args.length) {
146
+ config.path = args[++i];
147
+ } else if (arg === '--prompt' && i + 1 < args.length) {
148
+ config.prompt = args[++i];
149
+ } else if (arg === '--system-prompt' && i + 1 < args.length) {
150
+ config.systemPrompt = args[++i];
151
+ } else if (arg === '--schema' && i + 1 < args.length) {
152
+ config.schema = args[++i];
153
+ } else if (arg === '--provider' && i + 1 < args.length) {
154
+ config.provider = args[++i];
155
+ } else if (arg === '--model' && i + 1 < args.length) {
156
+ config.model = args[++i];
157
+ } else if (arg === '--max-iterations' && i + 1 < args.length) {
158
+ config.maxIterations = parseInt(args[++i], 10);
159
+ } else if (arg === '--max-response-tokens' && i + 1 < args.length) {
160
+ config.maxResponseTokens = parseInt(args[++i], 10);
161
+ } else if (arg === '--trace-file' && i + 1 < args.length) {
162
+ config.traceFile = args[++i];
163
+ } else if (arg === '--trace-remote' && i + 1 < args.length) {
164
+ config.traceRemote = args[++i];
165
+ } else if (arg === '--trace-console') {
166
+ config.traceConsole = true;
167
+ } else if (arg === '--outline') {
168
+ config.outline = true;
169
+ } else if (arg === '--no-mermaid-validation') {
170
+ config.noMermaidValidation = true;
171
+ } else if (!arg.startsWith('--') && !config.question) {
172
+ // First non-flag argument is the question
173
+ config.question = arg;
174
+ }
175
+ }
176
+
177
+ // Auto-detect stdin usage if no question provided and stdin appears to be piped
178
+ // For simplicity, let's use a more practical approach:
179
+ // If user provides no arguments at all, we try to read from stdin with a short timeout
180
+ // This works better across different environments
181
+ if (!config.question && !config.mcp && !config.acp && !config.help) {
182
+ // We'll check for stdin in the main function with a timeout approach
183
+ config.useStdin = true;
184
+ }
185
+
186
+ return config;
187
+ }
188
+
189
+ // Show help message
190
+ function showHelp() {
191
+ console.log(`
192
+ probe agent - AI-powered code exploration tool
193
+
194
+ Usage:
195
+ probe agent <question> Answer a question about the codebase
196
+ probe agent <file> Read question from file
197
+ echo "question" | probe agent Read question from stdin (pipe input)
198
+ probe agent --mcp Start as MCP server
199
+ probe agent --acp Start as ACP server
200
+
201
+ Options:
202
+ --path <dir> Search directory (default: current)
203
+ --prompt <type> Persona: code-explorer, engineer, code-review, support, architect
204
+ --system-prompt <text|file> Custom system prompt (text or file path)
205
+ --schema <schema|file> Output schema (JSON, XML, any format - text or file path)
206
+ --provider <name> Force AI provider: anthropic, openai, google
207
+ --model <name> Override model name
208
+ --allow-edit Enable code modification capabilities
209
+ --verbose Enable verbose output
210
+ --outline Use outline-xml format for code search results
211
+ --mcp Run as MCP server
212
+ --acp Run as ACP server (Agent Client Protocol)
213
+ --max-iterations <number> Max tool iterations (default: 30)
214
+ --max-response-tokens <number> Max tokens for AI response (overrides model defaults)
215
+ --trace-file <path> Enable tracing to file (JSONL format)
216
+ --trace-remote <endpoint> Enable tracing to remote OTLP endpoint
217
+ --trace-console Enable tracing to console output
218
+ --no-mermaid-validation Disable automatic mermaid diagram validation and fixing
219
+ --help, -h Show this help message
220
+
221
+ Environment Variables:
222
+ ANTHROPIC_API_KEY Anthropic Claude API key
223
+ OPENAI_API_KEY OpenAI GPT API key
224
+ GOOGLE_API_KEY Google Gemini API key
225
+ FORCE_PROVIDER Force specific provider (anthropic, openai, google)
226
+ MODEL_NAME Override model name
227
+ MAX_RESPONSE_TOKENS Maximum tokens for AI response
228
+ DEBUG Enable verbose mode (set to '1')
229
+
230
+ Examples:
231
+ probe agent "How does authentication work?"
232
+ probe agent question.txt # Read question from file
233
+ echo "How does the search algorithm work?" | probe agent # Read from stdin
234
+ cat requirements.txt | probe agent --prompt architect # Pipe file content
235
+ probe agent "Find all database queries" --path ./src --prompt engineer
236
+ probe agent "Review this code for bugs" --prompt code-review --system-prompt custom-prompt.txt
237
+ probe agent "List all functions" --schema '{"functions": [{"name": "string", "file": "string"}]}'
238
+ probe agent "Analyze codebase" --schema schema.json # Schema from file
239
+ probe agent "Debug issue" --trace-file ./debug.jsonl --verbose
240
+ probe agent "Analyze code" --trace-remote http://localhost:4318/v1/traces
241
+ probe agent --mcp # Start MCP server mode
242
+ probe agent --acp # Start ACP server mode
243
+
244
+ Personas:
245
+ code-explorer Default. Explores and explains code structure and functionality
246
+ engineer Senior engineer focused on implementation and architecture
247
+ code-review Reviews code for bugs, performance, and best practices
248
+ support Helps troubleshoot issues and solve problems
249
+ architect Focuses on software architecture and high-level design
250
+ `);
251
+ }
252
+
253
+ // MCP Server implementation
254
+ class ProbeAgentMcpServer {
255
+ constructor() {
256
+ this.server = new Server(
257
+ {
258
+ name: '@buger/probe-agent',
259
+ version: '1.0.0',
260
+ },
261
+ {
262
+ capabilities: {
263
+ tools: {},
264
+ },
265
+ }
266
+ );
267
+
268
+ this.setupToolHandlers();
269
+ this.server.onerror = (error) => console.error('[MCP Error]', error);
270
+ process.on('SIGINT', async () => {
271
+ await this.server.close();
272
+ process.exit(0);
273
+ });
274
+ }
275
+
276
+ setupToolHandlers() {
277
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
278
+ tools: [
279
+ {
280
+ name: 'search_code',
281
+ description: "Search code and answer questions about the codebase using an AI agent. This tool provides intelligent responses based on code analysis.",
282
+ inputSchema: {
283
+ type: 'object',
284
+ properties: {
285
+ query: {
286
+ type: 'string',
287
+ description: 'The question or request about the codebase.',
288
+ },
289
+ path: {
290
+ type: 'string',
291
+ description: 'Optional path to the directory to search in. Defaults to current directory.',
292
+ },
293
+ prompt: {
294
+ type: 'string',
295
+ description: 'Optional persona type: code-explorer, engineer, code-review, support, architect.',
296
+ },
297
+ system_prompt: {
298
+ type: 'string',
299
+ description: 'Optional custom system prompt (text or file path).',
300
+ },
301
+ provider: {
302
+ type: 'string',
303
+ description: 'Optional AI provider to force: anthropic, openai, google.',
304
+ },
305
+ model: {
306
+ type: 'string',
307
+ description: 'Optional model name override.',
308
+ },
309
+ allow_edit: {
310
+ type: 'boolean',
311
+ description: 'Enable code modification capabilities.',
312
+ },
313
+ max_iterations: {
314
+ type: 'number',
315
+ description: 'Maximum number of tool iterations (default: 30).',
316
+ },
317
+ max_response_tokens: {
318
+ type: 'number',
319
+ description: 'Maximum tokens for AI response (overrides model defaults).',
320
+ },
321
+ schema: {
322
+ type: 'string',
323
+ description: 'Optional output schema (JSON, XML, or any format - text or file path).',
324
+ },
325
+ no_mermaid_validation: {
326
+ type: 'boolean',
327
+ description: 'Disable automatic mermaid diagram validation and fixing.',
328
+ }
329
+ },
330
+ required: ['query']
331
+ },
332
+ },
333
+ ],
334
+ }));
335
+
336
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
337
+ if (request.params.name !== 'search_code') {
338
+ throw new McpError(
339
+ ErrorCode.MethodNotFound,
340
+ `Unknown tool: ${request.params.name}`
341
+ );
342
+ }
343
+
344
+ try {
345
+ const args = request.params.arguments;
346
+
347
+ // Validate required fields
348
+ if (!args.query) {
349
+ throw new Error("Query is required");
350
+ }
351
+
352
+ // Set MAX_TOOL_ITERATIONS if provided
353
+ if (args.max_iterations) {
354
+ process.env.MAX_TOOL_ITERATIONS = args.max_iterations.toString();
355
+ }
356
+
357
+ // Process system prompt if provided (could be file or literal string)
358
+ let systemPrompt = null;
359
+ if (args.system_prompt) {
360
+ systemPrompt = readInputContent(args.system_prompt);
361
+ if (!systemPrompt) {
362
+ throw new Error('System prompt could not be read');
363
+ }
364
+ }
365
+
366
+ // Process query input (could be file or literal string)
367
+ const query = readInputContent(args.query);
368
+ if (!query) {
369
+ throw new Error('Query is required and could not be read');
370
+ }
371
+
372
+ // Process schema if provided (could be file or literal string)
373
+ let schema = null;
374
+ if (args.schema) {
375
+ schema = readInputContent(args.schema);
376
+ if (!schema) {
377
+ throw new Error('Schema could not be read');
378
+ }
379
+ }
380
+
381
+ // Create agent with configuration
382
+ const agentConfig = {
383
+ path: args.path || process.cwd(),
384
+ promptType: args.prompt || 'code-explorer',
385
+ customPrompt: systemPrompt,
386
+ provider: args.provider,
387
+ model: args.model,
388
+ allowEdit: !!args.allow_edit,
389
+ debug: process.env.DEBUG === '1',
390
+ maxResponseTokens: args.max_response_tokens,
391
+ disableMermaidValidation: !!args.no_mermaid_validation
392
+ };
393
+
394
+ const agent = new ProbeAgent(agentConfig);
395
+ let result = await agent.answer(query, [], { schema });
396
+
397
+ // If schema is provided, make a follow-up request to format the output
398
+ if (schema) {
399
+ const schemaPrompt = `Now you need to respond according to this schema:\n\n${schema}\n\nPlease reformat your previous response to match this schema exactly. Only return the formatted response, no additional text.`;
400
+
401
+ try {
402
+ result = await agent.answer(schemaPrompt, [], { schema });
403
+ // Clean the schema response to remove code blocks and formatting
404
+ result = cleanSchemaResponse(result);
405
+
406
+ // Check for mermaid diagrams in response and validate/fix them regardless of schema
407
+ if (!args.no_mermaid_validation) {
408
+ try {
409
+ const mermaidValidation = await validateAndFixMermaidResponse(result, {
410
+ debug: args.debug,
411
+ path: agentConfig.path,
412
+ provider: args.provider,
413
+ model: args.model
414
+ });
415
+
416
+ if (mermaidValidation.wasFixed) {
417
+ result = mermaidValidation.fixedResponse;
418
+ if (args.debug) {
419
+ console.error(`[DEBUG] Mermaid diagrams fixed using specialized agent`);
420
+ mermaidValidation.fixingResults.forEach((fixResult, index) => {
421
+ if (fixResult.wasFixed) {
422
+ console.error(`[DEBUG] Fixed diagram ${index + 1}: ${fixResult.originalError}`);
423
+ }
424
+ });
425
+ }
426
+ } else if (!mermaidValidation.isValid && mermaidValidation.diagrams && mermaidValidation.diagrams.length > 0 && args.debug) {
427
+ console.error(`[DEBUG] Mermaid validation failed: ${mermaidValidation.errors?.join(', ')}`);
428
+ }
429
+ } catch (error) {
430
+ if (args.debug) {
431
+ console.error(`[DEBUG] Enhanced mermaid validation failed: ${error.message}`);
432
+ }
433
+ }
434
+ } else if (args.debug) {
435
+ console.error(`[DEBUG] Mermaid validation skipped due to --no-mermaid-validation flag`);
436
+ }
437
+
438
+ // Then, if schema expects JSON, validate and retry if invalid
439
+ if (isJsonSchema(schema)) {
440
+ const validation = validateJsonResponse(result);
441
+ if (!validation.isValid) {
442
+ // Retry once with correction prompt
443
+ const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
444
+ try {
445
+ result = await agent.answer(correctionPrompt, [], { schema });
446
+ result = cleanSchemaResponse(result);
447
+
448
+ // Validate again after correction
449
+ const finalValidation = validateJsonResponse(result);
450
+ if (!finalValidation.isValid && args.debug) {
451
+ console.error(`[DEBUG] JSON validation failed after retry: ${finalValidation.error}`);
452
+ }
453
+ } catch (retryError) {
454
+ // If retry fails, keep the original result
455
+ if (args.debug) {
456
+ console.error(`[DEBUG] JSON correction retry failed: ${retryError.message}`);
457
+ }
458
+ }
459
+ }
460
+ }
461
+ } catch (error) {
462
+ // If schema formatting fails, use original result
463
+ }
464
+ }
465
+
466
+ // Get token usage for debugging
467
+ const tokenUsage = agent.getTokenUsage();
468
+ console.error(`Token usage: ${JSON.stringify(tokenUsage)}`);
469
+
470
+ return {
471
+ content: [
472
+ {
473
+ type: 'text',
474
+ text: result,
475
+ },
476
+ ],
477
+ };
478
+ } catch (error) {
479
+ console.error(`Error executing search_code:`, error);
480
+ return {
481
+ content: [
482
+ {
483
+ type: 'text',
484
+ text: `Error: ${error.message}`,
485
+ },
486
+ ],
487
+ isError: true,
488
+ };
489
+ }
490
+ });
491
+ }
492
+
493
+ async run() {
494
+ const transport = new StdioServerTransport();
495
+ await this.server.connect(transport);
496
+ console.error('Probe Agent MCP server running on stdio');
497
+ }
498
+ }
499
+
500
+ // Main function
501
+ async function main() {
502
+ const config = parseArgs();
503
+
504
+ if (config.help) {
505
+ showHelp();
506
+ return;
507
+ }
508
+
509
+ if (config.mcp) {
510
+ // Start as MCP server
511
+ const server = new ProbeAgentMcpServer();
512
+ await server.run();
513
+ return;
514
+ }
515
+
516
+ if (config.acp) {
517
+ // Start as ACP server
518
+ const server = new ACPServer({
519
+ provider: config.provider,
520
+ model: config.model,
521
+ path: config.path,
522
+ allowEdit: config.allowEdit,
523
+ debug: config.verbose
524
+ });
525
+ await server.start();
526
+ return;
527
+ }
528
+
529
+ // Handle stdin input if detected
530
+ if (config.useStdin) {
531
+ try {
532
+ if (config.verbose) {
533
+ console.error('[DEBUG] Reading question from stdin...');
534
+ }
535
+ config.question = await readFromStdin();
536
+ if (!config.question) {
537
+ console.error('Error: No input received from stdin');
538
+ process.exit(1);
539
+ }
540
+ } catch (error) {
541
+ // If this is interactive mode (no piped input), show help
542
+ if (error.message === 'INTERACTIVE_MODE') {
543
+ showHelp();
544
+ process.exit(0);
545
+ } else {
546
+ console.error(`Error reading from stdin: ${error.message}`);
547
+ process.exit(1);
548
+ }
549
+ }
550
+ }
551
+
552
+ if (!config.question) {
553
+ showHelp();
554
+ process.exit(1);
555
+ }
556
+
557
+ try {
558
+ // Initialize tracing if any tracing options are provided
559
+ let telemetryConfig = null;
560
+ let appTracer = null;
561
+ if (config.traceFile !== undefined || config.traceRemote !== undefined || config.traceConsole) {
562
+ try {
563
+ telemetryConfig = initializeSimpleTelemetryFromOptions(config);
564
+ appTracer = new SimpleAppTracer(telemetryConfig);
565
+ if (config.verbose) {
566
+ console.error('[DEBUG] Simple tracing initialized');
567
+ }
568
+ } catch (error) {
569
+ if (config.verbose) {
570
+ console.error(`[DEBUG] Failed to initialize tracing: ${error.message}`);
571
+ }
572
+ }
573
+ }
574
+
575
+ // Set environment variables if provided via flags
576
+ if (config.verbose) {
577
+ process.env.DEBUG = '1';
578
+ }
579
+ if (config.provider) {
580
+ process.env.FORCE_PROVIDER = config.provider;
581
+ }
582
+ if (config.model) {
583
+ process.env.MODEL_NAME = config.model;
584
+ }
585
+ if (config.maxIterations) {
586
+ process.env.MAX_TOOL_ITERATIONS = config.maxIterations.toString();
587
+ }
588
+
589
+ // Process question input (could be file or literal string)
590
+ const question = readInputContent(config.question);
591
+ if (!question) {
592
+ console.error('Error: Question is required and could not be read');
593
+ process.exit(1);
594
+ }
595
+
596
+ // Process system prompt if provided (could be file or literal string)
597
+ let systemPrompt = null;
598
+ if (config.systemPrompt) {
599
+ systemPrompt = readInputContent(config.systemPrompt);
600
+ if (!systemPrompt) {
601
+ console.error('Error: System prompt could not be read');
602
+ process.exit(1);
603
+ }
604
+ }
605
+
606
+ // Process schema if provided (could be file or literal string)
607
+ let schema = null;
608
+ if (config.schema) {
609
+ schema = readInputContent(config.schema);
610
+ if (!schema) {
611
+ console.error('Error: Schema could not be read');
612
+ process.exit(1);
613
+ }
614
+ }
615
+
616
+ // Create and configure agent
617
+ const agentConfig = {
618
+ path: config.path,
619
+ promptType: config.prompt,
620
+ customPrompt: systemPrompt,
621
+ allowEdit: config.allowEdit,
622
+ debug: config.verbose,
623
+ tracer: appTracer,
624
+ outline: config.outline,
625
+ maxResponseTokens: config.maxResponseTokens,
626
+ disableMermaidValidation: config.noMermaidValidation
627
+ };
628
+
629
+ const agent = new ProbeAgent(agentConfig);
630
+
631
+ // Execute with tracing if available
632
+ let result;
633
+ if (appTracer) {
634
+ const sessionSpan = appTracer.createSessionSpan({
635
+ 'question': question.substring(0, 100) + (question.length > 100 ? '...' : ''),
636
+ 'path': config.path || process.cwd(),
637
+ 'prompt_type': config.prompt || 'code-explorer'
638
+ });
639
+
640
+ try {
641
+ result = await appTracer.withSpan('agent.answer',
642
+ () => agent.answer(question, [], { schema }),
643
+ { 'question.length': question.length }
644
+ );
645
+ } finally {
646
+ if (sessionSpan) {
647
+ sessionSpan.end();
648
+ }
649
+ }
650
+ } else {
651
+ result = await agent.answer(question, [], { schema });
652
+ }
653
+
654
+ // If schema is provided, make a follow-up request to format the output
655
+ if (schema) {
656
+ if (config.verbose) {
657
+ console.error('[DEBUG] Schema provided, making follow-up request to format output...');
658
+ }
659
+
660
+ const schemaPrompt = `Now you need to respond according to this schema:\n\n${schema}\n\nPlease reformat your previous response to match this schema exactly. Only return the formatted response, no additional text.`;
661
+
662
+ try {
663
+ if (appTracer) {
664
+ result = await appTracer.withSpan('agent.schema_formatting',
665
+ () => agent.answer(schemaPrompt, [], { schema }),
666
+ { 'schema.length': schema.length }
667
+ );
668
+ } else {
669
+ result = await agent.answer(schemaPrompt, [], { schema });
670
+ }
671
+
672
+ // Clean the schema response to remove code blocks and formatting
673
+ const cleaningResult = processSchemaResponse(result, schema, {
674
+ debug: config.verbose
675
+ });
676
+ result = cleaningResult.cleaned;
677
+
678
+ if (config.verbose && cleaningResult.debug && cleaningResult.debug.wasModified) {
679
+ console.error('[DEBUG] Schema response was cleaned:');
680
+ console.error(` Original length: ${cleaningResult.debug.originalLength}`);
681
+ console.error(` Cleaned length: ${cleaningResult.debug.cleanedLength}`);
682
+ }
683
+
684
+ // Check for mermaid diagrams in response and validate/fix them regardless of schema
685
+ if (!config.noMermaidValidation) {
686
+ try {
687
+ const mermaidValidationResult = await validateAndFixMermaidResponse(result, {
688
+ debug: config.verbose,
689
+ path: config.path,
690
+ provider: config.provider,
691
+ model: config.model,
692
+ tracer: appTracer
693
+ });
694
+
695
+ if (mermaidValidationResult.wasFixed) {
696
+ result = mermaidValidationResult.fixedResponse;
697
+ if (config.verbose) {
698
+ console.error(`[DEBUG] Mermaid diagrams fixed using specialized agent`);
699
+ mermaidValidationResult.fixingResults.forEach((fixResult, index) => {
700
+ if (fixResult.wasFixed) {
701
+ console.error(`[DEBUG] Fixed diagram ${index + 1}: ${fixResult.originalError}`);
702
+ }
703
+ });
704
+ }
705
+ } else if (!mermaidValidationResult.isValid && mermaidValidationResult.diagrams && mermaidValidationResult.diagrams.length > 0 && config.verbose) {
706
+ console.error(`[DEBUG] Mermaid validation failed: ${mermaidValidationResult.errors?.join(', ')}`);
707
+ }
708
+ } catch (error) {
709
+ if (config.verbose) {
710
+ console.error(`[DEBUG] Enhanced mermaid validation failed: ${error.message}`);
711
+ }
712
+ }
713
+ } else if (config.verbose) {
714
+ console.error(`[DEBUG] Mermaid validation skipped due to --no-mermaid-validation flag`);
715
+ }
716
+
717
+ // Then, if schema expects JSON, validate and retry if invalid
718
+ if (isJsonSchema(schema)) {
719
+ const validation = validateJsonResponse(result);
720
+ if (!validation.isValid) {
721
+ if (config.verbose) {
722
+ console.error(`[DEBUG] JSON validation failed: ${validation.error}`);
723
+ console.error('[DEBUG] Attempting to correct JSON...');
724
+ }
725
+
726
+ // Retry once with correction prompt
727
+ const correctionPrompt = createJsonCorrectionPrompt(result, schema, validation.error);
728
+ try {
729
+ if (appTracer) {
730
+ result = await appTracer.withSpan('agent.json_correction',
731
+ () => agent.answer(correctionPrompt, [], { schema }),
732
+ { 'original_error': validation.error }
733
+ );
734
+ } else {
735
+ result = await agent.answer(correctionPrompt, [], { schema });
736
+ }
737
+ result = cleanSchemaResponse(result);
738
+
739
+ // Validate again after correction
740
+ const finalValidation = validateJsonResponse(result);
741
+ if (config.verbose) {
742
+ if (finalValidation.isValid) {
743
+ console.error('[DEBUG] JSON correction successful');
744
+ } else {
745
+ console.error(`[DEBUG] JSON validation failed after retry: ${finalValidation.error}`);
746
+ }
747
+ }
748
+ } catch (retryError) {
749
+ // If retry fails, keep the original result
750
+ if (config.verbose) {
751
+ console.error(`[DEBUG] JSON correction retry failed: ${retryError.message}`);
752
+ }
753
+ }
754
+ } else if (config.verbose) {
755
+ console.error('[DEBUG] JSON validation passed');
756
+ }
757
+ }
758
+ } catch (error) {
759
+ if (config.verbose) {
760
+ console.error('[DEBUG] Schema formatting failed, using original result');
761
+ }
762
+ // If schema formatting fails, use original result
763
+ }
764
+ }
765
+
766
+ // Output the result
767
+ console.log(result);
768
+
769
+ // Show token usage in verbose mode
770
+ if (config.verbose) {
771
+ const tokenUsage = agent.getTokenUsage();
772
+ console.error(`\n[DEBUG] Token usage: ${JSON.stringify(tokenUsage, null, 2)}`);
773
+ }
774
+
775
+ // Flush and shutdown tracing
776
+ if (appTracer) {
777
+ try {
778
+ await appTracer.flush();
779
+ if (config.verbose) {
780
+ console.error('[DEBUG] Tracing flushed');
781
+ }
782
+ } catch (error) {
783
+ if (config.verbose) {
784
+ console.error(`[DEBUG] Failed to flush tracing: ${error.message}`);
785
+ }
786
+ }
787
+ }
788
+
789
+ } catch (error) {
790
+ console.error(`Error: ${error.message}`);
791
+ if (config.verbose) {
792
+ console.error(error.stack);
793
+ }
794
+ process.exit(1);
795
+ }
796
+ }
797
+
798
+ // Handle uncaught exceptions
799
+ process.on('uncaughtException', (error) => {
800
+ console.error('Uncaught Exception:', error);
801
+ process.exit(1);
802
+ });
803
+
804
+ process.on('unhandledRejection', (reason, promise) => {
805
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
806
+ process.exit(1);
807
+ });
808
+
809
+ // Run main function
810
+ main().catch((error) => {
811
+ console.error('Fatal error:', error);
812
+ process.exit(1);
813
+ });