@astralyn/pi 0.84.4 → 0.84.5

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 (234) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +11 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/auth-command.d.ts.map +1 -1
  7. package/dist/cli/auth-command.js +5 -4
  8. package/dist/cli/auth-command.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +6 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +7 -6
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/experimental.d.ts +4 -0
  14. package/dist/core/experimental.d.ts.map +1 -1
  15. package/dist/core/experimental.js +4 -0
  16. package/dist/core/experimental.js.map +1 -1
  17. package/dist/core/extensions/runner.d.ts.map +1 -1
  18. package/dist/core/extensions/runner.js +4 -0
  19. package/dist/core/extensions/runner.js.map +1 -1
  20. package/dist/core/extensions/types.d.ts +8 -1
  21. package/dist/core/extensions/types.d.ts.map +1 -1
  22. package/dist/core/extensions/types.js.map +1 -1
  23. package/dist/core/keybindings.d.ts +29 -0
  24. package/dist/core/keybindings.d.ts.map +1 -1
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/keybindings.js.map +1 -1
  27. package/dist/core/model-config.d.ts +5 -0
  28. package/dist/core/model-config.d.ts.map +1 -1
  29. package/dist/core/model-config.js +1 -0
  30. package/dist/core/model-config.js.map +1 -1
  31. package/dist/core/model-registry.d.ts +2 -0
  32. package/dist/core/model-registry.d.ts.map +1 -1
  33. package/dist/core/model-registry.js +4 -0
  34. package/dist/core/model-registry.js.map +1 -1
  35. package/dist/core/project-trust.js +2 -2
  36. package/dist/core/project-trust.js.map +1 -1
  37. package/dist/core/sdk.d.ts +5 -3
  38. package/dist/core/sdk.d.ts.map +1 -1
  39. package/dist/core/sdk.js +2 -1
  40. package/dist/core/sdk.js.map +1 -1
  41. package/dist/core/session-manager.js +2 -2
  42. package/dist/core/session-manager.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +6 -0
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +12 -0
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/system-prompt.js +1 -1
  48. package/dist/core/system-prompt.js.map +1 -1
  49. package/dist/core/tools/bash.d.ts +2 -1
  50. package/dist/core/tools/bash.d.ts.map +1 -1
  51. package/dist/core/tools/bash.js +3 -1
  52. package/dist/core/tools/bash.js.map +1 -1
  53. package/dist/core/tools/edit.d.ts.map +1 -1
  54. package/dist/core/tools/edit.js +2 -0
  55. package/dist/core/tools/edit.js.map +1 -1
  56. package/dist/core/tools/find.js +1 -1
  57. package/dist/core/tools/find.js.map +1 -1
  58. package/dist/core/tools/grep.js +1 -1
  59. package/dist/core/tools/grep.js.map +1 -1
  60. package/dist/core/tools/read.d.ts.map +1 -1
  61. package/dist/core/tools/read.js +2 -0
  62. package/dist/core/tools/read.js.map +1 -1
  63. package/dist/core/tools/write.d.ts.map +1 -1
  64. package/dist/core/tools/write.js +2 -0
  65. package/dist/core/tools/write.js.map +1 -1
  66. package/dist/extensions/background/index.d.ts +74 -0
  67. package/dist/extensions/background/index.d.ts.map +1 -0
  68. package/dist/extensions/background/index.js +332 -0
  69. package/dist/extensions/background/index.js.map +1 -0
  70. package/dist/extensions/background/manager.d.ts +81 -0
  71. package/dist/extensions/background/manager.d.ts.map +1 -0
  72. package/dist/extensions/background/manager.js +320 -0
  73. package/dist/extensions/background/manager.js.map +1 -0
  74. package/dist/extensions/background/registry.d.ts +138 -0
  75. package/dist/extensions/background/registry.d.ts.map +1 -0
  76. package/dist/extensions/background/registry.js +358 -0
  77. package/dist/extensions/background/registry.js.map +1 -0
  78. package/dist/extensions/background/render.d.ts +26 -0
  79. package/dist/extensions/background/render.d.ts.map +1 -0
  80. package/dist/extensions/background/render.js +118 -0
  81. package/dist/extensions/background/render.js.map +1 -0
  82. package/dist/extensions/index.d.ts.map +1 -1
  83. package/dist/extensions/index.js +2 -0
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/subagent/activity.d.ts +0 -1
  86. package/dist/extensions/subagent/activity.d.ts.map +1 -1
  87. package/dist/extensions/subagent/activity.js +2 -5
  88. package/dist/extensions/subagent/activity.js.map +1 -1
  89. package/dist/extensions/subagent/agents-command.d.ts.map +1 -1
  90. package/dist/extensions/subagent/agents-command.js +308 -153
  91. package/dist/extensions/subagent/agents-command.js.map +1 -1
  92. package/dist/extensions/subagent/agents.d.ts +3 -6
  93. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  94. package/dist/extensions/subagent/agents.js +22 -192
  95. package/dist/extensions/subagent/agents.js.map +1 -1
  96. package/dist/extensions/subagent/constants.d.ts +4 -5
  97. package/dist/extensions/subagent/constants.d.ts.map +1 -1
  98. package/dist/extensions/subagent/constants.js +11 -7
  99. package/dist/extensions/subagent/constants.js.map +1 -1
  100. package/dist/extensions/subagent/index.d.ts +0 -3
  101. package/dist/extensions/subagent/index.d.ts.map +1 -1
  102. package/dist/extensions/subagent/index.js +56 -188
  103. package/dist/extensions/subagent/index.js.map +1 -1
  104. package/dist/extensions/subagent/render.d.ts +1 -1
  105. package/dist/extensions/subagent/render.d.ts.map +1 -1
  106. package/dist/extensions/subagent/render.js +208 -576
  107. package/dist/extensions/subagent/render.js.map +1 -1
  108. package/dist/extensions/subagent/resolve.d.ts +2 -2
  109. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  110. package/dist/extensions/subagent/resolve.js +23 -15
  111. package/dist/extensions/subagent/resolve.js.map +1 -1
  112. package/dist/extensions/subagent/runner.d.ts +1 -2
  113. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  114. package/dist/extensions/subagent/runner.js +109 -131
  115. package/dist/extensions/subagent/runner.js.map +1 -1
  116. package/dist/extensions/subagent/schema.d.ts +4 -10
  117. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  118. package/dist/extensions/subagent/schema.js +8 -13
  119. package/dist/extensions/subagent/schema.js.map +1 -1
  120. package/dist/extensions/subagent/sdk-runner.d.ts.map +1 -1
  121. package/dist/extensions/subagent/sdk-runner.js +9 -20
  122. package/dist/extensions/subagent/sdk-runner.js.map +1 -1
  123. package/dist/extensions/subagent/settings.d.ts +2 -2
  124. package/dist/extensions/subagent/settings.d.ts.map +1 -1
  125. package/dist/extensions/subagent/settings.js +27 -14
  126. package/dist/extensions/subagent/settings.js.map +1 -1
  127. package/dist/extensions/subagent/text.d.ts +3 -0
  128. package/dist/extensions/subagent/text.d.ts.map +1 -0
  129. package/dist/extensions/subagent/text.js +16 -0
  130. package/dist/extensions/subagent/text.js.map +1 -0
  131. package/dist/extensions/subagent/types.d.ts +13 -35
  132. package/dist/extensions/subagent/types.d.ts.map +1 -1
  133. package/dist/extensions/subagent/types.js.map +1 -1
  134. package/dist/extensions/subagent/ui/model-list.d.ts +4 -3
  135. package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -1
  136. package/dist/extensions/subagent/ui/model-list.js +10 -4
  137. package/dist/extensions/subagent/ui/model-list.js.map +1 -1
  138. package/dist/index.d.ts +1 -1
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js.map +1 -1
  141. package/dist/main.d.ts.map +1 -1
  142. package/dist/main.js +5 -1
  143. package/dist/main.js.map +1 -1
  144. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  145. package/dist/modes/interactive/components/model-selector.js +2 -1
  146. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  147. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  148. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/settings-selector.js +10 -0
  150. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  151. package/dist/modes/interactive/interactive-mode.d.ts +8 -2
  152. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  153. package/dist/modes/interactive/interactive-mode.js +77 -25
  154. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  155. package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
  156. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  157. package/dist/modes/interactive/model-catalog-refresh.js +36 -0
  158. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  159. package/dist/modes/interactive/theme/dark.json +2 -0
  160. package/dist/modes/interactive/theme/light.json +2 -0
  161. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  162. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  163. package/dist/modes/interactive/theme/theme-controller.js +23 -10
  164. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  165. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  166. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  167. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  168. package/dist/modes/interactive/theme/theme.js +12 -2
  169. package/dist/modes/interactive/theme/theme.js.map +1 -1
  170. package/dist/modes/json-event.d.ts +4 -1
  171. package/dist/modes/json-event.d.ts.map +1 -1
  172. package/dist/modes/json-event.js +5 -2
  173. package/dist/modes/json-event.js.map +1 -1
  174. package/dist/package-manager-cli.js +1 -1
  175. package/dist/package-manager-cli.js.map +1 -1
  176. package/dist/server/create-harness.d.ts.map +1 -1
  177. package/dist/server/create-harness.js +2 -0
  178. package/dist/server/create-harness.js.map +1 -1
  179. package/dist/utils/tools-manager.d.ts +10 -1
  180. package/dist/utils/tools-manager.d.ts.map +1 -1
  181. package/dist/utils/tools-manager.js +14 -19
  182. package/dist/utils/tools-manager.js.map +1 -1
  183. package/docs/bundled/README.md +1 -0
  184. package/docs/bundled/extensions/background.md +64 -0
  185. package/docs/bundled/extensions/subagent.md +83 -71
  186. package/docs/compaction.md +6 -6
  187. package/docs/custom-provider.md +1 -1
  188. package/docs/docs.json +4 -0
  189. package/docs/environment-variables.md +8 -2
  190. package/docs/extensions.md +6 -2
  191. package/docs/json.md +6 -4
  192. package/docs/keybindings.md +9 -2
  193. package/docs/rpc.md +15 -4
  194. package/docs/sdk.md +9 -0
  195. package/docs/settings.md +31 -0
  196. package/docs/terminal-setup.md +23 -2
  197. package/docs/themes.md +24 -3
  198. package/docs/tui.md +2 -2
  199. package/docs/usage.md +4 -3
  200. package/examples/README.md +1 -1
  201. package/examples/extensions/README.md +0 -1
  202. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  203. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  204. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  205. package/examples/extensions/gondolin/package-lock.json +2 -2
  206. package/examples/extensions/gondolin/package.json +1 -1
  207. package/examples/extensions/sandbox/package-lock.json +2 -2
  208. package/examples/extensions/sandbox/package.json +1 -1
  209. package/examples/extensions/with-deps/package-lock.json +2 -2
  210. package/examples/extensions/with-deps/package.json +1 -1
  211. package/npm-shrinkwrap.json +170 -221
  212. package/package.json +6 -6
  213. package/dist/extensions/subagent/display-name.d.ts +0 -6
  214. package/dist/extensions/subagent/display-name.d.ts.map +0 -1
  215. package/dist/extensions/subagent/display-name.js +0 -13
  216. package/dist/extensions/subagent/display-name.js.map +0 -1
  217. package/dist/extensions/subagent/ui/profile-editor.d.ts +0 -54
  218. package/dist/extensions/subagent/ui/profile-editor.d.ts.map +0 -1
  219. package/dist/extensions/subagent/ui/profile-editor.js +0 -154
  220. package/dist/extensions/subagent/ui/profile-editor.js.map +0 -1
  221. package/dist/extensions/subagent/ui/profile-picker.d.ts +0 -32
  222. package/dist/extensions/subagent/ui/profile-picker.d.ts.map +0 -1
  223. package/dist/extensions/subagent/ui/profile-picker.js +0 -149
  224. package/dist/extensions/subagent/ui/profile-picker.js.map +0 -1
  225. package/examples/extensions/subagent/README.md +0 -175
  226. package/examples/extensions/subagent/agents/planner.md +0 -37
  227. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  228. package/examples/extensions/subagent/agents/scout.md +0 -50
  229. package/examples/extensions/subagent/agents/worker.md +0 -24
  230. package/examples/extensions/subagent/agents.ts +0 -126
  231. package/examples/extensions/subagent/index.ts +0 -1009
  232. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  233. package/examples/extensions/subagent/prompts/implement.md +0 -10
  234. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
