erosolar-cli 1.7.411 โ 1.7.413
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 -2
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +15 -28
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts +1 -0
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +13 -4
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/runtime/agentSession.d.ts.map +1 -1
- package/dist/runtime/agentSession.js +5 -6
- package/dist/runtime/agentSession.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +15 -19
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +274 -226
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +2 -2
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/subagents/parallelAgentManager.d.ts.map +1 -1
- package/dist/subagents/parallelAgentManager.js +1 -2
- package/dist/subagents/parallelAgentManager.js.map +1 -1
- package/dist/tools/editTools.js +2 -2
- package/dist/tools/editTools.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +3 -5
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +15 -28
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +2 -3
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts +2 -12
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +6 -57
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/streamingFormatter.d.ts +9 -5
- package/dist/ui/streamingFormatter.d.ts.map +1 -1
- package/dist/ui/streamingFormatter.js +52 -22
- package/dist/ui/streamingFormatter.js.map +1 -1
- package/dist/ui/toolDisplay.d.ts +3 -3
- package/dist/ui/toolDisplay.d.ts.map +1 -1
- package/dist/ui/toolDisplay.js +5 -12
- package/dist/ui/toolDisplay.js.map +1 -1
- package/dist/ui/unified/layout.d.ts +0 -37
- package/dist/ui/unified/layout.d.ts.map +1 -1
- package/dist/ui/unified/layout.js +1 -169
- package/dist/ui/unified/layout.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { display } from '../ui/display.js';
|
|
7
7
|
import { isPlainOutputMode } from '../ui/outputMode.js';
|
|
8
8
|
import { theme } from '../ui/theme.js';
|
|
9
|
+
import { renderDivider } from '../ui/unified/layout.js';
|
|
9
10
|
import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
|
|
10
11
|
import { getContextWindowTokens } from '../core/contextWindow.js';
|
|
11
12
|
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
|
|
@@ -37,7 +38,6 @@ import { analyzeTokenUsage, discoverModularTargets, getModularStatusDisplay, gen
|
|
|
37
38
|
import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActions, simulateOffsecRollout, formatOffsecStatus, listOffsecRuns, } from '../core/offsecAlphaZero.js';
|
|
38
39
|
import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
|
|
39
40
|
import { TerminalInputAdapter } from './terminalInputAdapter.js';
|
|
40
|
-
import { renderSessionFrame } from '../ui/unified/layout.js';
|
|
41
41
|
import { writeLock } from '../ui/writeLock.js';
|
|
42
42
|
import { enterStreamingMode, exitStreamingMode } from '../ui/globalWriteLock.js';
|
|
43
43
|
import { setGlobalAIEnhancer } from '../tools/localExplore.js';
|
|
@@ -72,7 +72,7 @@ const BASE_SLASH_COMMANDS = getSlashCommands().map((cmd) => ({
|
|
|
72
72
|
// Load PROVIDER_LABELS from centralized schema
|
|
73
73
|
const PROVIDER_LABELS = Object.fromEntries(getProviders().map((provider) => [provider.id, provider.label]));
|
|
74
74
|
// Allow enough time for paste detection to kick in before flushing buffered lines
|
|
75
|
-
const CONTEXT_USAGE_THRESHOLD = 0.
|
|
75
|
+
const CONTEXT_USAGE_THRESHOLD = 0.97;
|
|
76
76
|
const CONTEXT_AUTOCOMPACT_PERCENT = Math.round(CONTEXT_USAGE_THRESHOLD * 100);
|
|
77
77
|
const CONTEXT_AUTOCOMPACT_FLOOR = 0.6;
|
|
78
78
|
const MIN_COMPACTION_TOKEN_SAVINGS = 200;
|
|
@@ -80,12 +80,11 @@ const MIN_COMPACTION_PERCENT_SAVINGS = 0.5;
|
|
|
80
80
|
const CONTEXT_RECENT_MESSAGE_COUNT = 12;
|
|
81
81
|
const CONTEXT_CLEANUP_CHARS_PER_CHUNK = 6000;
|
|
82
82
|
const CONTEXT_CLEANUP_MAX_OUTPUT_TOKENS = 800;
|
|
83
|
-
const CONTEXT_CLEANUP_SYSTEM_PROMPT = `
|
|
84
|
-
- Merge any prior summary with the new
|
|
85
|
-
- Capture
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
- Never call tools or run shell commands; respond with plain Markdown text only.`;
|
|
83
|
+
const CONTEXT_CLEANUP_SYSTEM_PROMPT = `Summarize earlier IDE collaboration so the agent can keep working.
|
|
84
|
+
- Merge any prior summary with the new chunk.
|
|
85
|
+
- Capture decisions, file edits/paths, tool findings, and open questions.
|
|
86
|
+
- Separate finished work from follow-ups; keep it under ~180 words with tight bullets.
|
|
87
|
+
- Respond in plain Markdown only (no tool or shell calls).`;
|
|
89
88
|
export class InteractiveShell {
|
|
90
89
|
agent = null;
|
|
91
90
|
profile;
|
|
@@ -112,7 +111,6 @@ export class InteractiveShell {
|
|
|
112
111
|
thinkingMode = 'balanced';
|
|
113
112
|
agentMenu;
|
|
114
113
|
slashCommands;
|
|
115
|
-
bannerSessionState = null;
|
|
116
114
|
statusTracker;
|
|
117
115
|
ui;
|
|
118
116
|
uiAdapter;
|
|
@@ -126,6 +124,7 @@ export class InteractiveShell {
|
|
|
126
124
|
latestTokenUsage = { used: null, limit: null };
|
|
127
125
|
planApprovalBridgeRegistered = false;
|
|
128
126
|
contextCompactionInFlight = false;
|
|
127
|
+
contextCompactionLog = [];
|
|
129
128
|
lastContextWarningLevel = null;
|
|
130
129
|
sessionPreferences;
|
|
131
130
|
autosaveEnabled;
|
|
@@ -165,11 +164,14 @@ export class InteractiveShell {
|
|
|
165
164
|
streamingHeartbeatFrame = 0;
|
|
166
165
|
streamingStatusLabel = null;
|
|
167
166
|
lastStreamingElapsedSeconds = null; // Preserve final elapsed time
|
|
167
|
+
aiRuntimeStart = null;
|
|
168
|
+
aiRuntimeTotalMs = 0;
|
|
168
169
|
streamingFormatter = null;
|
|
169
170
|
statusLineState = null;
|
|
170
171
|
statusMessageOverride = null;
|
|
171
172
|
promptRefreshTimer = null;
|
|
172
173
|
launchPaletteShown = false;
|
|
174
|
+
launchBannerText = null;
|
|
173
175
|
version;
|
|
174
176
|
alternateScreenEnabled;
|
|
175
177
|
constructor(config) {
|
|
@@ -199,11 +201,6 @@ export class InteractiveShell {
|
|
|
199
201
|
reasoningEffort: config.initialModel.reasoningEffort,
|
|
200
202
|
};
|
|
201
203
|
this.applyPresetReasoningDefaults();
|
|
202
|
-
// The welcome banner only includes model + provider on launch, so mark that as the initial state.
|
|
203
|
-
this.bannerSessionState = {
|
|
204
|
-
model: this.sessionState.model,
|
|
205
|
-
provider: this.sessionState.provider,
|
|
206
|
-
};
|
|
207
204
|
this.agentMenu = config.agentSelection ?? null;
|
|
208
205
|
this.slashCommands = [...BASE_SLASH_COMMANDS];
|
|
209
206
|
if (this.agentMenu) {
|
|
@@ -293,15 +290,12 @@ export class InteractiveShell {
|
|
|
293
290
|
else if (output.isTTY) {
|
|
294
291
|
this.terminalInput.clearScreen();
|
|
295
292
|
}
|
|
296
|
-
//
|
|
297
|
-
const banner = this.buildBanner();
|
|
298
|
-
this.terminalInput.streamContent(banner + '\n\n');
|
|
299
|
-
// Render chat box after banner is streamed
|
|
293
|
+
// Render chat box immediately using the streaming UI lifecycle
|
|
300
294
|
this.refreshControlBar();
|
|
301
295
|
this.terminalInput.forceRender();
|
|
302
296
|
this.rebuildAgent();
|
|
303
297
|
this.setupHandlers();
|
|
304
|
-
this.
|
|
298
|
+
this.refreshSessionContext();
|
|
305
299
|
// Subscribe to parallel agent manager events
|
|
306
300
|
this.setupParallelAgentTracking();
|
|
307
301
|
}
|
|
@@ -383,7 +377,30 @@ export class InteractiveShell {
|
|
|
383
377
|
display.showInfo(this.sessionResumeNotice);
|
|
384
378
|
this.sessionResumeNotice = null;
|
|
385
379
|
}
|
|
380
|
+
renderWelcomeBanner(force = false) {
|
|
381
|
+
if (isPlainOutputMode() || !output.isTTY) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const header = `${theme.fields.model(this.sessionState.model)} ${theme.ui.muted('@')} ${theme.fields.agent(this.providerLabel(this.sessionState.provider))}`;
|
|
385
|
+
const profileLine = `${theme.fields.profile(this.profileLabel)} ${theme.ui.muted(`(${this.profile})`)}`;
|
|
386
|
+
const workspaceLine = theme.fields.workspace(this.workingDir);
|
|
387
|
+
const versionLine = this.version ? theme.ui.muted(`v${this.version} ยท support@ero.solar`) : null;
|
|
388
|
+
const hintLine = theme.ui.muted('/help for commands ยท /model to switch ยท /secrets for keys');
|
|
389
|
+
const width = output.columns ?? 80;
|
|
390
|
+
const banner = [header, profileLine, workspaceLine, versionLine, hintLine, renderDivider(width)]
|
|
391
|
+
.filter(Boolean)
|
|
392
|
+
.join('\n');
|
|
393
|
+
if (!force && this.launchBannerText) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (this.launchBannerText === banner) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
display.stream(`${banner}\n`);
|
|
400
|
+
this.launchBannerText = banner;
|
|
401
|
+
}
|
|
386
402
|
async start(initialPrompt) {
|
|
403
|
+
this.renderWelcomeBanner();
|
|
387
404
|
if (initialPrompt) {
|
|
388
405
|
await this.processInputBlock(initialPrompt);
|
|
389
406
|
return;
|
|
@@ -568,18 +585,20 @@ export class InteractiveShell {
|
|
|
568
585
|
' Toggle with Ctrl+Shift+C.');
|
|
569
586
|
}
|
|
570
587
|
/**
|
|
571
|
-
* Cycle through thinking modes (
|
|
588
|
+
* Cycle through thinking modes (Tab shortcut).
|
|
572
589
|
*/
|
|
573
590
|
cycleThinkingMode() {
|
|
574
591
|
const nextMode = this.thinkingMode === 'balanced' ? 'extended' : 'balanced';
|
|
575
592
|
this.thinkingMode = nextMode;
|
|
576
593
|
saveSessionPreferences({ thinkingMode: this.thinkingMode });
|
|
577
594
|
this.refreshControlBar();
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
595
|
+
const headline = nextMode === 'extended'
|
|
596
|
+
? `${theme.info('Thinking on')} (Tab to toggle)`
|
|
597
|
+
: `${theme.info('Thinking off')} (Tab to toggle)`;
|
|
598
|
+
const detail = nextMode === 'extended'
|
|
599
|
+
? 'Longer reasoning enabled; expect extra usage for deeper answers.'
|
|
600
|
+
: 'Balanced (default) reasoning restored.';
|
|
601
|
+
display.showSystemMessage([headline, theme.ui.muted(detail)].join('\n'));
|
|
583
602
|
}
|
|
584
603
|
/**
|
|
585
604
|
* Handle context clear/compact request (Alt+X keyboard shortcut).
|
|
@@ -591,26 +610,35 @@ export class InteractiveShell {
|
|
|
591
610
|
}
|
|
592
611
|
// Trigger context compaction
|
|
593
612
|
display.showInfo('Compacting context... This will summarize the conversation and free up space.');
|
|
594
|
-
void this.performContextCompaction();
|
|
613
|
+
void this.performContextCompaction('Manual shortcut compaction');
|
|
595
614
|
}
|
|
596
615
|
/**
|
|
597
616
|
* Perform context compaction by summarizing conversation history.
|
|
598
617
|
*/
|
|
599
|
-
async performContextCompaction() {
|
|
618
|
+
async performContextCompaction(reason = 'Manual context compaction') {
|
|
600
619
|
try {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (oldLength === 0) {
|
|
605
|
-
display.showInfo('Context is already empty.');
|
|
620
|
+
const agent = this.agent;
|
|
621
|
+
if (!agent) {
|
|
622
|
+
display.showWarning('No active agent to compact context.');
|
|
606
623
|
return;
|
|
607
624
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
625
|
+
const contextManager = agent.getContextManager();
|
|
626
|
+
if (!contextManager) {
|
|
627
|
+
display.showWarning('Context manager unavailable. Try sending a message first.');
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const history = agent.getHistory();
|
|
631
|
+
if (history.length <= 1) {
|
|
632
|
+
display.showInfo('Context is already minimal.');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const stats = contextManager.getStats(history);
|
|
636
|
+
const windowTokens = this.activeContextWindowTokens ?? this.latestTokenUsage.limit ?? null;
|
|
637
|
+
await this.runContextCleanup(windowTokens, stats.totalTokens, {
|
|
638
|
+
reason,
|
|
639
|
+
forced: true,
|
|
640
|
+
source: 'manual',
|
|
641
|
+
});
|
|
614
642
|
}
|
|
615
643
|
catch (error) {
|
|
616
644
|
display.showError('Failed to compact context', error);
|
|
@@ -722,6 +750,7 @@ export class InteractiveShell {
|
|
|
722
750
|
// Stop any active spinner to prevent process hang
|
|
723
751
|
display.stopThinking(false);
|
|
724
752
|
this.stopStreamingHeartbeat();
|
|
753
|
+
this.endAiRuntime();
|
|
725
754
|
this.uiUpdates.dispose();
|
|
726
755
|
this.clearPromptRefreshTimer();
|
|
727
756
|
this.teardownStatusTracking();
|
|
@@ -1147,8 +1176,8 @@ export class InteractiveShell {
|
|
|
1147
1176
|
autoContinueEnabled: this.autoContinueEnabled,
|
|
1148
1177
|
verificationHotkey: 'ctrl+shift+v',
|
|
1149
1178
|
autoContinueHotkey: 'ctrl+shift+c',
|
|
1150
|
-
thinkingModeLabel: this.thinkingMode,
|
|
1151
|
-
thinkingHotkey: '
|
|
1179
|
+
thinkingModeLabel: this.thinkingMode === 'extended' ? 'on' : 'off',
|
|
1180
|
+
thinkingHotkey: 'tab',
|
|
1152
1181
|
});
|
|
1153
1182
|
this.refreshStatusLine();
|
|
1154
1183
|
this.terminalInput.render();
|
|
@@ -1167,6 +1196,25 @@ export class InteractiveShell {
|
|
|
1167
1196
|
process.stdout.write(content);
|
|
1168
1197
|
}, 'interactiveShell.stdout');
|
|
1169
1198
|
}
|
|
1199
|
+
beginAiRuntime() {
|
|
1200
|
+
if (this.aiRuntimeStart === null) {
|
|
1201
|
+
this.aiRuntimeStart = Date.now();
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
endAiRuntime() {
|
|
1205
|
+
if (this.aiRuntimeStart !== null) {
|
|
1206
|
+
this.aiRuntimeTotalMs += Date.now() - this.aiRuntimeStart;
|
|
1207
|
+
this.aiRuntimeStart = null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
getAiRuntimeSeconds() {
|
|
1211
|
+
const runningMs = this.aiRuntimeStart ? Date.now() - this.aiRuntimeStart : 0;
|
|
1212
|
+
const totalMs = this.aiRuntimeTotalMs + runningMs;
|
|
1213
|
+
if (totalMs <= 0 && this.aiRuntimeStart === null) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
return Math.max(0, Math.floor(totalMs / 1000));
|
|
1217
|
+
}
|
|
1170
1218
|
/**
|
|
1171
1219
|
* Refresh the status line in the persistent input area.
|
|
1172
1220
|
* Uses combined status display: streaming label + override + main status.
|
|
@@ -1181,20 +1229,7 @@ export class InteractiveShell {
|
|
|
1181
1229
|
const statusText = this.formatStatusLine(this.statusLineState);
|
|
1182
1230
|
this.terminalInput.setStatusMessage(statusText);
|
|
1183
1231
|
// Surface meta header (elapsed + context usage) above the divider
|
|
1184
|
-
|
|
1185
|
-
let elapsedSeconds = null;
|
|
1186
|
-
if (this.streamingHeartbeatStart) {
|
|
1187
|
-
// Actively streaming - compute live elapsed
|
|
1188
|
-
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1189
|
-
}
|
|
1190
|
-
else if (this.lastStreamingElapsedSeconds !== null) {
|
|
1191
|
-
// Just finished streaming - use preserved final time
|
|
1192
|
-
elapsedSeconds = this.lastStreamingElapsedSeconds;
|
|
1193
|
-
}
|
|
1194
|
-
else if (this.statusLineState) {
|
|
1195
|
-
// Fallback to status line state elapsed
|
|
1196
|
-
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
|
|
1197
|
-
}
|
|
1232
|
+
const elapsedSeconds = this.getAiRuntimeSeconds();
|
|
1198
1233
|
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1199
1234
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1200
1235
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
@@ -1373,6 +1408,8 @@ export class InteractiveShell {
|
|
|
1373
1408
|
this.terminalInput.streamContent(closing);
|
|
1374
1409
|
}
|
|
1375
1410
|
this.streamingFormatter = null;
|
|
1411
|
+
// Force the prompt to re-render after streaming so it stays below the last response
|
|
1412
|
+
this.requestPromptRefresh(true);
|
|
1376
1413
|
}
|
|
1377
1414
|
buildStreamingStatus(label, _elapsedSeconds) {
|
|
1378
1415
|
// Model + elapsed time already live in the pinned meta header; keep the streaming
|
|
@@ -1686,7 +1723,7 @@ export class InteractiveShell {
|
|
|
1686
1723
|
this.handleCostCommand();
|
|
1687
1724
|
break;
|
|
1688
1725
|
case '/usage':
|
|
1689
|
-
this.handleUsageCommand();
|
|
1726
|
+
this.handleUsageCommand(input);
|
|
1690
1727
|
break;
|
|
1691
1728
|
case '/clear':
|
|
1692
1729
|
this.handleClearCommand();
|
|
@@ -2022,7 +2059,8 @@ export class InteractiveShell {
|
|
|
2022
2059
|
handleThinkingCommand(input) {
|
|
2023
2060
|
const value = input.slice('/thinking'.length).trim().toLowerCase();
|
|
2024
2061
|
if (!value) {
|
|
2025
|
-
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [balanced|extended]
|
|
2062
|
+
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [balanced|extended].` +
|
|
2063
|
+
' Press Tab any time to toggle.');
|
|
2026
2064
|
return;
|
|
2027
2065
|
}
|
|
2028
2066
|
if (value !== 'balanced' && value !== 'extended') {
|
|
@@ -2038,11 +2076,15 @@ export class InteractiveShell {
|
|
|
2038
2076
|
if (this.rebuildAgent()) {
|
|
2039
2077
|
this.resetChatBoxAfterModelSwap();
|
|
2040
2078
|
}
|
|
2079
|
+
this.refreshControlBar();
|
|
2041
2080
|
const descriptions = {
|
|
2042
|
-
balanced: '
|
|
2043
|
-
extended: '
|
|
2081
|
+
balanced: 'Balanced (default) reasoning with short thoughts only when helpful.',
|
|
2082
|
+
extended: 'Longer reasoning enabled; expect extra usage for deeper answers.',
|
|
2044
2083
|
};
|
|
2045
|
-
|
|
2084
|
+
const headline = this.thinkingMode === 'extended'
|
|
2085
|
+
? `${theme.info('Thinking on')} (Tab to toggle)`
|
|
2086
|
+
: `${theme.info('Thinking off')} (Tab to toggle)`;
|
|
2087
|
+
display.showSystemMessage(`${headline}\n${theme.ui.muted(descriptions[this.thinkingMode])}`);
|
|
2046
2088
|
}
|
|
2047
2089
|
handleShortcutsCommand() {
|
|
2048
2090
|
// Display keyboard shortcuts help (Claude Code style)
|
|
@@ -3391,7 +3433,7 @@ export class InteractiveShell {
|
|
|
3391
3433
|
lines.push(' /rewind code Rewind code only (keep conversation)');
|
|
3392
3434
|
lines.push(' /rewind conv Rewind conversation only (keep code)');
|
|
3393
3435
|
lines.push('');
|
|
3394
|
-
lines.push(theme.ui.muted('
|
|
3436
|
+
lines.push(theme.ui.muted('Press Esc+Esc for quick access to the rewind menu'));
|
|
3395
3437
|
display.showSystemMessage(lines.join('\n'));
|
|
3396
3438
|
}
|
|
3397
3439
|
handleMemoryCommand(input) {
|
|
@@ -3410,7 +3452,7 @@ export class InteractiveShell {
|
|
|
3410
3452
|
lines.push(' - Use # prefix to quickly add notes to project memory');
|
|
3411
3453
|
lines.push(' - Import other files with @./relative/path syntax');
|
|
3412
3454
|
lines.push('');
|
|
3413
|
-
lines.push(theme.ui.muted('
|
|
3455
|
+
lines.push(theme.ui.muted('Create EROSOLAR.md with project coding standards for better results'));
|
|
3414
3456
|
display.showSystemMessage(lines.join('\n'));
|
|
3415
3457
|
}
|
|
3416
3458
|
handleVimCommand() {
|
|
@@ -3468,7 +3510,13 @@ export class InteractiveShell {
|
|
|
3468
3510
|
lines.push(theme.ui.muted('Token usage is tracked automatically during the session.'));
|
|
3469
3511
|
display.showSystemMessage(lines.join('\n'));
|
|
3470
3512
|
}
|
|
3471
|
-
handleUsageCommand() {
|
|
3513
|
+
handleUsageCommand(input) {
|
|
3514
|
+
const tokens = input ? input.trim().split(/\s+/) : [];
|
|
3515
|
+
const subcommand = tokens[1]?.toLowerCase();
|
|
3516
|
+
if (subcommand === 'history' || subcommand === 'log') {
|
|
3517
|
+
this.showCompactionHistory();
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3472
3520
|
const percentage = this.latestTokenUsage.limit && this.latestTokenUsage.used
|
|
3473
3521
|
? Math.round((this.latestTokenUsage.used / this.latestTokenUsage.limit) * 100)
|
|
3474
3522
|
: 0;
|
|
@@ -3489,6 +3537,37 @@ export class InteractiveShell {
|
|
|
3489
3537
|
else {
|
|
3490
3538
|
lines.push(theme.success('Plenty of context available.'));
|
|
3491
3539
|
}
|
|
3540
|
+
if (this.contextCompactionLog.length > 0) {
|
|
3541
|
+
lines.push('');
|
|
3542
|
+
lines.push(theme.secondary('Tip: /usage history shows recent context compactions.'));
|
|
3543
|
+
}
|
|
3544
|
+
display.showSystemMessage(lines.join('\n'));
|
|
3545
|
+
}
|
|
3546
|
+
showCompactionHistory() {
|
|
3547
|
+
if (!this.contextCompactionLog.length) {
|
|
3548
|
+
display.showInfo('No context compactions recorded yet.');
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
const entries = [...this.contextCompactionLog].slice(-10).reverse();
|
|
3552
|
+
const lines = [];
|
|
3553
|
+
lines.push(theme.bold('Context Compaction History'));
|
|
3554
|
+
lines.push('');
|
|
3555
|
+
for (const entry of entries) {
|
|
3556
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
3557
|
+
const reason = entry.reason || (entry.source === 'auto' ? 'Auto-compaction' : 'Manual compaction');
|
|
3558
|
+
const before = entry.beforeTokens.toLocaleString('en-US');
|
|
3559
|
+
const after = entry.afterTokens.toLocaleString('en-US');
|
|
3560
|
+
const saved = Math.max(0, entry.beforeTokens - entry.afterTokens);
|
|
3561
|
+
const savedLabel = saved > 0 ? `${saved.toLocaleString('en-US')} saved` : 'no savings';
|
|
3562
|
+
const pctBefore = entry.percentBefore ?? null;
|
|
3563
|
+
const pctAfter = entry.percentAfter ?? null;
|
|
3564
|
+
const pctLabel = pctBefore !== null && pctAfter !== null
|
|
3565
|
+
? `(${pctBefore}% โ ${pctAfter}%)`
|
|
3566
|
+
: '';
|
|
3567
|
+
const summaryNote = entry.summarized ? 'summary applied' : 'prune only';
|
|
3568
|
+
lines.push(` ${time} ยท ${reason} [${entry.source} ยท ${entry.model}]`);
|
|
3569
|
+
lines.push(` ${before} โ ${after} tokens ${pctLabel} ยท ${savedLabel} ยท ${summaryNote}`);
|
|
3570
|
+
}
|
|
3492
3571
|
display.showSystemMessage(lines.join('\n'));
|
|
3493
3572
|
}
|
|
3494
3573
|
handleClearCommand() {
|
|
@@ -3625,13 +3704,7 @@ export class InteractiveShell {
|
|
|
3625
3704
|
return;
|
|
3626
3705
|
}
|
|
3627
3706
|
display.showInfo('Compacting conversation context...');
|
|
3628
|
-
|
|
3629
|
-
const { used, limit } = this.latestTokenUsage;
|
|
3630
|
-
if (used && limit && used / limit < 0.5) {
|
|
3631
|
-
display.showInfo('Context usage is low. No compaction needed.');
|
|
3632
|
-
return;
|
|
3633
|
-
}
|
|
3634
|
-
await this.processRequest('Please summarize our conversation so far in a concise way, preserving key decisions, code changes, and next steps. This will help compact the context.');
|
|
3707
|
+
await this.performContextCompaction('Manual /compact request');
|
|
3635
3708
|
}
|
|
3636
3709
|
// ==================== End Claude Code Style Commands ====================
|
|
3637
3710
|
updateActiveSession(summary, remember = false) {
|
|
@@ -4464,7 +4537,7 @@ export class InteractiveShell {
|
|
|
4464
4537
|
this.applyPresetReasoningDefaults();
|
|
4465
4538
|
if (this.rebuildAgent()) {
|
|
4466
4539
|
display.showInfo(`Switched to ${preset.label}.`);
|
|
4467
|
-
this.
|
|
4540
|
+
this.refreshSessionContext();
|
|
4468
4541
|
this.persistSessionPreference();
|
|
4469
4542
|
this.resetChatBoxAfterModelSwap();
|
|
4470
4543
|
}
|
|
@@ -4573,6 +4646,7 @@ export class InteractiveShell {
|
|
|
4573
4646
|
this.currentToolCalls = [];
|
|
4574
4647
|
this.uiAdapter.startProcessing('Working on your request');
|
|
4575
4648
|
this.setProcessingStatus();
|
|
4649
|
+
this.beginAiRuntime();
|
|
4576
4650
|
let responseText = '';
|
|
4577
4651
|
try {
|
|
4578
4652
|
// Start streaming - no header needed, the input area already provides context
|
|
@@ -4638,6 +4712,7 @@ export class InteractiveShell {
|
|
|
4638
4712
|
display.stopThinking(false);
|
|
4639
4713
|
this.uiUpdates.setMode('processing');
|
|
4640
4714
|
this.stopStreamingHeartbeat();
|
|
4715
|
+
this.endAiRuntime();
|
|
4641
4716
|
this.isProcessing = false;
|
|
4642
4717
|
this.terminalInput.setStreaming(false);
|
|
4643
4718
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
@@ -4693,6 +4768,7 @@ export class InteractiveShell {
|
|
|
4693
4768
|
display.showSystemMessage(`Continuous mode active. Ctrl+C to stop.`);
|
|
4694
4769
|
this.uiAdapter.startProcessing('Continuous execution mode');
|
|
4695
4770
|
this.setProcessingStatus();
|
|
4771
|
+
this.beginAiRuntime();
|
|
4696
4772
|
// No streaming header - just start streaming directly
|
|
4697
4773
|
this.startStreamingHeartbeat('Streaming');
|
|
4698
4774
|
let iteration = 0;
|
|
@@ -4868,6 +4944,7 @@ What's the next action?`;
|
|
|
4868
4944
|
resetTaskCompletionDetector();
|
|
4869
4945
|
this.uiUpdates.setMode('processing');
|
|
4870
4946
|
this.stopStreamingHeartbeat();
|
|
4947
|
+
this.endAiRuntime();
|
|
4871
4948
|
this.isProcessing = false;
|
|
4872
4949
|
this.terminalInput.setStreaming(false);
|
|
4873
4950
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
@@ -5424,6 +5501,7 @@ Return ONLY JSON array:
|
|
|
5424
5501
|
try {
|
|
5425
5502
|
ensureSecretForProvider(this.sessionState.provider);
|
|
5426
5503
|
this.runtimeSession.updateToolContext(this.sessionState);
|
|
5504
|
+
this.ensureActiveSummarizer(this.runtimeSession.contextManager);
|
|
5427
5505
|
const selection = {
|
|
5428
5506
|
provider: this.sessionState.provider,
|
|
5429
5507
|
model: this.sessionState.model,
|
|
@@ -5574,6 +5652,7 @@ Return ONLY JSON array:
|
|
|
5574
5652
|
* Ensures the input area is visible and ready for input, just like on startup.
|
|
5575
5653
|
*/
|
|
5576
5654
|
resetChatBoxAfterModelSwap() {
|
|
5655
|
+
this.renderWelcomeBanner(true);
|
|
5577
5656
|
this.updateStatusMessage(null);
|
|
5578
5657
|
this.terminalInput.setStreaming(false);
|
|
5579
5658
|
this.terminalInput.render();
|
|
@@ -5689,7 +5768,7 @@ Return ONLY JSON array:
|
|
|
5689
5768
|
if (!trigger) {
|
|
5690
5769
|
return null;
|
|
5691
5770
|
}
|
|
5692
|
-
return this.runContextCleanup(windowTokens, total, trigger);
|
|
5771
|
+
return this.runContextCleanup(windowTokens, total, { ...trigger, source: 'auto' });
|
|
5693
5772
|
}
|
|
5694
5773
|
totalTokens(usage) {
|
|
5695
5774
|
if (!usage) {
|
|
@@ -5716,58 +5795,104 @@ Return ONLY JSON array:
|
|
|
5716
5795
|
if (history.length <= 1) {
|
|
5717
5796
|
return;
|
|
5718
5797
|
}
|
|
5798
|
+
if (!this.areToolTurnsComplete(history)) {
|
|
5799
|
+
display.showInfo('Context compaction postponed until all tool calls have returned results.');
|
|
5800
|
+
return;
|
|
5801
|
+
}
|
|
5802
|
+
this.ensureActiveSummarizer(contextManager);
|
|
5719
5803
|
this.cleanupInProgress = true;
|
|
5720
5804
|
this.contextCompactionInFlight = true;
|
|
5721
5805
|
const cleanupStatusId = 'context-cleanup';
|
|
5722
5806
|
let cleanupOverlayActive = false;
|
|
5723
5807
|
try {
|
|
5724
|
-
const
|
|
5725
|
-
const
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5808
|
+
const beforeStats = contextManager.getStats(history);
|
|
5809
|
+
const resolvedWindowTokens = windowTokens ?? this.activeContextWindowTokens ?? this.latestTokenUsage.limit ?? null;
|
|
5810
|
+
const resolvedTotalTokens = totalTokens ?? beforeStats.totalTokens;
|
|
5811
|
+
const percentUsed = resolvedWindowTokens && resolvedWindowTokens > 0
|
|
5812
|
+
? Math.min(100, Math.round((resolvedTotalTokens / resolvedWindowTokens) * 100))
|
|
5813
|
+
: beforeStats.percentage;
|
|
5814
|
+
const statusDetailParts = [
|
|
5815
|
+
`${percentUsed}% full`,
|
|
5816
|
+
trigger?.reason,
|
|
5817
|
+
].filter(Boolean);
|
|
5729
5818
|
this.statusTracker.pushOverride(cleanupStatusId, 'Running context cleanup', {
|
|
5730
5819
|
detail: statusDetailParts.join(' ยท '),
|
|
5731
5820
|
tone: 'warning',
|
|
5732
5821
|
});
|
|
5733
5822
|
cleanupOverlayActive = true;
|
|
5734
5823
|
const triggerReason = trigger?.reason ?? 'Context optimization';
|
|
5824
|
+
const limitLabel = resolvedWindowTokens
|
|
5825
|
+
? `of ${resolvedWindowTokens.toLocaleString('en-US')} tokens`
|
|
5826
|
+
: 'of estimated context';
|
|
5735
5827
|
display.showSystemMessage([
|
|
5736
|
-
`Context usage: ${
|
|
5828
|
+
`Context usage: ${resolvedTotalTokens.toLocaleString('en-US')} ${limitLabel}`,
|
|
5737
5829
|
`(${percentUsed}% full). Auto-compacting${triggerReason ? `: ${triggerReason}` : '...'}`,
|
|
5738
5830
|
].join(' '));
|
|
5739
|
-
const beforeStats = contextManager.getStats(history);
|
|
5740
5831
|
const result = await contextManager.intelligentCompact(history);
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
const
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5832
|
+
let afterStats = contextManager.getStats(result.compacted);
|
|
5833
|
+
let appliedHistory = result.compacted;
|
|
5834
|
+
let appliedSummarized = result.summarized;
|
|
5835
|
+
const initialTokenSavings = Math.max(0, beforeStats.totalTokens - afterStats.totalTokens);
|
|
5836
|
+
let bestTokenSavings = initialTokenSavings;
|
|
5837
|
+
let bestPercentSavings = beforeStats.totalTokens > 0 ? (initialTokenSavings / beforeStats.totalTokens) * 100 : 0;
|
|
5838
|
+
let changed = this.historiesDiffer(history, appliedHistory);
|
|
5839
|
+
if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
|
|
5840
|
+
const fallback = await contextManager.pruneMessagesWithSummary(history, { force: true });
|
|
5841
|
+
const fallbackStats = contextManager.getStats(fallback.pruned);
|
|
5842
|
+
const fallbackTokenSavings = Math.max(0, beforeStats.totalTokens - fallbackStats.totalTokens);
|
|
5843
|
+
const fallbackPercentSavings = beforeStats.totalTokens > 0
|
|
5844
|
+
? (fallbackTokenSavings / beforeStats.totalTokens) * 100
|
|
5845
|
+
: 0;
|
|
5846
|
+
if (this.historiesDiffer(history, fallback.pruned) &&
|
|
5847
|
+
(fallbackTokenSavings > bestTokenSavings || !changed)) {
|
|
5848
|
+
appliedHistory = fallback.pruned;
|
|
5849
|
+
afterStats = fallbackStats;
|
|
5850
|
+
appliedSummarized = fallback.summarized;
|
|
5851
|
+
bestTokenSavings = fallbackTokenSavings;
|
|
5852
|
+
bestPercentSavings = fallbackPercentSavings;
|
|
5853
|
+
changed = true;
|
|
5750
5854
|
}
|
|
5855
|
+
}
|
|
5856
|
+
if (!changed) {
|
|
5857
|
+
display.showInfo('Context compaction completed but no changes were applied.');
|
|
5751
5858
|
return;
|
|
5752
5859
|
}
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5860
|
+
if (!this.hasMeaningfulCompaction(changed, bestTokenSavings, bestPercentSavings, trigger?.forced)) {
|
|
5861
|
+
display.showInfo('Auto-compaction completed but did not meaningfully reduce context size. Keeping existing history.');
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
agent.loadHistory(appliedHistory);
|
|
5865
|
+
this.cachedHistory = appliedHistory;
|
|
5866
|
+
const newPercentUsed = resolvedWindowTokens && resolvedWindowTokens > 0
|
|
5867
|
+
? Math.min(100, Math.round((afterStats.totalTokens / resolvedWindowTokens) * 100))
|
|
5757
5868
|
: afterStats.percentage;
|
|
5758
5869
|
this.latestTokenUsage = {
|
|
5759
5870
|
used: afterStats.totalTokens,
|
|
5760
|
-
limit:
|
|
5871
|
+
limit: resolvedWindowTokens,
|
|
5761
5872
|
};
|
|
5762
5873
|
this.updateContextUsage(newPercentUsed, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
5763
5874
|
this.lastContextWarningLevel = this.getContextWarningLevel(newPercentUsed);
|
|
5764
5875
|
this.refreshStatusLine(true);
|
|
5765
5876
|
const primarySignal = result.analysis.signals[0]?.reason ?? triggerReason;
|
|
5877
|
+
const savingsLabel = bestTokenSavings > 0
|
|
5878
|
+
? `(saved ~${bestTokenSavings.toLocaleString('en-US')} tokens, ~${bestPercentSavings.toFixed(1)}%)`
|
|
5879
|
+
: '(no token savings)';
|
|
5766
5880
|
display.showSystemMessage([
|
|
5767
5881
|
`Context compacted: ${beforeStats.percentage}% โ ${afterStats.percentage}%`,
|
|
5768
|
-
|
|
5882
|
+
savingsLabel,
|
|
5769
5883
|
primarySignal ? `Reason: ${primarySignal}.` : '',
|
|
5770
5884
|
].filter(Boolean).join(' '));
|
|
5885
|
+
this.recordContextCompaction({
|
|
5886
|
+
timestamp: Date.now(),
|
|
5887
|
+
source: trigger?.source ?? 'auto',
|
|
5888
|
+
reason: triggerReason,
|
|
5889
|
+
beforeTokens: beforeStats.totalTokens,
|
|
5890
|
+
afterTokens: afterStats.totalTokens,
|
|
5891
|
+
summarized: appliedSummarized,
|
|
5892
|
+
percentBefore: resolvedWindowTokens ? percentUsed : beforeStats.percentage,
|
|
5893
|
+
percentAfter: newPercentUsed,
|
|
5894
|
+
model: this.sessionState.model,
|
|
5895
|
+
});
|
|
5771
5896
|
}
|
|
5772
5897
|
catch (error) {
|
|
5773
5898
|
display.showError('Context compaction failed.', error);
|
|
@@ -5780,7 +5905,35 @@ Return ONLY JSON array:
|
|
|
5780
5905
|
this.contextCompactionInFlight = false;
|
|
5781
5906
|
}
|
|
5782
5907
|
}
|
|
5783
|
-
|
|
5908
|
+
recordContextCompaction(event) {
|
|
5909
|
+
this.contextCompactionLog.push(event);
|
|
5910
|
+
if (this.contextCompactionLog.length > 20) {
|
|
5911
|
+
this.contextCompactionLog.shift();
|
|
5912
|
+
}
|
|
5913
|
+
}
|
|
5914
|
+
ensureActiveSummarizer(contextManager) {
|
|
5915
|
+
if (!contextManager?.updateConfig) {
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
const summarizer = async (messages) => {
|
|
5919
|
+
const summary = await this.buildContextSummary(messages);
|
|
5920
|
+
return summary ?? '[No content to summarize]';
|
|
5921
|
+
};
|
|
5922
|
+
contextManager.updateConfig({
|
|
5923
|
+
summarizationCallback: summarizer,
|
|
5924
|
+
useLLMSummarization: true,
|
|
5925
|
+
});
|
|
5926
|
+
}
|
|
5927
|
+
hasMeaningfulCompaction(changed, tokenSavings, percentSavings, forced) {
|
|
5928
|
+
if (!changed) {
|
|
5929
|
+
return false;
|
|
5930
|
+
}
|
|
5931
|
+
if (tokenSavings >= MIN_COMPACTION_TOKEN_SAVINGS || percentSavings >= MIN_COMPACTION_PERCENT_SAVINGS) {
|
|
5932
|
+
return true;
|
|
5933
|
+
}
|
|
5934
|
+
return Boolean(forced && tokenSavings > 0);
|
|
5935
|
+
}
|
|
5936
|
+
shouldAutoCompactContext(usageRatio, _windowTokens, _totalTokens) {
|
|
5784
5937
|
const featureFlags = loadFeatureFlags();
|
|
5785
5938
|
if (featureFlags.autoCompact === false) {
|
|
5786
5939
|
return null;
|
|
@@ -5901,6 +6054,25 @@ Return ONLY JSON array:
|
|
|
5901
6054
|
}
|
|
5902
6055
|
return false;
|
|
5903
6056
|
}
|
|
6057
|
+
areToolTurnsComplete(messages) {
|
|
6058
|
+
const pending = new Set();
|
|
6059
|
+
for (const message of messages) {
|
|
6060
|
+
if (message.role === 'assistant' && message.toolCalls && message.toolCalls.length > 0) {
|
|
6061
|
+
for (const call of message.toolCalls) {
|
|
6062
|
+
if (call.id) {
|
|
6063
|
+
pending.add(call.id);
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
if (message.role === 'tool') {
|
|
6068
|
+
const toolCallId = message.toolCallId;
|
|
6069
|
+
if (toolCallId && pending.has(toolCallId)) {
|
|
6070
|
+
pending.delete(toolCallId);
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
return pending.size === 0;
|
|
6075
|
+
}
|
|
5904
6076
|
partitionHistory(history) {
|
|
5905
6077
|
const system = [];
|
|
5906
6078
|
const conversation = [];
|
|
@@ -5992,10 +6164,10 @@ Return ONLY JSON array:
|
|
|
5992
6164
|
sections.push(`Conversation chunk:\n${chunk}`);
|
|
5993
6165
|
sections.push([
|
|
5994
6166
|
'Instructions:',
|
|
5995
|
-
'- Merge
|
|
5996
|
-
'-
|
|
5997
|
-
'-
|
|
5998
|
-
'-
|
|
6167
|
+
'- Merge with the running summary.',
|
|
6168
|
+
'- Keep TODOs/bugs/test gaps and file references.',
|
|
6169
|
+
'- Mark what is done vs. pending.',
|
|
6170
|
+
'- Stay concise (<180 words) with short bullets.',
|
|
5999
6171
|
].join('\n'));
|
|
6000
6172
|
return sections.join('\n\n');
|
|
6001
6173
|
}
|
|
@@ -6080,7 +6252,7 @@ Return ONLY JSON array:
|
|
|
6080
6252
|
else {
|
|
6081
6253
|
lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
|
|
6082
6254
|
}
|
|
6083
|
-
lines.push(`
|
|
6255
|
+
lines.push(`Run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
|
|
6084
6256
|
display.showSystemMessage(lines.join('\n'));
|
|
6085
6257
|
}
|
|
6086
6258
|
colorizeDropdownLine(text, index) {
|
|
@@ -6102,136 +6274,12 @@ Return ONLY JSON array:
|
|
|
6102
6274
|
this.sessionState.reasoningEffort = preset.reasoningEffort;
|
|
6103
6275
|
}
|
|
6104
6276
|
}
|
|
6105
|
-
|
|
6106
|
-
* Build the session banner with comprehensive feature status.
|
|
6107
|
-
*/
|
|
6108
|
-
buildBanner() {
|
|
6109
|
-
const terminalWidth = output.columns ?? 100;
|
|
6110
|
-
const width = Math.min(terminalWidth - 4, 110);
|
|
6111
|
-
// Collect tool categories for display
|
|
6112
|
-
const toolCategories = this.collectToolCategories();
|
|
6113
|
-
// Load feature flags for banner display
|
|
6114
|
-
const featureFlags = loadFeatureFlags();
|
|
6115
|
-
return renderSessionFrame({
|
|
6116
|
-
profileLabel: this.profileLabel,
|
|
6117
|
-
profileName: this.profile,
|
|
6118
|
-
model: this.sessionState.model,
|
|
6119
|
-
provider: this.sessionState.provider,
|
|
6120
|
-
workspace: this.workingDir,
|
|
6121
|
-
version: this.version,
|
|
6122
|
-
width,
|
|
6123
|
-
features: {
|
|
6124
|
-
verification: this.verificationEnabled,
|
|
6125
|
-
autoContinue: this.autoContinueEnabled,
|
|
6126
|
-
thinkingMode: this.thinkingMode,
|
|
6127
|
-
plugins: this._enabledPlugins,
|
|
6128
|
-
tools: toolCategories,
|
|
6129
|
-
sessionId: this.activeSessionId ?? undefined,
|
|
6130
|
-
// Include feature flags
|
|
6131
|
-
alphaZeroDual: featureFlags.alphaZeroDual,
|
|
6132
|
-
autoCompact: featureFlags.autoCompact,
|
|
6133
|
-
mcpEnabled: featureFlags.mcpEnabled,
|
|
6134
|
-
metrics: featureFlags.metrics,
|
|
6135
|
-
},
|
|
6136
|
-
});
|
|
6137
|
-
}
|
|
6138
|
-
/**
|
|
6139
|
-
* Collect tool categories for banner display.
|
|
6140
|
-
*/
|
|
6141
|
-
collectToolCategories() {
|
|
6142
|
-
const categories = [];
|
|
6143
|
-
try {
|
|
6144
|
-
const providerTools = this.runtimeSession.toolRuntime.listProviderTools();
|
|
6145
|
-
if (providerTools.length > 0) {
|
|
6146
|
-
// Group by category (first word of tool name or namespace)
|
|
6147
|
-
const groups = new Map();
|
|
6148
|
-
for (const tool of providerTools) {
|
|
6149
|
-
const category = this.extractToolCategory(tool.name);
|
|
6150
|
-
groups.set(category, (groups.get(category) || 0) + 1);
|
|
6151
|
-
}
|
|
6152
|
-
// Convert to array sorted by count
|
|
6153
|
-
const sorted = Array.from(groups.entries())
|
|
6154
|
-
.sort((a, b) => b[1] - a[1])
|
|
6155
|
-
.slice(0, 5); // Top 5 categories
|
|
6156
|
-
for (const [name, count] of sorted) {
|
|
6157
|
-
categories.push({ name, count, icon: this.getToolCategoryIcon(name) });
|
|
6158
|
-
}
|
|
6159
|
-
}
|
|
6160
|
-
}
|
|
6161
|
-
catch {
|
|
6162
|
-
// Ignore errors in tool collection
|
|
6163
|
-
}
|
|
6164
|
-
return categories;
|
|
6165
|
-
}
|
|
6166
|
-
/**
|
|
6167
|
-
* Extract category from tool name.
|
|
6168
|
-
*/
|
|
6169
|
-
extractToolCategory(toolName) {
|
|
6170
|
-
// Common tool prefixes
|
|
6171
|
-
const prefixMap = {
|
|
6172
|
-
git: 'Git',
|
|
6173
|
-
npm: 'NPM',
|
|
6174
|
-
bash: 'Shell',
|
|
6175
|
-
file: 'Files',
|
|
6176
|
-
read: 'Files',
|
|
6177
|
-
write: 'Files',
|
|
6178
|
-
edit: 'Files',
|
|
6179
|
-
search: 'Search',
|
|
6180
|
-
glob: 'Search',
|
|
6181
|
-
grep: 'Search',
|
|
6182
|
-
web: 'Web',
|
|
6183
|
-
fetch: 'Web',
|
|
6184
|
-
test: 'Testing',
|
|
6185
|
-
build: 'Build',
|
|
6186
|
-
deploy: 'Deploy',
|
|
6187
|
-
cloud: 'Cloud',
|
|
6188
|
-
browser: 'Browser',
|
|
6189
|
-
};
|
|
6190
|
-
const lower = toolName.toLowerCase();
|
|
6191
|
-
for (const [prefix, category] of Object.entries(prefixMap)) {
|
|
6192
|
-
if (lower.startsWith(prefix)) {
|
|
6193
|
-
return category;
|
|
6194
|
-
}
|
|
6195
|
-
}
|
|
6196
|
-
// Default to first word capitalized
|
|
6197
|
-
const firstWord = toolName.split(/[_\-\s]/)[0] || 'Other';
|
|
6198
|
-
return firstWord.charAt(0).toUpperCase() + firstWord.slice(1).toLowerCase();
|
|
6199
|
-
}
|
|
6200
|
-
/**
|
|
6201
|
-
* Get icon for tool category.
|
|
6202
|
-
*/
|
|
6203
|
-
getToolCategoryIcon(category) {
|
|
6204
|
-
const icons = {
|
|
6205
|
-
Git: 'โ',
|
|
6206
|
-
NPM: '๐ฆ',
|
|
6207
|
-
Shell: 'โ',
|
|
6208
|
-
Files: '๐',
|
|
6209
|
-
Search: '๐',
|
|
6210
|
-
Web: '๐',
|
|
6211
|
-
Testing: '๐งช',
|
|
6212
|
-
Build: '๐ง',
|
|
6213
|
-
Deploy: '๐',
|
|
6214
|
-
Cloud: 'โ',
|
|
6215
|
-
Browser: '๐',
|
|
6216
|
-
};
|
|
6217
|
-
return icons[category] || 'โ';
|
|
6218
|
-
}
|
|
6219
|
-
refreshBannerSessionInfo() {
|
|
6220
|
-
const nextState = {
|
|
6221
|
-
model: this.sessionState.model,
|
|
6222
|
-
provider: this.sessionState.provider,
|
|
6223
|
-
};
|
|
6224
|
-
const previous = this.bannerSessionState;
|
|
6225
|
-
if (previous && previous.model === nextState.model && previous.provider === nextState.provider) {
|
|
6226
|
-
return;
|
|
6227
|
-
}
|
|
6277
|
+
refreshSessionContext() {
|
|
6228
6278
|
this.refreshContextGauge();
|
|
6229
|
-
// Banner is no longer stored in display - it was streamed as content
|
|
6230
|
-
// Model/provider changes are visible in the control bar
|
|
6231
6279
|
if (!this.isProcessing) {
|
|
6232
6280
|
this.setIdleStatus();
|
|
6233
6281
|
}
|
|
6234
|
-
this.
|
|
6282
|
+
this.refreshStatusLine(true);
|
|
6235
6283
|
}
|
|
6236
6284
|
providerLabel(id) {
|
|
6237
6285
|
return PROVIDER_LABELS[id] ?? id;
|
|
@@ -6338,7 +6386,7 @@ Return ONLY JSON array:
|
|
|
6338
6386
|
// Rebuild agent with new provider
|
|
6339
6387
|
if (this.rebuildAgent()) {
|
|
6340
6388
|
this.persistSessionPreference();
|
|
6341
|
-
this.
|
|
6389
|
+
this.refreshSessionContext();
|
|
6342
6390
|
display.showInfo(`Switched from ${this.providerLabel(oldProvider)}/${oldModel} to ${match.label}/${defaultModel.id}`);
|
|
6343
6391
|
this.resetChatBoxAfterModelSwap();
|
|
6344
6392
|
}
|