@codex-infinity/pi-infinity 0.64.1 → 0.64.3

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 (79) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +10 -4
  3. package/dist/core/agent-session-runtime.d.ts +136 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +265 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +5 -42
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +13 -207
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  12. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  13. package/dist/core/export-html/tool-renderer.js +2 -2
  14. package/dist/core/export-html/tool-renderer.js.map +1 -1
  15. package/dist/core/extensions/index.d.ts +2 -2
  16. package/dist/core/extensions/index.d.ts.map +1 -1
  17. package/dist/core/extensions/index.js +1 -1
  18. package/dist/core/extensions/index.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +16 -16
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js +10 -0
  22. package/dist/core/extensions/types.js.map +1 -1
  23. package/dist/core/footer-data-provider.d.ts +5 -1
  24. package/dist/core/footer-data-provider.d.ts.map +1 -1
  25. package/dist/core/footer-data-provider.js +69 -8
  26. package/dist/core/footer-data-provider.js.map +1 -1
  27. package/dist/core/index.d.ts +2 -1
  28. package/dist/core/index.d.ts.map +1 -1
  29. package/dist/core/index.js +2 -1
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/sdk.d.ts +4 -1
  32. package/dist/core/sdk.d.ts.map +1 -1
  33. package/dist/core/sdk.js +3 -0
  34. package/dist/core/sdk.js.map +1 -1
  35. package/dist/index.d.ts +3 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/main.d.ts.map +1 -1
  40. package/dist/main.js +42 -9
  41. package/dist/main.js.map +1 -1
  42. package/dist/modes/interactive/components/footer.d.ts +1 -0
  43. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  44. package/dist/modes/interactive/components/footer.js +4 -1
  45. package/dist/modes/interactive/components/footer.js.map +1 -1
  46. package/dist/modes/interactive/interactive-mode.d.ts +8 -4
  47. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  48. package/dist/modes/interactive/interactive-mode.js +89 -86
  49. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  50. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  51. package/dist/modes/interactive/theme/theme.js +6 -11
  52. package/dist/modes/interactive/theme/theme.js.map +1 -1
  53. package/dist/modes/print-mode.d.ts +2 -2
  54. package/dist/modes/print-mode.d.ts.map +1 -1
  55. package/dist/modes/print-mode.js +37 -36
  56. package/dist/modes/print-mode.js.map +1 -1
  57. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  58. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  59. package/dist/modes/rpc/rpc-mode.js +69 -49
  60. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  61. package/docs/extensions.md +75 -19
  62. package/docs/sdk.md +160 -72
  63. package/docs/tree.md +1 -1
  64. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  65. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  66. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  67. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  68. package/examples/extensions/hello.ts +18 -17
  69. package/examples/extensions/hidden-thinking-label.ts +0 -4
  70. package/examples/extensions/rpc-demo.ts +3 -9
  71. package/examples/extensions/status-line.ts +0 -8
  72. package/examples/extensions/todo.ts +0 -2
  73. package/examples/extensions/tools.ts +0 -5
  74. package/examples/extensions/widget-placement.ts +4 -12
  75. package/examples/extensions/with-deps/package-lock.json +2 -2
  76. package/examples/extensions/with-deps/package.json +1 -1
  77. package/examples/sdk/13-session-runtime.ts +49 -0
  78. package/examples/sdk/README.md +2 -1
  79. package/package.json +4 -4
@@ -54,6 +54,9 @@ function isExpandable(obj) {
54
54
  }
