@oh-my-pi/pi-coding-agent 16.1.22 → 16.2.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 (276) hide show
  1. package/CHANGELOG.md +99 -1
  2. package/dist/cli.js +4387 -4164
  3. package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
  4. package/dist/types/advisor/emission-guard.d.ts +73 -0
  5. package/dist/types/advisor/index.d.ts +1 -0
  6. package/dist/types/advisor/runtime.d.ts +7 -0
  7. package/dist/types/advisor/watchdog.d.ts +2 -0
  8. package/dist/types/cli/flag-tables.d.ts +1 -0
  9. package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
  10. package/dist/types/cli/gc-cli.d.ts +58 -0
  11. package/dist/types/collab/display-name.d.ts +3 -0
  12. package/dist/types/collab/host.d.ts +0 -2
  13. package/dist/types/commands/gc.d.ts +37 -0
  14. package/dist/types/commands/worktree.d.ts +0 -3
  15. package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
  16. package/dist/types/config/model-discovery.d.ts +6 -1
  17. package/dist/types/config/model-registry.d.ts +6 -2
  18. package/dist/types/config/model-resolver.d.ts +12 -0
  19. package/dist/types/config/models-config-schema.d.ts +29 -2
  20. package/dist/types/config/models-config.d.ts +22 -1
  21. package/dist/types/config/settings-schema.d.ts +145 -21
  22. package/dist/types/config/settings.d.ts +13 -0
  23. package/dist/types/dap/config.d.ts +1 -1
  24. package/dist/types/edit/hashline/filesystem.d.ts +4 -2
  25. package/dist/types/edit/renderer.d.ts +4 -0
  26. package/dist/types/extensibility/extensions/types.d.ts +17 -17
  27. package/dist/types/extensibility/hooks/types.d.ts +10 -10
  28. package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
  29. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
  30. package/dist/types/internal-urls/index.d.ts +1 -0
  31. package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
  32. package/dist/types/internal-urls/router.d.ts +1 -1
  33. package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
  34. package/dist/types/internal-urls/types.d.ts +19 -2
  35. package/dist/types/irc/bus.d.ts +6 -0
  36. package/dist/types/mcp/transports/stdio.d.ts +25 -1
  37. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
  38. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  39. package/dist/types/modes/components/move-overlay.d.ts +23 -0
  40. package/dist/types/modes/components/plugin-selector.d.ts +2 -1
  41. package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
  42. package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
  43. package/dist/types/modes/components/settings-defs.d.ts +4 -1
  44. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  45. package/dist/types/modes/components/show-images-selector.d.ts +2 -1
  46. package/dist/types/modes/components/status-line/component.d.ts +2 -0
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/theme-selector.d.ts +2 -1
  49. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +10 -1
  51. package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
  52. package/dist/types/modes/interactive-mode.d.ts +10 -1
  53. package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
  54. package/dist/types/modes/running-subagent-badge.d.ts +6 -0
  55. package/dist/types/modes/theme/mermaid-rendering.test.d.ts +1 -0
  56. package/dist/types/modes/theme/theme.d.ts +2 -1
  57. package/dist/types/modes/types.d.ts +9 -1
  58. package/dist/types/sdk.d.ts +2 -2
  59. package/dist/types/session/agent-session.d.ts +2 -1
  60. package/dist/types/session/session-listing.d.ts +10 -1
  61. package/dist/types/session/session-manager.d.ts +13 -0
  62. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  63. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
  64. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
  65. package/dist/types/ssh/connection-manager.d.ts +2 -0
  66. package/dist/types/ssh/file-transfer.d.ts +79 -0
  67. package/dist/types/ssh/utils.d.ts +6 -0
  68. package/dist/types/system-prompt.d.ts +5 -0
  69. package/dist/types/task/executor.d.ts +16 -0
  70. package/dist/types/tiny/text.d.ts +1 -1
  71. package/dist/types/tools/builtin-names.d.ts +5 -1
  72. package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
  73. package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
  74. package/dist/types/tools/index.d.ts +3 -3
  75. package/dist/types/tools/path-utils.d.ts +29 -0
  76. package/dist/types/tools/plan-mode-guard.d.ts +7 -0
  77. package/dist/types/tools/read.d.ts +2 -2
  78. package/dist/types/tools/render-utils.d.ts +8 -0
  79. package/dist/types/tools/renderers.d.ts +11 -0
  80. package/dist/types/tools/ssh.d.ts +2 -0
  81. package/dist/types/tools/todo.d.ts +0 -16
  82. package/dist/types/tools/write.d.ts +2 -2
  83. package/dist/types/utils/active-repo-context.d.ts +8 -0
  84. package/dist/types/utils/image-resize.d.ts +1 -0
  85. package/dist/types/utils/markit-cache.d.ts +23 -0
  86. package/dist/types/utils/markit.d.ts +5 -1
  87. package/dist/types/utils/prompt-path.d.ts +1 -0
  88. package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
  89. package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
  90. package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
  91. package/dist/types/web/search/providers/xai.d.ts +13 -0
  92. package/dist/types/web/search/types.d.ts +18 -2
  93. package/package.json +30 -15
  94. package/scripts/bench-guard.ts +1 -1
  95. package/scripts/build-binary.ts +9 -9
  96. package/scripts/bundle-dist.ts +2 -2
  97. package/src/advisor/__tests__/advisor.test.ts +40 -4
  98. package/src/advisor/__tests__/emission-guard.test.ts +147 -0
  99. package/src/advisor/advise-tool.ts +1 -1
  100. package/src/advisor/emission-guard.ts +172 -0
  101. package/src/advisor/index.ts +1 -0
  102. package/src/advisor/runtime.ts +11 -0
  103. package/src/advisor/watchdog.ts +12 -1
  104. package/src/cli/args.ts +5 -2
  105. package/src/cli/auth-broker-cli.ts +17 -0
  106. package/src/cli/config-cli.ts +4 -0
  107. package/src/cli/flag-tables.ts +7 -4
  108. package/src/cli/gallery-cli.ts +14 -2
  109. package/src/cli/gallery-fixtures/edit.ts +60 -0
  110. package/src/cli/gallery-fixtures/fs.ts +17 -17
  111. package/src/cli/gallery-fixtures/search.ts +4 -4
  112. package/src/cli/gc-cli.ts +939 -0
  113. package/src/cli/usage-cli.ts +20 -1
  114. package/src/cli/web-search-cli.ts +1 -1
  115. package/src/cli-commands.ts +1 -0
  116. package/src/collab/display-name.ts +13 -0
  117. package/src/collab/guest.ts +5 -1
  118. package/src/collab/host.ts +2 -13
  119. package/src/commands/gc.ts +46 -0
  120. package/src/commands/worktree.ts +6 -0
  121. package/src/config/inline-tool-descriptors-mode.ts +27 -0
  122. package/src/config/model-discovery.ts +77 -8
  123. package/src/config/model-registry.ts +89 -11
  124. package/src/config/model-resolver.ts +39 -1
  125. package/src/config/models-config-schema.ts +41 -5
  126. package/src/config/models-config.ts +4 -1
  127. package/src/config/settings-schema.ts +130 -27
  128. package/src/config/settings.ts +216 -7
  129. package/src/cursor.ts +1 -1
  130. package/src/dap/config.ts +152 -8
  131. package/src/dap/session.ts +1 -1
  132. package/src/discovery/helpers.ts +3 -1
  133. package/src/edit/hashline/diff.ts +14 -3
  134. package/src/edit/hashline/execute.ts +42 -6
  135. package/src/edit/hashline/filesystem.ts +49 -8
  136. package/src/edit/index.ts +1 -0
  137. package/src/edit/modes/patch.ts +11 -1
  138. package/src/edit/renderer.ts +140 -13
  139. package/src/eval/__tests__/agent-bridge.test.ts +101 -0
  140. package/src/eval/__tests__/julia-prelude.test.ts +18 -0
  141. package/src/eval/agent-bridge.ts +26 -3
  142. package/src/eval/jl/runner.jl +7 -1
  143. package/src/eval/js/tool-bridge.ts +2 -2
  144. package/src/export/html/index.ts +1 -1
  145. package/src/export/html/template.js +3 -1
  146. package/src/export/html/tool-views.generated.js +20 -20
  147. package/src/extensibility/extensions/types.ts +22 -22
  148. package/src/extensibility/hooks/types.ts +11 -11
  149. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +10 -3
  150. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +31 -10
  151. package/src/extensibility/plugins/marketplace/manager.ts +22 -0
  152. package/src/extensibility/plugins/marketplace/types.ts +1 -0
  153. package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
  154. package/src/internal-urls/docs-index.generated.txt +2 -2
  155. package/src/internal-urls/docs-index.ts +2 -3
  156. package/src/internal-urls/index.ts +1 -0
  157. package/src/internal-urls/registry-helpers.ts +19 -4
  158. package/src/internal-urls/router.ts +5 -3
  159. package/src/internal-urls/ssh-protocol.ts +367 -0
  160. package/src/internal-urls/types.ts +19 -2
  161. package/src/irc/bus.ts +11 -3
  162. package/src/lsp/index.ts +8 -1
  163. package/src/mcp/oauth-discovery.ts +20 -20
  164. package/src/mcp/tool-bridge.ts +32 -2
  165. package/src/mcp/transports/stdio.test.ts +20 -3
  166. package/src/mcp/transports/stdio.ts +45 -14
  167. package/src/memories/index.ts +1 -1
  168. package/src/modes/acp/acp-event-mapper.ts +2 -2
  169. package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
  170. package/src/modes/components/agent-hub.ts +3 -0
  171. package/src/modes/components/agent-transcript-viewer.ts +9 -7
  172. package/src/modes/components/custom-editor.ts +90 -5
  173. package/src/modes/components/move-overlay.ts +282 -0
  174. package/src/modes/components/plan-review-overlay.ts +35 -35
  175. package/src/modes/components/plugin-selector.ts +6 -1
  176. package/src/modes/components/queue-mode-selector.ts +6 -1
  177. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  178. package/src/modes/components/session-selector.ts +11 -10
  179. package/src/modes/components/settings-defs.ts +14 -1
  180. package/src/modes/components/settings-selector.ts +196 -29
  181. package/src/modes/components/show-images-selector.ts +6 -1
  182. package/src/modes/components/status-line/component.ts +108 -28
  183. package/src/modes/components/status-line/segments.ts +5 -1
  184. package/src/modes/components/status-line/types.ts +2 -0
  185. package/src/modes/components/theme-selector.ts +6 -1
  186. package/src/modes/components/thinking-selector.ts +6 -1
  187. package/src/modes/components/tool-execution.ts +25 -9
  188. package/src/modes/components/tree-selector.ts +5 -5
  189. package/src/modes/controllers/command-controller.ts +140 -47
  190. package/src/modes/controllers/event-controller.ts +59 -3
  191. package/src/modes/controllers/input-controller.ts +70 -4
  192. package/src/modes/controllers/selector-controller.ts +15 -2
  193. package/src/modes/controllers/streaming-reveal.ts +17 -0
  194. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  195. package/src/modes/interactive-mode.ts +142 -59
  196. package/src/modes/internal-url-autocomplete.ts +17 -2
  197. package/src/modes/prompt-action-autocomplete.ts +3 -1
  198. package/src/modes/running-subagent-badge.ts +13 -0
  199. package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
  200. package/src/modes/setup-wizard/scenes/providers.ts +2 -1
  201. package/src/modes/setup-wizard/scenes/theme.ts +6 -12
  202. package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
  203. package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
  204. package/src/modes/theme/mermaid-rendering.test.ts +53 -0
  205. package/src/modes/theme/theme.ts +42 -15
  206. package/src/modes/types.ts +9 -1
  207. package/src/modes/utils/interactive-context-helpers.ts +1 -1
  208. package/src/priority.json +15 -0
  209. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  210. package/src/prompts/advisor/system.md +21 -7
  211. package/src/prompts/agents/designer.md +1 -1
  212. package/src/prompts/agents/explore.md +1 -1
  213. package/src/prompts/agents/librarian.md +2 -2
  214. package/src/prompts/agents/plan.md +2 -2
  215. package/src/prompts/agents/reviewer.md +1 -1
  216. package/src/prompts/agents/task.md +2 -2
  217. package/src/prompts/system/active-repo-context.md +4 -0
  218. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  219. package/src/prompts/system/plan-mode-active.md +12 -3
  220. package/src/prompts/system/project-prompt.md +2 -2
  221. package/src/prompts/system/system-prompt.md +8 -6
  222. package/src/prompts/tools/bash.md +2 -2
  223. package/src/prompts/tools/checkpoint.md +1 -1
  224. package/src/prompts/tools/{find.md → glob.md} +1 -1
  225. package/src/prompts/tools/{search.md → grep.md} +3 -3
  226. package/src/prompts/tools/read.md +4 -2
  227. package/src/sdk.ts +98 -29
  228. package/src/session/agent-session.ts +828 -191
  229. package/src/session/session-history-format.ts +2 -2
  230. package/src/session/session-listing.ts +35 -2
  231. package/src/session/session-manager.ts +46 -0
  232. package/src/slash-commands/builtin-registry.ts +158 -22
  233. package/src/slash-commands/helpers/usage-report.ts +23 -2
  234. package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
  235. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
  236. package/src/ssh/connection-manager.ts +12 -15
  237. package/src/ssh/file-transfer.ts +209 -0
  238. package/src/ssh/utils.ts +24 -0
  239. package/src/system-prompt.ts +60 -28
  240. package/src/task/executor.ts +96 -36
  241. package/src/task/index.ts +3 -3
  242. package/src/task/render.ts +14 -13
  243. package/src/task/worktree.ts +38 -1
  244. package/src/tiny/text.ts +49 -4
  245. package/src/tiny/worker.ts +3 -3
  246. package/src/tools/acp-bridge.ts +6 -1
  247. package/src/tools/bash.ts +2 -2
  248. package/src/tools/builtin-names.ts +26 -2
  249. package/src/tools/{find.ts → glob.ts} +48 -42
  250. package/src/tools/{search.ts → grep.ts} +311 -129
  251. package/src/tools/index.ts +13 -14
  252. package/src/tools/irc.ts +6 -2
  253. package/src/tools/path-utils.ts +64 -1
  254. package/src/tools/plan-mode-guard.ts +26 -13
  255. package/src/tools/read.ts +35 -6
  256. package/src/tools/render-utils.ts +14 -0
  257. package/src/tools/renderers.ts +15 -4
  258. package/src/tools/ssh.ts +17 -2
  259. package/src/tools/todo.ts +4 -26
  260. package/src/tools/write.ts +25 -9
  261. package/src/utils/active-repo-context.ts +143 -0
  262. package/src/utils/edit-mode.ts +19 -2
  263. package/src/utils/image-resize.ts +88 -7
  264. package/src/utils/lang-from-path.ts +3 -3
  265. package/src/utils/markit-cache.ts +166 -0
  266. package/src/utils/markit.ts +86 -18
  267. package/src/utils/prompt-path.ts +3 -0
  268. package/src/utils/shell-snapshot.ts +1 -1
  269. package/src/utils/title-generator.ts +1 -1
  270. package/src/web/search/provider.ts +54 -34
  271. package/src/web/search/providers/duckduckgo.ts +140 -0
  272. package/src/web/search/providers/firecrawl.ts +144 -0
  273. package/src/web/search/providers/tinyfish.ts +159 -0
  274. package/src/web/search/providers/xai.ts +292 -0
  275. package/src/web/search/providers/zai.ts +142 -56
  276. package/src/web/search/types.ts +6 -2
