@mariozechner/pi-coding-agent 0.63.2 → 0.65.0

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 (144) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +11 -5
  3. package/dist/cli/args.d.ts +7 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +37 -15
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-runtime.d.ts +83 -0
  8. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  9. package/dist/core/agent-session-runtime.js +232 -0
  10. package/dist/core/agent-session-runtime.js.map +1 -0
  11. package/dist/core/agent-session-services.d.ts +86 -0
  12. package/dist/core/agent-session-services.d.ts.map +1 -0
  13. package/dist/core/agent-session-services.js +116 -0
  14. package/dist/core/agent-session-services.js.map +1 -0
  15. package/dist/core/agent-session.d.ts +10 -42
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +58 -237
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  20. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  21. package/dist/core/export-html/tool-renderer.js +2 -2
  22. package/dist/core/export-html/tool-renderer.js.map +1 -1
  23. package/dist/core/extensions/index.d.ts +2 -2
  24. package/dist/core/extensions/index.d.ts.map +1 -1
  25. package/dist/core/extensions/index.js +1 -1
  26. package/dist/core/extensions/index.js.map +1 -1
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +1 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +20 -28
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js +10 -0
  33. package/dist/core/extensions/types.js.map +1 -1
  34. package/dist/core/footer-data-provider.d.ts +5 -1
  35. package/dist/core/footer-data-provider.d.ts.map +1 -1
  36. package/dist/core/footer-data-provider.js +70 -8
  37. package/dist/core/footer-data-provider.js.map +1 -1
  38. package/dist/core/index.d.ts +3 -1
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +3 -1
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/keybindings.d.ts +14 -1
  43. package/dist/core/keybindings.d.ts.map +1 -1
  44. package/dist/core/keybindings.js +13 -14
  45. package/dist/core/keybindings.js.map +1 -1
  46. package/dist/core/model-registry.d.ts +3 -1
  47. package/dist/core/model-registry.d.ts.map +1 -1
  48. package/dist/core/model-registry.js +7 -1
  49. package/dist/core/model-registry.js.map +1 -1
  50. package/dist/core/package-manager.d.ts +20 -0
  51. package/dist/core/package-manager.d.ts.map +1 -1
  52. package/dist/core/package-manager.js +32 -0
  53. package/dist/core/package-manager.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +21 -0
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/sdk.d.ts +5 -2
  58. package/dist/core/sdk.d.ts.map +1 -1
  59. package/dist/core/sdk.js +5 -2
  60. package/dist/core/sdk.js.map +1 -1
  61. package/dist/core/session-manager.d.ts +3 -0
  62. package/dist/core/session-manager.d.ts.map +1 -1
  63. package/dist/core/session-manager.js +13 -6
  64. package/dist/core/session-manager.js.map +1 -1
  65. package/dist/core/settings-manager.d.ts +1 -1
  66. package/dist/core/settings-manager.d.ts.map +1 -1
  67. package/dist/core/settings-manager.js +2 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/tools/edit.d.ts.map +1 -1
  70. package/dist/core/tools/edit.js +15 -4
  71. package/dist/core/tools/edit.js.map +1 -1
  72. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  73. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  74. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  75. package/dist/index.d.ts +3 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -3
  78. package/dist/index.js.map +1 -1
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/main.js +205 -427
  81. package/dist/main.js.map +1 -1
  82. package/dist/migrations.d.ts.map +1 -1
  83. package/dist/migrations.js +20 -0
  84. package/dist/migrations.js.map +1 -1
  85. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  86. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/assistant-message.js +14 -3
  88. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  89. package/dist/modes/interactive/components/footer.d.ts +1 -0
  90. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/footer.js +4 -1
  92. package/dist/modes/interactive/components/footer.js.map +1 -1
  93. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  94. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/tree-selector.js +48 -15
  96. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.d.ts +12 -4
  98. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  99. package/dist/modes/interactive/interactive-mode.js +146 -96
  100. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  101. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  102. package/dist/modes/interactive/theme/theme.js +6 -11
  103. package/dist/modes/interactive/theme/theme.js.map +1 -1
  104. package/dist/modes/print-mode.d.ts +2 -2
  105. package/dist/modes/print-mode.d.ts.map +1 -1
  106. package/dist/modes/print-mode.js +41 -36
  107. package/dist/modes/print-mode.js.map +1 -1
  108. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  109. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-mode.js +95 -64
  111. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  112. package/dist/package-manager-cli.d.ts +4 -0
  113. package/dist/package-manager-cli.d.ts.map +1 -0
  114. package/dist/package-manager-cli.js +234 -0
  115. package/dist/package-manager-cli.js.map +1 -0
  116. package/docs/compaction.md +4 -2
  117. package/docs/extensions.md +133 -40
  118. package/docs/json.md +5 -2
  119. package/docs/keybindings.md +2 -0
  120. package/docs/rpc.md +21 -7
  121. package/docs/sdk.md +239 -83
  122. package/docs/settings.md +1 -1
  123. package/docs/tree.md +6 -3
  124. package/examples/extensions/README.md +1 -0
  125. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  126. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  127. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  128. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  129. package/examples/extensions/hello.ts +18 -17
  130. package/examples/extensions/hidden-thinking-label.ts +53 -0
  131. package/examples/extensions/rpc-demo.ts +3 -9
  132. package/examples/extensions/sandbox/index.ts +4 -0
  133. package/examples/extensions/status-line.ts +0 -8
  134. package/examples/extensions/todo.ts +0 -2
  135. package/examples/extensions/tools.ts +0 -5
  136. package/examples/extensions/widget-placement.ts +4 -12
  137. package/examples/extensions/with-deps/package-lock.json +2 -2
  138. package/examples/extensions/with-deps/package.json +1 -1
  139. package/examples/sdk/02-custom-model.ts +1 -1
  140. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  141. package/examples/sdk/12-full-control.ts +1 -1
  142. package/examples/sdk/13-session-runtime.ts +67 -0
  143. package/examples/sdk/README.md +7 -4
  144. package/package.json +4 -4