55
55
  export class InteractiveMode {
56
56
  // Convenience accessors
57
+ get session() {
58
+ return this.runtimeHost.session;
59
+ }
57
60
  get agent() {
58
61
  return this.session.agent;
59
62
  }
@@ -63,7 +66,7 @@ export class InteractiveMode {
63
66
  get settingsManager() {
64
67
  return this.session.settingsManager;
65
68
  }
66
- constructor(session, options = {}) {
69
+ constructor(runtimeHost, options = {}) {
67
70
  this.options = options;
68
71
  this.isInitialized = false;
69
72
  this.loadingAnimation = undefined;
@@ -121,7 +124,7 @@ export class InteractiveMode {
121
124
  * Emits shutdown event to extensions, then exits.
122
125
  */
123
126
  this.isShuttingDown = false;
124
- this.session = session;
127
+ this.runtimeHost = runtimeHost;
125
128
  this.version = VERSION;
126
129
  this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
127
130
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
@@ -142,9 +145,9 @@ export class InteractiveMode {
142
145
  this.editor = this.defaultEditor;
143
146
  this.editorContainer = new Container();
144
147
  this.editorContainer.addChild(this.editor);
145
- this.footerDataProvider = new FooterDataProvider();
146
- this.footer = new FooterComponent(session, this.footerDataProvider);
147
- this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
148
+ this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
149
+ this.footer = new FooterComponent(this.session, this.footerDataProvider);
150
+ this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
148
151
  // Load hide thinking block setting
149
152
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
150
153
  // Register themes from resource loader and initialize
@@ -251,7 +254,7 @@ export class InteractiveMode {
251
254
  }
252
255
  }
253
256
  // Setup autocomplete
254
- this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], process.cwd(), fdPath);
257
+ this.autocompleteProvider = new CombinedAutocompleteProvider([...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList], this.sessionManager.getCwd(), fdPath);
255
258
  this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
256
259
  if (this.editor !== this.defaultEditor) {
257
260
  this.editor.setAutocompleteProvider?.(this.autocompleteProvider);
@@ -346,7 +349,7 @@ export class InteractiveMode {
346
349
  this.ui.start();
347
350
  this.isInitialized = true;
348
351
  // Initialize extensions first so resources are shown before messages
349
- await this.initExtensions();
352
+ await this.bindCurrentSessionExtensions();
350
353
  // Render initial messages AFTER showing loaded resources
351
354
  this.renderInitialMessages();
352
355
  // Set terminal title
@@ -370,7 +373,7 @@ export class InteractiveMode {
370
373
  * Update terminal title with session name and cwd.
371
374
  */
372
375
  updateTerminalTitle() {
373
- const cwdBasename = path.basename(process.cwd());
376
+ const cwdBasename = path.basename(this.sessionManager.getCwd());
374
377
  const sessionName = this.sessionManager.getSessionName();
375
378
  if (sessionName) {
376
379
  this.ui.terminal.setTitle(`π - ${sessionName} - ${cwdBasename}`);
@@ -477,7 +480,7 @@ export class InteractiveMode {
477
480
  }
478
481
  try {
479
482
  const packageManager = new DefaultPackageManager({
480
- cwd: process.cwd(),
483
+ cwd: this.sessionManager.getCwd(),
481
484
  agentDir: getAgentDir(),
482
485
  settingsManager: this.settingsManager,
483
486
  });
@@ -865,7 +868,7 @@ export class InteractiveMode {
865
868
  /**
866
869
  * Initialize the extension system with TUI-based UI context.
867
870
  */
868
- async initExtensions() {
871
+ async bindCurrentSessionExtensions() {
869
872
  const uiContext = this.createExtensionUIContext();
870
873
  await this.session.bindExtensions({
871
874
  uiContext,
@@ -877,33 +880,23 @@ export class InteractiveMode {
877
880
  this.loadingAnimation = undefined;
878
881
  }
879
882
  this.statusContainer.clear();
880
- // Delegate to AgentSession (handles setup + agent state sync)
881
- const success = await this.session.newSession(options);
882
- if (!success) {
883
- return { cancelled: true };
883
+ const result = await this.runtimeHost.newSession(options);
884
+ if (!result.cancelled) {
885
+ await this.handleRuntimeSessionChange();
886
+ this.renderCurrentSessionState();
887
+ this.ui.requestRender();
884
888
  }
885
- // Clear UI state
886
- this.chatContainer.clear();
887
- this.pendingMessagesContainer.clear();
888
- this.compactionQueuedMessages = [];
889
- this.streamingComponent = undefined;
890
- this.streamingMessage = undefined;
891
- this.pendingTools.clear();
892
- // Render any messages added via setup, or show empty session
893
- this.renderInitialMessages();
894
- this.ui.requestRender();
895
- return { cancelled: false };
889
+ return result;
896
890
  },
897
891
  fork: async (entryId) => {
898
- const result = await this.session.fork(entryId);
899
- if (result.cancelled) {
900
- return { cancelled: true };
892
+ const result = await this.runtimeHost.fork(entryId);
893
+ if (!result.cancelled) {
894
+ await this.handleRuntimeSessionChange();
895
+ this.renderCurrentSessionState();
896
+ this.editor.setText(result.selectedText ?? "");
897
+ this.showStatus("Forked to new session");
901
898
  }
902
- this.chatContainer.clear();
903
- this.renderInitialMessages();
904
- this.editor.setText(result.selectedText);
905
- this.showStatus("Forked to new session");
906
- return { cancelled: false };
899
+ return { cancelled: result.cancelled };
907
900
  },
908
901
  navigateTree: async (targetId, options) => {
909
902
  const result = await this.session.navigateTree(targetId, {
@@ -951,6 +944,42 @@ export class InteractiveMode {
951
944
  this.setupExtensionShortcuts(extensionRunner);
952
945
  this.showLoadedResources({ force: false });
953
946
  }
947
+ applyRuntimeSettings() {
948
+ this.footer.setSession(this.session);
949
+ this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
950
+ this.footerDataProvider.setCwd(this.sessionManager.getCwd());
951
+ this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
952
+ this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
953
+ this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
954
+ const editorPaddingX = this.settingsManager.getEditorPaddingX();
955
+ const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
956
+ this.defaultEditor.setPaddingX(editorPaddingX);
957
+ this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
958
+ if (this.editor !== this.defaultEditor) {
959
+ this.editor.setPaddingX?.(editorPaddingX);
960
+ this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
961
+ }
962
+ }
963
+ async handleRuntimeSessionChange() {
964
+ this.resetExtensionUI();
965
+ this.unsubscribe?.();
966
+ this.unsubscribe = undefined;
967
+ this.applyRuntimeSettings();
968
+ await this.bindCurrentSessionExtensions();
969
+ this.subscribeToAgent();
970
+ await this.updateAvailableProviderCount();
971
+ this.updateEditorBorderColor();
972
+ this.updateTerminalTitle();
973
+ }
974
+ renderCurrentSessionState() {
975
+ this.chatContainer.clear();
976
+ this.pendingMessagesContainer.clear();
977
+ this.compactionQueuedMessages = [];
978
+ this.streamingComponent = undefined;
979
+ this.streamingMessage = undefined;
980
+ this.pendingTools.clear();
981
+ this.renderInitialMessages();
982
+ }
954
983
  /**
955
984
  * Get a registered tool definition by name (for custom rendering).
956
985
  */
@@ -968,7 +997,7 @@ export class InteractiveMode {
968
997
  const createContext = () => ({
969
998
  ui: this.createExtensionUIContext(),
970
999
  hasUI: true,
971
- cwd: process.cwd(),
1000
+ cwd: this.sessionManager.getCwd(),
972
1001
  sessionManager: this.sessionManager,
973
1002
  modelRegistry: this.session.modelRegistry,
974
1003
  model: this.session.model,
@@ -1864,7 +1893,7 @@ export class InteractiveMode {
1864
1893
  if (!this.pendingTools.has(content.id)) {
1865
1894
  const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
1866
1895
  showImages: this.settingsManager.getShowImages(),
1867
- }, this.getRegisteredToolDefinition(content.name), this.ui);
1896
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
1868
1897
  component.setExpanded(this.toolOutputExpanded);
1869
1898
  this.chatContainer.addChild(component);
1870
1899
  this.pendingTools.set(content.id, component);
@@ -1924,7 +1953,7 @@ export class InteractiveMode {
1924
1953
  if (!component) {
1925
1954
  component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
1926
1955
  showImages: this.settingsManager.getShowImages(),
1927
- }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1956
+ }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
1928
1957
  component.setExpanded(this.toolOutputExpanded);
1929
1958
  this.chatContainer.addChild(component);
1930
1959
  this.pendingTools.set(event.toolCallId, component);
@@ -2178,7 +2207,7 @@ export class InteractiveMode {
2178
2207
  // Render tool call components
2179
2208
  for (const content of message.content) {
2180
2209
  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);
2210
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2182
2211
  component.setExpanded(this.toolOutputExpanded);
2183
2212
  this.chatContainer.addChild(component);
2184
2213
  if (message.stopReason === "aborted" || message.stopReason === "error") {
@@ -2266,13 +2295,7 @@ export class InteractiveMode {
2266
2295
  if (this.isShuttingDown)
2267
2296
  return;
2268
2297
  this.isShuttingDown = true;
2269
- // Emit shutdown event to extensions
2270
- const extensionRunner = this.session.extensionRunner;
2271
- if (extensionRunner?.hasHandlers("session_shutdown")) {
2272
- await extensionRunner.emit({
2273
- type: "session_shutdown",
2274
- });
2275
- }
2298
+ await this.runtimeHost.dispose();
2276
2299
  // Wait for any pending renders to complete
2277
2300
  // requestRender() uses process.nextTick(), so we wait one tick
2278
2301
  await new Promise((resolve) => process.nextTick(resolve));
@@ -2998,16 +3021,15 @@ export class InteractiveMode {
2998
3021
  }
2999
3022
  this.showSelector((done) => {
3000
3023
  const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
3001
- const result = await this.session.fork(entryId);
3024
+ const result = await this.runtimeHost.fork(entryId);
3002
3025
  if (result.cancelled) {
3003
- // Extension cancelled the fork
3004
3026
  done();
3005
3027
  this.ui.requestRender();
3006
3028
  return;
3007
3029
  }
3008
- this.chatContainer.clear();
3009
- this.renderInitialMessages();
3010
- this.editor.setText(result.selectedText);
3030
+ await this.handleRuntimeSessionChange();
3031
+ this.renderCurrentSessionState();
3032
+ this.editor.setText(result.selectedText ?? "");
3011
3033
  done();
3012
3034
  this.showStatus("Branched to new session");
3013
3035
  }, () => {
@@ -3143,23 +3165,17 @@ export class InteractiveMode {
3143
3165
  });
3144
3166
  }
3145
3167
  async handleResumeSession(sessionPath) {
3146
- // Stop loading animation
3147
3168
  if (this.loadingAnimation) {
3148
3169
  this.loadingAnimation.stop();
3149
3170
  this.loadingAnimation = undefined;
3150
3171
  }
3151
3172
  this.statusContainer.clear();
3152
- // Clear UI state
3153
- this.pendingMessagesContainer.clear();
3154
- this.compactionQueuedMessages = [];
3155
- this.streamingComponent = undefined;
3156
- this.streamingMessage = undefined;
3157
- this.pendingTools.clear();
3158
- // Switch session via AgentSession (emits extension session events)
3159
- await this.session.switchSession(sessionPath);
3160
- // Clear and re-render the chat
3161
- this.chatContainer.clear();
3162
- this.renderInitialMessages();
3173
+ const result = await this.runtimeHost.switchSession(sessionPath);
3174
+ if (result.cancelled) {
3175
+ return;
3176
+ }
3177
+ await this.handleRuntimeSessionChange();
3178
+ this.renderCurrentSessionState();
3163
3179
  this.showStatus("Resumed session");
3164
3180
  }
3165
3181
  async showOAuthSelector(mode) {
@@ -3378,26 +3394,18 @@ export class InteractiveMode {
3378
3394
  return;
3379
3395
  }
3380
3396
  try {
3381
- // Stop loading animation
3382
3397
  if (this.loadingAnimation) {
3383
3398
  this.loadingAnimation.stop();
3384
3399
  this.loadingAnimation = undefined;
3385
3400
  }
3386
3401
  this.statusContainer.clear();
3387
- // Clear UI state
3388
- this.pendingMessagesContainer.clear();
3389
- this.compactionQueuedMessages = [];
3390
- this.streamingComponent = undefined;
3391
- this.streamingMessage = undefined;
3392
- this.pendingTools.clear();
3393
- const success = await this.session.importFromJsonl(inputPath);
3394
- if (!success) {
3395
- this.showWarning("Import cancelled");
3402
+ const result = await this.runtimeHost.importFromJsonl(inputPath);
3403
+ if (result.cancelled) {
3404
+ this.showStatus("Import cancelled");
3396
3405
  return;
3397
3406
  }
3398
- // Clear and re-render the chat
3399
- this.chatContainer.clear();
3400
- this.renderInitialMessages();
3407
+ await this.handleRuntimeSessionChange();
3408
+ this.renderCurrentSessionState();
3401
3409
  this.showStatus(`Session imported from: ${inputPath}`);
3402
3410
  }
3403
3411
  catch (error) {
@@ -3711,22 +3719,17 @@ export class InteractiveMode {
3711
3719
  this.ui.requestRender();
3712
3720
  }
3713
3721
  async handleClearCommand() {
3714
- // Stop loading animation
3715
3722
  if (this.loadingAnimation) {
3716
3723
  this.loadingAnimation.stop();
3717
3724
  this.loadingAnimation = undefined;
3718
3725
  }
3719
3726
  this.statusContainer.clear();
3720
- // New session via session (emits extension session events)
3721
- await this.session.newSession();
3722
- // Clear UI state
3723
- this.headerContainer.clear();
3724
- this.chatContainer.clear();
3725
- this.pendingMessagesContainer.clear();
3726
- this.compactionQueuedMessages = [];
3727
- this.streamingComponent = undefined;
3728
- this.streamingMessage = undefined;
3729
- this.pendingTools.clear();
3727
+ const result = await this.runtimeHost.newSession();
3728
+ if (result.cancelled) {
3729
+ return;
3730
+ }
3731
+ await this.handleRuntimeSessionChange();
3732
+ this.renderCurrentSessionState();
3730
3733
  this.chatContainer.addChild(new Spacer(1));
3731
3734
  this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
3732
3735
  this.ui.requestRender();
@@ -3781,7 +3784,7 @@ export class InteractiveMode {
3781
3784
  type: "user_bash",
3782
3785
  command,
3783
3786
  excludeFromContext,
3784
- cwd: process.cwd(),
3787
+ cwd: this.sessionManager.getCwd(),
3785
3788
  })
3786
3789
  : undefined;
3787
3790
  // If extension returned a full result, use it directly