@@ -10,7 +10,9 @@ import {
10
10
  type ImageBudget,
11
11
  Input,
12
12
  matchesKey,
13
- parseSgrMouse,
13
+ replaceTabs,
14
+ routeSelectListMouse,
15
+ routeSgrMouseInput,
14
16
  type SelectItem,
15
17
  SelectList,
16
18
  type SettingItem,
@@ -24,7 +26,14 @@ import {
24
26
  visibleWidth,
25
27
  } from "@oh-my-pi/pi-tui";
26
28
  import type { ShapeTarget } from "@oh-my-pi/snapcompact";
27
- import { getDefault, type SettingPath, settings } from "../../config/settings";
29
+ import {
30
+ getDefault,
31
+ getType,
32
+ normalizeProviderMaxInFlightRequests,
33
+ type SettingPath,
34
+ settings,
35
+ validateProviderMaxInFlightRequests,
36
+ } from "../../config/settings";
28
37
  import type {
29
38
  SettingTab,
30
39
  StatusLinePreset,
@@ -50,6 +59,7 @@ import { getPreset } from "./status-line/presets";
50
59
  */
51
60
  class TextInputSubmenu extends Container {
52
61
  #input: Input;
62
+ #error: Text;
53
63
 
54
64
  constructor(
55
65
  label: string,
@@ -71,11 +81,18 @@ class TextInputSubmenu extends Container {
71
81
  if (currentValue) {
72
82
  this.#input.setValue(currentValue);
73
83
  }
84
+ this.#error = new Text("", 0, 0);
74
85
  this.#input.onSubmit = value => {
75
- this.onSubmit(value); // empty string clears the setting
86
+ try {
87
+ this.onSubmit(value); // empty string clears the setting
88
+ } catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ this.#error.setText(theme.fg("error", truncateToWidth(replaceTabs(message).replace(/[\r\n]+/g, " "), 100)));
91
+ }
76
92
  };
77
93
  this.addChild(this.#input);
78
94
  this.addChild(new Spacer(1));
95
+ this.addChild(this.#error);
79
96
  this.addChild(new Text(theme.fg("dim", " Enter to save · Esc to cancel · Clear field to unset"), 0, 0));
80
97
  }
81
98
 
@@ -89,7 +106,6 @@ class SelectSubmenu extends Container {
89
106
  #previewText: Text | null = null;
90
107
  #previewUpdateRequestId: number = 0;
91
108
  #selectListLineOffset = 0;
92
- #selectListLineCount = 0;
93
109
 
94
110
  constructor(
95
111
  title: string,
@@ -187,7 +203,6 @@ class SelectSubmenu extends Container {
187
203
  const childLines = child.render(Math.max(1, width));
188
204
  if (child === this.#selectList) {
189
205
  this.#selectListLineOffset = lines.length;
190
- this.#selectListLineCount = childLines.length;
191
206
  }
192
207
  lines.push(...childLines);
193
208
  }
@@ -196,20 +211,7 @@ class SelectSubmenu extends Container {
196
211
 
197
212
  /** Mouse routed from the host: wheel steps, hover lights, click confirms. */
198
213
  routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
199
- if (event.wheel !== null) {
200
- this.#selectList.handleWheel(event.wheel);
201
- return;
202
- }
203
- const listLine = line - this.#selectListLineOffset;
204
- const within = listLine >= 0 && listLine < this.#selectListLineCount;
205
- const index = within ? this.#selectList.hitTest(listLine) : undefined;
206
- if (event.motion) {
207
- this.#selectList.setHoverIndex(index ?? null);
208
- return;
209
- }
210
- if (event.leftClick && index !== undefined) {
211
- this.#selectList.clickItem(index);
212
- }
214
+ routeSelectListMouse(this.#selectList, event, line - this.#selectListLineOffset);
213
215
  }
214
216
 
215
217
  handleInput(data: string): void {
@@ -217,6 +219,113 @@ class SelectSubmenu extends Container {
217
219
  }
218
220
  }
219
221
 
222
+ class ProviderLimitsSubmenu extends Container {
223
+ #selectList: SelectList | undefined;
224
+
225
+ constructor(
226
+ private readonly providers: readonly string[],
227
+ private readonly onChange: (value: Record<string, number>) => void,
228
+ private readonly onCancel: () => void,
229
+ private readonly requestRender?: () => void,
230
+ ) {
231
+ super();
232
+ this.#showProviderList();
233
+ }
234
+
235
+ #providerIds(): string[] {
236
+ const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
237
+ return [...new Set([...this.providers, ...Object.keys(limits)])].sort((a, b) => a.localeCompare(b));
238
+ }
239
+
240
+ #showProviderList(): void {
241
+ this.clear();
242
+ this.addChild(new Text(theme.bold(theme.fg("accent", "Max In-Flight Requests")), 0, 0));
243
+ this.addChild(new Spacer(1));
244
+ this.addChild(
245
+ new Text(
246
+ theme.fg(
247
+ "muted",
248
+ "Select a provider, enter a positive number to cap concurrent LLM requests, or clear it for unlimited.",
249
+ ),
250
+ 0,
251
+ 0,
252
+ ),
253
+ );
254
+ this.addChild(new Spacer(1));
255
+
256
+ const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
257
+ const providerItems = this.#providerIds().map((provider): SelectItem => {
258
+ const limit = limits[provider];
259
+ return {
260
+ value: provider,
261
+ label: provider,
262
+ description: limit === undefined ? "Unlimited" : `Limit: ${limit}`,
263
+ };
264
+ });
265
+ const clearItem: SelectItem[] =
266
+ Object.keys(limits).length === 0
267
+ ? []
268
+ : [{ value: "__clear_all", label: "Clear all limits", description: "Make every provider unlimited" }];
269
+ const items = [...providerItems, ...clearItem];
270
+ this.#selectList = new SelectList(items, Math.min(Math.max(items.length, 1), 12), getSelectListTheme());
271
+ this.#selectList.onSelect = item => {
272
+ if (item.value === "__clear_all") {
273
+ settings.set("providers.maxInFlightRequests", {});
274
+ this.onChange({});
275
+ this.#showProviderList();
276
+ this.requestRender?.();
277
+ return;
278
+ }
279
+ this.#showProviderEditor(item.value);
280
+ };
281
+ this.#selectList.onCancel = this.onCancel;
282
+ this.addChild(this.#selectList);
283
+ this.addChild(new Spacer(1));
284
+ this.addChild(new Text(theme.fg("dim", " Enter to edit provider · Esc to go back"), 0, 0));
285
+ }
286
+
287
+ #showProviderEditor(provider: string): void {
288
+ const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
289
+ this.clear();
290
+ this.#selectList = undefined;
291
+ this.addChild(
292
+ new TextInputSubmenu(
293
+ `Max In-Flight Requests: ${provider}`,
294
+ "Enter a positive number. Decimals round down. Clear the field to make this provider unlimited.",
295
+ limits[provider]?.toString() ?? "",
296
+ value => {
297
+ const next = { ...limits };
298
+ const trimmed = value.trim();
299
+ if (trimmed === "") {
300
+ delete next[provider];
301
+ } else {
302
+ const limit = Number(trimmed);
303
+ if (!Number.isFinite(limit) || limit <= 0) throw new Error("Limit must be a positive number.");
304
+ next[provider] = Math.max(1, Math.floor(limit));
305
+ }
306
+ const normalized = validateProviderMaxInFlightRequests(next);
307
+ settings.set("providers.maxInFlightRequests", normalized);
308
+ this.onChange(normalized);
309
+ this.#showProviderList();
310
+ this.requestRender?.();
311
+ },
312
+ () => {
313
+ this.#showProviderList();
314
+ this.requestRender?.();
315
+ },
316
+ ),
317
+ );
318
+ }
319
+
320
+ handleInput(data: string): void {
321
+ if (this.#selectList) {
322
+ this.#selectList.handleInput(data);
323
+ return;
324
+ }
325
+ this.children[0]?.handleInput?.(data);
326
+ }
327
+ }
328
+
220
329
  let cachedSidebarWidth: number | undefined;
221
330
  /**
222
331
  * Split-sidebar width derived from every group name in the schema (not just
@@ -258,6 +367,8 @@ export interface SettingsRuntimeContext {
258
367
  thinkingLevel: ThinkingLevel | undefined;
259
368
  /** Available themes */
260
369
  availableThemes: string[];
370
+ /** Provider/source ids shown in /model. */
371
+ providers: string[];
261
372
  /** Working directory for plugins tab */
262
373
  cwd: string;
263
374
  /** Active model (api + id); resolves what the snapcompact `auto` shape maps to. */
@@ -458,12 +569,14 @@ export class SettingsSelectorComponent implements Component {
458
569
  * activates it (toggle / open submenu).
459
570
  */
460
571
  #handleMouse(data: string): boolean {
461
- const event = parseSgrMouse(data);
462
- if (!event) return false;
572
+ return routeSgrMouseInput(data, event => this.#routeMouseEvent(event));
573
+ }
463
574
 
575
+ #routeMouseEvent(event: SgrMouseEvent): boolean {
464
576
  const list = this.#searchList ?? this.#currentList;
465
- // row() insets content by two columns (border + space).
466
- const innerCol = event.col - 2;
577
+ // row() insets content by the border column plus a space.
578
+ const contentColInset = 2;
579
+ const innerCol = event.col - contentColInset;
467
580
  const contentLine = event.row - this.#contentRowStart;
468
581
 
469
582
  // An open submenu owns the pointer: wheel, hover, and clicks route into
@@ -723,10 +836,20 @@ export class SettingsSelectorComponent implements Component {
723
836
  id: def.path,
724
837
  label: def.label,
725
838
  description: def.description,
726
- currentValue: String(currentValue ?? ""),
839
+ currentValue: this.#formatTextInputValue(def.path, currentValue),
727
840
  submenu: (cv, done) => this.#createTextInput(def, cv, done),
728
841
  changed,
729
842
  };
843
+
844
+ case "providerLimits":
845
+ return {
846
+ id: def.path,
847
+ label: def.label,
848
+ description: def.description,
849
+ currentValue: this.#formatProviderLimitsValue(currentValue),
850
+ submenu: (_cv, done) => this.#createProviderLimitsInput(done),
851
+ changed,
852
+ };
730
853
  }
731
854
  }
732
855
 
@@ -856,7 +979,7 @@ export class SettingsSelectorComponent implements Component {
856
979
  */
857
980
  #createTextInput(
858
981
  def: SettingDef & { type: "text" },
859
- currentValue: string,
982
+ _currentValue: string,
860
983
  done: (value?: string) => void,
861
984
  ): Container {
862
985
  this.#textInputActive = true;
@@ -867,28 +990,72 @@ export class SettingsSelectorComponent implements Component {
867
990
  return new TextInputSubmenu(
868
991
  def.label,
869
992
  def.description,
870
- currentValue,
993
+ this.#formatTextInputEditValue(def.path, settings.get(def.path)),
871
994
  value => {
872
995
  // Empty string clears the setting; undefined-typed string settings
873
996
  // store "" which the browser.ts expandPath ignores (no-op fallback).
874
997
  this.#setSettingValue(def.path, value);
875
- this.callbacks.onChange(def.path, value);
876
- wrappedDone(value);
998
+ this.callbacks.onChange(def.path, settings.get(def.path));
999
+ wrappedDone(this.#formatTextInputValue(def.path, settings.get(def.path)));
877
1000
  },
878
1001
  () => wrappedDone(),
879
1002
  );
880
1003
  }
881
1004
 
1005
+ #createProviderLimitsInput(done: (value?: string) => void): Container {
1006
+ return new ProviderLimitsSubmenu(
1007
+ this.context.providers,
1008
+ value => {
1009
+ this.callbacks.onChange("providers.maxInFlightRequests", value);
1010
+ done(this.#formatProviderLimitsValue(value));
1011
+ },
1012
+ () => done(),
1013
+ this.context.requestRender,
1014
+ );
1015
+ }
1016
+
1017
+ #formatProviderLimitsValue(value: unknown): string {
1018
+ const limits = normalizeProviderMaxInFlightRequests(value);
1019
+ const entries = Object.entries(limits).sort(([a], [b]) => a.localeCompare(b));
1020
+ if (entries.length === 0) return "Unlimited";
1021
+ return entries.map(([provider, limit]) => `${provider}: ${limit}`).join(", ");
1022
+ }
1023
+
1024
+ #formatTextInputValue(path: SettingPath, value: unknown): string {
1025
+ if (path === "providers.maxInFlightRequests") return this.#formatProviderLimitsValue(value);
1026
+ return this.#formatTextInputEditValue(path, value);
1027
+ }
1028
+
1029
+ #formatTextInputEditValue(_path: SettingPath, value: unknown): string {
1030
+ if (value === undefined || value === null) return "";
1031
+ if (typeof value === "object") return JSON.stringify(value);
1032
+ return String(value);
1033
+ }
1034
+
882
1035
  /**
883
1036
  * Set a setting value, handling type conversion.
884
1037
  */
