erosolar-cli 2.1.49 → 2.1.51

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.
Files changed (40) hide show
  1. package/dist/capabilities/askUserCapability.d.ts +3 -10
  2. package/dist/capabilities/askUserCapability.d.ts.map +1 -1
  3. package/dist/capabilities/askUserCapability.js +29 -33
  4. package/dist/capabilities/askUserCapability.js.map +1 -1
  5. package/dist/core/agent.d.ts +1 -1
  6. package/dist/core/agent.d.ts.map +1 -1
  7. package/dist/core/agent.js +14 -4
  8. package/dist/core/agent.js.map +1 -1
  9. package/dist/runtime/agentController.js +2 -2
  10. package/dist/runtime/agentController.js.map +1 -1
  11. package/dist/shell/interactiveShell.d.ts +7 -0
  12. package/dist/shell/interactiveShell.d.ts.map +1 -1
  13. package/dist/shell/interactiveShell.js +398 -216
  14. package/dist/shell/interactiveShell.js.map +1 -1
  15. package/dist/tools/grepTools.d.ts.map +1 -1
  16. package/dist/tools/grepTools.js +51 -8
  17. package/dist/tools/grepTools.js.map +1 -1
  18. package/dist/tools/interactionTools.d.ts +3 -10
  19. package/dist/tools/interactionTools.d.ts.map +1 -1
  20. package/dist/tools/interactionTools.js +32 -32
  21. package/dist/tools/interactionTools.js.map +1 -1
  22. package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
  23. package/dist/ui/ShellUIAdapter.js +29 -1
  24. package/dist/ui/ShellUIAdapter.js.map +1 -1
  25. package/dist/ui/UnifiedUIRenderer.d.ts +5 -2
  26. package/dist/ui/UnifiedUIRenderer.d.ts.map +1 -1
  27. package/dist/ui/UnifiedUIRenderer.js +43 -29
  28. package/dist/ui/UnifiedUIRenderer.js.map +1 -1
  29. package/dist/ui/display.d.ts +8 -0
  30. package/dist/ui/display.d.ts.map +1 -1
  31. package/dist/ui/display.js +43 -0
  32. package/dist/ui/display.js.map +1 -1
  33. package/dist/ui/toolDisplay.d.ts.map +1 -1
  34. package/dist/ui/toolDisplay.js +22 -1
  35. package/dist/ui/toolDisplay.js.map +1 -1
  36. package/dist/utils/askUserPrompt.d.ts +21 -0
  37. package/dist/utils/askUserPrompt.d.ts.map +1 -0
  38. package/dist/utils/askUserPrompt.js +76 -0
  39. package/dist/utils/askUserPrompt.js.map +1 -0
  40. package/package.json +1 -1
@@ -2,10 +2,11 @@ import { stdin as input, stdout as output, exit } from 'node:process';
2
2
  import { homedir } from 'node:os';
3
3
  import { exec } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
6
- import { join } from 'node:path';
5
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
6
+ import { join, resolve } from 'node:path';
7
7
  import { display } from '../ui/display.js';
8
8
  import { theme } from '../ui/theme.js';
9
+ import { getTerminalColumns } from '../ui/layout.js';
9
10
  import { StreamingResponseFormatter } from '../ui/streamingFormatter.js';
10
11
  import { getContextWindowTokens } from '../core/contextWindow.js';
11
12
  import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue, listSecretDefinitions, maskSecret, setSecretValue, } from '../core/secretStore.js';
@@ -82,6 +83,8 @@ const CONTEXT_CLEANUP_SYSTEM_PROMPT = `Summarize earlier IDE collaboration so th
82
83
  - Capture decisions, file edits/paths, tool findings, and open questions.
83
84
  - Separate finished work from follow-ups; keep it under ~180 words with tight bullets.
84
85
  - Respond in plain Markdown only (no tool or shell calls).`;
