erosolar-cli 1.7.409 → 1.7.411
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/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/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.map +1 -1
- package/dist/core/toolPreconditions.js +0 -60
- 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/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 +30 -79
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +726 -1519
- 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 +60 -517
- 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 +1 -8
- package/dist/shell/updateManager.d.ts.map +1 -1
- package/dist/shell/updateManager.js +14 -9
- package/dist/shell/updateManager.js.map +1 -1
- package/dist/subagents/parallelAgentManager.d.ts.map +1 -1
- package/dist/subagents/parallelAgentManager.js +2 -1
- package/dist/subagents/parallelAgentManager.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/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 +16 -44
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +84 -337
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/display.d.ts +37 -18
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +310 -95
- 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 +11 -1
- package/dist/ui/shortcutsHelp.d.ts.map +1 -1
- package/dist/ui/shortcutsHelp.js +54 -8
- package/dist/ui/shortcutsHelp.js.map +1 -1
- package/dist/ui/streamingFormatter.d.ts +16 -0
- package/dist/ui/streamingFormatter.d.ts.map +1 -0
- package/dist/ui/streamingFormatter.js +62 -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 +3 -3
- package/dist/ui/toolDisplay.d.ts.map +1 -1
- package/dist/ui/toolDisplay.js +8 -7
- package/dist/ui/toolDisplay.js.map +1 -1
- package/dist/ui/unified/index.d.ts +2 -2
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/ui/unified/layout.d.ts +23 -0
- package/dist/ui/unified/layout.d.ts.map +1 -1
- package/dist/ui/unified/layout.js +113 -11
- 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';
|
|
@@ -37,13 +38,11 @@ import { startOffsecRun, resumeOffsecRun, recordOffsecOutcome, getOffsecNextActi
|
|
|
37
38
|
import { generateTestFlows, detectBugs, detectUIUpdates, saveTestFlows, saveBugReports, saveUIUpdates, getTestFlowStatus, } from '../core/intelligentTestFlows.js';
|
|
38
39
|
import { TerminalInputAdapter } from './terminalInputAdapter.js';
|
|
39
40
|
import { renderSessionFrame } from '../ui/unified/layout.js';
|
|
40
|
-
import { isUpdateInProgress, maybeOfferCliUpdate } from './updateManager.js';
|
|
41
41
|
import { writeLock } from '../ui/writeLock.js';
|
|
42
42
|
import { enterStreamingMode, exitStreamingMode } from '../ui/globalWriteLock.js';
|
|
43
43
|
import { setGlobalAIEnhancer } from '../tools/localExplore.js';
|
|
44
44
|
import { createProvider } from '../providers/providerFactory.js';
|
|
45
45
|
import { getParallelAgentManager } from '../subagents/parallelAgentManager.js';
|
|
46
|
-
import { formatThinkingContent } from '../ui/textHighlighter.js';
|
|
47
46
|
const execAsync = promisify(exec);
|
|
48
47
|
const DROPDOWN_COLORS = [
|
|
49
48
|
theme.primary,
|
|
@@ -118,14 +117,11 @@ export class InteractiveShell {
|
|
|
118
117
|
ui;
|
|
119
118
|
uiAdapter;
|
|
120
119
|
uiUpdates;
|
|
121
|
-
assistantBlocksEnabled;
|
|
122
|
-
assistantBlockRenderer;
|
|
123
120
|
_fileChangeTracker = new FileChangeTracker(); // Reserved for future file tracking features
|
|
124
121
|
alphaZeroMetrics; // Alpha Zero 2 performance tracking
|
|
125
122
|
statusSubscription = null;
|
|
126
123
|
followUpQueue = [];
|
|
127
124
|
isDrainingQueue = false;
|
|
128
|
-
apiKeyGateActive = false;
|
|
129
125
|
activeContextWindowTokens = null;
|
|
130
126
|
latestTokenUsage = { used: null, limit: null };
|
|
131
127
|
planApprovalBridgeRegistered = false;
|
|
@@ -134,9 +130,7 @@ export class InteractiveShell {
|
|
|
134
130
|
sessionPreferences;
|
|
135
131
|
autosaveEnabled;
|
|
136
132
|
autoContinueEnabled;
|
|
137
|
-
verificationEnabled =
|
|
138
|
-
alphaZeroModeEnabled;
|
|
139
|
-
alphaZeroVerificationSnapshot = null;
|
|
133
|
+
verificationEnabled = false;
|
|
140
134
|
editGuardMode = 'display-edits';
|
|
141
135
|
pendingPermissionInput = null;
|
|
142
136
|
pendingHistoryLoad = null;
|
|
@@ -148,51 +142,36 @@ export class InteractiveShell {
|
|
|
148
142
|
customCommandMap;
|
|
149
143
|
sessionRestoreConfig;
|
|
150
144
|
_enabledPlugins;
|
|
151
|
-
|
|
145
|
+
// Cached provider status for unified status bar display after streaming
|
|
146
|
+
cachedProviderStatus = [];
|
|
152
147
|
// Auto-test tracking
|
|
153
148
|
autoTestInFlight = false;
|
|
154
149
|
// AlphaZero learning tracking
|
|
155
150
|
currentTaskType = 'general';
|
|
156
151
|
currentToolCalls = [];
|
|
157
152
|
lastUserQuery = '';
|
|
153
|
+
lastAssistantResponse = null;
|
|
158
154
|
lastFailure = null;
|
|
159
155
|
lastAutoTestRun = null;
|
|
160
|
-
runIdCounter = 0;
|
|
161
|
-
lastRunLog = null;
|
|
162
|
-
lastReflectedRunId = null;
|
|
163
|
-
skipNextAutoReflection = false;
|
|
164
|
-
alphaZeroAutoImproveActive = false;
|
|
165
|
-
alphaZeroAutoImproveIterations = 0;
|
|
166
|
-
alphaZeroAutoImproveMaxIterations = 6;
|
|
167
156
|
// Auto-build tracking
|
|
168
157
|
autoBuildInFlight = false;
|
|
158
|
+
autoBuildPromise = null;
|
|
169
159
|
lastAutoBuildRun = null;
|
|
160
|
+
lastBuildSucceededAt = null;
|
|
170
161
|
// Offsec AlphaZero tracking
|
|
171
162
|
offsecRunId = null;
|
|
172
163
|
// Streaming UX tracking
|
|
173
164
|
streamingHeartbeatStart = null;
|
|
174
165
|
streamingHeartbeatFrame = 0;
|
|
175
166
|
streamingStatusLabel = null;
|
|
176
|
-
streamingStatusBase = null;
|
|
177
|
-
streamingStatusDetail = null;
|
|
178
167
|
lastStreamingElapsedSeconds = null; // Preserve final elapsed time
|
|
168
|
+
streamingFormatter = null;
|
|
179
169
|
statusLineState = null;
|
|
180
170
|
statusMessageOverride = null;
|
|
181
|
-
latestThoughtSummary = null;
|
|
182
|
-
hasShownThoughtProcess = false;
|
|
183
171
|
promptRefreshTimer = null;
|
|
184
172
|
launchPaletteShown = false;
|
|
185
173
|
version;
|
|
186
174
|
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
175
|
constructor(config) {
|
|
197
176
|
this.profile = config.profile;
|
|
198
177
|
this.profileLabel = config.profileLabel;
|
|
@@ -204,10 +183,10 @@ export class InteractiveShell {
|
|
|
204
183
|
this.thinkingMode = this.sessionPreferences.thinkingMode;
|
|
205
184
|
this.autosaveEnabled = this.sessionPreferences.autosave;
|
|
206
185
|
this.autoContinueEnabled = this.sessionPreferences.autoContinue;
|
|
207
|
-
|
|
186
|
+
const featureFlags = loadFeatureFlags();
|
|
187
|
+
this.verificationEnabled = featureFlags.verification === true;
|
|
208
188
|
this.sessionRestoreConfig = config.sessionRestore ?? { mode: 'none' };
|
|
209
189
|
this._enabledPlugins = config.enabledPlugins ?? [];
|
|
210
|
-
this.assistantBlocksEnabled = config.assistantBlocksEnabled ?? true;
|
|
211
190
|
this.version = config.version ?? '0.0.0';
|
|
212
191
|
// Alternate screen disabled - use terminal-native mode for proper scrollback and text selection
|
|
213
192
|
this.alternateScreenEnabled = false;
|
|
@@ -256,21 +235,11 @@ export class InteractiveShell {
|
|
|
256
235
|
description: 'Show available and loaded plugins',
|
|
257
236
|
category: 'configuration',
|
|
258
237
|
});
|
|
259
|
-
this.slashCommands.push({
|
|
260
|
-
command: '/contextlog',
|
|
261
|
-
description: 'Show recent context compaction summaries',
|
|
262
|
-
category: 'context',
|
|
263
|
-
});
|
|
264
238
|
this.slashCommands.push({
|
|
265
239
|
command: '/offsec',
|
|
266
240
|
description: 'AlphaZero offensive security run (start/status/next)',
|
|
267
241
|
category: 'automation',
|
|
268
242
|
});
|
|
269
|
-
this.slashCommands.push({
|
|
270
|
-
command: '/alphazero',
|
|
271
|
-
description: 'Toggle AlphaZero RL mode with full-cycle verification',
|
|
272
|
-
category: 'mode',
|
|
273
|
-
});
|
|
274
243
|
this.statusTracker = config.statusTracker;
|
|
275
244
|
this.ui = config.ui;
|
|
276
245
|
this.uiAdapter = config.ui.adapter;
|
|
@@ -282,11 +251,7 @@ export class InteractiveShell {
|
|
|
282
251
|
});
|
|
283
252
|
// Set up tool status callback to update status during tool execution
|
|
284
253
|
this.uiAdapter.setToolStatusCallback((status) => {
|
|
285
|
-
|
|
286
|
-
if (statusText) {
|
|
287
|
-
this.terminalInput.recordRecentAction(`[tool] ${statusText}`);
|
|
288
|
-
}
|
|
289
|
-
this.updateStatusMessage(statusText, { logRecent: false });
|
|
254
|
+
this.updateStatusMessage(status ?? null);
|
|
290
255
|
});
|
|
291
256
|
this.skillRepository = new SkillRepository({
|
|
292
257
|
workingDir: this.workingDir,
|
|
@@ -306,19 +271,14 @@ export class InteractiveShell {
|
|
|
306
271
|
onToggleVerify: () => this.toggleVerificationMode(),
|
|
307
272
|
onToggleAutoContinue: () => this.toggleAutoContinueMode(),
|
|
308
273
|
onToggleThinking: () => this.cycleThinkingMode(),
|
|
309
|
-
onToggleAlphaZero: () => this.toggleAlphaZeroMode('shortcut'),
|
|
310
274
|
onClearContext: () => this.handleClearContext(),
|
|
311
275
|
});
|
|
312
|
-
this.assistantBlockRenderer = this.assistantBlocksEnabled
|
|
313
|
-
? new AssistantBlockRenderer({
|
|
314
|
-
write: (text) => this.writeAssistantBlock(text),
|
|
315
|
-
updates: this.uiUpdates,
|
|
316
|
-
})
|
|
317
|
-
: null;
|
|
318
276
|
// Initialize Alpha Zero 2 metrics tracking
|
|
319
277
|
this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
|
|
320
278
|
this.setupStatusTracking();
|
|
321
279
|
this.refreshContextGauge();
|
|
280
|
+
// Start terminal input (sets up handlers)
|
|
281
|
+
this.terminalInput.start();
|
|
322
282
|
// Allow planning tools (e.g., ProposePlan) to open the interactive approval UI just like Codex CLI
|
|
323
283
|
this.registerPlanApprovalBridge();
|
|
324
284
|
// Capture display output into the scrollback/chat log so system messages
|
|
@@ -336,6 +296,9 @@ export class InteractiveShell {
|
|
|
336
296
|
// Stream banner first - this sets up scroll region dynamically
|
|
337
297
|
const banner = this.buildBanner();
|
|
338
298
|
this.terminalInput.streamContent(banner + '\n\n');
|
|
299
|
+
// Render chat box after banner is streamed
|
|
300
|
+
this.refreshControlBar();
|
|
301
|
+
this.terminalInput.forceRender();
|
|
339
302
|
this.rebuildAgent();
|
|
340
303
|
this.setupHandlers();
|
|
341
304
|
this.refreshBannerSessionInfo();
|
|
@@ -350,13 +313,7 @@ export class InteractiveShell {
|
|
|
350
313
|
this.parallelAgentDisplayLines = manager.formatDisplay();
|
|
351
314
|
// Trigger UI refresh if streaming
|
|
352
315
|
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
|
-
});
|
|
316
|
+
this.displayParallelAgents();
|
|
360
317
|
}
|
|
361
318
|
};
|
|
362
319
|
manager.on('agent:started', updateDisplay);
|
|
@@ -427,56 +384,13 @@ export class InteractiveShell {
|
|
|
427
384
|
this.sessionResumeNotice = null;
|
|
428
385
|
}
|
|
429
386
|
async start(initialPrompt) {
|
|
430
|
-
await this.runStartupUpdatePrompt();
|
|
431
|
-
this.ensureInputInitialized();
|
|
432
387
|
if (initialPrompt) {
|
|
433
388
|
await this.processInputBlock(initialPrompt);
|
|
434
389
|
return;
|
|
435
390
|
}
|
|
436
391
|
this.showLaunchCommandPalette();
|
|
437
392
|
// 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'));
|
|
393
|
+
this.terminalInput.render();
|
|
480
394
|
}
|
|
481
395
|
showLaunchCommandPalette() {
|
|
482
396
|
// Disabled: Quick commands palette takes up too much space
|
|
@@ -487,10 +401,6 @@ export class InteractiveShell {
|
|
|
487
401
|
* TerminalInputAdapter submit handler
|
|
488
402
|
*/
|
|
489
403
|
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
404
|
const approved = this.resolveEditPermission(text);
|
|
495
405
|
if (!approved) {
|
|
496
406
|
this.handleInputChange('');
|
|
@@ -506,10 +416,6 @@ export class InteractiveShell {
|
|
|
506
416
|
* TerminalInputAdapter queue handler (streaming mode)
|
|
507
417
|
*/
|
|
508
418
|
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
419
|
if (this.isExitCommand(text)) {
|
|
514
420
|
// Allow immediate exits even while streaming
|
|
515
421
|
this.terminalInput.dequeue();
|
|
@@ -534,8 +440,7 @@ export class InteractiveShell {
|
|
|
534
440
|
// Keep adapter queue trimmed so hints stay accurate
|
|
535
441
|
this.terminalInput.dequeue();
|
|
536
442
|
this.followUpQueue.push({ type: 'request', text });
|
|
537
|
-
|
|
538
|
-
this.terminalInput.recordRecentAction(`queued: ${text}`);
|
|
443
|
+
display.showInfo(`Queued: "${text}"`);
|
|
539
444
|
this.refreshQueueIndicators();
|
|
540
445
|
this.scheduleQueueProcessing();
|
|
541
446
|
this.handleInputChange('');
|
|
@@ -574,7 +479,6 @@ export class InteractiveShell {
|
|
|
574
479
|
// Mode toggles
|
|
575
480
|
'/thinking',
|
|
576
481
|
'/autocontinue',
|
|
577
|
-
'/alphazero',
|
|
578
482
|
// Discovery and plugins
|
|
579
483
|
'/local', '/discover',
|
|
580
484
|
'/plugins',
|
|
@@ -596,30 +500,15 @@ export class InteractiveShell {
|
|
|
596
500
|
* Execute a command immediately during streaming.
|
|
597
501
|
*/
|
|
598
502
|
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
|
-
}
|
|
503
|
+
// Pause streaming display briefly to show command output
|
|
504
|
+
display.showInfo(`Running command during stream: ${text}`);
|
|
505
|
+
await this.processSlashCommand(text);
|
|
613
506
|
}
|
|
614
507
|
/**
|
|
615
508
|
* TerminalInputAdapter change handler
|
|
616
509
|
*/
|
|
617
510
|
handleInputChange(text) {
|
|
618
|
-
const previous = this.currentInput;
|
|
619
511
|
this.currentInput = text;
|
|
620
|
-
if (previous !== text) {
|
|
621
|
-
this.terminalInput.clearInlineCommandPanel();
|
|
622
|
-
}
|
|
623
512
|
if (text.length > 0) {
|
|
624
513
|
this.resetCtrlCSequence();
|
|
625
514
|
}
|
|
@@ -641,7 +530,7 @@ export class InteractiveShell {
|
|
|
641
530
|
display.showSystemMessage('✏️ Display edits mode enabled.');
|
|
642
531
|
}
|
|
643
532
|
}
|
|
644
|
-
this.
|
|
533
|
+
this.terminalInput.render();
|
|
645
534
|
}
|
|
646
535
|
toggleVerificationMode() {
|
|
647
536
|
this.setVerificationMode(!this.verificationEnabled, 'shortcut');
|
|
@@ -654,8 +543,8 @@ export class InteractiveShell {
|
|
|
654
543
|
return;
|
|
655
544
|
}
|
|
656
545
|
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)';
|
|
546
|
+
? '✅ 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)'
|
|
547
|
+
: '⏭️ Verification off. Skipping auto-tests until re-enabled. (Ctrl+Shift+V to toggle; defaults to off unless enabled via /features)';
|
|
659
548
|
display.showSystemMessage(message);
|
|
660
549
|
}
|
|
661
550
|
toggleAutoContinueMode() {
|
|
@@ -678,50 +567,15 @@ export class InteractiveShell {
|
|
|
678
567
|
: 'The model will not be auto-prompted to continue.') +
|
|
679
568
|
' Toggle with Ctrl+Shift+C.');
|
|
680
569
|
}
|
|
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
570
|
/**
|
|
713
571
|
* Cycle through thinking modes (Ctrl+Shift+T keyboard shortcut).
|
|
714
572
|
*/
|
|
715
573
|
cycleThinkingMode() {
|
|
716
|
-
const
|
|
717
|
-
const currentIndex = modes.indexOf(this.thinkingMode);
|
|
718
|
-
const nextIndex = (currentIndex + 1) % modes.length;
|
|
719
|
-
const nextMode = modes[nextIndex];
|
|
574
|
+
const nextMode = this.thinkingMode === 'balanced' ? 'extended' : 'balanced';
|
|
720
575
|
this.thinkingMode = nextMode;
|
|
721
576
|
saveSessionPreferences({ thinkingMode: this.thinkingMode });
|
|
722
577
|
this.refreshControlBar();
|
|
723
578
|
const descriptions = {
|
|
724
|
-
concise: 'Minimal reasoning, faster responses',
|
|
725
579
|
balanced: 'Default reasoning depth',
|
|
726
580
|
extended: 'Deep reasoning, thorough analysis',
|
|
727
581
|
};
|
|
@@ -744,27 +598,17 @@ export class InteractiveShell {
|
|
|
744
598
|
*/
|
|
745
599
|
async performContextCompaction() {
|
|
746
600
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
601
|
+
// For now, just clear the history and show a message
|
|
602
|
+
// A full implementation would summarize the conversation
|
|
603
|
+
const oldLength = this.cachedHistory.length;
|
|
749
604
|
if (oldLength === 0) {
|
|
750
605
|
display.showInfo('Context is already empty.');
|
|
751
606
|
return;
|
|
752
607
|
}
|
|
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}. ` +
|
|
608
|
+
// Keep the last few messages for continuity
|
|
609
|
+
const keepCount = Math.min(4, oldLength);
|
|
610
|
+
this.cachedHistory = this.cachedHistory.slice(-keepCount);
|
|
611
|
+
display.showSuccess(`Context compacted: ${oldLength} messages reduced to ${keepCount}. ` +
|
|
768
612
|
`Context usage reset. Continue your conversation.`);
|
|
769
613
|
this.refreshControlBar();
|
|
770
614
|
}
|
|
@@ -796,7 +640,7 @@ export class InteractiveShell {
|
|
|
796
640
|
if (['n', 'no', 'cancel', '/cancel'].includes(lower)) {
|
|
797
641
|
this.pendingPermissionInput = null;
|
|
798
642
|
display.showInfo('Request cancelled.');
|
|
799
|
-
this.
|
|
643
|
+
this.terminalInput.render();
|
|
800
644
|
return null;
|
|
801
645
|
}
|
|
802
646
|
// Treat any other input as a replacement request that also needs confirmation
|
|
@@ -814,7 +658,7 @@ export class InteractiveShell {
|
|
|
814
658
|
]
|
|
815
659
|
.filter(Boolean)
|
|
816
660
|
.join('\n'));
|
|
817
|
-
this.
|
|
661
|
+
this.terminalInput.render();
|
|
818
662
|
}
|
|
819
663
|
/**
|
|
820
664
|
* Handle Ctrl+C presses in three stages:
|
|
@@ -828,8 +672,8 @@ export class InteractiveShell {
|
|
|
828
672
|
if (this.ctrlCPressCount === 1) {
|
|
829
673
|
this.clearChatInput();
|
|
830
674
|
const prefix = hadBuffer ? 'Input cleared.' : 'Nothing to clear.';
|
|
831
|
-
display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI
|
|
832
|
-
this.
|
|
675
|
+
display.showSystemMessage(`${prefix} Press Ctrl+C again to pause the AI.`);
|
|
676
|
+
this.terminalInput.render();
|
|
833
677
|
return;
|
|
834
678
|
}
|
|
835
679
|
if (this.ctrlCPressCount === 2) {
|
|
@@ -841,10 +685,10 @@ export class InteractiveShell {
|
|
|
841
685
|
pauseAiExecution() {
|
|
842
686
|
if (this.isProcessing && this.agent) {
|
|
843
687
|
this.agent.requestCancellation();
|
|
844
|
-
display.showWarning('Pausing AI execution...
|
|
688
|
+
display.showWarning('Pausing AI execution...');
|
|
845
689
|
return;
|
|
846
690
|
}
|
|
847
|
-
display.showInfo('No active AI execution to pause.
|
|
691
|
+
display.showInfo('No active AI execution to pause.');
|
|
848
692
|
}
|
|
849
693
|
resetCtrlCSequence() {
|
|
850
694
|
this.ctrlCPressCount = 0;
|
|
@@ -877,7 +721,7 @@ export class InteractiveShell {
|
|
|
877
721
|
: null;
|
|
878
722
|
// Stop any active spinner to prevent process hang
|
|
879
723
|
display.stopThinking(false);
|
|
880
|
-
this.stopStreamingHeartbeat(
|
|
724
|
+
this.stopStreamingHeartbeat();
|
|
881
725
|
this.uiUpdates.dispose();
|
|
882
726
|
this.clearPromptRefreshTimer();
|
|
883
727
|
this.teardownStatusTracking();
|
|
@@ -886,9 +730,7 @@ export class InteractiveShell {
|
|
|
886
730
|
// Clear any pending cleanup to prevent hanging
|
|
887
731
|
this.pendingCleanup = null;
|
|
888
732
|
// Reset terminal state before disposing adapters
|
|
889
|
-
|
|
890
|
-
this.terminalInput.exitStreamingScrollRegion({ skipRender: true });
|
|
891
|
-
}
|
|
733
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
892
734
|
if (this.alternateScreenEnabled) {
|
|
893
735
|
this.terminalInput.exitAlternateScreen();
|
|
894
736
|
}
|
|
@@ -901,9 +743,7 @@ export class InteractiveShell {
|
|
|
901
743
|
if (scrollbackSnapshot && scrollbackSnapshot.length > 0) {
|
|
902
744
|
this.restoreScrollbackSnapshot(scrollbackSnapshot);
|
|
903
745
|
}
|
|
904
|
-
|
|
905
|
-
console.log(theme.ui.muted(' Goodbye! · support@ero.solar'));
|
|
906
|
-
console.log(theme.ui.muted('━'.repeat(44)));
|
|
746
|
+
display.stream(`\n${theme.ui.muted('Session closed.')}\n`);
|
|
907
747
|
exit(0);
|
|
908
748
|
}
|
|
909
749
|
restoreScrollbackSnapshot(lines) {
|
|
@@ -913,8 +753,8 @@ export class InteractiveShell {
|
|
|
913
753
|
const transcript = lines.join('\n');
|
|
914
754
|
const separator = theme.ui.muted('─'.repeat(44));
|
|
915
755
|
const header = theme.ui.muted('Restored scrollback from this session:');
|
|
916
|
-
//
|
|
917
|
-
|
|
756
|
+
// Stream the restored transcript so it stays within the in-stream UI flow
|
|
757
|
+
display.stream(`\n${separator}\n${header}\n${transcript}\n${separator}\n`);
|
|
918
758
|
}
|
|
919
759
|
/**
|
|
920
760
|
* Wire the planning tool suite to the interactive plan approval UI so ProposePlan behaves like Codex CLI.
|
|
@@ -931,11 +771,8 @@ export class InteractiveShell {
|
|
|
931
771
|
/**
|
|
932
772
|
* Update status bar message
|
|
933
773
|
*/
|
|
934
|
-
updateStatusMessage(message
|
|
774
|
+
updateStatusMessage(message) {
|
|
935
775
|
this.statusMessageOverride = message;
|
|
936
|
-
if (message && options.logRecent !== false) {
|
|
937
|
-
this.terminalInput.recordRecentAction(`[status] ${message}`);
|
|
938
|
-
}
|
|
939
776
|
// During streaming we still want the spinner prefix; when idle force a fast refresh.
|
|
940
777
|
this.refreshStatusLine(!this.isProcessing);
|
|
941
778
|
}
|
|
@@ -947,26 +784,26 @@ export class InteractiveShell {
|
|
|
947
784
|
const trimmed = input.trim();
|
|
948
785
|
if (!trimmed) {
|
|
949
786
|
display.showWarning('Enter a number, "save", "defaults", or "cancel".');
|
|
950
|
-
this.
|
|
787
|
+
this.terminalInput.render();
|
|
951
788
|
return;
|
|
952
789
|
}
|
|
953
790
|
const normalized = trimmed.toLowerCase();
|
|
954
791
|
if (normalized === 'cancel') {
|
|
955
792
|
this.pendingInteraction = null;
|
|
956
793
|
display.showInfo('Tool selection cancelled.');
|
|
957
|
-
this.
|
|
794
|
+
this.terminalInput.render();
|
|
958
795
|
return;
|
|
959
796
|
}
|
|
960
797
|
if (normalized === 'defaults') {
|
|
961
798
|
pending.selection = buildEnabledToolSet(null);
|
|
962
799
|
this.renderToolMenu(pending);
|
|
963
|
-
this.
|
|
800
|
+
this.terminalInput.render();
|
|
964
801
|
return;
|
|
965
802
|
}
|
|
966
803
|
if (normalized === 'save') {
|
|
967
804
|
await this.persistToolSelection(pending);
|
|
968
805
|
this.pendingInteraction = null;
|
|
969
|
-
this.
|
|
806
|
+
this.terminalInput.render();
|
|
970
807
|
return;
|
|
971
808
|
}
|
|
972
809
|
const choice = Number.parseInt(trimmed, 10);
|
|
@@ -976,19 +813,24 @@ export class InteractiveShell {
|
|
|
976
813
|
display.showWarning('That option is not available.');
|
|
977
814
|
}
|
|
978
815
|
else {
|
|
979
|
-
if (
|
|
980
|
-
|
|
816
|
+
if (option.locked) {
|
|
817
|
+
display.showInfo('The default tool package is always enabled and cannot be toggled.');
|
|
981
818
|
}
|
|
982
819
|
else {
|
|
983
|
-
pending.selection.
|
|
820
|
+
if (pending.selection.has(option.id)) {
|
|
821
|
+
pending.selection.delete(option.id);
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
pending.selection.add(option.id);
|
|
825
|
+
}
|
|
984
826
|
}
|
|
985
827
|
this.renderToolMenu(pending);
|
|
986
828
|
}
|
|
987
|
-
this.
|
|
829
|
+
this.terminalInput.render();
|
|
988
830
|
return;
|
|
989
831
|
}
|
|
990
832
|
display.showWarning('Enter a number, "save", "defaults", or "cancel".');
|
|
991
|
-
this.
|
|
833
|
+
this.terminalInput.render();
|
|
992
834
|
}
|
|
993
835
|
async persistToolSelection(interaction) {
|
|
994
836
|
if (setsEqual(interaction.selection, interaction.initialSelection)) {
|
|
@@ -998,10 +840,11 @@ export class InteractiveShell {
|
|
|
998
840
|
const defaults = buildEnabledToolSet(null);
|
|
999
841
|
if (setsEqual(interaction.selection, defaults)) {
|
|
1000
842
|
clearToolSettings();
|
|
1001
|
-
display.showInfo('Tool settings cleared. Defaults will be used on the next launch.
|
|
843
|
+
display.showInfo('Tool settings cleared. Defaults will be used on the next launch.');
|
|
1002
844
|
return;
|
|
1003
845
|
}
|
|
1004
846
|
const ordered = interaction.options
|
|
847
|
+
.filter((option) => !option.locked)
|
|
1005
848
|
.map((option) => option.id)
|
|
1006
849
|
.filter((id) => interaction.selection.has(id));
|
|
1007
850
|
saveToolSettings({ enabledTools: ordered });
|
|
@@ -1015,36 +858,36 @@ export class InteractiveShell {
|
|
|
1015
858
|
if (!this.agentMenu) {
|
|
1016
859
|
this.pendingInteraction = null;
|
|
1017
860
|
display.showWarning('Agent selection is unavailable in this CLI.');
|
|
1018
|
-
this.
|
|
861
|
+
this.terminalInput.render();
|
|
1019
862
|
return;
|
|
1020
863
|
}
|
|
1021
864
|
const trimmed = input.trim();
|
|
1022
865
|
if (!trimmed) {
|
|
1023
866
|
display.showWarning('Enter a number or type "cancel".');
|
|
1024
|
-
this.
|
|
867
|
+
this.terminalInput.render();
|
|
1025
868
|
return;
|
|
1026
869
|
}
|
|
1027
870
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
1028
871
|
this.pendingInteraction = null;
|
|
1029
872
|
display.showInfo('Agent selection cancelled.');
|
|
1030
|
-
this.
|
|
873
|
+
this.terminalInput.render();
|
|
1031
874
|
return;
|
|
1032
875
|
}
|
|
1033
876
|
const choice = Number.parseInt(trimmed, 10);
|
|
1034
877
|
if (!Number.isFinite(choice)) {
|
|
1035
878
|
display.showWarning('Please enter a valid number.');
|
|
1036
|
-
this.
|
|
879
|
+
this.terminalInput.render();
|
|
1037
880
|
return;
|
|
1038
881
|
}
|
|
1039
882
|
const option = pending.options[choice - 1];
|
|
1040
883
|
if (!option) {
|
|
1041
884
|
display.showWarning('That option is not available.');
|
|
1042
|
-
this.
|
|
885
|
+
this.terminalInput.render();
|
|
1043
886
|
return;
|
|
1044
887
|
}
|
|
1045
888
|
await this.persistAgentSelection(option.name);
|
|
1046
889
|
this.pendingInteraction = null;
|
|
1047
|
-
this.
|
|
890
|
+
this.terminalInput.render();
|
|
1048
891
|
}
|
|
1049
892
|
async persistAgentSelection(profileName) {
|
|
1050
893
|
if (!this.agentMenu) {
|
|
@@ -1117,7 +960,7 @@ export class InteractiveShell {
|
|
|
1117
960
|
lines.push(` ${theme.primary('[text]')} Submit your own solution instead`);
|
|
1118
961
|
lines.push('');
|
|
1119
962
|
display.showSystemMessage(lines.join('\n'));
|
|
1120
|
-
this.
|
|
963
|
+
this.terminalInput.render();
|
|
1121
964
|
}
|
|
1122
965
|
async handlePlanApprovalInput(input) {
|
|
1123
966
|
const pending = this.pendingInteraction;
|
|
@@ -1126,7 +969,7 @@ export class InteractiveShell {
|
|
|
1126
969
|
const trimmed = input.trim();
|
|
1127
970
|
if (!trimmed) {
|
|
1128
971
|
display.showWarning('Enter a command or your own solution.');
|
|
1129
|
-
this.
|
|
972
|
+
this.terminalInput.render();
|
|
1130
973
|
return;
|
|
1131
974
|
}
|
|
1132
975
|
const lower = trimmed.toLowerCase();
|
|
@@ -1134,7 +977,7 @@ export class InteractiveShell {
|
|
|
1134
977
|
if (lower === 'cancel' || lower === 'c') {
|
|
1135
978
|
this.pendingInteraction = null;
|
|
1136
979
|
display.showInfo('Plan cancelled. You can continue with a different approach.');
|
|
1137
|
-
this.
|
|
980
|
+
this.terminalInput.render();
|
|
1138
981
|
return;
|
|
1139
982
|
}
|
|
1140
983
|
// Select all
|
|
@@ -1154,7 +997,7 @@ export class InteractiveShell {
|
|
|
1154
997
|
const selectedSteps = pending.steps.filter(s => pending.selectedSteps.has(s.id));
|
|
1155
998
|
if (selectedSteps.length === 0) {
|
|
1156
999
|
display.showWarning('No steps selected. Select steps or enter your own solution.');
|
|
1157
|
-
this.
|
|
1000
|
+
this.terminalInput.render();
|
|
1158
1001
|
return;
|
|
1159
1002
|
}
|
|
1160
1003
|
this.pendingInteraction = null;
|
|
@@ -1187,18 +1030,15 @@ export class InteractiveShell {
|
|
|
1187
1030
|
return;
|
|
1188
1031
|
}
|
|
1189
1032
|
display.showWarning('Invalid input. Enter a step number, command (go/cancel/all/none), or your own solution.');
|
|
1190
|
-
this.
|
|
1033
|
+
this.terminalInput.render();
|
|
1191
1034
|
}
|
|
1192
1035
|
setupHandlers() {
|
|
1193
1036
|
// Handle terminal resize
|
|
1194
1037
|
output.on('resize', () => {
|
|
1195
|
-
if (!this.inputInitialized) {
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
this.terminalInput.resetContentPosition();
|
|
1199
1038
|
this.terminalInput.handleResize();
|
|
1200
|
-
this.terminalInput.forceRender();
|
|
1201
1039
|
});
|
|
1040
|
+
// Show initial input UI
|
|
1041
|
+
this.terminalInput.render();
|
|
1202
1042
|
}
|
|
1203
1043
|
/**
|
|
1204
1044
|
* Set up command autocomplete with all available slash commands.
|
|
@@ -1229,14 +1069,6 @@ export class InteractiveShell {
|
|
|
1229
1069
|
catch {
|
|
1230
1070
|
// Custom commands are optional
|
|
1231
1071
|
}
|
|
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
1072
|
// Sort commands alphabetically
|
|
1241
1073
|
commands.sort((a, b) => a.command.localeCompare(b.command));
|
|
1242
1074
|
this.terminalInput.setAvailableCommands(commands);
|
|
@@ -1317,318 +1149,23 @@ export class InteractiveShell {
|
|
|
1317
1149
|
autoContinueHotkey: 'ctrl+shift+c',
|
|
1318
1150
|
thinkingModeLabel: this.thinkingMode,
|
|
1319
1151
|
thinkingHotkey: 'ctrl+shift+t',
|
|
1320
|
-
alphaZeroEnabled: this.alphaZeroModeEnabled,
|
|
1321
|
-
alphaZeroHotkey: 'ctrl+shift+a',
|
|
1322
|
-
alphaZeroLabel: 'AlphaZero RL',
|
|
1323
1152
|
});
|
|
1324
|
-
this.refreshFeatureStatusDisplay();
|
|
1325
1153
|
this.refreshStatusLine();
|
|
1326
|
-
this.
|
|
1327
|
-
}
|
|
1328
|
-
refreshFeatureStatusDisplay() {
|
|
1329
|
-
this.terminalInput.setFeatureStatus(this.buildFeatureStatusSnapshot());
|
|
1154
|
+
this.terminalInput.render();
|
|
1330
1155
|
}
|
|
1331
1156
|
writeLocked(content) {
|
|
1332
1157
|
if (!content) {
|
|
1333
1158
|
return;
|
|
1334
1159
|
}
|
|
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;
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
this.assistantStreamActive = true;
|
|
1352
|
-
// Default to thought phase so the first output after a prompt is the assistant's thinking.
|
|
1353
|
-
this.assistantStreamPhase = 'thought';
|
|
1354
|
-
this.assistantStreamHeaderShown = false;
|
|
1355
|
-
this.assistantStreamBuffer = '';
|
|
1356
|
-
this.assistantStreamMetadata = metadata;
|
|
1357
|
-
this.assistantStreamHadContent = false;
|
|
1358
|
-
}
|
|
1359
|
-
streamAssistantChunk(chunk, channel) {
|
|
1360
|
-
if (!chunk) {
|
|
1160
|
+
// If lock is already held, write directly - we're in a protected context
|
|
1161
|
+
// This prevents queuing issues where content gets delayed
|
|
1162
|
+
if (writeLock.isLocked()) {
|
|
1163
|
+
process.stdout.write(content);
|
|
1361
1164
|
return;
|
|
1362
1165
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
// Use channel to directly set phase for streaming chunks
|
|
1369
|
-
// Agent sends channel='thought' for reasoning content, channel='response' for regular content
|
|
1370
|
-
if (channel === 'thought' || channel === 'response') {
|
|
1371
|
-
this.setAssistantStreamPhase(channel === 'thought' ? 'thought' : 'response');
|
|
1372
|
-
// Write content directly without tag parsing for channel-routed chunks
|
|
1373
|
-
this.writeAssistantStream(chunk);
|
|
1374
|
-
}
|
|
1375
|
-
else {
|
|
1376
|
-
// Fall back to tag parsing for chunks without explicit channel
|
|
1377
|
-
this.processAssistantStreamChunk(chunk);
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
finalizeAssistantStream() {
|
|
1381
|
-
if (!this.assistantStreamActive) {
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
if (this.assistantStreamBuffer) {
|
|
1385
|
-
this.writeAssistantStream(this.assistantStreamBuffer);
|
|
1386
|
-
this.assistantStreamBuffer = '';
|
|
1387
|
-
}
|
|
1388
|
-
this.assistantStreamActive = false;
|
|
1389
|
-
this.assistantStreamPhase = null;
|
|
1390
|
-
this.assistantStreamHeaderShown = false;
|
|
1391
|
-
this.assistantStreamMetadata = undefined;
|
|
1392
|
-
}
|
|
1393
|
-
processAssistantStreamChunk(chunk) {
|
|
1394
|
-
this.assistantStreamBuffer += chunk;
|
|
1395
|
-
const tagRegex = /<(\/?)(thinking|response)>/i;
|
|
1396
|
-
while (this.assistantStreamBuffer.length > 0) {
|
|
1397
|
-
const match = tagRegex.exec(this.assistantStreamBuffer);
|
|
1398
|
-
if (!match || match.index === undefined) {
|
|
1399
|
-
// No more full tags - flush what we can but keep any partial tag prefix buffered
|
|
1400
|
-
const { flushable, remainder } = this.splitAssistantStreamRemainder(this.assistantStreamBuffer);
|
|
1401
|
-
if (flushable) {
|
|
1402
|
-
this.writeAssistantStream(flushable);
|
|
1403
|
-
}
|
|
1404
|
-
this.assistantStreamBuffer = remainder;
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1407
|
-
const tagIndex = match.index;
|
|
1408
|
-
const beforeTag = this.assistantStreamBuffer.slice(0, tagIndex);
|
|
1409
|
-
if (beforeTag) {
|
|
1410
|
-
this.writeAssistantStream(beforeTag);
|
|
1411
|
-
}
|
|
1412
|
-
// Advance buffer past the tag
|
|
1413
|
-
this.assistantStreamBuffer = this.assistantStreamBuffer.slice(tagIndex + match[0].length);
|
|
1414
|
-
const isClosing = match[1] === '/';
|
|
1415
|
-
const rawTagType = (match[2] ?? '').toLowerCase();
|
|
1416
|
-
// Map 'thinking' to 'thought' for AssistantBlockType compatibility
|
|
1417
|
-
const tagType = (rawTagType === 'thinking' ? 'thought' : rawTagType);
|
|
1418
|
-
if (isClosing) {
|
|
1419
|
-
if (this.assistantStreamPhase === tagType) {
|
|
1420
|
-
this.assistantStreamPhase = null;
|
|
1421
|
-
this.assistantStreamHeaderShown = false;
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
else {
|
|
1425
|
-
this.setAssistantStreamPhase(tagType);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
writeAssistantStream(content) {
|
|
1430
|
-
if (!content) {
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
if (content.trim().length > 0) {
|
|
1434
|
-
this.assistantStreamHadContent = true;
|
|
1435
|
-
}
|
|
1436
|
-
// If no explicit phase has been set yet, keep the stream labeled as "thought"
|
|
1437
|
-
// until a <response> tag arrives so operators see thinking first.
|
|
1438
|
-
this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
|
|
1439
|
-
if (this.assistantStreamPhase) {
|
|
1440
|
-
this.renderAssistantStreamHeader(this.assistantStreamPhase);
|
|
1441
|
-
}
|
|
1442
|
-
let output = content;
|
|
1443
|
-
if (this.assistantStreamPhase === 'thought') {
|
|
1444
|
-
output = this.formatThoughtDisplay(content, { streaming: true });
|
|
1445
|
-
}
|
|
1446
|
-
this.enqueueAssistantStream(output);
|
|
1447
|
-
}
|
|
1448
|
-
setAssistantStreamPhase(phase) {
|
|
1449
|
-
if (this.assistantStreamPhase === phase) {
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
|
-
this.assistantStreamPhase = phase;
|
|
1453
|
-
this.assistantStreamHeaderShown = false;
|
|
1454
|
-
if (phase) {
|
|
1455
|
-
this.renderAssistantStreamHeader(phase);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
renderAssistantStreamHeader(type) {
|
|
1459
|
-
if (this.assistantStreamHeaderShown) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
const header = this.formatAssistantHeader(type, this.assistantStreamMetadata);
|
|
1463
|
-
if (header.trim()) {
|
|
1464
|
-
this.enqueueAssistantStream(`\n${header}\n`);
|
|
1465
|
-
}
|
|
1466
|
-
this.assistantStreamHeaderShown = true;
|
|
1467
|
-
}
|
|
1468
|
-
splitAssistantStreamRemainder(buffer) {
|
|
1469
|
-
if (!buffer) {
|
|
1470
|
-
return { flushable: '', remainder: '' };
|
|
1471
|
-
}
|
|
1472
|
-
const tags = ['<thinking>', '</thinking>', '<response>', '</response>'];
|
|
1473
|
-
let longest = '';
|
|
1474
|
-
for (const tag of tags) {
|
|
1475
|
-
for (let i = 1; i < tag.length; i += 1) {
|
|
1476
|
-
const prefix = tag.slice(0, i);
|
|
1477
|
-
if (buffer.endsWith(prefix) && prefix.length > longest.length) {
|
|
1478
|
-
longest = prefix;
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
if (!longest) {
|
|
1483
|
-
return { flushable: buffer, remainder: '' };
|
|
1484
|
-
}
|
|
1485
|
-
return {
|
|
1486
|
-
flushable: buffer.slice(0, -longest.length),
|
|
1487
|
-
remainder: longest,
|
|
1488
|
-
};
|
|
1489
|
-
}
|
|
1490
|
-
enqueueAssistantStream(content) {
|
|
1491
|
-
if (!content) {
|
|
1492
|
-
return;
|
|
1493
|
-
}
|
|
1494
|
-
const run = () => this.writeAssistantBlock(content);
|
|
1495
|
-
this.uiUpdates.enqueue({
|
|
1496
|
-
lane: 'stream',
|
|
1497
|
-
mode: ['streaming', 'processing', 'idle'],
|
|
1498
|
-
priority: 'high',
|
|
1499
|
-
description: 'assistant stream flush',
|
|
1500
|
-
run,
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
writeAssistantBlock(content) {
|
|
1504
|
-
if (!content) {
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
// Ensure assistant block writes are serialized with terminal input renders
|
|
1508
|
-
writeLock.safeWrite(() => {
|
|
1509
|
-
this.terminalInput.streamContent(content);
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
renderAssistantBlock(type, content, metadata) {
|
|
1513
|
-
if (!this.assistantBlocksEnabled || !this.assistantBlockRenderer) {
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
this.assistantBlockRenderer.renderBlock(type, content, metadata);
|
|
1517
|
-
// Ensure the prompt stays pinned below freshly written blocks
|
|
1518
|
-
this.renderPromptArea();
|
|
1519
|
-
}
|
|
1520
|
-
renderAssistantContent(type, content, metadata) {
|
|
1521
|
-
const normalized = content.replace(/\r\n/g, '\n').trim();
|
|
1522
|
-
if (!normalized) {
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
|
|
1526
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1527
|
-
this.renderAssistantBlock(type, formatted, metadata);
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
this.renderAssistantFallback(type, formatted, metadata);
|
|
1531
|
-
}
|
|
1532
|
-
renderAssistantFallback(type, content, metadata) {
|
|
1533
|
-
const header = this.formatAssistantHeader(type, metadata);
|
|
1534
|
-
const compact = content.trimEnd().replace(/\n{3,}/g, '\n\n');
|
|
1535
|
-
const block = `\n${header}\n${compact}\n\n`;
|
|
1536
|
-
this.enqueueAssistantStream(block);
|
|
1537
|
-
}
|
|
1538
|
-
formatThoughtDisplay(content, options = {}) {
|
|
1539
|
-
const normalized = content.replace(/\r\n/g, '\n');
|
|
1540
|
-
if (options.streaming) {
|
|
1541
|
-
return formatThinkingContent(normalized);
|
|
1542
|
-
}
|
|
1543
|
-
const lines = normalized.split('\n');
|
|
1544
|
-
return lines
|
|
1545
|
-
.map((line, index) => {
|
|
1546
|
-
const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
|
|
1547
|
-
const trimmed = line.trim();
|
|
1548
|
-
const body = trimmed ? formatThinkingContent(trimmed) : '';
|
|
1549
|
-
return body ? `${prefix} ${body}` : `${prefix}`;
|
|
1550
|
-
})
|
|
1551
|
-
.join('\n');
|
|
1552
|
-
}
|
|
1553
|
-
formatAssistantHeader(type, metadata) {
|
|
1554
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1555
|
-
return this.assistantBlockRenderer.formatHeader(type, metadata);
|
|
1556
|
-
}
|
|
1557
|
-
const badges = [];
|
|
1558
|
-
badges.push(this.buildAssistantTypeBadge(type));
|
|
1559
|
-
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
1560
|
-
hour12: false,
|
|
1561
|
-
hour: '2-digit',
|
|
1562
|
-
minute: '2-digit',
|
|
1563
|
-
second: '2-digit',
|
|
1564
|
-
});
|
|
1565
|
-
badges.push({ text: timestamp, style: 'muted', icon: '🕑' });
|
|
1566
|
-
const total = metadata?.usage ? this.totalTokens(metadata.usage) : null;
|
|
1567
|
-
const windowTokens = metadata?.contextWindowTokens;
|
|
1568
|
-
if (typeof total === 'number') {
|
|
1569
|
-
badges.push({ text: `${total} tok`, style: 'muted', icon: '⎍' });
|
|
1570
|
-
}
|
|
1571
|
-
if (typeof total === 'number' && typeof windowTokens === 'number' && windowTokens > 0) {
|
|
1572
|
-
const percentage = Math.round((total / windowTokens) * 100);
|
|
1573
|
-
const style = percentage > 85 ? 'error' : percentage > 70 ? 'warning' : 'info';
|
|
1574
|
-
badges.push({ text: `${percentage}% ctx`, style, icon: '⊛' });
|
|
1575
|
-
}
|
|
1576
|
-
const elapsedBadge = this.buildElapsedBadge(metadata);
|
|
1577
|
-
if (elapsedBadge) {
|
|
1578
|
-
badges.push(elapsedBadge);
|
|
1579
|
-
}
|
|
1580
|
-
return compactRenderer.formatBadges(badges, theme.ui.muted(' │ '));
|
|
1581
|
-
}
|
|
1582
|
-
buildAssistantTypeBadge(type) {
|
|
1583
|
-
switch (type) {
|
|
1584
|
-
case 'thought':
|
|
1585
|
-
return { text: 'Thought', style: 'info', icon: '💭' };
|
|
1586
|
-
case 'tools':
|
|
1587
|
-
return { text: 'Tools', style: 'primary', icon: '🛠' };
|
|
1588
|
-
case 'response':
|
|
1589
|
-
default:
|
|
1590
|
-
return { text: 'Response', style: 'success', icon: '💬' };
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
buildElapsedBadge(metadata) {
|
|
1594
|
-
const elapsed = metadata?.elapsedMs;
|
|
1595
|
-
if (typeof elapsed !== 'number' || elapsed <= 0) {
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
const formatted = elapsed < 1000 ? `${Math.round(elapsed)}ms` : `${Math.round(elapsed / 1000)}s`;
|
|
1599
|
-
return { text: formatted, style: 'muted', icon: '⏱' };
|
|
1600
|
-
}
|
|
1601
|
-
isStreamingUiActive() {
|
|
1602
|
-
return this.streamingHeartbeatStart !== null;
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Render the prompt/control bar. During streaming, rely on the streaming frame
|
|
1606
|
-
* renderer and enqueue through the UIUpdateCoordinator to avoid fighting the
|
|
1607
|
-
* scroll region or duplicating the prompt.
|
|
1608
|
-
*/
|
|
1609
|
-
renderPromptArea(force = false) {
|
|
1610
|
-
if (this.isStreamingUiActive()) {
|
|
1611
|
-
this.uiUpdates.enqueue({
|
|
1612
|
-
lane: 'prompt',
|
|
1613
|
-
mode: ['streaming', 'processing'],
|
|
1614
|
-
coalesceKey: 'prompt:streaming-frame',
|
|
1615
|
-
description: 'render streaming prompt frame',
|
|
1616
|
-
run: () => {
|
|
1617
|
-
if (force) {
|
|
1618
|
-
this.terminalInput.renderStreamingFrame(true);
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
this.terminalInput.renderStreamingFrame();
|
|
1622
|
-
},
|
|
1623
|
-
});
|
|
1624
|
-
return;
|
|
1625
|
-
}
|
|
1626
|
-
if (force) {
|
|
1627
|
-
this.terminalInput.forceRender();
|
|
1628
|
-
}
|
|
1629
|
-
else {
|
|
1630
|
-
this.terminalInput.render();
|
|
1631
|
-
}
|
|
1166
|
+
writeLock.withLock(() => {
|
|
1167
|
+
process.stdout.write(content);
|
|
1168
|
+
}, 'interactiveShell.stdout');
|
|
1632
1169
|
}
|
|
1633
1170
|
/**
|
|
1634
1171
|
* Refresh the status line in the persistent input area.
|
|
@@ -1646,23 +1183,19 @@ export class InteractiveShell {
|
|
|
1646
1183
|
// Surface meta header (elapsed + context usage) above the divider
|
|
1647
1184
|
// Use streaming elapsed time if available, otherwise fall back to status line state
|
|
1648
1185
|
let elapsedSeconds = null;
|
|
1649
|
-
|
|
1650
|
-
if (this.streamingHeartbeatStart && shouldShowElapsed) {
|
|
1186
|
+
if (this.streamingHeartbeatStart) {
|
|
1651
1187
|
// Actively streaming - compute live elapsed
|
|
1652
1188
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1653
1189
|
}
|
|
1654
|
-
else if (
|
|
1190
|
+
else if (this.lastStreamingElapsedSeconds !== null) {
|
|
1655
1191
|
// Just finished streaming - use preserved final time
|
|
1656
1192
|
elapsedSeconds = this.lastStreamingElapsedSeconds;
|
|
1657
1193
|
}
|
|
1658
|
-
else if (
|
|
1194
|
+
else if (this.statusLineState) {
|
|
1659
1195
|
// Fallback to status line state elapsed
|
|
1660
1196
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
|
|
1661
1197
|
}
|
|
1662
|
-
const
|
|
1663
|
-
const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
|
|
1664
|
-
? display.getThinkingElapsedMs()
|
|
1665
|
-
: null;
|
|
1198
|
+
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1666
1199
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1667
1200
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1668
1201
|
this.terminalInput.setMetaStatus({
|
|
@@ -1670,7 +1203,7 @@ export class InteractiveShell {
|
|
|
1670
1203
|
tokensUsed,
|
|
1671
1204
|
tokenLimit,
|
|
1672
1205
|
thinkingMs,
|
|
1673
|
-
thinkingHasContent:
|
|
1206
|
+
thinkingHasContent: display.isSpinnerActive(),
|
|
1674
1207
|
});
|
|
1675
1208
|
// Keep model/provider visible in the controls bar
|
|
1676
1209
|
this.terminalInput.setModelContext({
|
|
@@ -1678,7 +1211,7 @@ export class InteractiveShell {
|
|
|
1678
1211
|
provider: this.providerLabel(this.sessionState.provider),
|
|
1679
1212
|
});
|
|
1680
1213
|
if (forceRender) {
|
|
1681
|
-
this.
|
|
1214
|
+
this.terminalInput.render();
|
|
1682
1215
|
}
|
|
1683
1216
|
}
|
|
1684
1217
|
/**
|
|
@@ -1715,7 +1248,7 @@ export class InteractiveShell {
|
|
|
1715
1248
|
* Ensure the terminal input is ready for interactive input.
|
|
1716
1249
|
*/
|
|
1717
1250
|
ensureReadlineReady() {
|
|
1718
|
-
this.
|
|
1251
|
+
this.terminalInput.render();
|
|
1719
1252
|
}
|
|
1720
1253
|
/**
|
|
1721
1254
|
* Log user prompt to the scroll region so it's part of the conversation flow.
|
|
@@ -1737,7 +1270,7 @@ export class InteractiveShell {
|
|
|
1737
1270
|
}
|
|
1738
1271
|
requestPromptRefresh(force = false) {
|
|
1739
1272
|
if (force) {
|
|
1740
|
-
this.
|
|
1273
|
+
this.terminalInput.forceRender();
|
|
1741
1274
|
return;
|
|
1742
1275
|
}
|
|
1743
1276
|
if (this.promptRefreshTimer) {
|
|
@@ -1745,7 +1278,7 @@ export class InteractiveShell {
|
|
|
1745
1278
|
}
|
|
1746
1279
|
this.promptRefreshTimer = setTimeout(() => {
|
|
1747
1280
|
this.promptRefreshTimer = null;
|
|
1748
|
-
this.
|
|
1281
|
+
this.terminalInput.render();
|
|
1749
1282
|
}, 48);
|
|
1750
1283
|
}
|
|
1751
1284
|
clearPromptRefreshTimer() {
|
|
@@ -1754,53 +1287,35 @@ export class InteractiveShell {
|
|
|
1754
1287
|
this.promptRefreshTimer = null;
|
|
1755
1288
|
}
|
|
1756
1289
|
}
|
|
1757
|
-
async withStreamingUi(label, run) {
|
|
1758
|
-
if (this.isStreamingUiActive()) {
|
|
1759
|
-
return run();
|
|
1760
|
-
}
|
|
1761
|
-
this.resetAssistantStreamTracking();
|
|
1762
|
-
this.terminalInput.setStreaming(true);
|
|
1763
|
-
this.startStreamingHeartbeat(label);
|
|
1764
|
-
try {
|
|
1765
|
-
return await run();
|
|
1766
|
-
}
|
|
1767
|
-
finally {
|
|
1768
|
-
this.stopStreamingHeartbeat();
|
|
1769
|
-
this.terminalInput.setStreaming(false);
|
|
1770
|
-
const nextMode = this.isProcessing ? 'processing' : 'idle';
|
|
1771
|
-
this.uiUpdates.setMode(nextMode);
|
|
1772
|
-
if (nextMode === 'processing') {
|
|
1773
|
-
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
1290
|
startStreamingHeartbeat(label = 'Streaming') {
|
|
1778
|
-
this.stopStreamingHeartbeat(
|
|
1291
|
+
this.stopStreamingHeartbeat();
|
|
1779
1292
|
// Enter global streaming mode - blocks all non-streaming UI output
|
|
1780
1293
|
enterStreamingMode();
|
|
1781
|
-
this.streamingStatusBase = label;
|
|
1782
|
-
this.streamingStatusDetail = null;
|
|
1783
|
-
this.latestThoughtSummary = null;
|
|
1784
|
-
this.lastStreamingElapsedSeconds = null;
|
|
1785
1294
|
// Set up scroll region for streaming content
|
|
1786
1295
|
this.terminalInput.enterStreamingScrollRegion();
|
|
1787
1296
|
this.uiUpdates.setMode('streaming');
|
|
1788
1297
|
this.streamingHeartbeatStart = Date.now();
|
|
1789
1298
|
this.streamingHeartbeatFrame = 0;
|
|
1790
|
-
this.
|
|
1299
|
+
const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1300
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
|
|
1301
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1791
1302
|
this.refreshStatusLine(true);
|
|
1792
1303
|
// Periodically refresh the pinned input/status region while streaming so
|
|
1793
1304
|
// elapsed time remains visible without interrupting the scroll region.
|
|
1794
1305
|
this.uiUpdates.startHeartbeat('streaming', {
|
|
1795
1306
|
intervalMs: 1000,
|
|
1796
1307
|
lane: 'heartbeat',
|
|
1797
|
-
priority: 'high',
|
|
1798
1308
|
mode: ['streaming', 'processing'],
|
|
1799
1309
|
coalesceKey: 'streaming:heartbeat',
|
|
1800
1310
|
run: () => {
|
|
1311
|
+
const elapsedSeconds = this.streamingHeartbeatStart
|
|
1312
|
+
? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
|
|
1313
|
+
: 0;
|
|
1801
1314
|
this.streamingHeartbeatFrame =
|
|
1802
1315
|
(this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
|
|
1803
|
-
this.
|
|
1316
|
+
const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1317
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
|
|
1318
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1804
1319
|
// Update parallel agent display during streaming
|
|
1805
1320
|
const manager = getParallelAgentManager();
|
|
1806
1321
|
if (manager.isRunning()) {
|
|
@@ -1811,13 +1326,7 @@ export class InteractiveShell {
|
|
|
1811
1326
|
},
|
|
1812
1327
|
});
|
|
1813
1328
|
}
|
|
1814
|
-
stopStreamingHeartbeat(
|
|
1815
|
-
const skipRender = !!options.skipRender;
|
|
1816
|
-
const streamingActive = this.isStreamingUiActive();
|
|
1817
|
-
const scrollRegionActive = this.terminalInput.isScrollRegionActive();
|
|
1818
|
-
if (!streamingActive && !scrollRegionActive) {
|
|
1819
|
-
return;
|
|
1820
|
-
}
|
|
1329
|
+
stopStreamingHeartbeat() {
|
|
1821
1330
|
// Exit global streaming mode - allows UI to render again
|
|
1822
1331
|
exitStreamingMode();
|
|
1823
1332
|
// Preserve final elapsed time before clearing heartbeat start
|
|
@@ -1825,44 +1334,51 @@ export class InteractiveShell {
|
|
|
1825
1334
|
this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1826
1335
|
}
|
|
1827
1336
|
// Exit scroll region mode
|
|
1828
|
-
this.terminalInput.exitStreamingScrollRegion(
|
|
1337
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
1829
1338
|
this.uiUpdates.stopHeartbeat('streaming');
|
|
1830
1339
|
this.streamingHeartbeatStart = null;
|
|
1831
1340
|
this.streamingHeartbeatFrame = 0;
|
|
1832
1341
|
this.streamingStatusLabel = null;
|
|
1833
|
-
this.streamingStatusBase = null;
|
|
1834
|
-
this.streamingStatusDetail = null;
|
|
1835
|
-
this.latestThoughtSummary = null;
|
|
1836
1342
|
// Clear streaming label specifically (keeps override and main status if set)
|
|
1837
1343
|
this.terminalInput.setStreamingLabel(null);
|
|
1838
1344
|
// Clear streaming status from display
|
|
1839
1345
|
display.updateStreamingStatus(null);
|
|
1840
1346
|
// Force refresh to update the input area now that streaming has ended
|
|
1841
|
-
|
|
1842
|
-
this.refreshStatusLine(true);
|
|
1843
|
-
}
|
|
1347
|
+
this.refreshStatusLine(true);
|
|
1844
1348
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
if (
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1349
|
+
handleStreamChunk(chunk) {
|
|
1350
|
+
if (!chunk) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
// Preserve raw output in plain/CI modes or non-TTY environments
|
|
1354
|
+
if (isPlainOutputMode() || !output.isTTY) {
|
|
1355
|
+
this.terminalInput.streamContent(chunk);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (!this.streamingFormatter) {
|
|
1359
|
+
this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
|
|
1360
|
+
this.terminalInput.streamContent(this.streamingFormatter.header());
|
|
1361
|
+
}
|
|
1362
|
+
const formatted = this.streamingFormatter.formatChunk(chunk);
|
|
1363
|
+
if (formatted) {
|
|
1364
|
+
this.terminalInput.streamContent(formatted);
|
|
1855
1365
|
}
|
|
1856
|
-
return `${prefix} ${parts.join(' · ')}`.trim();
|
|
1857
1366
|
}
|
|
1858
|
-
|
|
1859
|
-
if (this.
|
|
1367
|
+
finishStreamingFormatter(note) {
|
|
1368
|
+
if (!this.streamingFormatter) {
|
|
1860
1369
|
return;
|
|
1861
1370
|
}
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1371
|
+
const closing = this.streamingFormatter.finish(note);
|
|
1372
|
+
if (closing) {
|
|
1373
|
+
this.terminalInput.streamContent(closing);
|
|
1374
|
+
}
|
|
1375
|
+
this.streamingFormatter = null;
|
|
1376
|
+
}
|
|
1377
|
+
buildStreamingStatus(label, _elapsedSeconds) {
|
|
1378
|
+
// Model + elapsed time already live in the pinned meta header; keep the streaming
|
|
1379
|
+
// status focused on the activity to avoid duplicate info.
|
|
1380
|
+
const prefix = theme.info('⏺');
|
|
1381
|
+
return `${prefix} ${label}`.trim();
|
|
1866
1382
|
}
|
|
1867
1383
|
formatElapsedShort(seconds) {
|
|
1868
1384
|
if (seconds < 60) {
|
|
@@ -1879,7 +1395,7 @@ export class InteractiveShell {
|
|
|
1879
1395
|
else {
|
|
1880
1396
|
this.setIdleStatus();
|
|
1881
1397
|
}
|
|
1882
|
-
this.
|
|
1398
|
+
this.terminalInput.render();
|
|
1883
1399
|
}
|
|
1884
1400
|
enqueueFollowUpAction(action) {
|
|
1885
1401
|
this.followUpQueue.push(action);
|
|
@@ -1898,21 +1414,14 @@ export class InteractiveShell {
|
|
|
1898
1414
|
this.refreshQueueIndicators();
|
|
1899
1415
|
this.scheduleQueueProcessing();
|
|
1900
1416
|
// Re-show the prompt so user can continue typing more follow-ups
|
|
1901
|
-
this.
|
|
1417
|
+
this.terminalInput.render();
|
|
1902
1418
|
}
|
|
1903
1419
|
scheduleQueueProcessing() {
|
|
1904
1420
|
if (!this.followUpQueue.length) {
|
|
1905
1421
|
this.refreshQueueIndicators();
|
|
1906
1422
|
return;
|
|
1907
1423
|
}
|
|
1908
|
-
if (this.apiKeyGateActive) {
|
|
1909
|
-
this.refreshQueueIndicators();
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
1424
|
queueMicrotask(() => {
|
|
1913
|
-
if (this.apiKeyGateActive) {
|
|
1914
|
-
return;
|
|
1915
|
-
}
|
|
1916
1425
|
void this.processQueuedActions();
|
|
1917
1426
|
});
|
|
1918
1427
|
}
|
|
@@ -1920,12 +1429,12 @@ export class InteractiveShell {
|
|
|
1920
1429
|
* Process queued follow-up actions.
|
|
1921
1430
|
*/
|
|
1922
1431
|
async processQueuedActions() {
|
|
1923
|
-
if (this.
|
|
1432
|
+
if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
|
|
1924
1433
|
return;
|
|
1925
1434
|
}
|
|
1926
1435
|
this.isDrainingQueue = true;
|
|
1927
1436
|
try {
|
|
1928
|
-
while (!this.isProcessing &&
|
|
1437
|
+
while (!this.isProcessing && this.followUpQueue.length) {
|
|
1929
1438
|
const next = this.followUpQueue.shift();
|
|
1930
1439
|
const remaining = this.followUpQueue.length;
|
|
1931
1440
|
const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
|
|
@@ -1960,12 +1469,12 @@ export class InteractiveShell {
|
|
|
1960
1469
|
}
|
|
1961
1470
|
if (lower === 'clear') {
|
|
1962
1471
|
display.clear();
|
|
1963
|
-
this.
|
|
1472
|
+
this.terminalInput.render();
|
|
1964
1473
|
return;
|
|
1965
1474
|
}
|
|
1966
1475
|
if (lower === 'help') {
|
|
1967
1476
|
this.showHelp();
|
|
1968
|
-
this.
|
|
1477
|
+
this.terminalInput.render();
|
|
1969
1478
|
return;
|
|
1970
1479
|
}
|
|
1971
1480
|
if (trimmed.startsWith('/')) {
|
|
@@ -1975,12 +1484,12 @@ export class InteractiveShell {
|
|
|
1975
1484
|
// Check for continuous/infinite loop commands
|
|
1976
1485
|
if (this.isContinuousCommand(trimmed)) {
|
|
1977
1486
|
await this.processContinuousRequest(trimmed);
|
|
1978
|
-
this.
|
|
1487
|
+
this.terminalInput.render();
|
|
1979
1488
|
return;
|
|
1980
1489
|
}
|
|
1981
1490
|
// Direct execution for all inputs, including multi-line pastes
|
|
1982
1491
|
await this.processRequest(trimmed);
|
|
1983
|
-
this.
|
|
1492
|
+
this.terminalInput.render();
|
|
1984
1493
|
}
|
|
1985
1494
|
/**
|
|
1986
1495
|
* Check if the command is a continuous/infinite loop command
|
|
@@ -2018,153 +1527,6 @@ export class InteractiveShell {
|
|
|
2018
1527
|
];
|
|
2019
1528
|
return patterns.some(pattern => pattern.test(lower));
|
|
2020
1529
|
}
|
|
2021
|
-
isDifficultProblem(input) {
|
|
2022
|
-
const normalized = input.toLowerCase();
|
|
2023
|
-
const wordCount = normalized.split(/\s+/).filter(Boolean).length;
|
|
2024
|
-
if (normalized.length > 600 || wordCount > 80) {
|
|
2025
|
-
return true;
|
|
2026
|
-
}
|
|
2027
|
-
const signals = [
|
|
2028
|
-
'root cause',
|
|
2029
|
-
'postmortem',
|
|
2030
|
-
'crash',
|
|
2031
|
-
'incident',
|
|
2032
|
-
'outage',
|
|
2033
|
-
'optimiz',
|
|
2034
|
-
'performance',
|
|
2035
|
-
'throughput',
|
|
2036
|
-
'latency',
|
|
2037
|
-
'scalab',
|
|
2038
|
-
'architecture',
|
|
2039
|
-
'rewrite',
|
|
2040
|
-
'migration',
|
|
2041
|
-
'refactor',
|
|
2042
|
-
'reverse engineer',
|
|
2043
|
-
'security',
|
|
2044
|
-
'exploit',
|
|
2045
|
-
'injection',
|
|
2046
|
-
'vulnerability',
|
|
2047
|
-
'compliance',
|
|
2048
|
-
'multi-step',
|
|
2049
|
-
'complex',
|
|
2050
|
-
'difficult',
|
|
2051
|
-
'hard problem',
|
|
2052
|
-
'debug',
|
|
2053
|
-
'trace',
|
|
2054
|
-
'profil',
|
|
2055
|
-
'bottleneck',
|
|
2056
|
-
];
|
|
2057
|
-
return signals.some((signal) => normalized.includes(signal));
|
|
2058
|
-
}
|
|
2059
|
-
buildAlphaZeroPrompt(request, flaggedDifficult) {
|
|
2060
|
-
const playbook = [
|
|
2061
|
-
'AlphaZero RL MODE is ACTIVE. Operate as a self-play reinforcement loop.',
|
|
2062
|
-
flaggedDifficult
|
|
2063
|
-
? 'Treat this as a difficult, high-risk task and over-verify the result.'
|
|
2064
|
-
: 'Apply the reinforcement loop even if the task looks small.',
|
|
2065
|
-
'Follow this closed-loop playbook:',
|
|
2066
|
-
'- Draft two competing solution strategies and merge the strongest ideas before executing.',
|
|
2067
|
-
'- Execute with tools while logging decisions and evidence.',
|
|
2068
|
-
'- Self-critique and repair until the quality is excellent (aim ≥90/100).',
|
|
2069
|
-
'- Run full-lifecycle verification like a human reviewer: build/tests, manual sanity checks, edge cases, performance/safety/security probes, docs/UX/readiness notes.',
|
|
2070
|
-
'- Keep a verification ledger: each check with PASS/FAIL, evidence, and remaining risks. If anything fails, fix and re-verify before claiming completion.',
|
|
2071
|
-
'Finish with a concise sign-off that lists what was achieved and the proof of completion.',
|
|
2072
|
-
];
|
|
2073
|
-
return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
|
|
2074
|
-
}
|
|
2075
|
-
buildRunLogExcerpt(startIndex) {
|
|
2076
|
-
const buffer = this.terminalInput.getScrollbackBuffer();
|
|
2077
|
-
const sinceStart = buffer.slice(Math.max(0, startIndex));
|
|
2078
|
-
const excerpt = sinceStart.slice(-200); // Cap prompt size
|
|
2079
|
-
return excerpt.join('\n').trim();
|
|
2080
|
-
}
|
|
2081
|
-
buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
|
|
2082
|
-
return {
|
|
2083
|
-
id: ++this.runIdCounter,
|
|
2084
|
-
request,
|
|
2085
|
-
response,
|
|
2086
|
-
timestamp: new Date().toISOString(),
|
|
2087
|
-
alphaZero: meta.alphaZeroEngaged,
|
|
2088
|
-
difficult: meta.alphaZeroDifficult,
|
|
2089
|
-
failureType: meta.failureType,
|
|
2090
|
-
outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
buildAlphaZeroReflectionPrompt(runLog, options) {
|
|
2094
|
-
const lines = [];
|
|
2095
|
-
lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
|
|
2096
|
-
lines.push(`Timestamp: ${runLog.timestamp}`);
|
|
2097
|
-
lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
|
|
2098
|
-
lines.push('');
|
|
2099
|
-
lines.push('User request:');
|
|
2100
|
-
lines.push(runLog.request);
|
|
2101
|
-
lines.push('');
|
|
2102
|
-
lines.push('Previous run log excerpt:');
|
|
2103
|
-
lines.push(runLog.outputExcerpt || '[empty]');
|
|
2104
|
-
lines.push('');
|
|
2105
|
-
lines.push('Instructions:');
|
|
2106
|
-
lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
|
|
2107
|
-
lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
|
|
2108
|
-
lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
|
|
2109
|
-
lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
|
|
2110
|
-
if (options.autoChain) {
|
|
2111
|
-
lines.push('- Continue iterating automatically while meaningful improvements remain.');
|
|
2112
|
-
lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
|
|
2113
|
-
}
|
|
2114
|
-
return lines.join('\n');
|
|
2115
|
-
}
|
|
2116
|
-
maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
|
|
2117
|
-
if (!alphaZeroEngaged) {
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (!isErosolarRepo(this.workingDir)) {
|
|
2121
|
-
return;
|
|
2122
|
-
}
|
|
2123
|
-
if (!runLog.outputExcerpt) {
|
|
2124
|
-
return;
|
|
2125
|
-
}
|
|
2126
|
-
if (this.lastReflectedRunId === runLog.id) {
|
|
2127
|
-
return;
|
|
2128
|
-
}
|
|
2129
|
-
if (allowAutoChain) {
|
|
2130
|
-
if (!this.autoContinueEnabled) {
|
|
2131
|
-
return;
|
|
2132
|
-
}
|
|
2133
|
-
if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
|
|
2134
|
-
display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
|
|
2135
|
-
this.alphaZeroAutoImproveActive = false;
|
|
2136
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2137
|
-
return;
|
|
2138
|
-
}
|
|
2139
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2140
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2141
|
-
}
|
|
2142
|
-
this.alphaZeroAutoImproveActive = true;
|
|
2143
|
-
this.alphaZeroAutoImproveIterations++;
|
|
2144
|
-
}
|
|
2145
|
-
const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
|
|
2146
|
-
this.lastReflectedRunId = runLog.id;
|
|
2147
|
-
this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
|
|
2148
|
-
this.enqueueFollowUpAction({ type: 'request', text: prompt });
|
|
2149
|
-
display.showInfo(allowAutoChain
|
|
2150
|
-
? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
|
|
2151
|
-
: 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
|
|
2152
|
-
}
|
|
2153
|
-
shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
|
|
2154
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2155
|
-
return false;
|
|
2156
|
-
}
|
|
2157
|
-
if (!allowAutoChain) {
|
|
2158
|
-
return true;
|
|
2159
|
-
}
|
|
2160
|
-
if (!responseText) {
|
|
2161
|
-
return false;
|
|
2162
|
-
}
|
|
2163
|
-
const normalized = responseText.toLowerCase();
|
|
2164
|
-
return (normalized.includes('no_more_improvements') ||
|
|
2165
|
-
normalized.includes('no more improvements') ||
|
|
2166
|
-
normalized.includes('stop_auto_improve'));
|
|
2167
|
-
}
|
|
2168
1530
|
async handlePendingInteraction(input) {
|
|
2169
1531
|
if (!this.pendingInteraction) {
|
|
2170
1532
|
return false;
|
|
@@ -2172,7 +1534,7 @@ export class InteractiveShell {
|
|
|
2172
1534
|
switch (this.pendingInteraction.type) {
|
|
2173
1535
|
case 'model-loading':
|
|
2174
1536
|
display.showInfo('Still fetching model options. Please wait a moment.');
|
|
2175
|
-
this.
|
|
1537
|
+
this.terminalInput.render();
|
|
2176
1538
|
return true;
|
|
2177
1539
|
case 'model-provider':
|
|
2178
1540
|
await this.handleModelProviderSelection(input);
|
|
@@ -2203,213 +1565,166 @@ export class InteractiveShell {
|
|
|
2203
1565
|
const [command] = input.split(/\s+/);
|
|
2204
1566
|
if (!command) {
|
|
2205
1567
|
display.showWarning('Enter a slash command.');
|
|
2206
|
-
this.
|
|
1568
|
+
this.terminalInput.render();
|
|
2207
1569
|
return;
|
|
2208
1570
|
}
|
|
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
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
break;
|
|
2365
|
-
case '/init':
|
|
2366
|
-
this.handleInitCommand();
|
|
2367
|
-
break;
|
|
2368
|
-
case '/compact':
|
|
2369
|
-
await this.handleCompactCommand();
|
|
2370
|
-
break;
|
|
2371
|
-
default:
|
|
2372
|
-
if (!(await this.tryCustomSlashCommand(command, input))) {
|
|
2373
|
-
display.showWarning(`Unknown command "${command}".`);
|
|
2374
|
-
}
|
|
2375
|
-
break;
|
|
2376
|
-
}
|
|
2377
|
-
};
|
|
2378
|
-
const streamingUiActive = this.isStreamingUiActive();
|
|
2379
|
-
const captureOptions = streamingUiActive
|
|
2380
|
-
? { includeStreaming: false, suppressTypes: ['normal'] }
|
|
2381
|
-
: undefined;
|
|
2382
|
-
let capturedOutput = '';
|
|
2383
|
-
try {
|
|
2384
|
-
const { output: outputBuffer } = await display.captureOutput(runCommand, captureOptions);
|
|
2385
|
-
capturedOutput = outputBuffer;
|
|
2386
|
-
}
|
|
2387
|
-
catch (error) {
|
|
2388
|
-
capturedOutput = error?.capturedOutput ?? capturedOutput;
|
|
2389
|
-
if (streamingUiActive) {
|
|
2390
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2391
|
-
const { output: errorOutput } = await display.captureOutput(() => display.showError(message, error), { includeStreaming: false, suppressTypes: ['normal'] });
|
|
2392
|
-
capturedOutput = capturedOutput || errorOutput || message;
|
|
2393
|
-
}
|
|
2394
|
-
else {
|
|
2395
|
-
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
|
|
2399
|
-
this.terminalInput.setInlineCommandPanel(panelContent);
|
|
2400
|
-
this.renderPromptArea();
|
|
2401
|
-
}
|
|
2402
|
-
buildInlineCommandPanel(command, output) {
|
|
2403
|
-
const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
|
|
2404
|
-
if (!normalized) {
|
|
2405
|
-
return null;
|
|
2406
|
-
}
|
|
2407
|
-
const header = theme.ui.muted(command);
|
|
2408
|
-
const body = normalized.split('\n');
|
|
2409
|
-
if (body.length === 1) {
|
|
2410
|
-
return [`${header} ${body[0] ?? ''}`.trimEnd()];
|
|
1571
|
+
switch (command) {
|
|
1572
|
+
case '/help':
|
|
1573
|
+
case '/?':
|
|
1574
|
+
this.showHelp();
|
|
1575
|
+
break;
|
|
1576
|
+
case '/features':
|
|
1577
|
+
this.showFeaturesMenu(input);
|
|
1578
|
+
break;
|
|
1579
|
+
case '/learn':
|
|
1580
|
+
this.showLearningStatus(input);
|
|
1581
|
+
break;
|
|
1582
|
+
case '/improve':
|
|
1583
|
+
void this.handleImprovementCommand(input);
|
|
1584
|
+
break;
|
|
1585
|
+
case '/model':
|
|
1586
|
+
this.showModelMenu();
|
|
1587
|
+
break;
|
|
1588
|
+
case '/exit':
|
|
1589
|
+
case '/quit':
|
|
1590
|
+
case '/q':
|
|
1591
|
+
this.shutdown();
|
|
1592
|
+
break;
|
|
1593
|
+
case '/secrets':
|
|
1594
|
+
this.showSecretsMenu();
|
|
1595
|
+
break;
|
|
1596
|
+
case '/tools':
|
|
1597
|
+
this.showToolsMenu();
|
|
1598
|
+
break;
|
|
1599
|
+
case '/mcp':
|
|
1600
|
+
await this.showMcpStatus();
|
|
1601
|
+
break;
|
|
1602
|
+
case '/doctor':
|
|
1603
|
+
this.runDoctor();
|
|
1604
|
+
break;
|
|
1605
|
+
case '/checks':
|
|
1606
|
+
await this.runRepoChecksCommand();
|
|
1607
|
+
break;
|
|
1608
|
+
case '/context':
|
|
1609
|
+
await this.refreshWorkspaceContextCommand(input);
|
|
1610
|
+
break;
|
|
1611
|
+
case '/agents':
|
|
1612
|
+
this.showAgentsMenu();
|
|
1613
|
+
break;
|
|
1614
|
+
case '/sessions':
|
|
1615
|
+
await this.handleSessionCommand(input);
|
|
1616
|
+
break;
|
|
1617
|
+
case '/skills':
|
|
1618
|
+
await this.handleSkillsCommand(input);
|
|
1619
|
+
break;
|
|
1620
|
+
case '/thinking':
|
|
1621
|
+
this.handleThinkingCommand(input);
|
|
1622
|
+
break;
|
|
1623
|
+
case '/autocontinue':
|
|
1624
|
+
this.handleAutoContinueCommand(input);
|
|
1625
|
+
break;
|
|
1626
|
+
case '/shortcuts':
|
|
1627
|
+
case '/keys':
|
|
1628
|
+
this.handleShortcutsCommand();
|
|
1629
|
+
break;
|
|
1630
|
+
case '/changes':
|
|
1631
|
+
case '/summary':
|
|
1632
|
+
this.showFileChangeSummary();
|
|
1633
|
+
break;
|
|
1634
|
+
case '/metrics':
|
|
1635
|
+
case '/stats':
|
|
1636
|
+
case '/perf':
|
|
1637
|
+
this.showAlphaZeroMetrics();
|
|
1638
|
+
break;
|
|
1639
|
+
case '/suggestions':
|
|
1640
|
+
case '/improve':
|
|
1641
|
+
this.showImprovementSuggestions();
|
|
1642
|
+
break;
|
|
1643
|
+
case '/plugins':
|
|
1644
|
+
this.showPluginStatus();
|
|
1645
|
+
break;
|
|
1646
|
+
case '/evolve':
|
|
1647
|
+
void this.handleEvolveCommand(input);
|
|
1648
|
+
break;
|
|
1649
|
+
case '/modular':
|
|
1650
|
+
case '/a0':
|
|
1651
|
+
void this.handleModularCommand(input);
|
|
1652
|
+
break;
|
|
1653
|
+
case '/offsec':
|
|
1654
|
+
void this.handleOffsecCommand(input);
|
|
1655
|
+
break;
|
|
1656
|
+
case '/test':
|
|
1657
|
+
case '/tests':
|
|
1658
|
+
void this.handleTestCommand(input);
|
|
1659
|
+
break;
|
|
1660
|
+
case '/provider':
|
|
1661
|
+
await this.handleProviderCommand(input);
|
|
1662
|
+
break;
|
|
1663
|
+
case '/providers':
|
|
1664
|
+
this.showConfiguredProviders();
|
|
1665
|
+
break;
|
|
1666
|
+
case '/local':
|
|
1667
|
+
await this.handleLocalCommand(input);
|
|
1668
|
+
break;
|
|
1669
|
+
case '/discover':
|
|
1670
|
+
await this.discoverModelsCommand();
|
|
1671
|
+
break;
|
|
1672
|
+
// Claude Code style commands
|
|
1673
|
+
case '/rewind':
|
|
1674
|
+
await this.handleRewindCommand(input);
|
|
1675
|
+
break;
|
|
1676
|
+
case '/memory':
|
|
1677
|
+
this.handleMemoryCommand(input);
|
|
1678
|
+
break;
|
|
1679
|
+
case '/vim':
|
|
1680
|
+
this.handleVimCommand();
|
|
1681
|
+
break;
|
|
1682
|
+
case '/output-style':
|
|
1683
|
+
this.handleOutputStyleCommand(input);
|
|
1684
|
+
break;
|
|
1685
|
+
case '/cost':
|
|
1686
|
+
this.handleCostCommand();
|
|
1687
|
+
break;
|
|
1688
|
+
case '/usage':
|
|
1689
|
+
this.handleUsageCommand();
|
|
1690
|
+
break;
|
|
1691
|
+
case '/clear':
|
|
1692
|
+
this.handleClearCommand();
|
|
1693
|
+
break;
|
|
1694
|
+
case '/resume':
|
|
1695
|
+
await this.handleResumeCommand(input);
|
|
1696
|
+
break;
|
|
1697
|
+
case '/export':
|
|
1698
|
+
this.handleExportCommand(input);
|
|
1699
|
+
break;
|
|
1700
|
+
case '/review':
|
|
1701
|
+
await this.handleReviewCommand();
|
|
1702
|
+
break;
|
|
1703
|
+
case '/security-review':
|
|
1704
|
+
await this.handleSecurityReviewCommand();
|
|
1705
|
+
break;
|
|
1706
|
+
case '/bug':
|
|
1707
|
+
this.handleBugCommand();
|
|
1708
|
+
break;
|
|
1709
|
+
case '/terminal-setup':
|
|
1710
|
+
this.handleTerminalSetupCommand();
|
|
1711
|
+
break;
|
|
1712
|
+
case '/permissions':
|
|
1713
|
+
this.handlePermissionsCommand();
|
|
1714
|
+
break;
|
|
1715
|
+
case '/init':
|
|
1716
|
+
this.handleInitCommand();
|
|
1717
|
+
break;
|
|
1718
|
+
case '/compact':
|
|
1719
|
+
await this.handleCompactCommand();
|
|
1720
|
+
break;
|
|
1721
|
+
default:
|
|
1722
|
+
if (!(await this.tryCustomSlashCommand(command, input))) {
|
|
1723
|
+
display.showWarning(`Unknown command "${command}".`);
|
|
1724
|
+
}
|
|
1725
|
+
break;
|
|
2411
1726
|
}
|
|
2412
|
-
|
|
1727
|
+
this.terminalInput.render();
|
|
2413
1728
|
}
|
|
2414
1729
|
async tryCustomSlashCommand(command, fullInput) {
|
|
2415
1730
|
const custom = this.customCommandMap.get(command);
|
|
@@ -2446,13 +1761,11 @@ export class InteractiveShell {
|
|
|
2446
1761
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2447
1762
|
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2448
1763
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2449
|
-
` ${theme.info('Option+A')} ${theme.ui.muted('Toggle AlphaZero RL mode')}`,
|
|
2450
1764
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
2451
1765
|
` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
|
|
2452
1766
|
'',
|
|
2453
1767
|
theme.bold(' Navigation'),
|
|
2454
1768
|
` ${theme.info('PageUp/PageDown')} ${theme.ui.muted('Scroll through output')}`,
|
|
2455
|
-
` ${theme.info('Escape')} ${theme.ui.muted('Stop streaming')}`,
|
|
2456
1769
|
` ${theme.info('Ctrl+C')} ${theme.ui.muted('Clear input or interrupt')}`,
|
|
2457
1770
|
` ${theme.info('Up/Down')} ${theme.ui.muted('Navigate command history')}`,
|
|
2458
1771
|
'',
|
|
@@ -2550,66 +1863,6 @@ export class InteractiveShell {
|
|
|
2550
1863
|
display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
|
|
2551
1864
|
}
|
|
2552
1865
|
}
|
|
2553
|
-
showContextSummaryLog(input) {
|
|
2554
|
-
const agent = this.agent;
|
|
2555
|
-
if (!agent) {
|
|
2556
|
-
display.showWarning('No active agent session. Start one to inspect context summaries.');
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
const contextManager = agent.getContextManager();
|
|
2560
|
-
if (!contextManager?.getSummaryLog) {
|
|
2561
|
-
display.showWarning('Context summary logging is unavailable in this session.');
|
|
2562
|
-
return;
|
|
2563
|
-
}
|
|
2564
|
-
const tokens = input.trim().split(/\s+/).slice(1);
|
|
2565
|
-
let limit = 5;
|
|
2566
|
-
for (const token of tokens) {
|
|
2567
|
-
if (!token)
|
|
2568
|
-
continue;
|
|
2569
|
-
const normalized = token.toLowerCase();
|
|
2570
|
-
if (/^\d+$/.test(token)) {
|
|
2571
|
-
limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
|
|
2572
|
-
}
|
|
2573
|
-
else if (normalized.startsWith('limit=')) {
|
|
2574
|
-
const value = parseInt(normalized.split('=')[1] ?? '', 10);
|
|
2575
|
-
if (!Number.isNaN(value)) {
|
|
2576
|
-
limit = Math.min(Math.max(value, 1), 20);
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
const entries = contextManager.getSummaryLog(limit);
|
|
2581
|
-
if (!entries.length) {
|
|
2582
|
-
display.showInfo('No context summaries captured yet.');
|
|
2583
|
-
return;
|
|
2584
|
-
}
|
|
2585
|
-
const lines = [];
|
|
2586
|
-
lines.push(`${theme.primary('Context summaries')} ${theme.ui.muted(`(latest ${entries.length})`)}`);
|
|
2587
|
-
for (const entry of entries) {
|
|
2588
|
-
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
2589
|
-
const headerParts = [
|
|
2590
|
-
theme.ui.muted(timestamp),
|
|
2591
|
-
theme.info(entry.method),
|
|
2592
|
-
`removed ${theme.error(String(entry.removed))}`,
|
|
2593
|
-
`kept ${theme.success(String(entry.preserved))}`,
|
|
2594
|
-
];
|
|
2595
|
-
if (entry.reason) {
|
|
2596
|
-
headerParts.push(theme.ui.muted(entry.reason));
|
|
2597
|
-
}
|
|
2598
|
-
if (entry.model) {
|
|
2599
|
-
headerParts.push(theme.ui.muted(`model=${entry.model}`));
|
|
2600
|
-
}
|
|
2601
|
-
lines.push(headerParts.join(' · '));
|
|
2602
|
-
if (entry.summary) {
|
|
2603
|
-
const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}…` : entry.summary;
|
|
2604
|
-
lines.push(trimmed);
|
|
2605
|
-
}
|
|
2606
|
-
else {
|
|
2607
|
-
lines.push(theme.ui.muted('(no summary text — simple prune)'));
|
|
2608
|
-
}
|
|
2609
|
-
lines.push(''); // spacer
|
|
2610
|
-
}
|
|
2611
|
-
display.showSystemMessage(lines.join('\n'));
|
|
2612
|
-
}
|
|
2613
1866
|
parseContextOverrideTokens(input) {
|
|
2614
1867
|
const overrides = {};
|
|
2615
1868
|
let hasOverride = false;
|
|
@@ -2769,11 +2022,11 @@ export class InteractiveShell {
|
|
|
2769
2022
|
handleThinkingCommand(input) {
|
|
2770
2023
|
const value = input.slice('/thinking'.length).trim().toLowerCase();
|
|
2771
2024
|
if (!value) {
|
|
2772
|
-
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [
|
|
2025
|
+
display.showInfo(`Thinking mode is currently ${theme.info(this.thinkingMode)}. Usage: /thinking [balanced|extended]`);
|
|
2773
2026
|
return;
|
|
2774
2027
|
}
|
|
2775
|
-
if (value !== '
|
|
2776
|
-
display.showWarning('Usage: /thinking [
|
|
2028
|
+
if (value !== 'balanced' && value !== 'extended') {
|
|
2029
|
+
display.showWarning('Usage: /thinking [balanced|extended]');
|
|
2777
2030
|
return;
|
|
2778
2031
|
}
|
|
2779
2032
|
if (this.isProcessing) {
|
|
@@ -2786,30 +2039,11 @@ export class InteractiveShell {
|
|
|
2786
2039
|
this.resetChatBoxAfterModelSwap();
|
|
2787
2040
|
}
|
|
2788
2041
|
const descriptions = {
|
|
2789
|
-
concise: 'Hides internal reasoning and responds directly.',
|
|
2790
2042
|
balanced: 'Shows short thoughts only when helpful.',
|
|
2791
2043
|
extended: 'Always emits a <thinking> block before the final response.',
|
|
2792
2044
|
};
|
|
2793
2045
|
display.showInfo(`Thinking mode set to ${theme.info(value)} – ${descriptions[this.thinkingMode]}`);
|
|
2794
2046
|
}
|
|
2795
|
-
handleAlphaZeroCommand(input) {
|
|
2796
|
-
const value = input.slice('/alphazero'.length).trim().toLowerCase();
|
|
2797
|
-
if (!value || value === 'status') {
|
|
2798
|
-
const status = this.alphaZeroModeEnabled ? theme.success('on') : theme.ui.muted('off');
|
|
2799
|
-
const verification = this.verificationEnabled ? 'verification locked on' : 'verification optional';
|
|
2800
|
-
display.showInfo(`AlphaZero RL mode is ${status}. When enabled, difficult prompts use duel/self-critique and human-style verification (${verification}).`);
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
if (['on', 'enable', 'enabled'].includes(value)) {
|
|
2804
|
-
this.setAlphaZeroMode(true, 'command');
|
|
2805
|
-
return;
|
|
2806
|
-
}
|
|
2807
|
-
if (['off', 'disable', 'disabled'].includes(value)) {
|
|
2808
|
-
this.setAlphaZeroMode(false, 'command');
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
display.showWarning('Usage: /alphazero [on|off|status]');
|
|
2812
|
-
}
|
|
2813
2047
|
handleShortcutsCommand() {
|
|
2814
2048
|
// Display keyboard shortcuts help (Claude Code style)
|
|
2815
2049
|
display.showSystemMessage(formatShortcutsHelp());
|
|
@@ -2897,7 +2131,6 @@ export class InteractiveShell {
|
|
|
2897
2131
|
const updated = toggleFeatureFlag(matchedKey, newValue);
|
|
2898
2132
|
const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
|
|
2899
2133
|
display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
|
|
2900
|
-
this.refreshFeatureStatusDisplay();
|
|
2901
2134
|
display.showInfo('Changes will take effect on next launch or after /features refresh.');
|
|
2902
2135
|
return;
|
|
2903
2136
|
}
|
|
@@ -2910,7 +2143,6 @@ export class InteractiveShell {
|
|
|
2910
2143
|
}
|
|
2911
2144
|
saveFeatureFlags(updated);
|
|
2912
2145
|
display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
|
|
2913
|
-
this.refreshFeatureStatusDisplay();
|
|
2914
2146
|
return;
|
|
2915
2147
|
}
|
|
2916
2148
|
else {
|
|
@@ -4083,7 +3315,9 @@ export class InteractiveShell {
|
|
|
4083
3315
|
}
|
|
4084
3316
|
display.showInfo(`Deleted session "${summary.title}".`);
|
|
4085
3317
|
if (this.activeSessionId === summary.id) {
|
|
4086
|
-
this.
|
|
3318
|
+
this.activeSessionId = null;
|
|
3319
|
+
this.activeSessionTitle = null;
|
|
3320
|
+
saveSessionPreferences({ lastSessionId: null });
|
|
4087
3321
|
}
|
|
4088
3322
|
}
|
|
4089
3323
|
newSessionCommand(title) {
|
|
@@ -4103,7 +3337,6 @@ export class InteractiveShell {
|
|
|
4103
3337
|
clearAutosaveSnapshot(this.profile);
|
|
4104
3338
|
display.showInfo('Started a new empty session.');
|
|
4105
3339
|
this.refreshContextGauge();
|
|
4106
|
-
this.refreshFeatureStatusDisplay();
|
|
4107
3340
|
}
|
|
4108
3341
|
toggleAutosaveCommand(value) {
|
|
4109
3342
|
if (!value) {
|
|
@@ -4158,6 +3391,7 @@ export class InteractiveShell {
|
|
|
4158
3391
|
lines.push(' /rewind code Rewind code only (keep conversation)');
|
|
4159
3392
|
lines.push(' /rewind conv Rewind conversation only (keep code)');
|
|
4160
3393
|
lines.push('');
|
|
3394
|
+
lines.push(theme.ui.muted('Tip: Press Esc+Esc for quick access to rewind menu'));
|
|
4161
3395
|
display.showSystemMessage(lines.join('\n'));
|
|
4162
3396
|
}
|
|
4163
3397
|
handleMemoryCommand(input) {
|
|
@@ -4176,6 +3410,7 @@ export class InteractiveShell {
|
|
|
4176
3410
|
lines.push(' - Use # prefix to quickly add notes to project memory');
|
|
4177
3411
|
lines.push(' - Import other files with @./relative/path syntax');
|
|
4178
3412
|
lines.push('');
|
|
3413
|
+
lines.push(theme.ui.muted('Tip: Create EROSOLAR.md with project coding standards for better results'));
|
|
4179
3414
|
display.showSystemMessage(lines.join('\n'));
|
|
4180
3415
|
}
|
|
4181
3416
|
handleVimCommand() {
|
|
@@ -4256,20 +3491,6 @@ export class InteractiveShell {
|
|
|
4256
3491
|
}
|
|
4257
3492
|
display.showSystemMessage(lines.join('\n'));
|
|
4258
3493
|
}
|
|
4259
|
-
async handleUpdateCommand() {
|
|
4260
|
-
display.showInfo('Checking for updates...');
|
|
4261
|
-
const lines = [];
|
|
4262
|
-
lines.push(theme.bold('Update Check'));
|
|
4263
|
-
lines.push('');
|
|
4264
|
-
lines.push(`Current version: ${this.version ?? 'unknown'}`);
|
|
4265
|
-
lines.push('');
|
|
4266
|
-
lines.push(theme.secondary('To update:'));
|
|
4267
|
-
lines.push(' npm install -g erosolar-cli@latest');
|
|
4268
|
-
lines.push(' or: npm update erosolar-cli');
|
|
4269
|
-
lines.push('');
|
|
4270
|
-
lines.push(theme.ui.muted('Auto-updates will be checked periodically.'));
|
|
4271
|
-
display.showSystemMessage(lines.join('\n'));
|
|
4272
|
-
}
|
|
4273
3494
|
handleClearCommand() {
|
|
4274
3495
|
if (this.agent) {
|
|
4275
3496
|
this.agent.clearHistory();
|
|
@@ -4278,7 +3499,7 @@ export class InteractiveShell {
|
|
|
4278
3499
|
display.clear();
|
|
4279
3500
|
clearAutosaveSnapshot(this.profile);
|
|
4280
3501
|
display.showInfo('Conversation cleared. Starting fresh.');
|
|
4281
|
-
this.
|
|
3502
|
+
this.terminalInput.render();
|
|
4282
3503
|
}
|
|
4283
3504
|
async handleResumeCommand(input) {
|
|
4284
3505
|
const tokens = input.split(/\s+/).slice(1);
|
|
@@ -4419,7 +3640,6 @@ export class InteractiveShell {
|
|
|
4419
3640
|
if (remember) {
|
|
4420
3641
|
saveSessionPreferences({ lastSessionId: summary?.id ?? null });
|
|
4421
3642
|
}
|
|
4422
|
-
this.refreshFeatureStatusDisplay();
|
|
4423
3643
|
}
|
|
4424
3644
|
resolveSessionBySelector(selector) {
|
|
4425
3645
|
const sessions = listSessions(this.profile);
|
|
@@ -4588,7 +3808,7 @@ export class InteractiveShell {
|
|
|
4588
3808
|
if (!providerOptions.length) {
|
|
4589
3809
|
display.showWarning('No providers are available.');
|
|
4590
3810
|
this.pendingInteraction = null;
|
|
4591
|
-
this.
|
|
3811
|
+
this.terminalInput.render();
|
|
4592
3812
|
return;
|
|
4593
3813
|
}
|
|
4594
3814
|
const lines = [
|
|
@@ -4609,7 +3829,7 @@ export class InteractiveShell {
|
|
|
4609
3829
|
catch (error) {
|
|
4610
3830
|
display.showError('Failed to load model list. Try again in a moment.', error);
|
|
4611
3831
|
this.pendingInteraction = null;
|
|
4612
|
-
this.
|
|
3832
|
+
this.terminalInput.render();
|
|
4613
3833
|
}
|
|
4614
3834
|
}
|
|
4615
3835
|
buildProviderOptions() {
|
|
@@ -4793,7 +4013,7 @@ export class InteractiveShell {
|
|
|
4793
4013
|
}
|
|
4794
4014
|
renderToolMenu(interaction) {
|
|
4795
4015
|
const lines = [
|
|
4796
|
-
theme.bold('Select which tools are enabled (changes apply on next launch)
|
|
4016
|
+
theme.bold('Select which tools are enabled (changes apply on next launch; default package is locked on).'),
|
|
4797
4017
|
...interaction.options.map((option, index) => this.formatToolOptionLine(option, index, interaction.selection)),
|
|
4798
4018
|
'',
|
|
4799
4019
|
'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
|
|
@@ -4802,13 +4022,23 @@ export class InteractiveShell {
|
|
|
4802
4022
|
}
|
|
4803
4023
|
formatToolOptionLine(option, index, selection) {
|
|
4804
4024
|
const enabled = selection.has(option.id);
|
|
4805
|
-
const checkbox =
|
|
4025
|
+
const checkbox = option.locked
|
|
4026
|
+
? theme.primary('[✓]')
|
|
4027
|
+
: enabled
|
|
4028
|
+
? theme.primary('[x]')
|
|
4029
|
+
: theme.ui.muted('[ ]');
|
|
4806
4030
|
const details = [option.description];
|
|
4807
4031
|
if (option.requiresSecret) {
|
|
4808
4032
|
const hasSecret = Boolean(getSecretValue(option.requiresSecret));
|
|
4809
4033
|
const status = hasSecret ? theme.success('API key set') : theme.warning('API key missing');
|
|
4810
4034
|
details.push(status);
|
|
4811
4035
|
}
|
|
4036
|
+
if (option.locked) {
|
|
4037
|
+
details.push(theme.ui.muted('Locked default package'));
|
|
4038
|
+
}
|
|
4039
|
+
if (option.restartRequired) {
|
|
4040
|
+
details.push(theme.ui.muted('Restart required'));
|
|
4041
|
+
}
|
|
4812
4042
|
const numberLabel = this.colorizeDropdownLine(`${index + 1}.`, index);
|
|
4813
4043
|
const optionLabel = this.colorizeDropdownLine(option.label, index);
|
|
4814
4044
|
const detailLine = this.colorizeDropdownLine(` ${details.join(' • ')}`, index);
|
|
@@ -5154,29 +4384,29 @@ export class InteractiveShell {
|
|
|
5154
4384
|
const trimmed = input.trim();
|
|
5155
4385
|
if (!trimmed) {
|
|
5156
4386
|
display.showWarning('Enter a number or type cancel.');
|
|
5157
|
-
this.
|
|
4387
|
+
this.terminalInput.render();
|
|
5158
4388
|
return;
|
|
5159
4389
|
}
|
|
5160
4390
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5161
4391
|
this.pendingInteraction = null;
|
|
5162
4392
|
display.showInfo('Model selection cancelled.');
|
|
5163
|
-
this.
|
|
4393
|
+
this.terminalInput.render();
|
|
5164
4394
|
return;
|
|
5165
4395
|
}
|
|
5166
4396
|
const choice = Number.parseInt(trimmed, 10);
|
|
5167
4397
|
if (!Number.isFinite(choice)) {
|
|
5168
4398
|
display.showWarning('Please enter a valid number.');
|
|
5169
|
-
this.
|
|
4399
|
+
this.terminalInput.render();
|
|
5170
4400
|
return;
|
|
5171
4401
|
}
|
|
5172
4402
|
const option = pending.options[choice - 1];
|
|
5173
4403
|
if (!option) {
|
|
5174
4404
|
display.showWarning('That option is not available.');
|
|
5175
|
-
this.
|
|
4405
|
+
this.terminalInput.render();
|
|
5176
4406
|
return;
|
|
5177
4407
|
}
|
|
5178
4408
|
this.showProviderModels(option);
|
|
5179
|
-
this.
|
|
4409
|
+
this.terminalInput.render();
|
|
5180
4410
|
}
|
|
5181
4411
|
async handleModelSelection(input) {
|
|
5182
4412
|
const pending = this.pendingInteraction;
|
|
@@ -5186,35 +4416,35 @@ export class InteractiveShell {
|
|
|
5186
4416
|
const trimmed = input.trim();
|
|
5187
4417
|
if (!trimmed) {
|
|
5188
4418
|
display.showWarning('Enter a number, type "back", or type "cancel".');
|
|
5189
|
-
this.
|
|
4419
|
+
this.terminalInput.render();
|
|
5190
4420
|
return;
|
|
5191
4421
|
}
|
|
5192
4422
|
if (trimmed.toLowerCase() === 'back') {
|
|
5193
4423
|
this.showModelMenu();
|
|
5194
|
-
this.
|
|
4424
|
+
this.terminalInput.render();
|
|
5195
4425
|
return;
|
|
5196
4426
|
}
|
|
5197
4427
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5198
4428
|
this.pendingInteraction = null;
|
|
5199
4429
|
display.showInfo('Model selection cancelled.');
|
|
5200
|
-
this.
|
|
4430
|
+
this.terminalInput.render();
|
|
5201
4431
|
return;
|
|
5202
4432
|
}
|
|
5203
4433
|
const choice = Number.parseInt(trimmed, 10);
|
|
5204
4434
|
if (!Number.isFinite(choice)) {
|
|
5205
4435
|
display.showWarning('Please enter a valid number.');
|
|
5206
|
-
this.
|
|
4436
|
+
this.terminalInput.render();
|
|
5207
4437
|
return;
|
|
5208
4438
|
}
|
|
5209
4439
|
const preset = pending.options[choice - 1];
|
|
5210
4440
|
if (!preset) {
|
|
5211
4441
|
display.showWarning('That option is not available.');
|
|
5212
|
-
this.
|
|
4442
|
+
this.terminalInput.render();
|
|
5213
4443
|
return;
|
|
5214
4444
|
}
|
|
5215
4445
|
this.pendingInteraction = null;
|
|
5216
4446
|
await this.applyModelPreset(preset);
|
|
5217
|
-
this.
|
|
4447
|
+
this.terminalInput.render();
|
|
5218
4448
|
}
|
|
5219
4449
|
async applyModelPreset(preset) {
|
|
5220
4450
|
try {
|
|
@@ -5247,30 +4477,30 @@ export class InteractiveShell {
|
|
|
5247
4477
|
const trimmed = input.trim();
|
|
5248
4478
|
if (!trimmed) {
|
|
5249
4479
|
display.showWarning('Enter a number or type cancel.');
|
|
5250
|
-
this.
|
|
4480
|
+
this.terminalInput.render();
|
|
5251
4481
|
return;
|
|
5252
4482
|
}
|
|
5253
4483
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5254
4484
|
this.pendingInteraction = null;
|
|
5255
4485
|
display.showInfo('Secret management cancelled.');
|
|
5256
|
-
this.
|
|
4486
|
+
this.terminalInput.render();
|
|
5257
4487
|
return;
|
|
5258
4488
|
}
|
|
5259
4489
|
const choice = Number.parseInt(trimmed, 10);
|
|
5260
4490
|
if (!Number.isFinite(choice)) {
|
|
5261
4491
|
display.showWarning('Please enter a valid number.');
|
|
5262
|
-
this.
|
|
4492
|
+
this.terminalInput.render();
|
|
5263
4493
|
return;
|
|
5264
4494
|
}
|
|
5265
4495
|
const secret = pending.options[choice - 1];
|
|
5266
4496
|
if (!secret) {
|
|
5267
4497
|
display.showWarning('That option is not available.');
|
|
5268
|
-
this.
|
|
4498
|
+
this.terminalInput.render();
|
|
5269
4499
|
return;
|
|
5270
4500
|
}
|
|
5271
4501
|
display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
|
|
5272
4502
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
5273
|
-
this.
|
|
4503
|
+
this.terminalInput.render();
|
|
5274
4504
|
}
|
|
5275
4505
|
async handleSecretInput(input) {
|
|
5276
4506
|
const pending = this.pendingInteraction;
|
|
@@ -5280,16 +4510,14 @@ export class InteractiveShell {
|
|
|
5280
4510
|
const trimmed = input.trim();
|
|
5281
4511
|
if (!trimmed) {
|
|
5282
4512
|
display.showWarning('Enter a value or type cancel.');
|
|
5283
|
-
this.
|
|
4513
|
+
this.terminalInput.render();
|
|
5284
4514
|
return;
|
|
5285
4515
|
}
|
|
5286
4516
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5287
4517
|
this.pendingInteraction = null;
|
|
5288
4518
|
this.pendingSecretRetry = null;
|
|
5289
|
-
this.apiKeyGateActive = false;
|
|
5290
4519
|
display.showInfo('Secret unchanged.');
|
|
5291
|
-
this.
|
|
5292
|
-
this.scheduleQueueProcessing();
|
|
4520
|
+
this.terminalInput.render();
|
|
5293
4521
|
return;
|
|
5294
4522
|
}
|
|
5295
4523
|
try {
|
|
@@ -5298,7 +4526,6 @@ export class InteractiveShell {
|
|
|
5298
4526
|
this.pendingInteraction = null;
|
|
5299
4527
|
const deferred = this.pendingSecretRetry;
|
|
5300
4528
|
this.pendingSecretRetry = null;
|
|
5301
|
-
this.apiKeyGateActive = false;
|
|
5302
4529
|
if (pending.secret.providers.includes(this.sessionState.provider)) {
|
|
5303
4530
|
if (this.rebuildAgent()) {
|
|
5304
4531
|
this.resetChatBoxAfterModelSwap();
|
|
@@ -5313,14 +4540,12 @@ export class InteractiveShell {
|
|
|
5313
4540
|
display.showError(message);
|
|
5314
4541
|
this.pendingInteraction = null;
|
|
5315
4542
|
this.pendingSecretRetry = null;
|
|
5316
|
-
this.apiKeyGateActive = false;
|
|
5317
4543
|
}
|
|
5318
|
-
this.
|
|
5319
|
-
this.scheduleQueueProcessing();
|
|
4544
|
+
this.terminalInput.render();
|
|
5320
4545
|
}
|
|
5321
|
-
async processRequest(
|
|
4546
|
+
async processRequest(request) {
|
|
5322
4547
|
if (this.isProcessing) {
|
|
5323
|
-
this.enqueueFollowUpAction({ type: 'request', text:
|
|
4548
|
+
this.enqueueFollowUpAction({ type: 'request', text: request });
|
|
5324
4549
|
return;
|
|
5325
4550
|
}
|
|
5326
4551
|
if (!this.agent && !this.rebuildAgent()) {
|
|
@@ -5331,57 +4556,29 @@ export class InteractiveShell {
|
|
|
5331
4556
|
if (!agent) {
|
|
5332
4557
|
return;
|
|
5333
4558
|
}
|
|
5334
|
-
this.
|
|
5335
|
-
this.resetAssistantStreamTracking();
|
|
5336
|
-
const alphaZeroEngaged = this.alphaZeroModeEnabled;
|
|
5337
|
-
const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
|
|
5338
|
-
const requestForAgent = alphaZeroEngaged
|
|
5339
|
-
? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
|
|
5340
|
-
: userRequest;
|
|
5341
|
-
const skipReflectionForThisRun = this.skipNextAutoReflection;
|
|
5342
|
-
this.skipNextAutoReflection = false;
|
|
5343
|
-
const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
|
|
5344
|
-
const alphaZeroStatusId = 'alpha-zero';
|
|
5345
|
-
let alphaZeroStatusApplied = false;
|
|
5346
|
-
let alphaZeroTaskStarted = false;
|
|
5347
|
-
let alphaZeroTaskCompleted = false;
|
|
5348
|
-
if (alphaZeroEngaged) {
|
|
5349
|
-
const detail = alphaZeroDifficult ? 'Difficult request detected' : 'Reinforcement loop enabled';
|
|
5350
|
-
this.statusTracker.pushOverride(alphaZeroStatusId, 'AlphaZero RL active', {
|
|
5351
|
-
detail: `${detail} · full verification`,
|
|
5352
|
-
tone: 'info',
|
|
5353
|
-
});
|
|
5354
|
-
alphaZeroStatusApplied = true;
|
|
5355
|
-
display.showInfo(`AlphaZero RL mode engaged${alphaZeroDifficult ? ' for a difficult request' : ''}. Duel + self-critique with verification enabled.`);
|
|
5356
|
-
this.alphaZeroMetrics.startAlphaZeroTask(userRequest);
|
|
5357
|
-
alphaZeroTaskStarted = true;
|
|
5358
|
-
}
|
|
5359
|
-
this.logUserPrompt(userRequest);
|
|
4559
|
+
this.logUserPrompt(request);
|
|
5360
4560
|
this.isProcessing = true;
|
|
5361
4561
|
this.uiUpdates.setMode('processing');
|
|
5362
4562
|
this.terminalInput.setStreaming(true);
|
|
5363
4563
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5364
|
-
this.
|
|
4564
|
+
this.terminalInput.forceRender();
|
|
5365
4565
|
const requestStartTime = Date.now(); // Alpha Zero 2 timing
|
|
5366
|
-
this.lastRequestStartedAt = requestStartTime;
|
|
5367
|
-
this.lastToolSummaryRenderedAt = null;
|
|
5368
4566
|
// Clear previous parallel agents and start fresh for new request
|
|
5369
4567
|
const parallelManager = getParallelAgentManager();
|
|
5370
4568
|
parallelManager.clear();
|
|
5371
4569
|
parallelManager.startBatch();
|
|
5372
4570
|
// AlphaZero: Track task for learning
|
|
5373
|
-
this.lastUserQuery =
|
|
5374
|
-
this.currentTaskType = classifyTaskType(
|
|
4571
|
+
this.lastUserQuery = request;
|
|
4572
|
+
this.currentTaskType = classifyTaskType(request);
|
|
5375
4573
|
this.currentToolCalls = [];
|
|
5376
4574
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5377
4575
|
this.setProcessingStatus();
|
|
5378
4576
|
let responseText = '';
|
|
5379
|
-
let detectedFailure = null;
|
|
5380
|
-
let hadUnhandledError = false;
|
|
5381
4577
|
try {
|
|
5382
4578
|
// Start streaming - no header needed, the input area already provides context
|
|
5383
|
-
this.startStreamingHeartbeat(
|
|
5384
|
-
responseText = await agent.send(
|
|
4579
|
+
this.startStreamingHeartbeat('Streaming response');
|
|
4580
|
+
responseText = await agent.send(request, true);
|
|
4581
|
+
this.finishStreamingFormatter();
|
|
5385
4582
|
await this.awaitPendingCleanup();
|
|
5386
4583
|
this.captureHistorySnapshot();
|
|
5387
4584
|
this.autosaveIfEnabled();
|
|
@@ -5400,18 +4597,14 @@ export class InteractiveShell {
|
|
|
5400
4597
|
duration: 0,
|
|
5401
4598
|
}));
|
|
5402
4599
|
// AlphaZero: Check for failure in response
|
|
5403
|
-
|
|
4600
|
+
const failure = detectFailure(responseText, {
|
|
5404
4601
|
toolCalls: this.currentToolCalls,
|
|
5405
|
-
userMessage:
|
|
4602
|
+
userMessage: request,
|
|
5406
4603
|
});
|
|
5407
|
-
if (
|
|
5408
|
-
this.
|
|
5409
|
-
alphaZeroTaskCompleted = true;
|
|
5410
|
-
}
|
|
5411
|
-
if (detectedFailure) {
|
|
5412
|
-
this.lastFailure = detectedFailure;
|
|
4604
|
+
if (failure) {
|
|
4605
|
+
this.lastFailure = failure;
|
|
5413
4606
|
// Check if we have a recovery strategy
|
|
5414
|
-
const strategy = findRecoveryStrategy(
|
|
4607
|
+
const strategy = findRecoveryStrategy(failure);
|
|
5415
4608
|
if (strategy) {
|
|
5416
4609
|
display.showSystemMessage(`🔄 Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
|
|
5417
4610
|
}
|
|
@@ -5434,55 +4627,28 @@ export class InteractiveShell {
|
|
|
5434
4627
|
}
|
|
5435
4628
|
}
|
|
5436
4629
|
catch (error) {
|
|
5437
|
-
const handled = this.handleProviderError(error, () => this.processRequest(
|
|
5438
|
-
hadUnhandledError = !handled;
|
|
5439
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5440
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5441
|
-
alphaZeroTaskCompleted = true;
|
|
5442
|
-
}
|
|
4630
|
+
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
5443
4631
|
if (!handled) {
|
|
5444
4632
|
// Pass full error object for enhanced formatting with stack trace
|
|
5445
4633
|
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
5446
4634
|
}
|
|
5447
4635
|
}
|
|
5448
4636
|
finally {
|
|
5449
|
-
|
|
5450
|
-
alphaZeroEngaged,
|
|
5451
|
-
alphaZeroDifficult,
|
|
5452
|
-
failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
|
|
5453
|
-
});
|
|
5454
|
-
this.lastRunLog = runLogEntry;
|
|
5455
|
-
const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
|
|
5456
|
-
let shouldStopAuto = false;
|
|
5457
|
-
if (this.alphaZeroAutoImproveActive) {
|
|
5458
|
-
shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
|
|
5459
|
-
if (shouldStopAuto) {
|
|
5460
|
-
this.alphaZeroAutoImproveActive = false;
|
|
5461
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
5462
|
-
}
|
|
5463
|
-
}
|
|
5464
|
-
if (!skipReflectionForThisRun && !shouldStopAuto) {
|
|
5465
|
-
this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
|
|
5466
|
-
}
|
|
5467
|
-
if (alphaZeroEngaged && alphaZeroStatusApplied) {
|
|
5468
|
-
this.statusTracker.clearOverride(alphaZeroStatusId);
|
|
5469
|
-
}
|
|
5470
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5471
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5472
|
-
}
|
|
4637
|
+
this.finishStreamingFormatter();
|
|
5473
4638
|
display.stopThinking(false);
|
|
5474
4639
|
this.uiUpdates.setMode('processing');
|
|
5475
|
-
this.stopStreamingHeartbeat(
|
|
4640
|
+
this.stopStreamingHeartbeat();
|
|
5476
4641
|
this.isProcessing = false;
|
|
5477
4642
|
this.terminalInput.setStreaming(false);
|
|
5478
4643
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5479
4644
|
this.setIdleStatus();
|
|
4645
|
+
display.newLine();
|
|
5480
4646
|
this.updateStatusMessage(null);
|
|
5481
4647
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5482
4648
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5483
4649
|
// Claude Code style: New prompt naturally appears at bottom
|
|
5484
4650
|
this.ensureReadlineReady();
|
|
5485
|
-
this.
|
|
4651
|
+
this.terminalInput.render();
|
|
5486
4652
|
this.scheduleQueueProcessing();
|
|
5487
4653
|
this.refreshQueueIndicators();
|
|
5488
4654
|
}
|
|
@@ -5513,7 +4679,6 @@ export class InteractiveShell {
|
|
|
5513
4679
|
if (!agent) {
|
|
5514
4680
|
return;
|
|
5515
4681
|
}
|
|
5516
|
-
this.hasShownThoughtProcess = false;
|
|
5517
4682
|
this.isProcessing = true;
|
|
5518
4683
|
this.uiUpdates.setMode('processing');
|
|
5519
4684
|
this.terminalInput.setStreaming(true);
|
|
@@ -5554,8 +4719,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5554
4719
|
}
|
|
5555
4720
|
while (iteration < MAX_ITERATIONS) {
|
|
5556
4721
|
iteration++;
|
|
5557
|
-
this.hasShownThoughtProcess = false;
|
|
5558
|
-
this.resetAssistantStreamTracking();
|
|
5559
4722
|
display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
|
|
5560
4723
|
this.updateStatusMessage(`Working on iteration ${iteration}...`);
|
|
5561
4724
|
try {
|
|
@@ -5563,6 +4726,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5563
4726
|
display.showThinking('Responding...');
|
|
5564
4727
|
this.refreshStatusLine(true);
|
|
5565
4728
|
const response = await agent.send(currentPrompt, true);
|
|
4729
|
+
this.finishStreamingFormatter();
|
|
5566
4730
|
await this.awaitPendingCleanup();
|
|
5567
4731
|
this.captureHistorySnapshot();
|
|
5568
4732
|
this.autosaveIfEnabled();
|
|
@@ -5695,6 +4859,7 @@ What's the next action?`;
|
|
|
5695
4859
|
}
|
|
5696
4860
|
}
|
|
5697
4861
|
finally {
|
|
4862
|
+
this.finishStreamingFormatter();
|
|
5698
4863
|
const totalElapsed = Date.now() - overallStartTime;
|
|
5699
4864
|
const minutes = Math.floor(totalElapsed / 60000);
|
|
5700
4865
|
const seconds = Math.floor((totalElapsed % 60000) / 1000);
|
|
@@ -5708,6 +4873,10 @@ What's the next action?`;
|
|
|
5708
4873
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5709
4874
|
this.setIdleStatus();
|
|
5710
4875
|
this.updateStatusMessage(null);
|
|
4876
|
+
display.newLine();
|
|
4877
|
+
// Claude Code style: Show unified status bar before prompt
|
|
4878
|
+
// This creates consistent UI between startup and post-streaming
|
|
4879
|
+
this.showUnifiedStatusBar();
|
|
5711
4880
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5712
4881
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5713
4882
|
// Claude Code style: New prompt naturally appears at bottom
|
|
@@ -5886,25 +5055,7 @@ What's the next action?`;
|
|
|
5886
5055
|
}
|
|
5887
5056
|
if (name === 'bash' || name === 'execute_bash') {
|
|
5888
5057
|
const command = String(entry.args['command'] ?? '').toLowerCase();
|
|
5889
|
-
|
|
5890
|
-
'npm test',
|
|
5891
|
-
'yarn test',
|
|
5892
|
-
'pnpm test',
|
|
5893
|
-
'bun test',
|
|
5894
|
-
'go test',
|
|
5895
|
-
'cargo test',
|
|
5896
|
-
'pytest',
|
|
5897
|
-
'python -m pytest',
|
|
5898
|
-
'tox',
|
|
5899
|
-
'mvn test',
|
|
5900
|
-
'./mvnw test',
|
|
5901
|
-
'gradle test',
|
|
5902
|
-
'./gradlew test',
|
|
5903
|
-
'dotnet test',
|
|
5904
|
-
'make test',
|
|
5905
|
-
'swift test',
|
|
5906
|
-
];
|
|
5907
|
-
return patterns.some((pattern) => command.includes(pattern));
|
|
5058
|
+
return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
|
|
5908
5059
|
}
|
|
5909
5060
|
return false;
|
|
5910
5061
|
}
|
|
@@ -5922,170 +5073,310 @@ What's the next action?`;
|
|
|
5922
5073
|
const parts = [error?.stdout, error?.stderr, error?.message].filter((part) => typeof part === 'string' && part.trim());
|
|
5923
5074
|
return parts.join('\n').trim();
|
|
5924
5075
|
}
|
|
5925
|
-
|
|
5926
|
-
if (
|
|
5927
|
-
return
|
|
5076
|
+
runAutoQualityChecks(trigger, assistantResponse, verificationContext) {
|
|
5077
|
+
if (!this.verificationEnabled) {
|
|
5078
|
+
return;
|
|
5079
|
+
}
|
|
5080
|
+
void (async () => {
|
|
5081
|
+
try {
|
|
5082
|
+
const buildOk = await this.enforceAutoBuild(trigger);
|
|
5083
|
+
if (!buildOk) {
|
|
5084
|
+
return;
|
|
5085
|
+
}
|
|
5086
|
+
await this.enforceAutoTests(trigger, assistantResponse, verificationContext);
|
|
5087
|
+
}
|
|
5088
|
+
catch (error) {
|
|
5089
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5090
|
+
display.showWarning(`Auto quality checks failed: ${message}`);
|
|
5091
|
+
}
|
|
5092
|
+
})();
|
|
5093
|
+
}
|
|
5094
|
+
async enforceAutoTests(trigger, assistantResponse, verificationContext) {
|
|
5095
|
+
if (this.autoTestInFlight) {
|
|
5096
|
+
return;
|
|
5097
|
+
}
|
|
5098
|
+
if (!this.verificationEnabled) {
|
|
5099
|
+
return;
|
|
5928
5100
|
}
|
|
5929
5101
|
const latestChange = this.getLatestFileChangeTimestamp();
|
|
5930
5102
|
if (!latestChange) {
|
|
5931
|
-
return
|
|
5103
|
+
return;
|
|
5932
5104
|
}
|
|
5933
5105
|
const latestTest = this.getLatestTestTimestamp();
|
|
5934
5106
|
if (latestTest && latestChange <= latestTest) {
|
|
5935
|
-
return
|
|
5107
|
+
return;
|
|
5936
5108
|
}
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
|
|
5941
|
-
return 'skipped';
|
|
5109
|
+
if (!this.lastBuildSucceededAt || this.lastBuildSucceededAt < latestChange) {
|
|
5110
|
+
display.showSystemMessage('⏭️ Skipping auto-tests because no successful build is recorded for the latest changes.');
|
|
5111
|
+
return;
|
|
5942
5112
|
}
|
|
5943
5113
|
this.autoTestInFlight = true;
|
|
5944
|
-
const command =
|
|
5945
|
-
|
|
5946
|
-
display.showSystemMessage(`🧪 Auto-testing recent changes (${trigger}) with "${command}"${reasonSuffix}...`);
|
|
5114
|
+
const command = 'npm test -- --runInBand';
|
|
5115
|
+
display.showSystemMessage(`🧪 Auto-testing recent changes (${trigger}) with "${command}"...`);
|
|
5947
5116
|
this.updateStatusMessage('Running tests automatically...');
|
|
5117
|
+
let combinedOutput = '';
|
|
5948
5118
|
try {
|
|
5949
5119
|
const { stdout, stderr } = await execAsync(command, {
|
|
5950
5120
|
cwd: this.workingDir,
|
|
5951
5121
|
timeout: 10 * 60 * 1000,
|
|
5952
5122
|
maxBuffer: 10 * 1024 * 1024,
|
|
5953
5123
|
});
|
|
5954
|
-
|
|
5955
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5124
|
+
combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5956
5125
|
display.showSystemMessage('✅ Auto-tests finished.');
|
|
5957
|
-
if (outputText) {
|
|
5958
|
-
this.writeLocked(`${outputText}\n`);
|
|
5959
|
-
}
|
|
5960
5126
|
this.statusTracker.clearOverride('tests');
|
|
5961
|
-
return 'success';
|
|
5962
5127
|
}
|
|
5963
5128
|
catch (error) {
|
|
5964
|
-
|
|
5965
|
-
const message = this.formatCommandError(error);
|
|
5129
|
+
combinedOutput = this.formatCommandError(error);
|
|
5966
5130
|
display.showWarning('⚠️ Auto-tests failed. Review output below.');
|
|
5967
|
-
if (message) {
|
|
5968
|
-
this.writeLocked(`${message}\n`);
|
|
5969
|
-
}
|
|
5970
5131
|
this.statusTracker.pushOverride('tests', 'Tests failing', {
|
|
5971
|
-
detail:
|
|
5132
|
+
detail: 'Auto-run npm test failed',
|
|
5972
5133
|
tone: 'danger',
|
|
5973
5134
|
});
|
|
5974
|
-
return 'failed';
|
|
5975
5135
|
}
|
|
5976
5136
|
finally {
|
|
5137
|
+
if (combinedOutput) {
|
|
5138
|
+
this.writeLocked(`${combinedOutput}\n`);
|
|
5139
|
+
}
|
|
5140
|
+
try {
|
|
5141
|
+
await this.runAIDesignedTests(trigger, assistantResponse, verificationContext);
|
|
5142
|
+
}
|
|
5143
|
+
catch (error) {
|
|
5144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5145
|
+
display.showWarning(`AI-designed tests failed: ${message}`);
|
|
5146
|
+
}
|
|
5147
|
+
this.lastAutoTestRun = Date.now();
|
|
5977
5148
|
this.updateStatusMessage(null);
|
|
5978
5149
|
this.autoTestInFlight = false;
|
|
5979
|
-
this.terminalInput.resetContentPosition();
|
|
5980
|
-
this.terminalInput.forceRender();
|
|
5981
5150
|
}
|
|
5982
5151
|
}
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5152
|
+
/**
|
|
5153
|
+
* Auto-build verification after file edits.
|
|
5154
|
+
* Runs `npm run build` to catch TypeScript errors and feeds failures back to the agent.
|
|
5155
|
+
*/
|
|
5156
|
+
async enforceAutoBuild(trigger) {
|
|
5157
|
+
if (this.autoBuildPromise) {
|
|
5158
|
+
return this.autoBuildPromise;
|
|
5159
|
+
}
|
|
5160
|
+
if (!this.verificationEnabled) {
|
|
5161
|
+
return false;
|
|
5162
|
+
}
|
|
5163
|
+
const latestChange = this.getLatestFileChangeTimestamp();
|
|
5164
|
+
if (!latestChange) {
|
|
5986
5165
|
return true;
|
|
5987
5166
|
}
|
|
5988
|
-
if (
|
|
5989
|
-
|
|
5990
|
-
const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
|
|
5991
|
-
const patterns = [
|
|
5992
|
-
'npm run build',
|
|
5993
|
-
'yarn build',
|
|
5994
|
-
'pnpm build',
|
|
5995
|
-
'bun run build',
|
|
5996
|
-
'bun build',
|
|
5997
|
-
'tsc',
|
|
5998
|
-
'cargo build',
|
|
5999
|
-
'go build',
|
|
6000
|
-
'python -m build',
|
|
6001
|
-
'mvn -b -dskiptests package',
|
|
6002
|
-
'mvn package',
|
|
6003
|
-
'mvn install',
|
|
6004
|
-
'./mvnw -b -dskiptests package',
|
|
6005
|
-
'./mvnw package',
|
|
6006
|
-
'./mvnw install',
|
|
6007
|
-
'gradle build',
|
|
6008
|
-
'gradle assemble',
|
|
6009
|
-
'./gradlew build',
|
|
6010
|
-
'./gradlew assemble',
|
|
6011
|
-
'dotnet build',
|
|
6012
|
-
'swift build',
|
|
6013
|
-
];
|
|
6014
|
-
return makeBuild || patterns.some((pattern) => command.includes(pattern));
|
|
5167
|
+
if (this.lastBuildSucceededAt && latestChange <= this.lastBuildSucceededAt) {
|
|
5168
|
+
return true;
|
|
6015
5169
|
}
|
|
6016
|
-
|
|
5170
|
+
const command = 'npm run build';
|
|
5171
|
+
const runner = (async () => {
|
|
5172
|
+
this.autoBuildInFlight = true;
|
|
5173
|
+
display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger})...`);
|
|
5174
|
+
this.updateStatusMessage('Running build automatically...');
|
|
5175
|
+
try {
|
|
5176
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
5177
|
+
cwd: this.workingDir,
|
|
5178
|
+
timeout: 5 * 60 * 1000,
|
|
5179
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
5180
|
+
});
|
|
5181
|
+
const finishedAt = Date.now();
|
|
5182
|
+
this.lastAutoBuildRun = finishedAt;
|
|
5183
|
+
this.lastBuildSucceededAt = finishedAt;
|
|
5184
|
+
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5185
|
+
display.showSystemMessage('✅ Build succeeded.');
|
|
5186
|
+
if (outputText && outputText.length < 500) {
|
|
5187
|
+
this.writeLocked(`${outputText}\n`);
|
|
5188
|
+
}
|
|
5189
|
+
this.statusTracker.clearOverride('build');
|
|
5190
|
+
return true;
|
|
5191
|
+
}
|
|
5192
|
+
catch (error) {
|
|
5193
|
+
const finishedAt = Date.now();
|
|
5194
|
+
this.lastAutoBuildRun = finishedAt;
|
|
5195
|
+
this.lastBuildSucceededAt = null;
|
|
5196
|
+
const errorOutput = this.formatCommandError(error);
|
|
5197
|
+
display.showWarning('⚠️ Build failed. Feeding errors back to agent...');
|
|
5198
|
+
if (errorOutput) {
|
|
5199
|
+
this.writeLocked(`${errorOutput}\n`);
|
|
5200
|
+
}
|
|
5201
|
+
this.statusTracker.pushOverride('build', 'Build failing', {
|
|
5202
|
+
detail: 'Auto-run npm run build failed',
|
|
5203
|
+
tone: 'danger',
|
|
5204
|
+
});
|
|
5205
|
+
// Feed build errors back to the agent so it can fix them
|
|
5206
|
+
await this.feedBuildErrorsToAgent(errorOutput);
|
|
5207
|
+
return false;
|
|
5208
|
+
}
|
|
5209
|
+
finally {
|
|
5210
|
+
this.updateStatusMessage(null);
|
|
5211
|
+
this.autoBuildInFlight = false;
|
|
5212
|
+
this.autoBuildPromise = null;
|
|
5213
|
+
}
|
|
5214
|
+
})();
|
|
5215
|
+
this.autoBuildPromise = runner;
|
|
5216
|
+
return runner;
|
|
6017
5217
|
}
|
|
6018
|
-
|
|
6019
|
-
const
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
5218
|
+
describeRecentChanges() {
|
|
5219
|
+
const changes = this._fileChangeTracker.getAllChanges();
|
|
5220
|
+
if (!changes.length) {
|
|
5221
|
+
return 'No tracked changes';
|
|
5222
|
+
}
|
|
5223
|
+
const items = changes.slice(0, 8).map((change) => {
|
|
5224
|
+
const additions = change.additions ? `+${change.additions}` : '';
|
|
5225
|
+
const removals = change.removals ? `-${change.removals}` : '';
|
|
5226
|
+
const delta = [additions, removals].filter(Boolean).join('/');
|
|
5227
|
+
const deltaLabel = delta ? ` ${delta}` : '';
|
|
5228
|
+
return `${change.path} (${change.type}${deltaLabel})`;
|
|
5229
|
+
});
|
|
5230
|
+
return items.join('; ');
|
|
5231
|
+
}
|
|
5232
|
+
describePackageScripts() {
|
|
5233
|
+
try {
|
|
5234
|
+
const pkgPath = join(this.workingDir, 'package.json');
|
|
5235
|
+
if (!existsSync(pkgPath)) {
|
|
5236
|
+
return 'package.json not found';
|
|
6024
5237
|
}
|
|
5238
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
5239
|
+
const scripts = pkg.scripts ? Object.keys(pkg.scripts) : [];
|
|
5240
|
+
if (!scripts.length) {
|
|
5241
|
+
return 'package.json present without scripts';
|
|
5242
|
+
}
|
|
5243
|
+
return `package.json scripts: ${scripts.slice(0, 8).join(', ')}`;
|
|
5244
|
+
}
|
|
5245
|
+
catch {
|
|
5246
|
+
return 'package.json present but unreadable';
|
|
6025
5247
|
}
|
|
6026
|
-
return latest;
|
|
6027
5248
|
}
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
*/
|
|
6032
|
-
async enforceAutoBuild(trigger, commandInfo) {
|
|
6033
|
-
if (this.autoBuildInFlight || !this.verificationEnabled) {
|
|
6034
|
-
return 'skipped';
|
|
5249
|
+
parseAIDesignedTests(plan) {
|
|
5250
|
+
if (!plan?.trim()) {
|
|
5251
|
+
return [];
|
|
6035
5252
|
}
|
|
6036
|
-
const
|
|
6037
|
-
|
|
6038
|
-
return 'skipped';
|
|
6039
|
-
}
|
|
6040
|
-
const latestBuild = this.getLatestBuildTimestamp();
|
|
6041
|
-
if (latestBuild && latestChange <= latestBuild) {
|
|
6042
|
-
return 'skipped';
|
|
6043
|
-
}
|
|
6044
|
-
const detected = commandInfo ?? detectBuildCommand(this.workingDir);
|
|
6045
|
-
if (!detected) {
|
|
6046
|
-
this.lastAutoBuildRun = Date.now();
|
|
6047
|
-
display.showSystemMessage('ℹ️ Skipping auto-build: no build command detected for this repo.');
|
|
6048
|
-
return 'skipped';
|
|
6049
|
-
}
|
|
6050
|
-
this.autoBuildInFlight = true;
|
|
6051
|
-
const command = detected.command;
|
|
6052
|
-
const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
|
|
6053
|
-
display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger}) with "${command}"${reasonSuffix}...`);
|
|
6054
|
-
this.updateStatusMessage('Running build automatically...');
|
|
5253
|
+
const match = plan.match(/\[[\s\S]*\]/);
|
|
5254
|
+
const payload = match ? match[0] : plan;
|
|
6055
5255
|
try {
|
|
6056
|
-
const
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
5256
|
+
const parsed = JSON.parse(payload);
|
|
5257
|
+
if (!Array.isArray(parsed)) {
|
|
5258
|
+
return [];
|
|
5259
|
+
}
|
|
5260
|
+
return parsed
|
|
5261
|
+
.map((entry, index) => ({
|
|
5262
|
+
id: typeof entry.id === 'string' && entry.id.trim() ? entry.id.trim() : `ai-test-${index + 1}`,
|
|
5263
|
+
description: typeof entry.description === 'string' ? entry.description.trim() : `AI test ${index + 1}`,
|
|
5264
|
+
command: typeof entry.command === 'string' ? entry.command.trim() : '',
|
|
5265
|
+
expect: typeof entry.expect === 'string' ? entry.expect.trim() : undefined,
|
|
5266
|
+
timeoutMs: typeof entry.timeoutMs === 'number' ? entry.timeoutMs : undefined,
|
|
5267
|
+
}))
|
|
5268
|
+
.filter((test) => !!test.command);
|
|
5269
|
+
}
|
|
5270
|
+
catch {
|
|
5271
|
+
return [];
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
isDangerousTestCommand(command) {
|
|
5275
|
+
const patterns = [
|
|
5276
|
+
/\brm\s+-/i,
|
|
5277
|
+
/\brm\s+/i,
|
|
5278
|
+
/\brmdir\b/i,
|
|
5279
|
+
/\bchmod\s+7/i,
|
|
5280
|
+
/\bsudo\b/i,
|
|
5281
|
+
/\bmkfs\b/i,
|
|
5282
|
+
/\bshutdown\b/i,
|
|
5283
|
+
/\breboot\b/i,
|
|
5284
|
+
/\bgit\s+(push|reset|checkout|clean)\b/i,
|
|
5285
|
+
/\bnpm\s+install\b/i,
|
|
5286
|
+
/\byarn\s+add\b/i,
|
|
5287
|
+
/\bpnpm\s+add\b/i,
|
|
5288
|
+
];
|
|
5289
|
+
return patterns.some((pattern) => pattern.test(command));
|
|
5290
|
+
}
|
|
5291
|
+
async runAIDesignedTests(trigger, assistantResponse, verificationContext) {
|
|
5292
|
+
const userGoal = this.lastUserQuery?.trim() || 'Not provided';
|
|
5293
|
+
const implementationClaim = (assistantResponse ?? this.lastAssistantResponse ?? '').trim();
|
|
5294
|
+
const changeSummary = this.describeRecentChanges();
|
|
5295
|
+
const packageSummary = this.describePackageScripts();
|
|
5296
|
+
const recentConversation = verificationContext?.conversationHistory?.slice(-3).join('\n') ?? '';
|
|
5297
|
+
let provider;
|
|
5298
|
+
try {
|
|
5299
|
+
provider = createProvider({
|
|
5300
|
+
provider: this.sessionState.provider,
|
|
5301
|
+
model: this.sessionState.model,
|
|
5302
|
+
temperature: 0.15,
|
|
5303
|
+
maxTokens: 900,
|
|
6060
5304
|
});
|
|
6061
|
-
this.lastAutoBuildRun = Date.now();
|
|
6062
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
6063
|
-
display.showSystemMessage('✅ Build succeeded.');
|
|
6064
|
-
if (outputText && outputText.length < 500) {
|
|
6065
|
-
this.writeLocked(`${outputText}\n`);
|
|
6066
|
-
}
|
|
6067
|
-
this.statusTracker.clearOverride('build');
|
|
6068
|
-
return 'success';
|
|
6069
5305
|
}
|
|
6070
5306
|
catch (error) {
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
if (errorOutput) {
|
|
6075
|
-
this.writeLocked(`${errorOutput}\n`);
|
|
6076
|
-
}
|
|
6077
|
-
this.statusTracker.pushOverride('build', 'Build failing', {
|
|
6078
|
-
detail: `Auto-run ${command} failed`,
|
|
6079
|
-
tone: 'danger',
|
|
6080
|
-
});
|
|
6081
|
-
// Feed build errors back to the agent so it can fix them
|
|
6082
|
-
await this.feedBuildErrorsToAgent(errorOutput);
|
|
6083
|
-
return 'failed';
|
|
5307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5308
|
+
display.showWarning(`AI-designed tests skipped: ${message}`);
|
|
5309
|
+
return;
|
|
6084
5310
|
}
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
5311
|
+
const systemPrompt = 'You design high-value, repo-aware verification commands. Only emit JSON. Use read-only commands that run quickly.';
|
|
5312
|
+
const prompt = `Workspace: ${this.workingDir}
|
|
5313
|
+
User goal: ${userGoal.slice(0, 800)}
|
|
5314
|
+
Assistant claim: ${implementationClaim ? implementationClaim.slice(0, 1200) : 'No implementation summary captured.'}
|
|
5315
|
+
Recent file changes: ${changeSummary}
|
|
5316
|
+
Package signals: ${packageSummary}
|
|
5317
|
+
Recent conversation: ${recentConversation || 'n/a'}
|
|
5318
|
+
|
|
5319
|
+
Design 3-5 focused verification commands to confirm the requested functionality works after the successful build.
|
|
5320
|
+
Rules:
|
|
5321
|
+
- Commands must be safe and non-destructive (no rm, mv, chmod 7xx, sudo, package installs, or git mutations).
|
|
5322
|
+
- Prefer existing npm/yarn/pnpm scripts or targeted checks (node scripts, grep, curl localhost) that prove behavior.
|
|
5323
|
+
- Keep commands runnable from the repo root and finish quickly.
|
|
5324
|
+
Return ONLY JSON array:
|
|
5325
|
+
[{"id":"ai-test-1","description":"what it verifies","command":"npm test -- my-case","expect":"substring to find","timeoutMs":45000}]`;
|
|
5326
|
+
const plan = await provider.generate([
|
|
5327
|
+
{ role: 'system', content: systemPrompt },
|
|
5328
|
+
{ role: 'user', content: prompt },
|
|
5329
|
+
], []);
|
|
5330
|
+
const planText = plan.type === 'message' ? plan.content : plan.content ?? '';
|
|
5331
|
+
const tests = this.parseAIDesignedTests(planText).slice(0, 5);
|
|
5332
|
+
if (!tests.length) {
|
|
5333
|
+
display.showWarning('AI-designed tests could not be generated. Skipping.');
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
const results = [];
|
|
5337
|
+
results.push(`🤖 Running ${tests.length} AI-designed verification tests (${trigger})...`);
|
|
5338
|
+
for (const test of tests) {
|
|
5339
|
+
const command = test.command.trim();
|
|
5340
|
+
if (!command) {
|
|
5341
|
+
continue;
|
|
5342
|
+
}
|
|
5343
|
+
if (this.isDangerousTestCommand(command)) {
|
|
5344
|
+
results.push(`⚠️ ${test.id} blocked (unsafe command): ${command}`);
|
|
5345
|
+
continue;
|
|
5346
|
+
}
|
|
5347
|
+
try {
|
|
5348
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
5349
|
+
cwd: this.workingDir,
|
|
5350
|
+
timeout: Math.min(Math.max(test.timeoutMs ?? 45000, 5000), 120000),
|
|
5351
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
5352
|
+
});
|
|
5353
|
+
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
5354
|
+
const expected = test.expect?.trim();
|
|
5355
|
+
const success = expected ? output.toLowerCase().includes(expected.toLowerCase()) : true;
|
|
5356
|
+
const snippet = output.replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
5357
|
+
results.push(`${success ? '✅' : '❌'} ${test.description || test.id}`);
|
|
5358
|
+
results.push(` cmd: ${command}`);
|
|
5359
|
+
if (expected) {
|
|
5360
|
+
results.push(` expect: ${expected}`);
|
|
5361
|
+
}
|
|
5362
|
+
if (snippet) {
|
|
5363
|
+
results.push(` output: ${snippet}`);
|
|
5364
|
+
}
|
|
5365
|
+
if (!success && !snippet) {
|
|
5366
|
+
results.push(' output: [no output captured]');
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
catch (error) {
|
|
5370
|
+
const message = this.formatCommandError(error) || (error instanceof Error ? error.message : String(error));
|
|
5371
|
+
results.push(`❌ ${test.description || test.id}`);
|
|
5372
|
+
results.push(` cmd: ${command}`);
|
|
5373
|
+
if (test.expect) {
|
|
5374
|
+
results.push(` expect: ${test.expect}`);
|
|
5375
|
+
}
|
|
5376
|
+
results.push(` error: ${message.slice(0, 240)}`);
|
|
5377
|
+
}
|
|
6088
5378
|
}
|
|
5379
|
+
display.showSystemMessage(results.join('\n'));
|
|
6089
5380
|
}
|
|
6090
5381
|
/**
|
|
6091
5382
|
* Feed build errors back to the agent conversation for automatic fixing.
|
|
@@ -6111,43 +5402,21 @@ What's the next action?`;
|
|
|
6111
5402
|
// Send the error to the agent for fixing
|
|
6112
5403
|
display.showThinking('Analyzing build errors');
|
|
6113
5404
|
this.refreshStatusLine(true);
|
|
6114
|
-
this.
|
|
6115
|
-
|
|
5405
|
+
const response = await this.agent.send(prompt, true);
|
|
5406
|
+
this.finishStreamingFormatter();
|
|
6116
5407
|
display.stopThinking();
|
|
6117
5408
|
this.refreshStatusLine(true);
|
|
5409
|
+
if (response) {
|
|
5410
|
+
display.showAssistantMessage(response, { isFinal: true });
|
|
5411
|
+
}
|
|
6118
5412
|
// Recursively verify the fix worked
|
|
6119
5413
|
await this.enforceAutoBuild('verification');
|
|
6120
5414
|
}
|
|
6121
5415
|
catch (agentError) {
|
|
6122
5416
|
display.showWarning('Agent could not automatically fix build errors. Please review manually.');
|
|
6123
5417
|
}
|
|
6124
|
-
}
|
|
6125
|
-
async enforceAutoVerification(trigger) {
|
|
6126
|
-
if (this.autoVerificationInFlight || !this.verificationEnabled) {
|
|
6127
|
-
return;
|
|
6128
|
-
}
|
|
6129
|
-
const latestChange = this.getLatestFileChangeTimestamp();
|
|
6130
|
-
if (!latestChange) {
|
|
6131
|
-
return;
|
|
6132
|
-
}
|
|
6133
|
-
this.autoVerificationInFlight = true;
|
|
6134
|
-
try {
|
|
6135
|
-
const buildInfo = detectBuildCommand(this.workingDir);
|
|
6136
|
-
const testInfo = detectTestCommand(this.workingDir);
|
|
6137
|
-
const buildResult = await this.enforceAutoBuild(trigger, buildInfo);
|
|
6138
|
-
if (buildResult === 'failed') {
|
|
6139
|
-
return;
|
|
6140
|
-
}
|
|
6141
|
-
if (testInfo) {
|
|
6142
|
-
await this.enforceAutoTests(trigger, testInfo);
|
|
6143
|
-
}
|
|
6144
|
-
else {
|
|
6145
|
-
this.lastAutoTestRun = Date.now();
|
|
6146
|
-
display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
|
|
6147
|
-
}
|
|
6148
|
-
}
|
|
6149
5418
|
finally {
|
|
6150
|
-
this.
|
|
5419
|
+
this.finishStreamingFormatter();
|
|
6151
5420
|
}
|
|
6152
5421
|
}
|
|
6153
5422
|
rebuildAgent() {
|
|
@@ -6165,35 +5434,29 @@ What's the next action?`;
|
|
|
6165
5434
|
autoContinue: this.autoContinueEnabled,
|
|
6166
5435
|
};
|
|
6167
5436
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
6168
|
-
onStreamChunk: (chunk
|
|
6169
|
-
this.
|
|
5437
|
+
onStreamChunk: (chunk) => {
|
|
5438
|
+
this.handleStreamChunk(chunk);
|
|
6170
5439
|
},
|
|
6171
5440
|
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6172
5441
|
onAssistantMessage: (content, metadata) => {
|
|
6173
5442
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6174
|
-
|
|
6175
|
-
const parsed = this.splitThinkingResponse(content);
|
|
6176
|
-
const thinking = parsed?.thinking ?? null;
|
|
6177
|
-
const responseContent = parsed ? parsed.response?.trim() ?? '' : content.trim();
|
|
6178
|
-
const narrativeContent = (parsed?.response ?? content).trim();
|
|
6179
|
-
const streamRendered = metadata.wasStreamed && this.assistantStreamHadContent;
|
|
6180
|
-
if (thinking) {
|
|
6181
|
-
this.presentThoughtProcess(thinking, enriched, {
|
|
6182
|
-
wasStreamed: metadata.wasStreamed,
|
|
6183
|
-
// Always render the thought block immediately when present so it appears first.
|
|
6184
|
-
renderWhenStreamed: true,
|
|
6185
|
-
});
|
|
6186
|
-
}
|
|
5443
|
+
// Update spinner based on message type
|
|
6187
5444
|
if (metadata.isFinal) {
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
5445
|
+
const parsed = this.splitThinkingResponse(content);
|
|
5446
|
+
const finalContent = parsed?.response?.trim() || content;
|
|
5447
|
+
// Skip display if content was already streamed to avoid double-display
|
|
5448
|
+
if (!metadata.wasStreamed) {
|
|
5449
|
+
if (parsed?.thinking) {
|
|
5450
|
+
const summary = this.extractThoughtSummary(parsed.thinking);
|
|
5451
|
+
if (summary) {
|
|
5452
|
+
display.updateThinking(`💭 ${summary}`);
|
|
5453
|
+
}
|
|
5454
|
+
display.showAssistantMessage(parsed.thinking, { ...enriched, isFinal: false });
|
|
5455
|
+
}
|
|
5456
|
+
if (finalContent) {
|
|
5457
|
+
display.showAssistantMessage(finalContent, enriched);
|
|
5458
|
+
}
|
|
6195
5459
|
}
|
|
6196
|
-
this.renderToolUsageSummary({ ...enriched, isFinal: true });
|
|
6197
5460
|
// Status shown in mode controls bar - no separate status line needed
|
|
6198
5461
|
display.stopThinking();
|
|
6199
5462
|
// Update context usage for mode controls display
|
|
@@ -6204,22 +5467,19 @@ What's the next action?`;
|
|
|
6204
5467
|
this.updateContextUsage(percentage);
|
|
6205
5468
|
}
|
|
6206
5469
|
}
|
|
5470
|
+
if (finalContent) {
|
|
5471
|
+
this.lastAssistantResponse = finalContent;
|
|
5472
|
+
}
|
|
6207
5473
|
// Auto-verify changes: build first (catches type errors), then tests
|
|
6208
|
-
void this.
|
|
5474
|
+
void this.runAutoQualityChecks('final-response', finalContent);
|
|
6209
5475
|
}
|
|
6210
5476
|
else {
|
|
6211
5477
|
// Non-final message = narrative text before tool calls (Claude Code style)
|
|
6212
5478
|
// Stop spinner and show the narrative text directly
|
|
6213
5479
|
display.stopThinking();
|
|
6214
|
-
if
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
const shouldRenderNarrative = Boolean(narrativeContent && (!metadata.wasStreamed || !blocksActive || !streamRendered));
|
|
6218
|
-
if (shouldRenderNarrative) {
|
|
6219
|
-
this.renderAssistantContent('thought', narrativeContent, {
|
|
6220
|
-
...enriched,
|
|
6221
|
-
isFinal: false,
|
|
6222
|
-
});
|
|
5480
|
+
// Skip display if content was already streamed to avoid double-display
|
|
5481
|
+
if (!metadata.wasStreamed) {
|
|
5482
|
+
display.showNarrative(content.trim());
|
|
6223
5483
|
}
|
|
6224
5484
|
// The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
|
|
6225
5485
|
this.requestPromptRefresh();
|
|
@@ -6232,8 +5492,8 @@ What's the next action?`;
|
|
|
6232
5492
|
this.requestPromptRefresh();
|
|
6233
5493
|
},
|
|
6234
5494
|
onContextSquishing: (message) => {
|
|
6235
|
-
//
|
|
6236
|
-
display.
|
|
5495
|
+
// Show notification in UI when auto context squishing occurs
|
|
5496
|
+
display.showSystemMessage(`🔄 ${message}`);
|
|
6237
5497
|
this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
|
|
6238
5498
|
detail: 'Reducing conversation history to fit within token limits',
|
|
6239
5499
|
tone: 'warning',
|
|
@@ -6241,7 +5501,7 @@ What's the next action?`;
|
|
|
6241
5501
|
},
|
|
6242
5502
|
onContextRecovery: (attempt, maxAttempts, message) => {
|
|
6243
5503
|
// Show recovery progress in UI
|
|
6244
|
-
display.
|
|
5504
|
+
display.showSystemMessage(`⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
|
|
6245
5505
|
},
|
|
6246
5506
|
onContextPruned: (removedCount, stats) => {
|
|
6247
5507
|
// Clear squish overlay if active
|
|
@@ -6249,43 +5509,40 @@ What's the next action?`;
|
|
|
6249
5509
|
// Show notification that context was pruned
|
|
6250
5510
|
const method = stats['method'];
|
|
6251
5511
|
const percentage = stats['percentage'];
|
|
6252
|
-
const summarized = stats['summarized'] === true;
|
|
6253
5512
|
if (method === 'emergency-recovery') {
|
|
6254
|
-
display.
|
|
5513
|
+
display.showSystemMessage(`✅ Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%`);
|
|
6255
5514
|
}
|
|
6256
5515
|
// Update context usage in UI
|
|
6257
5516
|
if (typeof percentage === 'number') {
|
|
6258
5517
|
this.updateContextUsage(percentage);
|
|
6259
5518
|
}
|
|
6260
|
-
if (summarized) {
|
|
6261
|
-
display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
|
|
6262
|
-
}
|
|
6263
5519
|
// Ensure prompt remains visible at bottom after context messages
|
|
6264
|
-
this.
|
|
5520
|
+
this.terminalInput.render();
|
|
6265
5521
|
},
|
|
6266
5522
|
onContinueAfterRecovery: () => {
|
|
6267
5523
|
// Update UI to show we're continuing after context recovery
|
|
6268
|
-
display.
|
|
5524
|
+
display.showSystemMessage(`🔄 Continuing after context recovery...`);
|
|
6269
5525
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6270
|
-
this.
|
|
5526
|
+
this.terminalInput.render();
|
|
6271
5527
|
},
|
|
6272
5528
|
onAutoContinue: (attempt, maxAttempts, _message) => {
|
|
6273
5529
|
// Show auto-continue progress in UI
|
|
6274
5530
|
display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
|
|
6275
5531
|
this.updateStatusMessage('Auto-continuing...');
|
|
6276
|
-
this.
|
|
5532
|
+
this.terminalInput.render();
|
|
6277
5533
|
},
|
|
6278
5534
|
onCancelled: () => {
|
|
6279
5535
|
// Update UI to show operation was cancelled
|
|
6280
5536
|
display.showWarning('Operation cancelled.');
|
|
6281
5537
|
this.uiUpdates.setMode('processing');
|
|
6282
|
-
this.stopStreamingHeartbeat(
|
|
5538
|
+
this.stopStreamingHeartbeat();
|
|
6283
5539
|
this.updateStatusMessage(null);
|
|
6284
5540
|
this.terminalInput.setStreaming(false);
|
|
6285
|
-
this.
|
|
5541
|
+
this.terminalInput.render();
|
|
6286
5542
|
},
|
|
6287
|
-
onVerificationNeeded: () => {
|
|
6288
|
-
|
|
5543
|
+
onVerificationNeeded: (response, context) => {
|
|
5544
|
+
this.lastAssistantResponse = response;
|
|
5545
|
+
void this.runAutoQualityChecks('verification', response, context);
|
|
6289
5546
|
},
|
|
6290
5547
|
});
|
|
6291
5548
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
@@ -6319,7 +5576,7 @@ What's the next action?`;
|
|
|
6319
5576
|
resetChatBoxAfterModelSwap() {
|
|
6320
5577
|
this.updateStatusMessage(null);
|
|
6321
5578
|
this.terminalInput.setStreaming(false);
|
|
6322
|
-
this.
|
|
5579
|
+
this.terminalInput.render();
|
|
6323
5580
|
this.ensureReadlineReady();
|
|
6324
5581
|
}
|
|
6325
5582
|
/**
|
|
@@ -6382,22 +5639,19 @@ What's the next action?`;
|
|
|
6382
5639
|
}
|
|
6383
5640
|
buildThinkingDirective() {
|
|
6384
5641
|
switch (this.thinkingMode) {
|
|
6385
|
-
case 'concise':
|
|
6386
|
-
return 'Concise thinking mode: respond directly with the final answer and skip <thinking> blocks unless the user explicitly asks for reasoning.';
|
|
6387
5642
|
case 'extended':
|
|
6388
5643
|
return [
|
|
6389
|
-
'Extended thinking mode
|
|
5644
|
+
'Extended thinking mode is enabled. Format every reply as:',
|
|
6390
5645
|
'<thinking>',
|
|
6391
|
-
'
|
|
5646
|
+
'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
|
|
6392
5647
|
'</thinking>',
|
|
6393
5648
|
'<response>',
|
|
6394
|
-
'Final answer with
|
|
5649
|
+
'Final answer with actionable next steps and any code/commands requested.',
|
|
6395
5650
|
'</response>',
|
|
6396
5651
|
].join('\n');
|
|
6397
5652
|
case 'balanced':
|
|
6398
5653
|
default:
|
|
6399
|
-
|
|
6400
|
-
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.';
|
|
5654
|
+
return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
|
|
6401
5655
|
}
|
|
6402
5656
|
}
|
|
6403
5657
|
buildDisplayMetadata(metadata) {
|
|
@@ -6406,58 +5660,6 @@ What's the next action?`;
|
|
|
6406
5660
|
contextWindowTokens: this.activeContextWindowTokens,
|
|
6407
5661
|
};
|
|
6408
5662
|
}
|
|
6409
|
-
presentThoughtProcess(thinking, metadata, options) {
|
|
6410
|
-
if (!thinking || this.hasShownThoughtProcess) {
|
|
6411
|
-
return;
|
|
6412
|
-
}
|
|
6413
|
-
const summary = this.extractThoughtSummary(thinking);
|
|
6414
|
-
if (summary) {
|
|
6415
|
-
display.updateThinking(`💭 ${summary}`);
|
|
6416
|
-
this.latestThoughtSummary = summary;
|
|
6417
|
-
this.streamingStatusDetail = summary;
|
|
6418
|
-
this.terminalInput.recordRecentAction(`💭 ${summary}`);
|
|
6419
|
-
this.rebuildStreamingStatusLabel();
|
|
6420
|
-
this.refreshStatusLine(true);
|
|
6421
|
-
}
|
|
6422
|
-
const shouldRender = !options?.wasStreamed || options?.renderWhenStreamed;
|
|
6423
|
-
if (shouldRender) {
|
|
6424
|
-
this.renderAssistantContent('thought', thinking, { ...metadata, isFinal: false });
|
|
6425
|
-
}
|
|
6426
|
-
this.hasShownThoughtProcess = true;
|
|
6427
|
-
}
|
|
6428
|
-
renderToolUsageSummary(metadata) {
|
|
6429
|
-
const since = this.lastToolSummaryRenderedAt ?? this.lastRequestStartedAt ?? Date.now();
|
|
6430
|
-
const operations = this.uiAdapter.getToolOperationsSince(since);
|
|
6431
|
-
if (!operations.length) {
|
|
6432
|
-
return;
|
|
6433
|
-
}
|
|
6434
|
-
let summaryLine = compactRenderer.formatCompactLine(operations, {
|
|
6435
|
-
showIcons: true,
|
|
6436
|
-
showTimings: true,
|
|
6437
|
-
});
|
|
6438
|
-
const warnings = this.uiAdapter.getRecentWarnings(since);
|
|
6439
|
-
if (warnings.length) {
|
|
6440
|
-
const limited = warnings.slice(0, 3);
|
|
6441
|
-
const formattedWarnings = limited
|
|
6442
|
-
.map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
|
|
6443
|
-
.join(' • ');
|
|
6444
|
-
summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
|
|
6445
|
-
}
|
|
6446
|
-
if (!summaryLine.trim()) {
|
|
6447
|
-
this.lastToolSummaryRenderedAt = Date.now();
|
|
6448
|
-
return;
|
|
6449
|
-
}
|
|
6450
|
-
this.lastToolSummaryRenderedAt =
|
|
6451
|
-
operations[operations.length - 1]?.startedAt ?? Date.now();
|
|
6452
|
-
const blockMetadata = { ...metadata, isFinal: true };
|
|
6453
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
6454
|
-
this.renderAssistantBlock('tools', summaryLine, blockMetadata);
|
|
6455
|
-
return;
|
|
6456
|
-
}
|
|
6457
|
-
const header = this.formatAssistantHeader('tools', blockMetadata);
|
|
6458
|
-
const block = `\n${header}\n${summaryLine}\n\n`;
|
|
6459
|
-
this.enqueueAssistantStream(block);
|
|
6460
|
-
}
|
|
6461
5663
|
handleContextTelemetry(metadata, displayMetadata) {
|
|
6462
5664
|
if (!metadata.isFinal) {
|
|
6463
5665
|
return null;
|
|
@@ -6818,7 +6020,7 @@ What's the next action?`;
|
|
|
6818
6020
|
}
|
|
6819
6021
|
handleAgentSetupError(error, retryAction, providerOverride) {
|
|
6820
6022
|
this.pendingInteraction = null;
|
|
6821
|
-
const provider = providerOverride
|
|
6023
|
+
const provider = providerOverride ?? this.sessionState.provider;
|
|
6822
6024
|
const apiKeyIssue = detectApiKeyError(error, provider);
|
|
6823
6025
|
if (apiKeyIssue) {
|
|
6824
6026
|
this.handleApiKeyIssue(apiKeyIssue, retryAction);
|
|
@@ -6833,14 +6035,13 @@ What's the next action?`;
|
|
|
6833
6035
|
const detail = detailText ? ` Error: ${detailText}` : '';
|
|
6834
6036
|
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
6835
6037
|
const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
|
|
6836
|
-
this.finalizeAssistantStream();
|
|
6837
6038
|
display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
|
|
6039
|
+
this.finishStreamingFormatter('Stream interrupted - retrying without streaming');
|
|
6838
6040
|
this.startStreamingHeartbeat('Fallback in progress');
|
|
6839
6041
|
this.requestPromptRefresh(true);
|
|
6840
6042
|
}
|
|
6841
6043
|
handleProviderError(error, retryAction) {
|
|
6842
|
-
const
|
|
6843
|
-
const apiKeyIssue = detectApiKeyError(error, providerHint);
|
|
6044
|
+
const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
|
|
6844
6045
|
if (!apiKeyIssue) {
|
|
6845
6046
|
return false;
|
|
6846
6047
|
}
|
|
@@ -6849,19 +6050,13 @@ What's the next action?`;
|
|
|
6849
6050
|
}
|
|
6850
6051
|
handleApiKeyIssue(info, retryAction) {
|
|
6851
6052
|
const secret = info.secret ?? null;
|
|
6852
|
-
const providerLabel = info.provider
|
|
6853
|
-
? this.providerLabel(info.provider)
|
|
6854
|
-
: secret?.providers?.length
|
|
6855
|
-
? this.providerLabel(secret.providers[0])
|
|
6856
|
-
: null;
|
|
6857
|
-
const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
|
|
6858
|
-
this.apiKeyGateActive = !!secret;
|
|
6053
|
+
const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
|
|
6859
6054
|
if (!secret) {
|
|
6860
6055
|
this.pendingSecretRetry = null;
|
|
6861
6056
|
const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
|
|
6862
6057
|
const baseMessage = info.type === 'missing'
|
|
6863
|
-
? `An API key is required before using ${
|
|
6864
|
-
: `API authentication failed for ${
|
|
6058
|
+
? `An API key is required before using ${providerLabel}.`
|
|
6059
|
+
: `API authentication failed for ${providerLabel}.`;
|
|
6865
6060
|
display.showWarning(`${baseMessage} ${guidance}`.trim());
|
|
6866
6061
|
return;
|
|
6867
6062
|
}
|
|
@@ -6870,8 +6065,8 @@ What's the next action?`;
|
|
|
6870
6065
|
display.showWarning(info.message.trim());
|
|
6871
6066
|
}
|
|
6872
6067
|
const prefix = isMissing
|
|
6873
|
-
? `${secret.label} is required before you can use ${
|
|
6874
|
-
: `${secret.label} appears to be invalid for ${
|
|
6068
|
+
? `${secret.label} is required before you can use ${providerLabel}.`
|
|
6069
|
+
: `${secret.label} appears to be invalid for ${providerLabel}.`;
|
|
6875
6070
|
display.showWarning(prefix);
|
|
6876
6071
|
this.pendingSecretRetry = retryAction ?? null;
|
|
6877
6072
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
@@ -6885,7 +6080,7 @@ What's the next action?`;
|
|
|
6885
6080
|
else {
|
|
6886
6081
|
lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
|
|
6887
6082
|
}
|
|
6888
|
-
lines.push(`
|
|
6083
|
+
lines.push(`Tip: run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
|
|
6889
6084
|
display.showSystemMessage(lines.join('\n'));
|
|
6890
6085
|
}
|
|
6891
6086
|
colorizeDropdownLine(text, index) {
|
|
@@ -6910,22 +6105,34 @@ What's the next action?`;
|
|
|
6910
6105
|
/**
|
|
6911
6106
|
* Build the session banner with comprehensive feature status.
|
|
6912
6107
|
*/
|
|
6913
|
-
|
|
6914
|
-
|
|
6108
|
+
buildBanner() {
|
|
6109
|
+
const terminalWidth = output.columns ?? 100;
|
|
6110
|
+
const width = Math.min(terminalWidth - 4, 110);
|
|
6111
|
+
// Collect tool categories for display
|
|
6112
|
+
const toolCategories = this.collectToolCategories();
|
|
6113
|
+
// Load feature flags for banner display
|
|
6114
|
+
const featureFlags = loadFeatureFlags();
|
|
6115
|
+
return renderSessionFrame({
|
|
6915
6116
|
profileLabel: this.profileLabel,
|
|
6916
6117
|
profileName: this.profile,
|
|
6917
6118
|
model: this.sessionState.model,
|
|
6918
6119
|
provider: this.sessionState.provider,
|
|
6919
6120
|
workspace: this.workingDir,
|
|
6920
6121
|
version: this.version,
|
|
6921
|
-
};
|
|
6922
|
-
}
|
|
6923
|
-
buildBanner() {
|
|
6924
|
-
const terminalWidth = output.columns ?? 100;
|
|
6925
|
-
const width = Math.min(terminalWidth - 4, 110);
|
|
6926
|
-
return renderSessionFrame({
|
|
6927
|
-
...this.getSessionFrameProps(),
|
|
6928
6122
|
width,
|
|
6123
|
+
features: {
|
|
6124
|
+
verification: this.verificationEnabled,
|
|
6125
|
+
autoContinue: this.autoContinueEnabled,
|
|
6126
|
+
thinkingMode: this.thinkingMode,
|
|
6127
|
+
plugins: this._enabledPlugins,
|
|
6128
|
+
tools: toolCategories,
|
|
6129
|
+
sessionId: this.activeSessionId ?? undefined,
|
|
6130
|
+
// Include feature flags
|
|
6131
|
+
alphaZeroDual: featureFlags.alphaZeroDual,
|
|
6132
|
+
autoCompact: featureFlags.autoCompact,
|
|
6133
|
+
mcpEnabled: featureFlags.mcpEnabled,
|
|
6134
|
+
metrics: featureFlags.metrics,
|
|
6135
|
+
},
|
|
6929
6136
|
});
|
|
6930
6137
|
}
|
|
6931
6138
|
/**
|
|
@@ -6956,21 +6163,6 @@ What's the next action?`;
|
|
|
6956
6163
|
}
|
|
6957
6164
|
return categories;
|
|
6958
6165
|
}
|
|
6959
|
-
buildFeatureStatusSnapshot() {
|
|
6960
|
-
const featureFlags = loadFeatureFlags();
|
|
6961
|
-
const toolCategories = this.collectToolCategories();
|
|
6962
|
-
const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
|
|
6963
|
-
const pluginCount = this._enabledPlugins.length;
|
|
6964
|
-
return {
|
|
6965
|
-
pluginCount: pluginCount > 0 ? pluginCount : undefined,
|
|
6966
|
-
toolCount: toolCount > 0 ? toolCount : undefined,
|
|
6967
|
-
sessionId: this.activeSessionId,
|
|
6968
|
-
mcpEnabled: featureFlags.mcpEnabled,
|
|
6969
|
-
metricsEnabled: featureFlags.metrics,
|
|
6970
|
-
autoCompact: featureFlags.autoCompact,
|
|
6971
|
-
dualMode: featureFlags.alphaZeroDual,
|
|
6972
|
-
};
|
|
6973
|
-
}
|
|
6974
6166
|
/**
|
|
6975
6167
|
* Extract category from tool name.
|
|
6976
6168
|
*/
|
|
@@ -7034,7 +6226,8 @@ What's the next action?`;
|
|
|
7034
6226
|
return;
|
|
7035
6227
|
}
|
|
7036
6228
|
this.refreshContextGauge();
|
|
7037
|
-
// Banner is
|
|
6229
|
+
// Banner is no longer stored in display - it was streamed as content
|
|
6230
|
+
// Model/provider changes are visible in the control bar
|
|
7038
6231
|
if (!this.isProcessing) {
|
|
7039
6232
|
this.setIdleStatus();
|
|
7040
6233
|
}
|
|
@@ -7339,6 +6532,20 @@ What's the next action?`;
|
|
|
7339
6532
|
];
|
|
7340
6533
|
display.showSystemMessage(lines.join('\n'));
|
|
7341
6534
|
}
|
|
6535
|
+
/**
|
|
6536
|
+
* Set the cached provider status for unified status bar display.
|
|
6537
|
+
* Called once at startup after checking providers.
|
|
6538
|
+
*/
|
|
6539
|
+
setProviderStatus(providers) {
|
|
6540
|
+
this.cachedProviderStatus = providers;
|
|
6541
|
+
}
|
|
6542
|
+
/**
|
|
6543
|
+
* Show the unified status bar (Claude Code style).
|
|
6544
|
+
* Displays provider indicators and ready hints before the prompt.
|
|
6545
|
+
*/
|
|
6546
|
+
showUnifiedStatusBar() {
|
|
6547
|
+
display.showUnifiedStatusBar(this.cachedProviderStatus);
|
|
6548
|
+
}
|
|
7342
6549
|
}
|
|
7343
6550
|
function setsEqual(first, second) {
|
|
7344
6551
|
if (first.size !== second.size) {
|