gsd-pi 2.8.2 → 2.9.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 (193) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +5 -0
  3. package/dist/loader.js +1 -1
  4. package/dist/update-check.d.ts +24 -0
  5. package/dist/update-check.js +93 -0
  6. package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.d.ts +4 -2
  7. package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  8. package/node_modules/@gsd/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  9. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
  10. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
  11. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js +758 -0
  12. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
  13. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
  14. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
  15. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js +267 -0
  16. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
  17. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
  18. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
  19. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js +101 -0
  20. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
  21. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
  22. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
  23. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
  24. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
  25. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
  26. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
  27. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js +709 -0
  28. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
  29. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
  30. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
  31. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
  32. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
  33. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
  34. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
  35. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
  36. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
  37. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
  38. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
  39. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js +64 -0
  40. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
  41. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
  42. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
  43. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js +574 -0
  44. package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
  45. package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  46. package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js +1 -0
  47. package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  48. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
  49. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  50. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js +4 -0
  51. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  52. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
  53. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  54. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
  55. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  56. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  57. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  58. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +80 -1
  59. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  60. package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  61. package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  62. package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +5 -0
  63. package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  64. package/node_modules/@gsd/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  65. package/node_modules/@gsd/pi-coding-agent/src/core/extensions/types.ts +4 -2
  66. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/client.ts +880 -0
  67. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/config.ts +325 -0
  68. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/defaults.json +456 -0
  69. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/edits.ts +109 -0
  70. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
  71. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/index.ts +943 -0
  72. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
  73. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp.md +33 -0
  74. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
  75. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/types.ts +421 -0
  76. package/node_modules/@gsd/pi-coding-agent/src/core/lsp/utils.ts +682 -0
  77. package/node_modules/@gsd/pi-coding-agent/src/core/slash-commands.ts +1 -0
  78. package/node_modules/@gsd/pi-coding-agent/src/core/tools/index.ts +10 -0
  79. package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
  80. package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +94 -2
  81. package/node_modules/@gsd/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
  82. package/node_modules/@gsd/pi-coding-agent/src/modes/rpc/rpc-types.ts +2 -1
  83. package/package.json +1 -1
  84. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +4 -2
  85. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
  88. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
  89. package/packages/pi-coding-agent/dist/core/lsp/client.js +758 -0
  90. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
  91. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
  92. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
  93. package/packages/pi-coding-agent/dist/core/lsp/config.js +267 -0
  94. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
  95. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
  96. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
  97. package/packages/pi-coding-agent/dist/core/lsp/edits.js +101 -0
  98. package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
  99. package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
  100. package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
  101. package/packages/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
  102. package/packages/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
  103. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
  104. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
  105. package/packages/pi-coding-agent/dist/core/lsp/index.js +709 -0
  106. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
  107. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
  108. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
  109. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
  110. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
  112. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
  114. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
  116. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
  117. package/packages/pi-coding-agent/dist/core/lsp/types.js +64 -0
  118. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
  121. package/packages/pi-coding-agent/dist/core/lsp/utils.js +574 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
  123. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  125. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
  127. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/index.js +4 -0
  129. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
  133. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +80 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
  139. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +5 -0
  141. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
  143. package/packages/pi-coding-agent/src/core/extensions/types.ts +4 -2
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +880 -0
  145. package/packages/pi-coding-agent/src/core/lsp/config.ts +325 -0
  146. package/packages/pi-coding-agent/src/core/lsp/defaults.json +456 -0
  147. package/packages/pi-coding-agent/src/core/lsp/edits.ts +109 -0
  148. package/packages/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
  149. package/packages/pi-coding-agent/src/core/lsp/index.ts +943 -0
  150. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
  151. package/packages/pi-coding-agent/src/core/lsp/lsp.md +33 -0
  152. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
  153. package/packages/pi-coding-agent/src/core/lsp/types.ts +421 -0
  154. package/packages/pi-coding-agent/src/core/lsp/utils.ts +682 -0
  155. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  156. package/packages/pi-coding-agent/src/core/tools/index.ts +10 -0
  157. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
  158. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +94 -2
  159. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
  160. package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +2 -1
  161. package/src/resources/extensions/ask-user-questions.ts +42 -2
  162. package/src/resources/extensions/bg-shell/index.ts +34 -37
  163. package/src/resources/extensions/browser-tools/core.d.ts +205 -0
  164. package/src/resources/extensions/browser-tools/index.ts +2 -2
  165. package/src/resources/extensions/browser-tools/refs.ts +1 -1
  166. package/src/resources/extensions/browser-tools/tools/session.ts +1 -1
  167. package/src/resources/extensions/context7/index.ts +2 -2
  168. package/src/resources/extensions/get-secrets-from-user.ts +3 -2
  169. package/src/resources/extensions/google-search/index.ts +1 -1
  170. package/src/resources/extensions/gsd/auto.ts +126 -12
  171. package/src/resources/extensions/gsd/commands.ts +218 -3
  172. package/src/resources/extensions/gsd/doctor.ts +1 -1
  173. package/src/resources/extensions/gsd/git-service.ts +163 -13
  174. package/src/resources/extensions/gsd/guided-flow.ts +19 -9
  175. package/src/resources/extensions/gsd/index.ts +17 -7
  176. package/src/resources/extensions/gsd/preferences.ts +1 -1
  177. package/src/resources/extensions/gsd/tests/git-service.test.ts +226 -0
  178. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +2 -2
  179. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +1 -1
  180. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +10 -10
  181. package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +87 -0
  182. package/src/resources/extensions/gsd/tests/worktree.test.ts +352 -0
  183. package/src/resources/extensions/gsd/types.ts +1 -0
  184. package/src/resources/extensions/gsd/worktree.ts +20 -1
  185. package/src/resources/extensions/mac-tools/index.ts +1 -1
  186. package/src/resources/extensions/search-the-web/command-search-provider.ts +1 -1
  187. package/src/resources/extensions/search-the-web/format.ts +1 -1
  188. package/src/resources/extensions/search-the-web/index.ts +5 -5
  189. package/src/resources/extensions/search-the-web/native-search.ts +5 -6
  190. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +7 -7
  191. package/src/resources/extensions/search-the-web/tool-llm-context.ts +11 -11
  192. package/src/resources/extensions/search-the-web/tool-search.ts +10 -10
  193. package/src/resources/extensions/shared/interview-ui.ts +2 -2
