@oh-my-pi/pi-coding-agent 16.1.23 → 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 (254) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/cli.js +3817 -3735
  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/collab/display-name.d.ts +3 -0
  11. package/dist/types/collab/host.d.ts +0 -2
  12. package/dist/types/commands/worktree.d.ts +0 -3
  13. package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
  14. package/dist/types/config/model-discovery.d.ts +6 -1
  15. package/dist/types/config/model-registry.d.ts +6 -2
  16. package/dist/types/config/model-resolver.d.ts +12 -0
  17. package/dist/types/config/models-config-schema.d.ts +29 -2
  18. package/dist/types/config/models-config.d.ts +22 -1
  19. package/dist/types/config/settings-schema.d.ts +91 -21
  20. package/dist/types/config/settings.d.ts +2 -0
  21. package/dist/types/dap/config.d.ts +1 -1
  22. package/dist/types/edit/hashline/filesystem.d.ts +4 -2
  23. package/dist/types/edit/renderer.d.ts +4 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -17
  25. package/dist/types/extensibility/hooks/types.d.ts +10 -10
  26. package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
  27. package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
  28. package/dist/types/internal-urls/index.d.ts +1 -0
  29. package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
  30. package/dist/types/internal-urls/router.d.ts +1 -1
  31. package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
  32. package/dist/types/internal-urls/types.d.ts +19 -2
  33. package/dist/types/irc/bus.d.ts +6 -0
  34. package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
  35. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  36. package/dist/types/modes/components/move-overlay.d.ts +23 -0
  37. package/dist/types/modes/components/plugin-selector.d.ts +2 -1
  38. package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
  39. package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
  40. package/dist/types/modes/components/settings-defs.d.ts +4 -1
  41. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  42. package/dist/types/modes/components/show-images-selector.d.ts +2 -1
  43. package/dist/types/modes/components/status-line/component.d.ts +2 -0
  44. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  45. package/dist/types/modes/components/theme-selector.d.ts +2 -1
  46. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +10 -1
  48. package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
  49. package/dist/types/modes/interactive-mode.d.ts +10 -1
  50. package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
  51. package/dist/types/modes/running-subagent-badge.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +9 -1
  54. package/dist/types/sdk.d.ts +2 -2
  55. package/dist/types/session/agent-session.d.ts +2 -1
  56. package/dist/types/session/session-manager.d.ts +13 -0
  57. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  58. package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
  59. package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
  60. package/dist/types/ssh/connection-manager.d.ts +2 -0
  61. package/dist/types/ssh/file-transfer.d.ts +79 -0
  62. package/dist/types/ssh/utils.d.ts +6 -0
  63. package/dist/types/system-prompt.d.ts +3 -0
  64. package/dist/types/task/executor.d.ts +16 -0
  65. package/dist/types/tiny/text.d.ts +1 -1
  66. package/dist/types/tools/builtin-names.d.ts +5 -1
  67. package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
  68. package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
  69. package/dist/types/tools/index.d.ts +3 -3
  70. package/dist/types/tools/path-utils.d.ts +29 -0
  71. package/dist/types/tools/read.d.ts +2 -2
  72. package/dist/types/tools/render-utils.d.ts +8 -0
  73. package/dist/types/tools/renderers.d.ts +11 -0
  74. package/dist/types/tools/ssh.d.ts +2 -0
  75. package/dist/types/tools/todo.d.ts +0 -16
  76. package/dist/types/tools/write.d.ts +2 -2
  77. package/dist/types/utils/active-repo-context.d.ts +8 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/utils/markit-cache.d.ts +23 -0
  80. package/dist/types/utils/markit.d.ts +5 -1
  81. package/dist/types/utils/prompt-path.d.ts +1 -0
  82. package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
  83. package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
  84. package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
  85. package/dist/types/web/search/providers/xai.d.ts +13 -0
  86. package/dist/types/web/search/types.d.ts +18 -2
  87. package/package.json +30 -15
  88. package/scripts/build-binary.ts +9 -9
  89. package/scripts/bundle-dist.ts +2 -2
  90. package/src/advisor/__tests__/advisor.test.ts +40 -4
  91. package/src/advisor/__tests__/emission-guard.test.ts +147 -0
  92. package/src/advisor/advise-tool.ts +1 -1
  93. package/src/advisor/emission-guard.ts +172 -0
  94. package/src/advisor/index.ts +1 -0
  95. package/src/advisor/runtime.ts +11 -0
  96. package/src/advisor/watchdog.ts +12 -1
  97. package/src/cli/args.ts +5 -2
  98. package/src/cli/auth-broker-cli.ts +17 -0
  99. package/src/cli/config-cli.ts +4 -0
  100. package/src/cli/flag-tables.ts +7 -4
  101. package/src/cli/gallery-cli.ts +14 -2
  102. package/src/cli/gallery-fixtures/edit.ts +60 -0
  103. package/src/cli/gallery-fixtures/fs.ts +17 -17
  104. package/src/cli/gallery-fixtures/search.ts +4 -4
  105. package/src/cli/usage-cli.ts +20 -1
  106. package/src/cli/web-search-cli.ts +1 -1
  107. package/src/collab/display-name.ts +13 -0
  108. package/src/collab/guest.ts +5 -1
  109. package/src/collab/host.ts +2 -13
  110. package/src/commands/worktree.ts +6 -0
  111. package/src/config/inline-tool-descriptors-mode.ts +27 -0
  112. package/src/config/model-discovery.ts +77 -8
  113. package/src/config/model-registry.ts +89 -11
  114. package/src/config/model-resolver.ts +39 -1
  115. package/src/config/models-config-schema.ts +41 -5
  116. package/src/config/models-config.ts +4 -1
  117. package/src/config/settings-schema.ts +85 -27
  118. package/src/config/settings.ts +172 -1
  119. package/src/cursor.ts +1 -1
  120. package/src/dap/config.ts +152 -8
  121. package/src/dap/session.ts +1 -1
  122. package/src/discovery/helpers.ts +3 -1
  123. package/src/edit/hashline/diff.ts +14 -3
  124. package/src/edit/hashline/execute.ts +42 -6
  125. package/src/edit/hashline/filesystem.ts +38 -2
  126. package/src/edit/index.ts +1 -0
  127. package/src/edit/modes/patch.ts +11 -1
  128. package/src/edit/renderer.ts +140 -13
  129. package/src/eval/__tests__/agent-bridge.test.ts +101 -0
  130. package/src/eval/agent-bridge.ts +26 -3
  131. package/src/eval/js/tool-bridge.ts +2 -2
  132. package/src/export/html/index.ts +1 -1
  133. package/src/export/html/template.js +3 -1
  134. package/src/export/html/tool-views.generated.js +20 -20
  135. package/src/extensibility/extensions/types.ts +22 -22
  136. package/src/extensibility/hooks/types.ts +11 -11
  137. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +8 -3
  138. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -10
  139. package/src/extensibility/plugins/marketplace/manager.ts +22 -0
  140. package/src/extensibility/plugins/marketplace/types.ts +1 -0
  141. package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
  142. package/src/internal-urls/docs-index.generated.txt +2 -2
  143. package/src/internal-urls/docs-index.ts +2 -3
  144. package/src/internal-urls/index.ts +1 -0
  145. package/src/internal-urls/registry-helpers.ts +19 -4
  146. package/src/internal-urls/router.ts +5 -3
  147. package/src/internal-urls/ssh-protocol.ts +367 -0
  148. package/src/internal-urls/types.ts +19 -2
  149. package/src/irc/bus.ts +11 -3
  150. package/src/mcp/tool-bridge.ts +32 -2
  151. package/src/memories/index.ts +1 -1
  152. package/src/modes/acp/acp-event-mapper.ts +2 -2
  153. package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
  154. package/src/modes/components/agent-hub.ts +3 -0
  155. package/src/modes/components/agent-transcript-viewer.ts +9 -7
  156. package/src/modes/components/custom-editor.ts +90 -5
  157. package/src/modes/components/move-overlay.ts +282 -0
  158. package/src/modes/components/plan-review-overlay.ts +35 -35
  159. package/src/modes/components/plugin-selector.ts +6 -1
  160. package/src/modes/components/queue-mode-selector.ts +6 -1
  161. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  162. package/src/modes/components/session-selector.ts +11 -10
  163. package/src/modes/components/settings-defs.ts +14 -1
  164. package/src/modes/components/settings-selector.ts +196 -29
  165. package/src/modes/components/show-images-selector.ts +6 -1
  166. package/src/modes/components/status-line/component.ts +108 -28
  167. package/src/modes/components/status-line/segments.ts +5 -1
  168. package/src/modes/components/status-line/types.ts +2 -0
  169. package/src/modes/components/theme-selector.ts +6 -1
  170. package/src/modes/components/thinking-selector.ts +6 -1
  171. package/src/modes/components/tool-execution.ts +25 -9
  172. package/src/modes/components/tree-selector.ts +5 -5
  173. package/src/modes/controllers/command-controller.ts +140 -47
  174. package/src/modes/controllers/event-controller.ts +59 -3
  175. package/src/modes/controllers/input-controller.ts +68 -2
  176. package/src/modes/controllers/selector-controller.ts +5 -2
  177. package/src/modes/controllers/streaming-reveal.ts +17 -0
  178. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  179. package/src/modes/interactive-mode.ts +116 -50
  180. package/src/modes/internal-url-autocomplete.ts +17 -2
  181. package/src/modes/prompt-action-autocomplete.ts +3 -1
  182. package/src/modes/running-subagent-badge.ts +13 -0
  183. package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
  184. package/src/modes/setup-wizard/scenes/providers.ts +2 -1
  185. package/src/modes/setup-wizard/scenes/theme.ts +6 -12
  186. package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
  187. package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
  188. package/src/modes/theme/theme.ts +9 -1
  189. package/src/modes/types.ts +9 -1
  190. package/src/modes/utils/interactive-context-helpers.ts +1 -1
  191. package/src/priority.json +15 -0
  192. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  193. package/src/prompts/advisor/system.md +21 -7
  194. package/src/prompts/agents/designer.md +1 -1
  195. package/src/prompts/agents/explore.md +1 -1
  196. package/src/prompts/agents/librarian.md +2 -2
  197. package/src/prompts/agents/plan.md +2 -2
  198. package/src/prompts/agents/reviewer.md +1 -1
  199. package/src/prompts/agents/task.md +2 -2
  200. package/src/prompts/system/active-repo-context.md +4 -0
  201. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  202. package/src/prompts/system/plan-mode-active.md +3 -3
  203. package/src/prompts/system/project-prompt.md +2 -2
  204. package/src/prompts/system/system-prompt.md +6 -6
  205. package/src/prompts/tools/bash.md +2 -2
  206. package/src/prompts/tools/checkpoint.md +1 -1
  207. package/src/prompts/tools/{find.md → glob.md} +1 -1
  208. package/src/prompts/tools/{search.md → grep.md} +3 -3
  209. package/src/prompts/tools/read.md +4 -2
  210. package/src/sdk.ts +55 -25
  211. package/src/session/agent-session.ts +508 -132
  212. package/src/session/session-history-format.ts +2 -2
  213. package/src/session/session-manager.ts +46 -0
  214. package/src/slash-commands/builtin-registry.ts +138 -20
  215. package/src/slash-commands/helpers/usage-report.ts +23 -2
  216. package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
  217. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
  218. package/src/ssh/connection-manager.ts +12 -15
  219. package/src/ssh/file-transfer.ts +209 -0
  220. package/src/ssh/utils.ts +24 -0
  221. package/src/system-prompt.ts +56 -28
  222. package/src/task/executor.ts +96 -36
  223. package/src/task/index.ts +3 -3
  224. package/src/task/render.ts +14 -13
  225. package/src/task/worktree.ts +38 -1
  226. package/src/tiny/text.ts +49 -4
  227. package/src/tiny/worker.ts +3 -3
  228. package/src/tools/bash.ts +2 -2
  229. package/src/tools/builtin-names.ts +26 -2
  230. package/src/tools/{find.ts → glob.ts} +48 -42
  231. package/src/tools/{search.ts → grep.ts} +311 -129
  232. package/src/tools/index.ts +13 -14
  233. package/src/tools/irc.ts +6 -2
  234. package/src/tools/path-utils.ts +64 -1
  235. package/src/tools/read.ts +35 -6
  236. package/src/tools/render-utils.ts +14 -0
  237. package/src/tools/renderers.ts +15 -4
  238. package/src/tools/ssh.ts +17 -2
  239. package/src/tools/todo.ts +4 -26
  240. package/src/tools/write.ts +25 -9
  241. package/src/utils/active-repo-context.ts +143 -0
  242. package/src/utils/image-resize.ts +88 -7
  243. package/src/utils/lang-from-path.ts +3 -3
  244. package/src/utils/markit-cache.ts +166 -0
  245. package/src/utils/markit.ts +86 -18
  246. package/src/utils/prompt-path.ts +3 -0
  247. package/src/utils/title-generator.ts +1 -1
  248. package/src/web/search/provider.ts +54 -34
  249. package/src/web/search/providers/duckduckgo.ts +140 -0
  250. package/src/web/search/providers/firecrawl.ts +144 -0
  251. package/src/web/search/providers/tinyfish.ts +159 -0
  252. package/src/web/search/providers/xai.ts +292 -0
  253. package/src/web/search/providers/zai.ts +142 -56
  254. package/src/web/search/types.ts +6 -2
