@openeryc/pi-coding-agent 0.75.53 → 0.75.55

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 (46) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/agent-session.d.ts +4 -2
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +30 -30
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/mcp/client.d.ts +16 -7
  10. package/dist/core/mcp/client.d.ts.map +1 -1
  11. package/dist/core/mcp/client.js +135 -107
  12. package/dist/core/mcp/client.js.map +1 -1
  13. package/dist/core/mcp/manager.d.ts +12 -1
  14. package/dist/core/mcp/manager.d.ts.map +1 -1
  15. package/dist/core/mcp/manager.js +88 -34
  16. package/dist/core/mcp/manager.js.map +1 -1
  17. package/dist/core/mcp/types.d.ts +0 -13
  18. package/dist/core/mcp/types.d.ts.map +1 -1
  19. package/dist/core/mcp/types.js.map +1 -1
  20. package/dist/core/package-manager.d.ts.map +1 -1
  21. package/dist/core/package-manager.js +3 -1
  22. package/dist/core/package-manager.js.map +1 -1
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +1 -1
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/modes/interactive/interactive-mode.d.ts +18 -0
  27. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  28. package/dist/modes/interactive/interactive-mode.js +360 -427
  29. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  30. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  31. package/dist/modes/rpc/rpc-client.js +5 -1
  32. package/dist/modes/rpc/rpc-client.js.map +1 -1
  33. package/dist/utils/sleep.d.ts.map +1 -1
  34. package/dist/utils/sleep.js +7 -3
  35. package/dist/utils/sleep.js.map +1 -1
  36. package/dist/utils/tools-manager.d.ts.map +1 -1
  37. package/dist/utils/tools-manager.js.map +1 -1
  38. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  39. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  40. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  41. package/examples/extensions/sandbox/package-lock.json +2 -2
  42. package/examples/extensions/sandbox/package.json +1 -1
  43. package/examples/extensions/with-deps/package-lock.json +2 -2
  44. package/examples/extensions/with-deps/package.json +1 -1
  45. package/npm-shrinkwrap.json +12 -12
  46. package/package.json +4 -4