@@ -65,6 +65,13 @@ export {
65
65
  type WriteToolOptions,
66
66
  writeTool,
67
67
  } from "./write.js";
68
+ export {
69
+ createLspTool,
70
+ type LspToolDetails,
71
+ lspSchema,
72
+ lspTool,
73
+ } from "../lsp/index.js";
74
+ export type { LspServerStatus } from "../lsp/client.js";
68
75
 
69
76
  import type { AgentTool } from "@gsd/pi-agent-core";
70
77
  import { type BashToolOptions, bashTool, createBashTool } from "./bash.js";
@@ -74,6 +81,7 @@ import { createGrepTool, grepTool } from "./grep.js";
74
81
  import { createLsTool, lsTool } from "./ls.js";
75
82
  import { createReadTool, type ReadToolOptions, readTool } from "./read.js";
76
83
  import { createWriteTool, writeTool } from "./write.js";
84
+ import { createLspTool, lspTool } from "../lsp/index.js";
77
85
 
78
86
  /** Tool type (AgentTool from pi-ai) */
79
87
  export type Tool = AgentTool<any>;
@@ -93,6 +101,7 @@ export const allTools = {
93
101
  grep: grepTool,
94
102
  find: findTool,
95
103
  ls: lsTool,
104
+ lsp: lspTool,
96
105
  };
97
106
 
98
107
  export type ToolName = keyof typeof allTools;
@@ -135,5 +144,6 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
135
144
  grep: createGrepTool(cwd),
136
145
  find: createFindTool(cwd),
137
146
  ls: createLsTool(cwd),
147
+ lsp: createLspTool(cwd),
138
148
  };
139
149
  }