@@ -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];
@@ -202,7 +202,7 @@ const pathSegment: StatusLineSegment = {
202
202
  render(ctx) {
203
203
  const opts = ctx.options.path ?? {};
204
204
 
205
- const projectDir = getProjectDir();
205
+ const projectDir = ctx.activeRepo?.cwd ?? getProjectDir();
206
206
  const { scratch, relative } = classifyProjectDir(projectDir);
207
207
  let pwd = projectDir;
208
208
 
@@ -213,6 +213,7 @@ const pathSegment: StatusLineSegment = {
213
213
  pwd = stripDisplayRoot(pwd);
214
214
  }
215
215
  }
216
+ const repoSuffix = ctx.activeRepo ? ` ↳ ${ctx.activeRepo.relativeRepoRoot}` : "";
216
217
  if (opts.abbreviate !== false) {
217
218
  pwd = shortenPath(pwd);
218
219
  }
@@ -223,6 +224,9 @@ const pathSegment: StatusLineSegment = {
223
224
  const sliceLen = Math.max(0, maxLen - ellipsis.length);
224
225
  pwd = `${ellipsis}${pwd.slice(-sliceLen)}`;
225
226
  }
227
+ if (repoSuffix) {
228
+ pwd = `${pwd}${repoSuffix}`;
229
+ }
226
230
 
227
231
  const showScratchIcon = scratch && opts.stripWorkPrefix !== false;
228
232
  const icon = showScratchIcon ? theme.icon.scratchFolder : theme.icon.folder;
@@ -1,6 +1,7 @@
1
1
  import type { CollabSessionState } from "../../../collab/protocol";
2
2
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../../config/settings-schema";
3
3
  import type { AgentSession } from "../../../session/agent-session";
4
+ import type { ActiveRepoContext } from "../../../utils/active-repo-context";
4
5
 
5
6
  export type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle };
