erosolar-cli 1.7.80 → 1.7.82

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 (109) hide show
  1. package/agents/erosolar-code.rules.json +5 -0
  2. package/agents/general.rules.json +5 -0
  3. package/dist/bin/erosolar.js +0 -2
  4. package/dist/bin/erosolar.js.map +1 -1
  5. package/dist/contracts/agent-schemas.json +20 -12
  6. package/dist/contracts/unified-schema.json +1 -1
  7. package/dist/core/agent.d.ts +36 -3
  8. package/dist/core/agent.d.ts.map +1 -1
  9. package/dist/core/agent.js +223 -8
  10. package/dist/core/agent.js.map +1 -1
  11. package/dist/core/cliTestHarness.d.ts +200 -0
  12. package/dist/core/cliTestHarness.d.ts.map +1 -0
  13. package/dist/core/cliTestHarness.js +549 -0
  14. package/dist/core/cliTestHarness.js.map +1 -0
  15. package/dist/core/errors/apiKeyErrors.js +1 -1
  16. package/dist/core/errors/apiKeyErrors.js.map +1 -1
  17. package/dist/core/isolatedVerifier.js +274 -22
  18. package/dist/core/isolatedVerifier.js.map +1 -1
  19. package/dist/core/modelDiscovery.d.ts.map +1 -1
  20. package/dist/core/modelDiscovery.js +23 -28
  21. package/dist/core/modelDiscovery.js.map +1 -1
  22. package/dist/core/multilinePasteHandler.d.ts +35 -0
  23. package/dist/core/multilinePasteHandler.d.ts.map +1 -0
  24. package/dist/core/multilinePasteHandler.js +80 -0
  25. package/dist/core/multilinePasteHandler.js.map +1 -0
  26. package/dist/core/secretStore.d.ts +9 -0
  27. package/dist/core/secretStore.d.ts.map +1 -1
  28. package/dist/core/secretStore.js +52 -2
  29. package/dist/core/secretStore.js.map +1 -1
  30. package/dist/core/types.d.ts +6 -0
  31. package/dist/core/types.d.ts.map +1 -1
  32. package/dist/headless/headlessApp.d.ts.map +1 -1
  33. package/dist/headless/headlessApp.js +16 -0
  34. package/dist/headless/headlessApp.js.map +1 -1
  35. package/dist/plugins/providers/google/index.js +3 -2
  36. package/dist/plugins/providers/google/index.js.map +1 -1
  37. package/dist/providers/anthropicProvider.d.ts.map +1 -1
  38. package/dist/providers/anthropicProvider.js +27 -1
  39. package/dist/providers/anthropicProvider.js.map +1 -1
  40. package/dist/providers/googleProvider.d.ts.map +1 -1
  41. package/dist/providers/googleProvider.js +23 -1
  42. package/dist/providers/googleProvider.js.map +1 -1
  43. package/dist/providers/openaiChatCompletionsProvider.d.ts +2 -1
  44. package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
  45. package/dist/providers/openaiChatCompletionsProvider.js +111 -4
  46. package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
  47. package/dist/providers/openaiResponsesProvider.d.ts.map +1 -1
  48. package/dist/providers/openaiResponsesProvider.js +39 -18
  49. package/dist/providers/openaiResponsesProvider.js.map +1 -1
  50. package/dist/runtime/agentController.d.ts +4 -0
  51. package/dist/runtime/agentController.d.ts.map +1 -1
  52. package/dist/runtime/agentController.js +29 -3
  53. package/dist/runtime/agentController.js.map +1 -1
  54. package/dist/security/persistence-research.d.ts +0 -2
  55. package/dist/security/persistence-research.d.ts.map +1 -1
  56. package/dist/security/persistence-research.js +0 -2
  57. package/dist/security/persistence-research.js.map +1 -1
  58. package/dist/security/security-testing-framework.d.ts +0 -2
  59. package/dist/security/security-testing-framework.d.ts.map +1 -1
  60. package/dist/security/security-testing-framework.js +0 -2
  61. package/dist/security/security-testing-framework.js.map +1 -1
  62. package/dist/shell/bracketedPasteManager.d.ts +8 -5
  63. package/dist/shell/bracketedPasteManager.d.ts.map +1 -1
  64. package/dist/shell/bracketedPasteManager.js +27 -43
  65. package/dist/shell/bracketedPasteManager.js.map +1 -1
  66. package/dist/shell/composableMessage.d.ts +1 -1
  67. package/dist/shell/composableMessage.js +2 -2
  68. package/dist/shell/composableMessage.js.map +1 -1
  69. package/dist/shell/interactiveShell.d.ts +7 -48
  70. package/dist/shell/interactiveShell.d.ts.map +1 -1
  71. package/dist/shell/interactiveShell.js +144 -340
  72. package/dist/shell/interactiveShell.js.map +1 -1
  73. package/dist/shell/shellApp.d.ts.map +1 -1
  74. package/dist/shell/shellApp.js +54 -3
  75. package/dist/shell/shellApp.js.map +1 -1
  76. package/dist/shell/systemPrompt.d.ts +1 -1
  77. package/dist/shell/systemPrompt.d.ts.map +1 -1
  78. package/dist/shell/systemPrompt.js +10 -3
  79. package/dist/shell/systemPrompt.js.map +1 -1
  80. package/dist/shell/updateManager.js +4 -2
  81. package/dist/shell/updateManager.js.map +1 -1
  82. package/dist/subagents/taskRunner.js +2 -2
  83. package/dist/subagents/taskRunner.js.map +1 -1
  84. package/dist/tools/cloudTools.d.ts +0 -2
  85. package/dist/tools/cloudTools.d.ts.map +1 -1
  86. package/dist/tools/cloudTools.js +0 -2
  87. package/dist/tools/cloudTools.js.map +1 -1
  88. package/dist/tools/fileTools.d.ts.map +1 -1
  89. package/dist/tools/fileTools.js +31 -3
  90. package/dist/tools/fileTools.js.map +1 -1
  91. package/dist/ui/ShellUIAdapter.d.ts +10 -2
  92. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  93. package/dist/ui/ShellUIAdapter.js +123 -11
  94. package/dist/ui/ShellUIAdapter.js.map +1 -1
  95. package/dist/ui/keyboardShortcuts.d.ts.map +1 -1
  96. package/dist/ui/keyboardShortcuts.js +12 -2
  97. package/dist/ui/keyboardShortcuts.js.map +1 -1
  98. package/dist/ui/persistentPrompt.d.ts +24 -0
  99. package/dist/ui/persistentPrompt.d.ts.map +1 -1
  100. package/dist/ui/persistentPrompt.js +86 -4
  101. package/dist/ui/persistentPrompt.js.map +1 -1
  102. package/dist/ui/toolDisplay.d.ts.map +1 -1
  103. package/dist/ui/toolDisplay.js +652 -0
  104. package/dist/ui/toolDisplay.js.map +1 -1
  105. package/package.json +10 -10
  106. package/dist/shell/inputProcessor.d.ts +0 -55
  107. package/dist/shell/inputProcessor.d.ts.map +0 -1
  108. package/dist/shell/inputProcessor.js +0 -171
  109. package/dist/shell/inputProcessor.js.map +0 -1