@@ -13,7 +13,7 @@ import {
13
13
  import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
14
14
  import { DynamicBorder } from "./dynamic-border.js";
15
15
 
16
- const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
16
+ export const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
17
17
  off: "No reasoning",
18
18
  minimal: "Very brief reasoning (~1k tokens)",
19
19
  low: "Light reasoning (~2k tokens)",
@@ -73,7 +73,7 @@ export interface SettingsCallbacks {
73
73
  /**
74
74
  * A submenu component for selecting from a list of options.
75
75
  */
76
- class SelectSubmenu extends Container {
76
+ export class SelectSubmenu extends Container {
77
77
  private selectList: SelectList;
78
78
 
79
79
  constructor(
@@ -7,7 +7,7 @@ import * as crypto from "node:crypto";
7
7
  import * as fs from "node:fs";
8
8
  import * as os from "node:os";
9
9
  import * as path from "node:path";
10
- import type { AgentMessage } from "@gsd/pi-agent-core";
10
+ import type { AgentMessage, ThinkingLevel } from "@gsd/pi-agent-core";
11
11
  import type { AssistantMessage, ImageContent, Message, Model, OAuthProviderId } from "@gsd/pi-ai";
12
12
  import type {
13
13
  AutocompleteItem,
@@ -85,7 +85,7 @@ import { ModelSelectorComponent } from "./components/model-selector.js";
85
85
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
86
86
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
87
87
  import { SessionSelectorComponent } from "./components/session-selector.js";
88
- import { SettingsSelectorComponent } from "./components/settings-selector.js";
88
+ import { SelectSubmenu, SettingsSelectorComponent, THINKING_DESCRIPTIONS } from "./components/settings-selector.js";
89
89
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
90
90
  import { ToolExecutionComponent } from "./components/tool-execution.js";
91
91
  import { TreeSelectorComponent } from "./components/tree-selector.js";
@@ -2015,6 +2015,12 @@ export class InteractiveMode {
2015
2015
  await this.handleReloadCommand();
2016
2016
  return;
2017
2017
  }
2018
+ if (text === "/thinking" || text.startsWith("/thinking ")) {
2019
+ const arg = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined;
2020
+ this.editor.setText("");
2021
+ this.handleThinkingCommand(arg);
2022
+ return;
2023
+ }
2018
2024
  if (text === "/debug") {
2019
2025
  this.handleDebugCommand();
2020
2026
  this.editor.setText("");
@@ -2745,6 +2751,57 @@ export class InteractiveMode {
2745
2751
  }
2746
2752
  }
2747
2753
 
2754
+ private handleThinkingCommand(arg?: string): void {
2755
+ if (!this.session.supportsThinking()) {
2756
+ this.showStatus("Current model does not support thinking");
2757
+ return;
2758
+ }
2759
+
2760
+ const availableLevels = this.session.getAvailableThinkingLevels();
2761
+
2762
+ if (arg) {
2763
+ const level = arg.toLowerCase();
2764
+ if (!availableLevels.includes(level as ThinkingLevel)) {
2765
+ this.showStatus(`Invalid thinking level "${arg}". Available: ${availableLevels.join(", ")}`);
2766
+ return;
2767
+ }
2768
+ this.session.setThinkingLevel(level as ThinkingLevel);
2769
+ this.footer.invalidate();
2770
+ this.updateEditorBorderColor();
2771
+ this.showStatus(`Thinking level: ${level}`);
2772
+ return;
2773
+ }
2774
+
2775
+ this.showThinkingSelector();
2776
+ }
2777
+
2778
+ private showThinkingSelector(): void {
2779
+ const availableLevels = this.session.getAvailableThinkingLevels();
2780
+ this.showSelector((done) => {
2781
+ const selector = new SelectSubmenu(
2782
+ "Thinking Level",
2783
+ "Select reasoning depth for thinking-capable models",
2784
+ availableLevels.map((level) => ({
2785
+ value: level,
2786
+ label: level,
2787
+ description: THINKING_DESCRIPTIONS[level],
2788
+ })),
2789
+ this.session.thinkingLevel,
2790
+ (value) => {
2791
+ this.session.setThinkingLevel(value as ThinkingLevel);
2792
+ this.footer.invalidate();
2793
+ this.updateEditorBorderColor();
2794
+ done();
2795
+ this.showStatus(`Thinking level: ${value}`);
2796
+ },
2797
+ () => {
2798
+ done();
2799
+ },
2800
+ );
2801
+ return { component: selector, focus: selector };
2802
+ });
2803
+ }
2804
+
2748
2805
  private async cycleModel(direction: "forward" | "backward"): Promise<void> {
2749
2806
  try {
2750
2807
  const result = await this.session.cycleModel(direction);
@@ -3695,6 +3752,21 @@ export class InteractiveMode {
3695
3752
  this.session.modelRegistry.authStorage.logout(providerId);
3696
3753
  this.session.modelRegistry.refresh();
3697
3754
  await this.updateAvailableProviderCount();
3755
+
3756
+ // Auto-switch model if current model belongs to the logged-out provider
3757
+ const currentModel = this.session.model;
3758
+ if (currentModel?.provider === providerId) {
3759
+ try {
3760
+ const available = this.session.modelRegistry.getAvailable();
3761
+ const fallback = available.find((m) => m.provider !== providerId);
3762
+ if (fallback) {
3763
+ await this.session.setModel(fallback);
3764
+ }
3765
+ } catch {
3766
+ // Model switch failed — user can manually switch via /model
3767
+ }
3768
+ }
3769
+
3698
3770
  this.showStatus(`Logged out of ${providerName}`);
3699
3771
  } catch (error: unknown) {
3700
3772
  this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -3789,6 +3861,26 @@ export class InteractiveMode {
3789
3861
  restoreEditor();
3790
3862
  this.session.modelRegistry.refresh();
3791
3863
  await this.updateAvailableProviderCount();
3864
+
3865
+ // Auto-switch model if current model has no valid API key
3866
+ try {
3867
+ const currentModel = this.session.model;
3868
+ if (currentModel) {
3869
+ const currentKey = await this.session.modelRegistry.getApiKey(currentModel);
3870
+ if (!currentKey) {
3871
+ const available = this.session.modelRegistry.getAvailable();
3872
+ const newProviderModel = available.find((m) => m.provider === providerId);
3873
+ if (newProviderModel) {
3874
+ await this.session.setModel(newProviderModel);
3875
+ } else if (available.length > 0) {
3876
+ await this.session.setModel(available[0]);
3877
+ }
3878
+ }
3879
+ }
3880
+ } catch (error: unknown) {
3881
+ // Model switch failed — user can manually switch via /model
3882
+ }
3883
+
3792
3884
  this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
3793
3885
  } catch (error: unknown) {
3794
3886
  restoreEditor();
@@ -119,8 +119,8 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
119
119
  */
120
120
  const createExtensionUIContext = (): ExtensionUIContext => ({
121
121
  select: (title, options, opts) =>
122
- createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout }, (r) =>
123
- "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined,
122
+ createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout, allowMultiple: opts?.allowMultiple }, (r) =>
123
+ "cancelled" in r && r.cancelled ? undefined : "values" in r ? r.values : "value" in r ? r.value : undefined,
124
124
  ),
125
125
 
126
126
  confirm: (title, message, opts) =>
@@ -210,7 +210,7 @@ export type RpcResponse =
210
210
 
211
211
  /** Emitted when an extension needs user input */
212
212
  export type RpcExtensionUIRequest =
213
- | { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; timeout?: number }
213
+ | { type: "extension_ui_request"; id: string; method: "select"; title: string; options: string[]; timeout?: number; allowMultiple?: boolean }
214
214
  | { type: "extension_ui_request"; id: string; method: "confirm"; title: string; message: string; timeout?: number }
215
215
  | {
216
216
  type: "extension_ui_request";
@@ -253,6 +253,7 @@ export type RpcExtensionUIRequest =
253
253
  /** Response to an extension UI request */
254
254
  export type RpcExtensionUIResponse =
255
255
  | { type: "extension_ui_response"; id: string; value: string }
256
+ | { type: "extension_ui_response"; id: string; values: string[] }
256
257
  | { type: "extension_ui_response"; id: string; confirmed: boolean }
257
258
  | { type: "extension_ui_response"; id: string; cancelled: true };
258
259
 
@@ -137,12 +137,52 @@ export default function AskUserQuestions(pi: ExtensionAPI) {
137
137
  if (!ctx.hasUI) {
138
138
  const { tryRemoteQuestions } = await import("./remote-questions/manager.js");
139
139
  const remoteResult = await tryRemoteQuestions(params.questions, signal);
140
- if (remoteResult) return remoteResult;
140
+ if (remoteResult) return { ...remoteResult, details: remoteResult.details as unknown };
141
141
  return errorResult("Error: UI not available (non-interactive mode)", params.questions);
142
142
  }
143
143
 
144
144
  // Delegate to shared interview UI
145
- const result = await showInterviewRound(params.questions, {}, ctx);
145
+ const result = await showInterviewRound(params.questions, {}, ctx as any);
146
+
147
+ // RPC mode fallback: custom() returns undefined, so showInterviewRound
148
+ // may return undefined. Fall back to sequential ctx.ui.select() calls.
149
+ if (!result) {
150
+ const answers: Record<string, { answers: string[] }> = {};
151
+ for (const q of params.questions) {
152
+ const options = q.options.map((o) => o.label);
153
+ if (!q.allowMultiple) {
154
+ options.push(OTHER_OPTION_LABEL);
155
+ }
156
+ const selected = await ctx.ui.select(
157
+ `${q.header}: ${q.question}`,
158
+ options,
159
+ { signal, ...(q.allowMultiple ? { allowMultiple: true } : {}) },
160
+ );
161
+ if (selected === undefined) {
162
+ return errorResult("ask_user_questions was cancelled", params.questions);
163
+ }
164
+ answers[q.id] = {
165
+ answers: Array.isArray(selected) ? selected : [selected],
166
+ };
167
+ }
168
+ const roundResult: RoundResult = {
169
+ endInterview: false,
170
+ answers: Object.fromEntries(
171
+ Object.entries(answers).map(([id, a]) => [
172
+ id,
173
+ { selected: a.answers.length === 1 ? a.answers[0] : a.answers, notes: "" },
174
+ ]),
175
+ ),
176
+ };
177
+ return {
178
+ content: [{ type: "text" as const, text: JSON.stringify({ answers }) }],
179
+ details: {
180
+ questions: params.questions,
181
+ response: roundResult,
182
+ cancelled: false,
183
+ } satisfies LocalResultDetails,
184
+ };
185
+ }
146
186
 
147
187
  // Check if cancelled (empty answers = user exited)
148
188
  const hasAnswers = Object.keys(result.answers).length > 0;
@@ -1261,7 +1261,7 @@ export default function (pi: ExtensionAPI) {
1261
1261
  if (!params.command) {
1262
1262
  return {
1263
1263
  content: [{ type: "text" as const, text: "Error: 'command' is required for start" }],
1264
- isError: true,
1264
+ isError: true, details: undefined as unknown,
1265
1265
  };
1266
1266
  }
1267
1267
 
@@ -1316,7 +1316,7 @@ export default function (pi: ExtensionAPI) {
1316
1316
  if (!bg) {
1317
1317
  return {
1318
1318
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1319
- isError: true,
1319
+ isError: true, details: undefined as unknown,
1320
1320
  };
1321
1321
  }
1322
1322
  const digest = generateDigest(bg, true);
@@ -1356,7 +1356,7 @@ export default function (pi: ExtensionAPI) {
1356
1356
  if (!params.id) {
1357
1357
  return {
1358
1358
  content: [{ type: "text" as const, text: "Error: 'id' is required for highlights" }],
1359
- isError: true,
1359
+ isError: true, details: undefined as unknown,
1360
1360
  };
1361
1361
  }
1362
1362
 
@@ -1364,7 +1364,7 @@ export default function (pi: ExtensionAPI) {
1364
1364
  if (!bg) {
1365
1365
  return {
1366
1366
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1367
- isError: true,
1367
+ isError: true, details: undefined as unknown,
1368
1368
  };
1369
1369
  }
1370
1370
 
@@ -1388,7 +1388,7 @@ export default function (pi: ExtensionAPI) {
1388
1388
  if (!params.id) {
1389
1389
  return {
1390
1390
  content: [{ type: "text" as const, text: "Error: 'id' is required for output" }],
1391
- isError: true,
1391
+ isError: true, details: undefined as unknown,
1392
1392
  };
1393
1393
  }
1394
1394
 
@@ -1396,7 +1396,7 @@ export default function (pi: ExtensionAPI) {
1396
1396
  if (!bg) {
1397
1397
  return {
1398
1398
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1399
- isError: true,
1399
+ isError: true, details: undefined as unknown,
1400
1400
  };
1401
1401
  }
1402
1402
 
@@ -1429,7 +1429,7 @@ export default function (pi: ExtensionAPI) {
1429
1429
  if (!params.id) {
1430
1430
  return {
1431
1431
  content: [{ type: "text" as const, text: "Error: 'id' is required for wait_for_ready" }],
1432
- isError: true,
1432
+ isError: true, details: undefined as unknown,
1433
1433
  };
1434
1434
  }
1435
1435
 
@@ -1437,7 +1437,7 @@ export default function (pi: ExtensionAPI) {
1437
1437
  if (!bg) {
1438
1438
  return {
1439
1439
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1440
- isError: true,
1440
+ isError: true, details: undefined as unknown,
1441
1441
  };
1442
1442
  }
1443
1443
 
@@ -1472,13 +1472,13 @@ export default function (pi: ExtensionAPI) {
1472
1472
  if (!params.id) {
1473
1473
  return {
1474
1474
  content: [{ type: "text" as const, text: "Error: 'id' is required for send" }],
1475
- isError: true,
1475
+ isError: true, details: undefined as unknown,
1476
1476
  };
1477
1477
  }
1478
1478
  if (params.input === undefined) {
1479
1479
  return {
1480
1480
  content: [{ type: "text" as const, text: "Error: 'input' is required for send" }],
1481
- isError: true,
1481
+ isError: true, details: undefined as unknown,
1482
1482
  };
1483
1483
  }
1484
1484
 
@@ -1486,14 +1486,14 @@ export default function (pi: ExtensionAPI) {
1486
1486
  if (!bg) {
1487
1487
  return {
1488
1488
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1489
- isError: true,
1489
+ isError: true, details: undefined as unknown,
1490
1490
  };
1491
1491
  }
1492
1492
 
1493
1493
  if (!bg.alive) {
1494
1494
  return {
1495
1495
  content: [{ type: "text" as const, text: `Error: Process ${params.id} has already exited` }],
1496
- isError: true,
1496
+ isError: true, details: undefined as unknown,
1497
1497
  };
1498
1498
  }
1499
1499
 
@@ -1506,7 +1506,7 @@ export default function (pi: ExtensionAPI) {
1506
1506
  } catch (err) {
1507
1507
  return {
1508
1508
  content: [{ type: "text" as const, text: `Error writing to stdin: ${err instanceof Error ? err.message : String(err)}` }],
1509
- isError: true,
1509
+ isError: true, details: undefined as unknown,
1510
1510
  };
1511
1511
  }
1512
1512
  }
@@ -1516,19 +1516,19 @@ export default function (pi: ExtensionAPI) {
1516
1516
  if (!params.id) {
1517
1517
  return {
1518
1518
  content: [{ type: "text" as const, text: "Error: 'id' is required for send_and_wait" }],
1519
- isError: true,
1519
+ isError: true, details: undefined as unknown,
1520
1520
  };
1521
1521
  }
1522
1522
  if (params.input === undefined) {
1523
1523
  return {
1524
1524
  content: [{ type: "text" as const, text: "Error: 'input' is required for send_and_wait" }],
1525
- isError: true,
1525
+ isError: true, details: undefined as unknown,
1526
1526
  };
1527
1527
  }
1528
1528
  if (!params.wait_pattern) {
1529
1529
  return {
1530
1530
  content: [{ type: "text" as const, text: "Error: 'wait_pattern' is required for send_and_wait" }],
1531
- isError: true,
1531
+ isError: true, details: undefined as unknown,
1532
1532
  };
1533
1533
  }
1534
1534
 
@@ -1536,14 +1536,14 @@ export default function (pi: ExtensionAPI) {
1536
1536
  if (!bg) {
1537
1537
  return {
1538
1538
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1539
- isError: true,
1539
+ isError: true, details: undefined as unknown,
1540
1540
  };
1541
1541
  }
1542
1542
 
1543
1543
  if (!bg.alive) {
1544
1544
  return {
1545
1545
  content: [{ type: "text" as const, text: `Error: Process ${params.id} has already exited` }],
1546
- isError: true,
1546
+ isError: true, details: undefined as unknown,
1547
1547
  };
1548
1548
  }
1549
1549
 
@@ -1568,7 +1568,7 @@ export default function (pi: ExtensionAPI) {
1568
1568
  if (!params.id) {
1569
1569
  return {
1570
1570
  content: [{ type: "text" as const, text: "Error: 'id' is required for signal" }],
1571
- isError: true,
1571
+ isError: true, details: undefined as unknown,
1572
1572
  };
1573
1573
  }
1574
1574
 
@@ -1576,7 +1576,7 @@ export default function (pi: ExtensionAPI) {
1576
1576
  if (!bg) {
1577
1577
  return {
1578
1578
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1579
- isError: true,
1579
+ isError: true, details: undefined as unknown,
1580
1580
  };
1581
1581
  }
1582
1582
 
@@ -1621,7 +1621,7 @@ export default function (pi: ExtensionAPI) {
1621
1621
  if (!params.id) {
1622
1622
  return {
1623
1623
  content: [{ type: "text" as const, text: "Error: 'id' is required for kill" }],
1624
- isError: true,
1624
+ isError: true, details: undefined as unknown,
1625
1625
  };
1626
1626
  }
1627
1627
 
@@ -1629,7 +1629,7 @@ export default function (pi: ExtensionAPI) {
1629
1629
  if (!bg) {
1630
1630
  return {
1631
1631
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1632
- isError: true,
1632
+ isError: true, details: undefined as unknown,
1633
1633
  };
1634
1634
  }
1635
1635
 
@@ -1657,7 +1657,7 @@ export default function (pi: ExtensionAPI) {
1657
1657
  if (!params.id) {
1658
1658
  return {
1659
1659
  content: [{ type: "text" as const, text: "Error: 'id' is required for restart" }],
1660
- isError: true,
1660
+ isError: true, details: undefined as unknown,
1661
1661
  };
1662
1662
  }
1663
1663
 
@@ -1665,7 +1665,7 @@ export default function (pi: ExtensionAPI) {
1665
1665
  if (!newBg) {
1666
1666
  return {
1667
1667
  content: [{ type: "text" as const, text: `Error: No process found with id '${params.id}'` }],
1668
- isError: true,
1668
+ isError: true, details: undefined as unknown,
1669
1669
  };
1670
1670
  }
1671
1671
 
@@ -1732,7 +1732,7 @@ export default function (pi: ExtensionAPI) {
1732
1732
  default:
1733
1733
  return {
1734
1734
  content: [{ type: "text" as const, text: `Unknown action: ${params.action}` }],
1735
- isError: true,
1735
+ isError: true, details: undefined as unknown,
1736
1736
  };
1737
1737
  }
1738
1738
  },
@@ -1760,7 +1760,7 @@ export default function (pi: ExtensionAPI) {
1760
1760
 
1761
1761
  const action = details.action as string;
1762
1762
 
1763
- if (result.isError) {
1763
+ if ((result as any).isError) {
1764
1764
  const text = result.content[0];
1765
1765
  return new Text(
1766
1766
  theme.fg("error", text?.type === "text" ? text.text : "Error"),
@@ -2338,17 +2338,14 @@ export default function (pi: ExtensionAPI) {
2338
2338
  }, 2000);
2339
2339
 
2340
2340
  // Refresh widget after agent actions and session events
2341
- for (const event of [
2342
- "turn_end",
2343
- "agent_end",
2344
- "session_start",
2345
- "session_switch",
2346
- ] as const) {
2347
- pi.on(event, async (_event: unknown, ctx: ExtensionContext) => {
2348
- latestCtx = ctx;
2349
- refreshWidget();
2350
- });
2351
- }
2341
+ const refreshHandler = async (_event: unknown, ctx: ExtensionContext) => {
2342
+ latestCtx = ctx;
2343
+ refreshWidget();
2344
+ };
2345
+ pi.on("turn_end", refreshHandler as any);
2346
+ pi.on("agent_end", refreshHandler as any);
2347
+ pi.on("session_start", refreshHandler as any);
2348
+ pi.on("session_switch", refreshHandler as any);
2352
2349
 
2353
2350
  pi.on("tool_execution_end", async (_event, ctx) => {
2354
2351
  latestCtx = ctx;