@averyyy/pi-coding-agent 0.84.1-piclient.2 → 0.84.2-piclient.1

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 (138) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +1 -0
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +11 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/auth-command.d.ts.map +1 -1
  8. package/dist/cli/auth-command.js +5 -4
  9. package/dist/cli/auth-command.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +7 -2
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +6 -6
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/experimental.d.ts +4 -0
  15. package/dist/core/experimental.d.ts.map +1 -1
  16. package/dist/core/experimental.js +4 -0
  17. package/dist/core/experimental.js.map +1 -1
  18. package/dist/core/extensions/types.d.ts +4 -0
  19. package/dist/core/extensions/types.d.ts.map +1 -1
  20. package/dist/core/extensions/types.js.map +1 -1
  21. package/dist/core/keybindings.d.ts +29 -0
  22. package/dist/core/keybindings.d.ts.map +1 -1
  23. package/dist/core/keybindings.js +1 -0
  24. package/dist/core/keybindings.js.map +1 -1
  25. package/dist/core/model-config.d.ts +5 -0
  26. package/dist/core/model-config.d.ts.map +1 -1
  27. package/dist/core/model-config.js +1 -0
  28. package/dist/core/model-config.js.map +1 -1
  29. package/dist/core/project-trust.d.ts.map +1 -1
  30. package/dist/core/project-trust.js +2 -2
  31. package/dist/core/project-trust.js.map +1 -1
  32. package/dist/core/sdk.d.ts +5 -3
  33. package/dist/core/sdk.d.ts.map +1 -1
  34. package/dist/core/sdk.js +2 -1
  35. package/dist/core/sdk.js.map +1 -1
  36. package/dist/core/session-manager.d.ts.map +1 -1
  37. package/dist/core/session-manager.js +2 -2
  38. package/dist/core/session-manager.js.map +1 -1
  39. package/dist/core/settings-manager.d.ts +6 -0
  40. package/dist/core/settings-manager.d.ts.map +1 -1
  41. package/dist/core/settings-manager.js +12 -0
  42. package/dist/core/settings-manager.js.map +1 -1
  43. package/dist/core/system-prompt.d.ts.map +1 -1
  44. package/dist/core/system-prompt.js +1 -1
  45. package/dist/core/system-prompt.js.map +1 -1
  46. package/dist/core/tools/bash.d.ts.map +1 -1
  47. package/dist/core/tools/bash.js +2 -0
  48. package/dist/core/tools/bash.js.map +1 -1
  49. package/dist/core/tools/edit.d.ts.map +1 -1
  50. package/dist/core/tools/edit.js +2 -0
  51. package/dist/core/tools/edit.js.map +1 -1
  52. package/dist/core/tools/find.d.ts.map +1 -1
  53. package/dist/core/tools/find.js +1 -1
  54. package/dist/core/tools/find.js.map +1 -1
  55. package/dist/core/tools/grep.d.ts.map +1 -1
  56. package/dist/core/tools/grep.js +1 -1
  57. package/dist/core/tools/grep.js.map +1 -1
  58. package/dist/core/tools/read.d.ts.map +1 -1
  59. package/dist/core/tools/read.js +2 -0
  60. package/dist/core/tools/read.js.map +1 -1
  61. package/dist/core/tools/write.d.ts.map +1 -1
  62. package/dist/core/tools/write.js +2 -0
  63. package/dist/core/tools/write.js.map +1 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/main.d.ts.map +1 -1
  68. package/dist/main.js +5 -1
  69. package/dist/main.js.map +1 -1
  70. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/model-selector.js +2 -1
  72. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  73. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  74. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/settings-selector.js +10 -0
  76. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  77. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/tool-execution.js +10 -1
  79. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  80. package/dist/modes/interactive/interactive-mode.d.ts +17 -2
  81. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  82. package/dist/modes/interactive/interactive-mode.js +144 -28
  83. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  84. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  85. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  86. package/dist/modes/interactive/model-catalog-refresh.js +34 -0
  87. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  88. package/dist/modes/interactive/theme/dark.json +2 -0
  89. package/dist/modes/interactive/theme/light.json +2 -0
  90. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  91. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  92. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  93. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  94. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  95. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  96. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  97. package/dist/modes/interactive/theme/theme.js +12 -2
  98. package/dist/modes/interactive/theme/theme.js.map +1 -1
  99. package/dist/modes/json-event.d.ts +4 -1
  100. package/dist/modes/json-event.d.ts.map +1 -1
  101. package/dist/modes/json-event.js +5 -2
  102. package/dist/modes/json-event.js.map +1 -1
  103. package/dist/package-manager-cli.d.ts.map +1 -1
  104. package/dist/package-manager-cli.js +1 -1
  105. package/dist/package-manager-cli.js.map +1 -1
  106. package/dist/server/create-harness.d.ts.map +1 -1
  107. package/dist/server/create-harness.js +2 -0
  108. package/dist/server/create-harness.js.map +1 -1
  109. package/dist/utils/tools-manager.d.ts +10 -1
  110. package/dist/utils/tools-manager.d.ts.map +1 -1
  111. package/dist/utils/tools-manager.js +14 -19
  112. package/dist/utils/tools-manager.js.map +1 -1
  113. package/docs/compaction.md +6 -6
  114. package/docs/custom-provider.md +1 -1
  115. package/docs/environment-variables.md +8 -2
  116. package/docs/extensions.md +4 -0
  117. package/docs/json.md +6 -4
  118. package/docs/keybindings.md +9 -2
  119. package/docs/rpc.md +15 -4
  120. package/docs/sdk.md +9 -0
  121. package/docs/settings.md +31 -0
  122. package/docs/terminal-setup.md +23 -2
  123. package/docs/themes.md +24 -3
  124. package/docs/tui.md +2 -2
  125. package/docs/usage.md +3 -2
  126. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  127. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  128. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  129. package/examples/extensions/gondolin/package-lock.json +2 -2
  130. package/examples/extensions/gondolin/package.json +1 -1
  131. package/examples/extensions/sandbox/package-lock.json +2 -2
  132. package/examples/extensions/sandbox/package.json +1 -1
  133. package/examples/extensions/subagent/README.md +2 -0
  134. package/examples/extensions/subagent/agents.ts +40 -9
  135. package/examples/extensions/subagent/index.ts +21 -3
  136. package/examples/extensions/with-deps/package-lock.json +2 -2
  137. package/examples/extensions/with-deps/package.json +1 -1
  138. package/package.json +6 -6
