@trenchwork/erosolar 1.1.40 → 1.1.42
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/LICENSE +12 -17
- package/README.md +23 -12
- package/dist/bin/deepseek.js +18 -198
- package/dist/bin/deepseek.js.map +1 -1
- package/dist/contracts/agent-profiles.schema.json +5 -23
- package/dist/contracts/agent-schemas.json +31 -343
- package/dist/contracts/schemas/agent-schemas.schema.json +3 -3
- package/dist/contracts/unified-schema.json +8 -628
- package/dist/core/hitl.d.ts.map +1 -1
- package/dist/core/hitl.js +17 -16
- package/dist/core/hitl.js.map +1 -1
- package/dist/core/modelDiscovery.d.ts +0 -3
- package/dist/core/modelDiscovery.d.ts.map +1 -1
- package/dist/core/modelDiscovery.js +3 -355
- package/dist/core/modelDiscovery.js.map +1 -1
- package/dist/core/permissionMode.d.ts +40 -0
- package/dist/core/permissionMode.d.ts.map +1 -0
- package/dist/core/permissionMode.js +86 -0
- package/dist/core/permissionMode.js.map +1 -0
- package/dist/core/quotaErrors.d.ts +42 -0
- package/dist/core/quotaErrors.d.ts.map +1 -0
- package/dist/core/quotaErrors.js +88 -0
- package/dist/core/quotaErrors.js.map +1 -0
- package/dist/core/secretStore.d.ts.map +1 -1
- package/dist/core/secretStore.js +0 -13
- package/dist/core/secretStore.js.map +1 -1
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +21 -2
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/headless/interactiveShell.d.ts +7 -5
- package/dist/headless/interactiveShell.d.ts.map +1 -1
- package/dist/headless/interactiveShell.js +99 -217
- package/dist/headless/interactiveShell.js.map +1 -1
- package/dist/plugins/providers/deepseek/index.d.ts +1 -0
- package/dist/plugins/providers/deepseek/index.d.ts.map +1 -1
- package/dist/plugins/providers/deepseek/index.js +65 -4
- package/dist/plugins/providers/deepseek/index.js.map +1 -1
- package/dist/plugins/providers/index.d.ts.map +1 -1
- package/dist/plugins/providers/index.js +0 -7
- package/dist/plugins/providers/index.js.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.js +7 -30
- package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
- package/dist/providers/resilientProvider.d.ts.map +1 -1
- package/dist/providers/resilientProvider.js +0 -31
- package/dist/providers/resilientProvider.js.map +1 -1
- package/dist/shell/commandRegistry.js +6 -6
- package/dist/shell/commandRegistry.js.map +1 -1
- package/dist/shell/toolPresentation.d.ts +47 -0
- package/dist/shell/toolPresentation.d.ts.map +1 -0
- package/dist/shell/toolPresentation.js +260 -0
- package/dist/shell/toolPresentation.js.map +1 -0
- package/dist/tools/bashTools.js +2 -2
- package/dist/tools/bashTools.js.map +1 -1
- package/dist/tools/webTools.d.ts.map +1 -1
- package/dist/tools/webTools.js +85 -3
- package/dist/tools/webTools.js.map +1 -1
- package/dist/ui/ink/App.d.ts +2 -0
- package/dist/ui/ink/App.d.ts.map +1 -1
- package/dist/ui/ink/App.js +2 -2
- package/dist/ui/ink/App.js.map +1 -1
- package/dist/ui/ink/ChatStatic.d.ts +6 -5
- package/dist/ui/ink/ChatStatic.d.ts.map +1 -1
- package/dist/ui/ink/ChatStatic.js +35 -10
- package/dist/ui/ink/ChatStatic.js.map +1 -1
- package/dist/ui/ink/InkPromptController.d.ts +11 -0
- package/dist/ui/ink/InkPromptController.d.ts.map +1 -1
- package/dist/ui/ink/InkPromptController.js +50 -11
- package/dist/ui/ink/InkPromptController.js.map +1 -1
- package/dist/ui/ink/Prompt.d.ts +2 -0
- package/dist/ui/ink/Prompt.d.ts.map +1 -1
- package/dist/ui/ink/Prompt.js +8 -2
- package/dist/ui/ink/Prompt.js.map +1 -1
- package/dist/ui/ink/StatusLine.d.ts +16 -8
- package/dist/ui/ink/StatusLine.d.ts.map +1 -1
- package/dist/ui/ink/StatusLine.js +45 -4
- package/dist/ui/ink/StatusLine.js.map +1 -1
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +4 -6
- package/dist/ui/theme.js.map +1 -1
- package/package.json +2 -9
- package/scripts/postinstall.cjs +1 -2
- package/dist/bin/cliMode.d.ts +0 -8
- package/dist/bin/cliMode.d.ts.map +0 -1
- package/dist/bin/cliMode.js +0 -20
- package/dist/bin/cliMode.js.map +0 -1
- package/dist/bin/selfTest.d.ts +0 -14
- package/dist/bin/selfTest.d.ts.map +0 -1
- package/dist/bin/selfTest.js +0 -298
- package/dist/bin/selfTest.js.map +0 -1
- package/dist/headless/printMode.d.ts +0 -17
- package/dist/headless/printMode.d.ts.map +0 -1
- package/dist/headless/printMode.js +0 -40
- package/dist/headless/printMode.js.map +0 -1
- package/dist/plugins/providers/anthropic/index.d.ts +0 -9
- package/dist/plugins/providers/anthropic/index.d.ts.map +0 -1
- package/dist/plugins/providers/anthropic/index.js +0 -48
- package/dist/plugins/providers/anthropic/index.js.map +0 -1
- package/dist/plugins/providers/openai/index.d.ts +0 -10
- package/dist/plugins/providers/openai/index.d.ts.map +0 -1
- package/dist/plugins/providers/openai/index.js +0 -47
- package/dist/plugins/providers/openai/index.js.map +0 -1
- package/dist/plugins/providers/xai/index.d.ts +0 -10
- package/dist/plugins/providers/xai/index.d.ts.map +0 -1
- package/dist/plugins/providers/xai/index.js +0 -47
- package/dist/plugins/providers/xai/index.js.map +0 -1
|
@@ -43,6 +43,9 @@ import { startNewRun } from '../tools/fileChangeTracker.js';
|
|
|
43
43
|
import { onSudoPasswordNeeded, offSudoPasswordNeeded, provideSudoPassword } from '../core/sudoPasswordManager.js';
|
|
44
44
|
import { reportStatus, setStatusSink } from '../utils/statusReporter.js';
|
|
45
45
|
import { isSafetyRefusal } from '../core/refusalDetection.js';
|
|
46
|
+
import { formatToolCall, toolActivityLabel, formatToolResult, formatToolError } from '../shell/toolPresentation.js';
|
|
47
|
+
// Tool-result display (ANSI stripping, summarisation, the `⎿` block) now lives
|
|
48
|
+
// in ../shell/toolPresentation.ts — the shell just emits the formatted strings.
|
|
46
49
|
// Timeout constants for regular prompt processing (reasoning models like DeepSeek)
|
|
47
50
|
const PROMPT_REASONING_TIMEOUT_MS = 60 * 1000; // 60 seconds max for reasoning-only without action
|
|
48
51
|
// Per-step timeout: how long we'll wait for the *next* event before
|
|
@@ -128,24 +131,46 @@ function getVersion() {
|
|
|
128
131
|
return '0.0.0';
|
|
129
132
|
}
|
|
130
133
|
}
|
|
134
|
+
/** Inner content of the welcome box (plain, no border/colour). */
|
|
135
|
+
function welcomeBodyLines(input) {
|
|
136
|
+
const body = ['✻ Welcome to Erosolar Coder', ''];
|
|
137
|
+
if (!input.hasApiKey) {
|
|
138
|
+
body.push('⚠ No API key configured', '', 'Get your key: https://platform.deepseek.com/', 'Set your key: /key YOUR_API_KEY');
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
body.push(`${input.model} · ${input.provider}`, `Key: ${input.maskedKey} · /help for commands`);
|
|
142
|
+
}
|
|
143
|
+
if (input.cwd)
|
|
144
|
+
body.push(`cwd: ${input.cwd}`);
|
|
145
|
+
return body;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Wrap content lines in a Claude-Code-style rounded box (╭╮╰╯). `paint`
|
|
149
|
+
* colours an already-padded content cell; `border` colours the frame. Both
|
|
150
|
+
* default to identity so the pure version stays ANSI-free.
|
|
151
|
+
*/
|
|
152
|
+
function roundedBox(content, paint = (s) => s, border = (s) => s) {
|
|
153
|
+
const width = Math.min(content.reduce((m, c) => Math.max(m, c.length), 0), 72);
|
|
154
|
+
const pad = (c) => c + ' '.repeat(Math.max(0, width - c.length));
|
|
155
|
+
const rule = '─'.repeat(width + 2);
|
|
156
|
+
return [
|
|
157
|
+
border(`╭${rule}╮`),
|
|
158
|
+
...content.map((c) => `${border('│')} ${paint(pad(c))} ${border('│')}`),
|
|
159
|
+
border(`╰${rule}╯`),
|
|
160
|
+
];
|
|
161
|
+
}
|
|
131
162
|
/**
|
|
132
163
|
* Compose the lines shown when the interactive shell opens. Deliberately NOT a
|
|
133
164
|
* marketing splash — bare `erosolar` opens straight into the chat (like
|
|
134
|
-
* `claude`); this is
|
|
135
|
-
* the active model + masked key
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
165
|
+
* `claude`); this is the load-bearing welcome: a sparkle, the name, and either
|
|
166
|
+
* how to set a key or the active model + masked key, inside a rounded box that
|
|
167
|
+
* mirrors Claude Code's. Pure (no chalk/ANSI, no I/O) so the "no marketing
|
|
168
|
+
* splash, key guidance kept" contract is unit-testable without a PTY. The live
|
|
169
|
+
* renderer colourises equivalent content; this is the source of truth for
|
|
170
|
+
* WHICH lines appear.
|
|
139
171
|
*/
|
|
140
172
|
export function composeWelcomeLines(input) {
|
|
141
|
-
|
|
142
|
-
if (!input.hasApiKey) {
|
|
143
|
-
lines.push(' ⚠ No API key configured', '', ' Get your key: https://platform.deepseek.com/', ' Set your key: /key YOUR_API_KEY', '');
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
lines.push(` ${input.model} · ${input.provider}`, ` Key: ${input.maskedKey} · /help for commands`, '');
|
|
147
|
-
}
|
|
148
|
-
return lines;
|
|
173
|
+
return ['', ...(input.updateLines ?? []), ...roundedBox(welcomeBodyLines(input)), ''];
|
|
149
174
|
}
|
|
150
175
|
/**
|
|
151
176
|
* Run the fully interactive shell with rich UI.
|
|
@@ -171,7 +196,11 @@ export async function runInteractiveShell(options) {
|
|
|
171
196
|
exit(1);
|
|
172
197
|
}
|
|
173
198
|
loadAllSecrets();
|
|
174
|
-
|
|
199
|
+
// argv intentionally unused — the bin is shell-only. Any tokens after
|
|
200
|
+
// `erosolar` are ignored on purpose; configuration lives in /secrets,
|
|
201
|
+
// /model, /auto, etc. The options.argv field stays only because tests
|
|
202
|
+
// pass it; it does not affect runtime.
|
|
203
|
+
void options;
|
|
175
204
|
const profile = resolveProfile();
|
|
176
205
|
const workingDir = process.cwd();
|
|
177
206
|
const workspaceOptions = resolveWorkspaceCaptureOptions(process.env);
|
|
@@ -187,10 +216,6 @@ export async function runInteractiveShell(options) {
|
|
|
187
216
|
});
|
|
188
217
|
// Create the interactive shell instance
|
|
189
218
|
const shell = new InteractiveShell(controller, profile, profileConfig, workingDir);
|
|
190
|
-
// Handle initial prompt if provided
|
|
191
|
-
if (parsed.initialPrompt) {
|
|
192
|
-
shell.queuePrompt(parsed.initialPrompt);
|
|
193
|
-
}
|
|
194
219
|
await shell.run();
|
|
195
220
|
}
|
|
196
221
|
class InteractiveShell {
|
|
@@ -292,6 +317,7 @@ class InteractiveShell {
|
|
|
292
317
|
onCtrlC: (info) => this.handleCtrlC(info),
|
|
293
318
|
onToggleAutoContinue: () => this.handleAutoContinueToggle(),
|
|
294
319
|
onToggleHITL: () => this.handleHITLToggle(),
|
|
320
|
+
onCyclePermissionMode: (mode) => this.handlePermissionModeChange(mode),
|
|
295
321
|
});
|
|
296
322
|
// Register cleanup callback for graceful shutdown
|
|
297
323
|
onShutdown(() => {
|
|
@@ -395,21 +421,20 @@ class InteractiveShell {
|
|
|
395
421
|
chalk.dim(' · installing in background…'));
|
|
396
422
|
this.runBackgroundUpdate(updateInfo);
|
|
397
423
|
}
|
|
398
|
-
// Clean, minimal welcome
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const welcomeContent = welcomeLines.join('\n');
|
|
424
|
+
// Clean, minimal welcome — a sparkle + the essentials in a rounded box,
|
|
425
|
+
// mirroring Claude Code. The pure composeWelcomeLines() is the contract for
|
|
426
|
+
// WHICH lines appear; here we draw the same box with brand colour.
|
|
427
|
+
const flare = chalk.hex('#ff6a1f');
|
|
428
|
+
const wire = chalk.hex('#3a362e');
|
|
429
|
+
const body = welcomeBodyLines({
|
|
430
|
+
hasApiKey,
|
|
431
|
+
maskedKey: hasApiKey ? maskApiKey(apiKey) : '',
|
|
432
|
+
model: this.profileConfig.model,
|
|
433
|
+
provider: this.profileConfig.provider,
|
|
434
|
+
cwd: this.workingDir,
|
|
435
|
+
});
|
|
436
|
+
const boxed = roundedBox(body, (cell) => cell.replace('✻', flare('✻')), (s) => wire(s));
|
|
437
|
+
const welcomeContent = ['', ...updateLines, ...boxed, ''].join('\n');
|
|
413
438
|
// Use renderer event system instead of direct stdout writes
|
|
414
439
|
renderer.addEvent('banner', welcomeContent);
|
|
415
440
|
// Update renderer meta with model info
|
|
@@ -455,7 +480,7 @@ class InteractiveShell {
|
|
|
455
480
|
}
|
|
456
481
|
try {
|
|
457
482
|
// Show password prompt
|
|
458
|
-
renderer.addEvent('system', chalk.yellow('
|
|
483
|
+
renderer.addEvent('system', chalk.yellow('Sudo password required'));
|
|
459
484
|
renderer.setSecretMode(true);
|
|
460
485
|
renderer.clearBuffer();
|
|
461
486
|
// Capture password input
|
|
@@ -804,11 +829,6 @@ class InteractiveShell {
|
|
|
804
829
|
this.promptController?.getRenderer()?.addEvent('response', 'Email is not handled by the CLI.');
|
|
805
830
|
return true;
|
|
806
831
|
}
|
|
807
|
-
// Session stats
|
|
808
|
-
if (lower === '/stats' || lower === '/status') {
|
|
809
|
-
this.showSessionStats();
|
|
810
|
-
return true;
|
|
811
|
-
}
|
|
812
832
|
return false;
|
|
813
833
|
}
|
|
814
834
|
/**
|
|
@@ -933,18 +953,8 @@ class InteractiveShell {
|
|
|
933
953
|
return prefix.id;
|
|
934
954
|
// Alias matching
|
|
935
955
|
const aliases = {
|
|
936
|
-
'claude': 'anthropic',
|
|
937
|
-
'ant': 'anthropic',
|
|
938
|
-
'gpt': 'openai',
|
|
939
|
-
'oai': 'openai',
|
|
940
|
-
'gemini': 'google',
|
|
941
|
-
'gem': 'google',
|
|
942
956
|
'ds': 'deepseek',
|
|
943
957
|
'deep': 'deepseek',
|
|
944
|
-
'grok': 'xai',
|
|
945
|
-
'x': 'xai',
|
|
946
|
-
'local': 'ollama',
|
|
947
|
-
'llama': 'ollama',
|
|
948
958
|
};
|
|
949
959
|
if (aliases[lower]) {
|
|
950
960
|
const aliased = providers.find(p => p.id === aliases[lower]);
|
|
@@ -958,24 +968,9 @@ class InteractiveShell {
|
|
|
958
968
|
*/
|
|
959
969
|
inferProviderFromModel(model) {
|
|
960
970
|
const lower = model.toLowerCase();
|
|
961
|
-
if (lower.startsWith('claude') || lower.startsWith('opus') || lower.startsWith('sonnet') || lower.startsWith('haiku')) {
|
|
962
|
-
return 'anthropic';
|
|
963
|
-
}
|
|
964
|
-
if (lower.startsWith('gpt') || lower.startsWith('o1') || lower.startsWith('o3') || lower.startsWith('codex')) {
|
|
965
|
-
return 'openai';
|
|
966
|
-
}
|
|
967
|
-
if (lower.startsWith('gemini')) {
|
|
968
|
-
return 'google';
|
|
969
|
-
}
|
|
970
971
|
if (lower.startsWith('deepseek')) {
|
|
971
972
|
return 'deepseek';
|
|
972
973
|
}
|
|
973
|
-
if (lower.startsWith('grok')) {
|
|
974
|
-
return 'xai';
|
|
975
|
-
}
|
|
976
|
-
if (lower.startsWith('llama') || lower.startsWith('mistral') || lower.startsWith('qwen')) {
|
|
977
|
-
return 'ollama';
|
|
978
|
-
}
|
|
979
974
|
return null;
|
|
980
975
|
}
|
|
981
976
|
/**
|
|
@@ -1079,7 +1074,7 @@ class InteractiveShell {
|
|
|
1079
1074
|
// Clear loading message
|
|
1080
1075
|
this.promptController?.setStatusMessage(null);
|
|
1081
1076
|
// Show the interactive menu
|
|
1082
|
-
this.promptController?.setMenu(menuItems, { title: '
|
|
1077
|
+
this.promptController?.setMenu(menuItems, { title: 'Select Model' }, (selected) => {
|
|
1083
1078
|
if (selected) {
|
|
1084
1079
|
// Parse provider:model format
|
|
1085
1080
|
const [providerId, ...modelParts] = selected.id.split(':');
|
|
@@ -1110,16 +1105,8 @@ class InteractiveShell {
|
|
|
1110
1105
|
* Format model ID for display (shorten long IDs).
|
|
1111
1106
|
*/
|
|
1112
1107
|
formatModelLabel(modelId) {
|
|
1113
|
-
// Shorten common prefixes
|
|
1114
1108
|
let label = modelId
|
|
1115
|
-
.replace(/^
|
|
1116
|
-
.replace(/^gpt-/, 'GPT-')
|
|
1117
|
-
.replace(/^gemini-/, 'Gemini ')
|
|
1118
|
-
.replace(/^deepseek-/, 'DeepSeek ')
|
|
1119
|
-
.replace(/^grok-/, 'Grok ')
|
|
1120
|
-
.replace(/^llama/, 'Llama ')
|
|
1121
|
-
.replace(/^qwen-/, 'Qwen ');
|
|
1122
|
-
// Truncate if too long
|
|
1109
|
+
.replace(/^deepseek-/, 'DeepSeek ');
|
|
1123
1110
|
if (label.length > 30) {
|
|
1124
1111
|
label = label.slice(0, 27) + '...';
|
|
1125
1112
|
}
|
|
@@ -1148,7 +1135,7 @@ class InteractiveShell {
|
|
|
1148
1135
|
};
|
|
1149
1136
|
});
|
|
1150
1137
|
// Show the interactive menu
|
|
1151
|
-
this.promptController.setMenu(menuItems, { title: '
|
|
1138
|
+
this.promptController.setMenu(menuItems, { title: 'API Keys — Select to Configure' }, (selected) => {
|
|
1152
1139
|
if (selected) {
|
|
1153
1140
|
// Start secret input for selected key
|
|
1154
1141
|
this.promptForSecret(selected.id);
|
|
@@ -1262,7 +1249,7 @@ class InteractiveShell {
|
|
|
1262
1249
|
}
|
|
1263
1250
|
showHelp() {
|
|
1264
1251
|
if (!this.promptController?.supportsInlinePanel()) {
|
|
1265
|
-
this.promptController?.setStatusMessage('Help: /model /secrets /auto /
|
|
1252
|
+
this.promptController?.setStatusMessage('Help: /model /secrets /auto /keys /clear /exit');
|
|
1266
1253
|
setTimeout(() => this.promptController?.setStatusMessage(null), 3000);
|
|
1267
1254
|
return;
|
|
1268
1255
|
}
|
|
@@ -1281,18 +1268,16 @@ class InteractiveShell {
|
|
|
1281
1268
|
cmd('/debug') + dim(' Toggle debug mode'),
|
|
1282
1269
|
cmd('/adversarial') + dim(' Toggle the adversarial verifier (default on)'),
|
|
1283
1270
|
cmd('/ultracode') + dim(' Toggle ultracode operating mode (default on)'),
|
|
1284
|
-
cmd('/stats') + dim(' Show session token + cost stats'),
|
|
1285
1271
|
cmd('/keys') + dim(' Show keyboard shortcuts'),
|
|
1286
1272
|
cmd('/clear') + dim(' Clear the screen'),
|
|
1287
1273
|
cmd('/exit') + dim(' Quit'),
|
|
1288
1274
|
'',
|
|
1289
1275
|
heading('Quick start'),
|
|
1290
|
-
dim(' 1. /key sk-… (or set
|
|
1276
|
+
dim(' 1. /key sk-… (or set DEEPSEEK_API_KEY)'),
|
|
1291
1277
|
dim(' 2. Type any prompt; the agent reads files, edits, runs commands'),
|
|
1292
1278
|
dim(' 3. Ctrl+C interrupts an in-flight run'),
|
|
1293
1279
|
'',
|
|
1294
1280
|
dim('Launch: erosolar (interactive)'),
|
|
1295
|
-
dim(' erosolar "task" (interactive, pre-filled with the prompt)'),
|
|
1296
1281
|
dim('Docs: https://ero.solar/docs | README.md'),
|
|
1297
1282
|
];
|
|
1298
1283
|
this.promptController.setInlinePanel(lines);
|
|
@@ -1331,42 +1316,6 @@ class InteractiveShell {
|
|
|
1331
1316
|
this.promptController.setInlinePanel(lines);
|
|
1332
1317
|
this.scheduleInlinePanelDismiss();
|
|
1333
1318
|
}
|
|
1334
|
-
showSessionStats() {
|
|
1335
|
-
if (!this.promptController?.supportsInlinePanel()) {
|
|
1336
|
-
this.promptController?.setStatusMessage('Use /stats in interactive mode');
|
|
1337
|
-
setTimeout(() => this.promptController?.setStatusMessage(null), 3000);
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
const history = this.controller.getHistory();
|
|
1341
|
-
const messageCount = history.length;
|
|
1342
|
-
const userMessages = history.filter(m => m.role === 'user').length;
|
|
1343
|
-
const assistantMessages = history.filter(m => m.role === 'assistant').length;
|
|
1344
|
-
// Calculate approximate token usage from history
|
|
1345
|
-
let totalChars = 0;
|
|
1346
|
-
for (const msg of history) {
|
|
1347
|
-
if (typeof msg.content === 'string') {
|
|
1348
|
-
totalChars += msg.content.length;
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
const approxTokens = Math.round(totalChars / 4); // Rough estimate
|
|
1352
|
-
const collapsedCount = this.promptController?.getRenderer?.()?.getCollapsedResultCount?.() ?? 0;
|
|
1353
|
-
const lines = [
|
|
1354
|
-
chalk.bold.hex('#ece6da')('Session Stats') + chalk.dim(' (press any key to dismiss)'),
|
|
1355
|
-
'',
|
|
1356
|
-
chalk.hex('#cbf24e')('Conversation'),
|
|
1357
|
-
` ${chalk.white(messageCount.toString())} messages (${userMessages} user, ${assistantMessages} assistant)`,
|
|
1358
|
-
` ${chalk.dim('~')}${chalk.white(approxTokens.toLocaleString())} ${chalk.dim('tokens (estimate)')}`,
|
|
1359
|
-
'',
|
|
1360
|
-
chalk.hex('#cbf24e')('Model'),
|
|
1361
|
-
` ${chalk.white(this.profileConfig.model)} ${chalk.dim('on')} ${chalk.hex('#ff9352')(this.profileConfig.provider)}`,
|
|
1362
|
-
collapsedCount > 0 ? ` ${chalk.white(collapsedCount.toString())} collapsed results` : '',
|
|
1363
|
-
'',
|
|
1364
|
-
chalk.hex('#cbf24e')('Settings'),
|
|
1365
|
-
` Debug: ${this.debugEnabled ? chalk.green('on') : chalk.dim('off')}`,
|
|
1366
|
-
].filter(line => line !== '');
|
|
1367
|
-
this.promptController.setInlinePanel(lines);
|
|
1368
|
-
this.scheduleInlinePanelDismiss();
|
|
1369
|
-
}
|
|
1370
1319
|
/**
|
|
1371
1320
|
* Auto-dismiss inline panel after timeout or on next input.
|
|
1372
1321
|
*/
|
|
@@ -1462,11 +1411,11 @@ class InteractiveShell {
|
|
|
1462
1411
|
// Check for timeout marker
|
|
1463
1412
|
if (eventOrTimeout && typeof eventOrTimeout === 'object' && '__timeout' in eventOrTimeout) {
|
|
1464
1413
|
if (hitlDepth > 0) {
|
|
1465
|
-
this.promptController?.setStatusMessage('
|
|
1414
|
+
this.promptController?.setStatusMessage('Waiting for human decision…');
|
|
1466
1415
|
continue;
|
|
1467
1416
|
}
|
|
1468
1417
|
stepTimedOut = true;
|
|
1469
|
-
this.promptController?.setStatusMessage(
|
|
1418
|
+
this.promptController?.setStatusMessage(`Step timeout (${PROMPT_STEP_TIMEOUT_MS / 1000}s) — completing response`);
|
|
1470
1419
|
// Cancel the controller so the underlying agent stops generating
|
|
1471
1420
|
// events that would never be consumed. Without this the spinner
|
|
1472
1421
|
// can keep ticking against a "ghost" run after the for-await
|
|
@@ -1482,7 +1431,7 @@ class InteractiveShell {
|
|
|
1482
1431
|
const totalElapsed = Date.now() - promptStartTime;
|
|
1483
1432
|
if (!hasReceivedMeaningfulContent && totalElapsed > TOTAL_PROMPT_TIMEOUT_MS) {
|
|
1484
1433
|
if (renderer) {
|
|
1485
|
-
renderer.addEvent('response', chalk.yellow(`\
|
|
1434
|
+
renderer.addEvent('response', chalk.yellow(`\nResponse timeout (${Math.round(totalElapsed / 1000)}s) — completing\n`));
|
|
1486
1435
|
}
|
|
1487
1436
|
reasoningTimedOut = true;
|
|
1488
1437
|
try {
|
|
@@ -1576,7 +1525,6 @@ class InteractiveShell {
|
|
|
1576
1525
|
case 'tool.start': {
|
|
1577
1526
|
const toolName = event.toolName;
|
|
1578
1527
|
const args = event.parameters;
|
|
1579
|
-
let toolDisplay = `[${toolName}]`;
|
|
1580
1528
|
if (isHitlToolName(toolName)) {
|
|
1581
1529
|
hitlDepth += 1;
|
|
1582
1530
|
}
|
|
@@ -1586,98 +1534,34 @@ class InteractiveShell {
|
|
|
1586
1534
|
if (!toolsUsed.includes(toolName)) {
|
|
1587
1535
|
toolsUsed.push(toolName);
|
|
1588
1536
|
}
|
|
1589
|
-
const filePath = args?.['file_path'];
|
|
1590
|
-
if (filePath && (toolName
|
|
1537
|
+
const filePath = (args?.['file_path'] ?? args?.['path']);
|
|
1538
|
+
if (filePath && /edit|write|create|update/i.test(toolName)) {
|
|
1591
1539
|
if (!filesModified.includes(filePath)) {
|
|
1592
1540
|
filesModified.push(filePath);
|
|
1593
1541
|
}
|
|
1594
1542
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
}
|
|
1598
|
-
else if (toolName === 'Read' && args?.['file_path']) {
|
|
1599
|
-
toolDisplay += ` ${args['file_path']}`;
|
|
1600
|
-
}
|
|
1601
|
-
else if (toolName === 'Write' && args?.['file_path']) {
|
|
1602
|
-
toolDisplay += ` ${args['file_path']}`;
|
|
1603
|
-
}
|
|
1604
|
-
else if (toolName === 'Edit' && args?.['file_path']) {
|
|
1605
|
-
toolDisplay += ` ${args['file_path']}`;
|
|
1606
|
-
}
|
|
1607
|
-
else if (toolName === 'Search' && args?.['pattern']) {
|
|
1608
|
-
toolDisplay += ` ${args['pattern']}`;
|
|
1609
|
-
}
|
|
1610
|
-
else if (toolName === 'Grep' && args?.['pattern']) {
|
|
1611
|
-
toolDisplay += ` ${args['pattern']}`;
|
|
1612
|
-
}
|
|
1613
|
-
else if (toolName === 'WebSearch' && args?.['query']) {
|
|
1614
|
-
// Surface the query so the user can see exactly what the
|
|
1615
|
-
// agent is searching for. Without this, every web-search
|
|
1616
|
-
// turn looked like an opaque "[WebSearch]" in scrollback.
|
|
1617
|
-
toolDisplay = `🌐 WebSearch: "${String(args['query']).slice(0, 80)}"`;
|
|
1618
|
-
}
|
|
1619
|
-
else if (toolName === 'WebExtract') {
|
|
1620
|
-
const urlsArg = args?.['urls'];
|
|
1621
|
-
const urls = Array.isArray(urlsArg)
|
|
1622
|
-
? urlsArg.filter((u) => typeof u === 'string')
|
|
1623
|
-
: typeof args?.['url'] === 'string'
|
|
1624
|
-
? [args['url']]
|
|
1625
|
-
: [];
|
|
1626
|
-
const display = urls.length > 0
|
|
1627
|
-
? urls.length === 1 ? urls[0] : `${urls[0]} (+${urls.length - 1} more)`
|
|
1628
|
-
: '...';
|
|
1629
|
-
toolDisplay = `🌐 WebExtract: ${display}`;
|
|
1630
|
-
}
|
|
1543
|
+
// Claude-Code action line: `⏺ ToolName(primaryArg)`. The dim
|
|
1544
|
+
// present-tense label drives the working spinner above the prompt.
|
|
1631
1545
|
if (renderer) {
|
|
1632
|
-
renderer.addEvent('tool',
|
|
1633
|
-
}
|
|
1634
|
-
// Provide explanatory status messages for different tool types
|
|
1635
|
-
let statusMsg = '';
|
|
1636
|
-
if (toolName === 'Bash') {
|
|
1637
|
-
statusMsg = `Running: ${args?.['command'] ? String(args['command']).slice(0, 40) : '...'}`;
|
|
1638
|
-
}
|
|
1639
|
-
else if (toolName === 'Edit' || toolName === 'Write') {
|
|
1640
|
-
statusMsg = `📝 Editing file: ${args?.['file_path'] || '...'}`;
|
|
1546
|
+
renderer.addEvent('tool', formatToolCall(toolName, args, this.workingDir));
|
|
1641
1547
|
}
|
|
1642
|
-
|
|
1643
|
-
statusMsg = `📖 Reading file: ${args?.['file_path'] || '...'}`;
|
|
1644
|
-
}
|
|
1645
|
-
else if (toolName === 'Search' || toolName === 'Grep') {
|
|
1646
|
-
statusMsg = `🔍 Searching: ${args?.['pattern'] ? String(args['pattern']).slice(0, 30) : '...'}`;
|
|
1647
|
-
}
|
|
1648
|
-
else if (toolName === 'WebSearch') {
|
|
1649
|
-
statusMsg = `🌐 Searching web: ${args?.['query'] ? String(args['query']).slice(0, 40) : '...'}`;
|
|
1650
|
-
}
|
|
1651
|
-
else if (toolName === 'WebExtract') {
|
|
1652
|
-
const urlsArg = args?.['urls'];
|
|
1653
|
-
const firstUrl = Array.isArray(urlsArg)
|
|
1654
|
-
? urlsArg.find((u) => typeof u === 'string')
|
|
1655
|
-
: typeof args?.['url'] === 'string' ? args['url'] : '...';
|
|
1656
|
-
statusMsg = `🌐 Extracting: ${String(firstUrl ?? '...').slice(0, 50)}`;
|
|
1657
|
-
}
|
|
1658
|
-
else {
|
|
1659
|
-
statusMsg = `🔧 Running ${toolName}...`;
|
|
1660
|
-
}
|
|
1661
|
-
this.promptController?.setStatusMessage(statusMsg);
|
|
1548
|
+
this.promptController?.setStatusMessage(toolActivityLabel(toolName, args, this.workingDir));
|
|
1662
1549
|
break;
|
|
1663
1550
|
}
|
|
1664
1551
|
case 'tool.complete': {
|
|
1665
1552
|
if (isHitlToolName(event.toolName)) {
|
|
1666
1553
|
hitlDepth = Math.max(0, hitlDepth - 1);
|
|
1667
1554
|
}
|
|
1668
|
-
// Clear the
|
|
1669
|
-
this.promptController?.setStatusMessage('Thinking
|
|
1555
|
+
// Clear the activity label; the agent is thinking again.
|
|
1556
|
+
this.promptController?.setStatusMessage('Thinking…');
|
|
1670
1557
|
// Reset reasoning timer after tool completes
|
|
1671
1558
|
reasoningOnlyStartTime = null;
|
|
1672
|
-
//
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
renderer.addEvent('tool', 'Done:');
|
|
1676
|
-
}
|
|
1677
|
-
// Pass full result to renderer - it handles display truncation
|
|
1678
|
-
// and stores full content for Ctrl+O expansion
|
|
1559
|
+
// Render the result as a dim ` ⎿ …` block (summarised, never a
|
|
1560
|
+
// raw multi-KB dump). Pre-formatted ⏺ blocks (editTools) pass
|
|
1561
|
+
// through with just their duplicate header stripped.
|
|
1679
1562
|
if (event.result && typeof event.result === 'string' && event.result.trim() && renderer) {
|
|
1680
|
-
|
|
1563
|
+
const params = event.parameters;
|
|
1564
|
+
renderer.addEvent('tool-result', formatToolResult(event.toolName, event.result, params));
|
|
1681
1565
|
}
|
|
1682
1566
|
break;
|
|
1683
1567
|
}
|
|
@@ -1685,10 +1569,10 @@ class InteractiveShell {
|
|
|
1685
1569
|
if (isHitlToolName(event.toolName)) {
|
|
1686
1570
|
hitlDepth = Math.max(0, hitlDepth - 1);
|
|
1687
1571
|
}
|
|
1688
|
-
|
|
1689
|
-
this.promptController?.setStatusMessage('Thinking...');
|
|
1572
|
+
this.promptController?.setStatusMessage('Thinking…');
|
|
1690
1573
|
if (renderer) {
|
|
1691
|
-
|
|
1574
|
+
// Red ` ⎿ Error: …` line, mirroring a failed tool result.
|
|
1575
|
+
renderer.addEvent('error', formatToolError(event.error));
|
|
1692
1576
|
}
|
|
1693
1577
|
break;
|
|
1694
1578
|
case 'error':
|
|
@@ -1740,7 +1624,7 @@ class InteractiveShell {
|
|
|
1740
1624
|
const reasoningElapsed = Date.now() - reasoningOnlyStartTime;
|
|
1741
1625
|
if (reasoningElapsed > PROMPT_REASONING_TIMEOUT_MS) {
|
|
1742
1626
|
if (renderer) {
|
|
1743
|
-
renderer.addEvent('response', chalk.yellow(`\
|
|
1627
|
+
renderer.addEvent('response', chalk.yellow(`\nReasoning timeout (${Math.round(reasoningElapsed / 1000)}s)\n`));
|
|
1744
1628
|
}
|
|
1745
1629
|
reasoningTimedOut = true;
|
|
1746
1630
|
}
|
|
@@ -1969,6 +1853,21 @@ class InteractiveShell {
|
|
|
1969
1853
|
this.promptController?.setStatusMessage(`HITL: ${mode}`);
|
|
1970
1854
|
setTimeout(() => this.promptController?.setStatusMessage(null), 1500);
|
|
1971
1855
|
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Shift+Tab cycled the permission mode. The hint line under the input box
|
|
1858
|
+
* already shows the active mode; this surfaces a brief one-line note in
|
|
1859
|
+
* the chat so the change is unmistakable, matching how Claude Code echoes
|
|
1860
|
+
* a mode switch.
|
|
1861
|
+
*/
|
|
1862
|
+
handlePermissionModeChange(mode) {
|
|
1863
|
+
const note = mode === 'plan'
|
|
1864
|
+
? 'plan mode — read-only; I won’t edit files or run commands until you approve a plan'
|
|
1865
|
+
: mode === 'acceptEdits'
|
|
1866
|
+
? 'accept edits on — file edits apply without the adversarial pre-flight'
|
|
1867
|
+
: 'default mode';
|
|
1868
|
+
this.promptController?.setStatusMessage(note);
|
|
1869
|
+
setTimeout(() => this.promptController?.setStatusMessage(null), 2500);
|
|
1870
|
+
}
|
|
1972
1871
|
handleCtrlC(info) {
|
|
1973
1872
|
const now = Date.now();
|
|
1974
1873
|
// Reset count if more than 2 seconds since last Ctrl+C
|
|
@@ -2025,23 +1924,6 @@ class InteractiveShell {
|
|
|
2025
1924
|
});
|
|
2026
1925
|
}
|
|
2027
1926
|
}
|
|
2028
|
-
function parseArgs(argv) {
|
|
2029
|
-
const promptTokens = [];
|
|
2030
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
2031
|
-
const token = argv[index];
|
|
2032
|
-
if (!token) {
|
|
2033
|
-
continue;
|
|
2034
|
-
}
|
|
2035
|
-
// Skip known flags
|
|
2036
|
-
if (token.startsWith('--') || token.startsWith('-')) {
|
|
2037
|
-
continue;
|
|
2038
|
-
}
|
|
2039
|
-
promptTokens.push(token);
|
|
2040
|
-
}
|
|
2041
|
-
return {
|
|
2042
|
-
initialPrompt: promptTokens.length ? promptTokens.join(' ').trim() : null,
|
|
2043
|
-
};
|
|
2044
|
-
}
|
|
2045
1927
|
// The --profile / -p flag was removed; the only call site passes nothing.
|
|
2046
1928
|
// We retain the function as a single source of truth for the hardcoded
|
|
2047
1929
|
// profile name that downstream config (agent prompt, model, rulebook)
|