@@ -23,7 +23,6 @@ import { PersistentPrompt, PinnedChatBox } from '../ui/persistentPrompt.js';
23
23
  import { formatShortcutsHelp } from '../ui/shortcutsHelp.js';
24
24
  import { MetricsTracker } from '../alpha-zero/index.js';
25
25
  import { listAvailablePlugins } from '../plugins/index.js';
26
- import { verifyResponse, formatVerificationReport, } from '../core/responseVerifier.js';
27
26
  const DROPDOWN_COLORS = [
28
27
  theme.primary,
29
28
  theme.info,
@@ -74,7 +73,6 @@ export class InteractiveShell {
74
73
  workspaceOptions;
75
74
  sessionState;
76
75
  isProcessing = false;
77
- isInsideThinkingBlock = false;
78
76
  pendingInteraction = null;
79
77
  pendingSecretRetry = null;
80
78
  bufferedInputLines = [];
@@ -107,12 +105,8 @@ export class InteractiveShell {
107
105
  pendingHistoryLoad = null;
108
106
  cachedHistory = [];
109
107
  activeSessionId = null;
110
- sessionStartTime = Date.now();
111
108
  activeSessionTitle = null;
112
109
  sessionResumeNotice = null;
113
- lastAssistantResponse = null;
114
- verificationRetryCount = 0;
115
- maxVerificationRetries = 2;
116
110
  customCommands;
117
111
  customCommandMap;
118
112
  sessionRestoreConfig;
@@ -173,11 +167,16 @@ export class InteractiveShell {
173
167
  // Update persistent prompt status bar with file changes
174
168
  this.updatePersistentPromptFileChanges();
175
169
  });
176
- // Set up tool status callback to update streaming status line during tool execution
177
- // Uses Claude Code style: single line at bottom that updates in-place
170
+ // Set up tool status callback to update pinned chat box during tool execution
178
171
  this.uiAdapter.setToolStatusCallback((status) => {
179
- // Update the streaming status line (Claude Code style)
180
- display.updateStreamingStatus(status);
172
+ if (status) {
173
+ this.pinnedChatBox.setStatusMessage(status);
174
+ }
175
+ else {
176
+ // Clear status but keep processing indicator if still processing
177
+ this.pinnedChatBox.setStatusMessage(null);
178
+ }
179
+ this.pinnedChatBox.forceRender();
181
180
  });
182
181
  this.skillRepository = new SkillRepository({
183
182
  workingDir: this.workingDir,
@@ -189,8 +188,9 @@ export class InteractiveShell {
189
188
  this.rl = readline.createInterface({
190
189
  input,
191
190
  output,
192
- // Claude Code style: simple '> ' prompt
193
- prompt: '> ',
191
+ // Use empty prompt since PinnedChatBox handles all prompt rendering
192
+ // This prevents duplicate '>' characters from appearing
193
+ prompt: '',
194
194
  terminal: true,
195
195
  historySize: 100, // Enable native readline history
196
196
  });
@@ -281,10 +281,7 @@ export class InteractiveShell {
281
281
  this.pinnedChatBox.show();
282
282
  this.pinnedChatBox.forceRender();
283
283
  if (initialPrompt) {
284
- // For command-line prompts, show the user's input with separator (Claude Code style)
285
284
  display.newLine();
286
- const cols = Math.min(process.stdout.columns || 80, 72);
287
- console.log(theme.ui.border('─'.repeat(cols)));
288
285
  console.log(`${formatUserPrompt(this.profileLabel || this.profile)}${initialPrompt}`);
289
286
  await this.processInputBlock(initialPrompt);
290
287
  return;
@@ -475,9 +472,9 @@ export class InteractiveShell {
475
472
  inputStream.off('keypress', this.keypressHandler);
476
473
  this.keypressHandler = null;
477
474
  }
478
- // Remove raw data handler
479
- if (inputStream && this.rawDataHandler) {
480
- inputStream.off('data', this.rawDataHandler);
475
+ // Restore original stdin emit (cleanup from paste interception)
476
+ if (this.rawDataHandler) {
477
+ this.rawDataHandler(); // This restores the original emit function
481
478
  this.rawDataHandler = null;
482
479
  }
483
480
  // Clear any pending cleanup to prevent hanging
@@ -489,6 +486,8 @@ export class InteractiveShell {
489
486
  display.newLine();
490
487
  const highlightedEmail = theme.info('support@ero.solar');
491
488
  const infoMessage = [
489
+ 'Thank you to Anthropic for allowing me to use Claude Code to build erosolar-cli.',
490
+ '',
492
491
  `Email ${highlightedEmail} with any bugs or feedback`,
493
492
  'GitHub: https://github.com/ErosolarAI/erosolar-by-bo',
494
493
  'npm: https://www.npmjs.com/package/erosolar-cli',
@@ -526,21 +525,31 @@ export class InteractiveShell {
526
525
  // All pastes (single or multi-line) are captured for confirmation before submit
527
526
  this.capturePaste(content, lineCount);
528
527
  });
529
- // Set up raw data interception to catch bracketed paste before readline processes it
530
- // We prepend our listener so it runs before readline's listener
531
- this.rawDataHandler = (data) => {
532
- const str = data.toString();
533
- const result = this.bracketedPaste.processRawData(str);
534
- if (result.consumed) {
535
- // Don't show preview here - readline will still echo lines to the terminal,
536
- // and our preview would get clobbered. Instead, we show the preview in the
537
- // line handler after clearing readline's echoed output.
538
- // The processRawData() sets flags that the line handler will check.
528
+ // Set up raw data interception to catch bracketed paste before readline processes it.
529
+ // We need to actually PREVENT readline from seeing the paste content to avoid echo.
530
+ // Strategy: Replace stdin's 'data' event emission during paste capture.
531
+ const originalEmit = inputStream.emit.bind(inputStream);
532
+ inputStream.emit = (event, ...args) => {
533
+ if (event === 'data' && args[0]) {
534
+ const data = args[0];
535
+ const str = typeof data === 'string' ? data : data.toString();
536
+ const result = this.bracketedPaste.processRawData(str);
537
+ if (result.consumed) {
538
+ // Data was consumed by paste handler - don't pass to readline
539
+ // If there's passThrough data, emit that instead
540
+ if (result.passThrough) {
541
+ return originalEmit('data', Buffer.from(result.passThrough));
542
+ }
543
+ return true; // Event "handled" but not passed to other listeners
544
+ }
539
545
  }
546
+ // Pass through all other events and non-paste data normally
547
+ return originalEmit(event, ...args);
548
+ };
549
+ // Store reference for cleanup
550
+ this.rawDataHandler = () => {
551
+ inputStream.emit = originalEmit;
540
552
  };
541
- // Use prependListener to ensure our handler runs before readline's handlers
542
- // This gives us first look at the raw data including bracketed paste markers
543
- inputStream.prependListener('data', this.rawDataHandler);
544
553
  }
545
554
  setupSlashCommandPreviewHandler() {
546
555
  const inputStream = input;
@@ -565,8 +574,8 @@ export class InteractiveShell {
565
574
  const currentLine = this.rl.line || '';
566
575
  const cursorPos = this.rl.cursor || 0;
567
576
  this.persistentPrompt.updateInput(currentLine, cursorPos);
568
- // Sync to pinned chat box for display only (include cursor position)
569
- this.pinnedChatBox.setInput(currentLine, cursorPos);
577
+ // Sync to pinned chat box for display only
578
+ this.pinnedChatBox.setInput(currentLine);
570
579
  if (this.composableMessage.hasContent()) {
571
580
  this.composableMessage.setDraft(currentLine);
572
581
  this.updateComposeStatusSummary();
@@ -825,6 +834,9 @@ export class InteractiveShell {
825
834
  this.rl.write(newLine); // Write new content
826
835
  // Update persistent prompt display
827
836
  this.persistentPrompt.updateInput(newLine, newCursor);
837
+ // NOTE: Don't clear pasteJustCaptured here - the counter-based logic in shouldIgnoreLineEvent()
838
+ // will decrement for each readline line event and auto-clear when all are processed.
839
+ // Clearing prematurely causes the remaining readline-echoed lines to pass through.
828
840
  // Re-prompt to show the inline content
829
841
  this.rl.prompt(true);
830
842
  return;
@@ -842,13 +854,16 @@ export class InteractiveShell {
842
854
  });
843
855
  // Set the prompt to show paste chips, then position cursor after them
844
856
  // The user can type additional text after the chips
845
- this.persistentPrompt.updateInput(`${pasteChips} `, pasteChips.length + 1);
857
+ this.persistentPrompt.updateInput(pasteChips + ' ', pasteChips.length + 1);
846
858
  // Update readline's line buffer to include the chips as prefix
847
859
  // This ensures typed text appears after the chips
848
860
  if (this.rl.line !== undefined) {
849
- this.rl.line = `${pasteChips} `;
861
+ this.rl.line = pasteChips + ' ';
850
862
  this.rl.cursor = pasteChips.length + 1;
851
863
  }
864
+ // NOTE: Don't clear pasteJustCaptured here - the counter-based logic in shouldIgnoreLineEvent()
865
+ // will decrement for each readline line event (one per pasted line) and auto-clear when done.
866
+ // Clearing prematurely causes remaining readline-echoed lines to pass through and get displayed.
852
867
  this.rl.prompt(true); // preserveCursor=true to keep position after chips
853
868
  }
854
869
  /**
@@ -1188,9 +1203,6 @@ export class InteractiveShell {
1188
1203
  case '/discover':
1189
1204
  await this.discoverModelsCommand();
1190
1205
  break;
1191
- case '/verify':
1192
- await this.handleVerifyCommand();
1193
- break;
1194
1206
  default:
1195
1207
  if (!(await this.tryCustomSlashCommand(command, input))) {
1196
1208
  display.showWarning(`Unknown command "${command}".`);
@@ -1307,6 +1319,7 @@ export class InteractiveShell {
1307
1319
  this.baseSystemPrompt = buildInteractiveSystemPrompt(profileConfig.systemPrompt, profileConfig.label, tools);
1308
1320
  if (this.rebuildAgent()) {
1309
1321
  display.showInfo(`Workspace snapshot refreshed (${this.describeWorkspaceOptions()}).`);
1322
+ this.resetChatBoxAfterModelSwap();
1310
1323
  }
1311
1324
  else {
1312
1325
  display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
@@ -1484,7 +1497,9 @@ export class InteractiveShell {
1484
1497
  }
1485
1498
  this.thinkingMode = value;
1486
1499
  saveSessionPreferences({ thinkingMode: this.thinkingMode });
1487
- this.rebuildAgent();
1500
+ if (this.rebuildAgent()) {
1501
+ this.resetChatBoxAfterModelSwap();
1502
+ }
1488
1503
  const descriptions = {
1489
1504
  concise: 'Hides internal reasoning and responds directly.',
1490
1505
  balanced: 'Shows short thoughts only when helpful.',
@@ -1507,7 +1522,7 @@ export class InteractiveShell {
1507
1522
  lines.push(theme.bold('Session File Changes'));
1508
1523
  lines.push('');
1509
1524
  lines.push(`${theme.info('•')} ${summary.files} file${summary.files === 1 ? '' : 's'} modified`);
1510
- lines.push(`${theme.info('•')} ${theme.success(`+${summary.additions}`)} ${theme.error(`-${summary.removals}`)} lines`);
1525
+ lines.push(`${theme.info('•')} ${theme.success('+' + summary.additions)} ${theme.error('-' + summary.removals)} lines`);
1511
1526
  lines.push('');
1512
1527
  // Group changes by file
1513
1528
  const fileMap = new Map();
@@ -1531,7 +1546,7 @@ export class InteractiveShell {
1531
1546
  if (stats.writes > 0)
1532
1547
  operations.push(`${stats.writes} write${stats.writes === 1 ? '' : 's'}`);
1533
1548
  const opsText = operations.join(', ');
1534
- const diffText = `${theme.success(`+${stats.additions}`)} ${theme.error(`-${stats.removals}`)}`;
1549
+ const diffText = `${theme.success('+' + stats.additions)} ${theme.error('-' + stats.removals)}`;
1535
1550
  lines.push(` ${theme.dim(path)}`);
1536
1551
  lines.push(` ${opsText} • ${diffText}`);
1537
1552
  }
@@ -1541,211 +1556,6 @@ export class InteractiveShell {
1541
1556
  const summary = this.alphaZeroMetrics.getPerformanceSummary();
1542
1557
  display.showSystemMessage(summary);
1543
1558
  }
1544
- /**
1545
- * Create a verification context for isolated process verification.
1546
- *
1547
- * Verification now runs in a completely separate Node.js process for full isolation.
1548
- * This ensures:
1549
- * - Separate memory space from main CLI
1550
- * - Independent event loop
1551
- * - No shared state
1552
- * - Errors in verification cannot crash main process
1553
- */
1554
- createVerificationContext() {
1555
- // Build conversation history for context
1556
- const conversationHistory = this.cachedHistory
1557
- .filter(msg => msg.role === 'user' || msg.role === 'assistant')
1558
- .slice(-10) // Last 10 messages for context
1559
- .map(msg => `${msg.role}: ${typeof msg.content === 'string' ? msg.content.slice(0, 500) : '[complex content]'}`);
1560
- return {
1561
- workingDirectory: this.workingDir,
1562
- conversationHistory,
1563
- provider: this.sessionState.provider,
1564
- model: this.sessionState.model,
1565
- };
1566
- }
1567
- /**
1568
- * Handle /verify command - verify the last assistant response
1569
- */
1570
- async handleVerifyCommand() {
1571
- if (!this.lastAssistantResponse) {
1572
- display.showWarning('No assistant response to verify. Send a message first.');
1573
- return;
1574
- }
1575
- display.showSystemMessage('Verifying last response in isolated process...\n');
1576
- try {
1577
- const context = this.createVerificationContext();
1578
- const report = await verifyResponse(this.lastAssistantResponse, context);
1579
- const formattedReport = formatVerificationReport(report);
1580
- display.showSystemMessage(formattedReport);
1581
- // Show actionable summary
1582
- if (report.overallVerdict === 'contradicted') {
1583
- display.showError('Some claims in the response could not be verified!');
1584
- }
1585
- else if (report.overallVerdict === 'verified') {
1586
- display.showInfo('All verifiable claims in the response were verified.');
1587
- }
1588
- else if (report.overallVerdict === 'partially_verified') {
1589
- display.showWarning('Some claims were verified, but not all.');
1590
- }
1591
- else {
1592
- display.showInfo('No verifiable claims found in the response.');
1593
- }
1594
- }
1595
- catch (err) {
1596
- display.showError(`Verification failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
1597
- }
1598
- }
1599
- /**
1600
- * Check if a response looks like a completion (claims to be done)
1601
- * vs. asking follow-up questions or waiting for user input.
1602
- * Uses LLM to intelligently determine if verification should run.
1603
- * Only run auto-verification when assistant claims task completion.
1604
- */
1605
- async shouldRunAutoVerification(response) {
1606
- // Quick pre-filter: very short responses are unlikely to have verifiable claims
1607
- if (response.length < 100) {
1608
- return false;
1609
- }
1610
- try {
1611
- // Use LLM to determine if this response contains verifiable completion claims
1612
- const prompt = `Analyze this AI assistant response and determine if it claims to have COMPLETED a task that can be verified.
1613
-
1614
- RESPONSE:
1615
- ---
1616
- ${response.slice(0, 2000)}
1617
- ---
1618
-
1619
- Answer with ONLY "YES" or "NO":
1620
- - YES: The response claims to have completed something verifiable (created/modified files, ran commands, fixed bugs, implemented features, etc.)
1621
- - NO: The response is asking questions, requesting clarification, explaining concepts, or hasn't completed any verifiable action yet.
1622
-
1623
- Answer:`;
1624
- const agent = this.runtimeSession.createAgent({
1625
- provider: this.sessionState.provider,
1626
- model: this.sessionState.model,
1627
- temperature: 0,
1628
- maxTokens: 10,
1629
- systemPrompt: 'You are a classifier. Answer only YES or NO.',
1630
- });
1631
- const result = await agent.send(prompt);
1632
- const answer = result.trim().toUpperCase();
1633
- return answer.startsWith('YES');
1634
- }
1635
- catch {
1636
- // On error, fall back to not running verification
1637
- return false;
1638
- }
1639
- }
1640
- /**
1641
- * Schedule auto-verification after assistant response.
1642
- * Uses LLM-based semantic analysis to verify ALL claims.
1643
- * Runs asynchronously to not block the UI.
1644
- * Only runs when assistant claims completion, not when asking questions.
1645
- */
1646
- scheduleAutoVerification(response) {
1647
- // Run verification asynchronously after a short delay
1648
- // This allows the UI to update first
1649
- setTimeout(async () => {
1650
- try {
1651
- // Use LLM to determine if this response should be verified
1652
- const shouldVerify = await this.shouldRunAutoVerification(response);
1653
- if (!shouldVerify) {
1654
- return;
1655
- }
1656
- display.showSystemMessage(`\n🔍 Auto-verifying response in isolated process...`);
1657
- const context = this.createVerificationContext();
1658
- const report = await verifyResponse(response, context);
1659
- const formattedReport = formatVerificationReport(report);
1660
- // Show compact result
1661
- if (report.summary.total === 0) {
1662
- display.showInfo('No verifiable claims found in the response.');
1663
- this.verificationRetryCount = 0;
1664
- return;
1665
- }
1666
- if (report.overallVerdict === 'verified') {
1667
- display.showInfo(`✅ Verified: ${report.summary.verified}/${report.summary.total} claims confirmed`);
1668
- // Reset retry count on success
1669
- this.verificationRetryCount = 0;
1670
- }
1671
- else if (report.overallVerdict === 'contradicted' || report.overallVerdict === 'partially_verified') {
1672
- const failedCount = report.summary.failed;
1673
- const icon = report.overallVerdict === 'contradicted' ? '❌' : '⚠️';
1674
- const label = report.overallVerdict === 'contradicted' ? 'Verification failed' : 'Partial verification';
1675
- display.showError(`${icon} ${label}: ${failedCount} claim${failedCount > 1 ? 's' : ''} could not be verified`);
1676
- display.showSystemMessage(formattedReport);
1677
- // Attempt to fix if we have retries left
1678
- if (this.verificationRetryCount < this.maxVerificationRetries) {
1679
- this.verificationRetryCount++;
1680
- this.requestVerificationFix(report);
1681
- }
1682
- else {
1683
- display.showWarning(`Max verification retries (${this.maxVerificationRetries}) reached. Use /verify to check manually.`);
1684
- this.verificationRetryCount = 0;
1685
- }
1686
- }
1687
- }
1688
- catch (err) {
1689
- // Silently ignore verification errors to not disrupt the flow
1690
- // User can always run /verify manually
1691
- }
1692
- }, 500);
1693
- }
1694
- /**
1695
- * Request the AI to fix failed verification claims.
1696
- * Generates a strategic fix request with context about what failed and why.
1697
- */
1698
- requestVerificationFix(report) {
1699
- const failedResults = report.results.filter(r => !r.verified && r.confidence === 'high');
1700
- if (failedResults.length === 0) {
1701
- return;
1702
- }
1703
- // Build detailed failure descriptions with suggested fixes
1704
- const failureDetails = failedResults.map(r => {
1705
- const claim = r.claim;
1706
- const evidence = r.evidence;
1707
- // Generate specific fix strategy based on claim category
1708
- let suggestedFix = '';
1709
- switch (claim.category) {
1710
- case 'file_op':
1711
- suggestedFix = `Re-create or update the file at: ${claim.context['path'] || 'specified path'}`;
1712
- break;
1713
- case 'code':
1714
- suggestedFix = 'Fix any type errors or syntax issues, then run the build again';
1715
- break;
1716
- case 'command':
1717
- suggestedFix = 'Re-run the command and verify it completes successfully';
1718
- break;
1719
- case 'state':
1720
- suggestedFix = 'Verify the state change was applied correctly';
1721
- break;
1722
- case 'behavior':
1723
- suggestedFix = 'Test the feature manually or check implementation';
1724
- break;
1725
- default:
1726
- suggestedFix = 'Retry the operation';
1727
- }
1728
- return `• ${claim.statement}
1729
- Evidence: ${evidence.slice(0, 150)}
1730
- Suggested fix: ${suggestedFix}`;
1731
- }).join('\n\n');
1732
- const fixMessage = `🔧 VERIFICATION FAILED - AUTO-RETRY (attempt ${this.verificationRetryCount}/${this.maxVerificationRetries})
1733
-
1734
- The following claims could not be verified:
1735
-
1736
- ${failureDetails}
1737
-
1738
- Think through this carefully, then:
1739
- 1. Analyze why each operation failed (check files, errors, state)
1740
- 2. Identify the root cause
1741
- 3. Fix the underlying issue
1742
- 4. Re-execute the failed operation(s)
1743
- 5. Verify the fix worked`;
1744
- display.showSystemMessage(`\n🔧 Auto-retry: Generating fix strategy for ${failedResults.length} failed claim${failedResults.length > 1 ? 's' : ''}...`);
1745
- // Queue the fix request
1746
- this.followUpQueue.push({ type: 'request', text: fixMessage });
1747
- this.scheduleQueueProcessing();
1748
- }
1749
1559
  showImprovementSuggestions() {
1750
1560
  const suggestions = this.alphaZeroMetrics.getImprovementSuggestions();
1751
1561
  if (suggestions.length === 0) {
@@ -2384,6 +2194,7 @@ Think through this carefully, then:
2384
2194
  display.showInfo(`Switched to ${preset.label}.`);
2385
2195
  this.refreshBannerSessionInfo();
2386
2196
  this.persistSessionPreference();
2197
+ this.resetChatBoxAfterModelSwap();
2387
2198
  }
2388
2199
  }
2389
2200
  async handleSecretSelection(input) {
@@ -2444,7 +2255,9 @@ Think through this carefully, then:
2444
2255
  const deferred = this.pendingSecretRetry;
2445
2256
  this.pendingSecretRetry = null;
2446
2257
  if (pending.secret.providers.includes(this.sessionState.provider)) {
2447
- this.rebuildAgent();
2258
+ if (this.rebuildAgent()) {
2259
+ this.resetChatBoxAfterModelSwap();
2260
+ }
2448
2261
  }
2449
2262
  if (deferred) {
2450
2263
  await deferred();
@@ -2472,20 +2285,25 @@ Think through this carefully, then:
2472
2285
  return;
2473
2286
  }
2474
2287
  this.isProcessing = true;
2475
- this.resetThinkingState(); // Reset thinking block styling state
2476
2288
  const requestStartTime = Date.now(); // Alpha Zero 2 timing
2289
+ // Keep persistent prompt visible during processing so users can type follow-up requests
2290
+ // The prompt will show a "processing" indicator but remain interactive
2291
+ this.persistentPrompt.updateStatusBar({ message: '⏳ Processing... (type to queue follow-up)' });
2477
2292
  // Update pinned chat box to show processing state
2293
+ // Clear the input display since the request was already submitted
2294
+ // Note: Don't set statusMessage here - the isProcessing flag already shows "⏳ Processing..."
2478
2295
  this.pinnedChatBox.setProcessing(true);
2479
- this.pinnedChatBox.setStatusMessage(null);
2296
+ this.pinnedChatBox.setStatusMessage(null); // Clear any previous status to avoid duplication
2480
2297
  this.pinnedChatBox.clearInput();
2481
- // Add newline so user's submitted input stays visible
2482
- // (readline already displayed their input, we just need to preserve it)
2483
- process.stdout.write('\n');
2484
- // Note: Don't render pinned box during streaming - it interferes with content
2485
- // The spinner will handle showing activity
2486
2298
  this.uiAdapter.startProcessing('Working on your request');
2487
2299
  this.setProcessingStatus();
2488
2300
  try {
2301
+ display.newLine();
2302
+ // Pinned chat box already shows processing state - skip redundant spinner
2303
+ // which would conflict with the pinned area at terminal bottom
2304
+ // display.showThinking('Working on your request...');
2305
+ // Force render the pinned chat box to ensure it's visible during processing
2306
+ this.pinnedChatBox.forceRender();
2489
2307
  // Enable streaming for real-time text output (Claude Code style)
2490
2308
  await agent.send(request, true);
2491
2309
  await this.awaitPendingCleanup();
@@ -2507,15 +2325,17 @@ Think through this carefully, then:
2507
2325
  this.isProcessing = false;
2508
2326
  this.uiAdapter.endProcessing('Ready for prompts');
2509
2327
  this.setIdleStatus();
2510
- // Clear the pinned processing box before showing final output
2511
- this.pinnedChatBox.clear();
2512
- this.pinnedChatBox.setProcessing(false);
2513
- this.pinnedChatBox.setStatusMessage(null);
2328
+ display.newLine();
2514
2329
  // Clear the processing status and ensure persistent prompt is visible
2515
2330
  this.persistentPrompt.updateStatusBar({ message: undefined });
2516
2331
  this.persistentPrompt.show();
2332
+ // Update pinned chat box to show ready state and force render
2333
+ this.pinnedChatBox.setProcessing(false);
2334
+ this.pinnedChatBox.setStatusMessage(null);
2335
+ this.pinnedChatBox.forceRender();
2517
2336
  // CRITICAL: Ensure readline prompt is active for user input
2518
- // This is a safety net in case the caller doesn't call rl.prompt()
2337
+ // Call ensureReadlineReady to resume stdin if paused and re-enable keypress
2338
+ this.ensureReadlineReady();
2519
2339
  this.rl.prompt();
2520
2340
  this.scheduleQueueProcessing();
2521
2341
  this.refreshQueueIndicators();
@@ -2731,13 +2551,13 @@ What's the next action?`;
2731
2551
  // Clear the processing status and ensure persistent prompt is visible
2732
2552
  this.persistentPrompt.updateStatusBar({ message: undefined });
2733
2553
  this.persistentPrompt.show();
2734
- // Clear streaming status line (Claude Code style)
2735
- display.clearStreamingStatus();
2736
- // Update pinned chat box to show ready state
2554
+ // Update pinned chat box to show ready state and force render
2737
2555
  this.pinnedChatBox.setProcessing(false);
2738
2556
  this.pinnedChatBox.setStatusMessage(null);
2557
+ this.pinnedChatBox.forceRender();
2739
2558
  // CRITICAL: Ensure readline prompt is active for user input
2740
- // This is a safety net in case the caller doesn't call rl.prompt()
2559
+ // Call ensureReadlineReady to resume stdin if paused and re-enable keypress
2560
+ this.ensureReadlineReady();
2741
2561
  this.rl.prompt();
2742
2562
  this.scheduleQueueProcessing();
2743
2563
  this.refreshQueueIndicators();
@@ -2918,46 +2738,51 @@ What's the next action?`;
2918
2738
  display.stopThinking(false);
2919
2739
  process.stdout.write('\n'); // Newline after spinner
2920
2740
  }
2921
- // Style thinking blocks (Claude Code style)
2922
- const styledChunk = this.styleStreamingChunk(chunk);
2923
- process.stdout.write(styledChunk);
2741
+ process.stdout.write(chunk);
2924
2742
  });
2925
2743
  },
2926
2744
  onAssistantMessage: (content, metadata) => {
2927
2745
  const enriched = this.buildDisplayMetadata(metadata);
2928
2746
  // Update spinner based on message type
2929
2747
  if (metadata.isFinal) {
2930
- const parsed = this.splitThinkingResponse(content);
2931
- // Don't re-display thinking - it was already streamed in real-time
2932
- // Just extract the response part
2933
- const finalContent = parsed?.response?.trim() || content.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '').trim();
2934
- if (finalContent) {
2935
- display.showAssistantMessage(finalContent, enriched);
2748
+ // Skip display if content was already streamed to avoid double-display
2749
+ if (!metadata.wasStreamed) {
2750
+ const parsed = this.splitThinkingResponse(content);
2751
+ if (parsed?.thinking) {
2752
+ const summary = this.extractThoughtSummary(parsed.thinking);
2753
+ if (summary) {
2754
+ display.updateThinking(`💭 ${summary}`);
2755
+ }
2756
+ display.showAssistantMessage(parsed.thinking, { ...enriched, isFinal: false });
2757
+ }
2758
+ const finalContent = parsed?.response?.trim() || content;
2759
+ if (finalContent) {
2760
+ display.showAssistantMessage(finalContent, enriched);
2761
+ }
2936
2762
  }
2937
- // Store last response for verification
2938
- this.lastAssistantResponse = content;
2939
- // Auto-verify if response contains verifiable claims
2940
- this.scheduleAutoVerification(content);
2941
- // Show status line at end (Claude Code style: "Session 5m • Context X% used • Ready for prompts (2s)")
2763
+ // Show status line at end (Claude Code style: "• Context X% used • Ready for prompts (2s)")
2942
2764
  display.stopThinking();
2943
- // Calculate context usage and session time
2944
- const sessionElapsedMs = Date.now() - this.sessionStartTime;
2945
- let contextInfo = { sessionElapsedMs };
2765
+ // Calculate context usage
2766
+ let contextInfo;
2946
2767
  if (enriched.contextWindowTokens && metadata.usage) {
2947
2768
  const total = this.totalTokens(metadata.usage);
2948
2769
  if (total && total > 0) {
2949
2770
  const percentage = Math.round((total / enriched.contextWindowTokens) * 100);
2950
- contextInfo = { ...contextInfo, percentage, tokens: total };
2771
+ contextInfo = { percentage, tokens: total };
2951
2772
  }
2952
2773
  }
2953
2774
  display.showStatusLine('Ready for prompts', enriched.elapsedMs, contextInfo);
2954
2775
  }
2955
2776
  else {
2956
- // Non-final message = narrative text before tool calls
2957
- // This content was already streamed in real-time via onStreamChunk
2958
- // Don't display it again - just stop the spinner and continue
2777
+ // Non-final message = narrative text before tool calls (Claude Code style)
2778
+ // Stop spinner and show the narrative text directly
2959
2779
  display.stopThinking();
2960
- // Continue processing - content already shown via streaming
2780
+ // Skip display if content was already streamed to avoid double-display
2781
+ if (!metadata.wasStreamed) {
2782
+ display.showNarrative(content.trim());
2783
+ }
2784
+ // The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
2785
+ this.pinnedChatBox.forceRender();
2961
2786
  return;
2962
2787
  }
2963
2788
  const cleanup = this.handleContextTelemetry(metadata, enriched);
@@ -3028,6 +2853,18 @@ What's the next action?`;
3028
2853
  return false;
3029
2854
  }
3030
2855
  }
2856
+ /**
2857
+ * Reset the pinned chat box to a fresh state after model/provider swap.
2858
+ * Ensures the input box is properly visible and ready for input,
2859
+ * just like on fresh startup.
2860
+ */
2861
+ resetChatBoxAfterModelSwap() {
2862
+ this.pinnedChatBox.setStatusMessage(null);
2863
+ this.pinnedChatBox.setProcessing(false);
2864
+ this.pinnedChatBox.show();
2865
+ this.pinnedChatBox.forceRender();
2866
+ this.ensureReadlineReady();
2867
+ }
3031
2868
  buildSystemPrompt() {
3032
2869
  const providerLabel = this.providerLabel(this.sessionState.provider);
3033
2870
  const lines = [
@@ -3389,6 +3226,27 @@ What's the next action?`;
3389
3226
  const fileChangesText = `${summary.files} file${summary.files === 1 ? '' : 's'} +${summary.additions} -${summary.removals}`;
3390
3227
  this.persistentPrompt.updateStatusBar({ fileChanges: fileChangesText });
3391
3228
  }
3229
+ extractThoughtSummary(thought) {
3230
+ // Extract first non-empty line
3231
+ const lines = thought?.split('\n').filter(line => line.trim()) ?? [];
3232
+ if (!lines.length) {
3233
+ return null;
3234
+ }
3235
+ // Remove common thought prefixes
3236
+ const cleaned = lines[0]
3237
+ .trim()
3238
+ .replace(/^(Thinking|Analyzing|Considering|Looking at|Let me)[:.\s]+/i, '')
3239
+ .replace(/^I (should|need to|will|am)[:.\s]+/i, '')
3240
+ .trim();
3241
+ if (!cleaned) {
3242
+ return null;
3243
+ }
3244
+ // Truncate to reasonable length
3245
+ const maxLength = 50;
3246
+ return cleaned.length > maxLength
3247
+ ? cleaned.slice(0, maxLength - 3) + '...'
3248
+ : cleaned;
3249
+ }
3392
3250
  splitThinkingResponse(content) {
3393
3251
  if (!content?.includes('<thinking') && !content?.includes('<response')) {
3394
3252
  return null;
@@ -3411,61 +3269,6 @@ What's the next action?`;
3411
3269
  response: responseBody ?? '',
3412
3270
  };
3413
3271
  }
3414
- /**
3415
- * Style streaming chunks in real-time (Claude Code style)
3416
- * Detects <thinking> blocks and applies cyan styling, hides XML tags
3417
- */
3418
- styleStreamingChunk(chunk) {
3419
- let result = '';
3420
- let remaining = chunk;
3421
- while (remaining.length > 0) {
3422
- if (this.isInsideThinkingBlock) {
3423
- // Look for </thinking> end tag
3424
- const endIdx = remaining.indexOf('</thinking>');
3425
- if (endIdx !== -1) {
3426
- // End of thinking block found
3427
- const thinkingContent = remaining.slice(0, endIdx);
3428
- // Apply cyan thinking styling to content (hide the closing tag)
3429
- result += theme.thinking.text(thinkingContent);
3430
- remaining = remaining.slice(endIdx + '</thinking>'.length);
3431
- this.isInsideThinkingBlock = false;
3432
- // Add separator and newline after thinking block ends
3433
- result += `\n${theme.thinking.border('─'.repeat(40))}\n`;
3434
- }
3435
- else {
3436
- // Still inside thinking block, apply cyan styling to all remaining
3437
- result += theme.thinking.text(remaining);
3438
- remaining = '';
3439
- }
3440
- }
3441
- else {
3442
- // Look for <thinking> start tag
3443
- const startIdx = remaining.indexOf('<thinking>');
3444
- if (startIdx !== -1) {
3445
- // Output text before thinking tag normally
3446
- if (startIdx > 0) {
3447
- result += remaining.slice(0, startIdx);
3448
- }
3449
- // Show thinking header with cyan styling (Claude Code style)
3450
- result += `${theme.thinking.icon('💭')} ${theme.thinking.label('Thinking')}\n`;
3451
- remaining = remaining.slice(startIdx + '<thinking>'.length);
3452
- this.isInsideThinkingBlock = true;
3453
- }
3454
- else {
3455
- // No thinking tag, output normally
3456
- result += remaining;
3457
- remaining = '';
3458
- }
3459
- }
3460
- }
3461
- return result;
3462
- }
3463
- /**
3464
- * Reset thinking block state (call at start of new request)
3465
- */
3466
- resetThinkingState() {
3467
- this.isInsideThinkingBlock = false;
3468
- }
3469
3272
  persistSessionPreference() {
3470
3273
  saveModelPreference(this.profile, {
3471
3274
  provider: this.sessionState.provider,
@@ -3520,6 +3323,7 @@ What's the next action?`;
3520
3323
  this.persistSessionPreference();
3521
3324
  this.refreshBannerSessionInfo();
3522
3325
  display.showInfo(`Switched from ${this.providerLabel(oldProvider)}/${oldModel} to ${match.label}/${defaultModel.id}`);
3326
+ this.resetChatBoxAfterModelSwap();
3523
3327
  }
3524
3328
  else {
3525
3329
  // Revert on failure