erosolar-cli 2.1.10 → 2.1.12

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.
@@ -1,4 +1,5 @@
1
1
  import { stdin as input, stdout as output, exit } from 'node:process';
2
+ import { homedir } from 'node:os';
2
3
  import { exec } from 'node:child_process';
3
4
  import { promisify } from 'node:util';
4
5
  import { existsSync, readFileSync } from 'node:fs';
@@ -126,6 +127,7 @@ export class InteractiveShell {
126
127
  autosaveEnabled;
127
128
  autoContinueEnabled;
128
129
  verificationEnabled = false;
130
+ criticalApprovalMode = 'auto';
129
131
  editGuardMode = 'display-edits';
130
132
  pendingPermissionInput = null;
131
133
  pendingHistoryLoad = null;
@@ -188,6 +190,7 @@ export class InteractiveShell {
188
190
  this.thinkingMode = this.sessionPreferences.thinkingMode;
189
191
  this.autosaveEnabled = this.sessionPreferences.autosave;
190
192
  this.autoContinueEnabled = this.sessionPreferences.autoContinue;
193
+ this.criticalApprovalMode = this.sessionPreferences.criticalApprovalMode;
191
194
  const featureFlags = loadFeatureFlags();
192
195
  this.verificationEnabled = featureFlags.verification === true;
193
196
  this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
@@ -235,6 +238,11 @@ export class InteractiveShell {
235
238
  description: 'Show available and loaded plugins',
236
239
  category: 'configuration',
237
240
  });
241
+ this.slashCommands.push({
242
+ command: '/approvals',
243
+ description: 'Switch between auto and approval mode for high-impact actions',
244
+ category: 'configuration',
245
+ });
238
246
  this.slashCommands.push({
239
247
  command: '/offsec',
240
248
  description: 'AlphaZero offensive security run (start/status/next)',
@@ -272,6 +280,7 @@ export class InteractiveShell {
272
280
  onToggleAutoContinue: () => this.toggleAutoContinueMode(),
273
281
  onToggleThinking: () => this.cycleThinkingMode(),
274
282
  onClearContext: () => this.handleClearContext(),
283
+ onToggleCriticalApproval: () => this.toggleCriticalApprovalMode('shortcut'),
275
284
  });
276
285
  // Share renderer with Display so all output flows through the unified queue
277
286
  this.renderer = this.terminalInput.getRenderer();
@@ -280,6 +289,8 @@ export class InteractiveShell {
280
289
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
281
290
  this.setupStatusTracking();
282
291
  this.refreshContextGauge();
292
+ // Prime renderer state before it first paints
293
+ this.refreshControlBar();
283
294
  // Start terminal input (sets up handlers)
284
295
  this.terminalInput.start();
285
296
  // Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
@@ -287,7 +298,6 @@ export class InteractiveShell {
287
298
  // Set up command autocomplete with all slash commands
288
299
  this.setupCommandAutocomplete();
289
300
  // Render chat box immediately using the streaming UI lifecycle
290
- this.refreshControlBar();
291
301
  this.syncRendererInput();
292
302
  this.renderer?.render();
293
303
  this.rebuildAgent();
@@ -394,6 +404,38 @@ export class InteractiveShell {
394
404
  const intro = `${theme.ui.muted('To get started, describe a task or try one of these commands:')}`;
395
405
  return `${intro}\n\n${body}`;
396
406
  }
407
+ describeSessionLabel() {
408
+ if (this.sessionRestoreConfig.mode === 'autosave')
409
+ return 'resume (autosave)';
410
+ if (this.sessionRestoreConfig.mode === 'session-id')
411
+ return 'resume (id)';
412
+ return 'fresh';
413
+ }
414
+ describeEditGuardMode() {
415
+ switch (this.editGuardMode) {
416
+ case 'display-edits':
417
+ return 'show diffs';
418
+ case 'require-approval':
419
+ return 'ask before write';
420
+ case 'block-writes':
421
+ return 'no writes';
422
+ case 'ask-permission':
423
+ return 'permission gate';
424
+ case 'plan':
425
+ return 'plan then apply';
426
+ default:
427
+ return this.editGuardMode;
428
+ }
429
+ }
430
+ abbreviatePath(pathValue) {
431
+ if (!pathValue)
432
+ return '—';
433
+ const home = homedir();
434
+ if (home && pathValue.startsWith(home)) {
435
+ return pathValue.replace(home, '~');
436
+ }
437
+ return pathValue;
438
+ }
397
439
  async checkAndShowUpdates() {
398
440
  try {
399
441
  const { checkForUpdates, formatUpdateNotification } = await import('../core/updateChecker.js');
@@ -416,13 +458,7 @@ export class InteractiveShell {
416
458
  const workspace = this.workingDir;
417
459
  const profile = this.profileLabel;
418
460
  const thinkingLabel = (this.thinkingMode || 'off').toString();
419
- const sessionLabel = (() => {
420
- if (this.sessionRestoreConfig.mode === 'autosave')
421
- return 'resume (autosave)';
422
- if (this.sessionRestoreConfig.mode === 'session-id')
423
- return 'resume (id)';
424
- return 'fresh';
425
- })();
461
+ const sessionLabel = this.describeSessionLabel();
426
462
  const terminalWidth = typeof process.stdout.columns === 'number' && process.stdout.columns > 0
427
463
  ? process.stdout.columns
428
464
  : 80;
@@ -466,25 +502,11 @@ export class InteractiveShell {
466
502
  const rightColored = `${theme.primary(rightLabel)}: ${theme.ui.muted(rightDisplay)}`;
467
503
  return padLine(`${leftColored}${gap}${rightColored}`);
468
504
  };
469
- const editGuardLabel = (() => {
470
- switch (this.editGuardMode) {
471
- case 'display-edits':
472
- return 'show diffs';
473
- case 'require-approval':
474
- return 'ask before write';
475
- case 'block-writes':
476
- return 'no writes';
477
- case 'ask-permission':
478
- return 'permission gate';
479
- case 'plan':
480
- return 'plan then apply';
481
- default:
482
- return this.editGuardMode;
483
- }
484
- })();
505
+ const editGuardLabel = this.describeEditGuardMode();
485
506
  const statusParts = [
486
507
  theme.success('Ready'),
487
508
  `${theme.primary('Auto')}: ${this.autoContinueEnabled ? theme.success('on') : theme.ui.muted('off')}`,
509
+ `${theme.primary('Approvals')}: ${this.criticalApprovalMode === 'auto' ? theme.ui.muted('auto') : theme.warning('ask')}`,
488
510
  `${theme.primary('Verify')}: ${this.verificationEnabled ? theme.success('on') : theme.ui.muted('off')}`,
489
511
  `${theme.primary('Thinking')}: ${theme.info(thinkingLabel)}`,
490
512
  `${theme.primary('Autosave')}: ${this.autosaveEnabled ? theme.success('on') : theme.ui.muted('off')}`,
@@ -644,6 +666,7 @@ export class InteractiveShell {
644
666
  '/bug',
645
667
  '/changes', '/summary',
646
668
  '/export',
669
+ '/approvals',
647
670
  // Configuration menus
648
671
  '/model', '/models',
649
672
  '/secrets',
@@ -716,7 +739,7 @@ export class InteractiveShell {
716
739
  display.showSystemMessage('✏️ Display edits mode enabled.');
717
740
  }
718
741
  }
719
- this.syncRendererInput();
742
+ this.refreshControlBar();
720
743
  }
721
744
  toggleVerificationMode() {
722
745
  this.setVerificationMode(!this.verificationEnabled, 'shortcut');
@@ -733,6 +756,23 @@ export class InteractiveShell {
733
756
  : '⏭️ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle; defaults to off unless enabled via /features)';
734
757
  display.showSystemMessage(message);
735
758
  }
759
+ toggleCriticalApprovalMode(source) {
760
+ const nextMode = this.criticalApprovalMode === 'auto' ? 'approval' : 'auto';
761
+ this.setCriticalApprovalMode(nextMode, source);
762
+ }
763
+ setCriticalApprovalMode(mode, source) {
764
+ const changed = this.criticalApprovalMode !== mode;
765
+ this.criticalApprovalMode = mode;
766
+ saveSessionPreferences({ criticalApprovalMode: mode });
767
+ this.refreshControlBar();
768
+ if (!changed && source === 'shortcut') {
769
+ return;
770
+ }
771
+ const message = mode === 'auto'
772
+ ? '⚡ High-impact actions will run automatically. (Ctrl+Shift+A to toggle; use /approvals auto to persist)'
773
+ : '🛡️ High-impact actions now require your approval. (Ctrl+Shift+A to toggle; use /approvals ask to persist)';
774
+ display.showSystemMessage(message);
775
+ }
736
776
  toggleAutoContinueMode() {
737
777
  this.setAutoContinueMode(!this.autoContinueEnabled, 'shortcut');
738
778
  }
@@ -1208,6 +1248,78 @@ export class InteractiveShell {
1208
1248
  display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
1209
1249
  this.syncRendererInput();
1210
1250
  }
1251
+ async runWithCriticalApproval(label, detail, action) {
1252
+ if (this.criticalApprovalMode === 'auto') {
1253
+ await action();
1254
+ return;
1255
+ }
1256
+ if (this.pendingInteraction && this.pendingInteraction.type === 'critical-approval') {
1257
+ display.showWarning('Finish the pending approval first.');
1258
+ return;
1259
+ }
1260
+ await new Promise((resolve) => {
1261
+ this.pendingInteraction = {
1262
+ type: 'critical-approval',
1263
+ label,
1264
+ detail,
1265
+ onApprove: async () => {
1266
+ this.pendingInteraction = null;
1267
+ try {
1268
+ await action();
1269
+ }
1270
+ catch (error) {
1271
+ display.showError(error instanceof Error ? error.message : String(error), error);
1272
+ }
1273
+ resolve();
1274
+ },
1275
+ onCancel: () => {
1276
+ this.pendingInteraction = null;
1277
+ display.showInfo('Action cancelled.');
1278
+ resolve();
1279
+ },
1280
+ };
1281
+ this.showCriticalApprovalPrompt(label, detail);
1282
+ });
1283
+ }
1284
+ showCriticalApprovalPrompt(label, detail) {
1285
+ const lines = [];
1286
+ lines.push(theme.gradient.primary('⚠️ Approval required'));
1287
+ lines.push('');
1288
+ lines.push(theme.bold(label));
1289
+ if (detail) {
1290
+ lines.push(theme.ui.muted(detail));
1291
+ }
1292
+ lines.push('');
1293
+ lines.push('Type "yes" to proceed or "no" to cancel.');
1294
+ display.showSystemMessage(lines.join('\n'));
1295
+ this.syncRendererInput();
1296
+ }
1297
+ async handleCriticalApprovalInput(input) {
1298
+ const pending = this.pendingInteraction;
1299
+ if (!pending || pending.type !== 'critical-approval') {
1300
+ return;
1301
+ }
1302
+ const normalized = input.trim().toLowerCase();
1303
+ if (!normalized) {
1304
+ display.showWarning('Enter "yes" to proceed or "no" to cancel.');
1305
+ this.syncRendererInput();
1306
+ return;
1307
+ }
1308
+ if (normalized === 'yes' || normalized === 'y') {
1309
+ this.pendingInteraction = null;
1310
+ await pending.onApprove();
1311
+ this.syncRendererInput();
1312
+ return;
1313
+ }
1314
+ if (normalized === 'no' || normalized === 'n' || normalized === 'cancel' || normalized === 'c') {
1315
+ this.pendingInteraction = null;
1316
+ pending.onCancel?.();
1317
+ this.syncRendererInput();
1318
+ return;
1319
+ }
1320
+ display.showWarning('Please respond with "yes" to proceed or "no" to cancel.');
1321
+ this.syncRendererInput();
1322
+ }
1211
1323
  setupHandlers() {
1212
1324
  // Handle terminal resize
1213
1325
  output.on('resize', () => {
@@ -1323,8 +1435,21 @@ export class InteractiveShell {
1323
1435
  autoContinueEnabled: this.autoContinueEnabled,
1324
1436
  verificationHotkey: 'ctrl+shift+v',
1325
1437
  autoContinueHotkey: 'ctrl+shift+c',
1326
- thinkingModeLabel: this.thinkingMode === 'extended' ? 'on' : 'off',
1438
+ thinkingModeLabel: (this.thinkingMode || 'off').toString(),
1327
1439
  thinkingHotkey: 'tab',
1440
+ criticalApprovalMode: this.criticalApprovalMode,
1441
+ criticalApprovalHotkey: 'ctrl+shift+a',
1442
+ });
1443
+ const workspaceDisplay = this.abbreviatePath(this.workingDir);
1444
+ this.terminalInput.setChromeMeta({
1445
+ profile: this.profileLabel,
1446
+ workspace: workspaceDisplay,
1447
+ directory: workspaceDisplay,
1448
+ writes: this.describeEditGuardMode(),
1449
+ sessionLabel: this.describeSessionLabel(),
1450
+ thinkingLabel: (this.thinkingMode || 'off').toString(),
1451
+ autosave: this.autosaveEnabled,
1452
+ version: this.version,
1328
1453
  });
1329
1454
  this.refreshStatusLine();
1330
1455
  this.syncRendererInput();
@@ -1399,15 +1524,6 @@ export class InteractiveShell {
1399
1524
  * All three can be shown simultaneously (Erosolar-CLI style).
1400
1525
  */
1401
1526
  refreshStatusLine(forceRender = false) {
1402
- const statusText = this.formatStatusLine(this.statusLineState);
1403
- // Compose streaming/override/base status into a single line so the prompt
1404
- // looks the same before and during streaming.
1405
- this.terminalInput.setStatusLine({
1406
- streaming: this.streamingStatusLabel,
1407
- override: this.statusMessageOverride,
1408
- main: statusText,
1409
- });
1410
- // Surface meta header (elapsed + context usage) above the divider
1411
1527
  const elapsedSeconds = this.getAiRuntimeSeconds();
1412
1528
  const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
1413
1529
  const tokensUsed = this.latestTokenUsage.used;
@@ -1840,6 +1956,9 @@ export class InteractiveShell {
1840
1956
  case 'plan-approval':
1841
1957
  await this.handlePlanApprovalInput(input);
1842
1958
  return true;
1959
+ case 'critical-approval':
1960
+ await this.handleCriticalApprovalInput(input);
1961
+ return true;
1843
1962
  default:
1844
1963
  return false;
1845
1964
  }
@@ -1859,6 +1978,9 @@ export class InteractiveShell {
1859
1978
  case '/features':
1860
1979
  this.showFeaturesMenu(input);
1861
1980
  break;
1981
+ case '/approvals':
1982
+ this.handleApprovalsCommand(input);
1983
+ break;
1862
1984
  case '/learn':
1863
1985
  this.showLearningStatus(input);
1864
1986
  break;
@@ -2042,6 +2164,7 @@ export class InteractiveShell {
2042
2164
  ` ${theme.info('Shift+Tab')} ${theme.ui.muted('Toggle edit mode (auto/ask)')}`,
2043
2165
  ` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
2044
2166
  ` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
2167
+ ` ${theme.info('Ctrl+Shift+A')} ${theme.ui.muted('Toggle approvals for high-impact actions')}`,
2045
2168
  ` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
2046
2169
  ` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
2047
2170
  ` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
@@ -2606,40 +2729,43 @@ export class InteractiveShell {
2606
2729
  return;
2607
2730
  }
2608
2731
  if (subcommand === 'apply') {
2609
- display.showSystemMessage(theme.gradient.primary('🚀 Running Self-Improvement Cycle...'));
2610
- display.showSystemMessage('');
2611
- try {
2612
- const result = await runSelfImprovementCycle(this.workingDir, {
2613
- maxChanges: 3,
2614
- minConfidence: 0.7,
2615
- runTests: true,
2616
- autoCommit: true,
2617
- });
2618
- const lines = [];
2619
- lines.push(theme.bold('Results:'));
2620
- lines.push(result.summary);
2621
- lines.push('');
2622
- if (result.applied > 0) {
2623
- lines.push(theme.success(`✅ Applied ${result.applied} improvements!`));
2624
- for (const r of result.results.filter(r => r.success)) {
2625
- lines.push(` - Files: ${r.filesChanged.join(', ')}`);
2626
- if (r.commitHash) {
2627
- lines.push(` Commit: ${theme.ui.muted(r.commitHash)}`);
2628
- lines.push(` Rollback: ${theme.dim(r.rollbackCommand ?? '')}`);
2732
+ const runApply = async () => {
2733
+ display.showSystemMessage(theme.gradient.primary('🚀 Running Self-Improvement Cycle...'));
2734
+ display.showSystemMessage('');
2735
+ try {
2736
+ const result = await runSelfImprovementCycle(this.workingDir, {
2737
+ maxChanges: 3,
2738
+ minConfidence: 0.7,
2739
+ runTests: true,
2740
+ autoCommit: true,
2741
+ });
2742
+ const lines = [];
2743
+ lines.push(theme.bold('Results:'));
2744
+ lines.push(result.summary);
2745
+ lines.push('');
2746
+ if (result.applied > 0) {
2747
+ lines.push(theme.success(`✅ Applied ${result.applied} improvements!`));
2748
+ for (const r of result.results.filter(r => r.success)) {
2749
+ lines.push(` - Files: ${r.filesChanged.join(', ')}`);
2750
+ if (r.commitHash) {
2751
+ lines.push(` Commit: ${theme.ui.muted(r.commitHash)}`);
2752
+ lines.push(` Rollback: ${theme.dim(r.rollbackCommand ?? '')}`);
2753
+ }
2629
2754
  }
2630
2755
  }
2631
- }
2632
- else {
2633
- lines.push(theme.warning('No improvements were applied.'));
2634
- for (const r of result.results.filter(r => !r.success)) {
2635
- lines.push(` ${theme.error('✗')} ${r.error}`);
2756
+ else {
2757
+ lines.push(theme.warning('No improvements were applied.'));
2758
+ for (const r of result.results.filter(r => !r.success)) {
2759
+ lines.push(` ${theme.error('✗')} ${r.error}`);
2760
+ }
2636
2761
  }
2762
+ display.showSystemMessage(lines.join('\n'));
2637
2763
  }
2638
- display.showSystemMessage(lines.join('\n'));
2639
- }
2640
- catch (error) {
2641
- display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2642
- }
2764
+ catch (error) {
2765
+ display.showError(`Self-improvement failed: ${error instanceof Error ? error.message : String(error)}`);
2766
+ }
2767
+ };
2768
+ await this.runWithCriticalApproval('Apply self-improvement changes', 'Runs automated fixes with tests and commits before continuing.', runApply);
2643
2769
  return;
2644
2770
  }
2645
2771
  if (subcommand === 'dry-run') {
@@ -2668,7 +2794,7 @@ export class InteractiveShell {
2668
2794
  return;
2669
2795
  }
2670
2796
  if (subcommand === 'auto') {
2671
- void this.runAutonomousImprovementMode();
2797
+ await this.runWithCriticalApproval('Start autonomous improvement mode', 'Allows the CLI to apply and validate changes until stopped.', () => this.runAutonomousImprovementMode());
2672
2798
  return;
2673
2799
  }
2674
2800
  if (subcommand === 'stop') {
@@ -2677,13 +2803,15 @@ export class InteractiveShell {
2677
2803
  return;
2678
2804
  }
2679
2805
  if (subcommand === 'rollback') {
2680
- const result = emergencyRollback(this.workingDir);
2681
- if (result.success) {
2682
- display.showSuccess(result.message);
2683
- }
2684
- else {
2685
- display.showError(result.message);
2686
- }
2806
+ await this.runWithCriticalApproval('Emergency rollback of self-improvement', 'Restores the last checkpoint created by the improvement engine.', () => {
2807
+ const result = emergencyRollback(this.workingDir);
2808
+ if (result.success) {
2809
+ display.showSuccess(result.message);
2810
+ }
2811
+ else {
2812
+ display.showError(result.message);
2813
+ }
2814
+ });
2687
2815
  return;
2688
2816
  }
2689
2817
  if (subcommand === 'status') {
@@ -2886,64 +3014,67 @@ export class InteractiveShell {
2886
3014
  return;
2887
3015
  }
2888
3016
  if (subcommand === 'start') {
2889
- display.showSystemMessage(theme.gradient.primary('🧬 Starting Self-Evolution Mode'));
2890
- display.showSystemMessage('');
2891
- display.showSystemMessage(theme.bold('Safety Features:'));
2892
- display.showSystemMessage(' Git checkpoint created before starting');
2893
- display.showSystemMessage(' • Each change validated with build + tests');
2894
- display.showSystemMessage(' • Auto-rollback on failures');
2895
- display.showSystemMessage(' • Auto-relaunch with improved code');
2896
- display.showSystemMessage(' • Press Ctrl+C to stop gracefully');
2897
- display.showSystemMessage('');
2898
- try {
2899
- const result = await runSelfEvolution(this.workingDir, {
2900
- maxIterations: 50,
2901
- minConfidence: 0.8,
2902
- runTests: true,
2903
- autoRelaunch: true,
2904
- }, {
2905
- onStart: () => {
2906
- display.showInfo('Evolution started. Creating checkpoint...');
2907
- },
2908
- onIteration: (iteration, issues) => {
2909
- display.showSystemMessage(`\n[Iteration ${iteration}] Found ${issues.length} high-confidence issues`);
2910
- },
2911
- onFix: (issue, success) => {
2912
- if (success) {
2913
- display.showSuccess(`Fixed: ${issue.description.slice(0, 50)}`);
2914
- }
2915
- else {
2916
- display.showWarning(`Failed: ${issue.description.slice(0, 50)}`);
2917
- }
2918
- },
2919
- onRelaunch: () => {
2920
- display.showSystemMessage('');
2921
- display.showSystemMessage(theme.gradient.primary('🔄 Relaunching with improved code...'));
2922
- },
2923
- onComplete: (result) => {
2924
- display.showSystemMessage('');
2925
- display.showSuccess(`Evolution complete! Fixed ${result.issuesFixed} issues.`);
2926
- },
2927
- onError: (error) => {
2928
- display.showError(`Evolution error: ${error}`);
2929
- },
2930
- });
2931
- const lines = [];
2932
- lines.push('');
2933
- lines.push(theme.bold('Evolution Result:'));
2934
- lines.push(` Success: ${result.success ? theme.success('Yes') : theme.error('No')}`);
2935
- lines.push(` Iterations: ${result.iteration}`);
2936
- lines.push(` Issues Found: ${result.issuesFound}`);
2937
- lines.push(` Issues Fixed: ${result.issuesFixed}`);
2938
- if (result.error) {
2939
- lines.push(` Error: ${theme.error(result.error)}`);
3017
+ const startEvolution = async () => {
3018
+ display.showSystemMessage(theme.gradient.primary('🧬 Starting Self-Evolution Mode'));
3019
+ display.showSystemMessage('');
3020
+ display.showSystemMessage(theme.bold('Safety Features:'));
3021
+ display.showSystemMessage(' • Git checkpoint created before starting');
3022
+ display.showSystemMessage(' • Each change validated with build + tests');
3023
+ display.showSystemMessage(' • Auto-rollback on failures');
3024
+ display.showSystemMessage(' • Auto-relaunch with improved code');
3025
+ display.showSystemMessage(' • Press Ctrl+C to stop gracefully');
3026
+ display.showSystemMessage('');
3027
+ try {
3028
+ const result = await runSelfEvolution(this.workingDir, {
3029
+ maxIterations: 50,
3030
+ minConfidence: 0.8,
3031
+ runTests: true,
3032
+ autoRelaunch: true,
3033
+ }, {
3034
+ onStart: () => {
3035
+ display.showInfo('Evolution started. Creating checkpoint...');
3036
+ },
3037
+ onIteration: (iteration, issues) => {
3038
+ display.showSystemMessage(`\n[Iteration ${iteration}] Found ${issues.length} high-confidence issues`);
3039
+ },
3040
+ onFix: (issue, success) => {
3041
+ if (success) {
3042
+ display.showSuccess(`Fixed: ${issue.description.slice(0, 50)}`);
3043
+ }
3044
+ else {
3045
+ display.showWarning(`Failed: ${issue.description.slice(0, 50)}`);
3046
+ }
3047
+ },
3048
+ onRelaunch: () => {
3049
+ display.showSystemMessage('');
3050
+ display.showSystemMessage(theme.gradient.primary('🔄 Relaunching with improved code...'));
3051
+ },
3052
+ onComplete: (result) => {
3053
+ display.showSystemMessage('');
3054
+ display.showSuccess(`Evolution complete! Fixed ${result.issuesFixed} issues.`);
3055
+ },
3056
+ onError: (error) => {
3057
+ display.showError(`Evolution error: ${error}`);
3058
+ },
3059
+ });
3060
+ const lines = [];
3061
+ lines.push('');
3062
+ lines.push(theme.bold('Evolution Result:'));
3063
+ lines.push(` Success: ${result.success ? theme.success('Yes') : theme.error('No')}`);
3064
+ lines.push(` Iterations: ${result.iteration}`);
3065
+ lines.push(` Issues Found: ${result.issuesFound}`);
3066
+ lines.push(` Issues Fixed: ${result.issuesFixed}`);
3067
+ if (result.error) {
3068
+ lines.push(` Error: ${theme.error(result.error)}`);
3069
+ }
3070
+ lines.push(` Next Action: ${result.nextAction}`);
3071
+ display.showSystemMessage(lines.join('\n'));
2940
3072
  }
2941
- lines.push(` Next Action: ${result.nextAction}`);
2942
- display.showSystemMessage(lines.join('\n'));
2943
- }
2944
- catch (error) {
2945
- display.showError(`Evolution failed: ${error instanceof Error ? error.message : String(error)}`);
2946
- }
3073
+ catch (error) {
3074
+ display.showError(`Evolution failed: ${error instanceof Error ? error.message : String(error)}`);
3075
+ }
3076
+ };
3077
+ await this.runWithCriticalApproval('Start self-evolution mode', 'Automates repo changes with checkpoints, tests, and relaunch.', startEvolution);
2947
3078
  return;
2948
3079
  }
2949
3080
  if (subcommand === 'stop') {
@@ -3210,42 +3341,48 @@ export class InteractiveShell {
3210
3341
  ' /offsec runs';
3211
3342
  if (sub === 'start') {
3212
3343
  const rest = args.slice(1);
3213
- const scope = [];
3214
- const objectiveParts = [];
3215
- for (let i = 0; i < rest.length; i++) {
3216
- if (rest[i]?.toLowerCase() === '--scope') {
3217
- const scopeArg = rest[i + 1];
3218
- if (scopeArg) {
3219
- scope.push(...scopeArg.split(',').map((s) => s.trim()).filter(Boolean));
3344
+ const startRun = async () => {
3345
+ const scope = [];
3346
+ const objectiveParts = [];
3347
+ for (let i = 0; i < rest.length; i++) {
3348
+ if (rest[i]?.toLowerCase() === '--scope') {
3349
+ const scopeArg = rest[i + 1];
3350
+ if (scopeArg) {
3351
+ scope.push(...scopeArg.split(',').map((s) => s.trim()).filter(Boolean));
3352
+ }
3353
+ i += 1;
3354
+ continue;
3220
3355
  }
3221
- i += 1;
3222
- continue;
3356
+ objectiveParts.push(rest[i]);
3223
3357
  }
3224
- objectiveParts.push(rest[i]);
3225
- }
3226
- const objective = objectiveParts.join(' ').trim();
3227
- if (!objective) {
3228
- display.showWarning('Provide an objective. Example: /offsec start gain shell on api.example.com --scope api.example.com');
3229
- display.showInfo(usage);
3230
- return;
3231
- }
3232
- const run = startOffsecRun(objective, scope);
3233
- this.offsecRunId = run.id;
3234
- const next = getOffsecNextActions(run.id, 3);
3235
- display.showSystemMessage(theme.gradient.primary('🛡️ Offsec AlphaZero run started'));
3236
- display.showSystemMessage(formatOffsecStatus(run, next));
3358
+ const objective = objectiveParts.join(' ').trim();
3359
+ if (!objective) {
3360
+ display.showWarning('Provide an objective. Example: /offsec start gain shell on api.example.com --scope api.example.com');
3361
+ display.showInfo(usage);
3362
+ return;
3363
+ }
3364
+ const run = startOffsecRun(objective, scope);
3365
+ this.offsecRunId = run.id;
3366
+ const next = getOffsecNextActions(run.id, 3);
3367
+ display.showSystemMessage(theme.gradient.primary('🛡️ Offsec AlphaZero run started'));
3368
+ display.showSystemMessage(formatOffsecStatus(run, next));
3369
+ };
3370
+ await this.runWithCriticalApproval('Start offensive security run', 'Begins an automated security workflow and logs results to ~/.erosolar/offsec.', startRun);
3237
3371
  return;
3238
3372
  }
3239
3373
  if (sub === 'resume') {
3240
- const targetRun = args[1] ?? this.offsecRunId;
3241
- const run = resumeOffsecRun(targetRun);
3242
- if (!run) {
3243
- display.showWarning(`No offsec run found for id ${targetRun ?? '<unset>'}`);
3244
- return;
3245
- }
3246
- this.offsecRunId = run.id;
3247
- display.showSystemMessage(theme.gradient.primary(`Resumed offsec run ${run.id}`));
3248
- display.showSystemMessage(formatOffsecStatus(run, getOffsecNextActions(run.id, 3)));
3374
+ const resumeRun = async () => {
3375
+ const targetRun = args[1] ?? this.offsecRunId;
3376
+ const run = resumeOffsecRun(targetRun);
3377
+ if (!run) {
3378
+ display.showWarning(`No offsec run found for id ${targetRun ?? '<unset>'}`);
3379
+ return;
3380
+ }
3381
+ this.offsecRunId = run.id;
3382
+ display.showSystemMessage(theme.gradient.primary(`Resumed offsec run ${run.id}`));
3383
+ display.showSystemMessage(formatOffsecStatus(run, getOffsecNextActions(run.id, 3)));
3384
+ };
3385
+ await this.runWithCriticalApproval('Resume offensive security run', 'Continues a previously started offensive security workflow.', resumeRun);
3249
3386
  return;
3250
3387
  }
3251
3388
  if (sub === 'runs') {
@@ -3636,6 +3773,7 @@ export class InteractiveShell {
3636
3773
  else {
3637
3774
  this.autosaveIfEnabled();
3638
3775
  }
3776
+ this.refreshControlBar();
3639
3777
  }
3640
3778
  clearAutosaveCommand() {
3641
3779
  clearAutosaveSnapshot(this.profile);
@@ -3899,6 +4037,35 @@ export class InteractiveShell {
3899
4037
  lines.push(theme.ui.muted('Shift+Enter enables multi-line input.'));
3900
4038
  display.showSystemMessage(lines.join('\n'));
3901
4039
  }
4040
+ handleApprovalsCommand(input) {
4041
+ const mode = input.split(/\s+/)[1]?.toLowerCase();
4042
+ const showStatus = () => {
4043
+ const lines = [];
4044
+ lines.push(theme.bold('High-Impact Action Approvals'));
4045
+ lines.push('');
4046
+ lines.push(`Current mode: ${this.criticalApprovalMode === 'auto' ? theme.ui.muted('auto (no prompt)') : theme.warning('ask before high-impact actions')}`);
4047
+ lines.push('');
4048
+ lines.push(theme.secondary('Switch mode:'));
4049
+ lines.push(' /approvals auto - run critical flows without prompting');
4050
+ lines.push(' /approvals ask - require approval for major actions');
4051
+ lines.push('');
4052
+ lines.push(theme.ui.muted('Keyboard: Ctrl+Shift+A toggles modes.'));
4053
+ display.showSystemMessage(lines.join('\n'));
4054
+ };
4055
+ if (!mode || mode === 'status') {
4056
+ showStatus();
4057
+ return;
4058
+ }
4059
+ if (['auto', 'default', 'off'].includes(mode)) {
4060
+ this.setCriticalApprovalMode('auto', 'command');
4061
+ return;
4062
+ }
4063
+ if (['ask', 'approval', 'require', 'confirm', 'on'].includes(mode)) {
4064
+ this.setCriticalApprovalMode('approval', 'command');
4065
+ return;
4066
+ }
4067
+ display.showWarning('Usage: /approvals [auto|ask|status]');
4068
+ }
3902
4069
  handlePermissionsCommand() {
3903
4070
  const lines = [];
3904
4071
  lines.push(theme.bold('Tool Permissions'));
@@ -3913,6 +4080,9 @@ export class InteractiveShell {
3913
4080
  lines.push(' Shift+Tab Cycle through modes');
3914
4081
  lines.push(' Option+E Toggle edit permission');
3915
4082
  lines.push('');
4083
+ lines.push(theme.secondary('High-impact approvals:'));
4084
+ lines.push(' /approvals ask|auto (Ctrl+Shift+A to toggle)');
4085
+ lines.push('');
3916
4086
  lines.push(theme.ui.muted('Use /tools to manage which tool suites are enabled.'));
3917
4087
  display.showSystemMessage(lines.join('\n'));
3918
4088
  }