erosolar-cli 1.7.410 โ 1.7.412
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/README.md +6 -6
- package/dist/StringUtils.d.ts +1 -4
- package/dist/StringUtils.d.ts.map +1 -1
- package/dist/StringUtils.js +2 -8
- package/dist/StringUtils.js.map +1 -1
- package/dist/browser/BrowserSessionManager.d.ts +1 -3
- package/dist/browser/BrowserSessionManager.d.ts.map +1 -1
- package/dist/browser/BrowserSessionManager.js +4 -24
- package/dist/browser/BrowserSessionManager.js.map +1 -1
- package/dist/capabilities/askUserCapability.d.ts.map +1 -1
- package/dist/capabilities/askUserCapability.js +64 -10
- package/dist/capabilities/askUserCapability.js.map +1 -1
- package/dist/capabilities/toolRegistry.d.ts +2 -0
- package/dist/capabilities/toolRegistry.d.ts.map +1 -1
- package/dist/capabilities/toolRegistry.js +40 -5
- package/dist/capabilities/toolRegistry.js.map +1 -1
- package/dist/contracts/agent-profiles.schema.json +5 -5
- package/dist/contracts/agent-schemas.json +6 -16
- package/dist/contracts/schemas/agent.schema.json +1 -5
- package/dist/contracts/schemas/tool-selection.schema.json +1 -7
- package/dist/contracts/tools.schema.json +80 -207
- package/dist/contracts/unified-schema.json +4 -5
- package/dist/contracts/v1/agent.d.ts +0 -3
- package/dist/contracts/v1/agent.d.ts.map +1 -1
- package/dist/contracts/v1/provider.d.ts +1 -2
- package/dist/contracts/v1/provider.d.ts.map +1 -1
- package/dist/contracts/v1/toolAccess.d.ts +1 -1
- package/dist/contracts/v1/toolAccess.d.ts.map +1 -1
- package/dist/core/agent.d.ts +1 -7
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +2 -131
- package/dist/core/agent.js.map +1 -1
- package/dist/core/alphaZeroEngine.d.ts +0 -8
- package/dist/core/alphaZeroEngine.d.ts.map +1 -1
- package/dist/core/alphaZeroEngine.js +35 -149
- package/dist/core/alphaZeroEngine.js.map +1 -1
- package/dist/core/alphaZeroOrchestrator.d.ts +0 -17
- package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
- package/dist/core/alphaZeroOrchestrator.js +8 -95
- package/dist/core/alphaZeroOrchestrator.js.map +1 -1
- package/dist/core/claudeCodeFeatures.d.ts +2 -1
- package/dist/core/claudeCodeFeatures.d.ts.map +1 -1
- package/dist/core/claudeCodeFeatures.js +2 -1
- package/dist/core/claudeCodeFeatures.js.map +1 -1
- package/dist/core/cliTestHarness.d.ts +0 -5
- package/dist/core/cliTestHarness.d.ts.map +1 -1
- package/dist/core/cliTestHarness.js +3 -14
- package/dist/core/cliTestHarness.js.map +1 -1
- package/dist/core/contextManager.d.ts +0 -30
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +5 -87
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/contextWindow.d.ts +4 -4
- package/dist/core/contextWindow.js +9 -9
- package/dist/core/contextWindow.js.map +1 -1
- package/dist/core/modelDiscovery.js +3 -3
- package/dist/core/modelDiscovery.js.map +1 -1
- package/dist/core/preferences.d.ts +2 -3
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +11 -18
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/secretStore.d.ts.map +1 -1
- package/dist/core/secretStore.js +31 -0
- package/dist/core/secretStore.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 -64
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +0 -17
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +6 -22
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/plugins/providers/google/index.js +3 -2
- package/dist/plugins/providers/google/index.js.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.js +6 -60
- package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +6 -27
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +32 -98
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +733 -1645
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +41 -15
- package/dist/shell/shellApp.js.map +1 -1
- package/dist/shell/systemPrompt.d.ts.map +1 -1
- package/dist/shell/systemPrompt.js +0 -1
- package/dist/shell/systemPrompt.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +21 -85
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +62 -519
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/shell/terminalInputAdapter.d.ts +16 -37
- package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
- package/dist/shell/terminalInputAdapter.js +22 -44
- package/dist/shell/terminalInputAdapter.js.map +1 -1
- package/dist/shell/updateManager.d.ts.map +1 -1
- package/dist/shell/updateManager.js +17 -1
- package/dist/shell/updateManager.js.map +1 -1
- package/dist/tools/buildTools.d.ts.map +1 -1
- package/dist/tools/buildTools.js +76 -19
- package/dist/tools/buildTools.js.map +1 -1
- package/dist/tools/editTools.js +1 -1
- package/dist/tools/editTools.js.map +1 -1
- package/dist/tools/enhancedCodeIntelligenceTools.js +2 -1
- package/dist/tools/enhancedCodeIntelligenceTools.js.map +1 -1
- package/dist/tools/fileTools.js +0 -3
- package/dist/tools/fileTools.js.map +1 -1
- package/dist/tools/frontendTestingTools.js +1 -1
- package/dist/tools/frontendTestingTools.js.map +1 -1
- package/dist/tools/interactionTools.d.ts.map +1 -1
- package/dist/tools/interactionTools.js +82 -15
- package/dist/tools/interactionTools.js.map +1 -1
- package/dist/tools/learnTools.d.ts +0 -2
- package/dist/tools/learnTools.d.ts.map +1 -1
- package/dist/tools/learnTools.js +81 -29
- package/dist/tools/learnTools.js.map +1 -1
- package/dist/tools/localExplore.d.ts.map +1 -1
- package/dist/tools/localExplore.js +1 -0
- package/dist/tools/localExplore.js.map +1 -1
- package/dist/tools/notebookEditTools.js.map +1 -1
- package/dist/tools/repoChecksTools.js +3 -4
- package/dist/tools/repoChecksTools.js.map +1 -1
- package/dist/tools/searchTools.js +0 -4
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/softwareEngineeringTools.d.ts.map +1 -1
- package/dist/tools/softwareEngineeringTools.js +0 -1
- package/dist/tools/softwareEngineeringTools.js.map +1 -1
- package/dist/tools/webTools.d.ts.map +1 -1
- package/dist/tools/webTools.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +13 -52
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +72 -373
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/display.d.ts +38 -19
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +310 -96
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.js +3 -5
- package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts +1 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +6 -11
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/streamingFormatter.d.ts +17 -0
- package/dist/ui/streamingFormatter.d.ts.map +1 -0
- package/dist/ui/streamingFormatter.js +71 -0
- package/dist/ui/streamingFormatter.js.map +1 -0
- package/dist/ui/theme.d.ts +100 -100
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js.map +1 -1
- package/dist/ui/toolDisplay.d.ts.map +1 -1
- package/dist/ui/toolDisplay.js +1 -7
- package/dist/ui/toolDisplay.js.map +1 -1
- package/dist/ui/unified/index.d.ts +3 -2
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +1 -0
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/ui/unified/layout.d.ts +0 -14
- package/dist/ui/unified/layout.d.ts.map +1 -1
- package/dist/ui/unified/layout.js +1 -67
- package/dist/ui/unified/layout.js.map +1 -1
- package/package.json +24 -37
- package/dist/core/alphaZeroConfig.d.ts +0 -11
- package/dist/core/alphaZeroConfig.d.ts.map +0 -1
- package/dist/core/alphaZeroConfig.js +0 -59
- package/dist/core/alphaZeroConfig.js.map +0 -1
- package/dist/core/alphaZeroEnhanced.d.ts +0 -125
- package/dist/core/alphaZeroEnhanced.d.ts.map +0 -1
- package/dist/core/alphaZeroEnhanced.js +0 -386
- package/dist/core/alphaZeroEnhanced.js.map +0 -1
- package/dist/core/autonomousVerification.d.ts +0 -103
- package/dist/core/autonomousVerification.d.ts.map +0 -1
- package/dist/core/autonomousVerification.js +0 -583
- package/dist/core/autonomousVerification.js.map +0 -1
- package/dist/core/offsecAlphaZeroEnhanced.d.ts +0 -98
- package/dist/core/offsecAlphaZeroEnhanced.d.ts.map +0 -1
- package/dist/core/offsecAlphaZeroEnhanced.js +0 -441
- package/dist/core/offsecAlphaZeroEnhanced.js.map +0 -1
- package/dist/core/parallelAgentOrchestrator.d.ts +0 -171
- package/dist/core/parallelAgentOrchestrator.d.ts.map +0 -1
- package/dist/core/parallelAgentOrchestrator.js +0 -459
- package/dist/core/parallelAgentOrchestrator.js.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/tools/detectCommands.d.ts +0 -8
- package/dist/tools/detectCommands.d.ts.map +0 -1
- package/dist/tools/detectCommands.js +0 -183
- package/dist/tools/detectCommands.js.map +0 -1
- package/dist/ui/assistantBlockRenderer.d.ts +0 -30
- package/dist/ui/assistantBlockRenderer.d.ts.map +0 -1
- package/dist/ui/assistantBlockRenderer.js +0 -121
- package/dist/ui/assistantBlockRenderer.js.map +0 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { stdin as input, stdout as output, exit } from 'node:process';
|
|
2
2
|
import { exec } from 'node:child_process';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
|
-
import {
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
5
6
|
import { display } from '../ui/display.js';
|
|
6
|
-
import {
|
|
7
|
-
import { theme
|
|
7
|
+
import { isPlainOutputMode } from '../ui/outputMode.js';
|
|
8
|
+
import { theme } from '../ui/theme.js';
|
|
9
|
+
import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
|
|
8
10
|
import { getContextWindowTokens } from '../core/contextWindow.js';
|
|
9
|
-
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions,
|
|
11
|
+
import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
|
|
10
12
|
import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
|
|
11
13
|
import { getLearningSummary, getRecentLearning, commitLearning, exportAllLearning, getLearningDir, } from '../core/learningPersistence.js';
|
|
12
14
|
import { buildEnabledToolSet, evaluateToolPermissions, getToolToggleOptions, } from '../capabilities/toolRegistry.js';
|
|
13
15
|
import { detectApiKeyError } from '../core/errors/apiKeyErrors.js';
|
|
14
16
|
import { buildWorkspaceContext } from '../workspace.js';
|
|
15
|
-
import { detectBuildCommand, detectTestCommand } from '../tools/detectCommands.js';
|
|
16
17
|
import { buildInteractiveSystemPrompt } from './systemPrompt.js';
|
|
17
18
|
import { getTaskCompletionDetector, resetTaskCompletionDetector, } from './taskCompletionDetector.js';
|
|
18
19
|
import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
|
|
@@ -36,14 +37,11 @@ import { analyzeTokenUsage, discoverModularTargets, getModularStatusDisplay, gen
|
|
|
36
37
|
import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActions, simulateOffsecRollout, formatOffsecStatus, listOffsecRuns, } from '../core/offsecAlphaZero.js';
|
|
37
38
|
import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
|
|
38
39
|
import { TerminalInputAdapter } from './terminalInputAdapter.js';
|
|
39
|
-
import { renderSessionFrame } from '../ui/unified/layout.js';
|
|
40
|
-
import { isUpdateInProgress, maybeOfferCliUpdate } from './updateManager.js';
|
|
41
40
|
import { writeLock } from '../ui/writeLock.js';
|
|
42
41
|
import { enterStreamingMode, exitStreamingMode } from '../ui/globalWriteLock.js';
|
|
43
42
|
import { setGlobalAIEnhancer } from '../tools/localExplore.js';
|
|
44
43
|
import { createProvider } from '../providers/providerFactory.js';
|
|
45
44
|
import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
|
|
46
|
-
import { formatThinkingContent } from '../ui/textHighlighter.js';
|
|
47
45
|
const execAsync = promisify(exec);
|
|
48
46
|
const DROPDOWN_COLORS = [
|
|
49
47
|
theme.primary,
|
|
@@ -113,19 +111,15 @@ export class InteractiveShell {
|
|
|
113
111
|
thinkingMode = 'balanced';
|
|
114
112
|
agentMenu;
|
|
115
113
|
slashCommands;
|
|
116
|
-
bannerSessionState = null;
|
|
117
114
|
statusTracker;
|
|
118
115
|
ui;
|
|
119
116
|
uiAdapter;
|
|
120
117
|
uiUpdates;
|
|
121
|
-
assistantBlocksEnabled;
|
|
122
|
-
assistantBlockRenderer;
|
|
123
118
|
_fileChangeTracker = new FileChangeTracker(); // Reserved for future file tracking features
|
|
124
119
|
alphaZeroMetrics; // Alpha Zero 2 performance tracking
|
|
125
120
|
statusSubscription = null;
|
|
126
121
|
followUpQueue = [];
|
|
127
122
|
isDrainingQueue = false;
|
|
128
|
-
apiKeyGateActive = false;
|
|
129
123
|
activeContextWindowTokens = null;
|
|
130
124
|
latestTokenUsage = { used: null, limit: null };
|
|
131
125
|
planApprovalBridgeRegistered = false;
|
|
@@ -134,9 +128,7 @@ export class InteractiveShell {
|
|
|
134
128
|
sessionPreferences;
|
|
135
129
|
autosaveEnabled;
|
|
136
130
|
autoContinueEnabled;
|
|
137
|
-
verificationEnabled =
|
|
138
|
-
alphaZeroModeEnabled;
|
|
139
|
-
alphaZeroVerificationSnapshot = null;
|
|
131
|
+
verificationEnabled = false;
|
|
140
132
|
editGuardMode = 'display-edits';
|
|
141
133
|
pendingPermissionInput = null;
|
|
142
134
|
pendingHistoryLoad = null;
|
|
@@ -148,51 +140,36 @@ export class InteractiveShell {
|
|
|
148
140
|
customCommandMap;
|
|
149
141
|
sessionRestoreConfig;
|
|
150
142
|
_enabledPlugins;
|
|
151
|
-
|
|
143
|
+
// Cached provider status for unified status bar display after streaming
|
|
144
|
+
cachedProviderStatus = [];
|
|
152
145
|
// Auto-test tracking
|
|
153
146
|
autoTestInFlight = false;
|
|
154
147
|
// AlphaZero learning tracking
|
|
155
148
|
currentTaskType = 'general';
|
|
156
149
|
currentToolCalls = [];
|
|
157
150
|
lastUserQuery = '';
|
|
151
|
+
lastAssistantResponse = null;
|
|
158
152
|
lastFailure = null;
|
|
159
153
|
lastAutoTestRun = null;
|
|
160
|
-
runIdCounter = 0;
|
|
161
|
-
lastRunLog = null;
|
|
162
|
-
lastReflectedRunId = null;
|
|
163
|
-
skipNextAutoReflection = false;
|
|
164
|
-
alphaZeroAutoImproveActive = false;
|
|
165
|
-
alphaZeroAutoImproveIterations = 0;
|
|
166
|
-
alphaZeroAutoImproveMaxIterations = 6;
|
|
167
154
|
// Auto-build tracking
|
|
168
155
|
autoBuildInFlight = false;
|
|
156
|
+
autoBuildPromise = null;
|
|
169
157
|
lastAutoBuildRun = null;
|
|
158
|
+
lastBuildSucceededAt = null;
|
|
170
159
|
// Offsec AlphaZero tracking
|
|
171
160
|
offsecRunId = null;
|
|
172
161
|
// Streaming UX tracking
|
|
173
162
|
streamingHeartbeatStart = null;
|
|
174
163
|
streamingHeartbeatFrame = 0;
|
|
175
164
|
streamingStatusLabel = null;
|
|
176
|
-
streamingStatusBase = null;
|
|
177
|
-
streamingStatusDetail = null;
|
|
178
165
|
lastStreamingElapsedSeconds = null; // Preserve final elapsed time
|
|
166
|
+
streamingFormatter = null;
|
|
179
167
|
statusLineState = null;
|
|
180
168
|
statusMessageOverride = null;
|
|
181
|
-
latestThoughtSummary = null;
|
|
182
|
-
hasShownThoughtProcess = false;
|
|
183
169
|
promptRefreshTimer = null;
|
|
184
170
|
launchPaletteShown = false;
|
|
185
171
|
version;
|
|
186
172
|
alternateScreenEnabled;
|
|
187
|
-
inputInitialized = false;
|
|
188
|
-
assistantStreamActive = false;
|
|
189
|
-
assistantStreamPhase = null;
|
|
190
|
-
assistantStreamHeaderShown = false;
|
|
191
|
-
assistantStreamBuffer = '';
|
|
192
|
-
assistantStreamMetadata;
|
|
193
|
-
assistantStreamHadContent = false;
|
|
194
|
-
lastRequestStartedAt = null;
|
|
195
|
-
lastToolSummaryRenderedAt = null;
|
|
196
173
|
constructor(config) {
|
|
197
174
|
this.profile = config.profile;
|
|
198
175
|
this.profileLabel = config.profileLabel;
|
|
@@ -204,10 +181,10 @@ export class InteractiveShell {
|
|
|
204
181
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
205
182
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
206
183
|
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
207
|
-
|
|
184
|
+
const featureFlags = loadFeatureFlags();
|
|
185
|
+
this.verificationEnabled = featureFlags.verification === true;
|
|
208
186
|
this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
|
|
209
187
|
this._enabledPlugins = config.enabledPlugins ?? [];
|
|
210
|
-
this.assistantBlocksEnabled = config.assistantBlocksEnabled ?? true;
|
|
211
188
|
this.version = config.version ?? '0.0.0';
|
|
212
189
|
// Alternate screen disabled - use terminal-native mode for proper scrollback and text selection
|
|
213
190
|
this.alternateScreenEnabled = false;
|
|
@@ -220,11 +197,6 @@ export class InteractiveShell {
|
|
|
220
197
|
reasoningEffort: config.initialModel.reasoningEffort,
|
|
221
198
|
};
|
|
222
199
|
this.applyPresetReasoningDefaults();
|
|
223
|
-
// The welcome banner only includes model + provider on launch, so mark that as the initial state.
|
|
224
|
-
this.bannerSessionState = {
|
|
225
|
-
model: this.sessionState.model,
|
|
226
|
-
provider: this.sessionState.provider,
|
|
227
|
-
};
|
|
228
200
|
this.agentMenu = config.agentSelection ?? null;
|
|
229
201
|
this.slashCommands = [...BASE_SLASH_COMMANDS];
|
|
230
202
|
if (this.agentMenu) {
|
|
@@ -256,21 +228,11 @@ export class InteractiveShell {
|
|
|
256
228
|
description: 'Show available and loaded plugins',
|
|
257
229
|
category: 'configuration',
|
|
258
230
|
});
|
|
259
|
-
this.slashCommands.push({
|
|
260
|
-
command: '/contextlog',
|
|
261
|
-
description: 'Show recent context compaction summaries',
|
|
262
|
-
category: 'context',
|
|
263
|
-
});
|
|
264
231
|
this.slashCommands.push({
|
|
265
232
|
command: '/offsec',
|
|
266
233
|
description: 'AlphaZero offensive security run (start/status/next)',
|
|
267
234
|
category: 'automation',
|
|
268
235
|
});
|
|
269
|
-
this.slashCommands.push({
|
|
270
|
-
command: '/alphazero',
|
|
271
|
-
description: 'Toggle AlphaZero RL mode with full-cycle verification',
|
|
272
|
-
category: 'mode',
|
|
273
|
-
});
|
|
274
236
|
this.statusTracker = config.statusTracker;
|
|
275
237
|
this.ui = config.ui;
|
|
276
238
|
this.uiAdapter = config.ui.adapter;
|
|
@@ -282,11 +244,7 @@ export class InteractiveShell {
|
|
|
282
244
|
});
|
|
283
245
|
// Set up tool status callback to update status during tool execution
|
|
284
246
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
285
|
-
|
|
286
|
-
if (statusText) {
|
|
287
|
-
this.terminalInput.recordRecentAction(`[tool] ${statusText}`);
|
|
288
|
-
}
|
|
289
|
-
this.updateStatusMessage(statusText, { logRecent: false });
|
|
247
|
+
this.updateStatusMessage(status ?? null);
|
|
290
248
|
});
|
|
291
249
|
this.skillRepository = new SkillRepository({
|
|
292
250
|
workingDir: this.workingDir,
|
|
@@ -306,19 +264,14 @@ export class InteractiveShell {
|
|
|
306
264
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
307
265
|
onToggleAutoContinue: () => this.toggleAutoContinueMode(),
|
|
308
266
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
309
|
-
onToggleAlphaZero: () => this.toggleAlphaZeroMode('shortcut'),
|
|
310
267
|
onClearContext: () => this.handleClearContext(),
|
|
311
268
|
});
|
|
312
|
-
this.assistantBlockRenderer = this.assistantBlocksEnabled
|
|
313
|
-
? new AssistantBlockRenderer({
|
|
314
|
-
write: (text) => this.writeAssistantBlock(text),
|
|
315
|
-
updates: this.uiUpdates,
|
|
316
|
-
})
|
|
317
|
-
: null;
|
|
318
269
|
// Initialize Alpha Zero 2 metrics tracking
|
|
319
270
|
this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
|
|
320
271
|
this.setupStatusTracking();
|
|
321
272
|
this.refreshContextGauge();
|
|
273
|
+
// Start terminal input (sets up handlers)
|
|
274
|
+
this.terminalInput.start();
|
|
322
275
|
// Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
|
|
323
276
|
this.registerPlanApprovalBridge();
|
|
324
277
|
// Capture display output into the scrollback/chat log so system messages
|
|
@@ -333,12 +286,12 @@ export class InteractiveShell {
|
|
|
333
286
|
else if (output.isTTY) {
|
|
334
287
|
this.terminalInput.clearScreen();
|
|
335
288
|
}
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
this.terminalInput.
|
|
289
|
+
// Render chat box immediately using the streaming UI lifecycle
|
|
290
|
+
this.refreshControlBar();
|
|
291
|
+
this.terminalInput.forceRender();
|
|
339
292
|
this.rebuildAgent();
|
|
340
293
|
this.setupHandlers();
|
|
341
|
-
this.
|
|
294
|
+
this.refreshSessionContext();
|
|
342
295
|
// Subscribe to parallel agent manager events
|
|
343
296
|
this.setupParallelAgentTracking();
|
|
344
297
|
}
|
|
@@ -350,13 +303,7 @@ export class InteractiveShell {
|
|
|
350
303
|
this.parallelAgentDisplayLines = manager.formatDisplay();
|
|
351
304
|
// Trigger UI refresh if streaming
|
|
352
305
|
if (this.streamingHeartbeatStart) {
|
|
353
|
-
this.
|
|
354
|
-
lane: 'stream',
|
|
355
|
-
mode: ['streaming', 'processing'],
|
|
356
|
-
coalesceKey: 'parallel-agents',
|
|
357
|
-
description: 'parallel agent status',
|
|
358
|
-
run: () => this.displayParallelAgents(),
|
|
359
|
-
});
|
|
306
|
+
this.displayParallelAgents();
|
|
360
307
|
}
|
|
361
308
|
};
|
|
362
309
|
manager.on('agent:started', updateDisplay);
|
|
@@ -427,56 +374,13 @@ export class InteractiveShell {
|
|
|
427
374
|
this.sessionResumeNotice = null;
|
|
428
375
|
}
|
|
429
376
|
async start(initialPrompt) {
|
|
430
|
-
await this.runStartupUpdatePrompt();
|
|
431
|
-
this.ensureInputInitialized();
|
|
432
377
|
if (initialPrompt) {
|
|
433
378
|
await this.processInputBlock(initialPrompt);
|
|
434
379
|
return;
|
|
435
380
|
}
|
|
436
381
|
this.showLaunchCommandPalette();
|
|
437
382
|
// Ensure the terminal input is visible
|
|
438
|
-
this.
|
|
439
|
-
}
|
|
440
|
-
async runStartupUpdatePrompt() {
|
|
441
|
-
if (this.version) {
|
|
442
|
-
try {
|
|
443
|
-
await maybeOfferCliUpdate(this.version);
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
// Ignore update check failures at startup
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
this.showStartupSecretGuidance();
|
|
450
|
-
}
|
|
451
|
-
ensureInputInitialized() {
|
|
452
|
-
if (this.inputInitialized) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
this.terminalInput.start();
|
|
456
|
-
this.refreshControlBar();
|
|
457
|
-
this.renderPromptArea(true);
|
|
458
|
-
this.inputInitialized = true;
|
|
459
|
-
}
|
|
460
|
-
showStartupSecretGuidance() {
|
|
461
|
-
const definitions = listSecretDefinitions();
|
|
462
|
-
const providerSecret = definitions.find((definition) => definition.providers.includes(this.sessionState.provider));
|
|
463
|
-
const missingProviderKey = providerSecret ? !getSecretValue(providerSecret.id) : false;
|
|
464
|
-
const hasSearchKey = Boolean(getSecretValue('TAVILY_API_KEY')) ||
|
|
465
|
-
Boolean(getSecretValue('BRAVE_SEARCH_API_KEY')) ||
|
|
466
|
-
Boolean(getSecretValue('SERPAPI_API_KEY'));
|
|
467
|
-
if (!missingProviderKey && hasSearchKey) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const lines = [];
|
|
471
|
-
lines.push(theme.gradient.primary('Quick setup needed:'));
|
|
472
|
-
if (missingProviderKey && providerSecret) {
|
|
473
|
-
lines.push(`${theme.primary('โข')} Set ${providerSecret.label} to use ${this.providerLabel(this.sessionState.provider)}.`);
|
|
474
|
-
}
|
|
475
|
-
if (!hasSearchKey) {
|
|
476
|
-
lines.push(`${theme.primary('โข')} Add a web search API key (Tavily recommended) for web tools.`);
|
|
477
|
-
}
|
|
478
|
-
lines.push(theme.ui.muted('Run /secrets to configure keys now. Stored values override env vars.'));
|
|
479
|
-
display.showSystemMessage(lines.join('\n'));
|
|
383
|
+
this.terminalInput.render();
|
|
480
384
|
}
|
|
481
385
|
showLaunchCommandPalette() {
|
|
482
386
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -487,10 +391,6 @@ export class InteractiveShell {
|
|
|
487
391
|
* TerminalInputAdapter submit handler
|
|
488
392
|
*/
|
|
489
393
|
processInput(text) {
|
|
490
|
-
// Guard: Don't process input if CLI update is in progress (we're about to exit)
|
|
491
|
-
if (isUpdateInProgress()) {
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
394
|
const approved = this.resolveEditPermission(text);
|
|
495
395
|
if (!approved) {
|
|
496
396
|
this.handleInputChange('');
|
|
@@ -506,10 +406,6 @@ export class InteractiveShell {
|
|
|
506
406
|
* TerminalInputAdapter queue handler (streaming mode)
|
|
507
407
|
*/
|
|
508
408
|
handleQueuedInput(text) {
|
|
509
|
-
// Guard: Don't queue input if CLI update is in progress (we're about to exit)
|
|
510
|
-
if (isUpdateInProgress()) {
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
409
|
if (this.isExitCommand(text)) {
|
|
514
410
|
// Allow immediate exits even while streaming
|
|
515
411
|
this.terminalInput.dequeue();
|
|
@@ -534,8 +430,7 @@ export class InteractiveShell {
|
|
|
534
430
|
// Keep adapter queue trimmed so hints stay accurate
|
|
535
431
|
this.terminalInput.dequeue();
|
|
536
432
|
this.followUpQueue.push({ type: 'request', text });
|
|
537
|
-
|
|
538
|
-
this.terminalInput.recordRecentAction(`queued: ${text}`);
|
|
433
|
+
display.showInfo(`Queued: "${text}"`);
|
|
539
434
|
this.refreshQueueIndicators();
|
|
540
435
|
this.scheduleQueueProcessing();
|
|
541
436
|
this.handleInputChange('');
|
|
@@ -574,7 +469,6 @@ export class InteractiveShell {
|
|
|
574
469
|
// Mode toggles
|
|
575
470
|
'/thinking',
|
|
576
471
|
'/autocontinue',
|
|
577
|
-
'/alphazero',
|
|
578
472
|
// Discovery and plugins
|
|
579
473
|
'/local', '/discover',
|
|
580
474
|
'/plugins',
|
|
@@ -596,30 +490,15 @@ export class InteractiveShell {
|
|
|
596
490
|
* Execute a command immediately during streaming.
|
|
597
491
|
*/
|
|
598
492
|
async executeImmediateCommand(text) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
this.
|
|
602
|
-
// Surface a lightweight inline status while the command runs
|
|
603
|
-
const previousOverride = this.statusMessageOverride;
|
|
604
|
-
this.statusMessageOverride = `Running ${label}`;
|
|
605
|
-
this.refreshStatusLine(true);
|
|
606
|
-
try {
|
|
607
|
-
await this.processSlashCommand(text);
|
|
608
|
-
}
|
|
609
|
-
finally {
|
|
610
|
-
this.statusMessageOverride = previousOverride;
|
|
611
|
-
this.refreshStatusLine(true);
|
|
612
|
-
}
|
|
493
|
+
// Pause streaming display briefly to show command output
|
|
494
|
+
display.showInfo(`Running command during stream: ${text}`);
|
|
495
|
+
await this.processSlashCommand(text);
|
|
613
496
|
}
|
|
614
497
|
/**
|
|
615
498
|
* TerminalInputAdapter change handler
|
|
616
499
|
*/
|
|
617
500
|
handleInputChange(text) {
|
|
618
|
-
const previous = this.currentInput;
|
|
619
501
|
this.currentInput = text;
|
|
620
|
-
if (previous !== text) {
|
|
621
|
-
this.terminalInput.clearInlineCommandPanel();
|
|
622
|
-
}
|
|
623
502
|
if (text.length > 0) {
|
|
624
503
|
this.resetCtrlCSequence();
|
|
625
504
|
}
|
|
@@ -641,7 +520,7 @@ export class InteractiveShell {
|
|
|
641
520
|
display.showSystemMessage('โ๏ธ Display edits mode enabled.');
|
|
642
521
|
}
|
|
643
522
|
}
|
|
644
|
-
this.
|
|
523
|
+
this.terminalInput.render();
|
|
645
524
|
}
|
|
646
525
|
toggleVerificationMode() {
|
|
647
526
|
this.setVerificationMode(!this.verificationEnabled, 'shortcut');
|
|
@@ -654,8 +533,8 @@ export class InteractiveShell {
|
|
|
654
533
|
return;
|
|
655
534
|
}
|
|
656
535
|
const message = enabled
|
|
657
|
-
? 'โ
Verification on. Auto-tests will run after edits. (Ctrl+Shift+V to toggle)'
|
|
658
|
-
: 'โญ๏ธ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle)';
|
|
536
|
+
? 'โ
Verification on for this session. Auto-tests will run after edits. (Ctrl+Shift+V to toggle; use /features verification on and restart to make this the default)'
|
|
537
|
+
: 'โญ๏ธ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle; defaults to off unless enabled via /features)';
|
|
659
538
|
display.showSystemMessage(message);
|
|
660
539
|
}
|
|
661
540
|
toggleAutoContinueMode() {
|
|
@@ -678,54 +557,21 @@ export class InteractiveShell {
|
|
|
678
557
|
: 'The model will not be auto-prompted to continue.') +
|
|
679
558
|
' Toggle with Ctrl+Shift+C.');
|
|
680
559
|
}
|
|
681
|
-
toggleAlphaZeroMode(source = 'shortcut') {
|
|
682
|
-
this.setAlphaZeroMode(!this.alphaZeroModeEnabled, source);
|
|
683
|
-
}
|
|
684
|
-
setAlphaZeroMode(enabled, source) {
|
|
685
|
-
const changed = this.alphaZeroModeEnabled !== enabled;
|
|
686
|
-
this.alphaZeroModeEnabled = enabled;
|
|
687
|
-
saveSessionPreferences({ alphaZeroMode: this.alphaZeroModeEnabled });
|
|
688
|
-
// Force verification on while AlphaZero mode is active to guarantee deep checks
|
|
689
|
-
if (enabled) {
|
|
690
|
-
if (this.alphaZeroVerificationSnapshot === null) {
|
|
691
|
-
this.alphaZeroVerificationSnapshot = this.verificationEnabled;
|
|
692
|
-
}
|
|
693
|
-
if (!this.verificationEnabled) {
|
|
694
|
-
this.setVerificationMode(true, 'command');
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
else if (this.alphaZeroVerificationSnapshot !== null) {
|
|
698
|
-
this.setVerificationMode(this.alphaZeroVerificationSnapshot, 'command');
|
|
699
|
-
this.alphaZeroVerificationSnapshot = null;
|
|
700
|
-
}
|
|
701
|
-
this.refreshControlBar();
|
|
702
|
-
if (!changed && source === 'shortcut') {
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
if (enabled) {
|
|
706
|
-
display.showSystemMessage('โ AlphaZero RL mode enabled. Difficult prompts will use the duel/self-critique playbook with full lifecycle verification.');
|
|
707
|
-
}
|
|
708
|
-
else {
|
|
709
|
-
display.showInfo('AlphaZero RL mode disabled. Returning to standard flow.');
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
560
|
/**
|
|
713
|
-
* Cycle through thinking modes (
|
|
561
|
+
* Cycle through thinking modes (Tab shortcut).
|
|
714
562
|
*/
|
|
715
563
|
cycleThinkingMode() {
|
|
716
|
-
const
|
|
717
|
-
const currentIndex = modes.indexOf(this.thinkingMode);
|
|
718
|
-
const nextIndex = (currentIndex + 1) % modes.length;
|
|
719
|
-
const nextMode = modes[nextIndex];
|
|
564
|
+
const nextMode = this.thinkingMode === 'balanced' ? 'extended' : 'balanced';
|
|
720
565
|
this.thinkingMode = nextMode;
|
|
721
566
|
saveSessionPreferences({ thinkingMode: this.thinkingMode });
|
|
722
567
|
this.refreshControlBar();
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
568
|
+
const headline = nextMode === 'extended'
|
|
569
|
+
? `${theme.info('Thinking on')} (Tab to toggle)`
|
|
570
|
+
: `${theme.info('Thinking off')} (Tab to toggle)`;
|
|
571
|
+
const detail = nextMode === 'extended'
|
|
572
|
+
? 'Longer reasoning enabled; expect extra usage for deeper answers.'
|
|
573
|
+
: 'Balanced (default) reasoning restored.';
|
|
574
|
+
display.showSystemMessage([headline, theme.ui.muted(detail)].join('\n'));
|
|
729
575
|
}
|
|
730
576
|
/**
|
|
731
577
|
* Handle context clear/compact request (Alt+X keyboard shortcut).
|
|
@@ -744,27 +590,17 @@ export class InteractiveShell {
|
|
|
744
590
|
*/
|
|
745
591
|
async performContextCompaction() {
|
|
746
592
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
593
|
+
// For now, just clear the history and show a message
|
|
594
|
+
// A full implementation would summarize the conversation
|
|
595
|
+
const oldLength = this.cachedHistory.length;
|
|
749
596
|
if (oldLength === 0) {
|
|
750
597
|
display.showInfo('Context is already empty.');
|
|
751
598
|
return;
|
|
752
599
|
}
|
|
753
|
-
//
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const tail = keepCount > 0 ? conversationalMessages.slice(-keepCount) : [];
|
|
758
|
-
const compacted = systemMessage ? [systemMessage, ...tail] : tail;
|
|
759
|
-
this.cachedHistory = compacted;
|
|
760
|
-
if (this.agent) {
|
|
761
|
-
this.agent.loadHistory(compacted);
|
|
762
|
-
}
|
|
763
|
-
// Reset tracked context usage so tools and status bars stay in sync post-compaction.
|
|
764
|
-
this.latestTokenUsage = { used: null, limit: this.activeContextWindowTokens ?? null };
|
|
765
|
-
this.updateContextUsage(0, CONTEXT_AUTOCOMPACT_PERCENT);
|
|
766
|
-
this.lastContextWarningLevel = null;
|
|
767
|
-
display.showSuccess(`Context compacted: ${oldLength} messages reduced to ${compacted.length}. ` +
|
|
600
|
+
// Keep the last few messages for continuity
|
|
601
|
+
const keepCount = Math.min(4, oldLength);
|
|
602
|
+
this.cachedHistory = this.cachedHistory.slice(-keepCount);
|
|
603
|
+
display.showSuccess(`Context compacted: ${oldLength} messages reduced to ${keepCount}. ` +
|
|
768
604
|
`Context usage reset. Continue your conversation.`);
|
|
769
605
|
this.refreshControlBar();
|
|
770
606
|
}
|
|
@@ -796,7 +632,7 @@ export class InteractiveShell {
|
|
|
796
632
|
if (['n', 'no', 'cancel', '/cancel'].includes(lower)) {
|
|
797
633
|
this.pendingPermissionInput = null;
|
|
798
634
|
display.showInfo('Request cancelled.');
|
|
799
|
-
this.
|
|
635
|
+
this.terminalInput.render();
|
|
800
636
|
return null;
|
|
801
637
|
}
|
|
802
638
|
// Treat any other input as a replacement request that also needs confirmation
|
|
@@ -814,7 +650,7 @@ export class InteractiveShell {
|
|
|
814
650
|
]
|
|
815
651
|
.filter(Boolean)
|
|
816
652
|
.join('\n'));
|
|
817
|
-
this.
|
|
653
|
+
this.terminalInput.render();
|
|
818
654
|
}
|
|
819
655
|
/**
|
|
820
656
|
* Handle Ctrl+C presses in three stages:
|
|
@@ -828,8 +664,8 @@ export class InteractiveShell {
|
|
|
828
664
|
if (this.ctrlCPressCount === 1) {
|
|
829
665
|
this.clearChatInput();
|
|
830
666
|
const prefix = hadBuffer ? 'Input cleared.' : 'Nothing to clear.';
|
|
831
|
-
display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI
|
|
832
|
-
this.
|
|
667
|
+
display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI.`);
|
|
668
|
+
this.terminalInput.render();
|
|
833
669
|
return;
|
|
834
670
|
}
|
|
835
671
|
if (this.ctrlCPressCount === 2) {
|
|
@@ -841,10 +677,10 @@ export class InteractiveShell {
|
|
|
841
677
|
pauseAiExecution() {
|
|
842
678
|
if (this.isProcessing && this.agent) {
|
|
843
679
|
this.agent.requestCancellation();
|
|
844
|
-
display.showWarning('Pausing AI execution...
|
|
680
|
+
display.showWarning('Pausing AI execution...');
|
|
845
681
|
return;
|
|
846
682
|
}
|
|
847
|
-
display.showInfo('No active AI execution to pause.
|
|
683
|
+
display.showInfo('No active AI execution to pause.');
|
|
848
684
|
}
|
|
849
685
|
resetCtrlCSequence() {
|
|
850
686
|
this.ctrlCPressCount = 0;
|
|
@@ -877,7 +713,7 @@ export class InteractiveShell {
|
|
|
877
713
|
: null;
|
|
878
714
|
// Stop any active spinner to prevent process hang
|
|
879
715
|
display.stopThinking(false);
|
|
880
|
-
this.stopStreamingHeartbeat(
|
|
716
|
+
this.stopStreamingHeartbeat();
|
|
881
717
|
this.uiUpdates.dispose();
|
|
882
718
|
this.clearPromptRefreshTimer();
|
|
883
719
|
this.teardownStatusTracking();
|
|
@@ -886,9 +722,7 @@ export class InteractiveShell {
|
|
|
886
722
|
// Clear any pending cleanup to prevent hanging
|
|
887
723
|
this.pendingCleanup = null;
|
|
888
724
|
// Reset terminal state before disposing adapters
|
|
889
|
-
|
|
890
|
-
this.terminalInput.exitStreamingScrollRegion({ skipRender: true });
|
|
891
|
-
}
|
|
725
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
892
726
|
if (this.alternateScreenEnabled) {
|
|
893
727
|
this.terminalInput.exitAlternateScreen();
|
|
894
728
|
}
|
|
@@ -901,9 +735,7 @@ export class InteractiveShell {
|
|
|
901
735
|
if (scrollbackSnapshot && scrollbackSnapshot.length > 0) {
|
|
902
736
|
this.restoreScrollbackSnapshot(scrollbackSnapshot);
|
|
903
737
|
}
|
|
904
|
-
|
|
905
|
-
console.log(theme.ui.muted(' Goodbye! ยท support@ero.solar'));
|
|
906
|
-
console.log(theme.ui.muted('โ'.repeat(44)));
|
|
738
|
+
display.stream(`\n${theme.ui.muted('Session closed.')}\n`);
|
|
907
739
|
exit(0);
|
|
908
740
|
}
|
|
909
741
|
restoreScrollbackSnapshot(lines) {
|
|
@@ -913,8 +745,8 @@ export class InteractiveShell {
|
|
|
913
745
|
const transcript = lines.join('\n');
|
|
914
746
|
const separator = theme.ui.muted('โ'.repeat(44));
|
|
915
747
|
const header = theme.ui.muted('Restored scrollback from this session:');
|
|
916
|
-
//
|
|
917
|
-
|
|
748
|
+
// Stream the restored transcript so it stays within the in-stream UI flow
|
|
749
|
+
display.stream(`\n${separator}\n${header}\n${transcript}\n${separator}\n`);
|
|
918
750
|
}
|
|
919
751
|
/**
|
|
920
752
|
* Wire the planning tool suite to the interactive plan approval UI so ProposePlan behaves like Codex CLI.
|
|
@@ -931,11 +763,8 @@ export class InteractiveShell {
|
|
|
931
763
|
/**
|
|
932
764
|
* Update status bar message
|
|
933
765
|
*/
|
|
934
|
-
updateStatusMessage(message
|
|
766
|
+
updateStatusMessage(message) {
|
|
935
767
|
this.statusMessageOverride = message;
|
|
936
|
-
if (message && options.logRecent !== false) {
|
|
937
|
-
this.terminalInput.recordRecentAction(`[status] ${message}`);
|
|
938
|
-
}
|
|
939
768
|
// During streaming we still want the spinner prefix; when idle force a fast refresh.
|
|
940
769
|
this.refreshStatusLine(!this.isProcessing);
|
|
941
770
|
}
|
|
@@ -947,26 +776,26 @@ export class InteractiveShell {
|
|
|
947
776
|
const trimmed = input.trim();
|
|
948
777
|
if (!trimmed) {
|
|
949
778
|
display.showWarning('Enter a number, "save", "defaults", or "cancel".');
|
|
950
|
-
this.
|
|
779
|
+
this.terminalInput.render();
|
|
951
780
|
return;
|
|
952
781
|
}
|
|
953
782
|
const normalized = trimmed.toLowerCase();
|
|
954
783
|
if (normalized === 'cancel') {
|
|
955
784
|
this.pendingInteraction = null;
|
|
956
785
|
display.showInfo('Tool selection cancelled.');
|
|
957
|
-
this.
|
|
786
|
+
this.terminalInput.render();
|
|
958
787
|
return;
|
|
959
788
|
}
|
|
960
789
|
if (normalized === 'defaults') {
|
|
961
790
|
pending.selection = buildEnabledToolSet(null);
|
|
962
791
|
this.renderToolMenu(pending);
|
|
963
|
-
this.
|
|
792
|
+
this.terminalInput.render();
|
|
964
793
|
return;
|
|
965
794
|
}
|
|
966
795
|
if (normalized === 'save') {
|
|
967
796
|
await this.persistToolSelection(pending);
|
|
968
797
|
this.pendingInteraction = null;
|
|
969
|
-
this.
|
|
798
|
+
this.terminalInput.render();
|
|
970
799
|
return;
|
|
971
800
|
}
|
|
972
801
|
const choice = Number.parseInt(trimmed, 10);
|
|
@@ -976,19 +805,24 @@ export class InteractiveShell {
|
|
|
976
805
|
display.showWarning('That option is not available.');
|
|
977
806
|
}
|
|
978
807
|
else {
|
|
979
|
-
if (
|
|
980
|
-
|
|
808
|
+
if (option.locked) {
|
|
809
|
+
display.showInfo('The default tool package is always enabled and cannot be toggled.');
|
|
981
810
|
}
|
|
982
811
|
else {
|
|
983
|
-
pending.selection.
|
|
812
|
+
if (pending.selection.has(option.id)) {
|
|
813
|
+
pending.selection.delete(option.id);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
pending.selection.add(option.id);
|
|
817
|
+
}
|
|
984
818
|
}
|
|
985
819
|
this.renderToolMenu(pending);
|
|
986
820
|
}
|
|
987
|
-
this.
|
|
821
|
+
this.terminalInput.render();
|
|
988
822
|
return;
|
|
989
823
|
}
|
|
990
824
|
display.showWarning('Enter a number, "save", "defaults", or "cancel".');
|
|
991
|
-
this.
|
|
825
|
+
this.terminalInput.render();
|
|
992
826
|
}
|
|
993
827
|
async persistToolSelection(interaction) {
|
|
994
828
|
if (setsEqual(interaction.selection, interaction.initialSelection)) {
|
|
@@ -998,10 +832,11 @@ export class InteractiveShell {
|
|
|
998
832
|
const defaults = buildEnabledToolSet(null);
|
|
999
833
|
if (setsEqual(interaction.selection, defaults)) {
|
|
1000
834
|
clearToolSettings();
|
|
1001
|
-
display.showInfo('Tool settings cleared. Defaults will be used on the next launch.
|
|
835
|
+
display.showInfo('Tool settings cleared. Defaults will be used on the next launch.');
|
|
1002
836
|
return;
|
|
1003
837
|
}
|
|
1004
838
|
const ordered = interaction.options
|
|
839
|
+
.filter((option) => !option.locked)
|
|
1005
840
|
.map((option) => option.id)
|
|
1006
841
|
.filter((id) => interaction.selection.has(id));
|
|
1007
842
|
saveToolSettings({ enabledTools: ordered });
|
|
@@ -1015,36 +850,36 @@ export class InteractiveShell {
|
|
|
1015
850
|
if (!this.agentMenu) {
|
|
1016
851
|
this.pendingInteraction = null;
|
|
1017
852
|
display.showWarning('Agent selection is unavailable in this CLI.');
|
|
1018
|
-
this.
|
|
853
|
+
this.terminalInput.render();
|
|
1019
854
|
return;
|
|
1020
855
|
}
|
|
1021
856
|
const trimmed = input.trim();
|
|
1022
857
|
if (!trimmed) {
|
|
1023
858
|
display.showWarning('Enter a number or type "cancel".');
|
|
1024
|
-
this.
|
|
859
|
+
this.terminalInput.render();
|
|
1025
860
|
return;
|
|
1026
861
|
}
|
|
1027
862
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
1028
863
|
this.pendingInteraction = null;
|
|
1029
864
|
display.showInfo('Agent selection cancelled.');
|
|
1030
|
-
this.
|
|
865
|
+
this.terminalInput.render();
|
|
1031
866
|
return;
|
|
1032
867
|
}
|
|
1033
868
|
const choice = Number.parseInt(trimmed, 10);
|
|
1034
869
|
if (!Number.isFinite(choice)) {
|
|
1035
870
|
display.showWarning('Please enter a valid number.');
|
|
1036
|
-
this.
|
|
871
|
+
this.terminalInput.render();
|
|
1037
872
|
return;
|
|
1038
873
|
}
|
|
1039
874
|
const option = pending.options[choice - 1];
|
|
1040
875
|
if (!option) {
|
|
1041
876
|
display.showWarning('That option is not available.');
|
|
1042
|
-
this.
|
|
877
|
+
this.terminalInput.render();
|
|
1043
878
|
return;
|
|
1044
879
|
}
|
|
1045
880
|
await this.persistAgentSelection(option.name);
|
|
1046
881
|
this.pendingInteraction = null;
|
|
1047
|
-
this.
|
|
882
|
+
this.terminalInput.render();
|
|
1048
883
|
}
|
|
1049
884
|
async persistAgentSelection(profileName) {
|
|
1050
885
|
if (!this.agentMenu) {
|
|
@@ -1117,7 +952,7 @@ export class InteractiveShell {
|
|
|
1117
952
|
lines.push(` ${theme.primary('[text]')} Submit your own solution instead`);
|
|
1118
953
|
lines.push('');
|
|
1119
954
|
display.showSystemMessage(lines.join('\n'));
|
|
1120
|
-
this.
|
|
955
|
+
this.terminalInput.render();
|
|
1121
956
|
}
|
|
1122
957
|
async handlePlanApprovalInput(input) {
|
|
1123
958
|
const pending = this.pendingInteraction;
|
|
@@ -1126,7 +961,7 @@ export class InteractiveShell {
|
|
|
1126
961
|
const trimmed = input.trim();
|
|
1127
962
|
if (!trimmed) {
|
|
1128
963
|
display.showWarning('Enter a command or your own solution.');
|
|
1129
|
-
this.
|
|
964
|
+
this.terminalInput.render();
|
|
1130
965
|
return;
|
|
1131
966
|
}
|
|
1132
967
|
const lower = trimmed.toLowerCase();
|
|
@@ -1134,7 +969,7 @@ export class InteractiveShell {
|
|
|
1134
969
|
if (lower === 'cancel' || lower === 'c') {
|
|
1135
970
|
this.pendingInteraction = null;
|
|
1136
971
|
display.showInfo('Plan cancelled. You can continue with a different approach.');
|
|
1137
|
-
this.
|
|
972
|
+
this.terminalInput.render();
|
|
1138
973
|
return;
|
|
1139
974
|
}
|
|
1140
975
|
// Select all
|
|
@@ -1154,7 +989,7 @@ export class InteractiveShell {
|
|
|
1154
989
|
const selectedSteps = pending.steps.filter(s => pending.selectedSteps.has(s.id));
|
|
1155
990
|
if (selectedSteps.length === 0) {
|
|
1156
991
|
display.showWarning('No steps selected. Select steps or enter your own solution.');
|
|
1157
|
-
this.
|
|
992
|
+
this.terminalInput.render();
|
|
1158
993
|
return;
|
|
1159
994
|
}
|
|
1160
995
|
this.pendingInteraction = null;
|
|
@@ -1187,18 +1022,15 @@ export class InteractiveShell {
|
|
|
1187
1022
|
return;
|
|
1188
1023
|
}
|
|
1189
1024
|
display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
|
|
1190
|
-
this.
|
|
1025
|
+
this.terminalInput.render();
|
|
1191
1026
|
}
|
|
1192
1027
|
setupHandlers() {
|
|
1193
1028
|
// Handle terminal resize
|
|
1194
1029
|
output.on('resize', () => {
|
|
1195
|
-
if (!this.inputInitialized) {
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
this.terminalInput.resetContentPosition();
|
|
1199
1030
|
this.terminalInput.handleResize();
|
|
1200
|
-
this.terminalInput.forceRender();
|
|
1201
1031
|
});
|
|
1032
|
+
// Show initial input UI
|
|
1033
|
+
this.terminalInput.render();
|
|
1202
1034
|
}
|
|
1203
1035
|
/**
|
|
1204
1036
|
* Set up command autocomplete with all available slash commands.
|
|
@@ -1229,14 +1061,6 @@ export class InteractiveShell {
|
|
|
1229
1061
|
catch {
|
|
1230
1062
|
// Custom commands are optional
|
|
1231
1063
|
}
|
|
1232
|
-
// Add manual commands that are not yet in the schema
|
|
1233
|
-
if (!commands.some((cmd) => cmd.command === '/alphazero')) {
|
|
1234
|
-
commands.push({
|
|
1235
|
-
command: '/alphazero',
|
|
1236
|
-
description: 'Toggle AlphaZero RL mode',
|
|
1237
|
-
category: 'mode',
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
1064
|
// Sort commands alphabetically
|
|
1241
1065
|
commands.sort((a, b) => a.command.localeCompare(b.command));
|
|
1242
1066
|
this.terminalInput.setAvailableCommands(commands);
|
|
@@ -1315,312 +1139,25 @@ export class InteractiveShell {
|
|
|
1315
1139
|
autoContinueEnabled: this.autoContinueEnabled,
|
|
1316
1140
|
verificationHotkey: 'ctrl+shift+v',
|
|
1317
1141
|
autoContinueHotkey: 'ctrl+shift+c',
|
|
1318
|
-
thinkingModeLabel: this.thinkingMode,
|
|
1319
|
-
thinkingHotkey: '
|
|
1320
|
-
alphaZeroEnabled: this.alphaZeroModeEnabled,
|
|
1321
|
-
alphaZeroHotkey: 'ctrl+shift+a',
|
|
1322
|
-
alphaZeroLabel: 'AlphaZero RL',
|
|
1142
|
+
thinkingModeLabel: this.thinkingMode === 'extended' ? 'on' : 'off',
|
|
1143
|
+
thinkingHotkey: 'tab',
|
|
1323
1144
|
});
|
|
1324
|
-
this.refreshFeatureStatusDisplay();
|
|
1325
1145
|
this.refreshStatusLine();
|
|
1326
|
-
this.
|
|
1327
|
-
}
|
|
1328
|
-
refreshFeatureStatusDisplay() {
|
|
1329
|
-
this.terminalInput.setFeatureStatus(this.buildFeatureStatusSnapshot());
|
|
1146
|
+
this.terminalInput.render();
|
|
1330
1147
|
}
|
|
1331
1148
|
writeLocked(content) {
|
|
1332
1149
|
if (!content) {
|
|
1333
1150
|
return;
|
|
1334
1151
|
}
|
|
1335
|
-
//
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
this.assistantStreamActive = false;
|
|
1340
|
-
this.assistantStreamPhase = null;
|
|
1341
|
-
this.assistantStreamHeaderShown = false;
|
|
1342
|
-
this.assistantStreamBuffer = '';
|
|
1343
|
-
this.assistantStreamMetadata = undefined;
|
|
1344
|
-
this.assistantStreamHadContent = false;
|
|
1345
|
-
}
|
|
1346
|
-
startAssistantStream(metadata) {
|
|
1347
|
-
if (this.assistantStreamActive) {
|
|
1348
|
-
this.assistantStreamMetadata = metadata ?? this.assistantStreamMetadata;
|
|
1152
|
+
// If lock is already held, write directly - we're in a protected context
|
|
1153
|
+
// This prevents queuing issues where content gets delayed
|
|
1154
|
+
if (writeLock.isLocked()) {
|
|
1155
|
+
process.stdout.write(content);
|
|
1349
1156
|
return;
|
|
1350
1157
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
this.assistantStreamHeaderShown = false;
|
|
1355
|
-
this.assistantStreamBuffer = '';
|
|
1356
|
-
this.assistantStreamMetadata = metadata;
|
|
1357
|
-
this.assistantStreamHadContent = false;
|
|
1358
|
-
}
|
|
1359
|
-
streamAssistantChunk(chunk, channel) {
|
|
1360
|
-
if (!chunk) {
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
if (!this.assistantStreamActive) {
|
|
1364
|
-
this.startAssistantStream({
|
|
1365
|
-
contextWindowTokens: this.activeContextWindowTokens,
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
// Agent wraps reasoning chunks in <thinking> tags via openThinking()/closeThinking()
|
|
1369
|
-
// Always use tag parsing to properly handle the tags
|
|
1370
|
-
this.processAssistantStreamChunk(chunk);
|
|
1371
|
-
}
|
|
1372
|
-
finalizeAssistantStream() {
|
|
1373
|
-
if (!this.assistantStreamActive) {
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
if (this.assistantStreamBuffer) {
|
|
1377
|
-
this.writeAssistantStream(this.assistantStreamBuffer);
|
|
1378
|
-
this.assistantStreamBuffer = '';
|
|
1379
|
-
}
|
|
1380
|
-
this.assistantStreamActive = false;
|
|
1381
|
-
this.assistantStreamPhase = null;
|
|
1382
|
-
this.assistantStreamHeaderShown = false;
|
|
1383
|
-
this.assistantStreamMetadata = undefined;
|
|
1384
|
-
}
|
|
1385
|
-
processAssistantStreamChunk(chunk) {
|
|
1386
|
-
this.assistantStreamBuffer += chunk;
|
|
1387
|
-
const tagRegex = /<(\/?)(thinking|response)>/i;
|
|
1388
|
-
while (this.assistantStreamBuffer.length > 0) {
|
|
1389
|
-
const match = tagRegex.exec(this.assistantStreamBuffer);
|
|
1390
|
-
if (!match || match.index === undefined) {
|
|
1391
|
-
// No more full tags - flush what we can but keep any partial tag prefix buffered
|
|
1392
|
-
const { flushable, remainder } = this.splitAssistantStreamRemainder(this.assistantStreamBuffer);
|
|
1393
|
-
if (flushable) {
|
|
1394
|
-
this.writeAssistantStream(flushable);
|
|
1395
|
-
}
|
|
1396
|
-
this.assistantStreamBuffer = remainder;
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
const tagIndex = match.index;
|
|
1400
|
-
const beforeTag = this.assistantStreamBuffer.slice(0, tagIndex);
|
|
1401
|
-
if (beforeTag) {
|
|
1402
|
-
this.writeAssistantStream(beforeTag);
|
|
1403
|
-
}
|
|
1404
|
-
// Advance buffer past the tag
|
|
1405
|
-
this.assistantStreamBuffer = this.assistantStreamBuffer.slice(tagIndex + match[0].length);
|
|
1406
|
-
const isClosing = match[1] === '/';
|
|
1407
|
-
const rawTagType = (match[2] ?? '').toLowerCase();
|
|
1408
|
-
// Map 'thinking' to 'thought' for AssistantBlockType compatibility
|
|
1409
|
-
const tagType = (rawTagType === 'thinking' ? 'thought' : rawTagType);
|
|
1410
|
-
if (isClosing) {
|
|
1411
|
-
if (this.assistantStreamPhase === tagType) {
|
|
1412
|
-
this.assistantStreamPhase = null;
|
|
1413
|
-
this.assistantStreamHeaderShown = false;
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
else {
|
|
1417
|
-
this.setAssistantStreamPhase(tagType);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
writeAssistantStream(content) {
|
|
1422
|
-
if (!content) {
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
if (content.trim().length > 0) {
|
|
1426
|
-
this.assistantStreamHadContent = true;
|
|
1427
|
-
}
|
|
1428
|
-
// If no explicit phase has been set yet, keep the stream labeled as "thought"
|
|
1429
|
-
// until a <response> tag arrives so operators see thinking first.
|
|
1430
|
-
this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
|
|
1431
|
-
if (this.assistantStreamPhase) {
|
|
1432
|
-
this.renderAssistantStreamHeader(this.assistantStreamPhase);
|
|
1433
|
-
}
|
|
1434
|
-
let output = content;
|
|
1435
|
-
if (this.assistantStreamPhase === 'thought') {
|
|
1436
|
-
output = this.formatThoughtDisplay(content, { streaming: true });
|
|
1437
|
-
}
|
|
1438
|
-
this.enqueueAssistantStream(output);
|
|
1439
|
-
}
|
|
1440
|
-
setAssistantStreamPhase(phase) {
|
|
1441
|
-
if (this.assistantStreamPhase === phase) {
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
this.assistantStreamPhase = phase;
|
|
1445
|
-
this.assistantStreamHeaderShown = false;
|
|
1446
|
-
if (phase) {
|
|
1447
|
-
this.renderAssistantStreamHeader(phase);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
renderAssistantStreamHeader(type) {
|
|
1451
|
-
if (this.assistantStreamHeaderShown) {
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
const header = this.formatAssistantHeader(type, this.assistantStreamMetadata);
|
|
1455
|
-
if (header.trim()) {
|
|
1456
|
-
this.enqueueAssistantStream(`\n${header}\n`);
|
|
1457
|
-
}
|
|
1458
|
-
this.assistantStreamHeaderShown = true;
|
|
1459
|
-
}
|
|
1460
|
-
splitAssistantStreamRemainder(buffer) {
|
|
1461
|
-
if (!buffer) {
|
|
1462
|
-
return { flushable: '', remainder: '' };
|
|
1463
|
-
}
|
|
1464
|
-
const tags = ['<thinking>', '</thinking>', '<response>', '</response>'];
|
|
1465
|
-
let longest = '';
|
|
1466
|
-
for (const tag of tags) {
|
|
1467
|
-
for (let i = 1; i < tag.length; i += 1) {
|
|
1468
|
-
const prefix = tag.slice(0, i);
|
|
1469
|
-
if (buffer.endsWith(prefix) && prefix.length > longest.length) {
|
|
1470
|
-
longest = prefix;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
if (!longest) {
|
|
1475
|
-
return { flushable: buffer, remainder: '' };
|
|
1476
|
-
}
|
|
1477
|
-
return {
|
|
1478
|
-
flushable: buffer.slice(0, -longest.length),
|
|
1479
|
-
remainder: longest,
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
enqueueAssistantStream(content) {
|
|
1483
|
-
if (!content) {
|
|
1484
|
-
return;
|
|
1485
|
-
}
|
|
1486
|
-
const run = () => this.writeAssistantBlock(content);
|
|
1487
|
-
this.uiUpdates.enqueue({
|
|
1488
|
-
lane: 'stream',
|
|
1489
|
-
mode: ['streaming', 'processing', 'idle'],
|
|
1490
|
-
priority: 'high',
|
|
1491
|
-
description: 'assistant stream flush',
|
|
1492
|
-
run,
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
writeAssistantBlock(content) {
|
|
1496
|
-
if (!content) {
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
// Ensure assistant block writes are serialized with terminal input renders
|
|
1500
|
-
writeLock.safeWrite(() => {
|
|
1501
|
-
this.terminalInput.streamContent(content);
|
|
1502
|
-
});
|
|
1503
|
-
}
|
|
1504
|
-
renderAssistantBlock(type, content, metadata) {
|
|
1505
|
-
if (!this.assistantBlocksEnabled || !this.assistantBlockRenderer) {
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
this.assistantBlockRenderer.renderBlock(type, content, metadata);
|
|
1509
|
-
// Ensure the prompt stays pinned below freshly written blocks
|
|
1510
|
-
this.renderPromptArea();
|
|
1511
|
-
}
|
|
1512
|
-
renderAssistantContent(type, content, metadata) {
|
|
1513
|
-
const normalized = content.replace(/\r\n/g, '\n').trim();
|
|
1514
|
-
if (!normalized) {
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
|
|
1518
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1519
|
-
this.renderAssistantBlock(type, formatted, metadata);
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
this.renderAssistantFallback(type, formatted, metadata);
|
|
1523
|
-
}
|
|
1524
|
-
renderAssistantFallback(type, content, metadata) {
|
|
1525
|
-
const header = this.formatAssistantHeader(type, metadata);
|
|
1526
|
-
const compact = content.trimEnd().replace(/\n{3,}/g, '\n\n');
|
|
1527
|
-
const block = `\n${header}\n${compact}\n\n`;
|
|
1528
|
-
this.enqueueAssistantStream(block);
|
|
1529
|
-
}
|
|
1530
|
-
formatThoughtDisplay(content, options = {}) {
|
|
1531
|
-
const normalized = content.replace(/\r\n/g, '\n');
|
|
1532
|
-
if (options.streaming) {
|
|
1533
|
-
return formatThinkingContent(normalized);
|
|
1534
|
-
}
|
|
1535
|
-
const lines = normalized.split('\n');
|
|
1536
|
-
return lines
|
|
1537
|
-
.map((line, index) => {
|
|
1538
|
-
const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
|
|
1539
|
-
const trimmed = line.trim();
|
|
1540
|
-
const body = trimmed ? formatThinkingContent(trimmed) : '';
|
|
1541
|
-
return body ? `${prefix} ${body}` : `${prefix}`;
|
|
1542
|
-
})
|
|
1543
|
-
.join('\n');
|
|
1544
|
-
}
|
|
1545
|
-
formatAssistantHeader(type, metadata) {
|
|
1546
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1547
|
-
return this.assistantBlockRenderer.formatHeader(type, metadata);
|
|
1548
|
-
}
|
|
1549
|
-
const badges = [];
|
|
1550
|
-
badges.push(this.buildAssistantTypeBadge(type));
|
|
1551
|
-
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
1552
|
-
hour12: false,
|
|
1553
|
-
hour: '2-digit',
|
|
1554
|
-
minute: '2-digit',
|
|
1555
|
-
second: '2-digit',
|
|
1556
|
-
});
|
|
1557
|
-
badges.push({ text: timestamp, style: 'muted', icon: '๐' });
|
|
1558
|
-
const total = metadata?.usage ? this.totalTokens(metadata.usage) : null;
|
|
1559
|
-
const windowTokens = metadata?.contextWindowTokens;
|
|
1560
|
-
if (typeof total === 'number') {
|
|
1561
|
-
badges.push({ text: `${total} tok`, style: 'muted', icon: 'โ' });
|
|
1562
|
-
}
|
|
1563
|
-
if (typeof total === 'number' && typeof windowTokens === 'number' && windowTokens > 0) {
|
|
1564
|
-
const percentage = Math.round((total / windowTokens) * 100);
|
|
1565
|
-
const style = percentage > 85 ? 'error' : percentage > 70 ? 'warning' : 'info';
|
|
1566
|
-
badges.push({ text: `${percentage}% ctx`, style, icon: 'โ' });
|
|
1567
|
-
}
|
|
1568
|
-
const elapsedBadge = this.buildElapsedBadge(metadata);
|
|
1569
|
-
if (elapsedBadge) {
|
|
1570
|
-
badges.push(elapsedBadge);
|
|
1571
|
-
}
|
|
1572
|
-
return compactRenderer.formatBadges(badges, theme.ui.muted(' โ '));
|
|
1573
|
-
}
|
|
1574
|
-
buildAssistantTypeBadge(type) {
|
|
1575
|
-
switch (type) {
|
|
1576
|
-
case 'thought':
|
|
1577
|
-
return { text: 'Thought', style: 'info', icon: '๐ญ' };
|
|
1578
|
-
case 'tools':
|
|
1579
|
-
return { text: 'Tools', style: 'primary', icon: '๐ ' };
|
|
1580
|
-
case 'response':
|
|
1581
|
-
default:
|
|
1582
|
-
return { text: 'Response', style: 'success', icon: '๐ฌ' };
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
buildElapsedBadge(metadata) {
|
|
1586
|
-
const elapsed = metadata?.elapsedMs;
|
|
1587
|
-
if (typeof elapsed !== 'number' || elapsed <= 0) {
|
|
1588
|
-
return null;
|
|
1589
|
-
}
|
|
1590
|
-
const formatted = elapsed < 1000 ? `${Math.round(elapsed)}ms` : `${Math.round(elapsed / 1000)}s`;
|
|
1591
|
-
return { text: formatted, style: 'muted', icon: 'โฑ' };
|
|
1592
|
-
}
|
|
1593
|
-
isStreamingUiActive() {
|
|
1594
|
-
return this.streamingHeartbeatStart !== null;
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Render the prompt/control bar. During streaming, rely on the streaming frame
|
|
1598
|
-
* renderer and enqueue through the UIUpdateCoordinator to avoid fighting the
|
|
1599
|
-
* scroll region or duplicating the prompt.
|
|
1600
|
-
*/
|
|
1601
|
-
renderPromptArea(force = false) {
|
|
1602
|
-
if (this.isStreamingUiActive()) {
|
|
1603
|
-
this.uiUpdates.enqueue({
|
|
1604
|
-
lane: 'prompt',
|
|
1605
|
-
mode: ['streaming', 'processing'],
|
|
1606
|
-
coalesceKey: 'prompt:streaming-frame',
|
|
1607
|
-
description: 'render streaming prompt frame',
|
|
1608
|
-
run: () => {
|
|
1609
|
-
if (force) {
|
|
1610
|
-
this.terminalInput.renderStreamingFrame(true);
|
|
1611
|
-
return;
|
|
1612
|
-
}
|
|
1613
|
-
this.terminalInput.renderStreamingFrame();
|
|
1614
|
-
},
|
|
1615
|
-
});
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
if (force) {
|
|
1619
|
-
this.terminalInput.forceRender();
|
|
1620
|
-
}
|
|
1621
|
-
else {
|
|
1622
|
-
this.terminalInput.render();
|
|
1623
|
-
}
|
|
1158
|
+
writeLock.withLock(() => {
|
|
1159
|
+
process.stdout.write(content);
|
|
1160
|
+
}, 'interactiveShell.stdout');
|
|
1624
1161
|
}
|
|
1625
1162
|
/**
|
|
1626
1163
|
* Refresh the status line in the persistent input area.
|
|
@@ -1638,23 +1175,19 @@ export class InteractiveShell {
|
|
|
1638
1175
|
// Surface meta header (elapsed + context usage) above the divider
|
|
1639
1176
|
// Use streaming elapsed time if available, otherwise fall back to status line state
|
|
1640
1177
|
let elapsedSeconds = null;
|
|
1641
|
-
|
|
1642
|
-
if (this.streamingHeartbeatStart && shouldShowElapsed) {
|
|
1178
|
+
if (this.streamingHeartbeatStart) {
|
|
1643
1179
|
// Actively streaming - compute live elapsed
|
|
1644
1180
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1645
1181
|
}
|
|
1646
|
-
else if (
|
|
1182
|
+
else if (this.lastStreamingElapsedSeconds !== null) {
|
|
1647
1183
|
// Just finished streaming - use preserved final time
|
|
1648
1184
|
elapsedSeconds = this.lastStreamingElapsedSeconds;
|
|
1649
1185
|
}
|
|
1650
|
-
else if (
|
|
1186
|
+
else if (this.statusLineState) {
|
|
1651
1187
|
// Fallback to status line state elapsed
|
|
1652
1188
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
|
|
1653
1189
|
}
|
|
1654
|
-
const
|
|
1655
|
-
const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
|
|
1656
|
-
? display.getThinkingElapsedMs()
|
|
1657
|
-
: null;
|
|
1190
|
+
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1658
1191
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1659
1192
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1660
1193
|
this.terminalInput.setMetaStatus({
|
|
@@ -1662,7 +1195,7 @@ export class InteractiveShell {
|
|
|
1662
1195
|
tokensUsed,
|
|
1663
1196
|
tokenLimit,
|
|
1664
1197
|
thinkingMs,
|
|
1665
|
-
thinkingHasContent:
|
|
1198
|
+
thinkingHasContent: display.isSpinnerActive(),
|
|
1666
1199
|
});
|
|
1667
1200
|
// Keep model/provider visible in the controls bar
|
|
1668
1201
|
this.terminalInput.setModelContext({
|
|
@@ -1670,7 +1203,7 @@ export class InteractiveShell {
|
|
|
1670
1203
|
provider: this.providerLabel(this.sessionState.provider),
|
|
1671
1204
|
});
|
|
1672
1205
|
if (forceRender) {
|
|
1673
|
-
this.
|
|
1206
|
+
this.terminalInput.render();
|
|
1674
1207
|
}
|
|
1675
1208
|
}
|
|
1676
1209
|
/**
|
|
@@ -1707,7 +1240,7 @@ export class InteractiveShell {
|
|
|
1707
1240
|
* Ensure the terminal input is ready for interactive input.
|
|
1708
1241
|
*/
|
|
1709
1242
|
ensureReadlineReady() {
|
|
1710
|
-
this.
|
|
1243
|
+
this.terminalInput.render();
|
|
1711
1244
|
}
|
|
1712
1245
|
/**
|
|
1713
1246
|
* Log user prompt to the scroll region so it's part of the conversation flow.
|
|
@@ -1729,7 +1262,7 @@ export class InteractiveShell {
|
|
|
1729
1262
|
}
|
|
1730
1263
|
requestPromptRefresh(force = false) {
|
|
1731
1264
|
if (force) {
|
|
1732
|
-
this.
|
|
1265
|
+
this.terminalInput.forceRender();
|
|
1733
1266
|
return;
|
|
1734
1267
|
}
|
|
1735
1268
|
if (this.promptRefreshTimer) {
|
|
@@ -1737,7 +1270,7 @@ export class InteractiveShell {
|
|
|
1737
1270
|
}
|
|
1738
1271
|
this.promptRefreshTimer = setTimeout(() => {
|
|
1739
1272
|
this.promptRefreshTimer = null;
|
|
1740
|
-
this.
|
|
1273
|
+
this.terminalInput.render();
|
|
1741
1274
|
}, 48);
|
|
1742
1275
|
}
|
|
1743
1276
|
clearPromptRefreshTimer() {
|
|
@@ -1746,53 +1279,35 @@ export class InteractiveShell {
|
|
|
1746
1279
|
this.promptRefreshTimer = null;
|
|
1747
1280
|
}
|
|
1748
1281
|
}
|
|
1749
|
-
async withStreamingUi(label, run) {
|
|
1750
|
-
if (this.isStreamingUiActive()) {
|
|
1751
|
-
return run();
|
|
1752
|
-
}
|
|
1753
|
-
this.resetAssistantStreamTracking();
|
|
1754
|
-
this.terminalInput.setStreaming(true);
|
|
1755
|
-
this.startStreamingHeartbeat(label);
|
|
1756
|
-
try {
|
|
1757
|
-
return await run();
|
|
1758
|
-
}
|
|
1759
|
-
finally {
|
|
1760
|
-
this.stopStreamingHeartbeat();
|
|
1761
|
-
this.terminalInput.setStreaming(false);
|
|
1762
|
-
const nextMode = this.isProcessing ? 'processing' : 'idle';
|
|
1763
|
-
this.uiUpdates.setMode(nextMode);
|
|
1764
|
-
if (nextMode === 'processing') {
|
|
1765
|
-
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
1282
|
startStreamingHeartbeat(label = 'Streaming') {
|
|
1770
|
-
this.stopStreamingHeartbeat(
|
|
1283
|
+
this.stopStreamingHeartbeat();
|
|
1771
1284
|
// Enter global streaming mode - blocks all non-streaming UI output
|
|
1772
1285
|
enterStreamingMode();
|
|
1773
|
-
this.streamingStatusBase = label;
|
|
1774
|
-
this.streamingStatusDetail = null;
|
|
1775
|
-
this.latestThoughtSummary = null;
|
|
1776
|
-
this.lastStreamingElapsedSeconds = null;
|
|
1777
1286
|
// Set up scroll region for streaming content
|
|
1778
1287
|
this.terminalInput.enterStreamingScrollRegion();
|
|
1779
1288
|
this.uiUpdates.setMode('streaming');
|
|
1780
1289
|
this.streamingHeartbeatStart = Date.now();
|
|
1781
1290
|
this.streamingHeartbeatFrame = 0;
|
|
1782
|
-
this.
|
|
1291
|
+
const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1292
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
|
|
1293
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1783
1294
|
this.refreshStatusLine(true);
|
|
1784
1295
|
// Periodically refresh the pinned input/status region while streaming so
|
|
1785
1296
|
// elapsed time remains visible without interrupting the scroll region.
|
|
1786
1297
|
this.uiUpdates.startHeartbeat('streaming', {
|
|
1787
1298
|
intervalMs: 1000,
|
|
1788
1299
|
lane: 'heartbeat',
|
|
1789
|
-
priority: 'high',
|
|
1790
1300
|
mode: ['streaming', 'processing'],
|
|
1791
1301
|
coalesceKey: 'streaming:heartbeat',
|
|
1792
1302
|
run: () => {
|
|
1303
|
+
const elapsedSeconds = this.streamingHeartbeatStart
|
|
1304
|
+
? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
|
|
1305
|
+
: 0;
|
|
1793
1306
|
this.streamingHeartbeatFrame =
|
|
1794
1307
|
(this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
|
|
1795
|
-
this.
|
|
1308
|
+
const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1309
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
|
|
1310
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1796
1311
|
// Update parallel agent display during streaming
|
|
1797
1312
|
const manager = getParallelAgentManager();
|
|
1798
1313
|
if (manager.isRunning()) {
|
|
@@ -1803,13 +1318,7 @@ export class InteractiveShell {
|
|
|
1803
1318
|
},
|
|
1804
1319
|
});
|
|
1805
1320
|
}
|
|
1806
|
-
stopStreamingHeartbeat(
|
|
1807
|
-
const skipRender = !!options.skipRender;
|
|
1808
|
-
const streamingActive = this.isStreamingUiActive();
|
|
1809
|
-
const scrollRegionActive = this.terminalInput.isScrollRegionActive();
|
|
1810
|
-
if (!streamingActive && !scrollRegionActive) {
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1321
|
+
stopStreamingHeartbeat() {
|
|
1813
1322
|
// Exit global streaming mode - allows UI to render again
|
|
1814
1323
|
exitStreamingMode();
|
|
1815
1324
|
// Preserve final elapsed time before clearing heartbeat start
|
|
@@ -1817,44 +1326,51 @@ export class InteractiveShell {
|
|
|
1817
1326
|
this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1818
1327
|
}
|
|
1819
1328
|
// Exit scroll region mode
|
|
1820
|
-
this.terminalInput.exitStreamingScrollRegion(
|
|
1329
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
1821
1330
|
this.uiUpdates.stopHeartbeat('streaming');
|
|
1822
1331
|
this.streamingHeartbeatStart = null;
|
|
1823
1332
|
this.streamingHeartbeatFrame = 0;
|
|
1824
1333
|
this.streamingStatusLabel = null;
|
|
1825
|
-
this.streamingStatusBase = null;
|
|
1826
|
-
this.streamingStatusDetail = null;
|
|
1827
|
-
this.latestThoughtSummary = null;
|
|
1828
1334
|
// Clear streaming label specifically (keeps override and main status if set)
|
|
1829
1335
|
this.terminalInput.setStreamingLabel(null);
|
|
1830
1336
|
// Clear streaming status from display
|
|
1831
1337
|
display.updateStreamingStatus(null);
|
|
1832
1338
|
// Force refresh to update the input area now that streaming has ended
|
|
1833
|
-
|
|
1834
|
-
this.refreshStatusLine(true);
|
|
1835
|
-
}
|
|
1339
|
+
this.refreshStatusLine(true);
|
|
1836
1340
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
if (
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1341
|
+
handleStreamChunk(chunk) {
|
|
1342
|
+
if (!chunk) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
// Preserve raw output in plain/CI modes or non-TTY environments
|
|
1346
|
+
if (isPlainOutputMode() || !output.isTTY) {
|
|
1347
|
+
this.terminalInput.streamContent(chunk);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
if (!this.streamingFormatter) {
|
|
1351
|
+
this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
|
|
1352
|
+
this.terminalInput.streamContent(this.streamingFormatter.header());
|
|
1353
|
+
}
|
|
1354
|
+
const formatted = this.streamingFormatter.formatChunk(chunk);
|
|
1355
|
+
if (formatted) {
|
|
1356
|
+
this.terminalInput.streamContent(formatted);
|
|
1847
1357
|
}
|
|
1848
|
-
return `${prefix} ${parts.join(' ยท ')}`.trim();
|
|
1849
1358
|
}
|
|
1850
|
-
|
|
1851
|
-
if (this.
|
|
1359
|
+
finishStreamingFormatter(note) {
|
|
1360
|
+
if (!this.streamingFormatter) {
|
|
1852
1361
|
return;
|
|
1853
1362
|
}
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1363
|
+
const closing = this.streamingFormatter.finish(note);
|
|
1364
|
+
if (closing) {
|
|
1365
|
+
this.terminalInput.streamContent(closing);
|
|
1366
|
+
}
|
|
1367
|
+
this.streamingFormatter = null;
|
|
1368
|
+
}
|
|
1369
|
+
buildStreamingStatus(label, _elapsedSeconds) {
|
|
1370
|
+
// Model + elapsed time already live in the pinned meta header; keep the streaming
|
|
1371
|
+
// status focused on the activity to avoid duplicate info.
|
|
1372
|
+
const prefix = theme.info('โบ');
|
|
1373
|
+
return `${prefix} ${label}`.trim();
|
|
1858
1374
|
}
|
|
1859
1375
|
formatElapsedShort(seconds) {
|
|
1860
1376
|
if (seconds < 60) {
|
|
@@ -1871,7 +1387,7 @@ export class InteractiveShell {
|
|
|
1871
1387
|
else {
|
|
1872
1388
|
this.setIdleStatus();
|
|
1873
1389
|
}
|
|
1874
|
-
this.
|
|
1390
|
+
this.terminalInput.render();
|
|
1875
1391
|
}
|
|
1876
1392
|
enqueueFollowUpAction(action) {
|
|
1877
1393
|
this.followUpQueue.push(action);
|
|
@@ -1890,21 +1406,14 @@ export class InteractiveShell {
|
|
|
1890
1406
|
this.refreshQueueIndicators();
|
|
1891
1407
|
this.scheduleQueueProcessing();
|
|
1892
1408
|
// Re-show the prompt so user can continue typing more follow-ups
|
|
1893
|
-
this.
|
|
1409
|
+
this.terminalInput.render();
|
|
1894
1410
|
}
|
|
1895
1411
|
scheduleQueueProcessing() {
|
|
1896
1412
|
if (!this.followUpQueue.length) {
|
|
1897
1413
|
this.refreshQueueIndicators();
|
|
1898
1414
|
return;
|
|
1899
1415
|
}
|
|
1900
|
-
if (this.apiKeyGateActive) {
|
|
1901
|
-
this.refreshQueueIndicators();
|
|
1902
|
-
return;
|
|
1903
|
-
}
|
|
1904
1416
|
queueMicrotask(() => {
|
|
1905
|
-
if (this.apiKeyGateActive) {
|
|
1906
|
-
return;
|
|
1907
|
-
}
|
|
1908
1417
|
void this.processQueuedActions();
|
|
1909
1418
|
});
|
|
1910
1419
|
}
|
|
@@ -1912,12 +1421,12 @@ export class InteractiveShell {
|
|
|
1912
1421
|
* Process queued follow-up actions.
|
|
1913
1422
|
*/
|
|
1914
1423
|
async processQueuedActions() {
|
|
1915
|
-
if (this.
|
|
1424
|
+
if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
|
|
1916
1425
|
return;
|
|
1917
1426
|
}
|
|
1918
1427
|
this.isDrainingQueue = true;
|
|
1919
1428
|
try {
|
|
1920
|
-
while (!this.isProcessing &&
|
|
1429
|
+
while (!this.isProcessing && this.followUpQueue.length) {
|
|
1921
1430
|
const next = this.followUpQueue.shift();
|
|
1922
1431
|
const remaining = this.followUpQueue.length;
|
|
1923
1432
|
const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
|
|
@@ -1952,12 +1461,12 @@ export class InteractiveShell {
|
|
|
1952
1461
|
}
|
|
1953
1462
|
if (lower === 'clear') {
|
|
1954
1463
|
display.clear();
|
|
1955
|
-
this.
|
|
1464
|
+
this.terminalInput.render();
|
|
1956
1465
|
return;
|
|
1957
1466
|
}
|
|
1958
1467
|
if (lower === 'help') {
|
|
1959
1468
|
this.showHelp();
|
|
1960
|
-
this.
|
|
1469
|
+
this.terminalInput.render();
|
|
1961
1470
|
return;
|
|
1962
1471
|
}
|
|
1963
1472
|
if (trimmed.startsWith('/')) {
|
|
@@ -1967,12 +1476,12 @@ export class InteractiveShell {
|
|
|
1967
1476
|
// Check for continuous/infinite loop commands
|
|
1968
1477
|
if (this.isContinuousCommand(trimmed)) {
|
|
1969
1478
|
await this.processContinuousRequest(trimmed);
|
|
1970
|
-
this.
|
|
1479
|
+
this.terminalInput.render();
|
|
1971
1480
|
return;
|
|
1972
1481
|
}
|
|
1973
1482
|
// Direct execution for all inputs, including multi-line pastes
|
|
1974
1483
|
await this.processRequest(trimmed);
|
|
1975
|
-
this.
|
|
1484
|
+
this.terminalInput.render();
|
|
1976
1485
|
}
|
|
1977
1486
|
/**
|
|
1978
1487
|
* Check if the command is a continuous/infinite loop command
|
|
@@ -2010,153 +1519,6 @@ export class InteractiveShell {
|
|
|
2010
1519
|
];
|
|
2011
1520
|
return patterns.some(pattern => pattern.test(lower));
|
|
2012
1521
|
}
|
|
2013
|
-
isDifficultProblem(input) {
|
|
2014
|
-
const normalized = input.toLowerCase();
|
|
2015
|
-
const wordCount = normalized.split(/\s+/).filter(Boolean).length;
|
|
2016
|
-
if (normalized.length > 600 || wordCount > 80) {
|
|
2017
|
-
return true;
|
|
2018
|
-
}
|
|
2019
|
-
const signals = [
|
|
2020
|
-
'root cause',
|
|
2021
|
-
'postmortem',
|
|
2022
|
-
'crash',
|
|
2023
|
-
'incident',
|
|
2024
|
-
'outage',
|
|
2025
|
-
'optimiz',
|
|
2026
|
-
'performance',
|
|
2027
|
-
'throughput',
|
|
2028
|
-
'latency',
|
|
2029
|
-
'scalab',
|
|
2030
|
-
'architecture',
|
|
2031
|
-
'rewrite',
|
|
2032
|
-
'migration',
|
|
2033
|
-
'refactor',
|
|
2034
|
-
'reverse engineer',
|
|
2035
|
-
'security',
|
|
2036
|
-
'exploit',
|
|
2037
|
-
'injection',
|
|
2038
|
-
'vulnerability',
|
|
2039
|
-
'compliance',
|
|
2040
|
-
'multi-step',
|
|
2041
|
-
'complex',
|
|
2042
|
-
'difficult',
|
|
2043
|
-
'hard problem',
|
|
2044
|
-
'debug',
|
|
2045
|
-
'trace',
|
|
2046
|
-
'profil',
|
|
2047
|
-
'bottleneck',
|
|
2048
|
-
];
|
|
2049
|
-
return signals.some((signal) => normalized.includes(signal));
|
|
2050
|
-
}
|
|
2051
|
-
buildAlphaZeroPrompt(request, flaggedDifficult) {
|
|
2052
|
-
const playbook = [
|
|
2053
|
-
'AlphaZero RL MODE is ACTIVE. Operate as a self-play reinforcement loop.',
|
|
2054
|
-
flaggedDifficult
|
|
2055
|
-
? 'Treat this as a difficult, high-risk task and over-verify the result.'
|
|
2056
|
-
: 'Apply the reinforcement loop even if the task looks small.',
|
|
2057
|
-
'Follow this closed-loop playbook:',
|
|
2058
|
-
'- Draft two competing solution strategies and merge the strongest ideas before executing.',
|
|
2059
|
-
'- Execute with tools while logging decisions and evidence.',
|
|
2060
|
-
'- Self-critique and repair until the quality is excellent (aim โฅ90/100).',
|
|
2061
|
-
'- Run full-lifecycle verification like a human reviewer: build/tests, manual sanity checks, edge cases, performance/safety/security probes, docs/UX/readiness notes.',
|
|
2062
|
-
'- Keep a verification ledger: each check with PASS/FAIL, evidence, and remaining risks. If anything fails, fix and re-verify before claiming completion.',
|
|
2063
|
-
'Finish with a concise sign-off that lists what was achieved and the proof of completion.',
|
|
2064
|
-
];
|
|
2065
|
-
return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
|
|
2066
|
-
}
|
|
2067
|
-
buildRunLogExcerpt(startIndex) {
|
|
2068
|
-
const buffer = this.terminalInput.getScrollbackBuffer();
|
|
2069
|
-
const sinceStart = buffer.slice(Math.max(0, startIndex));
|
|
2070
|
-
const excerpt = sinceStart.slice(-200); // Cap prompt size
|
|
2071
|
-
return excerpt.join('\n').trim();
|
|
2072
|
-
}
|
|
2073
|
-
buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
|
|
2074
|
-
return {
|
|
2075
|
-
id: ++this.runIdCounter,
|
|
2076
|
-
request,
|
|
2077
|
-
response,
|
|
2078
|
-
timestamp: new Date().toISOString(),
|
|
2079
|
-
alphaZero: meta.alphaZeroEngaged,
|
|
2080
|
-
difficult: meta.alphaZeroDifficult,
|
|
2081
|
-
failureType: meta.failureType,
|
|
2082
|
-
outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
buildAlphaZeroReflectionPrompt(runLog, options) {
|
|
2086
|
-
const lines = [];
|
|
2087
|
-
lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
|
|
2088
|
-
lines.push(`Timestamp: ${runLog.timestamp}`);
|
|
2089
|
-
lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
|
|
2090
|
-
lines.push('');
|
|
2091
|
-
lines.push('User request:');
|
|
2092
|
-
lines.push(runLog.request);
|
|
2093
|
-
lines.push('');
|
|
2094
|
-
lines.push('Previous run log excerpt:');
|
|
2095
|
-
lines.push(runLog.outputExcerpt || '[empty]');
|
|
2096
|
-
lines.push('');
|
|
2097
|
-
lines.push('Instructions:');
|
|
2098
|
-
lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
|
|
2099
|
-
lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
|
|
2100
|
-
lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
|
|
2101
|
-
lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
|
|
2102
|
-
if (options.autoChain) {
|
|
2103
|
-
lines.push('- Continue iterating automatically while meaningful improvements remain.');
|
|
2104
|
-
lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
|
|
2105
|
-
}
|
|
2106
|
-
return lines.join('\n');
|
|
2107
|
-
}
|
|
2108
|
-
maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
|
|
2109
|
-
if (!alphaZeroEngaged) {
|
|
2110
|
-
return;
|
|
2111
|
-
}
|
|
2112
|
-
if (!isErosolarRepo(this.workingDir)) {
|
|
2113
|
-
return;
|
|
2114
|
-
}
|
|
2115
|
-
if (!runLog.outputExcerpt) {
|
|
2116
|
-
return;
|
|
2117
|
-
}
|
|
2118
|
-
if (this.lastReflectedRunId === runLog.id) {
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
if (allowAutoChain) {
|
|
2122
|
-
if (!this.autoContinueEnabled) {
|
|
2123
|
-
return;
|
|
2124
|
-
}
|
|
2125
|
-
if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
|
|
2126
|
-
display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
|
|
2127
|
-
this.alphaZeroAutoImproveActive = false;
|
|
2128
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2129
|
-
return;
|
|
2130
|
-
}
|
|
2131
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2132
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2133
|
-
}
|
|
2134
|
-
this.alphaZeroAutoImproveActive = true;
|
|
2135
|
-
this.alphaZeroAutoImproveIterations++;
|
|
2136
|
-
}
|
|
2137
|
-
const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
|
|
2138
|
-
this.lastReflectedRunId = runLog.id;
|
|
2139
|
-
this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
|
|
2140
|
-
this.enqueueFollowUpAction({ type: 'request', text: prompt });
|
|
2141
|
-
display.showInfo(allowAutoChain
|
|
2142
|
-
? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
|
|
2143
|
-
: 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
|
|
2144
|
-
}
|
|
2145
|
-
shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
|
|
2146
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2147
|
-
return false;
|
|
2148
|
-
}
|
|
2149
|
-
if (!allowAutoChain) {
|
|
2150
|
-
return true;
|
|
2151
|
-
}
|
|
2152
|
-
if (!responseText) {
|
|
2153
|
-
return false;
|
|
2154
|
-
}
|
|
2155
|
-
const normalized = responseText.toLowerCase();
|
|
2156
|
-
return (normalized.includes('no_more_improvements') ||
|
|
2157
|
-
normalized.includes('no more improvements') ||
|
|
2158
|
-
normalized.includes('stop_auto_improve'));
|
|
2159
|
-
}
|
|
2160
1522
|
async handlePendingInteraction(input) {
|
|
2161
1523
|
if (!this.pendingInteraction) {
|
|
2162
1524
|
return false;
|
|
@@ -2164,7 +1526,7 @@ export class InteractiveShell {
|
|
|
2164
1526
|
switch (this.pendingInteraction.type) {
|
|
2165
1527
|
case 'model-loading':
|
|
2166
1528
|
display.showInfo('Still fetching model options. Please wait a moment.');
|
|
2167
|
-
this.
|
|
1529
|
+
this.terminalInput.render();
|
|
2168
1530
|
return true;
|
|
2169
1531
|
case 'model-provider':
|
|
2170
1532
|
await this.handleModelProviderSelection(input);
|
|
@@ -2195,213 +1557,166 @@ export class InteractiveShell {
|
|
|
2195
1557
|
const [command] = input.split(/\s+/);
|
|
2196
1558
|
if (!command) {
|
|
2197
1559
|
display.showWarning('Enter a slash command.');
|
|
2198
|
-
this.
|
|
1560
|
+
this.terminalInput.render();
|
|
2199
1561
|
return;
|
|
2200
1562
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
break;
|
|
2357
|
-
case '/init':
|
|
2358
|
-
this.handleInitCommand();
|
|
2359
|
-
break;
|
|
2360
|
-
case '/compact':
|
|
2361
|
-
await this.handleCompactCommand();
|
|
2362
|
-
break;
|
|
2363
|
-
default:
|
|
2364
|
-
if (!(await this.tryCustomSlashCommand(command, input))) {
|
|
2365
|
-
display.showWarning(`Unknown command "${command}".`);
|
|
2366
|
-
}
|
|
2367
|
-
break;
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
const streamingUiActive = this.isStreamingUiActive();
|
|
2371
|
-
const captureOptions = streamingUiActive
|
|
2372
|
-
? { includeStreaming: false, suppressTypes: ['normal'] }
|
|
2373
|
-
: undefined;
|
|
2374
|
-
let capturedOutput = '';
|
|
2375
|
-
try {
|
|
2376
|
-
const { output: outputBuffer } = await display.captureOutput(runCommand, captureOptions);
|
|
2377
|
-
capturedOutput = outputBuffer;
|
|
2378
|
-
}
|
|
2379
|
-
catch (error) {
|
|
2380
|
-
capturedOutput = error?.capturedOutput ?? capturedOutput;
|
|
2381
|
-
if (streamingUiActive) {
|
|
2382
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2383
|
-
const { output: errorOutput } = await display.captureOutput(() => display.showError(message, error), { includeStreaming: false, suppressTypes: ['normal'] });
|
|
2384
|
-
capturedOutput = capturedOutput || errorOutput || message;
|
|
2385
|
-
}
|
|
2386
|
-
else {
|
|
2387
|
-
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
|
|
2391
|
-
this.terminalInput.setInlineCommandPanel(panelContent);
|
|
2392
|
-
this.renderPromptArea();
|
|
2393
|
-
}
|
|
2394
|
-
buildInlineCommandPanel(command, output) {
|
|
2395
|
-
const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
|
|
2396
|
-
if (!normalized) {
|
|
2397
|
-
return null;
|
|
2398
|
-
}
|
|
2399
|
-
const header = theme.ui.muted(command);
|
|
2400
|
-
const body = normalized.split('\n');
|
|
2401
|
-
if (body.length === 1) {
|
|
2402
|
-
return [`${header} ${body[0] ?? ''}`.trimEnd()];
|
|
1563
|
+
switch (command) {
|
|
1564
|
+
case '/help':
|
|
1565
|
+
case '/?':
|
|
1566
|
+
this.showHelp();
|
|
1567
|
+
break;
|
|
1568
|
+
case '/features':
|
|
1569
|
+
this.showFeaturesMenu(input);
|
|
1570
|
+
break;
|
|
1571
|
+
case '/learn':
|
|
1572
|
+
this.showLearningStatus(input);
|
|
1573
|
+
break;
|
|
1574
|
+
case '/improve':
|
|
1575
|
+
void this.handleImprovementCommand(input);
|
|
1576
|
+
break;
|
|
1577
|
+
case '/model':
|
|
1578
|
+
this.showModelMenu();
|
|
1579
|
+
break;
|
|
1580
|
+
case '/exit':
|
|
1581
|
+
case '/quit':
|
|
1582
|
+
case '/q':
|
|
1583
|
+
this.shutdown();
|
|
1584
|
+
break;
|
|
1585
|
+
case '/secrets':
|
|
1586
|
+
this.showSecretsMenu();
|
|
1587
|
+
break;
|
|
1588
|
+
case '/tools':
|
|
1589
|
+
this.showToolsMenu();
|
|
1590
|
+
break;
|
|
1591
|
+
case '/mcp':
|
|
1592
|
+
await this.showMcpStatus();
|
|
1593
|
+
break;
|
|
1594
|
+
case '/doctor':
|
|
1595
|
+
this.runDoctor();
|
|
1596
|
+
break;
|
|
1597
|
+
case '/checks':
|
|
1598
|
+
await this.runRepoChecksCommand();
|
|
1599
|
+
break;
|
|
1600
|
+
case '/context':
|
|
1601
|
+
await this.refreshWorkspaceContextCommand(input);
|
|
1602
|
+
break;
|
|
1603
|
+
case '/agents':
|
|
1604
|
+
this.showAgentsMenu();
|
|
1605
|
+
break;
|
|
1606
|
+
case '/sessions':
|
|
1607
|
+
await this.handleSessionCommand(input);
|
|
1608
|
+
break;
|
|
1609
|
+
case '/skills':
|
|
1610
|
+
await this.handleSkillsCommand(input);
|
|
1611
|
+
break;
|
|
1612
|
+
case '/thinking':
|
|
1613
|
+
this.handleThinkingCommand(input);
|
|
1614
|
+
break;
|
|
1615
|
+
case '/autocontinue':
|
|
1616
|
+
this.handleAutoContinueCommand(input);
|
|
1617
|
+
break;
|
|
1618
|
+
case '/shortcuts':
|
|
1619
|
+
case '/keys':
|
|
1620
|
+
this.handleShortcutsCommand();
|
|
1621
|
+
break;
|
|
1622
|
+
case '/changes':
|
|
1623
|
+
case '/summary':
|
|
1624
|
+
this.showFileChangeSummary();
|
|
1625
|
+
break;
|
|
1626
|
+
case '/metrics':
|
|
1627
|
+
case '/stats':
|
|
1628
|
+
case '/perf':
|
|
1629
|
+
this.showAlphaZeroMetrics();
|
|
1630
|
+
break;
|
|
1631
|
+
case '/suggestions':
|
|
1632
|
+
case '/improve':
|
|
1633
|
+
this.showImprovementSuggestions();
|
|
1634
|
+
break;
|
|
1635
|
+
case '/plugins':
|
|
1636
|
+
this.showPluginStatus();
|
|
1637
|
+
break;
|
|
1638
|
+
case '/evolve':
|
|
1639
|
+
void this.handleEvolveCommand(input);
|
|
1640
|
+
break;
|
|
1641
|
+
case '/modular':
|
|
1642
|
+
case '/a0':
|
|
1643
|
+
void this.handleModularCommand(input);
|
|
1644
|
+
break;
|
|
1645
|
+
case '/offsec':
|
|
1646
|
+
void this.handleOffsecCommand(input);
|
|
1647
|
+
break;
|
|
1648
|
+
case '/test':
|
|
1649
|
+
case '/tests':
|
|
1650
|
+
void this.handleTestCommand(input);
|
|
1651
|
+
break;
|
|
1652
|
+
case '/provider':
|
|
1653
|
+
await this.handleProviderCommand(input);
|
|
1654
|
+
break;
|
|
1655
|
+
case '/providers':
|
|
1656
|
+
this.showConfiguredProviders();
|
|
1657
|
+
break;
|
|
1658
|
+
case '/local':
|
|
1659
|
+
await this.handleLocalCommand(input);
|
|
1660
|
+
break;
|
|
1661
|
+
case '/discover':
|
|
1662
|
+
await this.discoverModelsCommand();
|
|
1663
|
+
break;
|
|
1664
|
+
// Claude Code style commands
|
|
1665
|
+
case '/rewind':
|
|
1666
|
+
await this.handleRewindCommand(input);
|
|
1667
|
+
break;
|
|
1668
|
+
case '/memory':
|
|
1669
|
+
this.handleMemoryCommand(input);
|
|
1670
|
+
break;
|
|
1671
|
+
case '/vim':
|
|
1672
|
+
this.handleVimCommand();
|
|
1673
|
+
break;
|
|
1674
|
+
case '/output-style':
|
|
1675
|
+
this.handleOutputStyleCommand(input);
|
|
1676
|
+
break;
|
|
1677
|
+
case '/cost':
|
|
1678
|
+
this.handleCostCommand();
|
|
1679
|
+
break;
|
|
1680
|
+
case '/usage':
|
|
1681
|
+
this.handleUsageCommand();
|
|
1682
|
+
break;
|
|
1683
|
+
case '/clear':
|
|
1684
|
+
this.handleClearCommand();
|
|
1685
|
+
break;
|
|
1686
|
+
case '/resume':
|
|
1687
|
+
await this.handleResumeCommand(input);
|
|
1688
|
+
break;
|
|
1689
|
+
case '/export':
|
|
1690
|
+
this.handleExportCommand(input);
|
|
1691
|
+
break;
|
|
1692
|
+
case '/review':
|
|
1693
|
+
await this.handleReviewCommand();
|
|
1694
|
+
break;
|
|
1695
|
+
case '/security-review':
|
|
1696
|
+
await this.handleSecurityReviewCommand();
|
|
1697
|
+
break;
|
|
1698
|
+
case '/bug':
|
|
1699
|
+
this.handleBugCommand();
|
|
1700
|
+
break;
|
|
1701
|
+
case '/terminal-setup':
|
|
1702
|
+
this.handleTerminalSetupCommand();
|
|
1703
|
+
break;
|
|
1704
|
+
case '/permissions':
|
|
1705
|
+
this.handlePermissionsCommand();
|
|
1706
|
+
break;
|
|
1707
|
+
case '/init':
|
|
1708
|
+
this.handleInitCommand();
|
|
1709
|
+
break;
|
|
1710
|
+
case '/compact':
|
|
1711
|
+
await this.handleCompactCommand();
|
|
1712
|
+
break;
|
|
1713
|
+
default:
|
|
1714
|
+
if (!(await this.tryCustomSlashCommand(command, input))) {
|
|
1715
|
+
display.showWarning(`Unknown command "${command}".`);
|
|
1716
|
+
}
|
|
1717
|
+
break;
|
|
2403
1718
|
}
|
|
2404
|
-
|
|
1719
|
+
this.terminalInput.render();
|
|
2405
1720
|
}
|
|
2406
1721
|
async tryCustomSlashCommand(command, fullInput) {
|
|
2407
1722
|
const custom = this.customCommandMap.get(command);
|
|
@@ -2438,13 +1753,11 @@ export class InteractiveShell {
|
|
|
2438
1753
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2439
1754
|
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2440
1755
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2441
|
-
` ${theme.info('Option+A')} ${theme.ui.muted('Toggle AlphaZero RL mode')}`,
|
|
2442
1756
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
2443
1757
|
` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
|
|
2444
1758
|
'',
|
|
2445
1759
|
theme.bold(' Navigation'),
|
|
2446
1760
|
` ${theme.info('PageUp/PageDown')} ${theme.ui.muted('Scroll through output')}`,
|
|
2447
|
-
` ${theme.info('Escape')} ${theme.ui.muted('Stop streaming')}`,
|
|
2448
1761
|
` ${theme.info('Ctrl+C')} ${theme.ui.muted('Clear input or interrupt')}`,
|
|
2449
1762
|
` ${theme.info('Up/Down')} ${theme.ui.muted('Navigate command history')}`,
|
|
2450
1763
|
'',
|
|
@@ -2531,76 +1844,16 @@ export class InteractiveShell {
|
|
|
2531
1844
|
}
|
|
2532
1845
|
display.showInfo('Refreshing workspace snapshot...');
|
|
2533
1846
|
const context = buildWorkspaceContext(this.workingDir, this.workspaceOptions);
|
|
2534
|
-
const profileConfig = this.runtimeSession.refreshWorkspaceContext(context);
|
|
2535
|
-
const tools = this.runtimeSession.toolRuntime.listProviderTools();
|
|
2536
|
-
this.baseSystemPrompt = buildInteractiveSystemPrompt(profileConfig.systemPrompt, profileConfig.label, tools);
|
|
2537
|
-
if (this.rebuildAgent()) {
|
|
2538
|
-
display.showInfo(`Workspace snapshot refreshed (${this.describeWorkspaceOptions()}).`);
|
|
2539
|
-
this.resetChatBoxAfterModelSwap();
|
|
2540
|
-
}
|
|
2541
|
-
else {
|
|
2542
|
-
display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
showContextSummaryLog(input) {
|
|
2546
|
-
const agent = this.agent;
|
|
2547
|
-
if (!agent) {
|
|
2548
|
-
display.showWarning('No active agent session. Start one to inspect context summaries.');
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
const contextManager = agent.getContextManager();
|
|
2552
|
-
if (!contextManager?.getSummaryLog) {
|
|
2553
|
-
display.showWarning('Context summary logging is unavailable in this session.');
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2556
|
-
const tokens = input.trim().split(/\s+/).slice(1);
|
|
2557
|
-
let limit = 5;
|
|
2558
|
-
for (const token of tokens) {
|
|
2559
|
-
if (!token)
|
|
2560
|
-
continue;
|
|
2561
|
-
const normalized = token.toLowerCase();
|
|
2562
|
-
if (/^\d+$/.test(token)) {
|
|
2563
|
-
limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
|
|
2564
|
-
}
|
|
2565
|
-
else if (normalized.startsWith('limit=')) {
|
|
2566
|
-
const value = parseInt(normalized.split('=')[1] ?? '', 10);
|
|
2567
|
-
if (!Number.isNaN(value)) {
|
|
2568
|
-
limit = Math.min(Math.max(value, 1), 20);
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
const entries = contextManager.getSummaryLog(limit);
|
|
2573
|
-
if (!entries.length) {
|
|
2574
|
-
display.showInfo('No context summaries captured yet.');
|
|
2575
|
-
return;
|
|
1847
|
+
const profileConfig = this.runtimeSession.refreshWorkspaceContext(context);
|
|
1848
|
+
const tools = this.runtimeSession.toolRuntime.listProviderTools();
|
|
1849
|
+
this.baseSystemPrompt = buildInteractiveSystemPrompt(profileConfig.systemPrompt, profileConfig.label, tools);
|
|
1850
|
+
if (this.rebuildAgent()) {
|
|
1851
|
+
display.showInfo(`Workspace snapshot refreshed (${this.describeWorkspaceOptions()}).`);
|
|
1852
|
+
this.resetChatBoxAfterModelSwap();
|
|
2576
1853
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
for (const entry of entries) {
|
|
2580
|
-
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
2581
|
-
const headerParts = [
|
|
2582
|
-
theme.ui.muted(timestamp),
|
|
2583
|
-
theme.info(entry.method),
|
|
2584
|
-
`removed ${theme.error(String(entry.removed))}`,
|
|
2585
|
-
`kept ${theme.success(String(entry.preserved))}`,
|
|
2586
|
-
];
|
|
2587
|
-
if (entry.reason) {
|
|
2588
|
-
headerParts.push(theme.ui.muted(entry.reason));
|
|
2589
|
-
}
|
|
2590
|
-
if (entry.model) {
|
|
2591
|
-
headerParts.push(theme.ui.muted(`model=${entry.model}`));
|
|
2592
|
-
}
|
|
2593
|
-
lines.push(headerParts.join(' ยท '));
|
|
2594
|
-
if (entry.summary) {
|
|
2595
|
-
const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}โฆ` : entry.summary;
|
|
2596
|
-
lines.push(trimmed);
|
|
2597
|
-
}
|
|
2598
|
-
else {
|
|
2599
|
-
lines.push(theme.ui.muted('(no summary text โ simple prune)'));
|
|
2600
|
-
}
|
|
2601
|
-
lines.push(''); // spacer
|
|
1854
|
+
else {
|
|
1855
|
+
display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
|
|
2602
1856
|
}
|
|
2603
|
-
display.showSystemMessage(lines.join('\n'));
|
|
2604
1857
|
}
|
|
2605
1858
|
parseContextOverrideTokens(input) {
|
|
2606
1859
|
const overrides = {};
|
|
@@ -2761,11 +2014,12 @@ export class InteractiveShell {
|
|
|
2761
2014
|
handleThinkingCommand(input) {
|
|
2762
2015
|
const value = input.slice('/thinking'.length).trim().toLowerCase();
|
|
2763
2016
|
if (!value) {
|
|
2764
|
-
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [
|
|
2017
|
+
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [balanced|extended].` +
|
|
2018
|
+
' Press Tab any time to toggle.');
|
|
2765
2019
|
return;
|
|
2766
2020
|
}
|
|
2767
|
-
if (value !== '
|
|
2768
|
-
display.showWarning('Usage: /thinking [
|
|
2021
|
+
if (value !== 'balanced' && value !== 'extended') {
|
|
2022
|
+
display.showWarning('Usage: /thinking [balanced|extended]');
|
|
2769
2023
|
return;
|
|
2770
2024
|
}
|
|
2771
2025
|
if (this.isProcessing) {
|
|
@@ -2777,30 +2031,15 @@ export class InteractiveShell {
|
|
|
2777
2031
|
if (this.rebuildAgent()) {
|
|
2778
2032
|
this.resetChatBoxAfterModelSwap();
|
|
2779
2033
|
}
|
|
2034
|
+
this.refreshControlBar();
|
|
2780
2035
|
const descriptions = {
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
extended: 'Always emits a <thinking> block before the final response.',
|
|
2036
|
+
balanced: 'Balanced (default) reasoning with short thoughts only when helpful.',
|
|
2037
|
+
extended: 'Longer reasoning enabled; expect extra usage for deeper answers.',
|
|
2784
2038
|
};
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
if (!value || value === 'status') {
|
|
2790
|
-
const status = this.alphaZeroModeEnabled ? theme.success('on') : theme.ui.muted('off');
|
|
2791
|
-
const verification = this.verificationEnabled ? 'verification locked on' : 'verification optional';
|
|
2792
|
-
display.showInfo(`AlphaZero RL mode is ${status}. When enabled, difficult prompts use duel/self-critique and human-style verification (${verification}).`);
|
|
2793
|
-
return;
|
|
2794
|
-
}
|
|
2795
|
-
if (['on', 'enable', 'enabled'].includes(value)) {
|
|
2796
|
-
this.setAlphaZeroMode(true, 'command');
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
if (['off', 'disable', 'disabled'].includes(value)) {
|
|
2800
|
-
this.setAlphaZeroMode(false, 'command');
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
display.showWarning('Usage: /alphazero [on|off|status]');
|
|
2039
|
+
const headline = this.thinkingMode === 'extended'
|
|
2040
|
+
? `${theme.info('Thinking on')} (Tab to toggle)`
|
|
2041
|
+
: `${theme.info('Thinking off')} (Tab to toggle)`;
|
|
2042
|
+
display.showSystemMessage(`${headline}\n${theme.ui.muted(descriptions[this.thinkingMode])}`);
|
|
2804
2043
|
}
|
|
2805
2044
|
handleShortcutsCommand() {
|
|
2806
2045
|
// Display keyboard shortcuts help (Claude Code style)
|
|
@@ -2889,7 +2128,6 @@ export class InteractiveShell {
|
|
|
2889
2128
|
const updated = toggleFeatureFlag(matchedKey, newValue);
|
|
2890
2129
|
const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
|
|
2891
2130
|
display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
|
|
2892
|
-
this.refreshFeatureStatusDisplay();
|
|
2893
2131
|
display.showInfo('Changes will take effect on next launch or after /features refresh.');
|
|
2894
2132
|
return;
|
|
2895
2133
|
}
|
|
@@ -2902,7 +2140,6 @@ export class InteractiveShell {
|
|
|
2902
2140
|
}
|
|
2903
2141
|
saveFeatureFlags(updated);
|
|
2904
2142
|
display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
|
|
2905
|
-
this.refreshFeatureStatusDisplay();
|
|
2906
2143
|
return;
|
|
2907
2144
|
}
|
|
2908
2145
|
else {
|
|
@@ -4075,7 +3312,9 @@ export class InteractiveShell {
|
|
|
4075
3312
|
}
|
|
4076
3313
|
display.showInfo(`Deleted session "${summary.title}".`);
|
|
4077
3314
|
if (this.activeSessionId === summary.id) {
|
|
4078
|
-
this.
|
|
3315
|
+
this.activeSessionId = null;
|
|
3316
|
+
this.activeSessionTitle = null;
|
|
3317
|
+
saveSessionPreferences({ lastSessionId: null });
|
|
4079
3318
|
}
|
|
4080
3319
|
}
|
|
4081
3320
|
newSessionCommand(title) {
|
|
@@ -4095,7 +3334,6 @@ export class InteractiveShell {
|
|
|
4095
3334
|
clearAutosaveSnapshot(this.profile);
|
|
4096
3335
|
display.showInfo('Started a new empty session.');
|
|
4097
3336
|
this.refreshContextGauge();
|
|
4098
|
-
this.refreshFeatureStatusDisplay();
|
|
4099
3337
|
}
|
|
4100
3338
|
toggleAutosaveCommand(value) {
|
|
4101
3339
|
if (!value) {
|
|
@@ -4150,6 +3388,7 @@ export class InteractiveShell {
|
|
|
4150
3388
|
lines.push(' /rewind code Rewind code only (keep conversation)');
|
|
4151
3389
|
lines.push(' /rewind conv Rewind conversation only (keep code)');
|
|
4152
3390
|
lines.push('');
|
|
3391
|
+
lines.push(theme.ui.muted('Press Esc+Esc for quick access to the rewind menu'));
|
|
4153
3392
|
display.showSystemMessage(lines.join('\n'));
|
|
4154
3393
|
}
|
|
4155
3394
|
handleMemoryCommand(input) {
|
|
@@ -4168,6 +3407,7 @@ export class InteractiveShell {
|
|
|
4168
3407
|
lines.push(' - Use # prefix to quickly add notes to project memory');
|
|
4169
3408
|
lines.push(' - Import other files with @./relative/path syntax');
|
|
4170
3409
|
lines.push('');
|
|
3410
|
+
lines.push(theme.ui.muted('Create EROSOLAR.md with project coding standards for better results'));
|
|
4171
3411
|
display.showSystemMessage(lines.join('\n'));
|
|
4172
3412
|
}
|
|
4173
3413
|
handleVimCommand() {
|
|
@@ -4248,20 +3488,6 @@ export class InteractiveShell {
|
|
|
4248
3488
|
}
|
|
4249
3489
|
display.showSystemMessage(lines.join('\n'));
|
|
4250
3490
|
}
|
|
4251
|
-
async handleUpdateCommand() {
|
|
4252
|
-
display.showInfo('Checking for updates...');
|
|
4253
|
-
const lines = [];
|
|
4254
|
-
lines.push(theme.bold('Update Check'));
|
|
4255
|
-
lines.push('');
|
|
4256
|
-
lines.push(`Current version: ${this.version ?? 'unknown'}`);
|
|
4257
|
-
lines.push('');
|
|
4258
|
-
lines.push(theme.secondary('To update:'));
|
|
4259
|
-
lines.push(' npm install -g erosolar-cli@latest');
|
|
4260
|
-
lines.push(' or: npm update erosolar-cli');
|
|
4261
|
-
lines.push('');
|
|
4262
|
-
lines.push(theme.ui.muted('Auto-updates will be checked periodically.'));
|
|
4263
|
-
display.showSystemMessage(lines.join('\n'));
|
|
4264
|
-
}
|
|
4265
3491
|
handleClearCommand() {
|
|
4266
3492
|
if (this.agent) {
|
|
4267
3493
|
this.agent.clearHistory();
|
|
@@ -4270,7 +3496,7 @@ export class InteractiveShell {
|
|
|
4270
3496
|
display.clear();
|
|
4271
3497
|
clearAutosaveSnapshot(this.profile);
|
|
4272
3498
|
display.showInfo('Conversation cleared. Starting fresh.');
|
|
4273
|
-
this.
|
|
3499
|
+
this.terminalInput.render();
|
|
4274
3500
|
}
|
|
4275
3501
|
async handleResumeCommand(input) {
|
|
4276
3502
|
const tokens = input.split(/\s+/).slice(1);
|
|
@@ -4411,7 +3637,6 @@ export class InteractiveShell {
|
|
|
4411
3637
|
if (remember) {
|
|
4412
3638
|
saveSessionPreferences({ lastSessionId: summary?.id ?? null });
|
|
4413
3639
|
}
|
|
4414
|
-
this.refreshFeatureStatusDisplay();
|
|
4415
3640
|
}
|
|
4416
3641
|
resolveSessionBySelector(selector) {
|
|
4417
3642
|
const sessions = listSessions(this.profile);
|
|
@@ -4580,7 +3805,7 @@ export class InteractiveShell {
|
|
|
4580
3805
|
if (!providerOptions.length) {
|
|
4581
3806
|
display.showWarning('No providers are available.');
|
|
4582
3807
|
this.pendingInteraction = null;
|
|
4583
|
-
this.
|
|
3808
|
+
this.terminalInput.render();
|
|
4584
3809
|
return;
|
|
4585
3810
|
}
|
|
4586
3811
|
const lines = [
|
|
@@ -4601,7 +3826,7 @@ export class InteractiveShell {
|
|
|
4601
3826
|
catch (error) {
|
|
4602
3827
|
display.showError('Failed to load model list. Try again in a moment.', error);
|
|
4603
3828
|
this.pendingInteraction = null;
|
|
4604
|
-
this.
|
|
3829
|
+
this.terminalInput.render();
|
|
4605
3830
|
}
|
|
4606
3831
|
}
|
|
4607
3832
|
buildProviderOptions() {
|
|
@@ -4785,7 +4010,7 @@ export class InteractiveShell {
|
|
|
4785
4010
|
}
|
|
4786
4011
|
renderToolMenu(interaction) {
|
|
4787
4012
|
const lines = [
|
|
4788
|
-
theme.bold('Select which tools are enabled (changes apply on next launch)
|
|
4013
|
+
theme.bold('Select which tools are enabled (changes apply on next launch; default package is locked on).'),
|
|
4789
4014
|
...interaction.options.map((option, index) => this.formatToolOptionLine(option, index, interaction.selection)),
|
|
4790
4015
|
'',
|
|
4791
4016
|
'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
|
|
@@ -4794,13 +4019,23 @@ export class InteractiveShell {
|
|
|
4794
4019
|
}
|
|
4795
4020
|
formatToolOptionLine(option, index, selection) {
|
|
4796
4021
|
const enabled = selection.has(option.id);
|
|
4797
|
-
const checkbox =
|
|
4022
|
+
const checkbox = option.locked
|
|
4023
|
+
? theme.primary('[โ]')
|
|
4024
|
+
: enabled
|
|
4025
|
+
? theme.primary('[x]')
|
|
4026
|
+
: theme.ui.muted('[ ]');
|
|
4798
4027
|
const details = [option.description];
|
|
4799
4028
|
if (option.requiresSecret) {
|
|
4800
4029
|
const hasSecret = Boolean(getSecretValue(option.requiresSecret));
|
|
4801
4030
|
const status = hasSecret ? theme.success('API key set') : theme.warning('API key missing');
|
|
4802
4031
|
details.push(status);
|
|
4803
4032
|
}
|
|
4033
|
+
if (option.locked) {
|
|
4034
|
+
details.push(theme.ui.muted('Locked default package'));
|
|
4035
|
+
}
|
|
4036
|
+
if (option.restartRequired) {
|
|
4037
|
+
details.push(theme.ui.muted('Restart required'));
|
|
4038
|
+
}
|
|
4804
4039
|
const numberLabel = this.colorizeDropdownLine(`${index + 1}.`, index);
|
|
4805
4040
|
const optionLabel = this.colorizeDropdownLine(option.label, index);
|
|
4806
4041
|
const detailLine = this.colorizeDropdownLine(` ${details.join(' โข ')}`, index);
|
|
@@ -5146,29 +4381,29 @@ export class InteractiveShell {
|
|
|
5146
4381
|
const trimmed = input.trim();
|
|
5147
4382
|
if (!trimmed) {
|
|
5148
4383
|
display.showWarning('Enter a number or type cancel.');
|
|
5149
|
-
this.
|
|
4384
|
+
this.terminalInput.render();
|
|
5150
4385
|
return;
|
|
5151
4386
|
}
|
|
5152
4387
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5153
4388
|
this.pendingInteraction = null;
|
|
5154
4389
|
display.showInfo('Model selection cancelled.');
|
|
5155
|
-
this.
|
|
4390
|
+
this.terminalInput.render();
|
|
5156
4391
|
return;
|
|
5157
4392
|
}
|
|
5158
4393
|
const choice = Number.parseInt(trimmed, 10);
|
|
5159
4394
|
if (!Number.isFinite(choice)) {
|
|
5160
4395
|
display.showWarning('Please enter a valid number.');
|
|
5161
|
-
this.
|
|
4396
|
+
this.terminalInput.render();
|
|
5162
4397
|
return;
|
|
5163
4398
|
}
|
|
5164
4399
|
const option = pending.options[choice - 1];
|
|
5165
4400
|
if (!option) {
|
|
5166
4401
|
display.showWarning('That option is not available.');
|
|
5167
|
-
this.
|
|
4402
|
+
this.terminalInput.render();
|
|
5168
4403
|
return;
|
|
5169
4404
|
}
|
|
5170
4405
|
this.showProviderModels(option);
|
|
5171
|
-
this.
|
|
4406
|
+
this.terminalInput.render();
|
|
5172
4407
|
}
|
|
5173
4408
|
async handleModelSelection(input) {
|
|
5174
4409
|
const pending = this.pendingInteraction;
|
|
@@ -5178,35 +4413,35 @@ export class InteractiveShell {
|
|
|
5178
4413
|
const trimmed = input.trim();
|
|
5179
4414
|
if (!trimmed) {
|
|
5180
4415
|
display.showWarning('Enter a number, type "back", or type "cancel".');
|
|
5181
|
-
this.
|
|
4416
|
+
this.terminalInput.render();
|
|
5182
4417
|
return;
|
|
5183
4418
|
}
|
|
5184
4419
|
if (trimmed.toLowerCase() === 'back') {
|
|
5185
4420
|
this.showModelMenu();
|
|
5186
|
-
this.
|
|
4421
|
+
this.terminalInput.render();
|
|
5187
4422
|
return;
|
|
5188
4423
|
}
|
|
5189
4424
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5190
4425
|
this.pendingInteraction = null;
|
|
5191
4426
|
display.showInfo('Model selection cancelled.');
|
|
5192
|
-
this.
|
|
4427
|
+
this.terminalInput.render();
|
|
5193
4428
|
return;
|
|
5194
4429
|
}
|
|
5195
4430
|
const choice = Number.parseInt(trimmed, 10);
|
|
5196
4431
|
if (!Number.isFinite(choice)) {
|
|
5197
4432
|
display.showWarning('Please enter a valid number.');
|
|
5198
|
-
this.
|
|
4433
|
+
this.terminalInput.render();
|
|
5199
4434
|
return;
|
|
5200
4435
|
}
|
|
5201
4436
|
const preset = pending.options[choice - 1];
|
|
5202
4437
|
if (!preset) {
|
|
5203
4438
|
display.showWarning('That option is not available.');
|
|
5204
|
-
this.
|
|
4439
|
+
this.terminalInput.render();
|
|
5205
4440
|
return;
|
|
5206
4441
|
}
|
|
5207
4442
|
this.pendingInteraction = null;
|
|
5208
4443
|
await this.applyModelPreset(preset);
|
|
5209
|
-
this.
|
|
4444
|
+
this.terminalInput.render();
|
|
5210
4445
|
}
|
|
5211
4446
|
async applyModelPreset(preset) {
|
|
5212
4447
|
try {
|
|
@@ -5226,7 +4461,7 @@ export class InteractiveShell {
|
|
|
5226
4461
|
this.applyPresetReasoningDefaults();
|
|
5227
4462
|
if (this.rebuildAgent()) {
|
|
5228
4463
|
display.showInfo(`Switched to ${preset.label}.`);
|
|
5229
|
-
this.
|
|
4464
|
+
this.refreshSessionContext();
|
|
5230
4465
|
this.persistSessionPreference();
|
|
5231
4466
|
this.resetChatBoxAfterModelSwap();
|
|
5232
4467
|
}
|
|
@@ -5239,30 +4474,30 @@ export class InteractiveShell {
|
|
|
5239
4474
|
const trimmed = input.trim();
|
|
5240
4475
|
if (!trimmed) {
|
|
5241
4476
|
display.showWarning('Enter a number or type cancel.');
|
|
5242
|
-
this.
|
|
4477
|
+
this.terminalInput.render();
|
|
5243
4478
|
return;
|
|
5244
4479
|
}
|
|
5245
4480
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5246
4481
|
this.pendingInteraction = null;
|
|
5247
4482
|
display.showInfo('Secret management cancelled.');
|
|
5248
|
-
this.
|
|
4483
|
+
this.terminalInput.render();
|
|
5249
4484
|
return;
|
|
5250
4485
|
}
|
|
5251
4486
|
const choice = Number.parseInt(trimmed, 10);
|
|
5252
4487
|
if (!Number.isFinite(choice)) {
|
|
5253
4488
|
display.showWarning('Please enter a valid number.');
|
|
5254
|
-
this.
|
|
4489
|
+
this.terminalInput.render();
|
|
5255
4490
|
return;
|
|
5256
4491
|
}
|
|
5257
4492
|
const secret = pending.options[choice - 1];
|
|
5258
4493
|
if (!secret) {
|
|
5259
4494
|
display.showWarning('That option is not available.');
|
|
5260
|
-
this.
|
|
4495
|
+
this.terminalInput.render();
|
|
5261
4496
|
return;
|
|
5262
4497
|
}
|
|
5263
4498
|
display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
|
|
5264
4499
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
5265
|
-
this.
|
|
4500
|
+
this.terminalInput.render();
|
|
5266
4501
|
}
|
|
5267
4502
|
async handleSecretInput(input) {
|
|
5268
4503
|
const pending = this.pendingInteraction;
|
|
@@ -5272,16 +4507,14 @@ export class InteractiveShell {
|
|
|
5272
4507
|
const trimmed = input.trim();
|
|
5273
4508
|
if (!trimmed) {
|
|
5274
4509
|
display.showWarning('Enter a value or type cancel.');
|
|
5275
|
-
this.
|
|
4510
|
+
this.terminalInput.render();
|
|
5276
4511
|
return;
|
|
5277
4512
|
}
|
|
5278
4513
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5279
4514
|
this.pendingInteraction = null;
|
|
5280
4515
|
this.pendingSecretRetry = null;
|
|
5281
|
-
this.apiKeyGateActive = false;
|
|
5282
4516
|
display.showInfo('Secret unchanged.');
|
|
5283
|
-
this.
|
|
5284
|
-
this.scheduleQueueProcessing();
|
|
4517
|
+
this.terminalInput.render();
|
|
5285
4518
|
return;
|
|
5286
4519
|
}
|
|
5287
4520
|
try {
|
|
@@ -5290,7 +4523,6 @@ export class InteractiveShell {
|
|
|
5290
4523
|
this.pendingInteraction = null;
|
|
5291
4524
|
const deferred = this.pendingSecretRetry;
|
|
5292
4525
|
this.pendingSecretRetry = null;
|
|
5293
|
-
this.apiKeyGateActive = false;
|
|
5294
4526
|
if (pending.secret.providers.includes(this.sessionState.provider)) {
|
|
5295
4527
|
if (this.rebuildAgent()) {
|
|
5296
4528
|
this.resetChatBoxAfterModelSwap();
|
|
@@ -5305,14 +4537,12 @@ export class InteractiveShell {
|
|
|
5305
4537
|
display.showError(message);
|
|
5306
4538
|
this.pendingInteraction = null;
|
|
5307
4539
|
this.pendingSecretRetry = null;
|
|
5308
|
-
this.apiKeyGateActive = false;
|
|
5309
4540
|
}
|
|
5310
|
-
this.
|
|
5311
|
-
this.scheduleQueueProcessing();
|
|
4541
|
+
this.terminalInput.render();
|
|
5312
4542
|
}
|
|
5313
|
-
async processRequest(
|
|
4543
|
+
async processRequest(request) {
|
|
5314
4544
|
if (this.isProcessing) {
|
|
5315
|
-
this.enqueueFollowUpAction({ type: 'request', text:
|
|
4545
|
+
this.enqueueFollowUpAction({ type: 'request', text: request });
|
|
5316
4546
|
return;
|
|
5317
4547
|
}
|
|
5318
4548
|
if (!this.agent && !this.rebuildAgent()) {
|
|
@@ -5323,57 +4553,29 @@ export class InteractiveShell {
|
|
|
5323
4553
|
if (!agent) {
|
|
5324
4554
|
return;
|
|
5325
4555
|
}
|
|
5326
|
-
this.
|
|
5327
|
-
this.resetAssistantStreamTracking();
|
|
5328
|
-
const alphaZeroEngaged = this.alphaZeroModeEnabled;
|
|
5329
|
-
const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
|
|
5330
|
-
const requestForAgent = alphaZeroEngaged
|
|
5331
|
-
? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
|
|
5332
|
-
: userRequest;
|
|
5333
|
-
const skipReflectionForThisRun = this.skipNextAutoReflection;
|
|
5334
|
-
this.skipNextAutoReflection = false;
|
|
5335
|
-
const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
|
|
5336
|
-
const alphaZeroStatusId = 'alpha-zero';
|
|
5337
|
-
let alphaZeroStatusApplied = false;
|
|
5338
|
-
let alphaZeroTaskStarted = false;
|
|
5339
|
-
let alphaZeroTaskCompleted = false;
|
|
5340
|
-
if (alphaZeroEngaged) {
|
|
5341
|
-
const detail = alphaZeroDifficult ? 'Difficult request detected' : 'Reinforcement loop enabled';
|
|
5342
|
-
this.statusTracker.pushOverride(alphaZeroStatusId, 'AlphaZero RL active', {
|
|
5343
|
-
detail: `${detail} ยท full verification`,
|
|
5344
|
-
tone: 'info',
|
|
5345
|
-
});
|
|
5346
|
-
alphaZeroStatusApplied = true;
|
|
5347
|
-
display.showInfo(`AlphaZero RL mode engaged${alphaZeroDifficult ? ' for a difficult request' : ''}. Duel + self-critique with verification enabled.`);
|
|
5348
|
-
this.alphaZeroMetrics.startAlphaZeroTask(userRequest);
|
|
5349
|
-
alphaZeroTaskStarted = true;
|
|
5350
|
-
}
|
|
5351
|
-
this.logUserPrompt(userRequest);
|
|
4556
|
+
this.logUserPrompt(request);
|
|
5352
4557
|
this.isProcessing = true;
|
|
5353
4558
|
this.uiUpdates.setMode('processing');
|
|
5354
4559
|
this.terminalInput.setStreaming(true);
|
|
5355
4560
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5356
|
-
this.
|
|
4561
|
+
this.terminalInput.forceRender();
|
|
5357
4562
|
const requestStartTime = Date.now(); // Alpha Zero 2 timing
|
|
5358
|
-
this.lastRequestStartedAt = requestStartTime;
|
|
5359
|
-
this.lastToolSummaryRenderedAt = null;
|
|
5360
4563
|
// Clear previous parallel agents and start fresh for new request
|
|
5361
4564
|
const parallelManager = getParallelAgentManager();
|
|
5362
4565
|
parallelManager.clear();
|
|
5363
4566
|
parallelManager.startBatch();
|
|
5364
4567
|
// AlphaZero: Track task for learning
|
|
5365
|
-
this.lastUserQuery =
|
|
5366
|
-
this.currentTaskType = classifyTaskType(
|
|
4568
|
+
this.lastUserQuery = request;
|
|
4569
|
+
this.currentTaskType = classifyTaskType(request);
|
|
5367
4570
|
this.currentToolCalls = [];
|
|
5368
4571
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5369
4572
|
this.setProcessingStatus();
|
|
5370
4573
|
let responseText = '';
|
|
5371
|
-
let detectedFailure = null;
|
|
5372
|
-
let hadUnhandledError = false;
|
|
5373
4574
|
try {
|
|
5374
4575
|
// Start streaming - no header needed, the input area already provides context
|
|
5375
|
-
this.startStreamingHeartbeat(
|
|
5376
|
-
responseText = await agent.send(
|
|
4576
|
+
this.startStreamingHeartbeat('Streaming response');
|
|
4577
|
+
responseText = await agent.send(request, true);
|
|
4578
|
+
this.finishStreamingFormatter();
|
|
5377
4579
|
await this.awaitPendingCleanup();
|
|
5378
4580
|
this.captureHistorySnapshot();
|
|
5379
4581
|
this.autosaveIfEnabled();
|
|
@@ -5392,18 +4594,14 @@ export class InteractiveShell {
|
|
|
5392
4594
|
duration: 0,
|
|
5393
4595
|
}));
|
|
5394
4596
|
// AlphaZero: Check for failure in response
|
|
5395
|
-
|
|
4597
|
+
const failure = detectFailure(responseText, {
|
|
5396
4598
|
toolCalls: this.currentToolCalls,
|
|
5397
|
-
userMessage:
|
|
4599
|
+
userMessage: request,
|
|
5398
4600
|
});
|
|
5399
|
-
if (
|
|
5400
|
-
this.
|
|
5401
|
-
alphaZeroTaskCompleted = true;
|
|
5402
|
-
}
|
|
5403
|
-
if (detectedFailure) {
|
|
5404
|
-
this.lastFailure = detectedFailure;
|
|
4601
|
+
if (failure) {
|
|
4602
|
+
this.lastFailure = failure;
|
|
5405
4603
|
// Check if we have a recovery strategy
|
|
5406
|
-
const strategy = findRecoveryStrategy(
|
|
4604
|
+
const strategy = findRecoveryStrategy(failure);
|
|
5407
4605
|
if (strategy) {
|
|
5408
4606
|
display.showSystemMessage(`๐ Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
|
|
5409
4607
|
}
|
|
@@ -5426,55 +4624,28 @@ export class InteractiveShell {
|
|
|
5426
4624
|
}
|
|
5427
4625
|
}
|
|
5428
4626
|
catch (error) {
|
|
5429
|
-
const handled = this.handleProviderError(error, () => this.processRequest(
|
|
5430
|
-
hadUnhandledError = !handled;
|
|
5431
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5432
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5433
|
-
alphaZeroTaskCompleted = true;
|
|
5434
|
-
}
|
|
4627
|
+
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
5435
4628
|
if (!handled) {
|
|
5436
4629
|
// Pass full error object for enhanced formatting with stack trace
|
|
5437
4630
|
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
5438
4631
|
}
|
|
5439
4632
|
}
|
|
5440
4633
|
finally {
|
|
5441
|
-
|
|
5442
|
-
alphaZeroEngaged,
|
|
5443
|
-
alphaZeroDifficult,
|
|
5444
|
-
failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
|
|
5445
|
-
});
|
|
5446
|
-
this.lastRunLog = runLogEntry;
|
|
5447
|
-
const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
|
|
5448
|
-
let shouldStopAuto = false;
|
|
5449
|
-
if (this.alphaZeroAutoImproveActive) {
|
|
5450
|
-
shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
|
|
5451
|
-
if (shouldStopAuto) {
|
|
5452
|
-
this.alphaZeroAutoImproveActive = false;
|
|
5453
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
5454
|
-
}
|
|
5455
|
-
}
|
|
5456
|
-
if (!skipReflectionForThisRun && !shouldStopAuto) {
|
|
5457
|
-
this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
|
|
5458
|
-
}
|
|
5459
|
-
if (alphaZeroEngaged && alphaZeroStatusApplied) {
|
|
5460
|
-
this.statusTracker.clearOverride(alphaZeroStatusId);
|
|
5461
|
-
}
|
|
5462
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5463
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5464
|
-
}
|
|
4634
|
+
this.finishStreamingFormatter();
|
|
5465
4635
|
display.stopThinking(false);
|
|
5466
4636
|
this.uiUpdates.setMode('processing');
|
|
5467
|
-
this.stopStreamingHeartbeat(
|
|
4637
|
+
this.stopStreamingHeartbeat();
|
|
5468
4638
|
this.isProcessing = false;
|
|
5469
4639
|
this.terminalInput.setStreaming(false);
|
|
5470
4640
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5471
4641
|
this.setIdleStatus();
|
|
4642
|
+
display.newLine();
|
|
5472
4643
|
this.updateStatusMessage(null);
|
|
5473
4644
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5474
4645
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5475
4646
|
// Claude Code style: New prompt naturally appears at bottom
|
|
5476
4647
|
this.ensureReadlineReady();
|
|
5477
|
-
this.
|
|
4648
|
+
this.terminalInput.render();
|
|
5478
4649
|
this.scheduleQueueProcessing();
|
|
5479
4650
|
this.refreshQueueIndicators();
|
|
5480
4651
|
}
|
|
@@ -5505,7 +4676,6 @@ export class InteractiveShell {
|
|
|
5505
4676
|
if (!agent) {
|
|
5506
4677
|
return;
|
|
5507
4678
|
}
|
|
5508
|
-
this.hasShownThoughtProcess = false;
|
|
5509
4679
|
this.isProcessing = true;
|
|
5510
4680
|
this.uiUpdates.setMode('processing');
|
|
5511
4681
|
this.terminalInput.setStreaming(true);
|
|
@@ -5546,8 +4716,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5546
4716
|
}
|
|
5547
4717
|
while (iteration < MAX_ITERATIONS) {
|
|
5548
4718
|
iteration++;
|
|
5549
|
-
this.hasShownThoughtProcess = false;
|
|
5550
|
-
this.resetAssistantStreamTracking();
|
|
5551
4719
|
display.showSystemMessage(`\n๐ Iteration ${iteration}/${MAX_ITERATIONS}`);
|
|
5552
4720
|
this.updateStatusMessage(`Working on iteration ${iteration}...`);
|
|
5553
4721
|
try {
|
|
@@ -5555,6 +4723,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5555
4723
|
display.showThinking('Responding...');
|
|
5556
4724
|
this.refreshStatusLine(true);
|
|
5557
4725
|
const response = await agent.send(currentPrompt, true);
|
|
4726
|
+
this.finishStreamingFormatter();
|
|
5558
4727
|
await this.awaitPendingCleanup();
|
|
5559
4728
|
this.captureHistorySnapshot();
|
|
5560
4729
|
this.autosaveIfEnabled();
|
|
@@ -5687,6 +4856,7 @@ What's the next action?`;
|
|
|
5687
4856
|
}
|
|
5688
4857
|
}
|
|
5689
4858
|
finally {
|
|
4859
|
+
this.finishStreamingFormatter();
|
|
5690
4860
|
const totalElapsed = Date.now() - overallStartTime;
|
|
5691
4861
|
const minutes = Math.floor(totalElapsed / 60000);
|
|
5692
4862
|
const seconds = Math.floor((totalElapsed % 60000) / 1000);
|
|
@@ -5700,6 +4870,10 @@ What's the next action?`;
|
|
|
5700
4870
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5701
4871
|
this.setIdleStatus();
|
|
5702
4872
|
this.updateStatusMessage(null);
|
|
4873
|
+
display.newLine();
|
|
4874
|
+
// Claude Code style: Show unified status bar before prompt
|
|
4875
|
+
// This creates consistent UI between startup and post-streaming
|
|
4876
|
+
this.showUnifiedStatusBar();
|
|
5703
4877
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5704
4878
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5705
4879
|
// Claude Code style: New prompt naturally appears at bottom
|
|
@@ -5878,25 +5052,7 @@ What's the next action?`;
|
|
|
5878
5052
|
}
|
|
5879
5053
|
if (name === 'bash' || name === 'execute_bash') {
|
|
5880
5054
|
const command = String(entry.args['command'] ?? '').toLowerCase();
|
|
5881
|
-
|
|
5882
|
-
'npm test',
|
|
5883
|
-
'yarn test',
|
|
5884
|
-
'pnpm test',
|
|
5885
|
-
'bun test',
|
|
5886
|
-
'go test',
|
|
5887
|
-
'cargo test',
|
|
5888
|
-
'pytest',
|
|
5889
|
-
'python -m pytest',
|
|
5890
|
-
'tox',
|
|
5891
|
-
'mvn test',
|
|
5892
|
-
'./mvnw test',
|
|
5893
|
-
'gradle test',
|
|
5894
|
-
'./gradlew test',
|
|
5895
|
-
'dotnet test',
|
|
5896
|
-
'make test',
|
|
5897
|
-
'swift test',
|
|
5898
|
-
];
|
|
5899
|
-
return patterns.some((pattern) => command.includes(pattern));
|
|
5055
|
+
return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
|
|
5900
5056
|
}
|
|
5901
5057
|
return false;
|
|
5902
5058
|
}
|
|
@@ -5914,170 +5070,310 @@ What's the next action?`;
|
|
|
5914
5070
|
const parts = [error?.stdout, error?.stderr, error?.message].filter((part) => typeof part === 'string' && part.trim());
|
|
5915
5071
|
return parts.join('\n').trim();
|
|
5916
5072
|
}
|
|
5917
|
-
|
|
5918
|
-
if (
|
|
5919
|
-
return
|
|
5073
|
+
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
5074
|
+
if (!this.verificationEnabled) {
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
void (async () => {
|
|
5078
|
+
try {
|
|
5079
|
+
const buildOk = await this.enforceAutoBuild(trigger);
|
|
5080
|
+
if (!buildOk) {
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
await this.enforceAutoTests(trigger, assistantResponse, verificationContext);
|
|
5084
|
+
}
|
|
5085
|
+
catch (error) {
|
|
5086
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5087
|
+
display.showWarning(`Auto quality checks failed: ${message}`);
|
|
5088
|
+
}
|
|
5089
|
+
})();
|
|
5090
|
+
}
|
|
5091
|
+
async enforceAutoTests(trigger, assistantResponse, verificationContext) {
|
|
5092
|
+
if (this.autoTestInFlight) {
|
|
5093
|
+
return;
|
|
5094
|
+
}
|
|
5095
|
+
if (!this.verificationEnabled) {
|
|
5096
|
+
return;
|
|
5920
5097
|
}
|
|
5921
5098
|
const latestChange = this.getLatestFileChangeTimestamp();
|
|
5922
5099
|
if (!latestChange) {
|
|
5923
|
-
return
|
|
5100
|
+
return;
|
|
5924
5101
|
}
|
|
5925
5102
|
const latestTest = this.getLatestTestTimestamp();
|
|
5926
5103
|
if (latestTest && latestChange <= latestTest) {
|
|
5927
|
-
return
|
|
5104
|
+
return;
|
|
5928
5105
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
display.showSystemMessage('โน๏ธ Skipping auto-tests: no test command detected for this repo.');
|
|
5933
|
-
return 'skipped';
|
|
5106
|
+
if (!this.lastBuildSucceededAt || this.lastBuildSucceededAt < latestChange) {
|
|
5107
|
+
display.showSystemMessage('โญ๏ธ Skipping auto-tests because no successful build is recorded for the latest changes.');
|
|
5108
|
+
return;
|
|
5934
5109
|
}
|
|
5935
5110
|
this.autoTestInFlight = true;
|
|
5936
|
-
const command =
|
|
5937
|
-
|
|
5938
|
-
display.showSystemMessage(`๐งช Auto-testing recent changes (${trigger}) with "${command}"${reasonSuffix}...`);
|
|
5111
|
+
const command = 'npm test -- --runInBand';
|
|
5112
|
+
display.showSystemMessage(`๐งช Auto-testing recent changes (${trigger}) with "${command}"...`);
|
|
5939
5113
|
this.updateStatusMessage('Running tests automatically...');
|
|
5114
|
+
let combinedOutput = '';
|
|
5940
5115
|
try {
|
|
5941
5116
|
const { stdout, stderr } = await execAsync(command, {
|
|
5942
5117
|
cwd: this.workingDir,
|
|
5943
5118
|
timeout: 10 * 60 * 1000,
|
|
5944
5119
|
maxBuffer: 10 * 1024 * 1024,
|
|
5945
5120
|
});
|
|
5946
|
-
|
|
5947
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5121
|
+
combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5948
5122
|
display.showSystemMessage('โ
Auto-tests finished.');
|
|
5949
|
-
if (outputText) {
|
|
5950
|
-
this.writeLocked(`${outputText}\n`);
|
|
5951
|
-
}
|
|
5952
5123
|
this.statusTracker.clearOverride('tests');
|
|
5953
|
-
return 'success';
|
|
5954
5124
|
}
|
|
5955
5125
|
catch (error) {
|
|
5956
|
-
|
|
5957
|
-
const message = this.formatCommandError(error);
|
|
5126
|
+
combinedOutput = this.formatCommandError(error);
|
|
5958
5127
|
display.showWarning('โ ๏ธ Auto-tests failed. Review output below.');
|
|
5959
|
-
if (message) {
|
|
5960
|
-
this.writeLocked(`${message}\n`);
|
|
5961
|
-
}
|
|
5962
5128
|
this.statusTracker.pushOverride('tests', 'Tests failing', {
|
|
5963
|
-
detail:
|
|
5129
|
+
detail: 'Auto-run npm test failed',
|
|
5964
5130
|
tone: 'danger',
|
|
5965
5131
|
});
|
|
5966
|
-
return 'failed';
|
|
5967
5132
|
}
|
|
5968
5133
|
finally {
|
|
5134
|
+
if (combinedOutput) {
|
|
5135
|
+
this.writeLocked(`${combinedOutput}\n`);
|
|
5136
|
+
}
|
|
5137
|
+
try {
|
|
5138
|
+
await this.runAIDesignedTests(trigger, assistantResponse, verificationContext);
|
|
5139
|
+
}
|
|
5140
|
+
catch (error) {
|
|
5141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5142
|
+
display.showWarning(`AI-designed tests failed: ${message}`);
|
|
5143
|
+
}
|
|
5144
|
+
this.lastAutoTestRun = Date.now();
|
|
5969
5145
|
this.updateStatusMessage(null);
|
|
5970
5146
|
this.autoTestInFlight = false;
|
|
5971
|
-
this.terminalInput.resetContentPosition();
|
|
5972
|
-
this.terminalInput.forceRender();
|
|
5973
5147
|
}
|
|
5974
5148
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5149
|
+
/**
|
|
5150
|
+
* Auto-build verification after file edits.
|
|
5151
|
+
* Runs `npm run build` to catch TypeScript errors and feeds failures back to the agent.
|
|
5152
|
+
*/
|
|
5153
|
+
async enforceAutoBuild(trigger) {
|
|
5154
|
+
if (this.autoBuildPromise) {
|
|
5155
|
+
return this.autoBuildPromise;
|
|
5156
|
+
}
|
|
5157
|
+
if (!this.verificationEnabled) {
|
|
5158
|
+
return false;
|
|
5159
|
+
}
|
|
5160
|
+
const latestChange = this.getLatestFileChangeTimestamp();
|
|
5161
|
+
if (!latestChange) {
|
|
5978
5162
|
return true;
|
|
5979
5163
|
}
|
|
5980
|
-
if (
|
|
5981
|
-
|
|
5982
|
-
const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
|
|
5983
|
-
const patterns = [
|
|
5984
|
-
'npm run build',
|
|
5985
|
-
'yarn build',
|
|
5986
|
-
'pnpm build',
|
|
5987
|
-
'bun run build',
|
|
5988
|
-
'bun build',
|
|
5989
|
-
'tsc',
|
|
5990
|
-
'cargo build',
|
|
5991
|
-
'go build',
|
|
5992
|
-
'python -m build',
|
|
5993
|
-
'mvn -b -dskiptests package',
|
|
5994
|
-
'mvn package',
|
|
5995
|
-
'mvn install',
|
|
5996
|
-
'./mvnw -b -dskiptests package',
|
|
5997
|
-
'./mvnw package',
|
|
5998
|
-
'./mvnw install',
|
|
5999
|
-
'gradle build',
|
|
6000
|
-
'gradle assemble',
|
|
6001
|
-
'./gradlew build',
|
|
6002
|
-
'./gradlew assemble',
|
|
6003
|
-
'dotnet build',
|
|
6004
|
-
'swift build',
|
|
6005
|
-
];
|
|
6006
|
-
return makeBuild || patterns.some((pattern) => command.includes(pattern));
|
|
5164
|
+
if (this.lastBuildSucceededAt && latestChange <= this.lastBuildSucceededAt) {
|
|
5165
|
+
return true;
|
|
6007
5166
|
}
|
|
6008
|
-
|
|
5167
|
+
const command = 'npm run build';
|
|
5168
|
+
const runner = (async () => {
|
|
5169
|
+
this.autoBuildInFlight = true;
|
|
5170
|
+
display.showSystemMessage(`๐จ Auto-building to verify changes (${trigger})...`);
|
|
5171
|
+
this.updateStatusMessage('Running build automatically...');
|
|
5172
|
+
try {
|
|
5173
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
5174
|
+
cwd: this.workingDir,
|
|
5175
|
+
timeout: 5 * 60 * 1000,
|
|
5176
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
5177
|
+
});
|
|
5178
|
+
const finishedAt = Date.now();
|
|
5179
|
+
this.lastAutoBuildRun = finishedAt;
|
|
5180
|
+
this.lastBuildSucceededAt = finishedAt;
|
|
5181
|
+
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5182
|
+
display.showSystemMessage('โ
Build succeeded.');
|
|
5183
|
+
if (outputText && outputText.length < 500) {
|
|
5184
|
+
this.writeLocked(`${outputText}\n`);
|
|
5185
|
+
}
|
|
5186
|
+
this.statusTracker.clearOverride('build');
|
|
5187
|
+
return true;
|
|
5188
|
+
}
|
|
5189
|
+
catch (error) {
|
|
5190
|
+
const finishedAt = Date.now();
|
|
5191
|
+
this.lastAutoBuildRun = finishedAt;
|
|
5192
|
+
this.lastBuildSucceededAt = null;
|
|
5193
|
+
const errorOutput = this.formatCommandError(error);
|
|
5194
|
+
display.showWarning('โ ๏ธ Build failed. Feeding errors back to agent...');
|
|
5195
|
+
if (errorOutput) {
|
|
5196
|
+
this.writeLocked(`${errorOutput}\n`);
|
|
5197
|
+
}
|
|
5198
|
+
this.statusTracker.pushOverride('build', 'Build failing', {
|
|
5199
|
+
detail: 'Auto-run npm run build failed',
|
|
5200
|
+
tone: 'danger',
|
|
5201
|
+
});
|
|
5202
|
+
// Feed build errors back to the agent so it can fix them
|
|
5203
|
+
await this.feedBuildErrorsToAgent(errorOutput);
|
|
5204
|
+
return false;
|
|
5205
|
+
}
|
|
5206
|
+
finally {
|
|
5207
|
+
this.updateStatusMessage(null);
|
|
5208
|
+
this.autoBuildInFlight = false;
|
|
5209
|
+
this.autoBuildPromise = null;
|
|
5210
|
+
}
|
|
5211
|
+
})();
|
|
5212
|
+
this.autoBuildPromise = runner;
|
|
5213
|
+
return runner;
|
|
6009
5214
|
}
|
|
6010
|
-
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
5215
|
+
describeRecentChanges() {
|
|
5216
|
+
const changes = this._fileChangeTracker.getAllChanges();
|
|
5217
|
+
if (!changes.length) {
|
|
5218
|
+
return 'No tracked changes';
|
|
5219
|
+
}
|
|
5220
|
+
const items = changes.slice(0, 8).map((change) => {
|
|
5221
|
+
const additions = change.additions ? `+${change.additions}` : '';
|
|
5222
|
+
const removals = change.removals ? `-${change.removals}` : '';
|
|
5223
|
+
const delta = [additions, removals].filter(Boolean).join('/');
|
|
5224
|
+
const deltaLabel = delta ? ` ${delta}` : '';
|
|
5225
|
+
return `${change.path} (${change.type}${deltaLabel})`;
|
|
5226
|
+
});
|
|
5227
|
+
return items.join('; ');
|
|
5228
|
+
}
|
|
5229
|
+
describePackageScripts() {
|
|
5230
|
+
try {
|
|
5231
|
+
const pkgPath = join(this.workingDir, 'package.json');
|
|
5232
|
+
if (!existsSync(pkgPath)) {
|
|
5233
|
+
return 'package.json not found';
|
|
5234
|
+
}
|
|
5235
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
5236
|
+
const scripts = pkg.scripts ? Object.keys(pkg.scripts) : [];
|
|
5237
|
+
if (!scripts.length) {
|
|
5238
|
+
return 'package.json present without scripts';
|
|
6016
5239
|
}
|
|
5240
|
+
return `package.json scripts: ${scripts.slice(0, 8).join(', ')}`;
|
|
5241
|
+
}
|
|
5242
|
+
catch {
|
|
5243
|
+
return 'package.json present but unreadable';
|
|
6017
5244
|
}
|
|
6018
|
-
return latest;
|
|
6019
5245
|
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
*/
|
|
6024
|
-
async enforceAutoBuild(trigger, commandInfo) {
|
|
6025
|
-
if (this.autoBuildInFlight || !this.verificationEnabled) {
|
|
6026
|
-
return 'skipped';
|
|
5246
|
+
parseAIDesignedTests(plan) {
|
|
5247
|
+
if (!plan?.trim()) {
|
|
5248
|
+
return [];
|
|
6027
5249
|
}
|
|
6028
|
-
const
|
|
6029
|
-
|
|
6030
|
-
return 'skipped';
|
|
6031
|
-
}
|
|
6032
|
-
const latestBuild = this.getLatestBuildTimestamp();
|
|
6033
|
-
if (latestBuild && latestChange <= latestBuild) {
|
|
6034
|
-
return 'skipped';
|
|
6035
|
-
}
|
|
6036
|
-
const detected = commandInfo ?? detectBuildCommand(this.workingDir);
|
|
6037
|
-
if (!detected) {
|
|
6038
|
-
this.lastAutoBuildRun = Date.now();
|
|
6039
|
-
display.showSystemMessage('โน๏ธ Skipping auto-build: no build command detected for this repo.');
|
|
6040
|
-
return 'skipped';
|
|
6041
|
-
}
|
|
6042
|
-
this.autoBuildInFlight = true;
|
|
6043
|
-
const command = detected.command;
|
|
6044
|
-
const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
|
|
6045
|
-
display.showSystemMessage(`๐จ Auto-building to verify changes (${trigger}) with "${command}"${reasonSuffix}...`);
|
|
6046
|
-
this.updateStatusMessage('Running build automatically...');
|
|
5250
|
+
const match = plan.match(/\[[\s\S]*\]/);
|
|
5251
|
+
const payload = match ? match[0] : plan;
|
|
6047
5252
|
try {
|
|
6048
|
-
const
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
5253
|
+
const parsed = JSON.parse(payload);
|
|
5254
|
+
if (!Array.isArray(parsed)) {
|
|
5255
|
+
return [];
|
|
5256
|
+
}
|
|
5257
|
+
return parsed
|
|
5258
|
+
.map((entry, index) => ({
|
|
5259
|
+
id: typeof entry.id === 'string' && entry.id.trim() ? entry.id.trim() : `ai-test-${index + 1}`,
|
|
5260
|
+
description: typeof entry.description === 'string' ? entry.description.trim() : `AI test ${index + 1}`,
|
|
5261
|
+
command: typeof entry.command === 'string' ? entry.command.trim() : '',
|
|
5262
|
+
expect: typeof entry.expect === 'string' ? entry.expect.trim() : undefined,
|
|
5263
|
+
timeoutMs: typeof entry.timeoutMs === 'number' ? entry.timeoutMs : undefined,
|
|
5264
|
+
}))
|
|
5265
|
+
.filter((test) => !!test.command);
|
|
5266
|
+
}
|
|
5267
|
+
catch {
|
|
5268
|
+
return [];
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
isDangerousTestCommand(command) {
|
|
5272
|
+
const patterns = [
|
|
5273
|
+
/\brm\s+-/i,
|
|
5274
|
+
/\brm\s+/i,
|
|
5275
|
+
/\brmdir\b/i,
|
|
5276
|
+
/\bchmod\s+7/i,
|
|
5277
|
+
/\bsudo\b/i,
|
|
5278
|
+
/\bmkfs\b/i,
|
|
5279
|
+
/\bshutdown\b/i,
|
|
5280
|
+
/\breboot\b/i,
|
|
5281
|
+
/\bgit\s+(push|reset|checkout|clean)\b/i,
|
|
5282
|
+
/\bnpm\s+install\b/i,
|
|
5283
|
+
/\byarn\s+add\b/i,
|
|
5284
|
+
/\bpnpm\s+add\b/i,
|
|
5285
|
+
];
|
|
5286
|
+
return patterns.some((pattern) => pattern.test(command));
|
|
5287
|
+
}
|
|
5288
|
+
async runAIDesignedTests(trigger, assistantResponse, verificationContext) {
|
|
5289
|
+
const userGoal = this.lastUserQuery?.trim() || 'Not provided';
|
|
5290
|
+
const implementationClaim = (assistantResponse ?? this.lastAssistantResponse ?? '').trim();
|
|
5291
|
+
const changeSummary = this.describeRecentChanges();
|
|
5292
|
+
const packageSummary = this.describePackageScripts();
|
|
5293
|
+
const recentConversation = verificationContext?.conversationHistory?.slice(-3).join('\n') ?? '';
|
|
5294
|
+
let provider;
|
|
5295
|
+
try {
|
|
5296
|
+
provider = createProvider({
|
|
5297
|
+
provider: this.sessionState.provider,
|
|
5298
|
+
model: this.sessionState.model,
|
|
5299
|
+
temperature: 0.15,
|
|
5300
|
+
maxTokens: 900,
|
|
6052
5301
|
});
|
|
6053
|
-
this.lastAutoBuildRun = Date.now();
|
|
6054
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
6055
|
-
display.showSystemMessage('โ
Build succeeded.');
|
|
6056
|
-
if (outputText && outputText.length < 500) {
|
|
6057
|
-
this.writeLocked(`${outputText}\n`);
|
|
6058
|
-
}
|
|
6059
|
-
this.statusTracker.clearOverride('build');
|
|
6060
|
-
return 'success';
|
|
6061
5302
|
}
|
|
6062
5303
|
catch (error) {
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
if (errorOutput) {
|
|
6067
|
-
this.writeLocked(`${errorOutput}\n`);
|
|
6068
|
-
}
|
|
6069
|
-
this.statusTracker.pushOverride('build', 'Build failing', {
|
|
6070
|
-
detail: `Auto-run ${command} failed`,
|
|
6071
|
-
tone: 'danger',
|
|
6072
|
-
});
|
|
6073
|
-
// Feed build errors back to the agent so it can fix them
|
|
6074
|
-
await this.feedBuildErrorsToAgent(errorOutput);
|
|
6075
|
-
return 'failed';
|
|
5304
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5305
|
+
display.showWarning(`AI-designed tests skipped: ${message}`);
|
|
5306
|
+
return;
|
|
6076
5307
|
}
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
5308
|
+
const systemPrompt = 'You design high-value, repo-aware verification commands. Only emit JSON. Use read-only commands that run quickly.';
|
|
5309
|
+
const prompt = `Workspace: ${this.workingDir}
|
|
5310
|
+
User goal: ${userGoal.slice(0, 800)}
|
|
5311
|
+
Assistant claim: ${implementationClaim ? implementationClaim.slice(0, 1200) : 'No implementation summary captured.'}
|
|
5312
|
+
Recent file changes: ${changeSummary}
|
|
5313
|
+
Package signals: ${packageSummary}
|
|
5314
|
+
Recent conversation: ${recentConversation || 'n/a'}
|
|
5315
|
+
|
|
5316
|
+
Design 3-5 focused verification commands to confirm the requested functionality works after the successful build.
|
|
5317
|
+
Rules:
|
|
5318
|
+
- Commands must be safe and non-destructive (no rm, mv, chmod 7xx, sudo, package installs, or git mutations).
|
|
5319
|
+
- Prefer existing npm/yarn/pnpm scripts or targeted checks (node scripts, grep, curl localhost) that prove behavior.
|
|
5320
|
+
- Keep commands runnable from the repo root and finish quickly.
|
|
5321
|
+
Return ONLY JSON array:
|
|
5322
|
+
[{"id":"ai-test-1","description":"what it verifies","command":"npm test -- my-case","expect":"substring to find","timeoutMs":45000}]`;
|
|
5323
|
+
const plan = await provider.generate([
|
|
5324
|
+
{ role: 'system', content: systemPrompt },
|
|
5325
|
+
{ role: 'user', content: prompt },
|
|
5326
|
+
], []);
|
|
5327
|
+
const planText = plan.type === 'message' ? plan.content : plan.content ?? '';
|
|
5328
|
+
const tests = this.parseAIDesignedTests(planText).slice(0, 5);
|
|
5329
|
+
if (!tests.length) {
|
|
5330
|
+
display.showWarning('AI-designed tests could not be generated. Skipping.');
|
|
5331
|
+
return;
|
|
5332
|
+
}
|
|
5333
|
+
const results = [];
|
|
5334
|
+
results.push(`๐ค Running ${tests.length} AI-designed verification tests (${trigger})...`);
|
|
5335
|
+
for (const test of tests) {
|
|
5336
|
+
const command = test.command.trim();
|
|
5337
|
+
if (!command) {
|
|
5338
|
+
continue;
|
|
5339
|
+
}
|
|
5340
|
+
if (this.isDangerousTestCommand(command)) {
|
|
5341
|
+
results.push(`โ ๏ธ ${test.id} blocked (unsafe command): ${command}`);
|
|
5342
|
+
continue;
|
|
5343
|
+
}
|
|
5344
|
+
try {
|
|
5345
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
5346
|
+
cwd: this.workingDir,
|
|
5347
|
+
timeout: Math.min(Math.max(test.timeoutMs ?? 45000, 5000), 120000),
|
|
5348
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
5349
|
+
});
|
|
5350
|
+
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
5351
|
+
const expected = test.expect?.trim();
|
|
5352
|
+
const success = expected ? output.toLowerCase().includes(expected.toLowerCase()) : true;
|
|
5353
|
+
const snippet = output.replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
5354
|
+
results.push(`${success ? 'โ
' : 'โ'} ${test.description || test.id}`);
|
|
5355
|
+
results.push(` cmd: ${command}`);
|
|
5356
|
+
if (expected) {
|
|
5357
|
+
results.push(` expect: ${expected}`);
|
|
5358
|
+
}
|
|
5359
|
+
if (snippet) {
|
|
5360
|
+
results.push(` output: ${snippet}`);
|
|
5361
|
+
}
|
|
5362
|
+
if (!success && !snippet) {
|
|
5363
|
+
results.push(' output: [no output captured]');
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
catch (error) {
|
|
5367
|
+
const message = this.formatCommandError(error) || (error instanceof Error ? error.message : String(error));
|
|
5368
|
+
results.push(`โ ${test.description || test.id}`);
|
|
5369
|
+
results.push(` cmd: ${command}`);
|
|
5370
|
+
if (test.expect) {
|
|
5371
|
+
results.push(` expect: ${test.expect}`);
|
|
5372
|
+
}
|
|
5373
|
+
results.push(` error: ${message.slice(0, 240)}`);
|
|
5374
|
+
}
|
|
6080
5375
|
}
|
|
5376
|
+
display.showSystemMessage(results.join('\n'));
|
|
6081
5377
|
}
|
|
6082
5378
|
/**
|
|
6083
5379
|
* Feed build errors back to the agent conversation for automatic fixing.
|
|
@@ -6103,43 +5399,21 @@ What's the next action?`;
|
|
|
6103
5399
|
// Send the error to the agent for fixing
|
|
6104
5400
|
display.showThinking('Analyzing build errors');
|
|
6105
5401
|
this.refreshStatusLine(true);
|
|
6106
|
-
this.
|
|
6107
|
-
|
|
5402
|
+
const response = await this.agent.send(prompt, true);
|
|
5403
|
+
this.finishStreamingFormatter();
|
|
6108
5404
|
display.stopThinking();
|
|
6109
5405
|
this.refreshStatusLine(true);
|
|
5406
|
+
if (response) {
|
|
5407
|
+
display.showAssistantMessage(response, { isFinal: true });
|
|
5408
|
+
}
|
|
6110
5409
|
// Recursively verify the fix worked
|
|
6111
5410
|
await this.enforceAutoBuild('verification');
|
|
6112
5411
|
}
|
|
6113
5412
|
catch (agentError) {
|
|
6114
5413
|
display.showWarning('Agent could not automatically fix build errors. Please review manually.');
|
|
6115
5414
|
}
|
|
6116
|
-
}
|
|
6117
|
-
async enforceAutoVerification(trigger) {
|
|
6118
|
-
if (this.autoVerificationInFlight || !this.verificationEnabled) {
|
|
6119
|
-
return;
|
|
6120
|
-
}
|
|
6121
|
-
const latestChange = this.getLatestFileChangeTimestamp();
|
|
6122
|
-
if (!latestChange) {
|
|
6123
|
-
return;
|
|
6124
|
-
}
|
|
6125
|
-
this.autoVerificationInFlight = true;
|
|
6126
|
-
try {
|
|
6127
|
-
const buildInfo = detectBuildCommand(this.workingDir);
|
|
6128
|
-
const testInfo = detectTestCommand(this.workingDir);
|
|
6129
|
-
const buildResult = await this.enforceAutoBuild(trigger, buildInfo);
|
|
6130
|
-
if (buildResult === 'failed') {
|
|
6131
|
-
return;
|
|
6132
|
-
}
|
|
6133
|
-
if (testInfo) {
|
|
6134
|
-
await this.enforceAutoTests(trigger, testInfo);
|
|
6135
|
-
}
|
|
6136
|
-
else {
|
|
6137
|
-
this.lastAutoTestRun = Date.now();
|
|
6138
|
-
display.showSystemMessage('โน๏ธ Skipping auto-tests: no test command detected for this repo.');
|
|
6139
|
-
}
|
|
6140
|
-
}
|
|
6141
5415
|
finally {
|
|
6142
|
-
this.
|
|
5416
|
+
this.finishStreamingFormatter();
|
|
6143
5417
|
}
|
|
6144
5418
|
}
|
|
6145
5419
|
rebuildAgent() {
|
|
@@ -6157,35 +5431,29 @@ What's the next action?`;
|
|
|
6157
5431
|
autoContinue: this.autoContinueEnabled,
|
|
6158
5432
|
};
|
|
6159
5433
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
6160
|
-
onStreamChunk: (chunk
|
|
6161
|
-
this.
|
|
5434
|
+
onStreamChunk: (chunk) => {
|
|
5435
|
+
this.handleStreamChunk(chunk);
|
|
6162
5436
|
},
|
|
6163
5437
|
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6164
5438
|
onAssistantMessage: (content, metadata) => {
|
|
6165
5439
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6166
|
-
|
|
6167
|
-
const parsed = this.splitThinkingResponse(content);
|
|
6168
|
-
const thinking = parsed?.thinking ?? null;
|
|
6169
|
-
const responseContent = parsed ? parsed.response?.trim() ?? '' : content.trim();
|
|
6170
|
-
const narrativeContent = (parsed?.response ?? content).trim();
|
|
6171
|
-
const streamRendered = metadata.wasStreamed && this.assistantStreamHadContent;
|
|
6172
|
-
if (thinking) {
|
|
6173
|
-
this.presentThoughtProcess(thinking, enriched, {
|
|
6174
|
-
wasStreamed: metadata.wasStreamed,
|
|
6175
|
-
// Always render the thought block immediately when present so it appears first.
|
|
6176
|
-
renderWhenStreamed: true,
|
|
6177
|
-
});
|
|
6178
|
-
}
|
|
5440
|
+
// Update spinner based on message type
|
|
6179
5441
|
if (metadata.isFinal) {
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
5442
|
+
const parsed = this.splitThinkingResponse(content);
|
|
5443
|
+
const finalContent = parsed?.response?.trim() || content;
|
|
5444
|
+
// Skip display if content was already streamed to avoid double-display
|
|
5445
|
+
if (!metadata.wasStreamed) {
|
|
5446
|
+
if (parsed?.thinking) {
|
|
5447
|
+
const summary = this.extractThoughtSummary(parsed.thinking);
|
|
5448
|
+
if (summary) {
|
|
5449
|
+
display.updateThinking(`๐ญ ${summary}`);
|
|
5450
|
+
}
|
|
5451
|
+
display.showAssistantMessage(parsed.thinking, { ...enriched, isFinal: false });
|
|
5452
|
+
}
|
|
5453
|
+
if (finalContent) {
|
|
5454
|
+
display.showAssistantMessage(finalContent, enriched);
|
|
5455
|
+
}
|
|
6187
5456
|
}
|
|
6188
|
-
this.renderToolUsageSummary({ ...enriched, isFinal: true });
|
|
6189
5457
|
// Status shown in mode controls bar - no separate status line needed
|
|
6190
5458
|
display.stopThinking();
|
|
6191
5459
|
// Update context usage for mode controls display
|
|
@@ -6196,22 +5464,19 @@ What's the next action?`;
|
|
|
6196
5464
|
this.updateContextUsage(percentage);
|
|
6197
5465
|
}
|
|
6198
5466
|
}
|
|
5467
|
+
if (finalContent) {
|
|
5468
|
+
this.lastAssistantResponse = finalContent;
|
|
5469
|
+
}
|
|
6199
5470
|
// Auto-verify changes: build first (catches type errors), then tests
|
|
6200
|
-
void this.
|
|
5471
|
+
void this.runAutoQualityChecks('final-response', finalContent);
|
|
6201
5472
|
}
|
|
6202
5473
|
else {
|
|
6203
5474
|
// Non-final message = narrative text before tool calls (Claude Code style)
|
|
6204
5475
|
// Stop spinner and show the narrative text directly
|
|
6205
5476
|
display.stopThinking();
|
|
6206
|
-
if
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
const shouldRenderNarrative = Boolean(narrativeContent && (!metadata.wasStreamed || !blocksActive || !streamRendered));
|
|
6210
|
-
if (shouldRenderNarrative) {
|
|
6211
|
-
this.renderAssistantContent('thought', narrativeContent, {
|
|
6212
|
-
...enriched,
|
|
6213
|
-
isFinal: false,
|
|
6214
|
-
});
|
|
5477
|
+
// Skip display if content was already streamed to avoid double-display
|
|
5478
|
+
if (!metadata.wasStreamed) {
|
|
5479
|
+
display.showNarrative(content.trim());
|
|
6215
5480
|
}
|
|
6216
5481
|
// The isProcessing flag already shows "โณ Processing..." - no need for duplicate status
|
|
6217
5482
|
this.requestPromptRefresh();
|
|
@@ -6224,8 +5489,8 @@ What's the next action?`;
|
|
|
6224
5489
|
this.requestPromptRefresh();
|
|
6225
5490
|
},
|
|
6226
5491
|
onContextSquishing: (message) => {
|
|
6227
|
-
//
|
|
6228
|
-
display.
|
|
5492
|
+
// Show notification in UI when auto context squishing occurs
|
|
5493
|
+
display.showSystemMessage(`๐ ${message}`);
|
|
6229
5494
|
this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
|
|
6230
5495
|
detail: 'Reducing conversation history to fit within token limits',
|
|
6231
5496
|
tone: 'warning',
|
|
@@ -6233,7 +5498,7 @@ What's the next action?`;
|
|
|
6233
5498
|
},
|
|
6234
5499
|
onContextRecovery: (attempt, maxAttempts, message) => {
|
|
6235
5500
|
// Show recovery progress in UI
|
|
6236
|
-
display.
|
|
5501
|
+
display.showSystemMessage(`โก Context Recovery (${attempt}/${maxAttempts}): ${message}`);
|
|
6237
5502
|
},
|
|
6238
5503
|
onContextPruned: (removedCount, stats) => {
|
|
6239
5504
|
// Clear squish overlay if active
|
|
@@ -6241,43 +5506,40 @@ What's the next action?`;
|
|
|
6241
5506
|
// Show notification that context was pruned
|
|
6242
5507
|
const method = stats['method'];
|
|
6243
5508
|
const percentage = stats['percentage'];
|
|
6244
|
-
const summarized = stats['summarized'] === true;
|
|
6245
5509
|
if (method === 'emergency-recovery') {
|
|
6246
|
-
display.
|
|
5510
|
+
display.showSystemMessage(`โ
Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%`);
|
|
6247
5511
|
}
|
|
6248
5512
|
// Update context usage in UI
|
|
6249
5513
|
if (typeof percentage === 'number') {
|
|
6250
5514
|
this.updateContextUsage(percentage);
|
|
6251
5515
|
}
|
|
6252
|
-
if (summarized) {
|
|
6253
|
-
display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
|
|
6254
|
-
}
|
|
6255
5516
|
// Ensure prompt remains visible at bottom after context messages
|
|
6256
|
-
this.
|
|
5517
|
+
this.terminalInput.render();
|
|
6257
5518
|
},
|
|
6258
5519
|
onContinueAfterRecovery: () => {
|
|
6259
5520
|
// Update UI to show we're continuing after context recovery
|
|
6260
|
-
display.
|
|
5521
|
+
display.showSystemMessage(`๐ Continuing after context recovery...`);
|
|
6261
5522
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6262
|
-
this.
|
|
5523
|
+
this.terminalInput.render();
|
|
6263
5524
|
},
|
|
6264
5525
|
onAutoContinue: (attempt, maxAttempts, _message) => {
|
|
6265
5526
|
// Show auto-continue progress in UI
|
|
6266
5527
|
display.showSystemMessage(`๐ Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
|
|
6267
5528
|
this.updateStatusMessage('Auto-continuing...');
|
|
6268
|
-
this.
|
|
5529
|
+
this.terminalInput.render();
|
|
6269
5530
|
},
|
|
6270
5531
|
onCancelled: () => {
|
|
6271
5532
|
// Update UI to show operation was cancelled
|
|
6272
5533
|
display.showWarning('Operation cancelled.');
|
|
6273
5534
|
this.uiUpdates.setMode('processing');
|
|
6274
|
-
this.stopStreamingHeartbeat(
|
|
5535
|
+
this.stopStreamingHeartbeat();
|
|
6275
5536
|
this.updateStatusMessage(null);
|
|
6276
5537
|
this.terminalInput.setStreaming(false);
|
|
6277
|
-
this.
|
|
5538
|
+
this.terminalInput.render();
|
|
6278
5539
|
},
|
|
6279
|
-
onVerificationNeeded: () => {
|
|
6280
|
-
|
|
5540
|
+
onVerificationNeeded: (response, context) => {
|
|
5541
|
+
this.lastAssistantResponse = response;
|
|
5542
|
+
void this.runAutoQualityChecks('verification', response, context);
|
|
6281
5543
|
},
|
|
6282
5544
|
});
|
|
6283
5545
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
@@ -6311,7 +5573,7 @@ What's the next action?`;
|
|
|
6311
5573
|
resetChatBoxAfterModelSwap() {
|
|
6312
5574
|
this.updateStatusMessage(null);
|
|
6313
5575
|
this.terminalInput.setStreaming(false);
|
|
6314
|
-
this.
|
|
5576
|
+
this.terminalInput.render();
|
|
6315
5577
|
this.ensureReadlineReady();
|
|
6316
5578
|
}
|
|
6317
5579
|
/**
|
|
@@ -6374,22 +5636,19 @@ What's the next action?`;
|
|
|
6374
5636
|
}
|
|
6375
5637
|
buildThinkingDirective() {
|
|
6376
5638
|
switch (this.thinkingMode) {
|
|
6377
|
-
case 'concise':
|
|
6378
|
-
return 'Concise thinking mode: respond directly with the final answer and skip <thinking> blocks unless the user explicitly asks for reasoning.';
|
|
6379
5639
|
case 'extended':
|
|
6380
5640
|
return [
|
|
6381
|
-
'Extended thinking mode
|
|
5641
|
+
'Extended thinking mode is enabled. Format every reply as:',
|
|
6382
5642
|
'<thinking>',
|
|
6383
|
-
'
|
|
5643
|
+
'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
|
|
6384
5644
|
'</thinking>',
|
|
6385
5645
|
'<response>',
|
|
6386
|
-
'Final answer with
|
|
5646
|
+
'Final answer with actionable next steps and any code/commands requested.',
|
|
6387
5647
|
'</response>',
|
|
6388
5648
|
].join('\n');
|
|
6389
5649
|
case 'balanced':
|
|
6390
5650
|
default:
|
|
6391
|
-
|
|
6392
|
-
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.';
|
|
5651
|
+
return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
|
|
6393
5652
|
}
|
|
6394
5653
|
}
|
|
6395
5654
|
buildDisplayMetadata(metadata) {
|
|
@@ -6398,58 +5657,6 @@ What's the next action?`;
|
|
|
6398
5657
|
contextWindowTokens: this.activeContextWindowTokens,
|
|
6399
5658
|
};
|
|
6400
5659
|
}
|
|
6401
|
-
presentThoughtProcess(thinking, metadata, options) {
|
|
6402
|
-
if (!thinking || this.hasShownThoughtProcess) {
|
|
6403
|
-
return;
|
|
6404
|
-
}
|
|
6405
|
-
const summary = this.extractThoughtSummary(thinking);
|
|
6406
|
-
if (summary) {
|
|
6407
|
-
display.updateThinking(`๐ญ ${summary}`);
|
|
6408
|
-
this.latestThoughtSummary = summary;
|
|
6409
|
-
this.streamingStatusDetail = summary;
|
|
6410
|
-
this.terminalInput.recordRecentAction(`๐ญ ${summary}`);
|
|
6411
|
-
this.rebuildStreamingStatusLabel();
|
|
6412
|
-
this.refreshStatusLine(true);
|
|
6413
|
-
}
|
|
6414
|
-
const shouldRender = !options?.wasStreamed || options?.renderWhenStreamed;
|
|
6415
|
-
if (shouldRender) {
|
|
6416
|
-
this.renderAssistantContent('thought', thinking, { ...metadata, isFinal: false });
|
|
6417
|
-
}
|
|
6418
|
-
this.hasShownThoughtProcess = true;
|
|
6419
|
-
}
|
|
6420
|
-
renderToolUsageSummary(metadata) {
|
|
6421
|
-
const since = this.lastToolSummaryRenderedAt ?? this.lastRequestStartedAt ?? Date.now();
|
|
6422
|
-
const operations = this.uiAdapter.getToolOperationsSince(since);
|
|
6423
|
-
if (!operations.length) {
|
|
6424
|
-
return;
|
|
6425
|
-
}
|
|
6426
|
-
let summaryLine = compactRenderer.formatCompactLine(operations, {
|
|
6427
|
-
showIcons: true,
|
|
6428
|
-
showTimings: true,
|
|
6429
|
-
});
|
|
6430
|
-
const warnings = this.uiAdapter.getRecentWarnings(since);
|
|
6431
|
-
if (warnings.length) {
|
|
6432
|
-
const limited = warnings.slice(0, 3);
|
|
6433
|
-
const formattedWarnings = limited
|
|
6434
|
-
.map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
|
|
6435
|
-
.join(' โข ');
|
|
6436
|
-
summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
|
|
6437
|
-
}
|
|
6438
|
-
if (!summaryLine.trim()) {
|
|
6439
|
-
this.lastToolSummaryRenderedAt = Date.now();
|
|
6440
|
-
return;
|
|
6441
|
-
}
|
|
6442
|
-
this.lastToolSummaryRenderedAt =
|
|
6443
|
-
operations[operations.length - 1]?.startedAt ?? Date.now();
|
|
6444
|
-
const blockMetadata = { ...metadata, isFinal: true };
|
|
6445
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
6446
|
-
this.renderAssistantBlock('tools', summaryLine, blockMetadata);
|
|
6447
|
-
return;
|
|
6448
|
-
}
|
|
6449
|
-
const header = this.formatAssistantHeader('tools', blockMetadata);
|
|
6450
|
-
const block = `\n${header}\n${summaryLine}\n\n`;
|
|
6451
|
-
this.enqueueAssistantStream(block);
|
|
6452
|
-
}
|
|
6453
5660
|
handleContextTelemetry(metadata, displayMetadata) {
|
|
6454
5661
|
if (!metadata.isFinal) {
|
|
6455
5662
|
return null;
|
|
@@ -6810,7 +6017,7 @@ What's the next action?`;
|
|
|
6810
6017
|
}
|
|
6811
6018
|
handleAgentSetupError(error, retryAction, providerOverride) {
|
|
6812
6019
|
this.pendingInteraction = null;
|
|
6813
|
-
const provider = providerOverride
|
|
6020
|
+
const provider = providerOverride ?? this.sessionState.provider;
|
|
6814
6021
|
const apiKeyIssue = detectApiKeyError(error, provider);
|
|
6815
6022
|
if (apiKeyIssue) {
|
|
6816
6023
|
this.handleApiKeyIssue(apiKeyIssue, retryAction);
|
|
@@ -6825,14 +6032,13 @@ What's the next action?`;
|
|
|
6825
6032
|
const detail = detailText ? ` Error: ${detailText}` : '';
|
|
6826
6033
|
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
6827
6034
|
const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
|
|
6828
|
-
this.finalizeAssistantStream();
|
|
6829
6035
|
display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
|
|
6036
|
+
this.finishStreamingFormatter('Stream interrupted - retrying without streaming');
|
|
6830
6037
|
this.startStreamingHeartbeat('Fallback in progress');
|
|
6831
6038
|
this.requestPromptRefresh(true);
|
|
6832
6039
|
}
|
|
6833
6040
|
handleProviderError(error, retryAction) {
|
|
6834
|
-
const
|
|
6835
|
-
const apiKeyIssue = detectApiKeyError(error, providerHint);
|
|
6041
|
+
const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
|
|
6836
6042
|
if (!apiKeyIssue) {
|
|
6837
6043
|
return false;
|
|
6838
6044
|
}
|
|
@@ -6841,19 +6047,13 @@ What's the next action?`;
|
|
|
6841
6047
|
}
|
|
6842
6048
|
handleApiKeyIssue(info, retryAction) {
|
|
6843
6049
|
const secret = info.secret ?? null;
|
|
6844
|
-
const providerLabel = info.provider
|
|
6845
|
-
? this.providerLabel(info.provider)
|
|
6846
|
-
: secret?.providers?.length
|
|
6847
|
-
? this.providerLabel(secret.providers[0])
|
|
6848
|
-
: null;
|
|
6849
|
-
const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
|
|
6850
|
-
this.apiKeyGateActive = !!secret;
|
|
6050
|
+
const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
|
|
6851
6051
|
if (!secret) {
|
|
6852
6052
|
this.pendingSecretRetry = null;
|
|
6853
6053
|
const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
|
|
6854
6054
|
const baseMessage = info.type === 'missing'
|
|
6855
|
-
? `An API key is required before using ${
|
|
6856
|
-
: `API authentication failed for ${
|
|
6055
|
+
? `An API key is required before using ${providerLabel}.`
|
|
6056
|
+
: `API authentication failed for ${providerLabel}.`;
|
|
6857
6057
|
display.showWarning(`${baseMessage} ${guidance}`.trim());
|
|
6858
6058
|
return;
|
|
6859
6059
|
}
|
|
@@ -6862,8 +6062,8 @@ What's the next action?`;
|
|
|
6862
6062
|
display.showWarning(info.message.trim());
|
|
6863
6063
|
}
|
|
6864
6064
|
const prefix = isMissing
|
|
6865
|
-
? `${secret.label} is required before you can use ${
|
|
6866
|
-
: `${secret.label} appears to be invalid for ${
|
|
6065
|
+
? `${secret.label} is required before you can use ${providerLabel}.`
|
|
6066
|
+
: `${secret.label} appears to be invalid for ${providerLabel}.`;
|
|
6867
6067
|
display.showWarning(prefix);
|
|
6868
6068
|
this.pendingSecretRetry = retryAction ?? null;
|
|
6869
6069
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
@@ -6899,138 +6099,12 @@ What's the next action?`;
|
|
|
6899
6099
|
this.sessionState.reasoningEffort = preset.reasoningEffort;
|
|
6900
6100
|
}
|
|
6901
6101
|
}
|
|
6902
|
-
|
|
6903
|
-
* Build the session banner with comprehensive feature status.
|
|
6904
|
-
*/
|
|
6905
|
-
getSessionFrameProps() {
|
|
6906
|
-
return {
|
|
6907
|
-
profileLabel: this.profileLabel,
|
|
6908
|
-
profileName: this.profile,
|
|
6909
|
-
model: this.sessionState.model,
|
|
6910
|
-
provider: this.sessionState.provider,
|
|
6911
|
-
workspace: this.workingDir,
|
|
6912
|
-
version: this.version,
|
|
6913
|
-
};
|
|
6914
|
-
}
|
|
6915
|
-
buildBanner() {
|
|
6916
|
-
const terminalWidth = output.columns ?? 100;
|
|
6917
|
-
const width = Math.min(terminalWidth - 4, 110);
|
|
6918
|
-
return renderSessionFrame({
|
|
6919
|
-
...this.getSessionFrameProps(),
|
|
6920
|
-
width,
|
|
6921
|
-
});
|
|
6922
|
-
}
|
|
6923
|
-
/**
|
|
6924
|
-
* Collect tool categories for banner display.
|
|
6925
|
-
*/
|
|
6926
|
-
collectToolCategories() {
|
|
6927
|
-
const categories = [];
|
|
6928
|
-
try {
|
|
6929
|
-
const providerTools = this.runtimeSession.toolRuntime.listProviderTools();
|
|
6930
|
-
if (providerTools.length > 0) {
|
|
6931
|
-
// Group by category (first word of tool name or namespace)
|
|
6932
|
-
const groups = new Map();
|
|
6933
|
-
for (const tool of providerTools) {
|
|
6934
|
-
const category = this.extractToolCategory(tool.name);
|
|
6935
|
-
groups.set(category, (groups.get(category) || 0) + 1);
|
|
6936
|
-
}
|
|
6937
|
-
// Convert to array sorted by count
|
|
6938
|
-
const sorted = Array.from(groups.entries())
|
|
6939
|
-
.sort((a, b) => b[1] - a[1])
|
|
6940
|
-
.slice(0, 5); // Top 5 categories
|
|
6941
|
-
for (const [name, count] of sorted) {
|
|
6942
|
-
categories.push({ name, count, icon: this.getToolCategoryIcon(name) });
|
|
6943
|
-
}
|
|
6944
|
-
}
|
|
6945
|
-
}
|
|
6946
|
-
catch {
|
|
6947
|
-
// Ignore errors in tool collection
|
|
6948
|
-
}
|
|
6949
|
-
return categories;
|
|
6950
|
-
}
|
|
6951
|
-
buildFeatureStatusSnapshot() {
|
|
6952
|
-
const featureFlags = loadFeatureFlags();
|
|
6953
|
-
const toolCategories = this.collectToolCategories();
|
|
6954
|
-
const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
|
|
6955
|
-
const pluginCount = this._enabledPlugins.length;
|
|
6956
|
-
return {
|
|
6957
|
-
pluginCount: pluginCount > 0 ? pluginCount : undefined,
|
|
6958
|
-
toolCount: toolCount > 0 ? toolCount : undefined,
|
|
6959
|
-
sessionId: this.activeSessionId,
|
|
6960
|
-
mcpEnabled: featureFlags.mcpEnabled,
|
|
6961
|
-
metricsEnabled: featureFlags.metrics,
|
|
6962
|
-
autoCompact: featureFlags.autoCompact,
|
|
6963
|
-
dualMode: featureFlags.alphaZeroDual,
|
|
6964
|
-
};
|
|
6965
|
-
}
|
|
6966
|
-
/**
|
|
6967
|
-
* Extract category from tool name.
|
|
6968
|
-
*/
|
|
6969
|
-
extractToolCategory(toolName) {
|
|
6970
|
-
// Common tool prefixes
|
|
6971
|
-
const prefixMap = {
|
|
6972
|
-
git: 'Git',
|
|
6973
|
-
npm: 'NPM',
|
|
6974
|
-
bash: 'Shell',
|
|
6975
|
-
file: 'Files',
|
|
6976
|
-
read: 'Files',
|
|
6977
|
-
write: 'Files',
|
|
6978
|
-
edit: 'Files',
|
|
6979
|
-
search: 'Search',
|
|
6980
|
-
glob: 'Search',
|
|
6981
|
-
grep: 'Search',
|
|
6982
|
-
web: 'Web',
|
|
6983
|
-
fetch: 'Web',
|
|
6984
|
-
test: 'Testing',
|
|
6985
|
-
build: 'Build',
|
|
6986
|
-
deploy: 'Deploy',
|
|
6987
|
-
cloud: 'Cloud',
|
|
6988
|
-
browser: 'Browser',
|
|
6989
|
-
};
|
|
6990
|
-
const lower = toolName.toLowerCase();
|
|
6991
|
-
for (const [prefix, category] of Object.entries(prefixMap)) {
|
|
6992
|
-
if (lower.startsWith(prefix)) {
|
|
6993
|
-
return category;
|
|
6994
|
-
}
|
|
6995
|
-
}
|
|
6996
|
-
// Default to first word capitalized
|
|
6997
|
-
const firstWord = toolName.split(/[_\-\s]/)[0] || 'Other';
|
|
6998
|
-
return firstWord.charAt(0).toUpperCase() + firstWord.slice(1).toLowerCase();
|
|
6999
|
-
}
|
|
7000
|
-
/**
|
|
7001
|
-
* Get icon for tool category.
|
|
7002
|
-
*/
|
|
7003
|
-
getToolCategoryIcon(category) {
|
|
7004
|
-
const icons = {
|
|
7005
|
-
Git: 'โ',
|
|
7006
|
-
NPM: '๐ฆ',
|
|
7007
|
-
Shell: 'โ',
|
|
7008
|
-
Files: '๐',
|
|
7009
|
-
Search: '๐',
|
|
7010
|
-
Web: '๐',
|
|
7011
|
-
Testing: '๐งช',
|
|
7012
|
-
Build: '๐ง',
|
|
7013
|
-
Deploy: '๐',
|
|
7014
|
-
Cloud: 'โ',
|
|
7015
|
-
Browser: '๐',
|
|
7016
|
-
};
|
|
7017
|
-
return icons[category] || 'โ';
|
|
7018
|
-
}
|
|
7019
|
-
refreshBannerSessionInfo() {
|
|
7020
|
-
const nextState = {
|
|
7021
|
-
model: this.sessionState.model,
|
|
7022
|
-
provider: this.sessionState.provider,
|
|
7023
|
-
};
|
|
7024
|
-
const previous = this.bannerSessionState;
|
|
7025
|
-
if (previous && previous.model === nextState.model && previous.provider === nextState.provider) {
|
|
7026
|
-
return;
|
|
7027
|
-
}
|
|
6102
|
+
refreshSessionContext() {
|
|
7028
6103
|
this.refreshContextGauge();
|
|
7029
|
-
// Banner is streamed once at launch; keep the control bar up to date without re-rendering it
|
|
7030
6104
|
if (!this.isProcessing) {
|
|
7031
6105
|
this.setIdleStatus();
|
|
7032
6106
|
}
|
|
7033
|
-
this.
|
|
6107
|
+
this.refreshStatusLine(true);
|
|
7034
6108
|
}
|
|
7035
6109
|
providerLabel(id) {
|
|
7036
6110
|
return PROVIDER_LABELS[id] ?? id;
|
|
@@ -7137,7 +6211,7 @@ What's the next action?`;
|
|
|
7137
6211
|
// Rebuild agent with new provider
|
|
7138
6212
|
if (this.rebuildAgent()) {
|
|
7139
6213
|
this.persistSessionPreference();
|
|
7140
|
-
this.
|
|
6214
|
+
this.refreshSessionContext();
|
|
7141
6215
|
display.showInfo(`Switched from ${this.providerLabel(oldProvider)}/${oldModel} to ${match.label}/${defaultModel.id}`);
|
|
7142
6216
|
this.resetChatBoxAfterModelSwap();
|
|
7143
6217
|
}
|
|
@@ -7331,6 +6405,20 @@ What's the next action?`;
|
|
|
7331
6405
|
];
|
|
7332
6406
|
display.showSystemMessage(lines.join('\n'));
|
|
7333
6407
|
}
|
|
6408
|
+
/**
|
|
6409
|
+
* Set the cached provider status for unified status bar display.
|
|
6410
|
+
* Called once at startup after checking providers.
|
|
6411
|
+
*/
|
|
6412
|
+
setProviderStatus(providers) {
|
|
6413
|
+
this.cachedProviderStatus = providers;
|
|
6414
|
+
}
|
|
6415
|
+
/**
|
|
6416
|
+
* Show the unified status bar (Claude Code style).
|
|
6417
|
+
* Displays provider indicators and ready hints before the prompt.
|
|
6418
|
+
*/
|
|
6419
|
+
showUnifiedStatusBar() {
|
|
6420
|
+
display.showUnifiedStatusBar(this.cachedProviderStatus);
|
|
6421
|
+
}
|
|
7334
6422
|
}
|
|
7335
6423
|
function setsEqual(first, second) {
|
|
7336
6424
|
if (first.size !== second.size) {
|