6
7
 
@@ -47,6 +48,7 @@ export interface SegmentContext {
47
48
  session: AgentSession;
48
49
  /** Focused subagent id while the view is proxied at its session, undefined otherwise. */
49
50
  focusedAgentId?: string | undefined;
51
+ activeRepo: ActiveRepoContext | null;
50
52
  width: number;
51
53
  options: StatusLineSegmentOptions;
52
54
  planMode: {
@@ -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 theme selector.
@@ -60,4 +61,8 @@ export class ThemeSelectorComponent extends Container {
60
61
  getSelectList(): SelectList {
61
62
  return this.#selectList;
62
63
  }
64
+
65
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void {
66
+ routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
67
+ }
63
68
  }
@@ -1,8 +1,9 @@
1
1
  import type { Effort } from "@oh-my-pi/pi-ai";
2
- import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
2
+ import { Container, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
3
3
  import { getSelectListTheme } from "../../modes/theme/theme";
4
4
  import { getThinkingLevelMetadata } from "../../thinking";
5
5
  import { DynamicBorder } from "./dynamic-border";
6
+ import { routeSelectListMouseWithTopBorder } from "./select-list-mouse-routing";
6
7
 
7
8
  /**
8
9
  * Component that renders a thinking level selector with borders
@@ -49,4 +50,8 @@ export class ThinkingSelectorComponent extends Container {
49
50
  getSelectList(): SelectList {
50
51
  return this.#selectList;
51
52
  }
53
+
54
+ routeMouse(event: SgrMouseEvent, line: number, col: number): void {
55
+ routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
56
+ }
52
57
  }
@@ -655,15 +655,31 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
655
655
  // `provisionalPendingPreview` describes only the PENDING call preview
656
656
  // (`renderCall`, before any result): the result render may re-anchor it
657
657
  // wholesale, so its rows must never commit. Once a (streaming partial)
658
- // result exists the result renderer is the live shape — its body is
659
- // top-anchored and grows append-only, and `deriveLiveCommitState` gates
660
- // per-row durability — so the block is commit-stable like any settled
661
- // stream. Gating the flag on the pending phase is what keeps a collapsed
662
- // streaming eval/bash/ssh whose box outgrows the viewport from stranding
663
- // its head: while commit-unstable its scrolled-off top committed nowhere
664
- // and repainted nowhere, so it read as truncated until ctrl+o (expanded)
665
- // flipped it stable.
666
- if (this.#result !== undefined) return true;
658
+ // result exists the result renderer is usually the live shape — its body
659
+ // is top-anchored and grows append-only, and `deriveLiveCommitState`
660
+ // gates per-row durability — so the block is commit-stable like any
661
+ // settled stream. Gating the flag on the pending phase is what keeps a
662
+ // collapsed streaming eval/bash/ssh whose box outgrows the viewport from
663
+ // stranding its head: while commit-unstable its scrolled-off top
664
+ // committed nowhere and repainted nowhere, so it read as truncated until
665
+ // ctrl+o (expanded) flipped it stable.
666
+ //
667
+ // Renderers whose partial-result chrome (header glyph, frame state)
668
+ // differs from the final result render set `provisionalPartialResult`
669
+ // to opt out of stream-commit while `isPartial` holds: the ratchet
670
+ // would otherwise promote the stable partial chrome to native scrollback
671
+ // after `STABLE_PREFIX_COMMIT_FRAMES` and leave it stranded above the
672
+ // final frame once the chrome flips. Once the result settles
673
+ // (`isPartial === false`) the block is commit-stable again.
674
+ if (this.#result !== undefined) {
675
+ if (this.#isPartial) {
676
+ const tool = this.#tool as { provisionalPartialResult?: boolean } | undefined;
677
+ const provisionalPartialResult =
678
+ tool?.provisionalPartialResult ?? toolRenderers[this.#toolName]?.provisionalPartialResult;
679
+ if (provisionalPartialResult) return false;
680
+ }
681
+ return true;
682
+ }
667
683
  const tool = this.#tool as { provisionalPendingPreview?: boolean | "collapsed" } | undefined;
668
684
  const provisionalPendingPreview =
669
685
  tool?.provisionalPendingPreview ?? toolRenderers[this.#toolName]?.provisionalPendingPreview;
@@ -15,8 +15,8 @@ import type { TreeFilterMode } from "../../config/settings-schema";
15
15
  import { theme } from "../../modes/theme/theme";
16
16
  import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
17
17
  import type { SessionTreeNode } from "../../session/session-entries";
18
+ import { toPathList } from "../../tools/grep";
18
19
  import { shortenPath } from "../../tools/render-utils";
19
- import { toPathList } from "../../tools/search";
20
20
  import { canonicalizeMessage } from "../../utils/thinking-display";
21
21
  import { DynamicBorder } from "./dynamic-border";
22
22
  import { centeredWindow, contentRowWidth, renderScrollableList } from "./selector-helpers";
@@ -740,7 +740,7 @@ class TreeList implements Component {
740
740
  .slice(0, 50);
741
741
  return `[bash: ${cmd}${rawCmd.length > 50 ? "..." : ""}]`;
742
742
  }
743
- case "search": {
743
+ case "grep": {
744
744
  const pattern = String(args.pattern || "");
745
745
  const searchPathsInput =
746
746
  typeof args.paths === "string" || Array.isArray(args.paths)
@@ -750,11 +750,11 @@ class TreeList implements Component {
750
750
  : undefined;
751
751
  const paths = toPathList(searchPathsInput);
752
752
  const scope = paths.length > 0 ? paths.join(", ") : ".";
753
- return `[search: /${pattern}/ in ${shortenPath(scope)}]`;
753
+ return `[grep: /${pattern}/ in ${shortenPath(scope)}]`;
754
754
  }
755
- case "find": {
755
+ case "glob": {
756
756
  const paths = Array.isArray(args.paths) ? args.paths.join(", ") : String(args.pattern || ".");
757
- return `[find: ${shortenPath(paths)}]`;
757
+ return `[glob: ${shortenPath(paths)}]`;
758
758
  }
759
759
  case "ls": {
760
760
  const path = shortenPath(String(args.path || "."));