@@ -69,9 +69,11 @@ import { TrustSelectorComponent } from "./components/trust-selector.js";
69
69
  import { UserMessageComponent } from "./components/user-message.js";
70
70
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
71
71
  import { editInExternalEditor } from "./external-editor.js";
72
+ import { refreshModelCatalogs } from "./model-catalog-refresh.js";
72
73
  import { getModelSearchText } from "./model-search.js";
73
74
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
74
75
  import { InteractiveThemeController } from "./theme/theme-controller.js";
76
+ const TRANSCRIPT_LAZY_LOAD_BATCH_SIZE = 20;
75
77
  function isExpandable(obj) {
76
78
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
77
79
  }
@@ -169,9 +171,21 @@ function formatLoginProviderCompletionDescription(provider) {
169
171
  export function createInteractiveTui(options) {
170
172
  const terminal = options.terminal ?? new ProcessTerminal();
171
173
  if (options.tuiMode === "fullscreen") {
174
+ const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
172
175
  return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
176
+ searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
177
+ searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
173
178
  openUrl: openBrowser,
174
179
  onRightClickPaste: options.onRightClickPaste,
180
+ copySelection: async (text) => {
181
+ try {
182
+ await copyToClipboard(text);
183
+ return true;
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ },
175
189
  });
176
190
  }
177
191
  return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
@@ -216,6 +230,11 @@ export class InteractiveMode {
216
230
  chatContainer;
217
231
  documentContainer;
218
232
  transcriptScrollView;
233
+ transcriptLazyEntries = [];
234
+ loadedTranscriptEntryIds = new Set();
235
+ transcriptWindowInitialized = false;
236
+ transcriptLatestCompactionId;
237
+ loadingEarlierTranscript = false;
219
238
  fullscreenLayoutRoot;
220
239
  pendingMessagesContainer;
221
240
  statusContainer;
@@ -253,6 +272,7 @@ export class InteractiveMode {
253
272
  // Status line tracking (for mutating immediately-sequential status updates)
254
273
  lastStatusSpacer = undefined;
255
274
  lastStatusText = undefined;
275
+ managedToolStatusStarted = false;
256
276
  // Streaming message tracking
257
277
  streamingComponent = undefined;
258
278
  streamingMessage = undefined;
@@ -333,6 +353,7 @@ export class InteractiveMode {
333
353
  });
334
354
  this.runtimeHost.setRebindSession(async () => {
335
355
  await this.rebindCurrentSession({ renderBeforeBind: true });
356
+ await this.themeController.applyFromSettings();
336
357
  });
337
358
  this.version = VERSION;
338
359
  this.renderer = createInteractiveTui({
@@ -375,7 +396,12 @@ export class InteractiveMode {
375
396
  this.outputPad = this.settingsManager.getOutputPad();
376
397
  // Register themes from resource loader and initialize
377
398
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
378
- this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
399
+ this.themeController = new InteractiveThemeController(this.ui, {
400
+ getSettingsManager: () => this.settingsManager,
401
+ showError: (message) => this.showError(message),
402
+ onChanged: () => this.updateEditorBorderColor(),
403
+ initialThemeSetting: options.initialThemeSetting,
404
+ });
379
405
  }
380
406
  getAutocompleteSourceTag(sourceInfo) {
381
407
  if (!sourceInfo) {
@@ -538,14 +564,14 @@ export class InteractiveMode {
538
564
  tui.setLayoutRoot(this.fullscreenLayoutRoot);
539
565
  }
540
566
  }
541
- stopInteractiveTui() {
542
- if (this.renderer.mode === "fullscreen") {
567
+ stopInteractiveTui(fullscreenExitOutput) {
568
+ if (this.renderer.mode === "fullscreen" && fullscreenExitOutput === "transcript") {
543
569
  while (this.renderer.hasOverlayEntries)
544
570
  this.renderer.hideOverlay();
545
571
  this.switchTuiMode("regular", false, false);
546
572
  this.renderer.renderNow();
547
573
  }
548
- this.ui.stop();
574
+ this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
549
575
  }
550
576
  switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
551
577
  const previousUi = this.renderer;
@@ -602,10 +628,6 @@ export class InteractiveMode {
602
628
  this.registerSignalHandlers();
603
629
  // Load changelog (only show new entries, skip for resumed sessions)
604
630
  this.changelogMarkdown = this.getChangelogForDisplay();
605
- // Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
606
- // Both are needed: fd for autocomplete, rg for grep tool and bash commands
607
- const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
608
- this.fdPath = fdPath;
609
631
  if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
610
632
  const modelList = this.session.scopedModels
611
633
  .map((sm) => {
@@ -625,6 +647,7 @@ export class InteractiveMode {
625
647
  follow: "end",
626
648
  primary: true,
627
649
  overscroll: "chain",
650
+ onScroll: (scrollTop) => this.handleTranscriptScroll(scrollTop),
628
651
  scrollbar: this.settingsManager.getFullscreenScrollbar(),
629
652
  scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
630
653
  });
@@ -649,9 +672,11 @@ export class InteractiveMode {
649
672
  this.widgetContainerBelow,
650
673
  this.footerContainer,
651
674
  ]);
675
+ // Accept text while startup completes, but only enable interrupt, exit, and submission feedback.
676
+ this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
677
+ this.defaultEditor.onCtrlD = () => this.handleCtrlD();
678
+ this.defaultEditor.onSubmit = (text) => this.handleStartupSubmit(text);
652
679
  this.ui.setFocus(this.editor);
653
- this.setupKeyHandlers();
654
- this.setupEditorSubmitHandler();
655
680
  // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
656
681
  this.ui.start();
657
682
  this.isInitialized = true;
@@ -703,6 +728,18 @@ export class InteractiveMode {
703
728
  this.headerContainer.addChild(this.builtInHeader);
704
729
  }
705
730
  this.ui.requestRender();
731
+ // Ensure fd and rg are available after mounting the TUI (downloads if missing, adds to PATH via getBinDir)
732
+ // so slow downloads do not make startup appear frozen.
733
+ // Both are needed: fd for autocomplete, rg for grep tool and bash commands.
734
+ const [fdPath] = await Promise.all([
735
+ ensureTool("fd", (status) => this.showManagedToolStatus(status)),
736
+ ensureTool("rg", (status) => this.showManagedToolStatus(status)),
737
+ ]);
738
+ this.fdPath = fdPath;
739
+ // Enable the remaining input handlers only after managed-tool setup completes.
740
+ this.setupKeyHandlers();
741
+ this.setupEditorSubmitHandler();
742
+ this.ui.requestRender();
706
743
  // Initialize extensions first so resources are shown before messages
707
744
  await this.rebindCurrentSession();
708
745
  // Render initial messages AFTER showing loaded resources
@@ -742,8 +779,7 @@ export class InteractiveMode {
742
779
  if (!process.env.PI_OFFLINE) {
743
780
  const controller = new AbortController();
744
781
  const timeout = setTimeout(() => controller.abort(), 15_000);
745
- void this.session.modelRuntime
746
- .refresh({ signal: controller.signal })
782
+ void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
747
783
  .then(() => this.updateAvailableProviderCount())
748
784
  .catch(() => { })
749
785
  .finally(() => clearTimeout(timeout));
@@ -1500,7 +1536,7 @@ export class InteractiveMode {
1500
1536
  const message = error instanceof Error ? error.message : String(error);
1501
1537
  this.showError(`${prefix}: ${message}`);
1502
1538
  stopThemeWatcher();
1503
- this.stop();
1539
+ this.stop("transcript");
1504
1540
  process.exit(1);
1505
1541
  }
1506
1542
  renderCurrentSessionState() {
@@ -2249,6 +2285,7 @@ export class InteractiveMode {
2249
2285
  this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
2250
2286
  this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
2251
2287
  this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
2288
+ this.defaultEditor.onAction("app.session.loadEarlier", () => this.loadEarlierTranscript());
2252
2289
  this.defaultEditor.onChange = (text) => {
2253
2290
  const wasBashMode = this.isBashMode;
2254
2291
  this.isBashMode = text.trimStart().startsWith("!");
@@ -2301,6 +2338,10 @@ export class InteractiveMode {
2301
2338
  // Silently ignore clipboard errors (may not have permission, etc.)
2302
2339
  }
2303
2340
  }
2341
+ handleStartupSubmit(text) {
2342
+ this.editor.setText(text);
2343
+ this.showStatus("Startup is still in progress");
2344
+ }
2304
2345
  setupEditorSubmitHandler() {
2305
2346
  this.defaultEditor.onSubmit = async (text) => {
2306
2347
  text = text.trim();
@@ -2783,6 +2824,19 @@ export class InteractiveMode {
2783
2824
  : message.content.filter((c) => c.type === "text");
2784
2825
  return textBlocks.map((c) => c.text).join("");
2785
2826
  }
2827
+ /** Show a managed-tool status update in the chat. */
2828
+ showManagedToolStatus(status) {
2829
+ if (!this.managedToolStatusStarted) {
2830
+ this.chatContainer.addChild(new Spacer(1));
2831
+ this.managedToolStatusStarted = true;
2832
+ }
2833
+ const message = status.type === "warning" ? `Warning: ${status.message}` : status.message;
2834
+ const color = status.type === "warning" ? "warning" : "dim";
2835
+ this.chatContainer.addChild(new Text(theme.fg(color, message), 1, 0));
2836
+ this.lastStatusSpacer = undefined;
2837
+ this.lastStatusText = undefined;
2838
+ this.ui.requestRender();
2839
+ }
2786
2840
  /**
2787
2841
  * Show a status message in the chat.
2788
2842
  *
@@ -3019,9 +3073,66 @@ export class InteractiveMode {
3019
3073
  this.chatContainer.addChild(new Spacer(1));
3020
3074
  this.chatContainer.addChild(new Text(text, 1, 0));
3021
3075
  }
3076
+ getTranscriptEntriesForRender(reset = false) {
3077
+ const branch = this.sessionManager.getBranch();
3078
+ const contextEntries = this.sessionManager.buildContextEntries();
3079
+ let latestCompactionId;
3080
+ for (let index = branch.length - 1; index >= 0; index--) {
3081
+ const entry = branch[index];
3082
+ if (entry?.type === "compaction") {
3083
+ latestCompactionId = entry.id;
3084
+ break;
3085
+ }
3086
+ }
3087
+ if (reset || (this.transcriptWindowInitialized && this.transcriptLatestCompactionId !== latestCompactionId)) {
3088
+ this.loadedTranscriptEntryIds.clear();
3089
+ }
3090
+ this.transcriptWindowInitialized = true;
3091
+ this.transcriptLatestCompactionId = latestCompactionId;
3092
+ const contextEntryIds = new Set(contextEntries.map((entry) => entry.id));
3093
+ const earlierEntries = branch.filter((entry) => !contextEntryIds.has(entry.id));
3094
+ const loadedEntries = earlierEntries.filter((entry) => this.loadedTranscriptEntryIds.has(entry.id));
3095
+ this.transcriptLazyEntries = earlierEntries.filter((entry) => !this.loadedTranscriptEntryIds.has(entry.id));
3096
+ return [...loadedEntries, ...contextEntries];
3097
+ }
3098
+ renderTranscriptEntries(entries, options = {}) {
3099
+ if (this.transcriptLazyEntries.length > 0) {
3100
+ const count = this.transcriptLazyEntries.length.toLocaleString();
3101
+ this.chatContainer.addChild(new Text(theme.fg("dim", `${count} earlier transcript entries hidden (${keyText("app.session.loadEarlier")} to load)`), 1, 0));
3102
+ this.chatContainer.addChild(new Spacer(1));
3103
+ }
3104
+ this.renderSessionEntries(entries, options);
3105
+ }
3106
+ handleTranscriptScroll(scrollTop) {
3107
+ if (scrollTop === 0) {
3108
+ this.loadEarlierTranscript();
3109
+ }
3110
+ }
3111
+ loadEarlierTranscript() {
3112
+ if (this.loadingEarlierTranscript || this.session.isStreaming || this.transcriptLazyEntries.length === 0) {
3113
+ return;
3114
+ }
3115
+ this.loadingEarlierTranscript = true;
3116
+ try {
3117
+ const firstLoadedIndex = Math.max(0, this.transcriptLazyEntries.length - TRANSCRIPT_LAZY_LOAD_BATCH_SIZE);
3118
+ const entriesToLoad = this.transcriptLazyEntries.slice(firstLoadedIndex);
3119
+ for (const entry of entriesToLoad) {
3120
+ this.loadedTranscriptEntryIds.add(entry.id);
3121
+ }
3122
+ this.rebuildChatFromMessages();
3123
+ const remaining = this.transcriptLazyEntries.length;
3124
+ this.showStatus(remaining > 0
3125
+ ? `Loaded ${entriesToLoad.length} earlier transcript entries (${remaining} remaining)`
3126
+ : "Loaded the beginning of the transcript");
3127
+ this.ui.requestRender();
3128
+ }
3129
+ finally {
3130
+ this.loadingEarlierTranscript = false;
3131
+ }
3132
+ }
3022
3133
  renderInitialMessages() {
3023
- const entries = this.sessionManager.getBranch();
3024
- this.renderSessionEntries(entries, {
3134
+ const entries = this.getTranscriptEntriesForRender(true);
3135
+ this.renderTranscriptEntries(entries, {
3025
3136
  updateFooter: true,
3026
3137
  populateHistory: true,
3027
3138
  });
@@ -3057,7 +3168,7 @@ export class InteractiveMode {
3057
3168
  }
3058
3169
  rebuildChatFromMessages() {
3059
3170
  this.chatContainer.clear();
3060
- this.renderSessionEntries(this.sessionManager.getBranch());
3171
+ this.renderTranscriptEntries(this.getTranscriptEntriesForRender());
3061
3172
  }
3062
3173
  // =========================================================================
3063
3174
  // Key handlers
@@ -3154,7 +3265,7 @@ export class InteractiveMode {
3154
3265
  this.ui.stop();
3155
3266
  }
3156
3267
  catch { }
3157
- console.error("pi exiting due to uncaughtException:");
3268
+ console.error(`${APP_NAME} exiting due to uncaughtException:`);
3158
3269
  console.error(error);
3159
3270
  process.exit(1);
3160
3271
  }
@@ -3639,7 +3750,7 @@ export class InteractiveMode {
3639
3750
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
3640
3751
  thinkingLevel: this.session.thinkingLevel,
3641
3752
  availableThinkingLevels: this.session.getAvailableThinkingLevels(),
3642
- currentTheme: this.settingsManager.getThemeSetting() || "dark",
3753
+ currentTheme: this.themeController.getThemeSelection() || "dark",
3643
3754
  terminalTheme: this.themeController.getTerminalTheme(),
3644
3755
  availableThemes: getAvailableThemes(),
3645
3756
  hideThinkingBlock: this.hideThinkingBlock,
@@ -3658,6 +3769,7 @@ export class InteractiveMode {
3658
3769
  clearOnShrink: this.settingsManager.getClearOnShrink(),
3659
3770
  showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
3660
3771
  tuiMode: this.ui.mode,
3772
+ fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
3661
3773
  fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
3662
3774
  warnings: this.settingsManager.getWarnings(),
3663
3775
  }, {
@@ -3713,7 +3825,7 @@ export class InteractiveMode {
3713
3825
  },
3714
3826
  onThemeChange: (themeSetting) => {
3715
3827
  this.settingsManager.setTheme(themeSetting);
3716
- void this.themeController.applyFromSettings();
3828
+ void this.themeController.setThemeSetting(themeSetting);
3717
3829
  },
3718
3830
  onThemePreview: (themeName) => this.themeController.preview(themeName),
3719
3831
  onHideThinkingBlockChange: (hidden) => {
@@ -3812,6 +3924,9 @@ export class InteractiveMode {
3812
3924
  this.statusContainer.clear();
3813
3925
  this.showStatus(`TUI mode: ${mode}`);
3814
3926
  },
3927
+ onFullscreenExitOutputChange: (output) => {
3928
+ this.settingsManager.setFullscreenExitOutput(output);
3929
+ },
3815
3930
  onFullscreenScrollbarChange: (mode) => {
3816
3931
  this.settingsManager.setFullscreenScrollbar(mode);
3817
3932
  this.applyFullscreenScrollbarSetting();
@@ -3864,7 +3979,7 @@ export class InteractiveMode {
3864
3979
  controller.abort();
3865
3980
  }, 15_000);
3866
3981
  try {
3867
- const result = await this.session.modelRuntime.refresh({ signal: controller.signal });
3982
+ const result = await refreshModelCatalogs(this.session.modelRuntime, controller.signal);
3868
3983
  if (result.aborted && timedOut) {
3869
3984
  this.showWarning("Model refresh timed out; searching cached models.");
3870
3985
  }
@@ -3952,7 +4067,7 @@ export class InteractiveMode {
3952
4067
  onSelect: (selection) => {
3953
4068
  trustStore.setMany(selection.updates);
3954
4069
  done();
3955
- this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart pi for this to take effect.`);
4070
+ this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart ${APP_NAME} for this to take effect.`);
3956
4071
  },
3957
4072
  onCancel: () => {
3958
4073
  done();
@@ -4052,8 +4167,7 @@ export class InteractiveMode {
4052
4167
  this.ui.requestRender();
4053
4168
  },
4054
4169
  });
4055
- void this.session.modelRuntime
4056
- .refresh({ signal: controller.signal })
4170
+ void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
4057
4171
  .then((result) => {
4058
4172
  if (disposed)
4059
4173
  return;
@@ -4607,7 +4721,7 @@ export class InteractiveMode {
4607
4721
  this.ui.requestRender();
4608
4722
  };
4609
4723
  const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
4610
- dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
4724
+ dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside ${APP_NAME}.`, [], true);
4611
4725
  this.editorContainer.clear();
4612
4726
  this.editorContainer.addChild(dialog);
4613
4727
  this.ui.setFocus(dialog);
@@ -4863,7 +4977,9 @@ export class InteractiveMode {
4863
4977
  this.showStatus(`Session exported to: ${filePath}`);
4864
4978
  }
4865
4979
  else {
4866
- const filePath = await this.session.exportToHtml(outputPath);
4980
+ const filePath = await this.session.exportToHtml(outputPath, {
4981
+ themeName: theme.name,
4982
+ });
4867
4983
  this.showStatus(`Session exported to: ${filePath}`);
4868
4984
  }
4869
4985
  }
@@ -4954,7 +5070,7 @@ export class InteractiveMode {
4954
5070
  // Export to a temp file
4955
5071
  const tmpFile = path.join(os.tmpdir(), "session.html");
4956
5072
  try {
4957
- await this.session.exportToHtml(tmpFile);
5073
+ await this.session.exportToHtml(tmpFile, { themeName: theme.name });
4958
5074
  }
4959
5075
  catch (error) {
4960
5076
  this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -5400,7 +5516,7 @@ export class InteractiveMode {
5400
5516
  // Ignore, will be emitted as an event
5401
5517
  }
5402
5518
  }
5403
- stop() {
5519
+ stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
5404
5520
  this.disposeActiveSelector();
5405
5521
  if (this.settingsManager.getShowTerminalProgress()) {
5406
5522
  this.ui.terminal.setProgress(false);
@@ -5414,7 +5530,7 @@ export class InteractiveMode {
5414
5530
  this.unsubscribe();
5415
5531
  }
5416
5532
  if (this.isInitialized) {
5417
- this.stopInteractiveTui();
5533
+ this.stopInteractiveTui(fullscreenExitOutput);
5418
5534
  this.isInitialized = false;
5419
5535
  }
5420
5536
  this.unregisterSignalHandlers();