@oh-my-pi/pi-coding-agent 17.0.0 → 17.0.2

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 (202) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/dist/cli.js +4006 -3966
  3. package/dist/types/advisor/advise-tool.d.ts +5 -2
  4. package/dist/types/advisor/config.d.ts +14 -6
  5. package/dist/types/advisor/runtime.d.ts +20 -11
  6. package/dist/types/autolearn/controller.d.ts +1 -0
  7. package/dist/types/cli-commands.d.ts +14 -1
  8. package/dist/types/config/model-discovery.d.ts +17 -0
  9. package/dist/types/config/model-registry.d.ts +4 -0
  10. package/dist/types/config/model-resolver.d.ts +6 -2
  11. package/dist/types/config/models-config-schema.d.ts +10 -0
  12. package/dist/types/config/models-config.d.ts +6 -0
  13. package/dist/types/config/settings-schema.d.ts +32 -7
  14. package/dist/types/config/settings.d.ts +50 -0
  15. package/dist/types/cursor.d.ts +11 -0
  16. package/dist/types/discovery/helpers.d.ts +5 -2
  17. package/dist/types/discovery/substitute-plugin-root.d.ts +20 -7
  18. package/dist/types/exec/bash-executor.d.ts +2 -0
  19. package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
  20. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  21. package/dist/types/extensibility/extensions/types.d.ts +18 -0
  22. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
  23. package/dist/types/extensibility/shared-events.d.ts +6 -0
  24. package/dist/types/extensibility/utils.d.ts +2 -2
  25. package/dist/types/internal-urls/agent-protocol.d.ts +1 -0
  26. package/dist/types/lib/xai-http.d.ts +11 -0
  27. package/dist/types/mcp/transports/stdio.d.ts +13 -1
  28. package/dist/types/modes/acp/acp-agent.d.ts +3 -0
  29. package/dist/types/modes/acp/acp-mode.d.ts +6 -2
  30. package/dist/types/modes/components/advisor-config.d.ts +8 -1
  31. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  32. package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +1 -0
  33. package/dist/types/modes/components/custom-editor.d.ts +23 -1
  34. package/dist/types/modes/components/hook-editor.d.ts +16 -2
  35. package/dist/types/modes/components/login-dialog.test.d.ts +1 -0
  36. package/dist/types/modes/components/model-hub.d.ts +5 -2
  37. package/dist/types/modes/components/session-selector.d.ts +4 -0
  38. package/dist/types/modes/components/tool-execution.d.ts +8 -1
  39. package/dist/types/modes/components/tool-execution.test.d.ts +1 -0
  40. package/dist/types/modes/controllers/command-controller.d.ts +8 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  42. package/dist/types/modes/interactive-mode.d.ts +6 -0
  43. package/dist/types/modes/noninteractive-dispose.test.d.ts +1 -0
  44. package/dist/types/modes/print-mode.d.ts +1 -1
  45. package/dist/types/modes/types.d.ts +10 -0
  46. package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -2
  47. package/dist/types/modes/warp-events.d.ts +24 -0
  48. package/dist/types/modes/warp-events.test.d.ts +1 -0
  49. package/dist/types/plan-mode/model-transition.d.ts +47 -0
  50. package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
  51. package/dist/types/registry/agent-lifecycle.d.ts +26 -1
  52. package/dist/types/sdk.d.ts +13 -2
  53. package/dist/types/session/agent-session.d.ts +43 -11
  54. package/dist/types/session/session-history-format.d.ts +10 -0
  55. package/dist/types/session/session-manager.d.ts +14 -0
  56. package/dist/types/session/streaming-output.d.ts +2 -0
  57. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
  58. package/dist/types/slash-commands/types.d.ts +4 -4
  59. package/dist/types/system-prompt.d.ts +1 -1
  60. package/dist/types/task/label.d.ts +1 -1
  61. package/dist/types/telemetry-export.d.ts +34 -9
  62. package/dist/types/tools/approval.d.ts +8 -0
  63. package/dist/types/tools/bash.d.ts +2 -0
  64. package/dist/types/tools/browser/registry.d.ts +7 -3
  65. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  66. package/dist/types/tools/essential-tools.d.ts +29 -0
  67. package/dist/types/tools/image-gen.d.ts +2 -1
  68. package/dist/types/tools/index.d.ts +4 -1
  69. package/dist/types/tools/manage-skill.d.ts +2 -0
  70. package/dist/types/tools/xdev.d.ts +11 -2
  71. package/dist/types/utils/title-generator.d.ts +2 -1
  72. package/dist/types/web/search/index.d.ts +2 -0
  73. package/dist/types/web/search/providers/base.d.ts +3 -0
  74. package/dist/types/web/search/providers/kimi.d.ts +4 -1
  75. package/dist/types/web/search/types.d.ts +1 -1
  76. package/package.json +21 -16
  77. package/src/advisor/__tests__/advisor.test.ts +1358 -86
  78. package/src/advisor/__tests__/config.test.ts +58 -2
  79. package/src/advisor/advise-tool.ts +7 -3
  80. package/src/advisor/config.ts +76 -24
  81. package/src/advisor/runtime.ts +445 -92
  82. package/src/autolearn/controller.ts +23 -28
  83. package/src/cli/bench-cli.ts +4 -1
  84. package/src/cli/grep-cli.ts +2 -1
  85. package/src/cli-commands.ts +57 -23
  86. package/src/cli.ts +5 -1
  87. package/src/config/model-discovery.ts +81 -21
  88. package/src/config/model-registry.ts +34 -6
  89. package/src/config/model-resolver.ts +57 -12
  90. package/src/config/models-config-schema.ts +1 -0
  91. package/src/config/settings-schema.ts +43 -6
  92. package/src/config/settings.ts +405 -25
  93. package/src/cursor.ts +20 -3
  94. package/src/debug/report-bundle.ts +40 -4
  95. package/src/discovery/codex.ts +14 -4
  96. package/src/discovery/helpers.ts +28 -5
  97. package/src/discovery/substitute-plugin-root.ts +23 -7
  98. package/src/exec/bash-executor.ts +14 -5
  99. package/src/export/html/template.js +2 -0
  100. package/src/extensibility/custom-tools/loader.ts +3 -3
  101. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  102. package/src/extensibility/extensions/loader.ts +3 -3
  103. package/src/extensibility/extensions/managed-timers.ts +83 -0
  104. package/src/extensibility/extensions/runner.ts +26 -0
  105. package/src/extensibility/extensions/types.ts +18 -0
  106. package/src/extensibility/extensions/wrapper.ts +2 -1
  107. package/src/extensibility/hooks/loader.ts +3 -3
  108. package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
  109. package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
  110. package/src/extensibility/plugins/manager.ts +2 -2
  111. package/src/extensibility/shared-events.ts +6 -0
  112. package/src/extensibility/utils.ts +91 -25
  113. package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +73 -0
  114. package/src/internal-urls/agent-protocol.ts +73 -39
  115. package/src/irc/bus.ts +22 -3
  116. package/src/launch/broker.ts +3 -2
  117. package/src/launch/client.ts +2 -2
  118. package/src/launch/presence.ts +2 -2
  119. package/src/lib/xai-http.ts +28 -2
  120. package/src/lsp/client.ts +1 -1
  121. package/src/main.ts +11 -8
  122. package/src/mcp/manager.ts +9 -3
  123. package/src/mcp/transports/stdio.ts +103 -23
  124. package/src/modes/acp/acp-agent.ts +28 -9
  125. package/src/modes/acp/acp-event-mapper.ts +8 -0
  126. package/src/modes/acp/acp-mode.ts +18 -4
  127. package/src/modes/components/advisor-config.ts +65 -3
  128. package/src/modes/components/ask-dialog.ts +1 -1
  129. package/src/modes/components/assistant-message.ts +35 -14
  130. package/src/modes/components/chat-transcript-builder.ts +2 -0
  131. package/src/modes/components/custom-editor-plugin-ctor.test.ts +36 -0
  132. package/src/modes/components/custom-editor.ts +62 -1
  133. package/src/modes/components/hook-editor.ts +35 -5
  134. package/src/modes/components/login-dialog.test.ts +56 -0
  135. package/src/modes/components/login-dialog.ts +7 -3
  136. package/src/modes/components/model-hub.ts +138 -42
  137. package/src/modes/components/plan-review-overlay.ts +51 -10
  138. package/src/modes/components/session-selector.ts +10 -0
  139. package/src/modes/components/status-line/component.test.ts +1 -0
  140. package/src/modes/components/status-line/component.ts +2 -4
  141. package/src/modes/components/status-line/segments.ts +21 -6
  142. package/src/modes/components/tool-execution.test.ts +101 -0
  143. package/src/modes/components/tool-execution.ts +117 -9
  144. package/src/modes/components/transcript-container.ts +30 -2
  145. package/src/modes/controllers/command-controller-shared.ts +1 -1
  146. package/src/modes/controllers/command-controller.ts +168 -48
  147. package/src/modes/controllers/event-controller.ts +6 -0
  148. package/src/modes/controllers/extension-ui-controller.ts +4 -22
  149. package/src/modes/controllers/input-controller.ts +12 -12
  150. package/src/modes/controllers/selector-controller.ts +208 -46
  151. package/src/modes/interactive-mode.ts +205 -65
  152. package/src/modes/noninteractive-dispose.test.ts +60 -0
  153. package/src/modes/print-mode.ts +10 -5
  154. package/src/modes/rpc/host-tools.ts +2 -1
  155. package/src/modes/rpc/rpc-mode.ts +31 -7
  156. package/src/modes/theme/theme.ts +2 -2
  157. package/src/modes/types.ts +10 -0
  158. package/src/modes/utils/interactive-context-helpers.ts +3 -1
  159. package/src/modes/utils/transcript-render-helpers.ts +3 -2
  160. package/src/modes/warp-events.test.ts +794 -0
  161. package/src/modes/warp-events.ts +232 -0
  162. package/src/plan-mode/model-transition.test.ts +60 -0
  163. package/src/plan-mode/model-transition.ts +51 -0
  164. package/src/prompts/system/system-prompt.md +1 -1
  165. package/src/registry/agent-lifecycle.ts +133 -18
  166. package/src/sdk.ts +227 -44
  167. package/src/session/agent-session.ts +1364 -382
  168. package/src/session/session-history-format.ts +20 -5
  169. package/src/session/session-listing.ts +6 -5
  170. package/src/session/session-loader.ts +9 -0
  171. package/src/session/session-manager.ts +48 -0
  172. package/src/session/session-persistence.ts +11 -0
  173. package/src/session/streaming-output.ts +3 -1
  174. package/src/slash-commands/builtin-registry.ts +9 -0
  175. package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
  176. package/src/slash-commands/types.ts +4 -4
  177. package/src/system-prompt.ts +2 -2
  178. package/src/task/executor.ts +1 -1
  179. package/src/task/label.ts +2 -0
  180. package/src/telemetry-export.ts +453 -97
  181. package/src/tools/approval.ts +11 -0
  182. package/src/tools/bash.ts +71 -38
  183. package/src/tools/browser/registry.ts +9 -2
  184. package/src/tools/browser/tab-supervisor.ts +65 -10
  185. package/src/tools/browser.ts +4 -3
  186. package/src/tools/essential-tools.ts +45 -0
  187. package/src/tools/eval.ts +3 -0
  188. package/src/tools/gh.ts +169 -2
  189. package/src/tools/image-gen.ts +624 -513
  190. package/src/tools/index.ts +10 -6
  191. package/src/tools/manage-skill.ts +5 -3
  192. package/src/tools/output-meta.ts +7 -0
  193. package/src/tools/path-utils.ts +8 -5
  194. package/src/tools/read.ts +48 -3
  195. package/src/tools/write.ts +22 -4
  196. package/src/tools/xdev.ts +14 -3
  197. package/src/utils/title-generator.ts +15 -4
  198. package/src/web/search/index.ts +21 -6
  199. package/src/web/search/providers/base.ts +3 -0
  200. package/src/web/search/providers/kimi.ts +18 -12
  201. package/src/web/search/providers/xai.ts +40 -9
  202. package/src/web/search/types.ts +6 -1