@@ -153,6 +153,8 @@ export class InteractiveMode {
153
153
  hideThinkingBlock = false;
154
154
  // Skill commands: command name -> skill file path
155
155
  skillCommands = new Map();
156
+ /** Slash command dispatch map */
157
+ commands = new Map();
156
158
  // Agent subscription unsubscribe function
157
159
  unsubscribe;
158
160
  signalCleanupHandlers = [];
@@ -478,6 +480,7 @@ export class InteractiveMode {
478
480
  this.ui.setFocus(this.editor);
479
481
  this.setupKeyHandlers();
480
482
  this.setupEditorSubmitHandler();
483
+ this.initCommands();
481
484
  // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
482
485
  this.ui.start();
483
486
  this.isInitialized = true;
@@ -528,23 +531,29 @@ export class InteractiveMode {
528
531
  await this.goalRunnerContinuationLoop(resumedGoal);
529
532
  }
530
533
  // Start version check asynchronously
531
- checkForNewPiVersion(this.version).then((newRelease) => {
534
+ checkForNewPiVersion(this.version)
535
+ .then((newRelease) => {
532
536
  if (newRelease) {
533
537
  this.showNewVersionNotification(newRelease);
534
538
  }
535
- });
539
+ })
540
+ .catch(() => { });
536
541
  // Start package update check asynchronously
537
- this.checkForPackageUpdates().then((updates) => {
542
+ this.checkForPackageUpdates()
543
+ .then((updates) => {
538
544
  if (updates.length > 0) {
539
545
  this.showPackageUpdateNotification(updates);
540
546
  }
541
- });
547
+ })
548
+ .catch(() => { });
542
549
  // Check tmux keyboard setup asynchronously
543
- this.checkTmuxKeyboardSetup().then((warning) => {
550
+ this.checkTmuxKeyboardSetup()
551
+ .then((warning) => {
544
552
  if (warning) {
545
553
  this.showWarning(warning);
546
554
  }
547
- });
555
+ })
556
+ .catch(() => { });
548
557
  // Show startup warnings
549
558
  const { migratedProviders, modelFallbackMessage, initialMessage, initialImages, initialMessages } = this.options;
550
559
  if (migratedProviders && migratedProviders.length > 0) {
@@ -1999,6 +2008,60 @@ export class InteractiveMode {
1999
2008
  // Silently ignore clipboard errors (may not have permission, etc.)
2000
2009
  }
2001
2010
  }
2011
+ initCommands() {
2012
+ const noArg = (fn) => async (_text) => {
2013
+ this.editor.setText("");
2014
+ await fn();
2015
+ };
2016
+ this.commands.set("/settings", noArg(() => this.showSettingsSelector()));
2017
+ this.commands.set("/scoped-models", noArg(() => this.showModelsSelector()));
2018
+ this.commands.set("/model", async (text) => {
2019
+ const s = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
2020
+ this.editor.setText("");
2021
+ await this.handleModelCommand(s);
2022
+ });
2023
+ this.commands.set("/export", async (text) => {
2024
+ await this.handleExportCommand(text);
2025
+ this.editor.setText("");
2026
+ });
2027
+ this.commands.set("/import", async (text) => {
2028
+ await this.handleImportCommand(text);
2029
+ this.editor.setText("");
2030
+ });
2031
+ this.commands.set("/share", noArg(() => this.handleShareCommand()));
2032
+ this.commands.set("/copy", noArg(() => this.handleCopyCommand()));
2033
+ this.commands.set("/name", async (text) => {
2034
+ this.handleNameCommand(text);
2035
+ this.editor.setText("");
2036
+ });
2037
+ this.commands.set("/session", noArg(() => this.handleSessionCommand()));
2038
+ this.commands.set("/mcp", noArg(() => this.showMcpSelector()));
2039
+ this.commands.set("/skills", noArg(() => this.showSkillsSelector()));
2040
+ this.commands.set("/usage", noArg(() => this.handleUsageCommand()));
2041
+ this.commands.set("/memory", noArg(() => this.handleMemoryCommand()));
2042
+ this.commands.set("/changelog", noArg(() => this.handleChangelogCommand()));
2043
+ this.commands.set("/hotkeys", noArg(() => this.handleHotkeysCommand()));
2044
+ this.commands.set("/fork", noArg(() => this.showUserMessageSelector()));
2045
+ this.commands.set("/clone", noArg(() => this.handleCloneCommand()));
2046
+ this.commands.set("/tree", noArg(() => this.showTreeSelector()));
2047
+ this.commands.set("/login", noArg(() => this.showOAuthSelector("login")));
2048
+ this.commands.set("/logout", noArg(() => this.showOAuthSelector("logout")));
2049
+ this.commands.set("/new", noArg(() => this.handleClearCommand()));
2050
+ this.commands.set("/compact", async (text) => {
2051
+ const ci = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
2052
+ this.editor.setText("");
2053
+ await this.handleCompactCommand(ci);
2054
+ });
2055
+ this.commands.set("/cleanup", noArg(() => this.handleCleanupCommand()));
2056
+ this.commands.set("/reload", noArg(() => this.handleReloadCommand()));
2057
+ this.commands.set("/debug", noArg(() => this.handleDebugCommand()));
2058
+ this.commands.set("/arminsayshi", noArg(() => this.handleArminSaysHi()));
2059
+ this.commands.set("/dementedelves", noArg(() => this.handleDementedDelves()));
2060
+ this.commands.set("/daxnuts", noArg(() => this.handleDaxnuts()));
2061
+ this.commands.set("/resume", noArg(() => this.showSessionSelector()));
2062
+ this.commands.set("/update", noArg(() => this.handleUpdateCommand()));
2063
+ this.commands.set("/quit", noArg(() => this.shutdown()));
2064
+ }
2002
2065
  setupEditorSubmitHandler() {
2003
2066
  this.defaultEditor.onSubmit = async (text) => {
2004
2067
  text = text.trim();
@@ -2006,171 +2069,22 @@ export class InteractiveMode {
2006
2069
  return;
2007
2070
  // Add to history before handling (so up/down can recall commands too)
2008
2071
  this.editor.addToHistory?.(text);
2009
- // Handle commands
2010
- if (text === "/settings") {
2011
- this.showSettingsSelector();
2012
- this.editor.setText("");
2013
- return;
2014
- }
2015
- if (text === "/scoped-models") {
2016
- this.editor.setText("");
2017
- await this.showModelsSelector();
2018
- return;
2019
- }
2020
- if (text === "/model" || text.startsWith("/model ")) {
2021
- const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
2022
- this.editor.setText("");
2023
- await this.handleModelCommand(searchTerm);
2024
- return;
2025
- }
2026
- if (text === "/export" || text.startsWith("/export ")) {
2027
- await this.handleExportCommand(text);
2028
- this.editor.setText("");
2029
- return;
2030
- }
2031
- if (text === "/import" || text.startsWith("/import ")) {
2032
- await this.handleImportCommand(text);
2033
- this.editor.setText("");
2034
- return;
2035
- }
2036
- if (text === "/share") {
2037
- await this.handleShareCommand();
2038
- this.editor.setText("");
2039
- return;
2040
- }
2041
- if (text === "/copy") {
2042
- await this.handleCopyCommand();
2043
- this.editor.setText("");
2044
- return;
2045
- }
2046
- if (text === "/name" || text.startsWith("/name ")) {
2047
- this.handleNameCommand(text);
2048
- this.editor.setText("");
2049
- return;
2050
- }
2051
- if (text === "/goal" || text.startsWith("/goal ")) {
2072
+ // Handle commands (with special case for /goal which continues to submit)
2073
+ if (text.startsWith("/goal")) {
2052
2074
  const goal = text.replace(/^\/goal\s*/, "").trim();
2053
2075
  this.handleGoalCommand(text);
2054
- if (goal)
2055
- text = goal; // Submit goal text as the user message, not /goal prefix
2056
- // Don't return — let the normal submit flow run so the agent starts working
2057
- }
2058
- if (text === "/session") {
2059
- this.handleSessionCommand();
2060
- this.editor.setText("");
2061
- return;
2062
- }
2063
- if (text === "/mcp") {
2064
- this.editor.setText("");
2065
- this.showMcpSelector();
2066
- return;
2067
- }
2068
- if (text.startsWith("/mcp ")) {
2069
- const name = text.slice(5).trim();
2070
- this.editor.setText("");
2071
- this.showMcpSelector(name);
2072
- return;
2073
- }
2074
- if (text === "/skills") {
2075
- this.editor.setText("");
2076
- this.showSkillsSelector();
2077
- return;
2078
- }
2079
- if (text === "/usage") {
2080
- this.editor.setText("");
2081
- await this.handleUsageCommand();
2082
- return;
2083
- }
2084
- if (text === "/memory") {
2085
- this.handleMemoryCommand();
2086
- this.editor.setText("");
2087
- return;
2088
- }
2089
- if (text === "/changelog") {
2090
- this.handleChangelogCommand();
2091
- this.editor.setText("");
2092
- return;
2093
- }
2094
- if (text === "/hotkeys") {
2095
- this.handleHotkeysCommand();
2096
- this.editor.setText("");
2097
- return;
2098
- }
2099
- if (text === "/fork") {
2100
- this.showUserMessageSelector();
2101
- this.editor.setText("");
2102
- return;
2103
- }
2104
- if (text === "/clone") {
2105
- this.editor.setText("");
2106
- await this.handleCloneCommand();
2107
- return;
2108
- }
2109
- if (text === "/tree") {
2110
- this.showTreeSelector();
2111
- this.editor.setText("");
2112
- return;
2113
- }
2114
- if (text === "/login") {
2115
- this.showOAuthSelector("login");
2116
- this.editor.setText("");
2117
- return;
2118
- }
2119
- if (text === "/logout") {
2120
- this.showOAuthSelector("logout");
2121
- this.editor.setText("");
2122
- return;
2123
- }
2124
- if (text === "/new") {
2125
2076
  this.editor.setText("");
2126
- await this.handleClearCommand();
2127
- return;
2128
- }
2129
- if (text === "/compact" || text.startsWith("/compact ")) {
2130
- const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
2131
- this.editor.setText("");
2132
- await this.handleCompactCommand(customInstructions);
2133
- return;
2134
- }
2135
- if (text === "/cleanup") {
2136
- this.editor.setText("");
2137
- await this.handleCleanupCommand();
2138
- return;
2139
- }
2140
- if (text === "/reload") {
2141
- this.editor.setText("");
2142
- await this.handleReloadCommand();
2143
- return;
2144
- }
2145
- if (text === "/debug") {
2146
- this.handleDebugCommand();
2147
- this.editor.setText("");
2148
- return;
2149
- }
2150
- if (text === "/arminsayshi") {
2151
- this.handleArminSaysHi();
2152
- this.editor.setText("");
2153
- return;
2154
- }
2155
- if (text === "/dementedelves") {
2156
- this.handleDementedDelves();
2157
- this.editor.setText("");
2158
- return;
2159
- }
2160
- if (text === "/resume") {
2161
- this.showSessionSelector();
2162
- this.editor.setText("");
2163
- return;
2164
- }
2165
- if (text === "/update") {
2166
- this.editor.setText("");
2167
- await this.handleUpdateCommand();
2168
- return;
2077
+ if (goal)
2078
+ text = goal;
2169
2079
  }
2170
- if (text === "/quit") {
2171
- this.editor.setText("");
2172
- await this.shutdown();
2173
- return;
2080
+ else {
2081
+ const spaceIdx = text.indexOf(" ");
2082
+ const cmd = spaceIdx === -1 ? text : text.slice(0, spaceIdx);
2083
+ const handler = this.commands.get(cmd);
2084
+ if (handler) {
2085
+ await handler(text);
2086
+ return;
2087
+ }
2174
2088
  }
2175
2089
  // Handle bash command (! for normal, !! for excluded from context)
2176
2090
  if (text.startsWith("!")) {
@@ -2232,280 +2146,299 @@ export class InteractiveMode {
2232
2146
  this.footer.invalidate();
2233
2147
  switch (event.type) {
2234
2148
  case "agent_start":
2235
- this.pendingTools.clear();
2236
- if (this.settingsManager.getShowTerminalProgress()) {
2237
- this.ui.terminal.setProgress(true);
2238
- }
2239
- // Restore main escape handler if retry handler is still active
2240
- // (retry success event fires later, but we need main handler now)
2241
- if (this.retryEscapeHandler) {
2242
- this.defaultEditor.onEscape = this.retryEscapeHandler;
2243
- this.retryEscapeHandler = undefined;
2244
- }
2245
- if (this.retryCountdown) {
2246
- this.retryCountdown.dispose();
2247
- this.retryCountdown = undefined;
2248
- }
2249
- if (this.retryLoader) {
2250
- this.retryLoader.stop();
2251
- this.retryLoader = undefined;
2252
- }
2253
- this.stopWorkingLoader();
2254
- if (this.workingVisible) {
2255
- this.loadingAnimation = this.createWorkingLoader();
2256
- this.statusContainer.addChild(this.loadingAnimation);
2257
- }
2258
- this.ui.requestRender();
2149
+ this._handleAgentStart();
2259
2150
  break;
2260
2151
  case "queue_update":
2261
- this.updatePendingMessagesDisplay();
2262
- this.ui.requestRender();
2152
+ this._handleQueueUpdate();
2263
2153
  break;
2264
2154
  case "session_info_changed":
2265
- this.updateTerminalTitle();
2266
- this.footer.invalidate();
2267
- this.ui.requestRender();
2155
+ this._handleSessionInfoChanged();
2268
2156
  break;
2269
2157
  case "thinking_level_changed":
2270
- this.footer.invalidate();
2271
- this.updateEditorBorderColor();
2158
+ this._handleThinkingLevelChanged();
2272
2159
  break;
2273
2160
  case "message_start":
2274
- if (event.message.role === "custom") {
2275
- this.addMessageToChat(event.message);
2276
- this.ui.requestRender();
2277
- }
2278
- else if (event.message.role === "user") {
2279
- this.addMessageToChat(event.message);
2280
- this.updatePendingMessagesDisplay();
2281
- this.ui.requestRender();
2282
- }
2283
- else if (event.message.role === "assistant") {
2284
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2285
- this.streamingMessage = event.message;
2286
- this.chatContainer.addChild(this.streamingComponent);
2287
- this.streamingComponent.updateContent(this.streamingMessage);
2288
- this.ui.requestRender();
2289
- }
2161
+ this._handleMessageStart(event);
2290
2162
  break;
2291
2163
  case "message_update":
2292
- if (this.streamingComponent && event.message.role === "assistant") {
2293
- this.streamingMessage = event.message;
2294
- this.streamingComponent.updateContent(this.streamingMessage);
2295
- for (const content of this.streamingMessage.content) {
2296
- if (content.type === "toolCall") {
2297
- if (!this.pendingTools.has(content.id)) {
2298
- const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2299
- showImages: this.settingsManager.getShowImages(),
2300
- imageWidthCells: this.settingsManager.getImageWidthCells(),
2301
- }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2302
- component.setExpanded(this.toolOutputExpanded);
2303
- this.chatContainer.addChild(component);
2304
- this.pendingTools.set(content.id, component);
2305
- }
2306
- else {
2307
- const component = this.pendingTools.get(content.id);
2308
- if (component) {
2309
- component.updateArgs(content.arguments);
2310
- }
2311
- }
2312
- }
2313
- }
2314
- this.ui.requestRender();
2315
- }
2164
+ this._handleMessageUpdate(event);
2316
2165
  break;
2317
2166
  case "message_end":
2318
- if (event.message.role === "user")
2319
- break;
2320
- if (this.streamingComponent && event.message.role === "assistant") {
2321
- this.streamingMessage = event.message;
2322
- let errorMessage;
2323
- if (this.streamingMessage.stopReason === "aborted") {
2324
- const retryAttempt = this.session.retryAttempt;
2325
- errorMessage =
2326
- retryAttempt > 0
2327
- ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
2328
- : "Operation aborted";
2329
- this.streamingMessage.errorMessage = errorMessage;
2330
- }
2331
- this.streamingComponent.updateContent(this.streamingMessage);
2332
- if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
2333
- if (!errorMessage) {
2334
- errorMessage = this.streamingMessage.errorMessage || "Error";
2335
- }
2336
- for (const [, component] of this.pendingTools.entries()) {
2337
- component.updateResult({
2338
- content: [{ type: "text", text: errorMessage }],
2339
- isError: true,
2340
- });
2341
- }
2342
- this.pendingTools.clear();
2343
- }
2344
- else {
2345
- // Args are now complete - trigger diff computation for edit tools
2346
- for (const [, component] of this.pendingTools.entries()) {
2347
- component.setArgsComplete();
2348
- }
2349
- }
2350
- this.streamingComponent = undefined;
2351
- this.streamingMessage = undefined;
2352
- this.footer.invalidate();
2353
- }
2354
- this.ui.requestRender();
2167
+ this._handleMessageEnd(event);
2355
2168
  break;
2356
- case "tool_execution_start": {
2357
- let component = this.pendingTools.get(event.toolCallId);
2358
- if (!component) {
2359
- component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
2360
- showImages: this.settingsManager.getShowImages(),
2361
- imageWidthCells: this.settingsManager.getImageWidthCells(),
2362
- }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
2363
- component.setExpanded(this.toolOutputExpanded);
2364
- this.chatContainer.addChild(component);
2365
- this.pendingTools.set(event.toolCallId, component);
2366
- }
2367
- component.markExecutionStarted();
2368
- this.ui.requestRender();
2169
+ case "tool_execution_start":
2170
+ this._handleToolExecutionStart(event);
2369
2171
  break;
2370
- }
2371
- case "tool_execution_update": {
2372
- const component = this.pendingTools.get(event.toolCallId);
2373
- if (component) {
2374
- component.updateResult({ ...event.partialResult, isError: false }, true);
2375
- this.ui.requestRender();
2376
- }
2172
+ case "tool_execution_update":
2173
+ this._handleToolExecutionUpdate(event);
2377
2174
  break;
2378
- }
2379
- case "tool_execution_end": {
2380
- const component = this.pendingTools.get(event.toolCallId);
2381
- if (component) {
2382
- component.updateResult({ ...event.result, isError: event.isError });
2383
- this.pendingTools.delete(event.toolCallId);
2384
- this.ui.requestRender();
2385
- }
2175
+ case "tool_execution_end":
2176
+ this._handleToolExecutionEnd(event);
2386
2177
  break;
2387
- }
2388
2178
  case "agent_end":
2389
- if (this.settingsManager.getShowTerminalProgress()) {
2390
- this.ui.terminal.setProgress(false);
2391
- }
2392
- if (this.loadingAnimation) {
2393
- this.loadingAnimation.stop();
2394
- this.loadingAnimation = undefined;
2395
- this.statusContainer.clear();
2396
- }
2397
- if (this.streamingComponent) {
2398
- this.chatContainer.removeChild(this.streamingComponent);
2399
- this.streamingComponent = undefined;
2400
- this.streamingMessage = undefined;
2401
- }
2402
- this.pendingTools.clear();
2403
- await this.checkShutdownRequested();
2404
- this.ui.requestRender();
2179
+ await this._handleAgentEnd();
2405
2180
  break;
2406
- case "compaction_start": {
2407
- if (this.settingsManager.getShowTerminalProgress()) {
2408
- this.ui.terminal.setProgress(true);
2409
- }
2410
- // Keep editor active; submissions are queued during compaction.
2411
- this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
2412
- this.defaultEditor.onEscape = () => {
2413
- this.session.abortCompaction();
2414
- };
2415
- this.statusContainer.clear();
2416
- const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
2417
- const label = event.reason === "manual"
2418
- ? `Compacting context... ${cancelHint}`
2419
- : `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
2420
- this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2421
- this.statusContainer.addChild(this.autoCompactionLoader);
2422
- this.ui.requestRender();
2181
+ case "compaction_start":
2182
+ this._handleCompactionStart(event);
2423
2183
  break;
2424
- }
2425
- case "compaction_end": {
2426
- if (this.settingsManager.getShowTerminalProgress()) {
2427
- this.ui.terminal.setProgress(false);
2428
- }
2429
- if (this.autoCompactionEscapeHandler) {
2430
- this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
2431
- this.autoCompactionEscapeHandler = undefined;
2432
- }
2433
- if (this.autoCompactionLoader) {
2434
- this.autoCompactionLoader.stop();
2435
- this.autoCompactionLoader = undefined;
2436
- this.statusContainer.clear();
2437
- }
2438
- if (event.aborted) {
2439
- if (event.reason === "manual") {
2440
- this.showError("Compaction cancelled");
2441
- }
2442
- else {
2443
- this.showStatus("Auto-compaction cancelled");
2444
- }
2445
- }
2446
- else if (event.result) {
2447
- this.chatContainer.clear();
2448
- this.rebuildChatFromMessages();
2449
- this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2450
- this.footer.invalidate();
2451
- }
2452
- else if (event.errorMessage) {
2453
- if (event.reason === "manual") {
2454
- this.showError(event.errorMessage);
2455
- }
2456
- else {
2457
- this.chatContainer.addChild(new Spacer(1));
2458
- this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
2459
- }
2460
- }
2461
- void this.flushCompactionQueue({ willRetry: event.willRetry });
2462
- this.ui.requestRender();
2184
+ case "compaction_end":
2185
+ this._handleCompactionEnd(event);
2463
2186
  break;
2464
- }
2465
- case "auto_retry_start": {
2466
- // Set up escape to abort retry
2467
- this.retryEscapeHandler = this.defaultEditor.onEscape;
2468
- this.defaultEditor.onEscape = () => {
2469
- this.session.abortRetry();
2470
- };
2471
- // Show retry indicator
2472
- this.statusContainer.clear();
2473
- this.retryCountdown?.dispose();
2474
- const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
2475
- this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
2476
- this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
2477
- this.retryLoader?.setMessage(retryMessage(seconds));
2478
- }, () => {
2479
- this.retryCountdown = undefined;
2480
- });
2481
- this.statusContainer.addChild(this.retryLoader);
2482
- this.ui.requestRender();
2187
+ case "auto_retry_start":
2188
+ this._handleAutoRetryStart(event);
2483
2189
  break;
2484
- }
2485
- case "auto_retry_end": {
2486
- // Restore escape handler
2487
- if (this.retryEscapeHandler) {
2488
- this.defaultEditor.onEscape = this.retryEscapeHandler;
2489
- this.retryEscapeHandler = undefined;
2490
- }
2491
- if (this.retryCountdown) {
2492
- this.retryCountdown.dispose();
2493
- this.retryCountdown = undefined;
2494
- }
2495
- // Stop loader
2496
- if (this.retryLoader) {
2497
- this.retryLoader.stop();
2498
- this.retryLoader = undefined;
2499
- this.statusContainer.clear();
2500
- }
2501
- // Show error only on final failure (success shows normal response)
2502
- if (!event.success) {
2503
- this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
2504
- }
2505
- this.ui.requestRender();
2190
+ case "auto_retry_end":
2191
+ this._handleAutoRetryEnd(event);
2506
2192
  break;
2193
+ }
2194
+ }
2195
+ _handleAgentStart() {
2196
+ this.pendingTools.clear();
2197
+ if (this.settingsManager.getShowTerminalProgress()) {
2198
+ this.ui.terminal.setProgress(true);
2199
+ }
2200
+ if (this.retryEscapeHandler) {
2201
+ this.defaultEditor.onEscape = this.retryEscapeHandler;
2202
+ this.retryEscapeHandler = undefined;
2203
+ }
2204
+ if (this.retryCountdown) {
2205
+ this.retryCountdown.dispose();
2206
+ this.retryCountdown = undefined;
2207
+ }
2208
+ if (this.retryLoader) {
2209
+ this.retryLoader.stop();
2210
+ this.retryLoader = undefined;
2211
+ }
2212
+ this.stopWorkingLoader();
2213
+ if (this.workingVisible) {
2214
+ this.loadingAnimation = this.createWorkingLoader();
2215
+ this.statusContainer.addChild(this.loadingAnimation);
2216
+ }
2217
+ this.ui.requestRender();
2218
+ }
2219
+ _handleQueueUpdate() {
2220
+ this.updatePendingMessagesDisplay();
2221
+ this.ui.requestRender();
2222
+ }
2223
+ _handleSessionInfoChanged() {
2224
+ this.updateTerminalTitle();
2225
+ this.footer.invalidate();
2226
+ this.ui.requestRender();
2227
+ }
2228
+ _handleThinkingLevelChanged() {
2229
+ this.footer.invalidate();
2230
+ this.updateEditorBorderColor();
2231
+ }
2232
+ _handleMessageStart(event) {
2233
+ if (event.message.role === "custom") {
2234
+ this.addMessageToChat(event.message);
2235
+ this.ui.requestRender();
2236
+ }
2237
+ else if (event.message.role === "user") {
2238
+ this.addMessageToChat(event.message);
2239
+ this.updatePendingMessagesDisplay();
2240
+ this.ui.requestRender();
2241
+ }
2242
+ else if (event.message.role === "assistant") {
2243
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2244
+ this.streamingMessage = event.message;
2245
+ this.chatContainer.addChild(this.streamingComponent);
2246
+ this.streamingComponent.updateContent(this.streamingMessage);
2247
+ this.ui.requestRender();
2248
+ }
2249
+ }
2250
+ _handleMessageUpdate(event) {
2251
+ if (!this.streamingComponent || event.message.role !== "assistant")
2252
+ return;
2253
+ this.streamingMessage = event.message;
2254
+ this.streamingComponent.updateContent(this.streamingMessage);
2255
+ for (const content of this.streamingMessage.content) {
2256
+ if (content.type !== "toolCall")
2257
+ continue;
2258
+ if (!this.pendingTools.has(content.id)) {
2259
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2260
+ showImages: this.settingsManager.getShowImages(),
2261
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
2262
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2263
+ component.setExpanded(this.toolOutputExpanded);
2264
+ this.chatContainer.addChild(component);
2265
+ this.pendingTools.set(content.id, component);
2266
+ }
2267
+ else {
2268
+ this.pendingTools.get(content.id)?.updateArgs(content.arguments);
2269
+ }
2270
+ }
2271
+ this.ui.requestRender();
2272
+ }
2273
+ _handleMessageEnd(event) {
2274
+ if (event.message.role === "user")
2275
+ return;
2276
+ if (!this.streamingComponent || event.message.role !== "assistant")
2277
+ return;
2278
+ this.streamingMessage = event.message;
2279
+ let errorMessage;
2280
+ if (this.streamingMessage.stopReason === "aborted") {
2281
+ const retryAttempt = this.session.retryAttempt;
2282
+ errorMessage =
2283
+ retryAttempt > 0
2284
+ ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
2285
+ : "Operation aborted";
2286
+ this.streamingMessage.errorMessage = errorMessage;
2287
+ }
2288
+ this.streamingComponent.updateContent(this.streamingMessage);
2289
+ if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
2290
+ const msg = errorMessage || this.streamingMessage.errorMessage || "Error";
2291
+ for (const [, component] of this.pendingTools.entries()) {
2292
+ component.updateResult({ content: [{ type: "text", text: msg }], isError: true });
2293
+ }
2294
+ this.pendingTools.clear();
2295
+ }
2296
+ else {
2297
+ for (const [, component] of this.pendingTools.entries()) {
2298
+ component.setArgsComplete();
2507
2299
  }
2508
2300
  }
2301
+ this.streamingComponent = undefined;
2302
+ this.streamingMessage = undefined;
2303
+ this.footer.invalidate();
2304
+ this.ui.requestRender();
2305
+ }
2306
+ _handleToolExecutionStart(event) {
2307
+ let component = this.pendingTools.get(event.toolCallId);
2308
+ if (!component) {
2309
+ component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
2310
+ showImages: this.settingsManager.getShowImages(),
2311
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
2312
+ }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
2313
+ component.setExpanded(this.toolOutputExpanded);
2314
+ this.chatContainer.addChild(component);
2315
+ this.pendingTools.set(event.toolCallId, component);
2316
+ }
2317
+ component.markExecutionStarted();
2318
+ this.ui.requestRender();
2319
+ }
2320
+ _handleToolExecutionUpdate(event) {
2321
+ const component = this.pendingTools.get(event.toolCallId);
2322
+ if (component) {
2323
+ component.updateResult({ ...event.partialResult, isError: false }, true);
2324
+ this.ui.requestRender();
2325
+ }
2326
+ }
2327
+ _handleToolExecutionEnd(event) {
2328
+ const component = this.pendingTools.get(event.toolCallId);
2329
+ if (component) {
2330
+ component.updateResult({ ...event.result, isError: event.isError });
2331
+ this.pendingTools.delete(event.toolCallId);
2332
+ this.ui.requestRender();
2333
+ }
2334
+ }
2335
+ async _handleAgentEnd() {
2336
+ if (this.settingsManager.getShowTerminalProgress()) {
2337
+ this.ui.terminal.setProgress(false);
2338
+ }
2339
+ if (this.loadingAnimation) {
2340
+ this.loadingAnimation.stop();
2341
+ this.loadingAnimation = undefined;
2342
+ this.statusContainer.clear();
2343
+ }
2344
+ if (this.streamingComponent) {
2345
+ this.chatContainer.removeChild(this.streamingComponent);
2346
+ this.streamingComponent = undefined;
2347
+ this.streamingMessage = undefined;
2348
+ }
2349
+ this.pendingTools.clear();
2350
+ await this.checkShutdownRequested();
2351
+ this.ui.requestRender();
2352
+ }
2353
+ _handleCompactionStart(event) {
2354
+ if (this.settingsManager.getShowTerminalProgress()) {
2355
+ this.ui.terminal.setProgress(true);
2356
+ }
2357
+ this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
2358
+ this.defaultEditor.onEscape = () => {
2359
+ this.session.abortCompaction();
2360
+ };
2361
+ this.statusContainer.clear();
2362
+ const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
2363
+ const label = event.reason === "manual"
2364
+ ? `Compacting context... ${cancelHint}`
2365
+ : `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
2366
+ this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2367
+ this.statusContainer.addChild(this.autoCompactionLoader);
2368
+ this.ui.requestRender();
2369
+ }
2370
+ _handleCompactionEnd(event) {
2371
+ if (this.settingsManager.getShowTerminalProgress()) {
2372
+ this.ui.terminal.setProgress(false);
2373
+ }
2374
+ if (this.autoCompactionEscapeHandler) {
2375
+ this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
2376
+ this.autoCompactionEscapeHandler = undefined;
2377
+ }
2378
+ if (this.autoCompactionLoader) {
2379
+ this.autoCompactionLoader.stop();
2380
+ this.autoCompactionLoader = undefined;
2381
+ this.statusContainer.clear();
2382
+ }
2383
+ if (event.aborted) {
2384
+ if (event.reason === "manual") {
2385
+ this.showError("Compaction cancelled");
2386
+ }
2387
+ else {
2388
+ this.showStatus("Auto-compaction cancelled");
2389
+ }
2390
+ }
2391
+ else if (event.result) {
2392
+ this.chatContainer.clear();
2393
+ this.rebuildChatFromMessages();
2394
+ this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2395
+ this.footer.invalidate();
2396
+ }
2397
+ else if (event.errorMessage) {
2398
+ if (event.reason === "manual") {
2399
+ this.showError(event.errorMessage);
2400
+ }
2401
+ else {
2402
+ this.chatContainer.addChild(new Spacer(1));
2403
+ this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
2404
+ }
2405
+ }
2406
+ void this.flushCompactionQueue({ willRetry: event.willRetry });
2407
+ this.ui.requestRender();
2408
+ }
2409
+ _handleAutoRetryStart(event) {
2410
+ this.retryEscapeHandler = this.defaultEditor.onEscape;
2411
+ this.defaultEditor.onEscape = () => {
2412
+ this.session.abortRetry();
2413
+ };
2414
+ this.statusContainer.clear();
2415
+ this.retryCountdown?.dispose();
2416
+ const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
2417
+ this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
2418
+ this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => this.retryLoader?.setMessage(retryMessage(seconds)), () => {
2419
+ this.retryCountdown = undefined;
2420
+ });
2421
+ this.statusContainer.addChild(this.retryLoader);
2422
+ this.ui.requestRender();
2423
+ }
2424
+ _handleAutoRetryEnd(event) {
2425
+ if (this.retryEscapeHandler) {
2426
+ this.defaultEditor.onEscape = this.retryEscapeHandler;
2427
+ this.retryEscapeHandler = undefined;
2428
+ }
2429
+ if (this.retryCountdown) {
2430
+ this.retryCountdown.dispose();
2431
+ this.retryCountdown = undefined;
2432
+ }
2433
+ if (this.retryLoader) {
2434
+ this.retryLoader.stop();
2435
+ this.retryLoader = undefined;
2436
+ this.statusContainer.clear();
2437
+ }
2438
+ if (!event.success) {
2439
+ this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
2440
+ }
2441
+ this.ui.requestRender();
2509
2442
  }
2510
2443
  /** Extract text content from a user message */
2511
2444
  getUserMessageText(message) {
@@ -4612,7 +4545,7 @@ export class InteractiveMode {
4612
4545
  this.chatContainer.addChild(new Text(info, 1, 0));
4613
4546
  this.ui.requestRender();
4614
4547
  }
4615
- showMcpSelector(highlightServer) {
4548
+ async showMcpSelector(highlightServer) {
4616
4549
  const buildItems = () => {
4617
4550
  // Fetch fresh config each time so toggle reflects immediately
4618
4551
  const configured = this.settingsManager.getMcpServers();
@@ -4635,22 +4568,22 @@ export class InteractiveMode {
4635
4568
  };
4636
4569
  });
4637
4570
  };
4638
- const items = buildItems();
4639
- if (items.length === 0) {
4640
- this.chatContainer.addChild(new Spacer(1));
4641
- this.chatContainer.addChild(new Text(`${theme.bold("MCP Servers")}\n\n${theme.fg("dim", "No MCP servers configured. Add 'mcpServers' to settings.json.")}`, 1, 0));
4642
- this.ui.requestRender();
4643
- return;
4644
- }
4645
4571
  if (highlightServer) {
4646
4572
  const configured = this.settingsManager.getMcpServers();
4647
4573
  const server = configured?.[highlightServer];
4648
4574
  if (server) {
4649
4575
  const currentEnabled = server.enabled !== false;
4650
4576
  this.settingsManager.setMcpServerEnabled(highlightServer, !currentEnabled);
4651
- this.session.reloadMcp();
4577
+ await this.session.reloadMcp(highlightServer);
4652
4578
  }
4653
4579
  }
4580
+ const items = buildItems();
4581
+ if (items.length === 0) {
4582
+ this.chatContainer.addChild(new Spacer(1));
4583
+ this.chatContainer.addChild(new Text(`${theme.bold("MCP Servers")}\n\n${theme.fg("dim", "No MCP servers configured. Add 'mcpServers' to settings.json.")}`, 1, 0));
4584
+ this.ui.requestRender();
4585
+ return;
4586
+ }
4654
4587
  this.showSelector((done) => {
4655
4588
  const selector = new McpSelectorComponent("MCP Servers", items, async (name) => {
4656
4589
  const cur = this.settingsManager.getMcpServers();
@@ -4658,7 +4591,7 @@ export class InteractiveMode {
4658
4591
  if (!server)
4659
4592
  return;
4660
4593
  this.settingsManager.setMcpServerEnabled(name, server.enabled === false);
4661
- await this.session.reloadMcp();
4594
+ await this.session.reloadMcp(name);
4662
4595
  selector.setItems(buildItems());
4663
4596
  }, () => done());
4664
4597
  return { component: selector, focus: selector };