erosolar-cli 1.7.383 → 1.7.385
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/dist/core/contextManager.d.ts +4 -0
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +16 -0
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +9 -0
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +12 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +213 -53
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +20 -0
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +110 -6
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +13 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +40 -3
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +12 -2
- package/dist/ui/display.js.map +1 -1
- package/package.json +1 -1
|
@@ -70,6 +70,9 @@ const PROVIDER_LABELS = Object.fromEntries(getProviders().map((provider) => [pro
|
|
|
70
70
|
// Allow enough time for paste detection to kick in before flushing buffered lines
|
|
71
71
|
const CONTEXT_USAGE_THRESHOLD = 0.9;
|
|
72
72
|
const CONTEXT_AUTOCOMPACT_PERCENT = Math.round(CONTEXT_USAGE_THRESHOLD * 100);
|
|
73
|
+
const CONTEXT_AUTOCOMPACT_FLOOR = 0.6;
|
|
74
|
+
const MIN_COMPACTION_TOKEN_SAVINGS = 200;
|
|
75
|
+
const MIN_COMPACTION_PERCENT_SAVINGS = 0.5;
|
|
73
76
|
const CONTEXT_RECENT_MESSAGE_COUNT = 12;
|
|
74
77
|
const CONTEXT_CLEANUP_CHARS_PER_CHUNK = 6000;
|
|
75
78
|
const CONTEXT_CLEANUP_MAX_OUTPUT_TOKENS = 800;
|
|
@@ -116,6 +119,9 @@ export class InteractiveShell {
|
|
|
116
119
|
isDrainingQueue = false;
|
|
117
120
|
activeContextWindowTokens = null;
|
|
118
121
|
latestTokenUsage = { used: null, limit: null };
|
|
122
|
+
planApprovalBridgeRegistered = false;
|
|
123
|
+
contextCompactionInFlight = false;
|
|
124
|
+
lastContextWarningLevel = null;
|
|
119
125
|
sessionPreferences;
|
|
120
126
|
autosaveEnabled;
|
|
121
127
|
autoContinueEnabled;
|
|
@@ -255,6 +261,11 @@ export class InteractiveShell {
|
|
|
255
261
|
this.refreshContextGauge();
|
|
256
262
|
// Start terminal input (sets up handlers)
|
|
257
263
|
this.terminalInput.start();
|
|
264
|
+
// Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
|
|
265
|
+
this.registerPlanApprovalBridge();
|
|
266
|
+
// Capture display output into the scrollback/chat log so system messages
|
|
267
|
+
// (e.g., /evolve) render above the pinned chat box.
|
|
268
|
+
this.terminalInput.registerOutputInterceptor(display);
|
|
258
269
|
// Set up command autocomplete with all slash commands
|
|
259
270
|
this.setupCommandAutocomplete();
|
|
260
271
|
// Enter alternate screen buffer when enabled, otherwise clear the main screen for layout
|
|
@@ -505,15 +516,9 @@ export class InteractiveShell {
|
|
|
505
516
|
this.editGuardMode = mode;
|
|
506
517
|
this.pendingPermissionInput = null;
|
|
507
518
|
if (mode === 'plan') {
|
|
508
|
-
// Register plan approval callback for interactive UI
|
|
509
|
-
setPlanApprovalCallback((steps, explanation) => {
|
|
510
|
-
this.showPlanApproval(steps, explanation);
|
|
511
|
-
});
|
|
512
519
|
display.showSystemMessage('📋 Plan mode enabled. AI will create a plan and ask for approval before implementing.');
|
|
513
520
|
}
|
|
514
521
|
else {
|
|
515
|
-
// Unregister callback when not in plan mode
|
|
516
|
-
setPlanApprovalCallback(null);
|
|
517
522
|
if (mode === 'ask-permission') {
|
|
518
523
|
display.showSystemMessage('🛡️ Ask-to-edit mode enabled. Confirm each request before sending.');
|
|
519
524
|
}
|
|
@@ -729,6 +734,8 @@ export class InteractiveShell {
|
|
|
729
734
|
if (this.alternateScreenEnabled) {
|
|
730
735
|
this.terminalInput.exitAlternateScreen();
|
|
731
736
|
}
|
|
737
|
+
// Unregister plan approval bridge
|
|
738
|
+
setPlanApprovalCallback(null);
|
|
732
739
|
// Dispose terminal input handler
|
|
733
740
|
this.terminalInput.dispose();
|
|
734
741
|
// Dispose unified UI adapter
|
|
@@ -752,6 +759,18 @@ export class InteractiveShell {
|
|
|
752
759
|
// Write directly to stdout after exiting alternate screen to preserve the transcript
|
|
753
760
|
process.stdout.write(`\n${separator}\n${header}\n${transcript}\n${separator}\n`);
|
|
754
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Wire the planning tool suite to the interactive plan approval UI so ProposePlan behaves like Codex CLI.
|
|
764
|
+
*/
|
|
765
|
+
registerPlanApprovalBridge() {
|
|
766
|
+
if (this.planApprovalBridgeRegistered) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
setPlanApprovalCallback((steps, explanation) => {
|
|
770
|
+
this.showPlanApproval(steps, explanation);
|
|
771
|
+
});
|
|
772
|
+
this.planApprovalBridgeRegistered = true;
|
|
773
|
+
}
|
|
755
774
|
/**
|
|
756
775
|
* Update status bar message
|
|
757
776
|
*/
|
|
@@ -1105,9 +1124,9 @@ export class InteractiveShell {
|
|
|
1105
1124
|
};
|
|
1106
1125
|
}
|
|
1107
1126
|
}
|
|
1108
|
-
updateContextUsage(percentage) {
|
|
1127
|
+
updateContextUsage(percentage, autoCompactThreshold = CONTEXT_AUTOCOMPACT_PERCENT) {
|
|
1109
1128
|
this.uiAdapter.updateContextUsage(percentage);
|
|
1110
|
-
this.terminalInput.setContextUsage(percentage,
|
|
1129
|
+
this.terminalInput.setContextUsage(percentage, autoCompactThreshold);
|
|
1111
1130
|
}
|
|
1112
1131
|
refreshControlBar() {
|
|
1113
1132
|
this.terminalInput.setModeToggles({
|
|
@@ -1190,10 +1209,6 @@ export class InteractiveShell {
|
|
|
1190
1209
|
if (state.detail) {
|
|
1191
1210
|
parts.push(state.detail);
|
|
1192
1211
|
}
|
|
1193
|
-
const elapsedSeconds = Math.max(0, Math.floor((Date.now() - state.startedAt) / 1000));
|
|
1194
|
-
if (elapsedSeconds > 0) {
|
|
1195
|
-
parts.push(`${elapsedSeconds}s`);
|
|
1196
|
-
}
|
|
1197
1212
|
return parts.join(' • ');
|
|
1198
1213
|
}
|
|
1199
1214
|
handleSlashCommandPreviewChange() {
|
|
@@ -1324,20 +1339,11 @@ export class InteractiveShell {
|
|
|
1324
1339
|
// Force refresh to update the input area now that streaming has ended
|
|
1325
1340
|
this.refreshStatusLine(true);
|
|
1326
1341
|
}
|
|
1327
|
-
buildStreamingStatus(label,
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
? theme.ui.muted(this.formatElapsedShort(elapsedSeconds))
|
|
1331
|
-
: null;
|
|
1342
|
+
buildStreamingStatus(label, _elapsedSeconds) {
|
|
1343
|
+
// Model + elapsed time already live in the pinned meta header; keep the streaming
|
|
1344
|
+
// status focused on the activity to avoid duplicate info.
|
|
1332
1345
|
const prefix = theme.info('⏺');
|
|
1333
|
-
|
|
1334
|
-
if (detail) {
|
|
1335
|
-
parts.push(theme.ui.muted('·'), detail);
|
|
1336
|
-
}
|
|
1337
|
-
if (elapsedLabel) {
|
|
1338
|
-
parts.push(theme.ui.muted('·'), elapsedLabel);
|
|
1339
|
-
}
|
|
1340
|
-
return `${prefix} ${parts.join(' ')}`.trim();
|
|
1346
|
+
return `${prefix} ${label}`.trim();
|
|
1341
1347
|
}
|
|
1342
1348
|
formatElapsedShort(seconds) {
|
|
1343
1349
|
if (seconds < 60) {
|
|
@@ -5304,15 +5310,17 @@ What's the next action?`;
|
|
|
5304
5310
|
};
|
|
5305
5311
|
// Always update context usage in the UI
|
|
5306
5312
|
const percentUsed = Math.round(usageRatio * 100);
|
|
5307
|
-
this.updateContextUsage(percentUsed);
|
|
5313
|
+
this.updateContextUsage(percentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
5314
|
+
this.maybeShowContextWarning(percentUsed);
|
|
5308
5315
|
this.refreshStatusLine(true);
|
|
5309
|
-
if (
|
|
5316
|
+
if (!this.agent || this.cleanupInProgress || this.contextCompactionInFlight) {
|
|
5310
5317
|
return null;
|
|
5311
5318
|
}
|
|
5312
|
-
|
|
5319
|
+
const trigger = this.shouldAutoCompactContext(usageRatio, windowTokens, total);
|
|
5320
|
+
if (!trigger) {
|
|
5313
5321
|
return null;
|
|
5314
5322
|
}
|
|
5315
|
-
return this.runContextCleanup(windowTokens, total);
|
|
5323
|
+
return this.runContextCleanup(windowTokens, total, trigger);
|
|
5316
5324
|
}
|
|
5317
5325
|
totalTokens(usage) {
|
|
5318
5326
|
if (!usage) {
|
|
@@ -5326,51 +5334,203 @@ What's the next action?`;
|
|
|
5326
5334
|
const sum = input + output;
|
|
5327
5335
|
return sum > 0 ? sum : null;
|
|
5328
5336
|
}
|
|
5329
|
-
async runContextCleanup(windowTokens, totalTokens) {
|
|
5330
|
-
|
|
5337
|
+
async runContextCleanup(windowTokens, totalTokens, trigger) {
|
|
5338
|
+
const agent = this.agent;
|
|
5339
|
+
if (!agent) {
|
|
5340
|
+
return;
|
|
5341
|
+
}
|
|
5342
|
+
const contextManager = agent.getContextManager();
|
|
5343
|
+
if (!contextManager) {
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
const history = agent.getHistory();
|
|
5347
|
+
if (history.length <= 1) {
|
|
5331
5348
|
return;
|
|
5332
5349
|
}
|
|
5333
5350
|
this.cleanupInProgress = true;
|
|
5351
|
+
this.contextCompactionInFlight = true;
|
|
5334
5352
|
const cleanupStatusId = 'context-cleanup';
|
|
5335
5353
|
let cleanupOverlayActive = false;
|
|
5336
5354
|
try {
|
|
5337
|
-
const
|
|
5338
|
-
const
|
|
5339
|
-
if (
|
|
5340
|
-
|
|
5341
|
-
}
|
|
5342
|
-
const preserveCount = Math.min(conversation.length, CONTEXT_RECENT_MESSAGE_COUNT);
|
|
5343
|
-
const preserved = conversation.slice(conversation.length - preserveCount);
|
|
5344
|
-
const toSummarize = conversation.slice(0, conversation.length - preserveCount);
|
|
5345
|
-
if (!toSummarize.length) {
|
|
5346
|
-
return;
|
|
5355
|
+
const percentUsed = Math.round((totalTokens / windowTokens) * 100);
|
|
5356
|
+
const statusDetailParts = [`${percentUsed}% full`];
|
|
5357
|
+
if (trigger?.reason) {
|
|
5358
|
+
statusDetailParts.push(trigger.reason);
|
|
5347
5359
|
}
|
|
5348
|
-
cleanupOverlayActive = true;
|
|
5349
5360
|
this.statusTracker.pushOverride(cleanupStatusId, 'Running context cleanup', {
|
|
5350
|
-
detail:
|
|
5361
|
+
detail: statusDetailParts.join(' · '),
|
|
5351
5362
|
tone: 'warning',
|
|
5352
5363
|
});
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
this.updateContextUsage(percentUsed);
|
|
5364
|
+
cleanupOverlayActive = true;
|
|
5365
|
+
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
5356
5366
|
display.showSystemMessage([
|
|
5357
5367
|
`Context usage: ${totalTokens.toLocaleString('en-US')} of ${windowTokens.toLocaleString('en-US')} tokens`,
|
|
5358
|
-
`(${percentUsed}% full).
|
|
5368
|
+
`(${percentUsed}% full). Auto-compacting${triggerReason ? `: ${triggerReason}` : '...'}`,
|
|
5359
5369
|
].join(' '));
|
|
5360
|
-
const
|
|
5361
|
-
|
|
5362
|
-
|
|
5370
|
+
const beforeStats = contextManager.getStats(history);
|
|
5371
|
+
const result = await contextManager.intelligentCompact(history);
|
|
5372
|
+
const afterStats = contextManager.getStats(result.compacted);
|
|
5373
|
+
const tokenSavings = Math.max(0, beforeStats.totalTokens - afterStats.totalTokens);
|
|
5374
|
+
const percentSavings = beforeStats.totalTokens > 0 ? (tokenSavings / beforeStats.totalTokens) * 100 : 0;
|
|
5375
|
+
const changed = this.historiesDiffer(history, result.compacted);
|
|
5376
|
+
const meaningfulSavings = changed &&
|
|
5377
|
+
(tokenSavings >= MIN_COMPACTION_TOKEN_SAVINGS || percentSavings >= MIN_COMPACTION_PERCENT_SAVINGS);
|
|
5378
|
+
if (!meaningfulSavings) {
|
|
5379
|
+
if (trigger?.forced || percentUsed >= 85) {
|
|
5380
|
+
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
5381
|
+
}
|
|
5382
|
+
return;
|
|
5363
5383
|
}
|
|
5364
|
-
|
|
5365
|
-
this.
|
|
5366
|
-
|
|
5384
|
+
agent.loadHistory(result.compacted);
|
|
5385
|
+
this.cachedHistory = result.compacted;
|
|
5386
|
+
const newPercentUsed = windowTokens > 0
|
|
5387
|
+
? Math.round((afterStats.totalTokens / windowTokens) * 100)
|
|
5388
|
+
: afterStats.percentage;
|
|
5389
|
+
this.latestTokenUsage = {
|
|
5390
|
+
used: afterStats.totalTokens,
|
|
5391
|
+
limit: windowTokens,
|
|
5392
|
+
};
|
|
5393
|
+
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
5394
|
+
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
5395
|
+
this.refreshStatusLine(true);
|
|
5396
|
+
const primarySignal = result.analysis.signals[0]?.reason ?? triggerReason;
|
|
5397
|
+
display.showSystemMessage([
|
|
5398
|
+
`Context compacted: ${beforeStats.percentage}% → ${afterStats.percentage}%`,
|
|
5399
|
+
`(saved ~${tokenSavings.toLocaleString('en-US')} tokens).`,
|
|
5400
|
+
primarySignal ? `Reason: ${primarySignal}.` : '',
|
|
5401
|
+
].filter(Boolean).join(' '));
|
|
5402
|
+
}
|
|
5403
|
+
catch (error) {
|
|
5404
|
+
display.showError('Context compaction failed.', error);
|
|
5367
5405
|
}
|
|
5368
5406
|
finally {
|
|
5369
5407
|
if (cleanupOverlayActive) {
|
|
5370
5408
|
this.statusTracker.clearOverride(cleanupStatusId);
|
|
5371
5409
|
}
|
|
5372
5410
|
this.cleanupInProgress = false;
|
|
5411
|
+
this.contextCompactionInFlight = false;
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
shouldAutoCompactContext(usageRatio, windowTokens, totalTokens) {
|
|
5415
|
+
const featureFlags = loadFeatureFlags();
|
|
5416
|
+
if (featureFlags.autoCompact === false) {
|
|
5417
|
+
return null;
|
|
5418
|
+
}
|
|
5419
|
+
const agent = this.agent;
|
|
5420
|
+
if (!agent) {
|
|
5421
|
+
return null;
|
|
5422
|
+
}
|
|
5423
|
+
const contextManager = agent.getContextManager();
|
|
5424
|
+
if (!contextManager) {
|
|
5425
|
+
return null;
|
|
5426
|
+
}
|
|
5427
|
+
const history = agent.getHistory();
|
|
5428
|
+
if (history.length <= 1) {
|
|
5429
|
+
return null;
|
|
5373
5430
|
}
|
|
5431
|
+
const percentUsed = Math.round(usageRatio * 100);
|
|
5432
|
+
const analysis = contextManager.shouldTriggerCompaction(history);
|
|
5433
|
+
const overflowRisk = this.hasContextOverflowRisk(contextManager);
|
|
5434
|
+
if (usageRatio >= CONTEXT_USAGE_THRESHOLD) {
|
|
5435
|
+
return { reason: analysis.reason ?? `Context usage at ${percentUsed}%`, forced: true };
|
|
5436
|
+
}
|
|
5437
|
+
if (usageRatio >= CONTEXT_AUTOCOMPACT_FLOOR && (analysis.shouldCompact || overflowRisk)) {
|
|
5438
|
+
const reason = analysis.reason
|
|
5439
|
+
?? (overflowRisk ? 'Heavy tool output detected' : 'Compaction signals detected');
|
|
5440
|
+
return { reason, forced: false };
|
|
5441
|
+
}
|
|
5442
|
+
return null;
|
|
5443
|
+
}
|
|
5444
|
+
hasContextOverflowRisk(contextManager) {
|
|
5445
|
+
if (!contextManager?.detectContextOverflowRisk) {
|
|
5446
|
+
return false;
|
|
5447
|
+
}
|
|
5448
|
+
try {
|
|
5449
|
+
const toolHistory = this.runtimeSession.toolRuntime.getToolHistory?.();
|
|
5450
|
+
if (!toolHistory?.length) {
|
|
5451
|
+
return false;
|
|
5452
|
+
}
|
|
5453
|
+
const serialized = toolHistory.map((entry) => {
|
|
5454
|
+
const args = entry.args ?? {};
|
|
5455
|
+
const argsText = Object.keys(args).length ? ` ${JSON.stringify(args)}` : '';
|
|
5456
|
+
return `${entry.toolName}${argsText}`;
|
|
5457
|
+
});
|
|
5458
|
+
return contextManager.detectContextOverflowRisk(serialized);
|
|
5459
|
+
}
|
|
5460
|
+
catch {
|
|
5461
|
+
return false;
|
|
5462
|
+
}
|
|
5463
|
+
}
|
|
5464
|
+
getContextWarningLevel(percentage) {
|
|
5465
|
+
if (percentage >= 90) {
|
|
5466
|
+
return 'danger';
|
|
5467
|
+
}
|
|
5468
|
+
if (percentage >= 70) {
|
|
5469
|
+
return 'warning';
|
|
5470
|
+
}
|
|
5471
|
+
if (percentage >= 50) {
|
|
5472
|
+
return 'info';
|
|
5473
|
+
}
|
|
5474
|
+
return null;
|
|
5475
|
+
}
|
|
5476
|
+
maybeShowContextWarning(percentage) {
|
|
5477
|
+
const level = this.getContextWarningLevel(percentage);
|
|
5478
|
+
if (level === this.lastContextWarningLevel) {
|
|
5479
|
+
if (level === null) {
|
|
5480
|
+
this.lastContextWarningLevel = null;
|
|
5481
|
+
}
|
|
5482
|
+
return;
|
|
5483
|
+
}
|
|
5484
|
+
this.lastContextWarningLevel = level;
|
|
5485
|
+
switch (level) {
|
|
5486
|
+
case 'info':
|
|
5487
|
+
display.showInfo('Context usage is climbing. Auto-compaction will engage if it keeps growing.');
|
|
5488
|
+
break;
|
|
5489
|
+
case 'warning':
|
|
5490
|
+
display.showWarning('Context is getting full. Auto-compaction will try to preserve space.');
|
|
5491
|
+
break;
|
|
5492
|
+
case 'danger':
|
|
5493
|
+
display.showWarning('Context is near capacity. Attempting automatic compaction.');
|
|
5494
|
+
break;
|
|
5495
|
+
default:
|
|
5496
|
+
break;
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
historiesDiffer(original, compacted) {
|
|
5500
|
+
if (original.length !== compacted.length) {
|
|
5501
|
+
return true;
|
|
5502
|
+
}
|
|
5503
|
+
for (let i = 0; i < original.length; i++) {
|
|
5504
|
+
const a = original[i];
|
|
5505
|
+
const b = compacted[i];
|
|
5506
|
+
if (a.role !== b.role) {
|
|
5507
|
+
return true;
|
|
5508
|
+
}
|
|
5509
|
+
if ((a.content ?? '') !== (b.content ?? '')) {
|
|
5510
|
+
return true;
|
|
5511
|
+
}
|
|
5512
|
+
const aName = 'name' in a ? a.name : undefined;
|
|
5513
|
+
const bName = 'name' in b ? b.name : undefined;
|
|
5514
|
+
if ((aName ?? '') !== (bName ?? '')) {
|
|
5515
|
+
return true;
|
|
5516
|
+
}
|
|
5517
|
+
const aToolCallId = 'toolCallId' in a ? a.toolCallId : undefined;
|
|
5518
|
+
const bToolCallId = 'toolCallId' in b ? b.toolCallId : undefined;
|
|
5519
|
+
if ((aToolCallId ?? '') !== (bToolCallId ?? '')) {
|
|
5520
|
+
return true;
|
|
5521
|
+
}
|
|
5522
|
+
const aTools = 'toolCalls' in a ? a.toolCalls ?? [] : [];
|
|
5523
|
+
const bTools = 'toolCalls' in b ? b.toolCalls ?? [] : [];
|
|
5524
|
+
if (aTools.length !== bTools.length) {
|
|
5525
|
+
return true;
|
|
5526
|
+
}
|
|
5527
|
+
for (let j = 0; j < aTools.length; j++) {
|
|
5528
|
+
if (JSON.stringify(aTools[j]) !== JSON.stringify(bTools[j])) {
|
|
5529
|
+
return true;
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
return false;
|
|
5374
5534
|
}
|
|
5375
5535
|
partitionHistory(history) {
|
|
5376
5536
|
const system = [];
|