erosolar-cli 1.7.405 → 1.7.407

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.
@@ -5,7 +5,7 @@ import { createInterface } from 'node:readline/promises';
5
5
  import { AssistantBlockRenderer } from '../ui/assistantBlockRenderer.js';
6
6
  import { display } from '../ui/display.js';
7
7
  import { compactRenderer } from '../ui/compactRenderer.js';
8
- import { theme } from '../ui/theme.js';
8
+ import { theme, icons } from '../ui/theme.js';
9
9
  import { getContextWindowTokens } from '../core/contextWindow.js';
10
10
  import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, MissingSecretError, maskSecret, setSecretValue, } from '../core/secretStore.js';
11
11
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
@@ -43,6 +43,7 @@ import { enterStreamingMode, exitStreamingMode } from '../ui/globalWriteLock.js'
43
43
  import { setGlobalAIEnhancer } from '../tools/localExplore.js';
44
44
  import { createProvider } from '../providers/providerFactory.js';
45
45
  import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
46
+ import { formatThinkingContent } from '../ui/textHighlighter.js';
46
47
  const execAsync = promisify(exec);
47
48
  const DROPDOWN_COLORS = [
48
49
  theme.primary,
@@ -147,6 +148,7 @@ export class InteractiveShell {
147
148
  customCommandMap;
148
149
  sessionRestoreConfig;
149
150
  _enabledPlugins;
151
+ autoVerificationInFlight = false;
150
152
  // Auto-test tracking
151
153
  autoTestInFlight = false;
152
154
  // AlphaZero learning tracking
@@ -254,6 +256,11 @@ export class InteractiveShell {
254
256
  description: 'Show available and loaded plugins',
255
257
  category: 'configuration',
256
258
  });
259
+ this.slashCommands.push({
260
+ command: '/contextlog',
261
+ description: 'Show recent context compaction summaries',
262
+ category: 'context',
263
+ });
257
264
  this.slashCommands.push({
258
265
  command: '/offsec',
259
266
  description: 'AlphaZero offensive security run (start/status/next)',
@@ -1374,13 +1381,14 @@ export class InteractiveShell {
1374
1381
  return;
1375
1382
  }
1376
1383
  this.assistantStreamActive = true;
1377
- this.assistantStreamPhase = null;
1384
+ // Default to thought phase so the first output after a prompt is the assistant's thinking.
1385
+ this.assistantStreamPhase = 'thought';
1378
1386
  this.assistantStreamHeaderShown = false;
1379
1387
  this.assistantStreamBuffer = '';
1380
1388
  this.assistantStreamMetadata = metadata;
1381
1389
  this.assistantStreamHadContent = false;
1382
1390
  }
1383
- streamAssistantChunk(chunk, _channel) {
1391
+ streamAssistantChunk(chunk, channel) {
1384
1392
  if (!chunk) {
1385
1393
  return;
1386
1394
  }
@@ -1389,6 +1397,9 @@ export class InteractiveShell {
1389
1397
  contextWindowTokens: this.activeContextWindowTokens,
1390
1398
  });
1391
1399
  }
1400
+ // Route chunks based on channel for proper phase tracking
1401
+ // The agent sends 'thought' channel for reasoning chunks already wrapped in <thinking> tags
1402
+ // The tag parsing in processAssistantStreamChunk will handle phase transitions
1392
1403
  this.processAssistantStreamChunk(chunk);
1393
1404
  }
1394
1405
  finalizeAssistantStream() {
@@ -1445,13 +1456,17 @@ export class InteractiveShell {
1445
1456
  if (content.trim().length > 0) {
1446
1457
  this.assistantStreamHadContent = true;
1447
1458
  }
1448
- if (!this.assistantStreamPhase) {
1449
- this.setAssistantStreamPhase('response');
1450
- }
1459
+ // If no explicit phase has been set yet, keep the stream labeled as "thought"
1460
+ // until a <response> tag arrives so operators see thinking first.
1461
+ this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
1451
1462
  if (this.assistantStreamPhase) {
1452
1463
  this.renderAssistantStreamHeader(this.assistantStreamPhase);
1453
1464
  }
1454
- this.enqueueAssistantStream(content);
1465
+ let output = content;
1466
+ if (this.assistantStreamPhase === 'thought') {
1467
+ output = this.formatThoughtDisplay(content, { streaming: true });
1468
+ }
1469
+ this.enqueueAssistantStream(output);
1455
1470
  }
1456
1471
  setAssistantStreamPhase(phase) {
1457
1472
  if (this.assistantStreamPhase === phase) {
@@ -1530,11 +1545,12 @@ export class InteractiveShell {
1530
1545
  if (!normalized) {
1531
1546
  return;
1532
1547
  }
1548
+ const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
1533
1549
  if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1534
- this.renderAssistantBlock(type, normalized, metadata);
1550
+ this.renderAssistantBlock(type, formatted, metadata);
1535
1551
  return;
1536
1552
  }
1537
- this.renderAssistantFallback(type, normalized, metadata);
1553
+ this.renderAssistantFallback(type, formatted, metadata);
1538
1554
  }
1539
1555
  renderAssistantFallback(type, content, metadata) {
1540
1556
  const header = this.formatAssistantHeader(type, metadata);
@@ -1542,6 +1558,21 @@ export class InteractiveShell {
1542
1558
  const block = `\n${header}\n${compact}\n\n`;
1543
1559
  this.enqueueAssistantStream(block);
1544
1560
  }
1561
+ formatThoughtDisplay(content, options = {}) {
1562
+ const normalized = content.replace(/\r\n/g, '\n');
1563
+ if (options.streaming) {
1564
+ return formatThinkingContent(normalized);
1565
+ }
1566
+ const lines = normalized.split('\n');
1567
+ return lines
1568
+ .map((line, index) => {
1569
+ const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
1570
+ const trimmed = line.trim();
1571
+ const body = trimmed ? formatThinkingContent(trimmed) : '';
1572
+ return body ? `${prefix} ${body}` : `${prefix}`;
1573
+ })
1574
+ .join('\n');
1575
+ }
1545
1576
  formatAssistantHeader(type, metadata) {
1546
1577
  if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
1547
1578
  return this.assistantBlockRenderer.formatHeader(type, metadata);
@@ -2241,6 +2272,9 @@ export class InteractiveShell {
2241
2272
  case '/context':
2242
2273
  await this.refreshWorkspaceContextCommand(input);
2243
2274
  break;
2275
+ case '/contextlog':
2276
+ this.showContextSummaryLog(input);
2277
+ break;
2244
2278
  case '/agents':
2245
2279
  this.showAgentsMenu();
2246
2280
  break;
@@ -2539,6 +2573,66 @@ export class InteractiveShell {
2539
2573
  display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
2540
2574
  }
2541
2575
  }
2576
+ showContextSummaryLog(input) {
2577
+ const agent = this.agent;
2578
+ if (!agent) {
2579
+ display.showWarning('No active agent session. Start one to inspect context summaries.');
2580
+ return;
2581
+ }
2582
+ const contextManager = agent.getContextManager();
2583
+ if (!contextManager?.getSummaryLog) {
2584
+ display.showWarning('Context summary logging is unavailable in this session.');
2585
+ return;
2586
+ }
2587
+ const tokens = input.trim().split(/\s+/).slice(1);
2588
+ let limit = 5;
2589
+ for (const token of tokens) {
2590
+ if (!token)
2591
+ continue;
2592
+ const normalized = token.toLowerCase();
2593
+ if (/^\d+$/.test(token)) {
2594
+ limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
2595
+ }
2596
+ else if (normalized.startsWith('limit=')) {
2597
+ const value = parseInt(normalized.split('=')[1] ?? '', 10);
2598
+ if (!Number.isNaN(value)) {
2599
+ limit = Math.min(Math.max(value, 1), 20);
2600
+ }
2601
+ }
2602
+ }
2603
+ const entries = contextManager.getSummaryLog(limit);
2604
+ if (!entries.length) {
2605
+ display.showInfo('No context summaries captured yet.');
2606
+ return;
2607
+ }
2608
+ const lines = [];
2609
+ lines.push(`${theme.primary('Context summaries')} ${theme.ui.muted(`(latest ${entries.length})`)}`);
2610
+ for (const entry of entries) {
2611
+ const timestamp = new Date(entry.timestamp).toLocaleString();
2612
+ const headerParts = [
2613
+ theme.ui.muted(timestamp),
2614
+ theme.info(entry.method),
2615
+ `removed ${theme.error(String(entry.removed))}`,
2616
+ `kept ${theme.success(String(entry.preserved))}`,
2617
+ ];
2618
+ if (entry.reason) {
2619
+ headerParts.push(theme.ui.muted(entry.reason));
2620
+ }
2621
+ if (entry.model) {
2622
+ headerParts.push(theme.ui.muted(`model=${entry.model}`));
2623
+ }
2624
+ lines.push(headerParts.join(' Ā· '));
2625
+ if (entry.summary) {
2626
+ const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}…` : entry.summary;
2627
+ lines.push(trimmed);
2628
+ }
2629
+ else {
2630
+ lines.push(theme.ui.muted('(no summary text — simple prune)'));
2631
+ }
2632
+ lines.push(''); // spacer
2633
+ }
2634
+ display.showSystemMessage(lines.join('\n'));
2635
+ }
2542
2636
  parseContextOverrideTokens(input) {
2543
2637
  const overrides = {};
2544
2638
  let hasOverride = false;
@@ -5815,7 +5909,25 @@ What's the next action?`;
5815
5909
  }
5816
5910
  if (name === 'bash' || name === 'execute_bash') {
5817
5911
  const command = String(entry.args['command'] ?? '').toLowerCase();
5818
- return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
5912
+ const patterns = [
5913
+ 'npm test',
5914
+ 'yarn test',
5915
+ 'pnpm test',
5916
+ 'bun test',
5917
+ 'go test',
5918
+ 'cargo test',
5919
+ 'pytest',
5920
+ 'python -m pytest',
5921
+ 'tox',
5922
+ 'mvn test',
5923
+ './mvnw test',
5924
+ 'gradle test',
5925
+ './gradlew test',
5926
+ 'dotnet test',
5927
+ 'make test',
5928
+ 'swift test',
5929
+ ];
5930
+ return patterns.some((pattern) => command.includes(pattern));
5819
5931
  }
5820
5932
  return false;
5821
5933
  }
@@ -5892,10 +6004,31 @@ What's the next action?`;
5892
6004
  }
5893
6005
  if (name === 'bash' || name === 'execute_bash') {
5894
6006
  const command = String(entry.args['command'] ?? '').toLowerCase();
5895
- return (command.includes('npm run build') ||
5896
- command.includes('yarn build') ||
5897
- command.includes('pnpm build') ||
5898
- command.includes('tsc'));
6007
+ const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
6008
+ const patterns = [
6009
+ 'npm run build',
6010
+ 'yarn build',
6011
+ 'pnpm build',
6012
+ 'bun run build',
6013
+ 'bun build',
6014
+ 'tsc',
6015
+ 'cargo build',
6016
+ 'go build',
6017
+ 'python -m build',
6018
+ 'mvn -b -dskiptests package',
6019
+ 'mvn package',
6020
+ 'mvn install',
6021
+ './mvnw -b -dskiptests package',
6022
+ './mvnw package',
6023
+ './mvnw install',
6024
+ 'gradle build',
6025
+ 'gradle assemble',
6026
+ './gradlew build',
6027
+ './gradlew assemble',
6028
+ 'dotnet build',
6029
+ 'swift build',
6030
+ ];
6031
+ return makeBuild || patterns.some((pattern) => command.includes(pattern));
5899
6032
  }
5900
6033
  return false;
5901
6034
  }
@@ -6083,8 +6216,8 @@ What's the next action?`;
6083
6216
  this.requestPromptRefresh();
6084
6217
  },
6085
6218
  onContextSquishing: (message) => {
6086
- // Show notification in UI when auto context squishing occurs
6087
- display.showSystemMessage(`šŸ”„ ${message}`);
6219
+ // Stream notification in UI when auto context squishing occurs
6220
+ display.stream(`\nšŸ”„ ${message}\n`);
6088
6221
  this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
6089
6222
  detail: 'Reducing conversation history to fit within token limits',
6090
6223
  tone: 'warning',
@@ -6092,7 +6225,7 @@ What's the next action?`;
6092
6225
  },
6093
6226
  onContextRecovery: (attempt, maxAttempts, message) => {
6094
6227
  // Show recovery progress in UI
6095
- display.showSystemMessage(`⚔ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
6228
+ display.stream(`\n⚔ Context Recovery (${attempt}/${maxAttempts}): ${message}\n`);
6096
6229
  },
6097
6230
  onContextPruned: (removedCount, stats) => {
6098
6231
  // Clear squish overlay if active
@@ -6100,19 +6233,23 @@ What's the next action?`;
6100
6233
  // Show notification that context was pruned
6101
6234
  const method = stats['method'];
6102
6235
  const percentage = stats['percentage'];
6236
+ const summarized = stats['summarized'] === true;
6103
6237
  if (method === 'emergency-recovery') {
6104
- display.showSystemMessage(`āœ… Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%`);
6238
+ display.stream(`\nāœ… Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%\n`);
6105
6239
  }
6106
6240
  // Update context usage in UI
6107
6241
  if (typeof percentage === 'number') {
6108
6242
  this.updateContextUsage(percentage);
6109
6243
  }
6244
+ if (summarized) {
6245
+ display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
6246
+ }
6110
6247
  // Ensure prompt remains visible at bottom after context messages
6111
6248
  this.renderPromptArea();
6112
6249
  },
6113
6250
  onContinueAfterRecovery: () => {
6114
6251
  // Update UI to show we're continuing after context recovery
6115
- display.showSystemMessage(`šŸ”„ Continuing after context recovery...`);
6252
+ display.stream(`\nšŸ”„ Continuing after context recovery...\n`);
6116
6253
  this.updateStatusMessage('Retrying with reduced context...');
6117
6254
  this.renderPromptArea();
6118
6255
  },
@@ -6244,8 +6381,8 @@ What's the next action?`;
6244
6381
  ].join('\n');
6245
6382
  case 'balanced':
6246
6383
  default:
6247
- // Balanced is the default; avoid injecting extra prompt text to keep context lean.
6248
- return null;
6384
+ // Balanced mode: show thinking for complex reasoning, planning, or multi-step tasks
6385
+ return 'When reasoning through complex tasks, planning approaches, or making decisions, wrap your thought process in <thinking> tags before your response. Keep thinking concise and focused on the decision-making process.';
6249
6386
  }
6250
6387
  }
6251
6388
  buildDisplayMetadata(metadata) {
@@ -6279,10 +6416,18 @@ What's the next action?`;
6279
6416
  if (!operations.length) {
6280
6417
  return;
6281
6418
  }
6282
- const summaryLine = compactRenderer.formatCompactLine(operations, {
6419
+ let summaryLine = compactRenderer.formatCompactLine(operations, {
6283
6420
  showIcons: true,
6284
6421
  showTimings: true,
6285
6422
  });
6423
+ const warnings = this.uiAdapter.getRecentWarnings(since);
6424
+ if (warnings.length) {
6425
+ const limited = warnings.slice(0, 3);
6426
+ const formattedWarnings = limited
6427
+ .map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
6428
+ .join(' • ');
6429
+ summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
6430
+ }
6286
6431
  if (!summaryLine.trim()) {
6287
6432
  this.lastToolSummaryRenderedAt = Date.now();
6288
6433
  return;