@@ -1,22 +1,20 @@
1
1
  import { clampThinkingLevel, getSupportedThinkingLevels } from "@earendil-works/pi-ai/compat";
2
+ import { Container, Spacer, Text, TruncatedText, } from "@earendil-works/pi-tui";
2
3
  import { getAgentDir } from "../../config.js";
3
- import { discoverAgents } from "./agents.js";
4
+ import { DynamicBorder } from "../../modes/interactive/components/dynamic-border.js";
5
+ import { keyLabel } from "../../modes/interactive/components/keybinding-hints.js";
6
+ import { AGENT_PROFILES } from "./agents.js";
4
7
  import { THINKING_LEVELS } from "./constants.js";
5
- import { displayAgentName } from "./display-name.js";
6
8
  import { loadSubagentConfig, updateProfileOverride } from "./settings.js";
7
- import { ProfileEditorComponent } from "./ui/profile-editor.js";
8
- import { ProfilePickerComponent } from "./ui/profile-picker.js";
9
+ import { ProfileModelListComponent } from "./ui/model-list.js";
9
10
  const MODEL_REFRESH_TIMEOUT_MS = 15_000;
10
- const MAX_DIAGNOSTICS = 20;
11
- const MAX_DIAGNOSTIC_LENGTH = 240;
12
11
  const MAX_REFRESH_ERROR_LENGTH = 240;