885
1038
  #setSettingValue(path: SettingPath, value: string): void {
886
- // Handle number conversions
887
1039
  const currentValue = settings.get(path);
1040
+ const schemaType = getType(path);
888
1041
  if (path === "compaction.thresholdPercent" && value === "default") {
889
1042
  settings.set(path, -1 as never);
890
1043
  } else if (path === "compaction.thresholdTokens" && value === "default") {
891
1044
  settings.set(path, -1 as never);
1045
+ } else if (schemaType === "record") {
1046
+ let parsed: unknown;
1047
+ try {
1048
+ parsed = JSON.parse(value || "{}");
1049
+ } catch {
1050
+ throw new Error(`Invalid record JSON for ${path}`);
1051
+ }
1052
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1053
+ throw new Error(`Invalid record JSON for ${path}`);
1054
+ }
1055
+ if (path === "providers.maxInFlightRequests") {
1056
+ parsed = validateProviderMaxInFlightRequests(parsed);
1057
+ }
1058
+ settings.set(path, parsed as never);
892
1059
  } else if (typeof currentValue === "number") {
893
1060
  settings.set(path, Number(value) as never);
894
1061
  } else if (typeof currentValue === "boolean") {
@@ -1,6 +1,7 @@
1
- import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
1
+ import { Container, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
2
2
  import { getSelectListTheme } from "../../modes/theme/theme";
3
3
  import { DynamicBorder } from "./dynamic-border";
4
+ import { routeSelectListMouseWithTopBorder } from "./select-list-mouse-routing";
4
5
 
5
6
  /**
6
7
  * Component that renders a show images selector with borders
@@ -42,4 +43,8 @@ export class ShowImagesSelectorComponent extends Container {
42
43
  getSelectList(): SelectList {
43
44
  return this.#selectList;
44
45
  }
46
+
47
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void {
48
+ routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
49
+ }
45
50
  }
@@ -9,6 +9,7 @@ import { settings } from "../../../config/settings";
9
9
  import type { AgentSession } from "../../../session/agent-session";
10
10
  import type { OAuthAccountIdentity } from "../../../session/auth-storage";
11
11
  import { limitMatchesActiveAccount } from "../../../slash-commands/helpers/active-oauth-account";
12
+ import { type ActiveRepoContext, resolveActiveRepoContextSync } from "../../../utils/active-repo-context";
12
13
  import * as git from "../../../utils/git";
13
14
  import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
14
15
  import { sanitizeStatusText } from "../../shared";
@@ -155,6 +156,12 @@ interface ContextUsageMemo {
155
156
  skillsRef: readonly any[] | undefined;
156
157
  }
157
158
 
159
+ interface ActiveRepoCache {
160
+ projectDir: string;
161
+ activeRepo: ActiveRepoContext | null;
162
+ effectiveGitCwd: string;
163
+ }
164
+
158
165
  const EMPTY_MESSAGES: readonly AgentMessage[] = [];
159
166
  const STATUS_USAGE_START_DELAY_MS = 0;
160
167
  const STATUS_USAGE_REFRESH_TIMEOUT_MS = 2_000;
@@ -169,6 +176,10 @@ function hasGitSegment(segments: readonly StatusLineSegmentId[]): boolean {
169
176
  function hasPrSegment(segments: readonly StatusLineSegmentId[]): boolean {
170
177
  return segments.includes("pr");
171
178
  }
179
+ function hasPathSegment(segments: readonly StatusLineSegmentId[]): boolean {
180
+ return segments.includes("path");
181
+ }
182
+
172
183
  function hasGitBackedSegment(segments: readonly StatusLineSegmentId[]): boolean {
173
184
  return hasGitSegment(segments) || hasPrSegment(segments);
174
185
  }
@@ -189,23 +200,27 @@ export class StatusLineComponent implements Component {
189
200
  #autoCompactEnabled: boolean = true;
190
201
  #hookStatuses: Map<string, string> = new Map();
191
202
  #subagentCount: number = 0;
203
+ #subagentHubHint: string | undefined;
192
204
  #sessionStartTime: number = Date.now();
193
205
  #planModeStatus: { enabled: boolean; paused: boolean } | null = null;
194
206
  #loopModeStatus: { enabled: boolean } | null = null;
195
207
  #goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
196
208
  #collabStatus: CollabStatus | null = null;
197
209
  #focusedAgentId: string | undefined;
210
+ #activeRepoCache: ActiveRepoCache | undefined;
198
211
 
199
212
  // Git status caching (1s TTL)
200
213
  #cachedGitStatus: { staged: number; unstaged: number; untracked: number } | null = null;
214
+ #cachedGitStatusCwd: string | undefined = undefined;
201
215
  #gitStatusLastFetch = 0;
202
- #gitStatusInFlight = false;
216
+ #gitStatusInFlightCwd: string | undefined = undefined;
203
217
 
204
218
  // PR lookup caching (invalidated on branch/repo context changes)
205
219
  #cachedPr: { number: number; url: string } | null | undefined = undefined;
206
220
  #cachedPrContext: PrCacheContext | undefined = undefined;
207
221
  #prLookupInFlight = false;
208
222
  #defaultBranch?: string;
223
+ #defaultBranchCwd: string | undefined = undefined;
209
224
  #lastTokensPerSecond: number | null = null;
210
225
  #lastTokensPerSecondTimestamp: number | null = null;
211
226
 
@@ -248,6 +263,18 @@ export class StatusLineComponent implements Component {
248
263
  );
249
264
  }
250
265
 
266
+ #resolveActiveRepoCache(): ActiveRepoCache {
267
+ const projectDir = getProjectDir();
268
+ if (this.#activeRepoCache?.projectDir === projectDir) {
269
+ return this.#activeRepoCache;
270
+ }
271
+
272
+ const activeRepo = resolveActiveRepoContextSync(projectDir);
273
+ const effectiveGitCwd = activeRepo?.repoRoot ?? projectDir;
274
+ this.#activeRepoCache = { projectDir, activeRepo, effectiveGitCwd };
275
+ return this.#activeRepoCache;
276
+ }
277
+
251
278
  /**
252
279
  * Re-point the status line at another session (focus proxy). Invalidate: model/context/usage all derive
253
280
  * from it. `focusedAgentId` is the focused subagent id while the view is proxied, undefined for main.
@@ -279,6 +306,12 @@ export class StatusLineComponent implements Component {
279
306
  this.#subagentCount = count;
280
307
  }
281
308
 
309
+ /** Hub key label shown in the forced running-subagents badge. */
310
+ setSubagentHubHint(hint: string | undefined): void {
311
+ const trimmed = hint?.trim();
312
+ this.#subagentHubHint = trimmed ? trimmed : undefined;
313
+ }
314
+
282
315
  /** Active subagent count as currently displayed (collab state mirroring). */
283
316
  get subagentCount(): number {
284
317
  return this.#subagentCount;
@@ -328,7 +361,8 @@ export class StatusLineComponent implements Component {
328
361
  return;
329
362
  }
330
363
 
331
- const repository = git.repo.resolveSync(getProjectDir());
364
+ const { effectiveGitCwd } = this.#resolveActiveRepoCache();
365
+ const repository = git.repo.resolveSync(effectiveGitCwd);
332
366
  if (!repository) return;
333
367
 
334
368
  const watchPath = git.repo.isReftableSync(repository)
@@ -383,17 +417,17 @@ export class StatusLineComponent implements Component {
383
417
  this.#cachedBranchCwd = undefined;
384
418
  this.#cachedPrContext = undefined;
385
419
  }
386
- #getCurrentBranch(): string | null {
420
+ #getCurrentBranch(effectiveGitCwd?: string): string | null {
387
421
  if (!this.#gitEnabled()) return null;
388
422
 
389
- const cwd = getProjectDir();
390
- if (this.#cachedBranch !== undefined && this.#cachedBranchCwd === cwd) {
423
+ const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
424
+ if (this.#cachedBranch !== undefined && this.#cachedBranchCwd === gitCwd) {
391
425
  return this.#cachedBranch;
392
426
  }
393
427
 
394
- const head = git.head.resolveSync(cwd);
428
+ const head = git.head.resolveSync(gitCwd);
395
429
  const gitHeadPath = head?.headPath ?? null;
396
- this.#cachedBranchCwd = cwd;
430
+ this.#cachedBranchCwd = gitCwd;
397
431
  this.#cachedBranchRepoId = gitHeadPath;
398
432
  if (!head) {
399
433
  this.#cachedBranch = null;
@@ -405,12 +439,18 @@ export class StatusLineComponent implements Component {
405
439
  return this.#cachedBranch ?? null;
406
440
  }
407
441
 
408
- #isDefaultBranch(branch: string): boolean {
442
+ #isDefaultBranch(branch: string, effectiveGitCwd: string): boolean {
443
+ if (this.#defaultBranchCwd !== effectiveGitCwd) {
444
+ this.#defaultBranch = undefined;
445
+ this.#defaultBranchCwd = effectiveGitCwd;
446
+ }
447
+
409
448
  if (this.#defaultBranch === undefined) {
410
449
  this.#defaultBranch = "main";
450
+ const lookupCwd = effectiveGitCwd;
411
451
  (async () => {
412
- const resolved = await git.branch.default(getProjectDir());
413
- if (this.#disposed) return;
452
+ const resolved = await git.branch.default(lookupCwd);
453
+ if (this.#disposed || this.#defaultBranchCwd !== lookupCwd) return;
414
454
  if (resolved) {
415
455
  this.#defaultBranch = resolved;
416
456
  if (this.#onBranchChange) {
@@ -422,32 +462,43 @@ export class StatusLineComponent implements Component {
422
462
  return branch === this.#defaultBranch;
423
463
  }
424
464
 
425
- #getGitStatus(): { staged: number; unstaged: number; untracked: number } | null {
465
+ #getGitStatus(effectiveGitCwd?: string): { staged: number; unstaged: number; untracked: number } | null {
426
466
  if (!this.#gitEnabled()) return null;
427
- if (this.#gitStatusInFlight || Date.now() - this.#gitStatusLastFetch < 1000) {
467
+
468
+ const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
469
+ if (this.#gitStatusInFlightCwd !== undefined) {
470
+ return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
471
+ }
472
+ if (this.#cachedGitStatusCwd === gitCwd && Date.now() - this.#gitStatusLastFetch < 1000) {
428
473
  return this.#cachedGitStatus;
429
474
  }
430
475
 
431
- this.#gitStatusInFlight = true;
476
+ this.#gitStatusInFlightCwd = gitCwd;
432
477
 
433
478
  (async () => {
479
+ let nextStatus: { staged: number; unstaged: number; untracked: number } | null = null;
434
480
  try {
435
- this.#cachedGitStatus = await git.status.summary(getProjectDir());
481
+ nextStatus = await git.status.summary(gitCwd);
436
482
  } catch {
437
- this.#cachedGitStatus = null;
483
+ nextStatus = null;
438
484
  } finally {
439
- this.#gitStatusLastFetch = Date.now();
440
- this.#gitStatusInFlight = false;
485
+ if (this.#gitStatusInFlightCwd === gitCwd) {
486
+ this.#cachedGitStatus = nextStatus;
487
+ this.#cachedGitStatusCwd = gitCwd;
488
+ this.#gitStatusLastFetch = Date.now();
489
+ this.#gitStatusInFlightCwd = undefined;
490
+ }
441
491
  }
442
492
  })();
443
493
 
444
- return this.#cachedGitStatus;
494
+ return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
445
495
  }
446
496
 
447
- #lookupPr(): { number: number; url: string } | null {
497
+ #lookupPr(effectiveGitCwd?: string): { number: number; url: string } | null {
448
498
  if (!this.#gitEnabled()) return null;
449
499
 
450
- const branch = this.#getCurrentBranch();
500
+ const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
501
+ const branch = this.#getCurrentBranch(gitCwd);
451
502
  const currentContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : null;
452
503
 
453
504
  if (canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
@@ -456,19 +507,26 @@ export class StatusLineComponent implements Component {
456
507
 
457
508
  const stalePr = this.#cachedPr;
458
509
 
459
- // Don't look up if no branch, detached HEAD, default branch, or already in flight
460
- if (!branch || branch === "detached" || this.#isDefaultBranch(branch) || this.#prLookupInFlight) {
510
+ if (!branch) {
511
+ this.#cachedPr = null;
512
+ this.#cachedPrContext = undefined;
513
+ return null;
514
+ }
515
+
516
+ // Don't look up if detached, default branch, or already in flight.
517
+ if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd) || this.#prLookupInFlight) {
461
518
  return stalePr ?? null;
462
519
  }
463
520
 
464
521
  this.#prLookupInFlight = true;
465
522
  const lookupContext = currentContext;
523
+ const lookupCwd = gitCwd;
466
524
 
467
525
  // Fire async lookup, keep stale value visible until resolved
468
526
  (async () => {
469
527
  // Helper: only write cache if branch/repo context hasn't changed since launch
470
528
  const setCachedPr = (value: { number: number; url: string } | null) => {
471
- const latestBranch = this.#getCurrentBranch();
529
+ const latestBranch = this.#getCurrentBranch(lookupCwd);
472
530
  const latestContext = latestBranch
473
531
  ? createPrCacheContext(latestBranch, this.#cachedBranchRepoId ?? null)
474
532
  : undefined;
@@ -479,7 +537,7 @@ export class StatusLineComponent implements Component {
479
537
  };
480
538
  try {
481
539
  // Requires `gh repo set-default` to be configured; fails gracefully if not
482
- const result = await $`gh pr view --json number,url`.quiet().nothrow();
540
+ const result = await $`gh pr view --json number,url`.cwd(lookupCwd).quiet().nothrow();
483
541
  if (this.#disposed) return;
484
542
  if (result.exitCode !== 0) {
485
543
  setCachedPr(null);
@@ -746,6 +804,7 @@ export class StatusLineComponent implements Component {
746
804
  #buildSegmentContext(
747
805
  width: number,
748
806
  segmentOptions: StatusLineSettings["segmentOptions"],
807
+ includePath: boolean,
749
808
  includeContext: boolean,
750
809
  includeGit: boolean,
751
810
  includePr: boolean,
@@ -788,13 +847,18 @@ export class StatusLineComponent implements Component {
788
847
  contextPercent = collabState.contextUsage.percent ?? contextPercent;
789
848
  }
790
849
 
791
- const gitBranch = includeGit || includePr ? this.#getCurrentBranch() : null;
792
- const gitStatus = includeGit ? this.#getGitStatus() : null;
793
- const gitPr = includePr ? this.#lookupPr() : null;
794
-
850
+ const shouldResolveActiveRepo = this.#gitEnabled() && (includePath || includeGit || includePr);
851
+ const projectDir = getProjectDir();
852
+ const activeRepoCache = shouldResolveActiveRepo
853
+ ? this.#resolveActiveRepoCache()
854
+ : { projectDir, activeRepo: null, effectiveGitCwd: projectDir };
855
+ const gitBranch = includeGit || includePr ? this.#getCurrentBranch(activeRepoCache.effectiveGitCwd) : null;
856
+ const gitStatus = includeGit ? this.#getGitStatus(activeRepoCache.effectiveGitCwd) : null;
857
+ const gitPr = includePr ? this.#lookupPr(activeRepoCache.effectiveGitCwd) : null;
795
858
  return {
796
859
  session: this.session,
797
860
  focusedAgentId: this.#focusedAgentId,
861
+ activeRepo: activeRepoCache.activeRepo,
798
862
  width,
799
863
  options: segmentOptions ?? {},
800
864
  planMode: this.#planModeStatus,
@@ -858,8 +922,17 @@ export class StatusLineComponent implements Component {
858
922
  };
859
923
  }
860
924
 
925
+ #subagentBadgeText(): string | undefined {
926
+ if (this.#subagentCount === 0) return undefined;
927
+ const noun = this.#subagentCount === 1 ? "agent" : "agents";
928
+ const hubHint = this.#subagentHubHint ? ` — ${this.#subagentHubHint} hub` : " — Agent Hub";
929
+ return theme.fg("statusLineSubagents", `${theme.icon.agents} ${this.#subagentCount} ${noun} running${hubHint}`);
930
+ }
931
+
861
932
  #buildStatusLine(width: number): string {
862
933
  const effectiveSettings = this.#resolveSettings();
934
+ const includePath =
935
+ hasPathSegment(effectiveSettings.leftSegments) || hasPathSegment(effectiveSettings.rightSegments);
863
936
  const includeContext =
864
937
  hasContextSegment(effectiveSettings.leftSegments) || hasContextSegment(effectiveSettings.rightSegments);
865
938
  const gitEnabled = this.#gitEnabled();
@@ -871,6 +944,7 @@ export class StatusLineComponent implements Component {
871
944
  const ctx = this.#buildSegmentContext(
872
945
  width,
873
946
  effectiveSettings.segmentOptions,
947
+ includePath,
874
948
  includeContext,
875
949
  includeGit,
876
950
  includePr,
@@ -888,11 +962,13 @@ export class StatusLineComponent implements Component {
888
962
  const transparentBg = bgAnsi === TRANSPARENT_BG_ANSI;
889
963
  const fgAnsi = theme.getFgAnsi("text");
890
964
  const sepAnsi = theme.getFgAnsi("statusLineSep");
965
+ const subagentBadge = this.#subagentBadgeText();
891
966
 
892
967
  // Collect visible segment contents
893
968
  const leftParts: string[] = [];
894
969
  const leftSegIds: StatusLineSegmentId[] = [];
895
970
  for (const segId of effectiveSettings.leftSegments) {
971
+ if (subagentBadge && segId === "subagents") continue;
896
972
  const rendered = renderSegment(segId, ctx);
897
973
  if (rendered.visible && rendered.content) {
898
974
  leftParts.push(rendered.content);
@@ -902,6 +978,7 @@ export class StatusLineComponent implements Component {
902
978
 
903
979
  const rightParts: string[] = [];
904
980
  for (const segId of effectiveSettings.rightSegments) {
981
+ if (subagentBadge && segId === "subagents") continue;
905
982
  const rendered = renderSegment(segId, ctx);
906
983
  if (rendered.visible && rendered.content) {
907
984
  rightParts.push(rendered.content);
@@ -912,6 +989,9 @@ export class StatusLineComponent implements Component {
912
989
  if (runningBackgroundJobs > 0) {
913
990
  rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${runningBackgroundJobs}`));
914
991
  }
992
+ if (subagentBadge) {
993
+ rightParts.unshift(subagentBadge);
994
+ }
915
995
  const topFillWidth = Math.max(0, width);
916
996
  const left = [...leftParts];
917
997
  const right = [...rightParts];