@probelabs/probe-chat 0.6.0-rc191 → 0.6.0-rc198
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.
- package/ChatSessionManager.js +6 -3
- package/index.js +55 -50
- package/package.json +3 -3
- package/probeChat.js +111 -7
- package/test/unit/chatSessionManager.test.js +86 -0
- package/webServer.js +14 -28
package/ChatSessionManager.js
CHANGED
|
@@ -63,15 +63,18 @@ export class ChatSessionManager {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Initialize the ChatSessionManager by loading history
|
|
66
|
+
* Initialize the ChatSessionManager by initializing ProbeAgent and loading history
|
|
67
67
|
* This must be called before using chat()
|
|
68
68
|
*/
|
|
69
69
|
async initialize() {
|
|
70
70
|
if (this._ready) return; // Already initialized
|
|
71
|
-
|
|
71
|
+
|
|
72
|
+
// Initialize ProbeAgent (handles API key detection and CLI fallback)
|
|
73
|
+
await this.agent.initialize();
|
|
74
|
+
|
|
72
75
|
await this.loadHistory();
|
|
73
76
|
this._ready = true;
|
|
74
|
-
|
|
77
|
+
|
|
75
78
|
if (this.debug) {
|
|
76
79
|
console.log(`[ChatSessionManager] Initialized session ${this.sessionId} with ${this.agent.history.length} messages from storage`);
|
|
77
80
|
}
|
package/index.js
CHANGED
|
@@ -346,12 +346,6 @@ export function main() {
|
|
|
346
346
|
process.env.PORT = options.port;
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
// Check for API keys
|
|
350
|
-
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
|
|
351
|
-
const openaiApiKey = process.env.OPENAI_API_KEY;
|
|
352
|
-
const googleApiKey = process.env.GOOGLE_API_KEY;
|
|
353
|
-
const hasApiKeys = !!(anthropicApiKey || openaiApiKey || googleApiKey);
|
|
354
|
-
|
|
355
349
|
// --- Bash Configuration Processing ---
|
|
356
350
|
let bashConfig = null;
|
|
357
351
|
if (options.enableBash) {
|
|
@@ -406,16 +400,13 @@ export function main() {
|
|
|
406
400
|
|
|
407
401
|
// --- Web Mode (check before non-interactive to override) ---
|
|
408
402
|
if (options.web) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
logWarn(chalk.yellow('Warning: No API key provided. The web interface will show instructions on how to set up API keys.'));
|
|
412
|
-
}
|
|
413
|
-
// Import and start web server
|
|
403
|
+
// Note: API key / CLI availability is checked lazily in ProbeAgent.initialize()
|
|
404
|
+
// when the first chat message is sent. This allows fallback to Claude Code or Codex CLI.
|
|
414
405
|
import('./webServer.js')
|
|
415
406
|
.then(async module => {
|
|
416
407
|
const { startWebServer } = module;
|
|
417
408
|
logInfo(`Starting web server on port ${process.env.PORT || 8080}...`);
|
|
418
|
-
await startWebServer(version,
|
|
409
|
+
await startWebServer(version, { allowEdit: options.allowEdit });
|
|
419
410
|
})
|
|
420
411
|
.catch(error => {
|
|
421
412
|
logError(chalk.red(`Error starting web server: ${error.message}`));
|
|
@@ -427,11 +418,6 @@ export function main() {
|
|
|
427
418
|
|
|
428
419
|
// --- Non-Interactive Mode ---
|
|
429
420
|
if (isNonInteractive) {
|
|
430
|
-
if (!hasApiKeys) {
|
|
431
|
-
logError(chalk.red('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY environment variable.'));
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
421
|
let chat;
|
|
436
422
|
try {
|
|
437
423
|
// Pass session ID if provided, ProbeChat generates one otherwise
|
|
@@ -445,8 +431,6 @@ export function main() {
|
|
|
445
431
|
bashConfig: bashConfig,
|
|
446
432
|
completionPrompt: completionPrompt
|
|
447
433
|
});
|
|
448
|
-
// Model/Provider info is logged via logInfo above if debug enabled
|
|
449
|
-
logInfo(chalk.blue(`Using Session ID: ${chat.getSessionId()}`)); // Log the actual session ID being used
|
|
450
434
|
} catch (error) {
|
|
451
435
|
logError(chalk.red(`Initializing chat failed: ${error.message}`));
|
|
452
436
|
process.exit(1);
|
|
@@ -468,6 +452,19 @@ export function main() {
|
|
|
468
452
|
// Async function to handle the single chat request
|
|
469
453
|
const runNonInteractiveChat = async () => {
|
|
470
454
|
try {
|
|
455
|
+
// Initialize the chat (handles API key validation, claude-code/codex fallback)
|
|
456
|
+
await chat.initialize();
|
|
457
|
+
|
|
458
|
+
// Validate provider after initialization
|
|
459
|
+
const providerInfo = chat.getProviderInfo();
|
|
460
|
+
if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
|
|
461
|
+
throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Log provider info after successful initialization
|
|
465
|
+
logInfo(chalk.green(`Using provider: ${providerInfo.provider} with model: ${providerInfo.model}`));
|
|
466
|
+
logInfo(chalk.blue(`Using Session ID: ${chat.getSessionId()}`));
|
|
467
|
+
|
|
471
468
|
// Get message from command line argument or stdin
|
|
472
469
|
let message = options.message;
|
|
473
470
|
|
|
@@ -533,16 +530,6 @@ export function main() {
|
|
|
533
530
|
// --- Interactive CLI Mode ---
|
|
534
531
|
// (This block only runs if not non-interactive and not web mode)
|
|
535
532
|
|
|
536
|
-
if (!hasApiKeys) {
|
|
537
|
-
// Use logError and standard console.log for setup instructions
|
|
538
|
-
logError(chalk.red('No API key provided. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY environment variable.'));
|
|
539
|
-
console.log(chalk.cyan('You can find these instructions in the .env.example file:'));
|
|
540
|
-
console.log(chalk.cyan('1. Create a .env file by copying .env.example'));
|
|
541
|
-
console.log(chalk.cyan('2. Add your API key to the .env file'));
|
|
542
|
-
console.log(chalk.cyan('3. Restart the application'));
|
|
543
|
-
process.exit(1);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
533
|
// Initialize ProbeChat for CLI mode
|
|
547
534
|
let chat;
|
|
548
535
|
try {
|
|
@@ -557,26 +544,44 @@ export function main() {
|
|
|
557
544
|
bashConfig: bashConfig,
|
|
558
545
|
completionPrompt: completionPrompt
|
|
559
546
|
});
|
|
560
|
-
|
|
561
|
-
// Log model/provider info using logInfo
|
|
562
|
-
if (chat.apiType === 'anthropic') {
|
|
563
|
-
logInfo(chalk.green(`Using Anthropic API with model: ${chat.model}`));
|
|
564
|
-
} else if (chat.apiType === 'openai') {
|
|
565
|
-
logInfo(chalk.green(`Using OpenAI API with model: ${chat.model}`));
|
|
566
|
-
} else if (chat.apiType === 'google') {
|
|
567
|
-
logInfo(chalk.green(`Using Google API with model: ${chat.model}`));
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
logInfo(chalk.blue(`Session ID: ${chat.getSessionId()}`));
|
|
571
|
-
logInfo(chalk.cyan('Type "exit" or "quit" to end the chat'));
|
|
572
|
-
logInfo(chalk.cyan('Type "usage" to see token usage statistics'));
|
|
573
|
-
logInfo(chalk.cyan('Type "clear" to clear the chat history'));
|
|
574
|
-
logInfo(chalk.cyan('-------------------------------------------'));
|
|
575
547
|
} catch (error) {
|
|
576
|
-
logError(chalk.red(`Error
|
|
548
|
+
logError(chalk.red(`Error creating chat instance: ${error.message}`));
|
|
577
549
|
process.exit(1);
|
|
578
550
|
}
|
|
579
551
|
|
|
552
|
+
// Async initialization wrapper for interactive mode
|
|
553
|
+
async function initializeAndStartChat() {
|
|
554
|
+
try {
|
|
555
|
+
// Initialize the chat (handles API key validation, claude-code/codex fallback)
|
|
556
|
+
await chat.initialize();
|
|
557
|
+
|
|
558
|
+
// Validate provider after initialization
|
|
559
|
+
const providerInfo = chat.getProviderInfo();
|
|
560
|
+
if (providerInfo.provider === 'unknown' || providerInfo.provider === 'uninitialized') {
|
|
561
|
+
throw new Error('No valid AI provider available. Please set an API key or install claude/codex CLI.');
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Log provider info after successful initialization
|
|
565
|
+
if (providerInfo.isCliProvider) {
|
|
566
|
+
logInfo(chalk.green(`Using ${providerInfo.provider} CLI`));
|
|
567
|
+
} else {
|
|
568
|
+
logInfo(chalk.green(`Using ${providerInfo.provider} API with model: ${providerInfo.model}`));
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
logInfo(chalk.blue(`Session ID: ${chat.getSessionId()}`));
|
|
572
|
+
logInfo(chalk.cyan('Type "exit" or "quit" to end the chat'));
|
|
573
|
+
logInfo(chalk.cyan('Type "usage" to see token usage statistics'));
|
|
574
|
+
logInfo(chalk.cyan('Type "clear" to clear the chat history'));
|
|
575
|
+
logInfo(chalk.cyan('-------------------------------------------'));
|
|
576
|
+
|
|
577
|
+
// Start the interactive chat loop
|
|
578
|
+
await runInteractiveChat();
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logError(chalk.red(`Error initializing chat: ${error.message}`));
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
580
585
|
// Format AI response for interactive mode
|
|
581
586
|
function formatResponseInteractive(response) {
|
|
582
587
|
// Check if response is a structured object with response and tokenUsage properties
|
|
@@ -598,7 +603,7 @@ export function main() {
|
|
|
598
603
|
}
|
|
599
604
|
|
|
600
605
|
// Main interactive chat loop
|
|
601
|
-
async function
|
|
606
|
+
async function runInteractiveChat() {
|
|
602
607
|
while (true) {
|
|
603
608
|
const { message } = await inquirer.prompt([
|
|
604
609
|
{
|
|
@@ -613,7 +618,7 @@ export function main() {
|
|
|
613
618
|
logInfo(chalk.yellow('Goodbye!'));
|
|
614
619
|
break;
|
|
615
620
|
} else if (message.toLowerCase() === 'usage') {
|
|
616
|
-
const usage = chat.
|
|
621
|
+
const usage = chat.getUsageSummary();
|
|
617
622
|
const display = new TokenUsageDisplay();
|
|
618
623
|
const formatted = display.format(usage);
|
|
619
624
|
|
|
@@ -630,9 +635,9 @@ export function main() {
|
|
|
630
635
|
process.stdout.write('\x1B]0;Context: ' + formatted.contextWindow + '\x07');
|
|
631
636
|
continue;
|
|
632
637
|
} else if (message.toLowerCase() === 'clear') {
|
|
633
|
-
|
|
638
|
+
chat.clearHistory();
|
|
634
639
|
logInfo(chalk.yellow('Chat history cleared'));
|
|
635
|
-
logInfo(chalk.blue(`New session ID: ${
|
|
640
|
+
logInfo(chalk.blue(`New session ID: ${chat.getSessionId()}`));
|
|
636
641
|
continue;
|
|
637
642
|
}
|
|
638
643
|
|
|
@@ -656,7 +661,7 @@ export function main() {
|
|
|
656
661
|
}
|
|
657
662
|
}
|
|
658
663
|
|
|
659
|
-
|
|
664
|
+
initializeAndStartChat().catch((error) => {
|
|
660
665
|
logError(chalk.red(`Fatal error in interactive chat: ${error.message}`));
|
|
661
666
|
process.exit(1);
|
|
662
667
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe-chat",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc198",
|
|
4
4
|
"description": "CLI and web interface for Probe code search (formerly @probelabs/probe-web and @probelabs/probe-chat)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"@ai-sdk/anthropic": "^2.0.8",
|
|
33
33
|
"@ai-sdk/google": "^2.0.14",
|
|
34
34
|
"@ai-sdk/openai": "^2.0.10",
|
|
35
|
-
"@
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
36
|
"@opentelemetry/api": "^1.9.0",
|
|
37
37
|
"@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
|
|
38
38
|
"@opentelemetry/resources": "^2.0.1",
|
|
39
39
|
"@opentelemetry/sdk-node": "^0.203.0",
|
|
40
40
|
"@opentelemetry/semantic-conventions": "^1.36.0",
|
|
41
|
-
"@
|
|
41
|
+
"@probelabs/probe": "latest",
|
|
42
42
|
"ai": "^5.0.0",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"commander": "^11.1.0",
|
package/probeChat.js
CHANGED
|
@@ -266,6 +266,9 @@ export class ProbeChat {
|
|
|
266
266
|
constructor(options = {}) {
|
|
267
267
|
this.isNonInteractive = options.isNonInteractive || process.env.PROBE_NON_INTERACTIVE === '1';
|
|
268
268
|
this.debug = options.debug || process.env.DEBUG_CHAT === '1';
|
|
269
|
+
this._initialized = false;
|
|
270
|
+
this._initializing = null; // Promise to prevent concurrent initialization
|
|
271
|
+
this._initError = null; // Store initialization error to prevent infinite retries
|
|
269
272
|
|
|
270
273
|
// Initialize ProbeAgent with MCP support
|
|
271
274
|
const agentOptions = {
|
|
@@ -283,11 +286,81 @@ export class ProbeChat {
|
|
|
283
286
|
|
|
284
287
|
if (this.debug) {
|
|
285
288
|
console.log(`[DEBUG] ProbeChat initialized with MCP ${agentOptions.enableMcp ? 'enabled' : 'disabled'}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Initialize the chat agent asynchronously.
|
|
294
|
+
*
|
|
295
|
+
* This method is safe to call multiple times - subsequent calls will return
|
|
296
|
+
* immediately if already initialized, or wait for the ongoing initialization.
|
|
297
|
+
*
|
|
298
|
+
* If initialization fails, the error is stored and rethrown on subsequent calls
|
|
299
|
+
* to prevent infinite retry loops.
|
|
300
|
+
*
|
|
301
|
+
* This initializes the ProbeAgent which handles:
|
|
302
|
+
* - API key validation
|
|
303
|
+
* - Auto-detection of claude-code or codex CLI as fallback
|
|
304
|
+
* - MCP initialization
|
|
305
|
+
* - History loading from storage
|
|
306
|
+
*
|
|
307
|
+
* @returns {Promise<void>}
|
|
308
|
+
* @throws {Error} If no valid AI provider is available
|
|
309
|
+
*/
|
|
310
|
+
async initialize() {
|
|
311
|
+
// If previous initialization failed, rethrow the stored error
|
|
312
|
+
if (this._initError) {
|
|
313
|
+
throw this._initError;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Return existing initialization promise if in progress (prevents concurrent init)
|
|
317
|
+
if (this._initializing) {
|
|
318
|
+
return this._initializing;
|
|
319
|
+
}
|
|
286
320
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
321
|
+
// Already initialized successfully
|
|
322
|
+
if (this._initialized) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this._initializing = (async () => {
|
|
327
|
+
try {
|
|
328
|
+
await this.agent.initialize();
|
|
329
|
+
|
|
330
|
+
// Mark as initialized immediately after agent init succeeds
|
|
331
|
+
this._initialized = true;
|
|
332
|
+
|
|
333
|
+
// Debug logging wrapped in try-catch to not affect initialization state
|
|
334
|
+
if (this.debug) {
|
|
335
|
+
try {
|
|
336
|
+
const provider = this.agent.clientApiProvider || this.agent.apiType || 'unknown';
|
|
337
|
+
console.log(`[DEBUG] ProbeChat agent initialized with provider: ${provider}`);
|
|
338
|
+
this.logAvailableTools();
|
|
339
|
+
} catch (logError) {
|
|
340
|
+
console.error('[DEBUG] Logging failed:', logError.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
// Store error to prevent infinite retry loops
|
|
345
|
+
this._initError = error;
|
|
346
|
+
throw error;
|
|
347
|
+
} finally {
|
|
348
|
+
// Always clear the initializing promise
|
|
349
|
+
this._initializing = null;
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
|
|
353
|
+
return this._initializing;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Ensure the agent is initialized before use.
|
|
358
|
+
* Called automatically by chat() - you don't need to call this directly.
|
|
359
|
+
* @private
|
|
360
|
+
*/
|
|
361
|
+
async _ensureInitialized() {
|
|
362
|
+
if (!this._initialized) {
|
|
363
|
+
await this.initialize();
|
|
291
364
|
}
|
|
292
365
|
}
|
|
293
366
|
|
|
@@ -333,18 +406,27 @@ export class ProbeChat {
|
|
|
333
406
|
}
|
|
334
407
|
|
|
335
408
|
/**
|
|
336
|
-
*
|
|
409
|
+
* Send a chat message and get AI response.
|
|
410
|
+
*
|
|
411
|
+
* This method automatically initializes the agent on first call if not already
|
|
412
|
+
* initialized. For explicit control over initialization (e.g., to handle errors
|
|
413
|
+
* separately), call initialize() first.
|
|
414
|
+
*
|
|
337
415
|
* @param {string} message - The user's question
|
|
338
416
|
* @param {Object} [options] - Optional configuration
|
|
339
417
|
* @param {string} [options.schema] - JSON schema for structured output
|
|
340
418
|
* @param {Array} [options.images] - Array of image data (base64 strings or URLs)
|
|
341
|
-
* @returns {Promise<
|
|
419
|
+
* @returns {Promise<Object>} - The AI response with token usage
|
|
420
|
+
* @throws {Error} If message is empty or if no valid AI provider is available
|
|
342
421
|
*/
|
|
343
422
|
async chat(message, options = {}) {
|
|
344
423
|
if (!message || typeof message !== 'string' || message.trim().length === 0) {
|
|
345
424
|
throw new Error('Message is required and must be a non-empty string');
|
|
346
425
|
}
|
|
347
426
|
|
|
427
|
+
// Ensure the agent is initialized (handles API key validation, claude-code/codex fallback)
|
|
428
|
+
await this._ensureInitialized();
|
|
429
|
+
|
|
348
430
|
// Extract images from the message text if not provided in options
|
|
349
431
|
let images = options.images || [];
|
|
350
432
|
let cleanedMessage = message;
|
|
@@ -379,6 +461,26 @@ export class ProbeChat {
|
|
|
379
461
|
return this.agent.sessionId;
|
|
380
462
|
}
|
|
381
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Get provider information
|
|
466
|
+
* @returns {Object} - Provider info with type and model
|
|
467
|
+
*/
|
|
468
|
+
getProviderInfo() {
|
|
469
|
+
return {
|
|
470
|
+
provider: this.agent.clientApiProvider || this.agent.apiType || 'unknown',
|
|
471
|
+
model: this.agent.model || 'unknown',
|
|
472
|
+
isCliProvider: ['claude-code', 'codex'].includes(this.agent.clientApiProvider || this.agent.apiType)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Check if the agent is initialized
|
|
478
|
+
* @returns {boolean}
|
|
479
|
+
*/
|
|
480
|
+
isInitialized() {
|
|
481
|
+
return this._initialized;
|
|
482
|
+
}
|
|
483
|
+
|
|
382
484
|
/**
|
|
383
485
|
* Get usage summary for the current session
|
|
384
486
|
*/
|
|
@@ -387,11 +489,13 @@ export class ProbeChat {
|
|
|
387
489
|
}
|
|
388
490
|
|
|
389
491
|
/**
|
|
390
|
-
* Clear conversation history
|
|
492
|
+
* Clear conversation history and return the new session ID
|
|
493
|
+
* @returns {string} - The new session ID
|
|
391
494
|
*/
|
|
392
495
|
clearHistory() {
|
|
393
496
|
this.agent.clearHistory();
|
|
394
497
|
this.tokenUsage.clear();
|
|
498
|
+
return this.agent.sessionId;
|
|
395
499
|
}
|
|
396
500
|
|
|
397
501
|
/**
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { ChatSessionManager } from '../../ChatSessionManager.js';
|
|
4
|
+
|
|
5
|
+
// Store original environment
|
|
6
|
+
let originalEnv;
|
|
7
|
+
|
|
8
|
+
describe('ChatSessionManager Tests', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Store original environment
|
|
11
|
+
originalEnv = { ...process.env };
|
|
12
|
+
|
|
13
|
+
// Set test mode
|
|
14
|
+
process.env.NODE_ENV = 'test';
|
|
15
|
+
process.env.USE_MOCK_AI = 'true';
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Restore original environment
|
|
20
|
+
Object.keys(process.env).forEach(key => {
|
|
21
|
+
if (!(key in originalEnv)) {
|
|
22
|
+
delete process.env[key];
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
Object.assign(process.env, originalEnv);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('initialize()', () => {
|
|
29
|
+
it('should call agent.initialize() when initializing', async () => {
|
|
30
|
+
// Create a ChatSessionManager
|
|
31
|
+
const session = new ChatSessionManager({
|
|
32
|
+
sessionId: 'test-session-123',
|
|
33
|
+
debug: false
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Track if agent.initialize was called
|
|
37
|
+
let agentInitializeCalled = false;
|
|
38
|
+
const originalInitialize = session.agent.initialize.bind(session.agent);
|
|
39
|
+
session.agent.initialize = async function() {
|
|
40
|
+
agentInitializeCalled = true;
|
|
41
|
+
return originalInitialize();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Initialize the session
|
|
45
|
+
await session.initialize();
|
|
46
|
+
|
|
47
|
+
// Verify agent.initialize was called
|
|
48
|
+
assert.strictEqual(agentInitializeCalled, true, 'agent.initialize() should be called during ChatSessionManager.initialize()');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should only initialize once even if called multiple times', async () => {
|
|
52
|
+
const session = new ChatSessionManager({
|
|
53
|
+
sessionId: 'test-session-456',
|
|
54
|
+
debug: false
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
let initializeCallCount = 0;
|
|
58
|
+
const originalInitialize = session.agent.initialize.bind(session.agent);
|
|
59
|
+
session.agent.initialize = async function() {
|
|
60
|
+
initializeCallCount++;
|
|
61
|
+
return originalInitialize();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Call initialize multiple times
|
|
65
|
+
await session.initialize();
|
|
66
|
+
await session.initialize();
|
|
67
|
+
await session.initialize();
|
|
68
|
+
|
|
69
|
+
// Should only be called once due to _ready flag
|
|
70
|
+
assert.strictEqual(initializeCallCount, 1, 'agent.initialize() should only be called once');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should set _ready flag after successful initialization', async () => {
|
|
74
|
+
const session = new ChatSessionManager({
|
|
75
|
+
sessionId: 'test-session-789',
|
|
76
|
+
debug: false
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
assert.strictEqual(session._ready, false, '_ready should be false before initialize');
|
|
80
|
+
|
|
81
|
+
await session.initialize();
|
|
82
|
+
|
|
83
|
+
assert.strictEqual(session._ready, true, '_ready should be true after initialize');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
package/webServer.js
CHANGED
|
@@ -98,13 +98,12 @@ function getOrCreateChat(sessionId, apiCredentials = null) {
|
|
|
98
98
|
/**
|
|
99
99
|
* Start the web server
|
|
100
100
|
* @param {string} version - The version of the application
|
|
101
|
-
* @param {boolean} hasApiKeys - Whether any API keys are configured
|
|
102
101
|
* @param {Object} options - Additional options
|
|
103
102
|
* @param {boolean} options.allowEdit - Whether to allow editing files via the implement tool
|
|
104
103
|
*/
|
|
105
|
-
export async function startWebServer(version,
|
|
104
|
+
export async function startWebServer(version, options = {}) {
|
|
106
105
|
const allowEdit = options?.allowEdit || false;
|
|
107
|
-
|
|
106
|
+
|
|
108
107
|
if (allowEdit) {
|
|
109
108
|
console.log('Edit mode enabled: implement tool is available');
|
|
110
109
|
}
|
|
@@ -120,11 +119,11 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
// Initialize persistent storage for web mode
|
|
123
|
-
globalStorage = new JsonChatStorage({
|
|
124
|
-
webMode: true,
|
|
125
|
-
verbose: process.env.DEBUG_CHAT === '1'
|
|
122
|
+
globalStorage = new JsonChatStorage({
|
|
123
|
+
webMode: true,
|
|
124
|
+
verbose: process.env.DEBUG_CHAT === '1'
|
|
126
125
|
});
|
|
127
|
-
|
|
126
|
+
|
|
128
127
|
// Initialize storage synchronously before server starts
|
|
129
128
|
try {
|
|
130
129
|
await globalStorage.initialize();
|
|
@@ -144,13 +143,9 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
144
143
|
? process.env.ALLOWED_FOLDERS.split(',').map(folder => folder.trim()).filter(Boolean)
|
|
145
144
|
: [];
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
console.log('Running in No API Keys mode - will show setup instructions to users');
|
|
151
|
-
} else {
|
|
152
|
-
console.log('API keys detected. Chat functionality enabled.');
|
|
153
|
-
}
|
|
146
|
+
// Note: API key / CLI availability is checked lazily in ProbeAgent.initialize()
|
|
147
|
+
// when the first chat message is sent. This allows fallback to Claude Code or Codex CLI.
|
|
148
|
+
console.log('Chat functionality enabled (API availability checked on first message)');
|
|
154
149
|
|
|
155
150
|
|
|
156
151
|
// Define the tools available for direct API calls (bypassing LLM loop)
|
|
@@ -419,7 +414,7 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
419
414
|
// UI Routes
|
|
420
415
|
'GET /': (req, res) => {
|
|
421
416
|
const htmlPath = join(__dirname, 'index.html');
|
|
422
|
-
serveHtml(res, htmlPath, { 'data-no-api-keys':
|
|
417
|
+
serveHtml(res, htmlPath, { 'data-no-api-keys': 'false' });
|
|
423
418
|
},
|
|
424
419
|
|
|
425
420
|
// Chat session route - serves HTML with injected session ID
|
|
@@ -428,15 +423,15 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
428
423
|
if (!sessionId) {
|
|
429
424
|
return sendError(res, 400, 'Invalid session ID in URL');
|
|
430
425
|
}
|
|
431
|
-
|
|
426
|
+
|
|
432
427
|
// Validate that session exists or at least has a valid UUID format
|
|
433
428
|
if (!isValidUUID(sessionId)) {
|
|
434
429
|
return sendError(res, 400, 'Invalid session ID format');
|
|
435
430
|
}
|
|
436
431
|
|
|
437
432
|
const htmlPath = join(__dirname, 'index.html');
|
|
438
|
-
serveHtml(res, htmlPath, {
|
|
439
|
-
'data-no-api-keys':
|
|
433
|
+
serveHtml(res, htmlPath, {
|
|
434
|
+
'data-no-api-keys': 'false',
|
|
440
435
|
'data-session-id': sessionId
|
|
441
436
|
});
|
|
442
437
|
},
|
|
@@ -451,7 +446,7 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
451
446
|
sendJson(res, 200, {
|
|
452
447
|
folders: folders,
|
|
453
448
|
currentDir: currentDir,
|
|
454
|
-
noApiKeysMode:
|
|
449
|
+
noApiKeysMode: false
|
|
455
450
|
});
|
|
456
451
|
},
|
|
457
452
|
|
|
@@ -700,12 +695,6 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
700
695
|
// Update last activity timestamp
|
|
701
696
|
chatInstance.lastActivity = Date.now();
|
|
702
697
|
|
|
703
|
-
// Check if API keys are needed but missing
|
|
704
|
-
if (chatInstance.noApiKeysMode) {
|
|
705
|
-
console.warn(`[WARN] Chat request for session ${chatSessionId} cannot proceed: No API keys configured.`);
|
|
706
|
-
return sendError(res, 503, 'Chat service unavailable: API key not configured on server.');
|
|
707
|
-
}
|
|
708
|
-
|
|
709
698
|
// Register this request as active for cancellation
|
|
710
699
|
registerRequest(chatSessionId, { abort: () => chatInstance.abort() });
|
|
711
700
|
if (DEBUG) console.log(`[DEBUG] Registered cancellable request for session: ${chatSessionId}`);
|
|
@@ -918,9 +907,6 @@ export async function startWebServer(version, hasApiKeys = true, options = {}) {
|
|
|
918
907
|
console.log(`Probe Web Interface v${version}`);
|
|
919
908
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
920
909
|
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
921
|
-
if (noApiKeysMode) {
|
|
922
|
-
console.log('*** Running in NO API KEYS mode. Chat functionality disabled. ***');
|
|
923
|
-
}
|
|
924
910
|
});
|
|
925
911
|
}
|
|
926
912
|
|