13
- function compareAgentNames(left, right) {
14
- if (left.name < right.name)
15
- return -1;
16
- if (left.name > right.name)
17
- return 1;
18
- return 0;
19
- }
12
+ // Static display titles for the two built-in profiles; agents.ts owns the
13
+ // model-facing copy.
14
+ const PROFILE_LABELS = {
15
+ explorer: "Explorer",
16
+ general: "General",
17
+ };
20
18
  function modelId(model) {
21
19
  return model ? `${model.provider}/${model.id}` : "none";
22
20
  }
@@ -29,27 +27,18 @@ function boundText(value, limit) {
29
27
  const characters = [...value];
30
28
  return characters.length <= limit ? value : `${characters.slice(0, Math.max(0, limit - 1)).join("")}…`;
31
29
  }
32
- function formatDiagnostics(diagnostics) {
33
- const visible = diagnostics
34
- .slice(0, MAX_DIAGNOSTICS)
35
- .map((diagnostic) => boundText(`${diagnostic.path}: ${diagnostic.message}`, MAX_DIAGNOSTIC_LENGTH));
36
- if (diagnostics.length > visible.length) {
37
- visible.push(`… ${diagnostics.length - visible.length} more issue${diagnostics.length - visible.length === 1 ? "" : "s"}`);
30
+ // Capability checks use the saved model when it is still in the catalog,
31
+ // falling back to the parent session model otherwise.
32
+ function effectiveModel(currentSessionModel, models, override) {
33
+ if (override?.model) {
34
+ const saved = findModel(models, override.model);
35
+ if (saved)
36
+ return saved;
38
37
  }
39
- return visible.join("\n");
40
- }
41
- function overridesEqual(override, result) {
42
- return override?.model === result.model && override?.thinking === result.thinking;
38
+ return currentSessionModel;
43
39
  }
44
- async function saveProfileResult(ctx, profileName, override, result) {
45
- if (overridesEqual(override, result))
46
- return;
47
- try {
48
- await updateProfileOverride(profileName, { model: result.model, thinking: result.thinking }, getAgentDir());
49
- }
50
- catch (error) {
51
- ctx.ui.notify(`Could not save ${displayAgentName(profileName)} settings: ${error instanceof Error ? error.message : String(error)}`, "error");
52
- }
40
+ function inheritedThinking(effective, currentThinking) {
41
+ return effective ? clampThinkingLevel(effective, currentThinking) : currentThinking;
53
42
  }
54
43
  function refreshFailureMessage(providerIds) {
55
44
  if (providerIds.length === 1)
@@ -59,162 +48,326 @@ function refreshFailureMessage(providerIds) {
59
48
  const suffix = omitted > 0 ? `, +${omitted} more` : "";
60
49
  return `Could not refresh ${providerIds.length} model catalogs (${visible.join(", ")}${suffix}); showing cached models.`;
61
50
  }
62
- async function refreshEditorModels(ctx, editor) {
51
+ function hint(theme, keybindings, binding, description) {
52
+ const key = keyLabel(binding, { keybindings });
53
+ return key ? `${theme.fg("dim", key)}${theme.fg("muted", ` ${description}`)}` : "";
54
+ }
55
+ function navigationHint(theme, keybindings) {
56
+ const keys = [keyLabel("tui.select.up", { keybindings }), keyLabel("tui.select.down", { keybindings })]
57
+ .filter(Boolean)
58
+ .join("/");
59
+ return keys ? `${theme.fg("dim", keys)}${theme.fg("muted", " navigate")}` : "";
60
+ }
61
+ /** Compact Model/Thinking settings menu for one profile; every confirm persists immediately. */
62
+ class ProfileSettingsMenuComponent extends Container {
63
+ get focused() {
64
+ return this._focused;
65
+ }
66
+ set focused(value) {
67
+ this._focused = value;
68
+ }
69
+ constructor(options) {
70
+ super();
71
+ this.selectedIndex = 0;
72
+ this._focused = false;
73
+ this.theme = options.theme;
74
+ this.keybindings = options.keybindings;
75
+ this.onDone = options.onDone;
76
+ const effective = effectiveModel(options.currentSessionModel, options.models, options.override);
77
+ this.rows = [
78
+ {
79
+ action: "model",
80
+ label: "Model",
81
+ value: options.override?.model
82
+ ? `override — ${options.override.model}${findModel(options.models, options.override.model) ? "" : " [unavailable]"}`
83
+ : `inherit — ${modelId(options.currentSessionModel)}`,
84
+ override: options.override?.model !== undefined,
85
+ },
86
+ {
87
+ action: "thinking",
88
+ label: "Thinking",
89
+ value: options.override?.thinking
90
+ ? `override — ${options.override.thinking}`
91
+ : `inherit — ${inheritedThinking(effective, options.currentThinking)}`,
92
+ override: options.override?.thinking !== undefined,
93
+ },
94
+ ];
95
+ this.addChild(new DynamicBorder());
96
+ this.addChild(new Spacer(1));
97
+ this.addChild(new Text(this.theme.fg("accent", this.theme.bold(PROFILE_LABELS[options.profile.name])), 1, 0));
98
+ this.addChild(new Spacer(1));
99
+ this.listContainer = new Container();
100
+ this.addChild(this.listContainer);
101
+ this.addChild(new Spacer(1));
102
+ const hints = [
103
+ navigationHint(this.theme, options.keybindings),
104
+ hint(this.theme, options.keybindings, "tui.select.confirm", "select"),
105
+ hint(this.theme, options.keybindings, "tui.select.cancel", "back"),
106
+ ].filter(Boolean);
107
+ this.addChild(new Text(hints.join(this.theme.fg("muted", " • ")), 1, 0));
108
+ this.addChild(new Spacer(1));
109
+ this.addChild(new DynamicBorder());
110
+ this.updateList();
111
+ }
112
+ updateList() {
113
+ this.listContainer.clear();
114
+ for (const [index, row] of this.rows.entries()) {
115
+ const selected = index === this.selectedIndex;
116
+ const prefix = selected ? this.theme.fg("accent", "→ ") : " ";
117
+ const label = selected ? this.theme.fg("accent", row.label) : this.theme.fg("text", row.label);
118
+ const value = this.theme.fg("muted", `— ${row.value}`);
119
+ const checkmark = row.override ? this.theme.fg("success", " ✓") : "";
120
+ this.listContainer.addChild(new TruncatedText(`${prefix}${label} ${value}${checkmark}`, 1, 0));
121
+ }
122
+ }
123
+ moveSelection(offset) {
124
+ this.selectedIndex = (this.selectedIndex + offset + this.rows.length) % this.rows.length;
125
+ this.updateList();
126
+ }
127
+ handleInput(data) {
128
+ if (this.keybindings.matches(data, "tui.select.up")) {
129
+ this.moveSelection(-1);
130
+ }
131
+ else if (this.keybindings.matches(data, "tui.select.down")) {
132
+ this.moveSelection(1);
133
+ }
134
+ else if (this.keybindings.matches(data, "tui.select.confirm")) {
135
+ this.onDone(this.rows[this.selectedIndex]?.action);
136
+ }
137
+ else if (this.keybindings.matches(data, "tui.select.cancel")) {
138
+ this.onDone(undefined);
139
+ }
140
+ }
141
+ }
142
+ /** Standalone searchable model picker for one profile; confirm saves immediately. */
143
+ class ModelPickerComponent extends Container {
144
+ get focused() {
145
+ return this._focused;
146
+ }
147
+ set focused(value) {
148
+ this._focused = value;
149
+ this.modelList.focused = value;
150
+ }
151
+ constructor(options) {
152
+ super();
153
+ this.refreshAbortController = new AbortController();
154
+ this.closed = false;
155
+ this._focused = false;
156
+ this.tui = options.tui;
157
+ this.theme = options.theme;
158
+ this.keybindings = options.keybindings;
159
+ this.addChild(new DynamicBorder());
160
+ this.addChild(new Spacer(1));
161
+ this.addChild(new Text(this.theme.fg("accent", this.theme.bold(`Model — ${PROFILE_LABELS[options.profile.name]}`)), 1, 0));
162
+ this.addChild(new Spacer(1));
163
+ this.modelList = new ProfileModelListComponent({
164
+ theme: options.theme,
165
+ keybindings: options.keybindings,
166
+ models: options.models,
167
+ scopedModels: options.scopedModels,
168
+ currentSessionModel: options.currentSessionModel,
169
+ savedModelId: options.savedModelId,
170
+ onDone: options.onDone,
171
+ });
172
+ this.addChild(this.modelList);
173
+ this.addChild(new Spacer(1));
174
+ const hints = [
175
+ navigationHint(this.theme, options.keybindings),
176
+ options.scopedModels.length > 0 ? hint(this.theme, options.keybindings, "tui.input.tab", "scope") : "",
177
+ hint(this.theme, options.keybindings, "tui.select.confirm", "select"),
178
+ hint(this.theme, options.keybindings, "tui.select.cancel", "back"),
179
+ ].filter(Boolean);
180
+ this.addChild(new Text(hints.join(this.theme.fg("muted", " • ")), 1, 0));
181
+ this.addChild(new Spacer(1));
182
+ this.addChild(new DynamicBorder());
183
+ }
184
+ get refreshSignal() {
185
+ return this.refreshAbortController.signal;
186
+ }
187
+ cancelRefresh() {
188
+ this.refreshAbortController.abort();
189
+ }
190
+ get isClosed() {
191
+ return this.closed;
192
+ }
193
+ dispose() {
194
+ this.closed = true;
195
+ this.refreshAbortController.abort();
196
+ }
197
+ updateModels(models) {
198
+ if (this.closed)
199
+ return;
200
+ this.modelList.updateModels(models);
201
+ this.tui.requestRender();
202
+ }
203
+ setRefreshStatus(message, tone = "muted") {
204
+ if (this.closed)
205
+ return;
206
+ this.modelList.setStatus(message, tone);
207
+ this.tui.requestRender();
208
+ }
209
+ handleInput(data) {
210
+ this.modelList.handleInput(data);
211
+ }
212
+ }
213
+ /** Persist a single override atomically; the UI only reflects the value on success. */
214
+ async function persistOverride(ctx, profile, patch, current) {
215
+ const next = "model" in patch ? patch.model : patch.thinking;
216
+ if (next === current)
217
+ return;
218
+ try {
219
+ await updateProfileOverride(profile.name, patch, getAgentDir());
220
+ }
221
+ catch (error) {
222
+ ctx.ui.notify(`Could not save ${PROFILE_LABELS[profile.name]} settings: ${error instanceof Error ? error.message : String(error)}`, "error");
223
+ }
224
+ }
225
+ async function refreshPickerModels(ctx, picker) {
63
226
  let timedOut = false;
64
227
  const timeout = setTimeout(() => {
65
228
  timedOut = true;
66
- editor.cancelRefresh();
229
+ picker.cancelRefresh();
67
230
  }, MODEL_REFRESH_TIMEOUT_MS);
68
231
  timeout.unref?.();
69
232
  try {
70
- const result = await ctx.modelRegistry.refresh({ signal: editor.refreshSignal });
71
- if (editor.refreshSignal.aborted && !timedOut)
233
+ const result = await ctx.modelRegistry.refresh({ signal: picker.refreshSignal });
234
+ if (picker.isClosed || (picker.refreshSignal.aborted && !timedOut))
72
235
  return;
73
- editor.updateModels(ctx.modelRegistry.getAvailable());
236
+ picker.updateModels(ctx.modelRegistry.getAvailable());
74
237
  if (result.aborted && timedOut) {
75
- editor.setRefreshStatus("Model refresh timed out; showing cached models.", "error");
238
+ picker.setRefreshStatus("Model refresh timed out; showing cached models.", "error");
76
239
  return;
77
240
  }
78
241
  const providerIds = [...result.errors.keys()];
79
242
  if (providerIds.length > 0) {
80
- editor.setRefreshStatus(refreshFailureMessage(providerIds), "error");
243
+ picker.setRefreshStatus(refreshFailureMessage(providerIds), "error");
81
244
  return;
82
245
  }
83
246
  const registryError = ctx.modelRegistry.getError();
84
247
  if (registryError) {
85
- editor.setRefreshStatus(boundText(registryError, MAX_REFRESH_ERROR_LENGTH), "error");
248
+ picker.setRefreshStatus(boundText(registryError, MAX_REFRESH_ERROR_LENGTH), "error");
86
249
  return;
87
250
  }
88
- editor.setRefreshStatus("Model catalogs refreshed.", "success");
251
+ picker.setRefreshStatus(undefined);
89
252
  }
90
253
  catch (error) {
91
- if (editor.refreshSignal.aborted && !timedOut)
254
+ if (picker.isClosed || (picker.refreshSignal.aborted && !timedOut))
92
255
  return;
93
256
  const message = timedOut
94
257
  ? "Model refresh timed out; showing cached models."
95
258
  : `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`;
96
- editor.setRefreshStatus(boundText(message, MAX_REFRESH_ERROR_LENGTH), "error");
259
+ picker.setRefreshStatus(boundText(message, MAX_REFRESH_ERROR_LENGTH), "error");
97
260
  }
98
261
  finally {
99
262
  clearTimeout(timeout);
100
263
  }
101
264
  }
102
- async function showTuiAgentsCommand(ctx, currentThinking) {
103
- const agentDir = getAgentDir();
104
- let selectedProfileName;
105
- while (true) {
106
- const discovery = discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir });
107
- const picked = await ctx.ui.custom((_tui, theme, keybindings, done) => new ProfilePickerComponent(theme, keybindings, discovery.agents, discovery.diagnostics, done, selectedProfileName));
108
- if (!picked)
109
- return;
110
- if (picked.kind === "diagnostics") {
111
- ctx.ui.notify(formatDiagnostics(discovery.diagnostics), "warning");
112
- continue;
113
- }
114
- selectedProfileName = picked.name;
115
- const agent = discovery.agents.find((candidate) => candidate.name === picked.name);
116
- if (!agent)
117
- continue;
118
- const config = await loadSubagentConfig(agentDir);
119
- const override = config.profiles[agent.name];
120
- const edited = await ctx.ui.custom((tui, theme, keybindings, done) => {
121
- const editor = new ProfileEditorComponent({
122
- tui,
123
- theme,
124
- keybindings,
125
- agent,
126
- override,
127
- models: ctx.modelRegistry.getAvailable(),
128
- scopedModels: ctx.scopedModels,
129
- currentSessionModel: ctx.model,
130
- currentThinking,
131
- onDone: done,
132
- });
133
- editor.setRefreshStatus("Refreshing model catalogs…");
134
- void refreshEditorModels(ctx, editor);
135
- return editor;
265
+ async function selectProfile(ctx) {
266
+ const labels = AGENT_PROFILES.map((profile) => PROFILE_LABELS[profile.name]);
267
+ const label = await ctx.ui.select("Agents", labels);
268
+ return label === undefined ? undefined : AGENT_PROFILES.find((profile) => PROFILE_LABELS[profile.name] === label);
269
+ }
270
+ async function showTuiSettingsMenu(ctx, profile, override, currentThinking) {
271
+ return ctx.ui.custom((_tui, theme, keybindings, done) => new ProfileSettingsMenuComponent({
272
+ theme,
273
+ keybindings,
274
+ profile,
275
+ override,
276
+ models: ctx.modelRegistry.getAvailable(),
277
+ currentSessionModel: ctx.model,
278
+ currentThinking,
279
+ onDone: done,
280
+ }));
281
+ }
282
+ async function showDialogSettingsMenu(ctx, profile, override, currentThinking) {
283
+ const models = ctx.modelRegistry.getAvailable();
284
+ const effective = effectiveModel(ctx.model, models, override);
285
+ const modelValue = override?.model
286
+ ? `override — ${override.model}${findModel(models, override.model) ? "" : " [unavailable]"}`
287
+ : `inherit — ${modelId(ctx.model)}`;
288
+ const thinkingValue = override?.thinking
289
+ ? `override — ${override.thinking}`
290
+ : `inherit — ${inheritedThinking(effective, currentThinking)}`;
291
+ const options = new Map();
292
+ options.set(`Model — ${modelValue}`, "model");
293
+ options.set(`Thinking — ${thinkingValue}`, "thinking");
294
+ const label = await ctx.ui.select(PROFILE_LABELS[profile.name], [...options.keys()]);
295
+ return label === undefined ? undefined : options.get(label);
296
+ }
297
+ async function showTuiModelPicker(ctx, profile, override) {
298
+ const chosen = await ctx.ui.custom((tui, theme, keybindings, done) => {
299
+ const picker = new ModelPickerComponent({
300
+ tui,
301
+ theme,
302
+ keybindings,
303
+ profile,
304
+ savedModelId: override?.model,
305
+ models: ctx.modelRegistry.getAvailable(),
306
+ scopedModels: ctx.scopedModels,
307
+ currentSessionModel: ctx.model,
308
+ onDone: done,
136
309
  });
137
- if (!edited)
138
- continue;
139
- await saveProfileResult(ctx, agent.name, override, edited);
310
+ picker.setRefreshStatus("Refreshing model catalogs…");
311
+ void refreshPickerModels(ctx, picker);
312
+ return picker;
313
+ });
314
+ return chosen === undefined ? undefined : { modelId: chosen.modelId };
315
+ }
316
+ async function showDialogModelPicker(ctx, profile, override) {
317
+ const models = ctx.modelRegistry.getAvailable().sort((left, right) => {
318
+ const provider = left.provider.localeCompare(right.provider);
319
+ return provider !== 0 ? provider : left.id.localeCompare(right.id);
320
+ });
321
+ const choices = new Map();
322
+ choices.set(`inherit (${modelId(ctx.model)})${override?.model ? "" : " ✓"}`, undefined);
323
+ if (override?.model && !findModel(models, override.model)) {
324
+ choices.set(`${override.model} [unavailable] ✓`, override.model);
140
325
  }
326
+ for (const model of models) {
327
+ const id = modelId(model);
328
+ choices.set(`${id}${override?.model === id ? " ✓" : ""}`, id);
329
+ }
330
+ const label = await ctx.ui.select(`Model — ${PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
331
+ return label === undefined ? undefined : { modelId: choices.get(label) };
141
332
  }
142
- function uniqueProfileLabels(agents) {
143
- const sorted = [...agents].sort(compareAgentNames);
144
- const counts = new Map();
145
- for (const agent of sorted) {
146
- const display = displayAgentName(agent.name);
147
- counts.set(display, (counts.get(display) ?? 0) + 1);
333
+ async function showThinkingPicker(ctx, profile, override, currentThinking) {
334
+ const models = ctx.modelRegistry.getAvailable();
335
+ const effective = effectiveModel(ctx.model, models, override);
336
+ const levels = effective ? getSupportedThinkingLevels(effective) : [...THINKING_LEVELS];
337
+ const inherited = inheritedThinking(effective, currentThinking);
338
+ const choices = new Map();
339
+ choices.set(`inherit (${inherited})${override?.thinking ? "" : " ✓"}`, undefined);
340
+ for (const level of levels) {
341
+ choices.set(`${level}${override?.thinking === level ? " ✓" : ""}`, level);
148
342
  }
149
- return new Map(sorted.map((agent) => {
150
- const display = displayAgentName(agent.name);
151
- return [counts.get(display) === 1 ? display : `${display} (${agent.name})`, agent];
152
- }));
343
+ const label = await ctx.ui.select(`Thinking — ${PROFILE_LABELS[profile.name]}`, [...choices.keys()]);
344
+ return label === undefined ? undefined : { level: choices.get(label) };
153
345
  }
154
- async function showDialogAgentsCommand(ctx, currentThinking) {
346
+ async function runProfileSettings(ctx, profile, currentThinking) {
155
347
  const agentDir = getAgentDir();
348
+ const isTui = ctx.mode === "tui";
156
349
  while (true) {
157
- const discovery = discoverAgents(ctx.cwd, { projectTrusted: ctx.isProjectTrusted(), agentDir });
158
- const labels = uniqueProfileLabels(discovery.agents);
159
- const diagnosticsLabel = discovery.diagnostics.length
160
- ? `View ${discovery.diagnostics.length} agent file issue${discovery.diagnostics.length === 1 ? "" : "s"}`
161
- : undefined;
162
- const selectedLabel = await ctx.ui.select("Agents", [
163
- ...labels.keys(),
164
- ...(diagnosticsLabel ? [diagnosticsLabel] : []),
165
- ]);
166
- if (!selectedLabel)
167
- return;
168
- if (selectedLabel === diagnosticsLabel) {
169
- ctx.ui.notify(formatDiagnostics(discovery.diagnostics), "warning");
170
- continue;
171
- }
172
- const agent = labels.get(selectedLabel);
173
- if (!agent)
174
- continue;
175
350
  const config = await loadSubagentConfig(agentDir);
176
- const override = config.profiles[agent.name];
177
- const models = ctx.modelRegistry.getAvailable().sort((left, right) => {
178
- const provider = left.provider.localeCompare(right.provider);
179
- return provider !== 0 ? provider : left.id.localeCompare(right.id);
180
- });
181
- const modelChoices = new Map();
182
- const inheritModelLabel = `inherit (${modelId(ctx.model)})${override?.model ? "" : " ✓"}`;
183
- modelChoices.set(inheritModelLabel, undefined);
184
- if (override?.model && !findModel(models, override.model)) {
185
- modelChoices.set(`${override.model} [unavailable] ✓`, override.model);
186
- }
187
- for (const model of models) {
188
- const id = modelId(model);
189
- modelChoices.set(`${id}${override?.model === id ? " ✓" : ""}`, id);
351
+ const override = config.profiles[profile.name];
352
+ const action = isTui
353
+ ? await showTuiSettingsMenu(ctx, profile, override, currentThinking)
354
+ : await showDialogSettingsMenu(ctx, profile, override, currentThinking);
355
+ if (!action)
356
+ return;
357
+ if (action === "model") {
358
+ const chosen = isTui
359
+ ? await showTuiModelPicker(ctx, profile, override)
360
+ : await showDialogModelPicker(ctx, profile, override);
361
+ if (!chosen)
362
+ continue;
363
+ await persistOverride(ctx, profile, { model: chosen.modelId }, override?.model);
190
364
  }
191
- const selectedModelLabel = await ctx.ui.select(`Model for ${displayAgentName(agent.name)}`, [
192
- ...modelChoices.keys(),
193
- ]);
194
- if (!selectedModelLabel)
195
- continue;
196
- const selectedModelId = modelChoices.get(selectedModelLabel);
197
- const effectiveModel = selectedModelId ? findModel(models, selectedModelId) : ctx.model;
198
- const levels = effectiveModel
199
- ? getSupportedThinkingLevels(effectiveModel)
200
- : [...THINKING_LEVELS];
201
- const inheritedThinking = effectiveModel
202
- ? clampThinkingLevel(effectiveModel, currentThinking)
203
- : currentThinking;
204
- const thinkingChoices = new Map();
205
- thinkingChoices.set(`inherit (${inheritedThinking})${override?.thinking ? "" : " ✓"}`, undefined);
206
- for (const level of levels) {
207
- thinkingChoices.set(`${level}${override?.thinking === level ? " ✓" : ""}`, level);
365
+ else {
366
+ const chosen = await showThinkingPicker(ctx, profile, override, currentThinking);
367
+ if (!chosen)
368
+ continue;
369
+ await persistOverride(ctx, profile, { thinking: chosen.level }, override?.thinking);
208
370
  }
209
- const selectedThinkingLabel = await ctx.ui.select(`Thinking for ${displayAgentName(agent.name)}`, [
210
- ...thinkingChoices.keys(),
211
- ]);
212
- if (!selectedThinkingLabel)
213
- continue;
214
- await saveProfileResult(ctx, agent.name, override, {
215
- model: selectedModelId,
216
- thinking: thinkingChoices.get(selectedThinkingLabel),
217
- });
218
371
  }
219
372
  }
220
373
  export async function showAgentsCommand(ctx, currentThinking) {
@@ -222,9 +375,11 @@ export async function showAgentsCommand(ctx, currentThinking) {
222
375
  ctx.ui.notify("/agents requires an interactive UI.", "warning");
223
376
  return;
224
377
  }
225
- if (ctx.mode === "tui")
226
- await showTuiAgentsCommand(ctx, currentThinking);
227
- else
228
- await showDialogAgentsCommand(ctx, currentThinking);
378
+ while (true) {
379
+ const profile = await selectProfile(ctx);
380
+ if (!profile)
381
+ return;
382
+ await runProfileSettings(ctx, profile, currentThinking);
383
+ }
229
384
  }
230
385
  //# sourceMappingURL=agents-command.js.map