erosolar-cli 1.7.410 → 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/askUserCapability.d.ts.map +1 -1
- package/dist/capabilities/askUserCapability.js +64 -10
- package/dist/capabilities/askUserCapability.js.map +1 -1
- package/dist/capabilities/toolRegistry.d.ts +2 -0
- package/dist/capabilities/toolRegistry.d.ts.map +1 -1
- package/dist/capabilities/toolRegistry.js +40 -5
- package/dist/capabilities/toolRegistry.js.map +1 -1
- package/dist/contracts/agent-profiles.schema.json +5 -5
- package/dist/contracts/agent-schemas.json +6 -16
- package/dist/contracts/schemas/agent.schema.json +1 -5
- package/dist/contracts/schemas/tool-selection.schema.json +1 -7
- package/dist/contracts/tools.schema.json +80 -207
- package/dist/contracts/unified-schema.json +4 -5
- package/dist/contracts/v1/agent.d.ts +0 -3
- package/dist/contracts/v1/agent.d.ts.map +1 -1
- package/dist/contracts/v1/provider.d.ts +1 -2
- package/dist/contracts/v1/provider.d.ts.map +1 -1
- package/dist/contracts/v1/toolAccess.d.ts +1 -1
- package/dist/contracts/v1/toolAccess.d.ts.map +1 -1
- package/dist/core/agent.d.ts +1 -7
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +2 -131
- package/dist/core/agent.js.map +1 -1
- package/dist/core/alphaZeroEngine.d.ts +0 -8
- package/dist/core/alphaZeroEngine.d.ts.map +1 -1
- package/dist/core/alphaZeroEngine.js +35 -149
- package/dist/core/alphaZeroEngine.js.map +1 -1
- package/dist/core/alphaZeroOrchestrator.d.ts +0 -17
- package/dist/core/alphaZeroOrchestrator.d.ts.map +1 -1
- package/dist/core/alphaZeroOrchestrator.js +8 -95
- package/dist/core/alphaZeroOrchestrator.js.map +1 -1
- package/dist/core/claudeCodeFeatures.d.ts +2 -1
- package/dist/core/claudeCodeFeatures.d.ts.map +1 -1
- package/dist/core/claudeCodeFeatures.js +2 -1
- package/dist/core/claudeCodeFeatures.js.map +1 -1
- package/dist/core/cliTestHarness.d.ts +0 -5
- package/dist/core/cliTestHarness.d.ts.map +1 -1
- package/dist/core/cliTestHarness.js +3 -14
- package/dist/core/cliTestHarness.js.map +1 -1
- package/dist/core/contextManager.d.ts +0 -30
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +5 -87
- package/dist/core/contextManager.js.map +1 -1
- package/dist/core/contextWindow.d.ts +4 -4
- package/dist/core/contextWindow.js +9 -9
- package/dist/core/contextWindow.js.map +1 -1
- package/dist/core/modelDiscovery.js +3 -3
- package/dist/core/modelDiscovery.js.map +1 -1
- package/dist/core/preferences.d.ts +2 -3
- package/dist/core/preferences.d.ts.map +1 -1
- package/dist/core/preferences.js +11 -18
- package/dist/core/preferences.js.map +1 -1
- package/dist/core/secretStore.d.ts.map +1 -1
- package/dist/core/secretStore.js +31 -0
- package/dist/core/secretStore.js.map +1 -1
- package/dist/core/toolPreconditions.d.ts.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/headless/headlessApp.d.ts.map +1 -1
- package/dist/headless/headlessApp.js +6 -22
- package/dist/headless/headlessApp.js.map +1 -1
- package/dist/plugins/providers/google/index.js +3 -2
- package/dist/plugins/providers/google/index.js.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.d.ts.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.js +6 -60
- package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
- package/dist/runtime/agentController.d.ts.map +1 -1
- package/dist/runtime/agentController.js +6 -27
- package/dist/runtime/agentController.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +30 -79
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +726 -1511
- 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.map +1 -1
- package/dist/shell/updateManager.js +17 -1
- 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/interactionTools.d.ts.map +1 -1
- package/dist/tools/interactionTools.js +82 -15
- package/dist/tools/interactionTools.js.map +1 -1
- package/dist/tools/learnTools.d.ts +0 -2
- package/dist/tools/learnTools.d.ts.map +1 -1
- package/dist/tools/learnTools.js +81 -29
- package/dist/tools/learnTools.js.map +1 -1
- package/dist/tools/localExplore.d.ts.map +1 -1
- package/dist/tools/localExplore.js +1 -0
- package/dist/tools/localExplore.js.map +1 -1
- package/dist/tools/notebookEditTools.js.map +1 -1
- package/dist/tools/repoChecksTools.js +3 -4
- package/dist/tools/repoChecksTools.js.map +1 -1
- package/dist/tools/searchTools.js +0 -4
- package/dist/tools/searchTools.js.map +1 -1
- package/dist/tools/softwareEngineeringTools.d.ts.map +1 -1
- package/dist/tools/softwareEngineeringTools.js +0 -1
- package/dist/tools/softwareEngineeringTools.js.map +1 -1
- package/dist/tools/webTools.d.ts.map +1 -1
- package/dist/tools/webTools.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +17 -54
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +90 -378
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/display.d.ts +38 -19
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +311 -96
- package/dist/ui/display.js.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.d.ts.map +1 -1
- package/dist/ui/orchestration/UIUpdateCoordinator.js +3 -5
- package/dist/ui/orchestration/UIUpdateCoordinator.js.map +1 -1
- package/dist/ui/shortcutsHelp.d.ts +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 +3 -2
- package/dist/ui/unified/index.d.ts.map +1 -1
- package/dist/ui/unified/index.js +1 -0
- package/dist/ui/unified/index.js.map +1 -1
- package/dist/ui/unified/layout.d.ts +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,310 +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;
|
|
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);
|
|
1349
1164
|
return;
|
|
1350
1165
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
this.assistantStreamHeaderShown = false;
|
|
1355
|
-
this.assistantStreamBuffer = '';
|
|
1356
|
-
this.assistantStreamMetadata = metadata;
|
|
1357
|
-
this.assistantStreamHadContent = false;
|
|
1358
|
-
}
|
|
1359
|
-
streamAssistantChunk(chunk, channel) {
|
|
1360
|
-
if (!chunk) {
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
if (!this.assistantStreamActive) {
|
|
1364
|
-
this.startAssistantStream({
|
|
1365
|
-
contextWindowTokens: this.activeContextWindowTokens,
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
// Agent wraps reasoning chunks in <thinking> tags via openThinking()/closeThinking()
|
|
1369
|
-
// Always use tag parsing to properly handle the tags
|
|
1370
|
-
this.processAssistantStreamChunk(chunk);
|
|
1371
|
-
}
|
|
1372
|
-
finalizeAssistantStream() {
|
|
1373
|
-
if (!this.assistantStreamActive) {
|
|
1374
|
-
return;
|
|
1375
|
-
}
|
|
1376
|
-
if (this.assistantStreamBuffer) {
|
|
1377
|
-
this.writeAssistantStream(this.assistantStreamBuffer);
|
|
1378
|
-
this.assistantStreamBuffer = '';
|
|
1379
|
-
}
|
|
1380
|
-
this.assistantStreamActive = false;
|
|
1381
|
-
this.assistantStreamPhase = null;
|
|
1382
|
-
this.assistantStreamHeaderShown = false;
|
|
1383
|
-
this.assistantStreamMetadata = undefined;
|
|
1384
|
-
}
|
|
1385
|
-
processAssistantStreamChunk(chunk) {
|
|
1386
|
-
this.assistantStreamBuffer += chunk;
|
|
1387
|
-
const tagRegex = /<(\/?)(thinking|response)>/i;
|
|
1388
|
-
while (this.assistantStreamBuffer.length > 0) {
|
|
1389
|
-
const match = tagRegex.exec(this.assistantStreamBuffer);
|
|
1390
|
-
if (!match || match.index === undefined) {
|
|
1391
|
-
// No more full tags - flush what we can but keep any partial tag prefix buffered
|
|
1392
|
-
const { flushable, remainder } = this.splitAssistantStreamRemainder(this.assistantStreamBuffer);
|
|
1393
|
-
if (flushable) {
|
|
1394
|
-
this.writeAssistantStream(flushable);
|
|
1395
|
-
}
|
|
1396
|
-
this.assistantStreamBuffer = remainder;
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
const tagIndex = match.index;
|
|
1400
|
-
const beforeTag = this.assistantStreamBuffer.slice(0, tagIndex);
|
|
1401
|
-
if (beforeTag) {
|
|
1402
|
-
this.writeAssistantStream(beforeTag);
|
|
1403
|
-
}
|
|
1404
|
-
// Advance buffer past the tag
|
|
1405
|
-
this.assistantStreamBuffer = this.assistantStreamBuffer.slice(tagIndex + match[0].length);
|
|
1406
|
-
const isClosing = match[1] === '/';
|
|
1407
|
-
const rawTagType = (match[2] ?? '').toLowerCase();
|
|
1408
|
-
// Map 'thinking' to 'thought' for AssistantBlockType compatibility
|
|
1409
|
-
const tagType = (rawTagType === 'thinking' ? 'thought' : rawTagType);
|
|
1410
|
-
if (isClosing) {
|
|
1411
|
-
if (this.assistantStreamPhase === tagType) {
|
|
1412
|
-
this.assistantStreamPhase = null;
|
|
1413
|
-
this.assistantStreamHeaderShown = false;
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
else {
|
|
1417
|
-
this.setAssistantStreamPhase(tagType);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
writeAssistantStream(content) {
|
|
1422
|
-
if (!content) {
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
if (content.trim().length > 0) {
|
|
1426
|
-
this.assistantStreamHadContent = true;
|
|
1427
|
-
}
|
|
1428
|
-
// If no explicit phase has been set yet, keep the stream labeled as "thought"
|
|
1429
|
-
// until a <response> tag arrives so operators see thinking first.
|
|
1430
|
-
this.setAssistantStreamPhase(this.assistantStreamPhase ?? 'thought');
|
|
1431
|
-
if (this.assistantStreamPhase) {
|
|
1432
|
-
this.renderAssistantStreamHeader(this.assistantStreamPhase);
|
|
1433
|
-
}
|
|
1434
|
-
let output = content;
|
|
1435
|
-
if (this.assistantStreamPhase === 'thought') {
|
|
1436
|
-
output = this.formatThoughtDisplay(content, { streaming: true });
|
|
1437
|
-
}
|
|
1438
|
-
this.enqueueAssistantStream(output);
|
|
1439
|
-
}
|
|
1440
|
-
setAssistantStreamPhase(phase) {
|
|
1441
|
-
if (this.assistantStreamPhase === phase) {
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
this.assistantStreamPhase = phase;
|
|
1445
|
-
this.assistantStreamHeaderShown = false;
|
|
1446
|
-
if (phase) {
|
|
1447
|
-
this.renderAssistantStreamHeader(phase);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
renderAssistantStreamHeader(type) {
|
|
1451
|
-
if (this.assistantStreamHeaderShown) {
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
const header = this.formatAssistantHeader(type, this.assistantStreamMetadata);
|
|
1455
|
-
if (header.trim()) {
|
|
1456
|
-
this.enqueueAssistantStream(`\n${header}\n`);
|
|
1457
|
-
}
|
|
1458
|
-
this.assistantStreamHeaderShown = true;
|
|
1459
|
-
}
|
|
1460
|
-
splitAssistantStreamRemainder(buffer) {
|
|
1461
|
-
if (!buffer) {
|
|
1462
|
-
return { flushable: '', remainder: '' };
|
|
1463
|
-
}
|
|
1464
|
-
const tags = ['<thinking>', '</thinking>', '<response>', '</response>'];
|
|
1465
|
-
let longest = '';
|
|
1466
|
-
for (const tag of tags) {
|
|
1467
|
-
for (let i = 1; i < tag.length; i += 1) {
|
|
1468
|
-
const prefix = tag.slice(0, i);
|
|
1469
|
-
if (buffer.endsWith(prefix) && prefix.length > longest.length) {
|
|
1470
|
-
longest = prefix;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
if (!longest) {
|
|
1475
|
-
return { flushable: buffer, remainder: '' };
|
|
1476
|
-
}
|
|
1477
|
-
return {
|
|
1478
|
-
flushable: buffer.slice(0, -longest.length),
|
|
1479
|
-
remainder: longest,
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
enqueueAssistantStream(content) {
|
|
1483
|
-
if (!content) {
|
|
1484
|
-
return;
|
|
1485
|
-
}
|
|
1486
|
-
const run = () => this.writeAssistantBlock(content);
|
|
1487
|
-
this.uiUpdates.enqueue({
|
|
1488
|
-
lane: 'stream',
|
|
1489
|
-
mode: ['streaming', 'processing', 'idle'],
|
|
1490
|
-
priority: 'high',
|
|
1491
|
-
description: 'assistant stream flush',
|
|
1492
|
-
run,
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
writeAssistantBlock(content) {
|
|
1496
|
-
if (!content) {
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
// Ensure assistant block writes are serialized with terminal input renders
|
|
1500
|
-
writeLock.safeWrite(() => {
|
|
1501
|
-
this.terminalInput.streamContent(content);
|
|
1502
|
-
});
|
|
1503
|
-
}
|
|
1504
|
-
renderAssistantBlock(type, content, metadata) {
|
|
1505
|
-
if (!this.assistantBlocksEnabled || !this.assistantBlockRenderer) {
|
|
1506
|
-
return;
|
|
1507
|
-
}
|
|
1508
|
-
this.assistantBlockRenderer.renderBlock(type, content, metadata);
|
|
1509
|
-
// Ensure the prompt stays pinned below freshly written blocks
|
|
1510
|
-
this.renderPromptArea();
|
|
1511
|
-
}
|
|
1512
|
-
renderAssistantContent(type, content, metadata) {
|
|
1513
|
-
const normalized = content.replace(/\r\n/g, '\n').trim();
|
|
1514
|
-
if (!normalized) {
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
const formatted = type === 'thought' ? this.formatThoughtDisplay(normalized) : normalized;
|
|
1518
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1519
|
-
this.renderAssistantBlock(type, formatted, metadata);
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
this.renderAssistantFallback(type, formatted, metadata);
|
|
1523
|
-
}
|
|
1524
|
-
renderAssistantFallback(type, content, metadata) {
|
|
1525
|
-
const header = this.formatAssistantHeader(type, metadata);
|
|
1526
|
-
const compact = content.trimEnd().replace(/\n{3,}/g, '\n\n');
|
|
1527
|
-
const block = `\n${header}\n${compact}\n\n`;
|
|
1528
|
-
this.enqueueAssistantStream(block);
|
|
1529
|
-
}
|
|
1530
|
-
formatThoughtDisplay(content, options = {}) {
|
|
1531
|
-
const normalized = content.replace(/\r\n/g, '\n');
|
|
1532
|
-
if (options.streaming) {
|
|
1533
|
-
return formatThinkingContent(normalized);
|
|
1534
|
-
}
|
|
1535
|
-
const lines = normalized.split('\n');
|
|
1536
|
-
return lines
|
|
1537
|
-
.map((line, index) => {
|
|
1538
|
-
const prefix = index === 0 ? theme.info(icons.action) : theme.ui.muted(icons.subaction);
|
|
1539
|
-
const trimmed = line.trim();
|
|
1540
|
-
const body = trimmed ? formatThinkingContent(trimmed) : '';
|
|
1541
|
-
return body ? `${prefix} ${body}` : `${prefix}`;
|
|
1542
|
-
})
|
|
1543
|
-
.join('\n');
|
|
1544
|
-
}
|
|
1545
|
-
formatAssistantHeader(type, metadata) {
|
|
1546
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
1547
|
-
return this.assistantBlockRenderer.formatHeader(type, metadata);
|
|
1548
|
-
}
|
|
1549
|
-
const badges = [];
|
|
1550
|
-
badges.push(this.buildAssistantTypeBadge(type));
|
|
1551
|
-
const timestamp = new Date().toLocaleTimeString('en-US', {
|
|
1552
|
-
hour12: false,
|
|
1553
|
-
hour: '2-digit',
|
|
1554
|
-
minute: '2-digit',
|
|
1555
|
-
second: '2-digit',
|
|
1556
|
-
});
|
|
1557
|
-
badges.push({ text: timestamp, style: 'muted', icon: '🕑' });
|
|
1558
|
-
const total = metadata?.usage ? this.totalTokens(metadata.usage) : null;
|
|
1559
|
-
const windowTokens = metadata?.contextWindowTokens;
|
|
1560
|
-
if (typeof total === 'number') {
|
|
1561
|
-
badges.push({ text: `${total} tok`, style: 'muted', icon: '⎍' });
|
|
1562
|
-
}
|
|
1563
|
-
if (typeof total === 'number' && typeof windowTokens === 'number' && windowTokens > 0) {
|
|
1564
|
-
const percentage = Math.round((total / windowTokens) * 100);
|
|
1565
|
-
const style = percentage > 85 ? 'error' : percentage > 70 ? 'warning' : 'info';
|
|
1566
|
-
badges.push({ text: `${percentage}% ctx`, style, icon: '⊛' });
|
|
1567
|
-
}
|
|
1568
|
-
const elapsedBadge = this.buildElapsedBadge(metadata);
|
|
1569
|
-
if (elapsedBadge) {
|
|
1570
|
-
badges.push(elapsedBadge);
|
|
1571
|
-
}
|
|
1572
|
-
return compactRenderer.formatBadges(badges, theme.ui.muted(' │ '));
|
|
1573
|
-
}
|
|
1574
|
-
buildAssistantTypeBadge(type) {
|
|
1575
|
-
switch (type) {
|
|
1576
|
-
case 'thought':
|
|
1577
|
-
return { text: 'Thought', style: 'info', icon: '💭' };
|
|
1578
|
-
case 'tools':
|
|
1579
|
-
return { text: 'Tools', style: 'primary', icon: '🛠' };
|
|
1580
|
-
case 'response':
|
|
1581
|
-
default:
|
|
1582
|
-
return { text: 'Response', style: 'success', icon: '💬' };
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
buildElapsedBadge(metadata) {
|
|
1586
|
-
const elapsed = metadata?.elapsedMs;
|
|
1587
|
-
if (typeof elapsed !== 'number' || elapsed <= 0) {
|
|
1588
|
-
return null;
|
|
1589
|
-
}
|
|
1590
|
-
const formatted = elapsed < 1000 ? `${Math.round(elapsed)}ms` : `${Math.round(elapsed / 1000)}s`;
|
|
1591
|
-
return { text: formatted, style: 'muted', icon: '⏱' };
|
|
1592
|
-
}
|
|
1593
|
-
isStreamingUiActive() {
|
|
1594
|
-
return this.streamingHeartbeatStart !== null;
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Render the prompt/control bar. During streaming, rely on the streaming frame
|
|
1598
|
-
* renderer and enqueue through the UIUpdateCoordinator to avoid fighting the
|
|
1599
|
-
* scroll region or duplicating the prompt.
|
|
1600
|
-
*/
|
|
1601
|
-
renderPromptArea(force = false) {
|
|
1602
|
-
if (this.isStreamingUiActive()) {
|
|
1603
|
-
this.uiUpdates.enqueue({
|
|
1604
|
-
lane: 'prompt',
|
|
1605
|
-
mode: ['streaming', 'processing'],
|
|
1606
|
-
coalesceKey: 'prompt:streaming-frame',
|
|
1607
|
-
description: 'render streaming prompt frame',
|
|
1608
|
-
run: () => {
|
|
1609
|
-
if (force) {
|
|
1610
|
-
this.terminalInput.renderStreamingFrame(true);
|
|
1611
|
-
return;
|
|
1612
|
-
}
|
|
1613
|
-
this.terminalInput.renderStreamingFrame();
|
|
1614
|
-
},
|
|
1615
|
-
});
|
|
1616
|
-
return;
|
|
1617
|
-
}
|
|
1618
|
-
if (force) {
|
|
1619
|
-
this.terminalInput.forceRender();
|
|
1620
|
-
}
|
|
1621
|
-
else {
|
|
1622
|
-
this.terminalInput.render();
|
|
1623
|
-
}
|
|
1166
|
+
writeLock.withLock(() => {
|
|
1167
|
+
process.stdout.write(content);
|
|
1168
|
+
}, 'interactiveShell.stdout');
|
|
1624
1169
|
}
|
|
1625
1170
|
/**
|
|
1626
1171
|
* Refresh the status line in the persistent input area.
|
|
@@ -1638,23 +1183,19 @@ export class InteractiveShell {
|
|
|
1638
1183
|
// Surface meta header (elapsed + context usage) above the divider
|
|
1639
1184
|
// Use streaming elapsed time if available, otherwise fall back to status line state
|
|
1640
1185
|
let elapsedSeconds = null;
|
|
1641
|
-
|
|
1642
|
-
if (this.streamingHeartbeatStart && shouldShowElapsed) {
|
|
1186
|
+
if (this.streamingHeartbeatStart) {
|
|
1643
1187
|
// Actively streaming - compute live elapsed
|
|
1644
1188
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1645
1189
|
}
|
|
1646
|
-
else if (
|
|
1190
|
+
else if (this.lastStreamingElapsedSeconds !== null) {
|
|
1647
1191
|
// Just finished streaming - use preserved final time
|
|
1648
1192
|
elapsedSeconds = this.lastStreamingElapsedSeconds;
|
|
1649
1193
|
}
|
|
1650
|
-
else if (
|
|
1194
|
+
else if (this.statusLineState) {
|
|
1651
1195
|
// Fallback to status line state elapsed
|
|
1652
1196
|
elapsedSeconds = Math.max(0, Math.floor((Date.now() - this.statusLineState.startedAt) / 1000));
|
|
1653
1197
|
}
|
|
1654
|
-
const
|
|
1655
|
-
const thinkingMs = hasThoughtSummary && display.isSpinnerActive()
|
|
1656
|
-
? display.getThinkingElapsedMs()
|
|
1657
|
-
: null;
|
|
1198
|
+
const thinkingMs = display.isSpinnerActive() ? display.getThinkingElapsedMs() : null;
|
|
1658
1199
|
const tokensUsed = this.latestTokenUsage.used;
|
|
1659
1200
|
const tokenLimit = this.latestTokenUsage.limit ?? this.activeContextWindowTokens;
|
|
1660
1201
|
this.terminalInput.setMetaStatus({
|
|
@@ -1662,7 +1203,7 @@ export class InteractiveShell {
|
|
|
1662
1203
|
tokensUsed,
|
|
1663
1204
|
tokenLimit,
|
|
1664
1205
|
thinkingMs,
|
|
1665
|
-
thinkingHasContent:
|
|
1206
|
+
thinkingHasContent: display.isSpinnerActive(),
|
|
1666
1207
|
});
|
|
1667
1208
|
// Keep model/provider visible in the controls bar
|
|
1668
1209
|
this.terminalInput.setModelContext({
|
|
@@ -1670,7 +1211,7 @@ export class InteractiveShell {
|
|
|
1670
1211
|
provider: this.providerLabel(this.sessionState.provider),
|
|
1671
1212
|
});
|
|
1672
1213
|
if (forceRender) {
|
|
1673
|
-
this.
|
|
1214
|
+
this.terminalInput.render();
|
|
1674
1215
|
}
|
|
1675
1216
|
}
|
|
1676
1217
|
/**
|
|
@@ -1707,7 +1248,7 @@ export class InteractiveShell {
|
|
|
1707
1248
|
* Ensure the terminal input is ready for interactive input.
|
|
1708
1249
|
*/
|
|
1709
1250
|
ensureReadlineReady() {
|
|
1710
|
-
this.
|
|
1251
|
+
this.terminalInput.render();
|
|
1711
1252
|
}
|
|
1712
1253
|
/**
|
|
1713
1254
|
* Log user prompt to the scroll region so it's part of the conversation flow.
|
|
@@ -1729,7 +1270,7 @@ export class InteractiveShell {
|
|
|
1729
1270
|
}
|
|
1730
1271
|
requestPromptRefresh(force = false) {
|
|
1731
1272
|
if (force) {
|
|
1732
|
-
this.
|
|
1273
|
+
this.terminalInput.forceRender();
|
|
1733
1274
|
return;
|
|
1734
1275
|
}
|
|
1735
1276
|
if (this.promptRefreshTimer) {
|
|
@@ -1737,7 +1278,7 @@ export class InteractiveShell {
|
|
|
1737
1278
|
}
|
|
1738
1279
|
this.promptRefreshTimer = setTimeout(() => {
|
|
1739
1280
|
this.promptRefreshTimer = null;
|
|
1740
|
-
this.
|
|
1281
|
+
this.terminalInput.render();
|
|
1741
1282
|
}, 48);
|
|
1742
1283
|
}
|
|
1743
1284
|
clearPromptRefreshTimer() {
|
|
@@ -1746,53 +1287,35 @@ export class InteractiveShell {
|
|
|
1746
1287
|
this.promptRefreshTimer = null;
|
|
1747
1288
|
}
|
|
1748
1289
|
}
|
|
1749
|
-
async withStreamingUi(label, run) {
|
|
1750
|
-
if (this.isStreamingUiActive()) {
|
|
1751
|
-
return run();
|
|
1752
|
-
}
|
|
1753
|
-
this.resetAssistantStreamTracking();
|
|
1754
|
-
this.terminalInput.setStreaming(true);
|
|
1755
|
-
this.startStreamingHeartbeat(label);
|
|
1756
|
-
try {
|
|
1757
|
-
return await run();
|
|
1758
|
-
}
|
|
1759
|
-
finally {
|
|
1760
|
-
this.stopStreamingHeartbeat();
|
|
1761
|
-
this.terminalInput.setStreaming(false);
|
|
1762
|
-
const nextMode = this.isProcessing ? 'processing' : 'idle';
|
|
1763
|
-
this.uiUpdates.setMode(nextMode);
|
|
1764
|
-
if (nextMode === 'processing') {
|
|
1765
|
-
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
1290
|
startStreamingHeartbeat(label = 'Streaming') {
|
|
1770
|
-
this.stopStreamingHeartbeat(
|
|
1291
|
+
this.stopStreamingHeartbeat();
|
|
1771
1292
|
// Enter global streaming mode - blocks all non-streaming UI output
|
|
1772
1293
|
enterStreamingMode();
|
|
1773
|
-
this.streamingStatusBase = label;
|
|
1774
|
-
this.streamingStatusDetail = null;
|
|
1775
|
-
this.latestThoughtSummary = null;
|
|
1776
|
-
this.lastStreamingElapsedSeconds = null;
|
|
1777
1294
|
// Set up scroll region for streaming content
|
|
1778
1295
|
this.terminalInput.enterStreamingScrollRegion();
|
|
1779
1296
|
this.uiUpdates.setMode('streaming');
|
|
1780
1297
|
this.streamingHeartbeatStart = Date.now();
|
|
1781
1298
|
this.streamingHeartbeatFrame = 0;
|
|
1782
|
-
this.
|
|
1299
|
+
const initialFrame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1300
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${initialFrame} ${label}`, 0);
|
|
1301
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1783
1302
|
this.refreshStatusLine(true);
|
|
1784
1303
|
// Periodically refresh the pinned input/status region while streaming so
|
|
1785
1304
|
// elapsed time remains visible without interrupting the scroll region.
|
|
1786
1305
|
this.uiUpdates.startHeartbeat('streaming', {
|
|
1787
1306
|
intervalMs: 1000,
|
|
1788
1307
|
lane: 'heartbeat',
|
|
1789
|
-
priority: 'high',
|
|
1790
1308
|
mode: ['streaming', 'processing'],
|
|
1791
1309
|
coalesceKey: 'streaming:heartbeat',
|
|
1792
1310
|
run: () => {
|
|
1311
|
+
const elapsedSeconds = this.streamingHeartbeatStart
|
|
1312
|
+
? Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000))
|
|
1313
|
+
: 0;
|
|
1793
1314
|
this.streamingHeartbeatFrame =
|
|
1794
1315
|
(this.streamingHeartbeatFrame + 1) % STREAMING_SPINNER_FRAMES.length;
|
|
1795
|
-
this.
|
|
1316
|
+
const frame = STREAMING_SPINNER_FRAMES[this.streamingHeartbeatFrame];
|
|
1317
|
+
this.streamingStatusLabel = this.buildStreamingStatus(`${frame} ${label}`, elapsedSeconds);
|
|
1318
|
+
display.updateStreamingStatus(this.streamingStatusLabel);
|
|
1796
1319
|
// Update parallel agent display during streaming
|
|
1797
1320
|
const manager = getParallelAgentManager();
|
|
1798
1321
|
if (manager.isRunning()) {
|
|
@@ -1803,13 +1326,7 @@ export class InteractiveShell {
|
|
|
1803
1326
|
},
|
|
1804
1327
|
});
|
|
1805
1328
|
}
|
|
1806
|
-
stopStreamingHeartbeat(
|
|
1807
|
-
const skipRender = !!options.skipRender;
|
|
1808
|
-
const streamingActive = this.isStreamingUiActive();
|
|
1809
|
-
const scrollRegionActive = this.terminalInput.isScrollRegionActive();
|
|
1810
|
-
if (!streamingActive && !scrollRegionActive) {
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1329
|
+
stopStreamingHeartbeat() {
|
|
1813
1330
|
// Exit global streaming mode - allows UI to render again
|
|
1814
1331
|
exitStreamingMode();
|
|
1815
1332
|
// Preserve final elapsed time before clearing heartbeat start
|
|
@@ -1817,44 +1334,51 @@ export class InteractiveShell {
|
|
|
1817
1334
|
this.lastStreamingElapsedSeconds = Math.max(0, Math.floor((Date.now() - this.streamingHeartbeatStart) / 1000));
|
|
1818
1335
|
}
|
|
1819
1336
|
// Exit scroll region mode
|
|
1820
|
-
this.terminalInput.exitStreamingScrollRegion(
|
|
1337
|
+
this.terminalInput.exitStreamingScrollRegion();
|
|
1821
1338
|
this.uiUpdates.stopHeartbeat('streaming');
|
|
1822
1339
|
this.streamingHeartbeatStart = null;
|
|
1823
1340
|
this.streamingHeartbeatFrame = 0;
|
|
1824
1341
|
this.streamingStatusLabel = null;
|
|
1825
|
-
this.streamingStatusBase = null;
|
|
1826
|
-
this.streamingStatusDetail = null;
|
|
1827
|
-
this.latestThoughtSummary = null;
|
|
1828
1342
|
// Clear streaming label specifically (keeps override and main status if set)
|
|
1829
1343
|
this.terminalInput.setStreamingLabel(null);
|
|
1830
1344
|
// Clear streaming status from display
|
|
1831
1345
|
display.updateStreamingStatus(null);
|
|
1832
1346
|
// Force refresh to update the input area now that streaming has ended
|
|
1833
|
-
|
|
1834
|
-
this.refreshStatusLine(true);
|
|
1835
|
-
}
|
|
1347
|
+
this.refreshStatusLine(true);
|
|
1836
1348
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
if (
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
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);
|
|
1847
1365
|
}
|
|
1848
|
-
return `${prefix} ${parts.join(' · ')}`.trim();
|
|
1849
1366
|
}
|
|
1850
|
-
|
|
1851
|
-
if (this.
|
|
1367
|
+
finishStreamingFormatter(note) {
|
|
1368
|
+
if (!this.streamingFormatter) {
|
|
1852
1369
|
return;
|
|
1853
1370
|
}
|
|
1854
|
-
const
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
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();
|
|
1858
1382
|
}
|
|
1859
1383
|
formatElapsedShort(seconds) {
|
|
1860
1384
|
if (seconds < 60) {
|
|
@@ -1871,7 +1395,7 @@ export class InteractiveShell {
|
|
|
1871
1395
|
else {
|
|
1872
1396
|
this.setIdleStatus();
|
|
1873
1397
|
}
|
|
1874
|
-
this.
|
|
1398
|
+
this.terminalInput.render();
|
|
1875
1399
|
}
|
|
1876
1400
|
enqueueFollowUpAction(action) {
|
|
1877
1401
|
this.followUpQueue.push(action);
|
|
@@ -1890,21 +1414,14 @@ export class InteractiveShell {
|
|
|
1890
1414
|
this.refreshQueueIndicators();
|
|
1891
1415
|
this.scheduleQueueProcessing();
|
|
1892
1416
|
// Re-show the prompt so user can continue typing more follow-ups
|
|
1893
|
-
this.
|
|
1417
|
+
this.terminalInput.render();
|
|
1894
1418
|
}
|
|
1895
1419
|
scheduleQueueProcessing() {
|
|
1896
1420
|
if (!this.followUpQueue.length) {
|
|
1897
1421
|
this.refreshQueueIndicators();
|
|
1898
1422
|
return;
|
|
1899
1423
|
}
|
|
1900
|
-
if (this.apiKeyGateActive) {
|
|
1901
|
-
this.refreshQueueIndicators();
|
|
1902
|
-
return;
|
|
1903
|
-
}
|
|
1904
1424
|
queueMicrotask(() => {
|
|
1905
|
-
if (this.apiKeyGateActive) {
|
|
1906
|
-
return;
|
|
1907
|
-
}
|
|
1908
1425
|
void this.processQueuedActions();
|
|
1909
1426
|
});
|
|
1910
1427
|
}
|
|
@@ -1912,12 +1429,12 @@ export class InteractiveShell {
|
|
|
1912
1429
|
* Process queued follow-up actions.
|
|
1913
1430
|
*/
|
|
1914
1431
|
async processQueuedActions() {
|
|
1915
|
-
if (this.
|
|
1432
|
+
if (this.isDrainingQueue || this.isProcessing || !this.followUpQueue.length) {
|
|
1916
1433
|
return;
|
|
1917
1434
|
}
|
|
1918
1435
|
this.isDrainingQueue = true;
|
|
1919
1436
|
try {
|
|
1920
|
-
while (!this.isProcessing &&
|
|
1437
|
+
while (!this.isProcessing && this.followUpQueue.length) {
|
|
1921
1438
|
const next = this.followUpQueue.shift();
|
|
1922
1439
|
const remaining = this.followUpQueue.length;
|
|
1923
1440
|
const label = next.type === 'continuous' ? 'continuous command' : 'follow-up';
|
|
@@ -1952,12 +1469,12 @@ export class InteractiveShell {
|
|
|
1952
1469
|
}
|
|
1953
1470
|
if (lower === 'clear') {
|
|
1954
1471
|
display.clear();
|
|
1955
|
-
this.
|
|
1472
|
+
this.terminalInput.render();
|
|
1956
1473
|
return;
|
|
1957
1474
|
}
|
|
1958
1475
|
if (lower === 'help') {
|
|
1959
1476
|
this.showHelp();
|
|
1960
|
-
this.
|
|
1477
|
+
this.terminalInput.render();
|
|
1961
1478
|
return;
|
|
1962
1479
|
}
|
|
1963
1480
|
if (trimmed.startsWith('/')) {
|
|
@@ -1967,12 +1484,12 @@ export class InteractiveShell {
|
|
|
1967
1484
|
// Check for continuous/infinite loop commands
|
|
1968
1485
|
if (this.isContinuousCommand(trimmed)) {
|
|
1969
1486
|
await this.processContinuousRequest(trimmed);
|
|
1970
|
-
this.
|
|
1487
|
+
this.terminalInput.render();
|
|
1971
1488
|
return;
|
|
1972
1489
|
}
|
|
1973
1490
|
// Direct execution for all inputs, including multi-line pastes
|
|
1974
1491
|
await this.processRequest(trimmed);
|
|
1975
|
-
this.
|
|
1492
|
+
this.terminalInput.render();
|
|
1976
1493
|
}
|
|
1977
1494
|
/**
|
|
1978
1495
|
* Check if the command is a continuous/infinite loop command
|
|
@@ -2010,153 +1527,6 @@ export class InteractiveShell {
|
|
|
2010
1527
|
];
|
|
2011
1528
|
return patterns.some(pattern => pattern.test(lower));
|
|
2012
1529
|
}
|
|
2013
|
-
isDifficultProblem(input) {
|
|
2014
|
-
const normalized = input.toLowerCase();
|
|
2015
|
-
const wordCount = normalized.split(/\s+/).filter(Boolean).length;
|
|
2016
|
-
if (normalized.length > 600 || wordCount > 80) {
|
|
2017
|
-
return true;
|
|
2018
|
-
}
|
|
2019
|
-
const signals = [
|
|
2020
|
-
'root cause',
|
|
2021
|
-
'postmortem',
|
|
2022
|
-
'crash',
|
|
2023
|
-
'incident',
|
|
2024
|
-
'outage',
|
|
2025
|
-
'optimiz',
|
|
2026
|
-
'performance',
|
|
2027
|
-
'throughput',
|
|
2028
|
-
'latency',
|
|
2029
|
-
'scalab',
|
|
2030
|
-
'architecture',
|
|
2031
|
-
'rewrite',
|
|
2032
|
-
'migration',
|
|
2033
|
-
'refactor',
|
|
2034
|
-
'reverse engineer',
|
|
2035
|
-
'security',
|
|
2036
|
-
'exploit',
|
|
2037
|
-
'injection',
|
|
2038
|
-
'vulnerability',
|
|
2039
|
-
'compliance',
|
|
2040
|
-
'multi-step',
|
|
2041
|
-
'complex',
|
|
2042
|
-
'difficult',
|
|
2043
|
-
'hard problem',
|
|
2044
|
-
'debug',
|
|
2045
|
-
'trace',
|
|
2046
|
-
'profil',
|
|
2047
|
-
'bottleneck',
|
|
2048
|
-
];
|
|
2049
|
-
return signals.some((signal) => normalized.includes(signal));
|
|
2050
|
-
}
|
|
2051
|
-
buildAlphaZeroPrompt(request, flaggedDifficult) {
|
|
2052
|
-
const playbook = [
|
|
2053
|
-
'AlphaZero RL MODE is ACTIVE. Operate as a self-play reinforcement loop.',
|
|
2054
|
-
flaggedDifficult
|
|
2055
|
-
? 'Treat this as a difficult, high-risk task and over-verify the result.'
|
|
2056
|
-
: 'Apply the reinforcement loop even if the task looks small.',
|
|
2057
|
-
'Follow this closed-loop playbook:',
|
|
2058
|
-
'- Draft two competing solution strategies and merge the strongest ideas before executing.',
|
|
2059
|
-
'- Execute with tools while logging decisions and evidence.',
|
|
2060
|
-
'- Self-critique and repair until the quality is excellent (aim ≥90/100).',
|
|
2061
|
-
'- Run full-lifecycle verification like a human reviewer: build/tests, manual sanity checks, edge cases, performance/safety/security probes, docs/UX/readiness notes.',
|
|
2062
|
-
'- Keep a verification ledger: each check with PASS/FAIL, evidence, and remaining risks. If anything fails, fix and re-verify before claiming completion.',
|
|
2063
|
-
'Finish with a concise sign-off that lists what was achieved and the proof of completion.',
|
|
2064
|
-
];
|
|
2065
|
-
return `${playbook.join('\n')}\n\nPrimary user request:\n${request.trim()}`;
|
|
2066
|
-
}
|
|
2067
|
-
buildRunLogExcerpt(startIndex) {
|
|
2068
|
-
const buffer = this.terminalInput.getScrollbackBuffer();
|
|
2069
|
-
const sinceStart = buffer.slice(Math.max(0, startIndex));
|
|
2070
|
-
const excerpt = sinceStart.slice(-200); // Cap prompt size
|
|
2071
|
-
return excerpt.join('\n').trim();
|
|
2072
|
-
}
|
|
2073
|
-
buildRunLogEntry(request, response, scrollbackStartIndex, meta) {
|
|
2074
|
-
return {
|
|
2075
|
-
id: ++this.runIdCounter,
|
|
2076
|
-
request,
|
|
2077
|
-
response,
|
|
2078
|
-
timestamp: new Date().toISOString(),
|
|
2079
|
-
alphaZero: meta.alphaZeroEngaged,
|
|
2080
|
-
difficult: meta.alphaZeroDifficult,
|
|
2081
|
-
failureType: meta.failureType,
|
|
2082
|
-
outputExcerpt: this.buildRunLogExcerpt(scrollbackStartIndex),
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
buildAlphaZeroReflectionPrompt(runLog, options) {
|
|
2086
|
-
const lines = [];
|
|
2087
|
-
lines.push('AlphaZero Post-Run Self-Reflection (erosolar-cli)');
|
|
2088
|
-
lines.push(`Timestamp: ${runLog.timestamp}`);
|
|
2089
|
-
lines.push(`AlphaZero: ${runLog.alphaZero ? 'on' : 'off'}${runLog.difficult ? ' | difficult' : ''}${runLog.failureType ? ` | signal: ${runLog.failureType}` : ''}`);
|
|
2090
|
-
lines.push('');
|
|
2091
|
-
lines.push('User request:');
|
|
2092
|
-
lines.push(runLog.request);
|
|
2093
|
-
lines.push('');
|
|
2094
|
-
lines.push('Previous run log excerpt:');
|
|
2095
|
-
lines.push(runLog.outputExcerpt || '[empty]');
|
|
2096
|
-
lines.push('');
|
|
2097
|
-
lines.push('Instructions:');
|
|
2098
|
-
lines.push('- Reflect on the log to spot erosolar-cli bugs, UX issues, or reliability gaps.');
|
|
2099
|
-
lines.push('- Propose and apply targeted fixes in this repository only (no user workspace edits).');
|
|
2100
|
-
lines.push('- Prefer small, test-backed changes; run any relevant checks you invoke.');
|
|
2101
|
-
lines.push('- Keep notes concise and finish with applied changes plus follow-ups.');
|
|
2102
|
-
if (options.autoChain) {
|
|
2103
|
-
lines.push('- Continue iterating automatically while meaningful improvements remain.');
|
|
2104
|
-
lines.push('- When no further improvements are possible, reply with NO_MORE_IMPROVEMENTS on its own line.');
|
|
2105
|
-
}
|
|
2106
|
-
return lines.join('\n');
|
|
2107
|
-
}
|
|
2108
|
-
maybeQueueAlphaZeroSelfReflection(runLog, alphaZeroEngaged, allowAutoChain) {
|
|
2109
|
-
if (!alphaZeroEngaged) {
|
|
2110
|
-
return;
|
|
2111
|
-
}
|
|
2112
|
-
if (!isErosolarRepo(this.workingDir)) {
|
|
2113
|
-
return;
|
|
2114
|
-
}
|
|
2115
|
-
if (!runLog.outputExcerpt) {
|
|
2116
|
-
return;
|
|
2117
|
-
}
|
|
2118
|
-
if (this.lastReflectedRunId === runLog.id) {
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
if (allowAutoChain) {
|
|
2122
|
-
if (!this.autoContinueEnabled) {
|
|
2123
|
-
return;
|
|
2124
|
-
}
|
|
2125
|
-
if (this.alphaZeroAutoImproveIterations >= this.alphaZeroAutoImproveMaxIterations) {
|
|
2126
|
-
display.showInfo('AlphaZero auto-improvement limit reached; stopping.');
|
|
2127
|
-
this.alphaZeroAutoImproveActive = false;
|
|
2128
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2129
|
-
return;
|
|
2130
|
-
}
|
|
2131
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2132
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
2133
|
-
}
|
|
2134
|
-
this.alphaZeroAutoImproveActive = true;
|
|
2135
|
-
this.alphaZeroAutoImproveIterations++;
|
|
2136
|
-
}
|
|
2137
|
-
const prompt = this.buildAlphaZeroReflectionPrompt(runLog, { autoChain: allowAutoChain });
|
|
2138
|
-
this.lastReflectedRunId = runLog.id;
|
|
2139
|
-
this.skipNextAutoReflection = !allowAutoChain; // Prevent reflection-on-reflection unless auto-chaining
|
|
2140
|
-
this.enqueueFollowUpAction({ type: 'request', text: prompt });
|
|
2141
|
-
display.showInfo(allowAutoChain
|
|
2142
|
-
? 'Auto AlphaZero self-improvement queued (auto-continue enabled).'
|
|
2143
|
-
: 'Queued AlphaZero self-reflection to improve erosolar-cli from the latest run log.');
|
|
2144
|
-
}
|
|
2145
|
-
shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain) {
|
|
2146
|
-
if (!this.alphaZeroAutoImproveActive) {
|
|
2147
|
-
return false;
|
|
2148
|
-
}
|
|
2149
|
-
if (!allowAutoChain) {
|
|
2150
|
-
return true;
|
|
2151
|
-
}
|
|
2152
|
-
if (!responseText) {
|
|
2153
|
-
return false;
|
|
2154
|
-
}
|
|
2155
|
-
const normalized = responseText.toLowerCase();
|
|
2156
|
-
return (normalized.includes('no_more_improvements') ||
|
|
2157
|
-
normalized.includes('no more improvements') ||
|
|
2158
|
-
normalized.includes('stop_auto_improve'));
|
|
2159
|
-
}
|
|
2160
1530
|
async handlePendingInteraction(input) {
|
|
2161
1531
|
if (!this.pendingInteraction) {
|
|
2162
1532
|
return false;
|
|
@@ -2164,7 +1534,7 @@ export class InteractiveShell {
|
|
|
2164
1534
|
switch (this.pendingInteraction.type) {
|
|
2165
1535
|
case 'model-loading':
|
|
2166
1536
|
display.showInfo('Still fetching model options. Please wait a moment.');
|
|
2167
|
-
this.
|
|
1537
|
+
this.terminalInput.render();
|
|
2168
1538
|
return true;
|
|
2169
1539
|
case 'model-provider':
|
|
2170
1540
|
await this.handleModelProviderSelection(input);
|
|
@@ -2195,213 +1565,166 @@ export class InteractiveShell {
|
|
|
2195
1565
|
const [command] = input.split(/\s+/);
|
|
2196
1566
|
if (!command) {
|
|
2197
1567
|
display.showWarning('Enter a slash command.');
|
|
2198
|
-
this.
|
|
1568
|
+
this.terminalInput.render();
|
|
2199
1569
|
return;
|
|
2200
1570
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
break;
|
|
2357
|
-
case '/init':
|
|
2358
|
-
this.handleInitCommand();
|
|
2359
|
-
break;
|
|
2360
|
-
case '/compact':
|
|
2361
|
-
await this.handleCompactCommand();
|
|
2362
|
-
break;
|
|
2363
|
-
default:
|
|
2364
|
-
if (!(await this.tryCustomSlashCommand(command, input))) {
|
|
2365
|
-
display.showWarning(`Unknown command "${command}".`);
|
|
2366
|
-
}
|
|
2367
|
-
break;
|
|
2368
|
-
}
|
|
2369
|
-
};
|
|
2370
|
-
const streamingUiActive = this.isStreamingUiActive();
|
|
2371
|
-
const captureOptions = streamingUiActive
|
|
2372
|
-
? { includeStreaming: false, suppressTypes: ['normal'] }
|
|
2373
|
-
: undefined;
|
|
2374
|
-
let capturedOutput = '';
|
|
2375
|
-
try {
|
|
2376
|
-
const { output: outputBuffer } = await display.captureOutput(runCommand, captureOptions);
|
|
2377
|
-
capturedOutput = outputBuffer;
|
|
2378
|
-
}
|
|
2379
|
-
catch (error) {
|
|
2380
|
-
capturedOutput = error?.capturedOutput ?? capturedOutput;
|
|
2381
|
-
if (streamingUiActive) {
|
|
2382
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2383
|
-
const { output: errorOutput } = await display.captureOutput(() => display.showError(message, error), { includeStreaming: false, suppressTypes: ['normal'] });
|
|
2384
|
-
capturedOutput = capturedOutput || errorOutput || message;
|
|
2385
|
-
}
|
|
2386
|
-
else {
|
|
2387
|
-
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
const panelContent = this.buildInlineCommandPanel(command, capturedOutput);
|
|
2391
|
-
this.terminalInput.setInlineCommandPanel(panelContent);
|
|
2392
|
-
this.renderPromptArea();
|
|
2393
|
-
}
|
|
2394
|
-
buildInlineCommandPanel(command, output) {
|
|
2395
|
-
const normalized = output ? output.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trimEnd() : '';
|
|
2396
|
-
if (!normalized) {
|
|
2397
|
-
return null;
|
|
2398
|
-
}
|
|
2399
|
-
const header = theme.ui.muted(command);
|
|
2400
|
-
const body = normalized.split('\n');
|
|
2401
|
-
if (body.length === 1) {
|
|
2402
|
-
return [`${header} ${body[0] ?? ''}`.trimEnd()];
|
|
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;
|
|
2403
1726
|
}
|
|
2404
|
-
|
|
1727
|
+
this.terminalInput.render();
|
|
2405
1728
|
}
|
|
2406
1729
|
async tryCustomSlashCommand(command, fullInput) {
|
|
2407
1730
|
const custom = this.customCommandMap.get(command);
|
|
@@ -2438,13 +1761,11 @@ export class InteractiveShell {
|
|
|
2438
1761
|
` ${theme.info('Option+V')} ${theme.ui.muted('Toggle verification')}`,
|
|
2439
1762
|
` ${theme.info('Option+C')} ${theme.ui.muted('Toggle auto-continue')}`,
|
|
2440
1763
|
` ${theme.info('Option+T')} ${theme.ui.muted('Cycle thinking mode')}`,
|
|
2441
|
-
` ${theme.info('Option+A')} ${theme.ui.muted('Toggle AlphaZero RL mode')}`,
|
|
2442
1764
|
` ${theme.info('Option+E')} ${theme.ui.muted('Toggle edit permission mode')}`,
|
|
2443
1765
|
` ${theme.info('Option+X')} ${theme.ui.muted('Clear/compact context')}`,
|
|
2444
1766
|
'',
|
|
2445
1767
|
theme.bold(' Navigation'),
|
|
2446
1768
|
` ${theme.info('PageUp/PageDown')} ${theme.ui.muted('Scroll through output')}`,
|
|
2447
|
-
` ${theme.info('Escape')} ${theme.ui.muted('Stop streaming')}`,
|
|
2448
1769
|
` ${theme.info('Ctrl+C')} ${theme.ui.muted('Clear input or interrupt')}`,
|
|
2449
1770
|
` ${theme.info('Up/Down')} ${theme.ui.muted('Navigate command history')}`,
|
|
2450
1771
|
'',
|
|
@@ -2542,66 +1863,6 @@ export class InteractiveShell {
|
|
|
2542
1863
|
display.showWarning('Workspace snapshot refreshed, but the agent failed to rebuild. Run /doctor for details.');
|
|
2543
1864
|
}
|
|
2544
1865
|
}
|
|
2545
|
-
showContextSummaryLog(input) {
|
|
2546
|
-
const agent = this.agent;
|
|
2547
|
-
if (!agent) {
|
|
2548
|
-
display.showWarning('No active agent session. Start one to inspect context summaries.');
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
const contextManager = agent.getContextManager();
|
|
2552
|
-
if (!contextManager?.getSummaryLog) {
|
|
2553
|
-
display.showWarning('Context summary logging is unavailable in this session.');
|
|
2554
|
-
return;
|
|
2555
|
-
}
|
|
2556
|
-
const tokens = input.trim().split(/\s+/).slice(1);
|
|
2557
|
-
let limit = 5;
|
|
2558
|
-
for (const token of tokens) {
|
|
2559
|
-
if (!token)
|
|
2560
|
-
continue;
|
|
2561
|
-
const normalized = token.toLowerCase();
|
|
2562
|
-
if (/^\d+$/.test(token)) {
|
|
2563
|
-
limit = Math.min(Math.max(parseInt(token, 10), 1), 20);
|
|
2564
|
-
}
|
|
2565
|
-
else if (normalized.startsWith('limit=')) {
|
|
2566
|
-
const value = parseInt(normalized.split('=')[1] ?? '', 10);
|
|
2567
|
-
if (!Number.isNaN(value)) {
|
|
2568
|
-
limit = Math.min(Math.max(value, 1), 20);
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
const entries = contextManager.getSummaryLog(limit);
|
|
2573
|
-
if (!entries.length) {
|
|
2574
|
-
display.showInfo('No context summaries captured yet.');
|
|
2575
|
-
return;
|
|
2576
|
-
}
|
|
2577
|
-
const lines = [];
|
|
2578
|
-
lines.push(`${theme.primary('Context summaries')} ${theme.ui.muted(`(latest ${entries.length})`)}`);
|
|
2579
|
-
for (const entry of entries) {
|
|
2580
|
-
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
2581
|
-
const headerParts = [
|
|
2582
|
-
theme.ui.muted(timestamp),
|
|
2583
|
-
theme.info(entry.method),
|
|
2584
|
-
`removed ${theme.error(String(entry.removed))}`,
|
|
2585
|
-
`kept ${theme.success(String(entry.preserved))}`,
|
|
2586
|
-
];
|
|
2587
|
-
if (entry.reason) {
|
|
2588
|
-
headerParts.push(theme.ui.muted(entry.reason));
|
|
2589
|
-
}
|
|
2590
|
-
if (entry.model) {
|
|
2591
|
-
headerParts.push(theme.ui.muted(`model=${entry.model}`));
|
|
2592
|
-
}
|
|
2593
|
-
lines.push(headerParts.join(' · '));
|
|
2594
|
-
if (entry.summary) {
|
|
2595
|
-
const trimmed = entry.summary.length > 600 ? `${entry.summary.slice(0, 600)}…` : entry.summary;
|
|
2596
|
-
lines.push(trimmed);
|
|
2597
|
-
}
|
|
2598
|
-
else {
|
|
2599
|
-
lines.push(theme.ui.muted('(no summary text — simple prune)'));
|
|
2600
|
-
}
|
|
2601
|
-
lines.push(''); // spacer
|
|
2602
|
-
}
|
|
2603
|
-
display.showSystemMessage(lines.join('\n'));
|
|
2604
|
-
}
|
|
2605
1866
|
parseContextOverrideTokens(input) {
|
|
2606
1867
|
const overrides = {};
|
|
2607
1868
|
let hasOverride = false;
|
|
@@ -2761,11 +2022,11 @@ export class InteractiveShell {
|
|
|
2761
2022
|
handleThinkingCommand(input) {
|
|
2762
2023
|
const value = input.slice('/thinking'.length).trim().toLowerCase();
|
|
2763
2024
|
if (!value) {
|
|
2764
|
-
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]`);
|
|
2765
2026
|
return;
|
|
2766
2027
|
}
|
|
2767
|
-
if (value !== '
|
|
2768
|
-
display.showWarning('Usage: /thinking [
|
|
2028
|
+
if (value !== 'balanced' && value !== 'extended') {
|
|
2029
|
+
display.showWarning('Usage: /thinking [balanced|extended]');
|
|
2769
2030
|
return;
|
|
2770
2031
|
}
|
|
2771
2032
|
if (this.isProcessing) {
|
|
@@ -2778,30 +2039,11 @@ export class InteractiveShell {
|
|
|
2778
2039
|
this.resetChatBoxAfterModelSwap();
|
|
2779
2040
|
}
|
|
2780
2041
|
const descriptions = {
|
|
2781
|
-
concise: 'Hides internal reasoning and responds directly.',
|
|
2782
2042
|
balanced: 'Shows short thoughts only when helpful.',
|
|
2783
2043
|
extended: 'Always emits a <thinking> block before the final response.',
|
|
2784
2044
|
};
|
|
2785
2045
|
display.showInfo(`Thinking mode set to ${theme.info(value)} – ${descriptions[this.thinkingMode]}`);
|
|
2786
2046
|
}
|
|
2787
|
-
handleAlphaZeroCommand(input) {
|
|
2788
|
-
const value = input.slice('/alphazero'.length).trim().toLowerCase();
|
|
2789
|
-
if (!value || value === 'status') {
|
|
2790
|
-
const status = this.alphaZeroModeEnabled ? theme.success('on') : theme.ui.muted('off');
|
|
2791
|
-
const verification = this.verificationEnabled ? 'verification locked on' : 'verification optional';
|
|
2792
|
-
display.showInfo(`AlphaZero RL mode is ${status}. When enabled, difficult prompts use duel/self-critique and human-style verification (${verification}).`);
|
|
2793
|
-
return;
|
|
2794
|
-
}
|
|
2795
|
-
if (['on', 'enable', 'enabled'].includes(value)) {
|
|
2796
|
-
this.setAlphaZeroMode(true, 'command');
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
if (['off', 'disable', 'disabled'].includes(value)) {
|
|
2800
|
-
this.setAlphaZeroMode(false, 'command');
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
display.showWarning('Usage: /alphazero [on|off|status]');
|
|
2804
|
-
}
|
|
2805
2047
|
handleShortcutsCommand() {
|
|
2806
2048
|
// Display keyboard shortcuts help (Claude Code style)
|
|
2807
2049
|
display.showSystemMessage(formatShortcutsHelp());
|
|
@@ -2889,7 +2131,6 @@ export class InteractiveShell {
|
|
|
2889
2131
|
const updated = toggleFeatureFlag(matchedKey, newValue);
|
|
2890
2132
|
const status = updated[matchedKey] ? theme.success('enabled') : theme.ui.muted('disabled');
|
|
2891
2133
|
display.showInfo(`Feature "${FEATURE_FLAG_INFO[matchedKey].label}" is now ${status}.`);
|
|
2892
|
-
this.refreshFeatureStatusDisplay();
|
|
2893
2134
|
display.showInfo('Changes will take effect on next launch or after /features refresh.');
|
|
2894
2135
|
return;
|
|
2895
2136
|
}
|
|
@@ -2902,7 +2143,6 @@ export class InteractiveShell {
|
|
|
2902
2143
|
}
|
|
2903
2144
|
saveFeatureFlags(updated);
|
|
2904
2145
|
display.showInfo(`All features ${newValue ? theme.success('enabled') : theme.ui.muted('disabled')}.`);
|
|
2905
|
-
this.refreshFeatureStatusDisplay();
|
|
2906
2146
|
return;
|
|
2907
2147
|
}
|
|
2908
2148
|
else {
|
|
@@ -4075,7 +3315,9 @@ export class InteractiveShell {
|
|
|
4075
3315
|
}
|
|
4076
3316
|
display.showInfo(`Deleted session "${summary.title}".`);
|
|
4077
3317
|
if (this.activeSessionId === summary.id) {
|
|
4078
|
-
this.
|
|
3318
|
+
this.activeSessionId = null;
|
|
3319
|
+
this.activeSessionTitle = null;
|
|
3320
|
+
saveSessionPreferences({ lastSessionId: null });
|
|
4079
3321
|
}
|
|
4080
3322
|
}
|
|
4081
3323
|
newSessionCommand(title) {
|
|
@@ -4095,7 +3337,6 @@ export class InteractiveShell {
|
|
|
4095
3337
|
clearAutosaveSnapshot(this.profile);
|
|
4096
3338
|
display.showInfo('Started a new empty session.');
|
|
4097
3339
|
this.refreshContextGauge();
|
|
4098
|
-
this.refreshFeatureStatusDisplay();
|
|
4099
3340
|
}
|
|
4100
3341
|
toggleAutosaveCommand(value) {
|
|
4101
3342
|
if (!value) {
|
|
@@ -4150,6 +3391,7 @@ export class InteractiveShell {
|
|
|
4150
3391
|
lines.push(' /rewind code Rewind code only (keep conversation)');
|
|
4151
3392
|
lines.push(' /rewind conv Rewind conversation only (keep code)');
|
|
4152
3393
|
lines.push('');
|
|
3394
|
+
lines.push(theme.ui.muted('Tip: Press Esc+Esc for quick access to rewind menu'));
|
|
4153
3395
|
display.showSystemMessage(lines.join('\n'));
|
|
4154
3396
|
}
|
|
4155
3397
|
handleMemoryCommand(input) {
|
|
@@ -4168,6 +3410,7 @@ export class InteractiveShell {
|
|
|
4168
3410
|
lines.push(' - Use # prefix to quickly add notes to project memory');
|
|
4169
3411
|
lines.push(' - Import other files with @./relative/path syntax');
|
|
4170
3412
|
lines.push('');
|
|
3413
|
+
lines.push(theme.ui.muted('Tip: Create EROSOLAR.md with project coding standards for better results'));
|
|
4171
3414
|
display.showSystemMessage(lines.join('\n'));
|
|
4172
3415
|
}
|
|
4173
3416
|
handleVimCommand() {
|
|
@@ -4248,20 +3491,6 @@ export class InteractiveShell {
|
|
|
4248
3491
|
}
|
|
4249
3492
|
display.showSystemMessage(lines.join('\n'));
|
|
4250
3493
|
}
|
|
4251
|
-
async handleUpdateCommand() {
|
|
4252
|
-
display.showInfo('Checking for updates...');
|
|
4253
|
-
const lines = [];
|
|
4254
|
-
lines.push(theme.bold('Update Check'));
|
|
4255
|
-
lines.push('');
|
|
4256
|
-
lines.push(`Current version: ${this.version ?? 'unknown'}`);
|
|
4257
|
-
lines.push('');
|
|
4258
|
-
lines.push(theme.secondary('To update:'));
|
|
4259
|
-
lines.push(' npm install -g erosolar-cli@latest');
|
|
4260
|
-
lines.push(' or: npm update erosolar-cli');
|
|
4261
|
-
lines.push('');
|
|
4262
|
-
lines.push(theme.ui.muted('Auto-updates will be checked periodically.'));
|
|
4263
|
-
display.showSystemMessage(lines.join('\n'));
|
|
4264
|
-
}
|
|
4265
3494
|
handleClearCommand() {
|
|
4266
3495
|
if (this.agent) {
|
|
4267
3496
|
this.agent.clearHistory();
|
|
@@ -4270,7 +3499,7 @@ export class InteractiveShell {
|
|
|
4270
3499
|
display.clear();
|
|
4271
3500
|
clearAutosaveSnapshot(this.profile);
|
|
4272
3501
|
display.showInfo('Conversation cleared. Starting fresh.');
|
|
4273
|
-
this.
|
|
3502
|
+
this.terminalInput.render();
|
|
4274
3503
|
}
|
|
4275
3504
|
async handleResumeCommand(input) {
|
|
4276
3505
|
const tokens = input.split(/\s+/).slice(1);
|
|
@@ -4411,7 +3640,6 @@ export class InteractiveShell {
|
|
|
4411
3640
|
if (remember) {
|
|
4412
3641
|
saveSessionPreferences({ lastSessionId: summary?.id ?? null });
|
|
4413
3642
|
}
|
|
4414
|
-
this.refreshFeatureStatusDisplay();
|
|
4415
3643
|
}
|
|
4416
3644
|
resolveSessionBySelector(selector) {
|
|
4417
3645
|
const sessions = listSessions(this.profile);
|
|
@@ -4580,7 +3808,7 @@ export class InteractiveShell {
|
|
|
4580
3808
|
if (!providerOptions.length) {
|
|
4581
3809
|
display.showWarning('No providers are available.');
|
|
4582
3810
|
this.pendingInteraction = null;
|
|
4583
|
-
this.
|
|
3811
|
+
this.terminalInput.render();
|
|
4584
3812
|
return;
|
|
4585
3813
|
}
|
|
4586
3814
|
const lines = [
|
|
@@ -4601,7 +3829,7 @@ export class InteractiveShell {
|
|
|
4601
3829
|
catch (error) {
|
|
4602
3830
|
display.showError('Failed to load model list. Try again in a moment.', error);
|
|
4603
3831
|
this.pendingInteraction = null;
|
|
4604
|
-
this.
|
|
3832
|
+
this.terminalInput.render();
|
|
4605
3833
|
}
|
|
4606
3834
|
}
|
|
4607
3835
|
buildProviderOptions() {
|
|
@@ -4785,7 +4013,7 @@ export class InteractiveShell {
|
|
|
4785
4013
|
}
|
|
4786
4014
|
renderToolMenu(interaction) {
|
|
4787
4015
|
const lines = [
|
|
4788
|
-
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).'),
|
|
4789
4017
|
...interaction.options.map((option, index) => this.formatToolOptionLine(option, index, interaction.selection)),
|
|
4790
4018
|
'',
|
|
4791
4019
|
'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
|
|
@@ -4794,13 +4022,23 @@ export class InteractiveShell {
|
|
|
4794
4022
|
}
|
|
4795
4023
|
formatToolOptionLine(option, index, selection) {
|
|
4796
4024
|
const enabled = selection.has(option.id);
|
|
4797
|
-
const checkbox =
|
|
4025
|
+
const checkbox = option.locked
|
|
4026
|
+
? theme.primary('[✓]')
|
|
4027
|
+
: enabled
|
|
4028
|
+
? theme.primary('[x]')
|
|
4029
|
+
: theme.ui.muted('[ ]');
|
|
4798
4030
|
const details = [option.description];
|
|
4799
4031
|
if (option.requiresSecret) {
|
|
4800
4032
|
const hasSecret = Boolean(getSecretValue(option.requiresSecret));
|
|
4801
4033
|
const status = hasSecret ? theme.success('API key set') : theme.warning('API key missing');
|
|
4802
4034
|
details.push(status);
|
|
4803
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
|
+
}
|
|
4804
4042
|
const numberLabel = this.colorizeDropdownLine(`${index + 1}.`, index);
|
|
4805
4043
|
const optionLabel = this.colorizeDropdownLine(option.label, index);
|
|
4806
4044
|
const detailLine = this.colorizeDropdownLine(` ${details.join(' • ')}`, index);
|
|
@@ -5146,29 +4384,29 @@ export class InteractiveShell {
|
|
|
5146
4384
|
const trimmed = input.trim();
|
|
5147
4385
|
if (!trimmed) {
|
|
5148
4386
|
display.showWarning('Enter a number or type cancel.');
|
|
5149
|
-
this.
|
|
4387
|
+
this.terminalInput.render();
|
|
5150
4388
|
return;
|
|
5151
4389
|
}
|
|
5152
4390
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5153
4391
|
this.pendingInteraction = null;
|
|
5154
4392
|
display.showInfo('Model selection cancelled.');
|
|
5155
|
-
this.
|
|
4393
|
+
this.terminalInput.render();
|
|
5156
4394
|
return;
|
|
5157
4395
|
}
|
|
5158
4396
|
const choice = Number.parseInt(trimmed, 10);
|
|
5159
4397
|
if (!Number.isFinite(choice)) {
|
|
5160
4398
|
display.showWarning('Please enter a valid number.');
|
|
5161
|
-
this.
|
|
4399
|
+
this.terminalInput.render();
|
|
5162
4400
|
return;
|
|
5163
4401
|
}
|
|
5164
4402
|
const option = pending.options[choice - 1];
|
|
5165
4403
|
if (!option) {
|
|
5166
4404
|
display.showWarning('That option is not available.');
|
|
5167
|
-
this.
|
|
4405
|
+
this.terminalInput.render();
|
|
5168
4406
|
return;
|
|
5169
4407
|
}
|
|
5170
4408
|
this.showProviderModels(option);
|
|
5171
|
-
this.
|
|
4409
|
+
this.terminalInput.render();
|
|
5172
4410
|
}
|
|
5173
4411
|
async handleModelSelection(input) {
|
|
5174
4412
|
const pending = this.pendingInteraction;
|
|
@@ -5178,35 +4416,35 @@ export class InteractiveShell {
|
|
|
5178
4416
|
const trimmed = input.trim();
|
|
5179
4417
|
if (!trimmed) {
|
|
5180
4418
|
display.showWarning('Enter a number, type "back", or type "cancel".');
|
|
5181
|
-
this.
|
|
4419
|
+
this.terminalInput.render();
|
|
5182
4420
|
return;
|
|
5183
4421
|
}
|
|
5184
4422
|
if (trimmed.toLowerCase() === 'back') {
|
|
5185
4423
|
this.showModelMenu();
|
|
5186
|
-
this.
|
|
4424
|
+
this.terminalInput.render();
|
|
5187
4425
|
return;
|
|
5188
4426
|
}
|
|
5189
4427
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5190
4428
|
this.pendingInteraction = null;
|
|
5191
4429
|
display.showInfo('Model selection cancelled.');
|
|
5192
|
-
this.
|
|
4430
|
+
this.terminalInput.render();
|
|
5193
4431
|
return;
|
|
5194
4432
|
}
|
|
5195
4433
|
const choice = Number.parseInt(trimmed, 10);
|
|
5196
4434
|
if (!Number.isFinite(choice)) {
|
|
5197
4435
|
display.showWarning('Please enter a valid number.');
|
|
5198
|
-
this.
|
|
4436
|
+
this.terminalInput.render();
|
|
5199
4437
|
return;
|
|
5200
4438
|
}
|
|
5201
4439
|
const preset = pending.options[choice - 1];
|
|
5202
4440
|
if (!preset) {
|
|
5203
4441
|
display.showWarning('That option is not available.');
|
|
5204
|
-
this.
|
|
4442
|
+
this.terminalInput.render();
|
|
5205
4443
|
return;
|
|
5206
4444
|
}
|
|
5207
4445
|
this.pendingInteraction = null;
|
|
5208
4446
|
await this.applyModelPreset(preset);
|
|
5209
|
-
this.
|
|
4447
|
+
this.terminalInput.render();
|
|
5210
4448
|
}
|
|
5211
4449
|
async applyModelPreset(preset) {
|
|
5212
4450
|
try {
|
|
@@ -5239,30 +4477,30 @@ export class InteractiveShell {
|
|
|
5239
4477
|
const trimmed = input.trim();
|
|
5240
4478
|
if (!trimmed) {
|
|
5241
4479
|
display.showWarning('Enter a number or type cancel.');
|
|
5242
|
-
this.
|
|
4480
|
+
this.terminalInput.render();
|
|
5243
4481
|
return;
|
|
5244
4482
|
}
|
|
5245
4483
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5246
4484
|
this.pendingInteraction = null;
|
|
5247
4485
|
display.showInfo('Secret management cancelled.');
|
|
5248
|
-
this.
|
|
4486
|
+
this.terminalInput.render();
|
|
5249
4487
|
return;
|
|
5250
4488
|
}
|
|
5251
4489
|
const choice = Number.parseInt(trimmed, 10);
|
|
5252
4490
|
if (!Number.isFinite(choice)) {
|
|
5253
4491
|
display.showWarning('Please enter a valid number.');
|
|
5254
|
-
this.
|
|
4492
|
+
this.terminalInput.render();
|
|
5255
4493
|
return;
|
|
5256
4494
|
}
|
|
5257
4495
|
const secret = pending.options[choice - 1];
|
|
5258
4496
|
if (!secret) {
|
|
5259
4497
|
display.showWarning('That option is not available.');
|
|
5260
|
-
this.
|
|
4498
|
+
this.terminalInput.render();
|
|
5261
4499
|
return;
|
|
5262
4500
|
}
|
|
5263
4501
|
display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
|
|
5264
4502
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
5265
|
-
this.
|
|
4503
|
+
this.terminalInput.render();
|
|
5266
4504
|
}
|
|
5267
4505
|
async handleSecretInput(input) {
|
|
5268
4506
|
const pending = this.pendingInteraction;
|
|
@@ -5272,16 +4510,14 @@ export class InteractiveShell {
|
|
|
5272
4510
|
const trimmed = input.trim();
|
|
5273
4511
|
if (!trimmed) {
|
|
5274
4512
|
display.showWarning('Enter a value or type cancel.');
|
|
5275
|
-
this.
|
|
4513
|
+
this.terminalInput.render();
|
|
5276
4514
|
return;
|
|
5277
4515
|
}
|
|
5278
4516
|
if (trimmed.toLowerCase() === 'cancel') {
|
|
5279
4517
|
this.pendingInteraction = null;
|
|
5280
4518
|
this.pendingSecretRetry = null;
|
|
5281
|
-
this.apiKeyGateActive = false;
|
|
5282
4519
|
display.showInfo('Secret unchanged.');
|
|
5283
|
-
this.
|
|
5284
|
-
this.scheduleQueueProcessing();
|
|
4520
|
+
this.terminalInput.render();
|
|
5285
4521
|
return;
|
|
5286
4522
|
}
|
|
5287
4523
|
try {
|
|
@@ -5290,7 +4526,6 @@ export class InteractiveShell {
|
|
|
5290
4526
|
this.pendingInteraction = null;
|
|
5291
4527
|
const deferred = this.pendingSecretRetry;
|
|
5292
4528
|
this.pendingSecretRetry = null;
|
|
5293
|
-
this.apiKeyGateActive = false;
|
|
5294
4529
|
if (pending.secret.providers.includes(this.sessionState.provider)) {
|
|
5295
4530
|
if (this.rebuildAgent()) {
|
|
5296
4531
|
this.resetChatBoxAfterModelSwap();
|
|
@@ -5305,14 +4540,12 @@ export class InteractiveShell {
|
|
|
5305
4540
|
display.showError(message);
|
|
5306
4541
|
this.pendingInteraction = null;
|
|
5307
4542
|
this.pendingSecretRetry = null;
|
|
5308
|
-
this.apiKeyGateActive = false;
|
|
5309
4543
|
}
|
|
5310
|
-
this.
|
|
5311
|
-
this.scheduleQueueProcessing();
|
|
4544
|
+
this.terminalInput.render();
|
|
5312
4545
|
}
|
|
5313
|
-
async processRequest(
|
|
4546
|
+
async processRequest(request) {
|
|
5314
4547
|
if (this.isProcessing) {
|
|
5315
|
-
this.enqueueFollowUpAction({ type: 'request', text:
|
|
4548
|
+
this.enqueueFollowUpAction({ type: 'request', text: request });
|
|
5316
4549
|
return;
|
|
5317
4550
|
}
|
|
5318
4551
|
if (!this.agent && !this.rebuildAgent()) {
|
|
@@ -5323,57 +4556,29 @@ export class InteractiveShell {
|
|
|
5323
4556
|
if (!agent) {
|
|
5324
4557
|
return;
|
|
5325
4558
|
}
|
|
5326
|
-
this.
|
|
5327
|
-
this.resetAssistantStreamTracking();
|
|
5328
|
-
const alphaZeroEngaged = this.alphaZeroModeEnabled;
|
|
5329
|
-
const alphaZeroDifficult = alphaZeroEngaged ? this.isDifficultProblem(userRequest) : false;
|
|
5330
|
-
const requestForAgent = alphaZeroEngaged
|
|
5331
|
-
? this.buildAlphaZeroPrompt(userRequest, alphaZeroDifficult)
|
|
5332
|
-
: userRequest;
|
|
5333
|
-
const skipReflectionForThisRun = this.skipNextAutoReflection;
|
|
5334
|
-
this.skipNextAutoReflection = false;
|
|
5335
|
-
const scrollbackStartIndex = this.terminalInput.getScrollbackBuffer().length;
|
|
5336
|
-
const alphaZeroStatusId = 'alpha-zero';
|
|
5337
|
-
let alphaZeroStatusApplied = false;
|
|
5338
|
-
let alphaZeroTaskStarted = false;
|
|
5339
|
-
let alphaZeroTaskCompleted = false;
|
|
5340
|
-
if (alphaZeroEngaged) {
|
|
5341
|
-
const detail = alphaZeroDifficult ? 'Difficult request detected' : 'Reinforcement loop enabled';
|
|
5342
|
-
this.statusTracker.pushOverride(alphaZeroStatusId, 'AlphaZero RL active', {
|
|
5343
|
-
detail: `${detail} · full verification`,
|
|
5344
|
-
tone: 'info',
|
|
5345
|
-
});
|
|
5346
|
-
alphaZeroStatusApplied = true;
|
|
5347
|
-
display.showInfo(`AlphaZero RL mode engaged${alphaZeroDifficult ? ' for a difficult request' : ''}. Duel + self-critique with verification enabled.`);
|
|
5348
|
-
this.alphaZeroMetrics.startAlphaZeroTask(userRequest);
|
|
5349
|
-
alphaZeroTaskStarted = true;
|
|
5350
|
-
}
|
|
5351
|
-
this.logUserPrompt(userRequest);
|
|
4559
|
+
this.logUserPrompt(request);
|
|
5352
4560
|
this.isProcessing = true;
|
|
5353
4561
|
this.uiUpdates.setMode('processing');
|
|
5354
4562
|
this.terminalInput.setStreaming(true);
|
|
5355
4563
|
// Keep the persistent input/control bar active as we transition into streaming.
|
|
5356
|
-
this.
|
|
4564
|
+
this.terminalInput.forceRender();
|
|
5357
4565
|
const requestStartTime = Date.now(); // Alpha Zero 2 timing
|
|
5358
|
-
this.lastRequestStartedAt = requestStartTime;
|
|
5359
|
-
this.lastToolSummaryRenderedAt = null;
|
|
5360
4566
|
// Clear previous parallel agents and start fresh for new request
|
|
5361
4567
|
const parallelManager = getParallelAgentManager();
|
|
5362
4568
|
parallelManager.clear();
|
|
5363
4569
|
parallelManager.startBatch();
|
|
5364
4570
|
// AlphaZero: Track task for learning
|
|
5365
|
-
this.lastUserQuery =
|
|
5366
|
-
this.currentTaskType = classifyTaskType(
|
|
4571
|
+
this.lastUserQuery = request;
|
|
4572
|
+
this.currentTaskType = classifyTaskType(request);
|
|
5367
4573
|
this.currentToolCalls = [];
|
|
5368
4574
|
this.uiAdapter.startProcessing('Working on your request');
|
|
5369
4575
|
this.setProcessingStatus();
|
|
5370
4576
|
let responseText = '';
|
|
5371
|
-
let detectedFailure = null;
|
|
5372
|
-
let hadUnhandledError = false;
|
|
5373
4577
|
try {
|
|
5374
4578
|
// Start streaming - no header needed, the input area already provides context
|
|
5375
|
-
this.startStreamingHeartbeat(
|
|
5376
|
-
responseText = await agent.send(
|
|
4579
|
+
this.startStreamingHeartbeat('Streaming response');
|
|
4580
|
+
responseText = await agent.send(request, true);
|
|
4581
|
+
this.finishStreamingFormatter();
|
|
5377
4582
|
await this.awaitPendingCleanup();
|
|
5378
4583
|
this.captureHistorySnapshot();
|
|
5379
4584
|
this.autosaveIfEnabled();
|
|
@@ -5392,18 +4597,14 @@ export class InteractiveShell {
|
|
|
5392
4597
|
duration: 0,
|
|
5393
4598
|
}));
|
|
5394
4599
|
// AlphaZero: Check for failure in response
|
|
5395
|
-
|
|
4600
|
+
const failure = detectFailure(responseText, {
|
|
5396
4601
|
toolCalls: this.currentToolCalls,
|
|
5397
|
-
userMessage:
|
|
4602
|
+
userMessage: request,
|
|
5398
4603
|
});
|
|
5399
|
-
if (
|
|
5400
|
-
this.
|
|
5401
|
-
alphaZeroTaskCompleted = true;
|
|
5402
|
-
}
|
|
5403
|
-
if (detectedFailure) {
|
|
5404
|
-
this.lastFailure = detectedFailure;
|
|
4604
|
+
if (failure) {
|
|
4605
|
+
this.lastFailure = failure;
|
|
5405
4606
|
// Check if we have a recovery strategy
|
|
5406
|
-
const strategy = findRecoveryStrategy(
|
|
4607
|
+
const strategy = findRecoveryStrategy(failure);
|
|
5407
4608
|
if (strategy) {
|
|
5408
4609
|
display.showSystemMessage(`🔄 Found recovery strategy for this type of issue (success rate: ${Math.round(strategy.successRate * 100)}%)`);
|
|
5409
4610
|
}
|
|
@@ -5426,55 +4627,28 @@ export class InteractiveShell {
|
|
|
5426
4627
|
}
|
|
5427
4628
|
}
|
|
5428
4629
|
catch (error) {
|
|
5429
|
-
const handled = this.handleProviderError(error, () => this.processRequest(
|
|
5430
|
-
hadUnhandledError = !handled;
|
|
5431
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5432
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5433
|
-
alphaZeroTaskCompleted = true;
|
|
5434
|
-
}
|
|
4630
|
+
const handled = this.handleProviderError(error, () => this.processRequest(request));
|
|
5435
4631
|
if (!handled) {
|
|
5436
4632
|
// Pass full error object for enhanced formatting with stack trace
|
|
5437
4633
|
display.showError(error instanceof Error ? error.message : String(error), error);
|
|
5438
4634
|
}
|
|
5439
4635
|
}
|
|
5440
4636
|
finally {
|
|
5441
|
-
|
|
5442
|
-
alphaZeroEngaged,
|
|
5443
|
-
alphaZeroDifficult,
|
|
5444
|
-
failureType: detectedFailure?.type ?? (hadUnhandledError ? 'unhandled-error' : null),
|
|
5445
|
-
});
|
|
5446
|
-
this.lastRunLog = runLogEntry;
|
|
5447
|
-
const allowAutoChain = alphaZeroEngaged && this.autoContinueEnabled && isErosolarRepo(this.workingDir);
|
|
5448
|
-
let shouldStopAuto = false;
|
|
5449
|
-
if (this.alphaZeroAutoImproveActive) {
|
|
5450
|
-
shouldStopAuto = this.shouldStopAlphaZeroAutoImprove(responseText, allowAutoChain);
|
|
5451
|
-
if (shouldStopAuto) {
|
|
5452
|
-
this.alphaZeroAutoImproveActive = false;
|
|
5453
|
-
this.alphaZeroAutoImproveIterations = 0;
|
|
5454
|
-
}
|
|
5455
|
-
}
|
|
5456
|
-
if (!skipReflectionForThisRun && !shouldStopAuto) {
|
|
5457
|
-
this.maybeQueueAlphaZeroSelfReflection(runLogEntry, alphaZeroEngaged, allowAutoChain);
|
|
5458
|
-
}
|
|
5459
|
-
if (alphaZeroEngaged && alphaZeroStatusApplied) {
|
|
5460
|
-
this.statusTracker.clearOverride(alphaZeroStatusId);
|
|
5461
|
-
}
|
|
5462
|
-
if (alphaZeroEngaged && alphaZeroTaskStarted && !alphaZeroTaskCompleted) {
|
|
5463
|
-
this.alphaZeroMetrics.completeAlphaZeroTask(false);
|
|
5464
|
-
}
|
|
4637
|
+
this.finishStreamingFormatter();
|
|
5465
4638
|
display.stopThinking(false);
|
|
5466
4639
|
this.uiUpdates.setMode('processing');
|
|
5467
|
-
this.stopStreamingHeartbeat(
|
|
4640
|
+
this.stopStreamingHeartbeat();
|
|
5468
4641
|
this.isProcessing = false;
|
|
5469
4642
|
this.terminalInput.setStreaming(false);
|
|
5470
4643
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5471
4644
|
this.setIdleStatus();
|
|
4645
|
+
display.newLine();
|
|
5472
4646
|
this.updateStatusMessage(null);
|
|
5473
4647
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5474
4648
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5475
4649
|
// Claude Code style: New prompt naturally appears at bottom
|
|
5476
4650
|
this.ensureReadlineReady();
|
|
5477
|
-
this.
|
|
4651
|
+
this.terminalInput.render();
|
|
5478
4652
|
this.scheduleQueueProcessing();
|
|
5479
4653
|
this.refreshQueueIndicators();
|
|
5480
4654
|
}
|
|
@@ -5505,7 +4679,6 @@ export class InteractiveShell {
|
|
|
5505
4679
|
if (!agent) {
|
|
5506
4680
|
return;
|
|
5507
4681
|
}
|
|
5508
|
-
this.hasShownThoughtProcess = false;
|
|
5509
4682
|
this.isProcessing = true;
|
|
5510
4683
|
this.uiUpdates.setMode('processing');
|
|
5511
4684
|
this.terminalInput.setStreaming(true);
|
|
@@ -5546,8 +4719,6 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5546
4719
|
}
|
|
5547
4720
|
while (iteration < MAX_ITERATIONS) {
|
|
5548
4721
|
iteration++;
|
|
5549
|
-
this.hasShownThoughtProcess = false;
|
|
5550
|
-
this.resetAssistantStreamTracking();
|
|
5551
4722
|
display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
|
|
5552
4723
|
this.updateStatusMessage(`Working on iteration ${iteration}...`);
|
|
5553
4724
|
try {
|
|
@@ -5555,6 +4726,7 @@ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
|
|
|
5555
4726
|
display.showThinking('Responding...');
|
|
5556
4727
|
this.refreshStatusLine(true);
|
|
5557
4728
|
const response = await agent.send(currentPrompt, true);
|
|
4729
|
+
this.finishStreamingFormatter();
|
|
5558
4730
|
await this.awaitPendingCleanup();
|
|
5559
4731
|
this.captureHistorySnapshot();
|
|
5560
4732
|
this.autosaveIfEnabled();
|
|
@@ -5687,6 +4859,7 @@ What's the next action?`;
|
|
|
5687
4859
|
}
|
|
5688
4860
|
}
|
|
5689
4861
|
finally {
|
|
4862
|
+
this.finishStreamingFormatter();
|
|
5690
4863
|
const totalElapsed = Date.now() - overallStartTime;
|
|
5691
4864
|
const minutes = Math.floor(totalElapsed / 60000);
|
|
5692
4865
|
const seconds = Math.floor((totalElapsed % 60000) / 1000);
|
|
@@ -5700,6 +4873,10 @@ What's the next action?`;
|
|
|
5700
4873
|
this.uiAdapter.endProcessing('Ready for prompts');
|
|
5701
4874
|
this.setIdleStatus();
|
|
5702
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();
|
|
5703
4880
|
queueMicrotask(() => this.uiUpdates.setMode('idle'));
|
|
5704
4881
|
// CRITICAL: Ensure readline prompt is active for user input
|
|
5705
4882
|
// Claude Code style: New prompt naturally appears at bottom
|
|
@@ -5878,25 +5055,7 @@ What's the next action?`;
|
|
|
5878
5055
|
}
|
|
5879
5056
|
if (name === 'bash' || name === 'execute_bash') {
|
|
5880
5057
|
const command = String(entry.args['command'] ?? '').toLowerCase();
|
|
5881
|
-
|
|
5882
|
-
'npm test',
|
|
5883
|
-
'yarn test',
|
|
5884
|
-
'pnpm test',
|
|
5885
|
-
'bun test',
|
|
5886
|
-
'go test',
|
|
5887
|
-
'cargo test',
|
|
5888
|
-
'pytest',
|
|
5889
|
-
'python -m pytest',
|
|
5890
|
-
'tox',
|
|
5891
|
-
'mvn test',
|
|
5892
|
-
'./mvnw test',
|
|
5893
|
-
'gradle test',
|
|
5894
|
-
'./gradlew test',
|
|
5895
|
-
'dotnet test',
|
|
5896
|
-
'make test',
|
|
5897
|
-
'swift test',
|
|
5898
|
-
];
|
|
5899
|
-
return patterns.some((pattern) => command.includes(pattern));
|
|
5058
|
+
return command.includes('npm test') || command.includes('yarn test') || command.includes('pnpm test');
|
|
5900
5059
|
}
|
|
5901
5060
|
return false;
|
|
5902
5061
|
}
|
|
@@ -5914,170 +5073,310 @@ What's the next action?`;
|
|
|
5914
5073
|
const parts = [error?.stdout, error?.stderr, error?.message].filter((part) => typeof part === 'string' && part.trim());
|
|
5915
5074
|
return parts.join('\n').trim();
|
|
5916
5075
|
}
|
|
5917
|
-
|
|
5918
|
-
if (
|
|
5919
|
-
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;
|
|
5920
5100
|
}
|
|
5921
5101
|
const latestChange = this.getLatestFileChangeTimestamp();
|
|
5922
5102
|
if (!latestChange) {
|
|
5923
|
-
return
|
|
5103
|
+
return;
|
|
5924
5104
|
}
|
|
5925
5105
|
const latestTest = this.getLatestTestTimestamp();
|
|
5926
5106
|
if (latestTest && latestChange <= latestTest) {
|
|
5927
|
-
return
|
|
5107
|
+
return;
|
|
5928
5108
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
|
|
5933
|
-
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;
|
|
5934
5112
|
}
|
|
5935
5113
|
this.autoTestInFlight = true;
|
|
5936
|
-
const command =
|
|
5937
|
-
|
|
5938
|
-
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}"...`);
|
|
5939
5116
|
this.updateStatusMessage('Running tests automatically...');
|
|
5117
|
+
let combinedOutput = '';
|
|
5940
5118
|
try {
|
|
5941
5119
|
const { stdout, stderr } = await execAsync(command, {
|
|
5942
5120
|
cwd: this.workingDir,
|
|
5943
5121
|
timeout: 10 * 60 * 1000,
|
|
5944
5122
|
maxBuffer: 10 * 1024 * 1024,
|
|
5945
5123
|
});
|
|
5946
|
-
|
|
5947
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5124
|
+
combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
5948
5125
|
display.showSystemMessage('✅ Auto-tests finished.');
|
|
5949
|
-
if (outputText) {
|
|
5950
|
-
this.writeLocked(`${outputText}\n`);
|
|
5951
|
-
}
|
|
5952
5126
|
this.statusTracker.clearOverride('tests');
|
|
5953
|
-
return 'success';
|
|
5954
5127
|
}
|
|
5955
5128
|
catch (error) {
|
|
5956
|
-
|
|
5957
|
-
const message = this.formatCommandError(error);
|
|
5129
|
+
combinedOutput = this.formatCommandError(error);
|
|
5958
5130
|
display.showWarning('⚠️ Auto-tests failed. Review output below.');
|
|
5959
|
-
if (message) {
|
|
5960
|
-
this.writeLocked(`${message}\n`);
|
|
5961
|
-
}
|
|
5962
5131
|
this.statusTracker.pushOverride('tests', 'Tests failing', {
|
|
5963
|
-
detail:
|
|
5132
|
+
detail: 'Auto-run npm test failed',
|
|
5964
5133
|
tone: 'danger',
|
|
5965
5134
|
});
|
|
5966
|
-
return 'failed';
|
|
5967
5135
|
}
|
|
5968
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();
|
|
5969
5148
|
this.updateStatusMessage(null);
|
|
5970
5149
|
this.autoTestInFlight = false;
|
|
5971
|
-
this.terminalInput.resetContentPosition();
|
|
5972
|
-
this.terminalInput.forceRender();
|
|
5973
5150
|
}
|
|
5974
5151
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
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) {
|
|
5978
5165
|
return true;
|
|
5979
5166
|
}
|
|
5980
|
-
if (
|
|
5981
|
-
|
|
5982
|
-
const makeBuild = command.startsWith('make') && !command.includes('make test') && !command.includes('make lint');
|
|
5983
|
-
const patterns = [
|
|
5984
|
-
'npm run build',
|
|
5985
|
-
'yarn build',
|
|
5986
|
-
'pnpm build',
|
|
5987
|
-
'bun run build',
|
|
5988
|
-
'bun build',
|
|
5989
|
-
'tsc',
|
|
5990
|
-
'cargo build',
|
|
5991
|
-
'go build',
|
|
5992
|
-
'python -m build',
|
|
5993
|
-
'mvn -b -dskiptests package',
|
|
5994
|
-
'mvn package',
|
|
5995
|
-
'mvn install',
|
|
5996
|
-
'./mvnw -b -dskiptests package',
|
|
5997
|
-
'./mvnw package',
|
|
5998
|
-
'./mvnw install',
|
|
5999
|
-
'gradle build',
|
|
6000
|
-
'gradle assemble',
|
|
6001
|
-
'./gradlew build',
|
|
6002
|
-
'./gradlew assemble',
|
|
6003
|
-
'dotnet build',
|
|
6004
|
-
'swift build',
|
|
6005
|
-
];
|
|
6006
|
-
return makeBuild || patterns.some((pattern) => command.includes(pattern));
|
|
5167
|
+
if (this.lastBuildSucceededAt && latestChange <= this.lastBuildSucceededAt) {
|
|
5168
|
+
return true;
|
|
6007
5169
|
}
|
|
6008
|
-
|
|
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;
|
|
6009
5217
|
}
|
|
6010
|
-
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
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';
|
|
6016
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';
|
|
6017
5247
|
}
|
|
6018
|
-
return latest;
|
|
6019
5248
|
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
*/
|
|
6024
|
-
async enforceAutoBuild(trigger, commandInfo) {
|
|
6025
|
-
if (this.autoBuildInFlight || !this.verificationEnabled) {
|
|
6026
|
-
return 'skipped';
|
|
5249
|
+
parseAIDesignedTests(plan) {
|
|
5250
|
+
if (!plan?.trim()) {
|
|
5251
|
+
return [];
|
|
6027
5252
|
}
|
|
6028
|
-
const
|
|
6029
|
-
|
|
6030
|
-
return 'skipped';
|
|
6031
|
-
}
|
|
6032
|
-
const latestBuild = this.getLatestBuildTimestamp();
|
|
6033
|
-
if (latestBuild && latestChange <= latestBuild) {
|
|
6034
|
-
return 'skipped';
|
|
6035
|
-
}
|
|
6036
|
-
const detected = commandInfo ?? detectBuildCommand(this.workingDir);
|
|
6037
|
-
if (!detected) {
|
|
6038
|
-
this.lastAutoBuildRun = Date.now();
|
|
6039
|
-
display.showSystemMessage('ℹ️ Skipping auto-build: no build command detected for this repo.');
|
|
6040
|
-
return 'skipped';
|
|
6041
|
-
}
|
|
6042
|
-
this.autoBuildInFlight = true;
|
|
6043
|
-
const command = detected.command;
|
|
6044
|
-
const reasonSuffix = detected.reason ? ` [${detected.reason}]` : '';
|
|
6045
|
-
display.showSystemMessage(`🔨 Auto-building to verify changes (${trigger}) with "${command}"${reasonSuffix}...`);
|
|
6046
|
-
this.updateStatusMessage('Running build automatically...');
|
|
5253
|
+
const match = plan.match(/\[[\s\S]*\]/);
|
|
5254
|
+
const payload = match ? match[0] : plan;
|
|
6047
5255
|
try {
|
|
6048
|
-
const
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
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,
|
|
6052
5304
|
});
|
|
6053
|
-
this.lastAutoBuildRun = Date.now();
|
|
6054
|
-
const outputText = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
6055
|
-
display.showSystemMessage('✅ Build succeeded.');
|
|
6056
|
-
if (outputText && outputText.length < 500) {
|
|
6057
|
-
this.writeLocked(`${outputText}\n`);
|
|
6058
|
-
}
|
|
6059
|
-
this.statusTracker.clearOverride('build');
|
|
6060
|
-
return 'success';
|
|
6061
5305
|
}
|
|
6062
5306
|
catch (error) {
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
if (errorOutput) {
|
|
6067
|
-
this.writeLocked(`${errorOutput}\n`);
|
|
6068
|
-
}
|
|
6069
|
-
this.statusTracker.pushOverride('build', 'Build failing', {
|
|
6070
|
-
detail: `Auto-run ${command} failed`,
|
|
6071
|
-
tone: 'danger',
|
|
6072
|
-
});
|
|
6073
|
-
// Feed build errors back to the agent so it can fix them
|
|
6074
|
-
await this.feedBuildErrorsToAgent(errorOutput);
|
|
6075
|
-
return 'failed';
|
|
5307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5308
|
+
display.showWarning(`AI-designed tests skipped: ${message}`);
|
|
5309
|
+
return;
|
|
6076
5310
|
}
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
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
|
+
}
|
|
6080
5378
|
}
|
|
5379
|
+
display.showSystemMessage(results.join('\n'));
|
|
6081
5380
|
}
|
|
6082
5381
|
/**
|
|
6083
5382
|
* Feed build errors back to the agent conversation for automatic fixing.
|
|
@@ -6103,43 +5402,21 @@ What's the next action?`;
|
|
|
6103
5402
|
// Send the error to the agent for fixing
|
|
6104
5403
|
display.showThinking('Analyzing build errors');
|
|
6105
5404
|
this.refreshStatusLine(true);
|
|
6106
|
-
this.
|
|
6107
|
-
|
|
5405
|
+
const response = await this.agent.send(prompt, true);
|
|
5406
|
+
this.finishStreamingFormatter();
|
|
6108
5407
|
display.stopThinking();
|
|
6109
5408
|
this.refreshStatusLine(true);
|
|
5409
|
+
if (response) {
|
|
5410
|
+
display.showAssistantMessage(response, { isFinal: true });
|
|
5411
|
+
}
|
|
6110
5412
|
// Recursively verify the fix worked
|
|
6111
5413
|
await this.enforceAutoBuild('verification');
|
|
6112
5414
|
}
|
|
6113
5415
|
catch (agentError) {
|
|
6114
5416
|
display.showWarning('Agent could not automatically fix build errors. Please review manually.');
|
|
6115
5417
|
}
|
|
6116
|
-
}
|
|
6117
|
-
async enforceAutoVerification(trigger) {
|
|
6118
|
-
if (this.autoVerificationInFlight || !this.verificationEnabled) {
|
|
6119
|
-
return;
|
|
6120
|
-
}
|
|
6121
|
-
const latestChange = this.getLatestFileChangeTimestamp();
|
|
6122
|
-
if (!latestChange) {
|
|
6123
|
-
return;
|
|
6124
|
-
}
|
|
6125
|
-
this.autoVerificationInFlight = true;
|
|
6126
|
-
try {
|
|
6127
|
-
const buildInfo = detectBuildCommand(this.workingDir);
|
|
6128
|
-
const testInfo = detectTestCommand(this.workingDir);
|
|
6129
|
-
const buildResult = await this.enforceAutoBuild(trigger, buildInfo);
|
|
6130
|
-
if (buildResult === 'failed') {
|
|
6131
|
-
return;
|
|
6132
|
-
}
|
|
6133
|
-
if (testInfo) {
|
|
6134
|
-
await this.enforceAutoTests(trigger, testInfo);
|
|
6135
|
-
}
|
|
6136
|
-
else {
|
|
6137
|
-
this.lastAutoTestRun = Date.now();
|
|
6138
|
-
display.showSystemMessage('ℹ️ Skipping auto-tests: no test command detected for this repo.');
|
|
6139
|
-
}
|
|
6140
|
-
}
|
|
6141
5418
|
finally {
|
|
6142
|
-
this.
|
|
5419
|
+
this.finishStreamingFormatter();
|
|
6143
5420
|
}
|
|
6144
5421
|
}
|
|
6145
5422
|
rebuildAgent() {
|
|
@@ -6157,35 +5434,29 @@ What's the next action?`;
|
|
|
6157
5434
|
autoContinue: this.autoContinueEnabled,
|
|
6158
5435
|
};
|
|
6159
5436
|
this.agent = this.runtimeSession.createAgent(selection, {
|
|
6160
|
-
onStreamChunk: (chunk
|
|
6161
|
-
this.
|
|
5437
|
+
onStreamChunk: (chunk) => {
|
|
5438
|
+
this.handleStreamChunk(chunk);
|
|
6162
5439
|
},
|
|
6163
5440
|
onStreamFallback: (info) => this.handleStreamingFallback(info),
|
|
6164
5441
|
onAssistantMessage: (content, metadata) => {
|
|
6165
5442
|
const enriched = this.buildDisplayMetadata(metadata);
|
|
6166
|
-
|
|
6167
|
-
const parsed = this.splitThinkingResponse(content);
|
|
6168
|
-
const thinking = parsed?.thinking ?? null;
|
|
6169
|
-
const responseContent = parsed ? parsed.response?.trim() ?? '' : content.trim();
|
|
6170
|
-
const narrativeContent = (parsed?.response ?? content).trim();
|
|
6171
|
-
const streamRendered = metadata.wasStreamed && this.assistantStreamHadContent;
|
|
6172
|
-
if (thinking) {
|
|
6173
|
-
this.presentThoughtProcess(thinking, enriched, {
|
|
6174
|
-
wasStreamed: metadata.wasStreamed,
|
|
6175
|
-
// Always render the thought block immediately when present so it appears first.
|
|
6176
|
-
renderWhenStreamed: true,
|
|
6177
|
-
});
|
|
6178
|
-
}
|
|
5443
|
+
// Update spinner based on message type
|
|
6179
5444
|
if (metadata.isFinal) {
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
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
|
+
}
|
|
6187
5459
|
}
|
|
6188
|
-
this.renderToolUsageSummary({ ...enriched, isFinal: true });
|
|
6189
5460
|
// Status shown in mode controls bar - no separate status line needed
|
|
6190
5461
|
display.stopThinking();
|
|
6191
5462
|
// Update context usage for mode controls display
|
|
@@ -6196,22 +5467,19 @@ What's the next action?`;
|
|
|
6196
5467
|
this.updateContextUsage(percentage);
|
|
6197
5468
|
}
|
|
6198
5469
|
}
|
|
5470
|
+
if (finalContent) {
|
|
5471
|
+
this.lastAssistantResponse = finalContent;
|
|
5472
|
+
}
|
|
6199
5473
|
// Auto-verify changes: build first (catches type errors), then tests
|
|
6200
|
-
void this.
|
|
5474
|
+
void this.runAutoQualityChecks('final-response', finalContent);
|
|
6201
5475
|
}
|
|
6202
5476
|
else {
|
|
6203
5477
|
// Non-final message = narrative text before tool calls (Claude Code style)
|
|
6204
5478
|
// Stop spinner and show the narrative text directly
|
|
6205
5479
|
display.stopThinking();
|
|
6206
|
-
if
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
const shouldRenderNarrative = Boolean(narrativeContent && (!metadata.wasStreamed || !blocksActive || !streamRendered));
|
|
6210
|
-
if (shouldRenderNarrative) {
|
|
6211
|
-
this.renderAssistantContent('thought', narrativeContent, {
|
|
6212
|
-
...enriched,
|
|
6213
|
-
isFinal: false,
|
|
6214
|
-
});
|
|
5480
|
+
// Skip display if content was already streamed to avoid double-display
|
|
5481
|
+
if (!metadata.wasStreamed) {
|
|
5482
|
+
display.showNarrative(content.trim());
|
|
6215
5483
|
}
|
|
6216
5484
|
// The isProcessing flag already shows "⏳ Processing..." - no need for duplicate status
|
|
6217
5485
|
this.requestPromptRefresh();
|
|
@@ -6224,8 +5492,8 @@ What's the next action?`;
|
|
|
6224
5492
|
this.requestPromptRefresh();
|
|
6225
5493
|
},
|
|
6226
5494
|
onContextSquishing: (message) => {
|
|
6227
|
-
//
|
|
6228
|
-
display.
|
|
5495
|
+
// Show notification in UI when auto context squishing occurs
|
|
5496
|
+
display.showSystemMessage(`🔄 ${message}`);
|
|
6229
5497
|
this.statusTracker.pushOverride('context-squish', 'Auto-squishing context', {
|
|
6230
5498
|
detail: 'Reducing conversation history to fit within token limits',
|
|
6231
5499
|
tone: 'warning',
|
|
@@ -6233,7 +5501,7 @@ What's the next action?`;
|
|
|
6233
5501
|
},
|
|
6234
5502
|
onContextRecovery: (attempt, maxAttempts, message) => {
|
|
6235
5503
|
// Show recovery progress in UI
|
|
6236
|
-
display.
|
|
5504
|
+
display.showSystemMessage(`⚡ Context Recovery (${attempt}/${maxAttempts}): ${message}`);
|
|
6237
5505
|
},
|
|
6238
5506
|
onContextPruned: (removedCount, stats) => {
|
|
6239
5507
|
// Clear squish overlay if active
|
|
@@ -6241,43 +5509,40 @@ What's the next action?`;
|
|
|
6241
5509
|
// Show notification that context was pruned
|
|
6242
5510
|
const method = stats['method'];
|
|
6243
5511
|
const percentage = stats['percentage'];
|
|
6244
|
-
const summarized = stats['summarized'] === true;
|
|
6245
5512
|
if (method === 'emergency-recovery') {
|
|
6246
|
-
display.
|
|
5513
|
+
display.showSystemMessage(`✅ Context recovery complete. Removed ${removedCount} messages. Context now at ${percentage ?? 'unknown'}%`);
|
|
6247
5514
|
}
|
|
6248
5515
|
// Update context usage in UI
|
|
6249
5516
|
if (typeof percentage === 'number') {
|
|
6250
5517
|
this.updateContextUsage(percentage);
|
|
6251
5518
|
}
|
|
6252
|
-
if (summarized) {
|
|
6253
|
-
display.showSystemMessage('Context summary captured. View the latest summaries with /contextlog.');
|
|
6254
|
-
}
|
|
6255
5519
|
// Ensure prompt remains visible at bottom after context messages
|
|
6256
|
-
this.
|
|
5520
|
+
this.terminalInput.render();
|
|
6257
5521
|
},
|
|
6258
5522
|
onContinueAfterRecovery: () => {
|
|
6259
5523
|
// Update UI to show we're continuing after context recovery
|
|
6260
|
-
display.
|
|
5524
|
+
display.showSystemMessage(`🔄 Continuing after context recovery...`);
|
|
6261
5525
|
this.updateStatusMessage('Retrying with reduced context...');
|
|
6262
|
-
this.
|
|
5526
|
+
this.terminalInput.render();
|
|
6263
5527
|
},
|
|
6264
5528
|
onAutoContinue: (attempt, maxAttempts, _message) => {
|
|
6265
5529
|
// Show auto-continue progress in UI
|
|
6266
5530
|
display.showSystemMessage(`🔄 Auto-continue (${attempt}/${maxAttempts}): Model expressed intent, prompting to act...`);
|
|
6267
5531
|
this.updateStatusMessage('Auto-continuing...');
|
|
6268
|
-
this.
|
|
5532
|
+
this.terminalInput.render();
|
|
6269
5533
|
},
|
|
6270
5534
|
onCancelled: () => {
|
|
6271
5535
|
// Update UI to show operation was cancelled
|
|
6272
5536
|
display.showWarning('Operation cancelled.');
|
|
6273
5537
|
this.uiUpdates.setMode('processing');
|
|
6274
|
-
this.stopStreamingHeartbeat(
|
|
5538
|
+
this.stopStreamingHeartbeat();
|
|
6275
5539
|
this.updateStatusMessage(null);
|
|
6276
5540
|
this.terminalInput.setStreaming(false);
|
|
6277
|
-
this.
|
|
5541
|
+
this.terminalInput.render();
|
|
6278
5542
|
},
|
|
6279
|
-
onVerificationNeeded: () => {
|
|
6280
|
-
|
|
5543
|
+
onVerificationNeeded: (response, context) => {
|
|
5544
|
+
this.lastAssistantResponse = response;
|
|
5545
|
+
void this.runAutoQualityChecks('verification', response, context);
|
|
6281
5546
|
},
|
|
6282
5547
|
});
|
|
6283
5548
|
// Register global AI enhancer for explore tool - uses active model by default
|
|
@@ -6311,7 +5576,7 @@ What's the next action?`;
|
|
|
6311
5576
|
resetChatBoxAfterModelSwap() {
|
|
6312
5577
|
this.updateStatusMessage(null);
|
|
6313
5578
|
this.terminalInput.setStreaming(false);
|
|
6314
|
-
this.
|
|
5579
|
+
this.terminalInput.render();
|
|
6315
5580
|
this.ensureReadlineReady();
|
|
6316
5581
|
}
|
|
6317
5582
|
/**
|
|
@@ -6374,22 +5639,19 @@ What's the next action?`;
|
|
|
6374
5639
|
}
|
|
6375
5640
|
buildThinkingDirective() {
|
|
6376
5641
|
switch (this.thinkingMode) {
|
|
6377
|
-
case 'concise':
|
|
6378
|
-
return 'Concise thinking mode: respond directly with the final answer and skip <thinking> blocks unless the user explicitly asks for reasoning.';
|
|
6379
5642
|
case 'extended':
|
|
6380
5643
|
return [
|
|
6381
|
-
'Extended thinking mode
|
|
5644
|
+
'Extended thinking mode is enabled. Format every reply as:',
|
|
6382
5645
|
'<thinking>',
|
|
6383
|
-
'
|
|
5646
|
+
'Detailed multi-step reasoning (reference tool runs/files when relevant, keep secrets redacted, no code blocks unless citing filenames).',
|
|
6384
5647
|
'</thinking>',
|
|
6385
5648
|
'<response>',
|
|
6386
|
-
'Final answer with
|
|
5649
|
+
'Final answer with actionable next steps and any code/commands requested.',
|
|
6387
5650
|
'</response>',
|
|
6388
5651
|
].join('\n');
|
|
6389
5652
|
case 'balanced':
|
|
6390
5653
|
default:
|
|
6391
|
-
|
|
6392
|
-
return 'When reasoning through complex tasks, planning approaches, or making decisions, wrap your thought process in <thinking> tags before your response. Keep thinking concise and focused on the decision-making process.';
|
|
5654
|
+
return 'Balanced thinking mode: include a short <thinking>...</thinking> block before <response> when the reasoning is non-trivial; skip it for simple answers.';
|
|
6393
5655
|
}
|
|
6394
5656
|
}
|
|
6395
5657
|
buildDisplayMetadata(metadata) {
|
|
@@ -6398,58 +5660,6 @@ What's the next action?`;
|
|
|
6398
5660
|
contextWindowTokens: this.activeContextWindowTokens,
|
|
6399
5661
|
};
|
|
6400
5662
|
}
|
|
6401
|
-
presentThoughtProcess(thinking, metadata, options) {
|
|
6402
|
-
if (!thinking || this.hasShownThoughtProcess) {
|
|
6403
|
-
return;
|
|
6404
|
-
}
|
|
6405
|
-
const summary = this.extractThoughtSummary(thinking);
|
|
6406
|
-
if (summary) {
|
|
6407
|
-
display.updateThinking(`💭 ${summary}`);
|
|
6408
|
-
this.latestThoughtSummary = summary;
|
|
6409
|
-
this.streamingStatusDetail = summary;
|
|
6410
|
-
this.terminalInput.recordRecentAction(`💭 ${summary}`);
|
|
6411
|
-
this.rebuildStreamingStatusLabel();
|
|
6412
|
-
this.refreshStatusLine(true);
|
|
6413
|
-
}
|
|
6414
|
-
const shouldRender = !options?.wasStreamed || options?.renderWhenStreamed;
|
|
6415
|
-
if (shouldRender) {
|
|
6416
|
-
this.renderAssistantContent('thought', thinking, { ...metadata, isFinal: false });
|
|
6417
|
-
}
|
|
6418
|
-
this.hasShownThoughtProcess = true;
|
|
6419
|
-
}
|
|
6420
|
-
renderToolUsageSummary(metadata) {
|
|
6421
|
-
const since = this.lastToolSummaryRenderedAt ?? this.lastRequestStartedAt ?? Date.now();
|
|
6422
|
-
const operations = this.uiAdapter.getToolOperationsSince(since);
|
|
6423
|
-
if (!operations.length) {
|
|
6424
|
-
return;
|
|
6425
|
-
}
|
|
6426
|
-
let summaryLine = compactRenderer.formatCompactLine(operations, {
|
|
6427
|
-
showIcons: true,
|
|
6428
|
-
showTimings: true,
|
|
6429
|
-
});
|
|
6430
|
-
const warnings = this.uiAdapter.getRecentWarnings(since);
|
|
6431
|
-
if (warnings.length) {
|
|
6432
|
-
const limited = warnings.slice(0, 3);
|
|
6433
|
-
const formattedWarnings = limited
|
|
6434
|
-
.map((warning) => `${icons.warning} ${warning.tool}: ${warning.text}`)
|
|
6435
|
-
.join(' • ');
|
|
6436
|
-
summaryLine = `${summaryLine} ${theme.warning('[warnings]')} ${formattedWarnings}`;
|
|
6437
|
-
}
|
|
6438
|
-
if (!summaryLine.trim()) {
|
|
6439
|
-
this.lastToolSummaryRenderedAt = Date.now();
|
|
6440
|
-
return;
|
|
6441
|
-
}
|
|
6442
|
-
this.lastToolSummaryRenderedAt =
|
|
6443
|
-
operations[operations.length - 1]?.startedAt ?? Date.now();
|
|
6444
|
-
const blockMetadata = { ...metadata, isFinal: true };
|
|
6445
|
-
if (this.assistantBlocksEnabled && this.assistantBlockRenderer) {
|
|
6446
|
-
this.renderAssistantBlock('tools', summaryLine, blockMetadata);
|
|
6447
|
-
return;
|
|
6448
|
-
}
|
|
6449
|
-
const header = this.formatAssistantHeader('tools', blockMetadata);
|
|
6450
|
-
const block = `\n${header}\n${summaryLine}\n\n`;
|
|
6451
|
-
this.enqueueAssistantStream(block);
|
|
6452
|
-
}
|
|
6453
5663
|
handleContextTelemetry(metadata, displayMetadata) {
|
|
6454
5664
|
if (!metadata.isFinal) {
|
|
6455
5665
|
return null;
|
|
@@ -6810,7 +6020,7 @@ What's the next action?`;
|
|
|
6810
6020
|
}
|
|
6811
6021
|
handleAgentSetupError(error, retryAction, providerOverride) {
|
|
6812
6022
|
this.pendingInteraction = null;
|
|
6813
|
-
const provider = providerOverride
|
|
6023
|
+
const provider = providerOverride ?? this.sessionState.provider;
|
|
6814
6024
|
const apiKeyIssue = detectApiKeyError(error, provider);
|
|
6815
6025
|
if (apiKeyIssue) {
|
|
6816
6026
|
this.handleApiKeyIssue(apiKeyIssue, retryAction);
|
|
@@ -6825,14 +6035,13 @@ What's the next action?`;
|
|
|
6825
6035
|
const detail = detailText ? ` Error: ${detailText}` : '';
|
|
6826
6036
|
const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
|
|
6827
6037
|
const partialNote = info.partialResponse ? ' Received partial stream before failure.' : '';
|
|
6828
|
-
this.finalizeAssistantStream();
|
|
6829
6038
|
display.showWarning(`Streaming failed${reason}, retrying without streaming.${detail}${partialNote}`);
|
|
6039
|
+
this.finishStreamingFormatter('Stream interrupted - retrying without streaming');
|
|
6830
6040
|
this.startStreamingHeartbeat('Fallback in progress');
|
|
6831
6041
|
this.requestPromptRefresh(true);
|
|
6832
6042
|
}
|
|
6833
6043
|
handleProviderError(error, retryAction) {
|
|
6834
|
-
const
|
|
6835
|
-
const apiKeyIssue = detectApiKeyError(error, providerHint);
|
|
6044
|
+
const apiKeyIssue = detectApiKeyError(error, this.sessionState.provider);
|
|
6836
6045
|
if (!apiKeyIssue) {
|
|
6837
6046
|
return false;
|
|
6838
6047
|
}
|
|
@@ -6841,19 +6050,13 @@ What's the next action?`;
|
|
|
6841
6050
|
}
|
|
6842
6051
|
handleApiKeyIssue(info, retryAction) {
|
|
6843
6052
|
const secret = info.secret ?? null;
|
|
6844
|
-
const providerLabel = info.provider
|
|
6845
|
-
? this.providerLabel(info.provider)
|
|
6846
|
-
: secret?.providers?.length
|
|
6847
|
-
? this.providerLabel(secret.providers[0])
|
|
6848
|
-
: null;
|
|
6849
|
-
const targetLabel = providerLabel ?? secret?.label ?? 'this tool';
|
|
6850
|
-
this.apiKeyGateActive = !!secret;
|
|
6053
|
+
const providerLabel = info.provider ? this.providerLabel(info.provider) : 'the selected provider';
|
|
6851
6054
|
if (!secret) {
|
|
6852
6055
|
this.pendingSecretRetry = null;
|
|
6853
6056
|
const guidance = 'Run "/secrets" to configure the required API key or export it (e.g., EXPORT KEY=value) before launching the CLI.';
|
|
6854
6057
|
const baseMessage = info.type === 'missing'
|
|
6855
|
-
? `An API key is required before using ${
|
|
6856
|
-
: `API authentication failed for ${
|
|
6058
|
+
? `An API key is required before using ${providerLabel}.`
|
|
6059
|
+
: `API authentication failed for ${providerLabel}.`;
|
|
6857
6060
|
display.showWarning(`${baseMessage} ${guidance}`.trim());
|
|
6858
6061
|
return;
|
|
6859
6062
|
}
|
|
@@ -6862,8 +6065,8 @@ What's the next action?`;
|
|
|
6862
6065
|
display.showWarning(info.message.trim());
|
|
6863
6066
|
}
|
|
6864
6067
|
const prefix = isMissing
|
|
6865
|
-
? `${secret.label} is required before you can use ${
|
|
6866
|
-
: `${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}.`;
|
|
6867
6070
|
display.showWarning(prefix);
|
|
6868
6071
|
this.pendingSecretRetry = retryAction ?? null;
|
|
6869
6072
|
this.pendingInteraction = { type: 'secret-input', secret };
|
|
@@ -6877,7 +6080,7 @@ What's the next action?`;
|
|
|
6877
6080
|
else {
|
|
6878
6081
|
lines.push(`Update the stored value for ${secret.label} or type "cancel".`);
|
|
6879
6082
|
}
|
|
6880
|
-
lines.push(`
|
|
6083
|
+
lines.push(`Tip: run "/secrets" anytime to manage credentials or export ${secret.envVar}=<value> before launching the CLI.`);
|
|
6881
6084
|
display.showSystemMessage(lines.join('\n'));
|
|
6882
6085
|
}
|
|
6883
6086
|
colorizeDropdownLine(text, index) {
|
|
@@ -6902,22 +6105,34 @@ What's the next action?`;
|
|
|
6902
6105
|
/**
|
|
6903
6106
|
* Build the session banner with comprehensive feature status.
|
|
6904
6107
|
*/
|
|
6905
|
-
|
|
6906
|
-
|
|
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({
|
|
6907
6116
|
profileLabel: this.profileLabel,
|
|
6908
6117
|
profileName: this.profile,
|
|
6909
6118
|
model: this.sessionState.model,
|
|
6910
6119
|
provider: this.sessionState.provider,
|
|
6911
6120
|
workspace: this.workingDir,
|
|
6912
6121
|
version: this.version,
|
|
6913
|
-
};
|
|
6914
|
-
}
|
|
6915
|
-
buildBanner() {
|
|
6916
|
-
const terminalWidth = output.columns ?? 100;
|
|
6917
|
-
const width = Math.min(terminalWidth - 4, 110);
|
|
6918
|
-
return renderSessionFrame({
|
|
6919
|
-
...this.getSessionFrameProps(),
|
|
6920
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
|
+
},
|
|
6921
6136
|
});
|
|
6922
6137
|
}
|
|
6923
6138
|
/**
|
|
@@ -6948,21 +6163,6 @@ What's the next action?`;
|
|
|
6948
6163
|
}
|
|
6949
6164
|
return categories;
|
|
6950
6165
|
}
|
|
6951
|
-
buildFeatureStatusSnapshot() {
|
|
6952
|
-
const featureFlags = loadFeatureFlags();
|
|
6953
|
-
const toolCategories = this.collectToolCategories();
|
|
6954
|
-
const toolCount = toolCategories.reduce((sum, cat) => sum + cat.count, 0);
|
|
6955
|
-
const pluginCount = this._enabledPlugins.length;
|
|
6956
|
-
return {
|
|
6957
|
-
pluginCount: pluginCount > 0 ? pluginCount : undefined,
|
|
6958
|
-
toolCount: toolCount > 0 ? toolCount : undefined,
|
|
6959
|
-
sessionId: this.activeSessionId,
|
|
6960
|
-
mcpEnabled: featureFlags.mcpEnabled,
|
|
6961
|
-
metricsEnabled: featureFlags.metrics,
|
|
6962
|
-
autoCompact: featureFlags.autoCompact,
|
|
6963
|
-
dualMode: featureFlags.alphaZeroDual,
|
|
6964
|
-
};
|
|
6965
|
-
}
|
|
6966
6166
|
/**
|
|
6967
6167
|
* Extract category from tool name.
|
|
6968
6168
|
*/
|
|
@@ -7026,7 +6226,8 @@ What's the next action?`;
|
|
|
7026
6226
|
return;
|
|
7027
6227
|
}
|
|
7028
6228
|
this.refreshContextGauge();
|
|
7029
|
-
// 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
|
|
7030
6231
|
if (!this.isProcessing) {
|
|
7031
6232
|
this.setIdleStatus();
|
|
7032
6233
|
}
|
|
@@ -7331,6 +6532,20 @@ What's the next action?`;
|
|
|
7331
6532
|
];
|
|
7332
6533
|
display.showSystemMessage(lines.join('\n'));
|
|
7333
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
|
+
}
|
|
7334
6549
|
}
|
|
7335
6550
|
function setsEqual(first, second) {
|
|
7336
6551
|
if (first.size !== second.size) {
|