@@ -48,13 +48,13 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
48
48
  import { TreeSelectorComponent } from "./components/tree-selector.js";
49
49
  import { UserMessageComponent } from "./components/user-message.js";
50
50
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
51
- import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
51
+ import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
52
52
  function isExpandable(obj) {
53
53
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
54
54
  }
55
55
  export class InteractiveMode {
56
56
  options;
57
- session;
57
+ runtimeHost;
58
58
  ui;
59
59
  chatContainer;
60
60
  pendingMessagesContainer;
@@ -74,6 +74,8 @@ export class InteractiveMode {
74
74
  loadingAnimation = undefined;
75
75
  pendingWorkingMessage = undefined;
76
76
  defaultWorkingMessage = "Working...";
77
+ defaultHiddenThinkingLabel = "Thinking...";
78
+ hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
77
79
  lastSigintTime = 0;
78
80
  lastEscapeTime = 0;
79
81
  changelogMarkdown = undefined;
@@ -128,6 +130,9 @@ export class InteractiveMode {
128
130
  // Custom header from extension (undefined = use built-in header)
129
131
  customHeader = undefined;
130
132
  // Convenience accessors
133
+ get session() {
134
+ return this.runtimeHost.session;
135
+ }
131
136
  get agent() {
132
137
  return this.session.agent;
133
138
  }
@@ -137,9 +142,9 @@ export class InteractiveMode {
137
142
  get settingsManager() {
138
143
  return this.session.settingsManager;
139
144
  }
140
- constructor(session, options = {}) {
145
+ constructor(runtimeHost, options = {}) {
141
146
  this.options = options;
142
- this.session = session;
147
+ this.runtimeHost = runtimeHost;
143
148
  this.version = VERSION;
144
149
  this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
145
150
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
@@ -160,9 +165,9 @@ export class InteractiveMode {
160
165
  this.editor = this.defaultEditor;
161
166
  this.editorContainer = new Container();
162
167
  this.editorContainer.addChild(this.editor);
163
- this.footerDataProvider = new FooterDataProvider();
164
- this.footer = new FooterComponent(session, this.footerDataProvider);
165
- this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
168
+ this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
169
+ this.footer = new FooterComponent(this.session, this.footerDataProvider);
170
+ this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
166
171
  // Load hide thinking block setting
167
172
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
168
173
  // Register themes from resource loader and initialize
@@ -269,7 +274,7 @@ export class InteractiveMode {
269
274
  }
270
275
  }
271
276
  // Setup autocomplete
272
- this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], process.cwd(), fdPath);
277
+ this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], this.sessionManager.getCwd(), fdPath);
273
278
  this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
274
279
  if (this.editor !== this.defaultEditor) {
275
280
  this.editor.setAutocompleteProvider?.(this.autocompleteProvider);
@@ -364,7 +369,7 @@ export class InteractiveMode {
364
369
  this.ui.start();
365
370
  this.isInitialized = true;
366
371
  // Initialize extensions first so resources are shown before messages
367
- await this.initExtensions();
372
+ await this.bindCurrentSessionExtensions();
368
373
  // Render initial messages AFTER showing loaded resources
369
374
  this.renderInitialMessages();
370
375
  // Set terminal title
@@ -388,7 +393,7 @@ export class InteractiveMode {
388
393
  * Update terminal title with session name and cwd.
389
394
  */
390
395
  updateTerminalTitle() {
391
- const cwdBasename = path.basename(process.cwd());
396
+ const cwdBasename = path.basename(this.sessionManager.getCwd());
392
397
  const sessionName = this.sessionManager.getSessionName();
393
398
  if (sessionName) {
394
399
  this.ui.terminal.setTitle(`π - ${sessionName} - ${cwdBasename}`);
@@ -495,7 +500,7 @@ export class InteractiveMode {
495
500
  }
496
501
  try {
497
502
  const packageManager = new DefaultPackageManager({
498
- cwd: process.cwd(),
503
+ cwd: this.sessionManager.getCwd(),
499
504
  agentDir: getAgentDir(),
500
505
  settingsManager: this.settingsManager,
501
506
  });
@@ -883,7 +888,7 @@ export class InteractiveMode {
883
888
  /**
884
889
  * Initialize the extension system with TUI-based UI context.
885
890
  */
886
- async initExtensions() {
891
+ async bindCurrentSessionExtensions() {
887
892
  const uiContext = this.createExtensionUIContext();
888
893
  await this.session.bindExtensions({
889
894
  uiContext,
@@ -895,33 +900,33 @@ export class InteractiveMode {
895
900
  this.loadingAnimation = undefined;
896
901
  }
897
902
  this.statusContainer.clear();
898
- // Delegate to AgentSession (handles setup + agent state sync)
899
- const success = await this.session.newSession(options);
900
- if (!success) {
901
- return { cancelled: true };
903
+ try {
904
+ const result = await this.runtimeHost.newSession(options);
905
+ if (!result.cancelled) {
906
+ await this.handleRuntimeSessionChange();
907
+ this.renderCurrentSessionState();
908
+ this.ui.requestRender();
909
+ }
910
+ return result;
911
+ }
912
+ catch (error) {
913
+ return this.handleFatalRuntimeError("Failed to create session", error);
902
914
  }
903
- // Clear UI state
904
- this.chatContainer.clear();
905
- this.pendingMessagesContainer.clear();
906
- this.compactionQueuedMessages = [];
907
- this.streamingComponent = undefined;
908
- this.streamingMessage = undefined;
909
- this.pendingTools.clear();
910
- // Render any messages added via setup, or show empty session
911
- this.renderInitialMessages();
912
- this.ui.requestRender();
913
- return { cancelled: false };
914
915
  },
915
916
  fork: async (entryId) => {
916
- const result = await this.session.fork(entryId);
917
- if (result.cancelled) {
918
- return { cancelled: true };
917
+ try {
918
+ const result = await this.runtimeHost.fork(entryId);
919
+ if (!result.cancelled) {
920
+ await this.handleRuntimeSessionChange();
921
+ this.renderCurrentSessionState();
922
+ this.editor.setText(result.selectedText ?? "");
923
+ this.showStatus("Forked to new session");
924
+ }
925
+ return { cancelled: result.cancelled };
926
+ }
927
+ catch (error) {
928
+ return this.handleFatalRuntimeError("Failed to fork session", error);
919
929
  }
920
- this.chatContainer.clear();
921
- this.renderInitialMessages();
922
- this.editor.setText(result.selectedText);
923
- this.showStatus("Forked to new session");
924
- return { cancelled: false };
925
930
  },
926
931
  navigateTree: async (targetId, options) => {
927
932
  const result = await this.session.navigateTree(targetId, {
@@ -969,6 +974,49 @@ export class InteractiveMode {
969
974
  this.setupExtensionShortcuts(extensionRunner);
970
975
  this.showLoadedResources({ force: false });
971
976
  }
977
+ applyRuntimeSettings() {
978
+ this.footer.setSession(this.session);
979
+ this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
980
+ this.footerDataProvider.setCwd(this.sessionManager.getCwd());
981
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
982
+ this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
983
+ this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
984
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
985
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
986
+ this.defaultEditor.setPaddingX(editorPaddingX);
987
+ this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
988
+ if (this.editor !== this.defaultEditor) {
989
+ this.editor.setPaddingX?.(editorPaddingX);
990
+ this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
991
+ }
992
+ }
993
+ async handleRuntimeSessionChange() {
994
+ this.resetExtensionUI();
995
+ this.unsubscribe?.();
996
+ this.unsubscribe = undefined;
997
+ this.applyRuntimeSettings();
998
+ await this.bindCurrentSessionExtensions();
999
+ this.subscribeToAgent();
1000
+ await this.updateAvailableProviderCount();
1001
+ this.updateEditorBorderColor();
1002
+ this.updateTerminalTitle();
1003
+ }
1004
+ async handleFatalRuntimeError(prefix, error) {
1005
+ const message = error instanceof Error ? error.message : String(error);
1006
+ this.showError(`${prefix}: ${message}`);
1007
+ stopThemeWatcher();
1008
+ this.stop();
1009
+ process.exit(1);
1010
+ }
1011
+ renderCurrentSessionState() {
1012
+ this.chatContainer.clear();
1013
+ this.pendingMessagesContainer.clear();
1014
+ this.compactionQueuedMessages = [];
1015
+ this.streamingComponent = undefined;
1016
+ this.streamingMessage = undefined;
1017
+ this.pendingTools.clear();
1018
+ this.renderInitialMessages();
1019
+ }
972
1020
  /**
973
1021
  * Get a registered tool definition by name (for custom rendering).
974
1022
  */
@@ -986,7 +1034,7 @@ export class InteractiveMode {
986
1034
  const createContext = () => ({
987
1035
  ui: this.createExtensionUIContext(),
988
1036
  hasUI: true,
989
- cwd: process.cwd(),
1037
+ cwd: this.sessionManager.getCwd(),
990
1038
  sessionManager: this.sessionManager,
991
1039
  modelRegistry: this.session.modelRegistry,
992
1040
  model: this.session.model,
@@ -1034,6 +1082,18 @@ export class InteractiveMode {
1034
1082
  this.footerDataProvider.setExtensionStatus(key, text);
1035
1083
  this.ui.requestRender();
1036
1084
  }
1085
+ setHiddenThinkingLabel(label) {
1086
+ this.hiddenThinkingLabel = label ?? this.defaultHiddenThinkingLabel;
1087
+ for (const child of this.chatContainer.children) {
1088
+ if (child instanceof AssistantMessageComponent) {
1089
+ child.setHiddenThinkingLabel(this.hiddenThinkingLabel);
1090
+ }
1091
+ }
1092
+ if (this.streamingComponent) {
1093
+ this.streamingComponent.setHiddenThinkingLabel(this.hiddenThinkingLabel);
1094
+ }
1095
+ this.ui.requestRender();
1096
+ }
1037
1097
  /**
1038
1098
  * Set an extension widget (string array or custom component).
1039
1099
  */
@@ -1105,6 +1165,7 @@ export class InteractiveMode {
1105
1165
  if (this.loadingAnimation) {
1106
1166
  this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1107
1167
  }
1168
+ this.setHiddenThinkingLabel();
1108
1169
  }
1109
1170
  // Maximum total widget lines to prevent viewport overflow
1110
1171
  static MAX_WIDGET_LINES = 10;
@@ -1234,6 +1295,7 @@ export class InteractiveMode {
1234
1295
  this.pendingWorkingMessage = message;
1235
1296
  }
1236
1297
  },
1298
+ setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
1237
1299
  setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
1238
1300
  setFooter: (factory) => this.setExtensionFooter(factory),
1239
1301
  setHeader: (factory) => this.setExtensionHeader(factory),
@@ -1837,6 +1899,10 @@ export class InteractiveMode {
1837
1899
  }
1838
1900
  this.ui.requestRender();
1839
1901
  break;
1902
+ case "queue_update":
1903
+ this.updatePendingMessagesDisplay();
1904
+ this.ui.requestRender();
1905
+ break;
1840
1906
  case "message_start":
1841
1907
  if (event.message.role === "custom") {
1842
1908
  this.addMessageToChat(event.message);
@@ -1848,7 +1914,7 @@ export class InteractiveMode {
1848
1914
  this.ui.requestRender();
1849
1915
  }
1850
1916
  else if (event.message.role === "assistant") {
1851
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
1917
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
1852
1918
  this.streamingMessage = event.message;
1853
1919
  this.chatContainer.addChild(this.streamingComponent);
1854
1920
  this.streamingComponent.updateContent(this.streamingMessage);
@@ -1864,7 +1930,7 @@ export class InteractiveMode {
1864
1930
  if (!this.pendingTools.has(content.id)) {
1865
1931
  const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
1866
1932
  showImages: this.settingsManager.getShowImages(),
1867
- }, this.getRegisteredToolDefinition(content.name), this.ui);
1933
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
1868
1934
  component.setExpanded(this.toolOutputExpanded);
1869
1935
  this.chatContainer.addChild(component);
1870
1936
  this.pendingTools.set(content.id, component);
@@ -1924,7 +1990,7 @@ export class InteractiveMode {
1924
1990
  if (!component) {
1925
1991
  component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
1926
1992
  showImages: this.settingsManager.getShowImages(),
1927
- }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1993
+ }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
1928
1994
  component.setExpanded(this.toolOutputExpanded);
1929
1995
  this.chatContainer.addChild(component);
1930
1996
  this.pendingTools.set(event.toolCallId, component);
@@ -2146,7 +2212,7 @@ export class InteractiveMode {
2146
2212
  break;
2147
2213
  }
2148
2214
  case "assistant": {
2149
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
2215
+ const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
2150
2216
  this.chatContainer.addChild(assistantComponent);
2151
2217
  break;
2152
2218
  }
@@ -2178,7 +2244,7 @@ export class InteractiveMode {
2178
2244
  // Render tool call components
2179
2245
  for (const content of message.content) {
2180
2246
  if (content.type === "toolCall") {
2181
- const component = new ToolExecutionComponent(content.name, content.id, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui);
2247
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2182
2248
  component.setExpanded(this.toolOutputExpanded);
2183
2249
  this.chatContainer.addChild(component);
2184
2250
  if (message.stopReason === "aborted" || message.stopReason === "error") {
@@ -2271,13 +2337,7 @@ export class InteractiveMode {
2271
2337
  if (this.isShuttingDown)
2272
2338
  return;
2273
2339
  this.isShuttingDown = true;
2274
- // Emit shutdown event to extensions
2275
- const extensionRunner = this.session.extensionRunner;
2276
- if (extensionRunner?.hasHandlers("session_shutdown")) {
2277
- await extensionRunner.emit({
2278
- type: "session_shutdown",
2279
- });
2280
- }
2340
+ await this.runtimeHost.dispose();
2281
2341
  // Wait for any pending renders to complete
2282
2342
  // requestRender() uses process.nextTick(), so we wait one tick
2283
2343
  await new Promise((resolve) => process.nextTick(resolve));
@@ -2747,7 +2807,7 @@ export class InteractiveMode {
2747
2807
  },
2748
2808
  onTransportChange: (transport) => {
2749
2809
  this.settingsManager.setTransport(transport);
2750
- this.session.agent.setTransport(transport);
2810
+ this.session.agent.transport = transport;
2751
2811
  },
2752
2812
  onThinkingLevelChange: (level) => {
2753
2813
  this.session.setThinkingLevel(level);
@@ -3003,16 +3063,15 @@ export class InteractiveMode {
3003
3063
  }
3004
3064
  this.showSelector((done) => {
3005
3065
  const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
3006
- const result = await this.session.fork(entryId);
3066
+ const result = await this.runtimeHost.fork(entryId);
3007
3067
  if (result.cancelled) {
3008
- // Extension cancelled the fork
3009
3068
  done();
3010
3069
  this.ui.requestRender();
3011
3070
  return;
3012
3071
  }
3013
- this.chatContainer.clear();
3014
- this.renderInitialMessages();
3015
- this.editor.setText(result.selectedText);
3072
+ await this.handleRuntimeSessionChange();
3073
+ this.renderCurrentSessionState();
3074
+ this.editor.setText(result.selectedText ?? "");
3016
3075
  done();
3017
3076
  this.showStatus("Branched to new session");
3018
3077
  }, () => {
@@ -3148,24 +3207,23 @@ export class InteractiveMode {
3148
3207
  });
3149
3208
  }
3150
3209
  async handleResumeSession(sessionPath) {
3151
- // Stop loading animation
3152
3210
  if (this.loadingAnimation) {
3153
3211
  this.loadingAnimation.stop();
3154
3212
  this.loadingAnimation = undefined;
3155
3213
  }
3156
3214
  this.statusContainer.clear();
3157
- // Clear UI state
3158
- this.pendingMessagesContainer.clear();
3159
- this.compactionQueuedMessages = [];
3160
- this.streamingComponent = undefined;
3161
- this.streamingMessage = undefined;
3162
- this.pendingTools.clear();
3163
- // Switch session via AgentSession (emits extension session events)
3164
- await this.session.switchSession(sessionPath);
3165
- // Clear and re-render the chat
3166
- this.chatContainer.clear();
3167
- this.renderInitialMessages();
3168
- this.showStatus("Resumed session");
3215
+ try {
3216
+ const result = await this.runtimeHost.switchSession(sessionPath);
3217
+ if (result.cancelled) {
3218
+ return;
3219
+ }
3220
+ await this.handleRuntimeSessionChange();
3221
+ this.renderCurrentSessionState();
3222
+ this.showStatus("Resumed session");
3223
+ }
3224
+ catch (error) {
3225
+ await this.handleFatalRuntimeError("Failed to resume session", error);
3226
+ }
3169
3227
  }
3170
3228
  async showOAuthSelector(mode) {
3171
3229
  if (mode === "logout") {
@@ -3383,30 +3441,22 @@ export class InteractiveMode {
3383
3441
  return;
3384
3442
  }
3385
3443
  try {
3386
- // Stop loading animation
3387
3444
  if (this.loadingAnimation) {
3388
3445
  this.loadingAnimation.stop();
3389
3446
  this.loadingAnimation = undefined;
3390
3447
  }
3391
3448
  this.statusContainer.clear();
3392
- // Clear UI state
3393
- this.pendingMessagesContainer.clear();
3394
- this.compactionQueuedMessages = [];
3395
- this.streamingComponent = undefined;
3396
- this.streamingMessage = undefined;
3397
- this.pendingTools.clear();
3398
- const success = await this.session.importFromJsonl(inputPath);
3399
- if (!success) {
3400
- this.showWarning("Import cancelled");
3449
+ const result = await this.runtimeHost.importFromJsonl(inputPath);
3450
+ if (result.cancelled) {
3451
+ this.showStatus("Import cancelled");
3401
3452
  return;
3402
3453
  }
3403
- // Clear and re-render the chat
3404
- this.chatContainer.clear();
3405
- this.renderInitialMessages();
3454
+ await this.handleRuntimeSessionChange();
3455
+ this.renderCurrentSessionState();
3406
3456
  this.showStatus(`Session imported from: ${inputPath}`);
3407
3457
  }
3408
3458
  catch (error) {
3409
- this.showError(`Failed to import session: ${error instanceof Error ? error.message : "Unknown error"}`);
3459
+ await this.handleFatalRuntimeError("Failed to import session", error);
3410
3460
  }
3411
3461
  }
3412
3462
  async handleShareCommand() {
@@ -3716,25 +3766,25 @@ export class InteractiveMode {
3716
3766
  this.ui.requestRender();
3717
3767
  }
3718
3768
  async handleClearCommand() {
3719
- // Stop loading animation
3720
3769
  if (this.loadingAnimation) {
3721
3770
  this.loadingAnimation.stop();
3722
3771
  this.loadingAnimation = undefined;
3723
3772
  }
3724
3773
  this.statusContainer.clear();
3725
- // New session via session (emits extension session events)
3726
- await this.session.newSession();
3727
- // Clear UI state
3728
- this.headerContainer.clear();
3729
- this.chatContainer.clear();
3730
- this.pendingMessagesContainer.clear();
3731
- this.compactionQueuedMessages = [];
3732
- this.streamingComponent = undefined;
3733
- this.streamingMessage = undefined;
3734
- this.pendingTools.clear();
3735
- this.chatContainer.addChild(new Spacer(1));
3736
- this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
3737
- this.ui.requestRender();
3774
+ try {
3775
+ const result = await this.runtimeHost.newSession();
3776
+ if (result.cancelled) {
3777
+ return;
3778
+ }
3779
+ await this.handleRuntimeSessionChange();
3780
+ this.renderCurrentSessionState();
3781
+ this.chatContainer.addChild(new Spacer(1));
3782
+ this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
3783
+ this.ui.requestRender();
3784
+ }
3785
+ catch (error) {
3786
+ await this.handleFatalRuntimeError("Failed to create session", error);
3787
+ }
3738
3788
  }
3739
3789
  handleDebugCommand() {
3740
3790
  const width = this.ui.terminal.columns;
@@ -3786,7 +3836,7 @@ export class InteractiveMode {
3786
3836
  type: "user_bash",
3787
3837
  command,
3788
3838
  excludeFromContext,
3789
- cwd: process.cwd(),
3839
+ cwd: this.sessionManager.getCwd(),
3790
3840
  })
3791
3841
  : undefined;
3792
3842
  // If extension returned a full result, use it directly