86
+ const MAX_ATTACHMENT_BYTES = 200 * 1024; // 200KB per attachment
87
+ const MAX_ATTACHMENT_CHARS = 16000; // Guardrail to avoid flooding context
85
88
  export class InteractiveShell {
86
89
  agent = null;
87
90
  profile;
@@ -175,6 +178,8 @@ export class InteractiveShell {
175
178
  statusLineState = null;
176
179
  statusMessageOverride = null;
177
180
  lastPromptBlockNotice = null;
181
+ inlineCommandActive = false;
182
+ inlinePanelScopeActive = false;
178
183
  promptRefreshTimer = null;
179
184
  launchPaletteShown = false;
180
185
  version;
@@ -242,6 +247,11 @@ export class InteractiveShell {
242
247
  description: 'Show available and loaded plugins',
243
248
  category: 'configuration',
244
249
  });
250
+ this.slashCommands.push({
251
+ command: '/attach',
252
+ description: 'Attach a local text file into the conversation (truncated, text-only)',
253
+ category: 'context',
254
+ });
245
255
  this.slashCommands.push({
246
256
  command: '/approvals',
247
257
  description: 'Switch between auto and approval mode for high-impact actions',
@@ -289,6 +299,17 @@ export class InteractiveShell {
289
299
  // Share renderer with Display so all output flows through the unified queue
290
300
  this.renderer = this.terminalInput.getRenderer();
291
301
  display.setRenderer(this.renderer);
302
+ display.setInlinePanelHandler((content) => {
303
+ if (!this.shouldCaptureInlinePanel()) {
304
+ return false;
305
+ }
306
+ const lines = content
307
+ .split('\n')
308
+ .map((line) => line.trimEnd())
309
+ .filter((line) => line.trim().length > 0);
310
+ this.showInlinePanel(lines);
311
+ return true;
312
+ });
292
313
  // Initialize Alpha Zero 2 metrics tracking
293
314
  this.alphaZeroMetrics = new MetricsTracker(`${this.profile}-${Date.now()}`);
294
315
  this.setupStatusTracking();
@@ -388,32 +409,47 @@ export class InteractiveShell {
388
409
  const stripAnsi = (value) => value.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '');
389
410
  const padCenter = (text, width) => {
390
411
  const len = stripAnsi(text).length;
391
- const left = Math.max(0, Math.floor((width - len) / 2));
392
- const right = Math.max(0, width - len - left);
412
+ if (len >= width)
413
+ return text;
414
+ const left = Math.floor((width - len) / 2);
415
+ const right = width - len - left;
393
416
  return ' '.repeat(left) + text + ' '.repeat(right);
394
417
  };
395
- const width = 62;
396
- const top = `╭─${' Claude Code '.padEnd(width - 2, '─')}╮`;
397
- const bottom = `╰${'─'.repeat(width)}╯`;
418
+ const targetWidth = 62;
419
+ const terminalWidth = getTerminalColumns();
420
+ const availableWidth = Number.isFinite(terminalWidth) && terminalWidth > 0
421
+ ? Math.max(28, terminalWidth - 4)
422
+ : targetWidth;
423
+ const contentWidth = Math.max(28, Math.min(targetWidth, Math.min(availableWidth, 84)));
424
+ const clamp = (value) => {
425
+ const normalized = stripAnsi(value);
426
+ if (normalized.length <= contentWidth)
427
+ return value;
428
+ if (contentWidth <= 1)
429
+ return normalized.slice(0, contentWidth);
430
+ return `${normalized.slice(0, contentWidth - 1)}…`;
431
+ };
432
+ const brand = 'Erosolar CLI';
433
+ const label = ` ${brand} `;
434
+ const labelLength = stripAnsi(label).length;
435
+ const labelFill = Math.max(0, contentWidth - labelLength);
436
+ const labelLeft = Math.floor(labelFill / 2);
437
+ const labelRight = labelFill - labelLeft;
438
+ const top = `╭${'─'.repeat(labelLeft)}${label}${'─'.repeat(labelRight)}╮`;
439
+ const bottom = `╰${'─'.repeat(contentWidth)}╯`;
398
440
  const userName = process.env['USER'] || 'there';
399
- const logoLines = [
400
- '╭╼─╾╮ ╭╼─╾╮',
401
- '╰╮✺╭╯ ╰╮✺╭╯',
402
- '╭╯✹╰╮ ╭╯✹╰╮',
403
- '╰╼─╾╯ ╰╼─╾╯',
404
- ];
405
- const framedLogo = logoLines.map((line) => frame(`│${padCenter(line, width)}│`));
441
+ const logo = clamp('⟣╍◎╍⟢');
442
+ const versionLabel = version ? clamp(`${brand} · v${version}`) : brand;
443
+ const modelLine = clamp(model);
444
+ const workspaceLine = clamp(workspace);
445
+ const welcomeLine = clamp(`Welcome back ${userName}!`);
406
446
  const lines = [
407
447
  frame(top),
408
- frame(`│${padCenter('', width)}│`),
409
- frame(`│${padCenter(`Welcome back ${userName}!`, width)}│`),
410
- frame(`│${padCenter('', width)}│`),
411
- ...framedLogo,
412
- frame(`│${padCenter('', width)}│`),
413
- frame(`│${padCenter(model, width)}│`),
414
- frame(`│${padCenter('Erosolar CLI', width)}│`),
415
- frame(`│${padCenter(workspace, width)}│`),
416
- frame(`│${padCenter('', width)}│`),
448
+ frame(`│${padCenter(welcomeLine, contentWidth)}│`),
449
+ frame(`│${padCenter(logo, contentWidth)}│`),
450
+ frame(`│${padCenter(modelLine, contentWidth)}│`),
451
+ frame(`│${padCenter(versionLabel, contentWidth)}│`),
452
+ frame(`│${padCenter(workspaceLine, contentWidth)}│`),
417
453
  frame(bottom),
418
454
  ].join('\n');
419
455
  if (this.renderer) {
@@ -985,6 +1021,7 @@ export class InteractiveShell {
985
1021
  if (normalized === 'cancel') {
986
1022
  this.pendingInteraction = null;
987
1023
  display.showInfo('Tool selection cancelled.');
1024
+ this.clearInlinePanel();
988
1025
  this.syncRendererInput();
989
1026
  return;
990
1027
  }
@@ -997,6 +1034,7 @@ export class InteractiveShell {
997
1034
  if (normalized === 'save') {
998
1035
  await this.persistToolSelection(pending);
999
1036
  this.pendingInteraction = null;
1037
+ this.clearInlinePanel();
1000
1038
  this.syncRendererInput();
1001
1039
  return;
1002
1040
  }
@@ -1064,6 +1102,7 @@ export class InteractiveShell {
1064
1102
  if (trimmed.toLowerCase() === 'cancel') {
1065
1103
  this.pendingInteraction = null;
1066
1104
  display.showInfo('Agent selection cancelled.');
1105
+ this.clearInlinePanel();
1067
1106
  this.syncRendererInput();
1068
1107
  return;
1069
1108
  }
@@ -1081,6 +1120,7 @@ export class InteractiveShell {
1081
1120
  }
1082
1121
  await this.persistAgentSelection(option.name);
1083
1122
  this.pendingInteraction = null;
1123
+ this.clearInlinePanel();
1084
1124
  this.syncRendererInput();
1085
1125
  }
1086
1126
  async persistAgentSelection(profileName) {
@@ -1761,13 +1801,16 @@ export class InteractiveShell {
1761
1801
  this.refreshStatusLine();
1762
1802
  }
1763
1803
  }
1764
- handleStreamChunk(chunk) {
1804
+ handleStreamChunk(chunk, type = 'content') {
1765
1805
  if (!chunk) {
1766
1806
  return;
1767
1807
  }
1808
+ const isReasoning = type === 'reasoning';
1809
+ // Keep pinned status updated for all streaming chunks
1810
+ this.updateStreamingStatusFromChunk(chunk);
1768
1811
  // Suppress raw streaming output in scrollback; keep only status + final message.
1769
- if (this.streamingOutputSuppressed) {
1770
- this.updateStreamingStatusFromChunk(chunk);
1812
+ // Reasoning chunks bypass this so the thought process stays visible.
1813
+ if (this.streamingOutputSuppressed && !isReasoning) {
1771
1814
  this.captureStreamingThought(chunk);
1772
1815
  this.streamingContentSeen = true;
1773
1816
  return;
@@ -1776,8 +1819,10 @@ export class InteractiveShell {
1776
1819
  this.streamingFormatter = new StreamingResponseFormatter(output.columns ?? undefined);
1777
1820
  this.pushUiEvent('streaming', this.streamingFormatter.header());
1778
1821
  }
1779
- this.updateStreamingStatusFromChunk(chunk);
1780
- const formatted = this.streamingFormatter.formatChunk(chunk);
1822
+ this.streamingFormatter.setMode(isReasoning ? 'reasoning' : 'content');
1823
+ const formatted = isReasoning
1824
+ ? this.streamingFormatter.formatReasoningChunk(chunk)
1825
+ : this.streamingFormatter.formatChunk(chunk);
1781
1826
  this.captureStreamingThought(chunk);
1782
1827
  if (formatted) {
1783
1828
  if (formatted.trim().length > 0) {
@@ -2078,181 +2123,195 @@ export class InteractiveShell {
2078
2123
  this.syncRendererInput();
2079
2124
  return;
2080
2125
  }
2081
- switch (command) {
2082
- case '/help':
2083
- case '/?':
2084
- this.showHelp();
2085
- break;
2086
- case '/features':
2087
- this.showFeaturesMenu(input);
2088
- break;
2089
- case '/approvals':
2090
- this.handleApprovalsCommand(input);
2091
- break;
2092
- case '/learn':
2093
- this.showLearningStatus(input);
2094
- break;
2095
- case '/improve':
2096
- void this.handleImprovementCommand(input);
2097
- break;
2098
- case '/model':
2099
- this.showModelMenu();
2100
- break;
2101
- case '/exit':
2102
- case '/quit':
2103
- case '/q':
2104
- this.shutdown();
2105
- break;
2106
- case '/secrets':
2107
- this.showSecretsMenu();
2108
- break;
2109
- case '/tools':
2110
- this.showToolsMenu();
2111
- break;
2112
- case '/mcp':
2113
- await this.showMcpStatus();
2114
- break;
2115
- case '/doctor':
2116
- this.runDoctor();
2117
- break;
2118
- case '/checks':
2119
- await this.runRepoChecksCommand();
2120
- break;
2121
- case '/context':
2122
- await this.refreshWorkspaceContextCommand(input);
2123
- break;
2124
- case '/agents':
2125
- this.showAgentsMenu();
2126
- break;
2127
- case '/sessions':
2128
- await this.handleSessionCommand(input);
2129
- break;
2130
- case '/skills':
2131
- await this.handleSkillsCommand(input);
2132
- break;
2133
- case '/thinking':
2134
- this.handleThinkingCommand(input);
2135
- break;
2136
- case '/autocontinue':
2137
- this.handleAutoContinueCommand(input);
2138
- break;
2139
- case '/shortcuts':
2140
- case '/keys':
2141
- this.handleShortcutsCommand();
2142
- break;
2143
- case '/changes':
2144
- case '/summary':
2145
- this.showFileChangeSummary();
2146
- break;
2147
- case '/metrics':
2148
- case '/stats':
2149
- case '/perf':
2150
- this.showAlphaZeroMetrics();
2151
- break;
2152
- case '/suggestions':
2153
- this.showImprovementSuggestions();
2154
- break;
2155
- case '/plugins':
2156
- this.showPluginStatus();
2157
- break;
2158
- case '/evolve':
2159
- void this.handleEvolveCommand(input);
2160
- break;
2161
- case '/modular':
2162
- case '/a0':
2163
- void this.handleModularCommand(input);
2164
- break;
2165
- case '/offsec':
2166
- void this.handleOffsecCommand(input);
2167
- break;
2168
- case '/test':
2169
- case '/tests':
2170
- void this.handleTestCommand(input);
2171
- break;
2172
- case '/provider':
2173
- await this.handleProviderCommand(input);
2174
- break;
2175
- case '/providers':
2176
- this.showConfiguredProviders();
2177
- break;
2178
- case '/local':
2179
- await this.handleLocalCommand(input);
2180
- break;
2181
- case '/discover':
2182
- await this.discoverModelsCommand();
2183
- break;
2184
- // Erosolar-CLI style commands
2185
- case '/rewind':
2186
- await this.handleRewindCommand(input);
2187
- break;
2188
- case '/memory':
2189
- this.handleMemoryCommand(input);
2190
- break;
2191
- case '/vim':
2192
- this.handleVimCommand();
2193
- break;
2194
- case '/output-style':
2195
- this.handleOutputStyleCommand(input);
2196
- break;
2197
- case '/cost':
2198
- this.handleCostCommand();
2199
- break;
2200
- case '/usage':
2201
- this.handleUsageCommand(input);
2202
- break;
2203
- case '/clear':
2204
- this.handleClearCommand();
2205
- break;
2206
- case '/resume':
2207
- await this.handleResumeCommand(input);
2208
- break;
2209
- case '/export':
2210
- this.handleExportCommand(input);
2211
- break;
2212
- case '/review':
2213
- await this.handleReviewCommand();
2214
- break;
2215
- case '/security-review':
2216
- await this.handleSecurityReviewCommand();
2217
- break;
2218
- case '/new':
2219
- this.handleClearCommand();
2220
- break;
2221
- case '/undo':
2222
- await this.handleRewindCommand(input);
2223
- break;
2224
- case '/diff':
2225
- await this.handleDiffCommand();
2226
- break;
2227
- case '/mention':
2228
- this.handleMentionCommand();
2229
- break;
2230
- case '/status':
2231
- this.handleStatusCommand();
2232
- break;
2233
- case '/bug':
2234
- this.handleBugCommand();
2235
- break;
2236
- case '/terminal-setup':
2237
- this.handleTerminalSetupCommand();
2238
- break;
2239
- case '/permissions':
2240
- this.handlePermissionsCommand();
2241
- break;
2242
- case '/init':
2243
- this.handleInitCommand(input);
2244
- break;
2245
- case '/compact':
2246
- await this.handleCompactCommand();
2247
- break;
2248
- case '/logout':
2249
- this.handleLogoutCommand();
2250
- break;
2251
- default:
2252
- if (!(await this.tryCustomSlashCommand(command, input))) {
2253
- display.showWarning(`Unknown command "${command}".`);
2254
- }
2255
- break;
2126
+ this.clearInlinePanel();
2127
+ this.inlinePanelScopeActive = true;
2128
+ this.inlineCommandActive = true;
2129
+ try {
2130
+ switch (command) {
2131
+ case '/help':
2132
+ case '/?':
2133
+ this.showHelp();
2134
+ break;
2135
+ case '/features':
2136
+ this.showFeaturesMenu(input);
2137
+ break;
2138
+ case '/approvals':
2139
+ this.handleApprovalsCommand(input);
2140
+ break;
2141
+ case '/learn':
2142
+ this.showLearningStatus(input);
2143
+ break;
2144
+ case '/improve':
2145
+ void this.handleImprovementCommand(input);
2146
+ break;
2147
+ case '/model':
2148
+ this.showModelMenu();
2149
+ break;
2150
+ case '/exit':
2151
+ case '/quit':
2152
+ case '/q':
2153
+ this.shutdown();
2154
+ break;
2155
+ case '/secrets':
2156
+ this.showSecretsMenu();
2157
+ break;
2158
+ case '/tools':
2159
+ this.showToolsMenu();
2160
+ break;
2161
+ case '/mcp':
2162
+ await this.showMcpStatus();
2163
+ break;
2164
+ case '/doctor':
2165
+ this.runDoctor();
2166
+ break;
2167
+ case '/checks':
2168
+ await this.runRepoChecksCommand();
2169
+ break;
2170
+ case '/attach':
2171
+ await this.handleAttachCommand(input);
2172
+ break;
2173
+ case '/context':
2174
+ await this.refreshWorkspaceContextCommand(input);
2175
+ break;
2176
+ case '/agents':
2177
+ this.showAgentsMenu();
2178
+ break;
2179
+ case '/sessions':
2180
+ await this.handleSessionCommand(input);
2181
+ break;
2182
+ case '/skills':
2183
+ await this.handleSkillsCommand(input);
2184
+ break;
2185
+ case '/thinking':
2186
+ this.handleThinkingCommand(input);
2187
+ break;
2188
+ case '/autocontinue':
2189
+ this.handleAutoContinueCommand(input);
2190
+ break;
2191
+ case '/shortcuts':
2192
+ case '/keys':
2193
+ this.handleShortcutsCommand();
2194
+ break;
2195
+ case '/changes':
2196
+ case '/summary':
2197
+ this.showFileChangeSummary();
2198
+ break;
2199
+ case '/metrics':
2200
+ case '/stats':
2201
+ case '/perf':
2202
+ this.showAlphaZeroMetrics();
2203
+ break;
2204
+ case '/suggestions':
2205
+ this.showImprovementSuggestions();
2206
+ break;
2207
+ case '/plugins':
2208
+ this.showPluginStatus();
2209
+ break;
2210
+ case '/evolve':
2211
+ void this.handleEvolveCommand(input);
2212
+ break;
2213
+ case '/modular':
2214
+ case '/a0':
2215
+ void this.handleModularCommand(input);
2216
+ break;
2217
+ case '/offsec':
2218
+ void this.handleOffsecCommand(input);
2219
+ break;
2220
+ case '/test':
2221
+ case '/tests':
2222
+ void this.handleTestCommand(input);
2223
+ break;
2224
+ case '/provider':
2225
+ await this.handleProviderCommand(input);
2226
+ break;
2227
+ case '/providers':
2228
+ this.showConfiguredProviders();
2229
+ break;
2230
+ case '/local':
2231
+ await this.handleLocalCommand(input);
2232
+ break;
2233
+ case '/discover':
2234
+ await this.discoverModelsCommand();
2235
+ break;
2236
+ // Erosolar-CLI style commands
2237
+ case '/rewind':
2238
+ await this.handleRewindCommand(input);
2239
+ break;
2240
+ case '/memory':
2241
+ this.handleMemoryCommand(input);
2242
+ break;
2243
+ case '/vim':
2244
+ this.handleVimCommand();
2245
+ break;
2246
+ case '/output-style':
2247
+ this.handleOutputStyleCommand(input);
2248
+ break;
2249
+ case '/cost':
2250
+ this.handleCostCommand();
2251
+ break;
2252
+ case '/usage':
2253
+ this.handleUsageCommand(input);
2254
+ break;
2255
+ case '/clear':
2256
+ this.handleClearCommand();
2257
+ break;
2258
+ case '/resume':
2259
+ await this.handleResumeCommand(input);
2260
+ break;
2261
+ case '/export':
2262
+ this.handleExportCommand(input);
2263
+ break;
2264
+ case '/review':
2265
+ await this.handleReviewCommand();
2266
+ break;
2267
+ case '/security-review':
2268
+ await this.handleSecurityReviewCommand();
2269
+ break;
2270
+ case '/new':
2271
+ this.handleClearCommand();
2272
+ break;
2273
+ case '/undo':
2274
+ await this.handleRewindCommand(input);
2275
+ break;
2276
+ case '/diff':
2277
+ await this.handleDiffCommand();
2278
+ break;
2279
+ case '/mention':
2280
+ this.handleMentionCommand();
2281
+ break;
2282
+ case '/status':
2283
+ this.handleStatusCommand();
2284
+ break;
2285
+ case '/bug':
2286
+ this.handleBugCommand();
2287
+ break;
2288
+ case '/terminal-setup':
2289
+ this.handleTerminalSetupCommand();
2290
+ break;
2291
+ case '/permissions':
2292
+ this.handlePermissionsCommand();
2293
+ break;
2294
+ case '/init':
2295
+ this.handleInitCommand(input);
2296
+ break;
2297
+ case '/compact':
2298
+ await this.handleCompactCommand();
2299
+ break;
2300
+ case '/logout':
2301
+ this.handleLogoutCommand();
2302
+ break;
2303
+ default:
2304
+ if (!(await this.tryCustomSlashCommand(command, input))) {
2305
+ display.showWarning(`Unknown command "${command}".`);
2306
+ }
2307
+ break;
2308
+ }
2309
+ }
2310
+ finally {
2311
+ this.inlineCommandActive = false;
2312
+ if (!this.pendingInteraction) {
2313
+ this.inlinePanelScopeActive = false;
2314
+ }
2256
2315
  }
2257
2316
  this.syncRendererInput();
2258
2317
  }
@@ -2580,6 +2639,104 @@ export class InteractiveShell {
2580
2639
  : `${theme.info('Thinking off')} (Tab to toggle)`;
2581
2640
  display.showSystemMessage(`${headline}\n${theme.ui.muted(descriptions[this.thinkingMode])}`);
2582
2641
  }
2642
+ async handleAttachCommand(input) {
2643
+ const paths = input
2644
+ .trim()
2645
+ .split(/\s+/)
2646
+ .slice(1)
2647
+ .filter(Boolean);
2648
+ if (!paths.length) {
2649
+ display.showInfo(`Usage: /attach <file> [...]. Text-only, max ${Math.round(MAX_ATTACHMENT_BYTES / 1024)}KB per file; large files are truncated.`);
2650
+ return;
2651
+ }
2652
+ if (this.isProcessing) {
2653
+ display.showWarning('Wait for the current request to finish before attaching files.');
2654
+ return;
2655
+ }
2656
+ if (!this.agent && !this.rebuildAgent()) {
2657
+ display.showWarning('Configure a provider via /secrets before attaching files.');
2658
+ return;
2659
+ }
2660
+ const agent = this.agent;
2661
+ if (!agent) {
2662
+ display.showWarning('No active agent is available. Try again in a moment.');
2663
+ return;
2664
+ }
2665
+ const history = agent.getHistory();
2666
+ const attachedSummaries = [];
2667
+ for (const rawPath of paths) {
2668
+ const filePath = resolve(this.workingDir, rawPath);
2669
+ if (!existsSync(filePath)) {
2670
+ display.showWarning(`File not found: ${rawPath}`);
2671
+ continue;
2672
+ }
2673
+ let stats;
2674
+ try {
2675
+ stats = statSync(filePath);
2676
+ }
2677
+ catch (error) {
2678
+ display.showWarning(`Unable to read file: ${rawPath} (${error instanceof Error ? error.message : 'unknown error'})`);
2679
+ continue;
2680
+ }
2681
+ if (!stats.isFile()) {
2682
+ display.showWarning(`Not a file: ${rawPath}`);
2683
+ continue;
2684
+ }
2685
+ if (stats.size > MAX_ATTACHMENT_BYTES) {
2686
+ display.showWarning(`Skipped ${rawPath} (${Math.round(stats.size / 1024)}KB) — limit is ${Math.round(MAX_ATTACHMENT_BYTES / 1024)}KB.`);
2687
+ continue;
2688
+ }
2689
+ let buffer;
2690
+ try {
2691
+ buffer = readFileSync(filePath);
2692
+ }
2693
+ catch (error) {
2694
+ display.showWarning(`Failed to read ${rawPath}: ${error instanceof Error ? error.message : String(error)}`);
2695
+ continue;
2696
+ }
2697
+ if (this.isBinaryBuffer(buffer)) {
2698
+ display.showWarning(`Skipped ${rawPath} — binary files are not attached.`);
2699
+ continue;
2700
+ }
2701
+ let content = buffer.toString('utf-8');
2702
+ let truncated = false;
2703
+ if (content.length > MAX_ATTACHMENT_CHARS) {
2704
+ content = content.slice(0, MAX_ATTACHMENT_CHARS);
2705
+ truncated = true;
2706
+ }
2707
+ const displayPath = this.abbreviatePath(filePath);
2708
+ const attachmentBlock = [
2709
+ `Attachment: ${displayPath}`,
2710
+ `Size: ${stats.size} bytes${truncated ? ` (truncated to ${MAX_ATTACHMENT_CHARS} chars)` : ''}`,
2711
+ '',
2712
+ '```text',
2713
+ content,
2714
+ '```',
2715
+ ].join('\n');
2716
+ history.push({ role: 'user', content: attachmentBlock });
2717
+ const kb = Math.max(1, Math.round(stats.size / 1024));
2718
+ attachedSummaries.push(`${displayPath} (${kb}KB${truncated ? ', truncated' : ''})`);
2719
+ }
2720
+ if (!attachedSummaries.length) {
2721
+ display.showWarning('No attachments were added. Ensure files exist, are under the size limit, and are text.');
2722
+ return;
2723
+ }
2724
+ agent.loadHistory(history);
2725
+ this.cachedHistory = history;
2726
+ this.refreshContextGauge();
2727
+ this.captureHistorySnapshot();
2728
+ this.autosaveIfEnabled();
2729
+ display.showSystemMessage(`Attached ${attachedSummaries.length} file${attachedSummaries.length === 1 ? '' : 's'}: ${attachedSummaries.join(', ')}`);
2730
+ }
2731
+ isBinaryBuffer(buffer) {
2732
+ const maxScan = Math.min(buffer.length, 8192);
2733
+ for (let i = 0; i < maxScan; i++) {
2734
+ if (buffer[i] === 0) {
2735
+ return true;
2736
+ }
2737
+ }
2738
+ return false;
2739
+ }
2583
2740
  handleShortcutsCommand() {
2584
2741
  // Display keyboard shortcuts help (Erosolar-CLI style)
2585
2742
  display.showSystemMessage(formatShortcutsHelp());
@@ -4517,6 +4674,16 @@ export class InteractiveShell {
4517
4674
  const suffix = values.length > maxItems ? ', …' : '';
4518
4675
  return `${shown.join(', ')}${suffix}`;
4519
4676
  }
4677
+ showInlinePanel(lines) {
4678
+ display.showInlinePanel(lines);
4679
+ }
4680
+ clearInlinePanel() {
4681
+ display.clearInlinePanel();
4682
+ }
4683
+ shouldCaptureInlinePanel() {
4684
+ return (this.inlinePanelScopeActive &&
4685
+ (this.inlineCommandActive || Boolean(this.pendingInteraction)));
4686
+ }
4520
4687
  buildSlashCommandList(header) {
4521
4688
  const lines = [theme.gradient.primary(header), ''];
4522
4689
  for (const command of this.slashCommands) {
@@ -4527,7 +4694,7 @@ export class InteractiveShell {
4527
4694
  async showModelMenu() {
4528
4695
  // Hold input immediately so numeric selections don't get queued as prompts while we fetch
4529
4696
  this.pendingInteraction = { type: 'model-loading' };
4530
- display.showSystemMessage(theme.ui.muted('Fetching latest models from providers...'));
4697
+ this.showInlinePanel([theme.ui.muted('Fetching latest models from providers...')]);
4531
4698
  try {
4532
4699
  // Fetch live models from all configured providers
4533
4700
  const providerStatuses = await quickCheckProviders();
@@ -4535,6 +4702,7 @@ export class InteractiveShell {
4535
4702
  if (!providerOptions.length) {
4536
4703
  display.showWarning('No providers are available.');
4537
4704
  this.pendingInteraction = null;
4705
+ this.clearInlinePanel();
4538
4706
  this.syncRendererInput();
4539
4707
  return;
4540
4708
  }
@@ -4550,12 +4718,13 @@ export class InteractiveShell {
4550
4718
  }),
4551
4719
  'Type the number of the provider to continue, or type "cancel".',
4552
4720
  ];
4553
- display.showSystemMessage(lines.join('\n'));
4721
+ this.showInlinePanel(lines);
4554
4722
  this.pendingInteraction = { type: 'model-provider', options: providerOptions };
4555
4723
  }
4556
4724
  catch (error) {
4557
4725
  display.showError('Failed to load model list. Try again in a moment.', error);
4558
4726
  this.pendingInteraction = null;
4727
+ this.clearInlinePanel();
4559
4728
  this.syncRendererInput();
4560
4729
  }
4561
4730
  }
@@ -4688,6 +4857,7 @@ export class InteractiveShell {
4688
4857
  if (!allModels.length) {
4689
4858
  display.showWarning(`No models available for ${option.label}.`);
4690
4859
  this.pendingInteraction = null;
4860
+ this.clearInlinePanel();
4691
4861
  return;
4692
4862
  }
4693
4863
  const lines = [
@@ -4703,7 +4873,7 @@ export class InteractiveShell {
4703
4873
  }),
4704
4874
  'Type the number of the model to select it, type "back" to change provider, or type "cancel".',
4705
4875
  ];
4706
- display.showSystemMessage(lines.join('\n'));
4876
+ this.showInlinePanel(lines);
4707
4877
  this.pendingInteraction = { type: 'model', provider: option.provider, options: allModels };
4708
4878
  }
4709
4879
  showSecretsMenu() {
@@ -4719,7 +4889,7 @@ export class InteractiveShell {
4719
4889
  }),
4720
4890
  'Enter the number to update a key, or type "cancel".',
4721
4891
  ];
4722
- display.showSystemMessage(lines.join('\n'));
4892
+ this.showInlinePanel(lines);
4723
4893
  this.pendingInteraction = { type: 'secret-select', options: definitions };
4724
4894
  }
4725
4895
  showToolsMenu() {
@@ -4745,7 +4915,7 @@ export class InteractiveShell {
4745
4915
  '',
4746
4916
  'Enter the number to toggle, "save" to persist, "defaults" to restore recommended tools, or "cancel".',
4747
4917
  ];
4748
- display.showSystemMessage(lines.join('\n'));
4918
+ this.showInlinePanel(lines);
4749
4919
  }
4750
4920
  formatToolOptionLine(option, index, selection) {
4751
4921
  const enabled = selection.has(option.id);
@@ -4857,7 +5027,7 @@ export class InteractiveShell {
4857
5027
  }
4858
5028
  lines.push('');
4859
5029
  lines.push(theme.ui.muted('Use /tools to enable/disable individual MCP tools.'));
4860
- display.showSystemMessage(lines.join('\n'));
5030
+ this.showInlinePanel(lines);
4861
5031
  }
4862
5032
  showMcpSetupGuide() {
4863
5033
  const lines = [];
@@ -4933,7 +5103,7 @@ export class InteractiveShell {
4933
5103
  lines.push(` ${theme.info('description')} - Human-readable description`);
4934
5104
  lines.push('');
4935
5105
  lines.push(`Run ${theme.primary('/mcp examples')} to see example configurations.`);
4936
- display.showSystemMessage(lines.join('\n'));
5106
+ this.showInlinePanel(lines);
4937
5107
  }
4938
5108
  showMcpExamples() {
4939
5109
  const lines = [];
@@ -5064,7 +5234,7 @@ export class InteractiveShell {
5064
5234
  lines.push(` ${theme.info('@anthropic/mcp-server-github')} - GitHub API`);
5065
5235
  lines.push('');
5066
5236
  lines.push(`More servers: ${theme.primary('https://github.com/modelcontextprotocol/servers')}`);
5067
- display.showSystemMessage(lines.join('\n'));
5237
+ this.showInlinePanel(lines);
5068
5238
  }
5069
5239
  showAgentsMenu() {
5070
5240
  if (!this.agentMenu) {
@@ -5077,7 +5247,7 @@ export class InteractiveShell {
5077
5247
  '',
5078
5248
  'Enter the number to save it, or type "cancel".',
5079
5249
  ];
5080
- display.showSystemMessage(lines.join('\n'));
5250
+ this.showInlinePanel(lines);
5081
5251
  this.pendingInteraction = { type: 'agent-selection', options: this.agentMenu.options };
5082
5252
  }
5083
5253
  formatAgentOptionLine(option, index) {
@@ -5117,6 +5287,7 @@ export class InteractiveShell {
5117
5287
  if (trimmed.toLowerCase() === 'cancel') {
5118
5288
  this.pendingInteraction = null;
5119
5289
  display.showInfo('Model selection cancelled.');
5290
+ this.clearInlinePanel();
5120
5291
  this.syncRendererInput();
5121
5292
  return;
5122
5293
  }
@@ -5154,6 +5325,7 @@ export class InteractiveShell {
5154
5325
  if (trimmed.toLowerCase() === 'cancel') {
5155
5326
  this.pendingInteraction = null;
5156
5327
  display.showInfo('Model selection cancelled.');
5328
+ this.clearInlinePanel();
5157
5329
  this.syncRendererInput();
5158
5330
  return;
5159
5331
  }
@@ -5195,6 +5367,7 @@ export class InteractiveShell {
5195
5367
  this.persistSessionPreference();
5196
5368
  this.resetChatBoxAfterModelSwap();
5197
5369
  }
5370
+ this.clearInlinePanel();
5198
5371
  }
5199
5372
  async handleSecretSelection(input) {
5200
5373
  const pending = this.pendingInteraction;
@@ -5210,6 +5383,7 @@ export class InteractiveShell {
5210
5383
  if (trimmed.toLowerCase() === 'cancel') {
5211
5384
  this.pendingInteraction = null;
5212
5385
  display.showInfo('Secret management cancelled.');
5386
+ this.clearInlinePanel();
5213
5387
  this.syncRendererInput();
5214
5388
  return;
5215
5389
  }
@@ -5225,7 +5399,7 @@ export class InteractiveShell {
5225
5399
  this.syncRendererInput();
5226
5400
  return;
5227
5401
  }
5228
- display.showSystemMessage(`Enter a new value for ${secret.label} or type "cancel".`);
5402
+ this.showInlinePanel([`Enter a new value for ${secret.label} or type "cancel".`]);
5229
5403
  this.pendingInteraction = { type: 'secret-input', secret };
5230
5404
  this.syncRendererInput();
5231
5405
  }
@@ -5244,6 +5418,7 @@ export class InteractiveShell {
5244
5418
  this.pendingInteraction = null;
5245
5419
  this.pendingSecretRetry = null;
5246
5420
  display.showInfo('Secret unchanged.');
5421
+ this.clearInlinePanel();
5247
5422
  this.syncRendererInput();
5248
5423
  return;
5249
5424
  }
@@ -5268,6 +5443,7 @@ export class InteractiveShell {
5268
5443
  this.pendingInteraction = null;
5269
5444
  this.pendingSecretRetry = null;
5270
5445
  }
5446
+ this.clearInlinePanel();
5271
5447
  this.syncRendererInput();
5272
5448
  }
5273
5449
  async processRequest(request) {
@@ -5279,6 +5455,8 @@ export class InteractiveShell {
5279
5455
  display.showWarning('Configure an API key via /secrets before sending requests.');
5280
5456
  return;
5281
5457
  }
5458
+ this.inlinePanelScopeActive = false;
5459
+ this.clearInlinePanel();
5282
5460
  const agent = this.agent;
5283
5461
  if (!agent) {
5284
5462
  return;
@@ -5418,6 +5596,8 @@ export class InteractiveShell {
5418
5596
  display.showWarning('Configure an API key via /secrets before sending requests.');
5419
5597
  return;
5420
5598
  }
5599
+ this.inlinePanelScopeActive = false;
5600
+ this.clearInlinePanel();
5421
5601
  const agent = this.agent;
5422
5602
  if (!agent) {
5423
5603
  return;
@@ -6209,8 +6389,8 @@ Return ONLY JSON array:
6209
6389
  autoContinue: this.autoContinueEnabled,
6210
6390
  };
6211
6391
  this.agent = this.runtimeSession.createAgent(selection, {
6212
- onStreamChunk: (chunk) => {
6213
- this.handleStreamChunk(chunk);
6392
+ onStreamChunk: (chunk, type) => {
6393
+ this.handleStreamChunk(chunk, type ?? 'content');
6214
6394
  },
6215
6395
  onStreamFallback: (info) => this.handleStreamingFallback(info),
6216
6396
  onAssistantMessage: (content, metadata) => {
@@ -6933,7 +7113,9 @@ Return ONLY JSON array:
6933
7113
  const detailText = info.message?.trim() ?? '';
6934
7114
  const detail = detailText ? ` ${detailText}` : '';
6935
7115
  const reason = info.reason ? ` (${info.reason.replace(/-/g, ' ')})` : '';
6936
- const partialNote = info.partialResponse ? ' Partial stream captured before failure.' : '';
7116
+ const partialNote = info.partialResponse
7117
+ ? ' Partial stream captured before failure; continuing from it without restarting.'
7118
+ : '';
6937
7119
  const baseMessage = 'Streaming interrupted, retrying without streaming';
6938
7120
  display.showWarning(`${baseMessage}${reason}.${detail}${partialNote}`.trim());
6939
7121
  this.finishStreamingFormatter('Stream interrupted - retrying without streaming', { mode: 'update' });