@@ -13,7 +13,11 @@ import {
13
13
  saveWatchdogConfigFile,
14
14
  } from "../../advisor";
15
15
  import { reset as resetCapabilities } from "../../capability";
16
- import { formatModelSelectorValue, resolveAdvisorRoleSelection } from "../../config/model-resolver";
16
+ import {
17
+ formatModelSelectorValue,
18
+ resolveAdvisorRoleSelection,
19
+ resolveModelRoleValue,
20
+ } from "../../config/model-resolver";
17
21
  import { getRoleInfo } from "../../config/model-roles";
18
22
  import { settings } from "../../config/settings";
19
23
  import { disableProvider, enableProvider } from "../../discovery";
@@ -46,7 +50,12 @@ import {
46
50
  type ResetUsageAccount,
47
51
  toResetUsageAccounts,
48
52
  } from "../../slash-commands/helpers/reset-usage";
49
- import { AUTO_THINKING, type ConfiguredThinkingLevel } from "../../thinking";
53
+ import {
54
+ AUTO_THINKING,
55
+ type ConfiguredThinkingLevel,
56
+ concreteThinkingLevel,
57
+ parseConfiguredThinkingLevel,
58
+ } from "../../thinking";
50
59
  import {
51
60
  isImageProviderPreference,
52
61
  isSearchProviderId,
@@ -68,7 +77,7 @@ import { ExtensionDashboard } from "../components/extensions";
68
77
  import { HistorySearchComponent } from "../components/history-search";
69
78
  import { LoginDialogComponent } from "../components/login-dialog";
70
79
  import { LogoutAccountSelectorComponent } from "../components/logout-account-selector";
71
- import { ModelHubComponent } from "../components/model-hub";
80
+ import { ModelHubComponent, type ModelRoleSelectionScope } from "../components/model-hub";
72
81
  import { ModelPickerComponent } from "../components/model-picker";
73
82
  import { OAuthSelectorComponent } from "../components/oauth-selector";
74
83
  import { PluginSelectorComponent } from "../components/plugin-selector";
@@ -83,10 +92,19 @@ import { UserMessageSelectorComponent } from "../components/user-message-selecto
83
92
  import type { SessionObserverRegistry } from "../session-observer-registry";
84
93
  import { buildCopyTargets } from "../utils/copy-targets";
85
94
 
86
- const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
95
+ const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL), then press Enter:";
87
96
 
88
97
  export class SelectorController {
89
98
  constructor(private ctx: InteractiveModeContext) {}
99
+ #defaultRoleMutationTail = Promise.resolve();
100
+
101
+ async #acquireDefaultRoleMutation(): Promise<() => void> {
102
+ const previous = this.#defaultRoleMutationTail;
103
+ const { promise, resolve } = Promise.withResolvers<void>();
104
+ this.#defaultRoleMutationTail = previous.then(() => promise);
105
+ await previous;
106
+ return resolve;
107
+ }
90
108
 
91
109
  async #refreshOAuthProviderAuthState(): Promise<void> {
92
110
  const oauthProviders = getOAuthProviders();
@@ -188,6 +206,7 @@ export class SelectorController {
188
206
  onPluginsChanged: async () => {
189
207
  const projectPath = await resolveActiveProjectRegistryPath(this.ctx.sessionManager.getCwd());
190
208
  clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
209
+ await this.ctx.refreshSkillState();
191
210
  await this.ctx.refreshSlashCommandState();
192
211
  resetCapabilities();
193
212
  this.ctx.ui.requestRender();
@@ -279,6 +298,13 @@ export class SelectorController {
279
298
  close: done,
280
299
  requestRender: () => this.ctx.ui.requestRender(),
281
300
  notify: message => this.ctx.showStatus(message),
301
+ getAdvisorStats: () => this.ctx.session.getAdvisorStats().advisors,
302
+ getUsageReports: async () => this.ctx.session.fetchUsageReports?.() ?? null,
303
+ resolveActiveAccount: (provider, sessionId) =>
304
+ this.ctx.session.modelRegistry.authStorage.getOAuthAccountIdentity(
305
+ provider,
306
+ sessionId ?? this.ctx.session.sessionId,
307
+ ),
282
308
  });
283
309
  overlayHandle = this.ctx.ui.showOverlay(overlay, {
284
310
  anchor: "bottom-center",
@@ -709,55 +735,173 @@ export class SelectorController {
709
735
  this.ctx.session.modelRegistry,
710
736
  this.ctx.session.scopedModels,
711
737
  {
712
- onAssign: async (model, role, thinkingLevel, selector) => {
738
+ onAssign: async (model, role, thinkingLevel, selector, scope?: ModelRoleSelectionScope) => {
739
+ const releaseDefaultMutation = role === "default" ? await this.#acquireDefaultRoleMutation() : undefined;
740
+ const configuredStorage = this.ctx.settings.get("modelRoleStorage");
741
+ const targetScope = configuredStorage === "project" ? (scope ?? "project") : "global";
713
742
  // `auto` is session-global: never baked into a per-role model value
714
743
  // (it can't round-trip through `model:<level>`). Apply it to the session
715
744
  // separately and persist via `defaultThinkingLevel`.
716
745
  const isAuto = thinkingLevel === AUTO_THINKING;
717
746
  const concreteThinking = isAuto || thinkingLevel === undefined ? undefined : thinkingLevel;
718
747
  const selectorValue = selector ?? `${model.provider}/${model.id}`;
748
+ const scopeLabel =
749
+ configuredStorage === "project" ? `${targetScope === "project" ? "Project" : "Global"} ` : "";
750
+ const defaultStatusLabel = configuredStorage === "project" ? `${scopeLabel}default` : "Default";
719
751
  try {
720
752
  if (role === "default") {
721
- const { switched } = await this.ctx.session.setModel(model, role, {
722
- selector,
723
- thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
724
- persist: true,
725
- currentContextTokens,
726
- });
727
- if (isAuto) {
728
- if (switched) {
729
- this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
730
- } else {
753
+ const effectiveProvenance = this.ctx.settings.getModelRoleProvenance("default");
754
+ const shadowedGlobal =
755
+ configuredStorage === "project" &&
756
+ targetScope === "global" &&
757
+ (effectiveProvenance === "project" ||
758
+ effectiveProvenance === "overlay" ||
759
+ (effectiveProvenance === "runtime" &&
760
+ this.ctx.settings.isProjectModelRoleRuntimeOverrideActive("default")));
761
+ const shadowedProject =
762
+ configuredStorage === "project" &&
763
+ targetScope === "project" &&
764
+ effectiveProvenance === "overlay";
765
+ if (shadowedGlobal) {
766
+ this.ctx.settings.setModelRole(
767
+ "default",
768
+ formatModelSelectorValue(selectorValue, concreteThinking),
769
+ );
770
+ if (isAuto) {
731
771
  this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
732
772
  }
733
- } else if (switched && concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
734
- this.ctx.session.setThinkingLevel(concreteThinking);
735
- }
736
- if (switched) {
773
+ } else if (shadowedProject) {
774
+ this.ctx.settings.setProjectModelRole(
775
+ "default",
776
+ formatModelSelectorValue(selectorValue, concreteThinking),
777
+ );
778
+ if (isAuto) {
779
+ this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
780
+ }
781
+ } else {
782
+ const { switched } = await this.ctx.session.setModel(model, role, {
783
+ selector,
784
+ thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
785
+ persist: targetScope === "global",
786
+ currentContextTokens,
787
+ });
788
+ if (!switched) return;
789
+ if (targetScope === "project") {
790
+ this.ctx.settings.setProjectModelRole(
791
+ "default",
792
+ formatModelSelectorValue(selectorValue, concreteThinking),
793
+ );
794
+ }
795
+ if (isAuto) {
796
+ this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
797
+ } else if (concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
798
+ this.ctx.session.setThinkingLevel(concreteThinking);
799
+ }
737
800
  this.ctx.statusLine.invalidate();
738
801
  this.ctx.updateEditorBorderColor();
739
802
  }
740
- this.ctx.showStatus(`Default model: ${selector ?? model.id}`);
803
+ this.ctx.showStatus(`${defaultStatusLabel} model: ${selector ?? model.id}`);
741
804
  } else {
742
805
  // Other roles (smol, slow, custom): update settings, not the current model.
743
- this.ctx.settings.setModelRole(role, formatModelSelectorValue(selectorValue, concreteThinking));
806
+ const modelRoleValue = formatModelSelectorValue(selectorValue, concreteThinking);
807
+ if (targetScope === "project") {
808
+ this.ctx.settings.setProjectModelRole(role, modelRoleValue);
809
+ } else {
810
+ this.ctx.settings.setModelRole(role, modelRoleValue);
811
+ }
744
812
  if (isAuto) {
745
813
  this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
746
814
  }
747
815
  const roleInfo = getRoleInfo(role, settings);
748
- this.ctx.showStatus(`${roleInfo?.name ?? role} model: ${selector ?? model.id}`);
816
+ this.ctx.showStatus(
817
+ `${scopeLabel}${roleInfo?.tag ?? roleInfo?.name ?? role} model: ${selector ?? model.id}`,
818
+ );
749
819
  }
750
820
  } catch (error) {
751
821
  this.ctx.showError(error instanceof Error ? error.message : String(error));
822
+ } finally {
823
+ releaseDefaultMutation?.();
824
+ hub?.refreshAfterExternalMutation();
752
825
  }
753
826
  },
754
- onUnassign: role => {
827
+ onUnassign: async (role, scope?: ModelRoleSelectionScope) => {
828
+ const releaseDefaultMutation = role === "default" ? await this.#acquireDefaultRoleMutation() : undefined;
829
+ const configuredStorage = this.ctx.settings.get("modelRoleStorage");
830
+ const targetScope = configuredStorage === "project" ? (scope ?? "project") : "global";
831
+ const scopeLabel =
832
+ configuredStorage === "project" ? `${targetScope === "project" ? "Project" : "Global"} ` : "";
755
833
  try {
756
- this.ctx.settings.setModelRole(role, undefined);
834
+ const previousEffectiveRoleValue =
835
+ role === "default" ? this.ctx.settings.getModelRole("default") : undefined;
836
+ if (targetScope === "project") {
837
+ this.ctx.settings.clearProjectModelRole(role);
838
+ } else {
839
+ this.ctx.settings.setModelRole(role, undefined);
840
+ }
757
841
  const roleInfo = getRoleInfo(role, settings);
758
- this.ctx.showStatus(`${roleInfo?.name ?? role} role cleared — auto-selection applies`);
842
+ this.ctx.showStatus(
843
+ `${scopeLabel}${roleInfo?.tag ?? roleInfo?.name ?? role} role cleared — auto-selection applies`,
844
+ );
845
+ // Clearing either persisted scope can also remove a captured
846
+ // runtime override. When that changes the effective default,
847
+ // resolve the newly exposed persisted layer and switch the live
848
+ // session without writing it back to global settings. Overlay
849
+ // and runtime provenance remain authoritative and session-neutral.
850
+ if (role === "default") {
851
+ const fallbackRoleValue = this.ctx.settings.getModelRole("default");
852
+ const fallbackProvenance = this.ctx.settings.getModelRoleProvenance("default");
853
+ const exposesPersistedFallback =
854
+ fallbackProvenance === "project" || fallbackProvenance === "global";
855
+ if (
856
+ fallbackRoleValue &&
857
+ fallbackRoleValue !== previousEffectiveRoleValue &&
858
+ exposesPersistedFallback
859
+ ) {
860
+ const scopedModels = this.ctx.session.scopedModels.map(sm => sm.model);
861
+ const availableModels =
862
+ scopedModels.length > 0 ? scopedModels : this.ctx.session.getAvailableModels();
863
+ const resolved = resolveModelRoleValue(fallbackRoleValue, availableModels, {
864
+ settings: this.ctx.settings,
865
+ });
866
+ if (resolved.model) {
867
+ const fallbackModel = resolved.model;
868
+ const isAuto = resolved.thinkingLevel === AUTO_THINKING;
869
+ let concreteThinking = concreteThinkingLevel(resolved.thinkingLevel);
870
+ let isAutoFromDefault = false;
871
+ if (!resolved.explicitThinkingLevel && !concreteThinking) {
872
+ const defaultLevel = parseConfiguredThinkingLevel(
873
+ this.ctx.settings.get("defaultThinkingLevel"),
874
+ );
875
+ if (defaultLevel === AUTO_THINKING) {
876
+ isAutoFromDefault = true;
877
+ } else if (defaultLevel) {
878
+ concreteThinking = defaultLevel;
879
+ }
880
+ }
881
+ const effectiveIsAuto = isAuto || isAutoFromDefault;
882
+ const { switched } = await this.ctx.session.setModel(fallbackModel, "default", {
883
+ persist: false,
884
+ thinkingLevel: effectiveIsAuto
885
+ ? ThinkingLevel.Inherit
886
+ : (concreteThinking ?? ThinkingLevel.Inherit),
887
+ currentContextTokens,
888
+ });
889
+ if (!switched) return;
890
+ if (effectiveIsAuto) {
891
+ this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
892
+ } else if (concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
893
+ this.ctx.session.setThinkingLevel(concreteThinking);
894
+ }
895
+ this.ctx.statusLine.invalidate();
896
+ this.ctx.updateEditorBorderColor();
897
+ }
898
+ }
899
+ }
759
900
  } catch (error) {
760
901
  this.ctx.showError(error instanceof Error ? error.message : String(error));
902
+ } finally {
903
+ releaseDefaultMutation?.();
904
+ hub?.refreshAfterExternalMutation();
761
905
  }
762
906
  },
763
907
  onFallbackChainChange: (role, chain) => {
@@ -772,8 +916,8 @@ export class SelectorController {
772
916
  const roleInfo = getRoleInfo(role, settings);
773
917
  this.ctx.showStatus(
774
918
  chain.length > 0
775
- ? `${roleInfo?.name ?? role} fallbacks: ${chain.join(" → ")}`
776
- : `${roleInfo?.name ?? role} fallbacks cleared`,
919
+ ? `${roleInfo?.tag ?? roleInfo?.name ?? role} fallbacks: ${chain.join(" → ")}`
920
+ : `${roleInfo?.tag ?? roleInfo?.name ?? role} fallbacks cleared`,
777
921
  );
778
922
  } catch (error) {
779
923
  this.ctx.showError(error instanceof Error ? error.message : String(error));
@@ -1109,12 +1253,10 @@ export class SelectorController {
1109
1253
  // every project's history when the cwd has nothing to resume. See #3099.
1110
1254
  const historyStorage = this.ctx.historyStorage;
1111
1255
  const historyMatcher = historyStorage ? (query: string) => historyStorage.matchingSessionIds(query) : undefined;
1112
- // Fullscreen session picker on the alternate screen (the /settings idiom):
1113
- // the overlay borrows the alt buffer and enables mouse tracking (wheel
1114
- // scroll + click-to-resume) for its lifetime, leaving the transcript
1115
- // untouched underneath. Anchored top-left at full size so a mouse row maps
1116
- // directly to a rendered line (the overlay paints from screen row 0), and
1117
- // `fillHeight` pads the body so the footer pins to the screen bottom.
1256
+ // Keep the fullscreen picker on the alternate buffer while a selected
1257
+ // session is loaded and its transcript is rebuilt. Closing it first exposes
1258
+ // the stale normal buffer for the entire async switch on terminals without
1259
+ // effective synchronized output.
1118
1260
  let overlayHandle: OverlayHandle | undefined;
1119
1261
  const done = () => {
1120
1262
  overlayHandle?.hide();
@@ -1124,8 +1266,18 @@ export class SelectorController {
1124
1266
  const selector = new SessionSelectorComponent(
1125
1267
  sessions,
1126
1268
  async (session: SessionInfo) => {
1127
- done();
1128
- await this.handleResumeSession(session.path);
1269
+ selector.lockInput();
1270
+ let keepOpen = false;
1271
+ try {
1272
+ const success = await this.handleResumeSession(session.path);
1273
+ if (!success) {
1274
+ keepOpen = true;
1275
+ selector.unlockInput();
1276
+ this.ctx.ui.requestRender();
1277
+ }
1278
+ } finally {
1279
+ if (!keepOpen) done();
1280
+ }
1129
1281
  },
1130
1282
  () => {
1131
1283
  done();
@@ -1202,13 +1354,23 @@ export class SelectorController {
1202
1354
  return true;
1203
1355
  }
1204
1356
 
1205
- async handleResumeSession(sessionPath: string): Promise<void> {
1206
- this.ctx.clearTransientSessionUi();
1207
-
1357
+ async handleResumeSession(sessionPath: string, options?: { settingsFlushed?: boolean }): Promise<boolean> {
1208
1358
  const previousCwd = this.ctx.sessionManager.getCwd();
1359
+ // Flush pending settings writes before switching sessions so a save
1360
+ // failure leaves the session, process project dir, and Settings in the
1361
+ // source scope — the switch below mutates the SessionManager cwd.
1362
+ if (!options?.settingsFlushed) {
1363
+ try {
1364
+ await this.ctx.settings.flush();
1365
+ } catch (err) {
1366
+ this.ctx.showError(`Failed to save pending settings: ${err instanceof Error ? err.message : String(err)}`);
1367
+ return false;
1368
+ }
1369
+ }
1209
1370
  // Switch session via AgentSession (emits hook and tool session events). The
1210
1371
  // SessionManager adopts the resumed session's own cwd when it differs.
1211
1372
  await this.ctx.session.switchSession(sessionPath);
1373
+ this.ctx.clearTransientSessionUi();
1212
1374
  const newCwd = this.ctx.sessionManager.getCwd();
1213
1375
  const movedProject = normalizePathForComparison(newCwd) !== normalizePathForComparison(previousCwd);
1214
1376
  if (movedProject) {
@@ -1223,6 +1385,7 @@ export class SelectorController {
1223
1385
  this.ctx.renderInitialMessages({ clearTerminalHistory: true });
1224
1386
  await this.ctx.reloadTodos();
1225
1387
  this.ctx.showStatus(movedProject ? `Resumed session in ${shortenPath(newCwd)}` : "Resumed session");
1388
+ return true;
1226
1389
  }
1227
1390
 
1228
1391
  async handleSessionDeleteCommand(): Promise<void> {
@@ -1272,7 +1435,6 @@ export class SelectorController {
1272
1435
  */
1273
1436
  async #handleOAuthLogin(providerId: string): Promise<boolean> {
1274
1437
  this.ctx.showStatus(`Logging in to ${providerId}…`);
1275
- const manualInput = this.ctx.oauthManualInput;
1276
1438
  const useManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(providerId);
1277
1439
  let restored = false;
1278
1440
  const restoreEditor = () => {
@@ -1300,16 +1462,19 @@ export class SelectorController {
1300
1462
  // The dialog renders the full URL (SSH-safe copy target) and
1301
1463
  // opens the browser best-effort.
1302
1464
  dialog.showAuth(info.url, info.instructions, info.launchUrl);
1303
- if (useManualInput) {
1304
- dialog.showProgress(MANUAL_LOGIN_TIP);
1305
- }
1306
1465
  },
1307
1466
  onPrompt: (prompt: { message: string; placeholder?: string }) =>
1308
1467
  dialog.showPrompt(prompt.message, prompt.placeholder),
1309
1468
  onProgress: (message: string) => {
1310
1469
  dialog.showProgress(message);
1311
1470
  },
1312
- onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
1471
+ // Paste-code providers (e.g. Codex) may need the user to paste the
1472
+ // fallback redirect URL when the loopback callback can't complete
1473
+ // (headless/remote/Windows). Mount a focused input in the dialog so
1474
+ // the paste lands somewhere the OAuth flow consumes — the hidden
1475
+ // editor's `/login <url>` path is unreachable while the dialog holds
1476
+ // focus (#5339).
1477
+ onManualCodeInput: useManualInput ? () => dialog.showManualInput(MANUAL_LOGIN_PROMPT) : undefined,
1313
1478
  });
1314
1479
  this.ctx.session.modelRegistry.refreshInBackground();
1315
1480
  const block = new TranscriptBlock();
@@ -1338,9 +1503,6 @@ export class SelectorController {
1338
1503
  this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
1339
1504
  return false;
1340
1505
  } finally {
1341
- if (useManualInput) {
1342
- manualInput.clear(`Manual OAuth input cleared for ${providerId}`);
1343
- }
1344
1506
  restoreEditor();
1345
1507
  }
1346
1508
  }