@oh-my-pi/pi-coding-agent 17.1.8 → 17.2.1

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 (246) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-dj46zzrm.md} +110 -0
  3. package/dist/cli.js +10009 -9654
  4. package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
  5. package/dist/types/advisor/runtime.d.ts +8 -2
  6. package/dist/types/advisor/transcript-recorder.d.ts +15 -0
  7. package/dist/types/auto-thinking/classifier.d.ts +9 -2
  8. package/dist/types/cli/args.d.ts +2 -0
  9. package/dist/types/cli/session-picker.d.ts +14 -7
  10. package/dist/types/collab/guest.d.ts +21 -2
  11. package/dist/types/commands/launch.d.ts +6 -0
  12. package/dist/types/config/model-discovery.d.ts +12 -0
  13. package/dist/types/config/settings-schema.d.ts +73 -8
  14. package/dist/types/cursor-bridge-tools.d.ts +54 -0
  15. package/dist/types/cursor.d.ts +193 -8
  16. package/dist/types/edit/edit-clipboard.d.ts +19 -0
  17. package/dist/types/edit/hashline/diff.d.ts +9 -1
  18. package/dist/types/edit/index.d.ts +8 -1
  19. package/dist/types/edit/streaming.d.ts +8 -1
  20. package/dist/types/extensibility/extensions/runner.d.ts +19 -2
  21. package/dist/types/extensibility/extensions/types.d.ts +25 -1
  22. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +25 -0
  23. package/dist/types/internal-urls/index.d.ts +1 -0
  24. package/dist/types/internal-urls/local-protocol.d.ts +3 -2
  25. package/dist/types/internal-urls/security-protocol.d.ts +15 -0
  26. package/dist/types/internal-urls/types.d.ts +1 -1
  27. package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
  28. package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
  29. package/dist/types/launch/terminal-output-worker.d.ts +1 -0
  30. package/dist/types/lsp/client.d.ts +2 -0
  31. package/dist/types/main.d.ts +2 -0
  32. package/dist/types/mcp/manager.d.ts +33 -4
  33. package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
  34. package/dist/types/modes/components/read-tool-group.d.ts +12 -0
  35. package/dist/types/modes/components/session-selector.d.ts +6 -0
  36. package/dist/types/modes/components/status-line/component.d.ts +23 -0
  37. package/dist/types/modes/components/tool-execution.d.ts +16 -7
  38. package/dist/types/modes/components/usage-row.d.ts +2 -0
  39. package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
  40. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  41. package/dist/types/modes/interactive-mode.d.ts +4 -2
  42. package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
  43. package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
  44. package/dist/types/modes/theme/theme.d.ts +12 -1
  45. package/dist/types/modes/types.d.ts +2 -1
  46. package/dist/types/sdk.d.ts +18 -0
  47. package/dist/types/security/auth.d.ts +30 -0
  48. package/dist/types/security/cloud.d.ts +79 -0
  49. package/dist/types/security/comparison.d.ts +49 -0
  50. package/dist/types/security/contracts/ids.d.ts +15 -0
  51. package/dist/types/security/contracts/index.d.ts +4 -0
  52. package/dist/types/security/contracts/schemas.d.ts +660 -0
  53. package/dist/types/security/contracts/types.d.ts +234 -0
  54. package/dist/types/security/contracts/validation.d.ts +5 -0
  55. package/dist/types/security/coordinator.d.ts +100 -0
  56. package/dist/types/security/importers/codex-security.d.ts +7 -0
  57. package/dist/types/security/importers/index.d.ts +2 -0
  58. package/dist/types/security/importers/sarif.d.ts +9 -0
  59. package/dist/types/security/index.d.ts +13 -0
  60. package/dist/types/security/preflight.d.ts +61 -0
  61. package/dist/types/security/provenance.d.ts +24 -0
  62. package/dist/types/security/publication.d.ts +78 -0
  63. package/dist/types/security/remediation.d.ts +27 -0
  64. package/dist/types/security/resource-output.d.ts +8 -0
  65. package/dist/types/security/sarif.d.ts +2 -0
  66. package/dist/types/security/store.d.ts +40 -0
  67. package/dist/types/session/agent-session-events.d.ts +2 -0
  68. package/dist/types/session/agent-session-types.d.ts +36 -1
  69. package/dist/types/session/agent-session.d.ts +4 -2
  70. package/dist/types/session/claude-session-store.d.ts +13 -0
  71. package/dist/types/session/codex-session-store.d.ts +14 -0
  72. package/dist/types/session/credential-pin.d.ts +58 -0
  73. package/dist/types/session/foreign-session-import.d.ts +15 -0
  74. package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
  75. package/dist/types/session/foreign-session-store.d.ts +23 -0
  76. package/dist/types/session/session-advisors.d.ts +47 -2
  77. package/dist/types/session/session-entries.d.ts +19 -1
  78. package/dist/types/session/session-handoff.d.ts +4 -1
  79. package/dist/types/session/session-loader.d.ts +2 -0
  80. package/dist/types/session/session-maintenance.d.ts +1 -1
  81. package/dist/types/session/session-manager.d.ts +21 -0
  82. package/dist/types/session/session-tools.d.ts +25 -1
  83. package/dist/types/session/turn-recovery.d.ts +10 -4
  84. package/dist/types/slash-commands/helpers/security.d.ts +2 -0
  85. package/dist/types/system-prompt.d.ts +4 -1
  86. package/dist/types/task/executor.d.ts +14 -2
  87. package/dist/types/task/render.d.ts +7 -0
  88. package/dist/types/thinking.d.ts +18 -6
  89. package/dist/types/tools/acp-bridge.d.ts +28 -5
  90. package/dist/types/tools/builtin-names.d.ts +1 -1
  91. package/dist/types/tools/grep.d.ts +17 -1
  92. package/dist/types/tools/hub/launch.d.ts +5 -1
  93. package/dist/types/tools/index.d.ts +10 -2
  94. package/dist/types/tools/path-utils.d.ts +19 -0
  95. package/dist/types/tools/read.d.ts +2 -0
  96. package/dist/types/tools/security-scan.d.ts +96 -0
  97. package/dist/types/tools/xdev.d.ts +2 -1
  98. package/dist/types/utils/changelog.d.ts +19 -0
  99. package/dist/types/utils/git.d.ts +10 -0
  100. package/dist/types/utils/jj.d.ts +11 -4
  101. package/dist/types/web/search/types.d.ts +2 -2
  102. package/package.json +12 -12
  103. package/scripts/security-compare.ts +40 -0
  104. package/src/advisor/runtime.ts +138 -12
  105. package/src/advisor/transcript-recorder.ts +56 -0
  106. package/src/auto-thinking/classifier.ts +48 -10
  107. package/src/cli/args.ts +6 -0
  108. package/src/cli/flag-tables.ts +2 -0
  109. package/src/cli/gallery-fixtures/fs.ts +25 -0
  110. package/src/cli/session-picker.ts +34 -17
  111. package/src/cli/ttsr-cli.ts +19 -1
  112. package/src/cli.ts +9 -0
  113. package/src/collab/guest.ts +37 -4
  114. package/src/collab/host.ts +1 -0
  115. package/src/commands/launch.ts +6 -0
  116. package/src/config/model-discovery.ts +32 -5
  117. package/src/config/model-registry.ts +107 -25
  118. package/src/config/settings-schema.ts +77 -6
  119. package/src/config/settings.ts +25 -0
  120. package/src/cursor-bridge-tools.ts +81 -0
  121. package/src/cursor.ts +463 -12
  122. package/src/edit/edit-clipboard.ts +23 -0
  123. package/src/edit/hashline/diff.ts +49 -10
  124. package/src/edit/hashline/execute.ts +38 -6
  125. package/src/edit/hashline/filesystem.ts +27 -3
  126. package/src/edit/index.ts +12 -2
  127. package/src/edit/renderer.ts +4 -4
  128. package/src/edit/streaming.ts +17 -2
  129. package/src/eval/py/prelude.py +7 -3
  130. package/src/export/html/template.js +1 -1
  131. package/src/extensibility/extensions/runner.ts +70 -0
  132. package/src/extensibility/extensions/types.ts +30 -0
  133. package/src/extensibility/legacy-pi-coding-agent-shim.ts +65 -13
  134. package/src/internal-urls/index.ts +1 -0
  135. package/src/internal-urls/local-protocol.ts +3 -2
  136. package/src/internal-urls/mcp-protocol.ts +4 -1
  137. package/src/internal-urls/router.ts +4 -1
  138. package/src/internal-urls/security-protocol.ts +261 -0
  139. package/src/internal-urls/types.ts +1 -1
  140. package/src/launch/terminal-output-worker-client.ts +53 -0
  141. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  142. package/src/launch/terminal-output-worker.ts +23 -0
  143. package/src/lsp/client.ts +18 -3
  144. package/src/lsp/index.ts +7 -0
  145. package/src/lsp/render.ts +1 -1
  146. package/src/main.ts +127 -56
  147. package/src/mcp/manager.ts +120 -18
  148. package/src/modes/acp/acp-agent.ts +15 -9
  149. package/src/modes/components/chat-transcript-builder.ts +30 -11
  150. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  151. package/src/modes/components/read-tool-group.ts +159 -17
  152. package/src/modes/components/session-selector.ts +19 -3
  153. package/src/modes/components/status-line/component.ts +381 -54
  154. package/src/modes/components/tool-execution.ts +78 -31
  155. package/src/modes/components/usage-row.ts +10 -5
  156. package/src/modes/controllers/event-controller.ts +26 -4
  157. package/src/modes/controllers/mcp-command-controller.ts +68 -3
  158. package/src/modes/controllers/selector-controller.ts +94 -36
  159. package/src/modes/controllers/tan-command-controller.ts +14 -0
  160. package/src/modes/interactive-mode.ts +31 -17
  161. package/src/modes/rpc/host-uris.ts +6 -0
  162. package/src/modes/rpc/rpc-client.ts +18 -0
  163. package/src/modes/rpc/rpc-mode.ts +16 -1
  164. package/src/modes/rpc/rpc-types.ts +11 -0
  165. package/src/modes/theme/theme.ts +151 -6
  166. package/src/modes/types.ts +2 -1
  167. package/src/modes/utils/ui-helpers.ts +38 -21
  168. package/src/prompts/agents/security-reviewer.md +75 -0
  169. package/src/prompts/security/scan-coordinator.md +7 -0
  170. package/src/prompts/security/scan-request.md +21 -0
  171. package/src/prompts/security/validate-request.md +8 -0
  172. package/src/prompts/system/auto-thinking-difficulty.md +4 -2
  173. package/src/prompts/system/plan-mode-active.md +2 -2
  174. package/src/prompts/system/system-prompt.md +6 -0
  175. package/src/prompts/system/xdev-mount-notice.md +1 -1
  176. package/src/prompts/tools/bash.md +15 -17
  177. package/src/prompts/tools/checkpoint.md +1 -1
  178. package/src/prompts/tools/glob.md +7 -6
  179. package/src/prompts/tools/grep.md +7 -6
  180. package/src/prompts/tools/security-publish.md +1 -0
  181. package/src/prompts/tools/security-scan.md +1 -0
  182. package/src/sdk.ts +212 -21
  183. package/src/security/auth.ts +98 -0
  184. package/src/security/cloud.ts +686 -0
  185. package/src/security/comparison.ts +255 -0
  186. package/src/security/contracts/ids.ts +111 -0
  187. package/src/security/contracts/index.ts +4 -0
  188. package/src/security/contracts/schemas.ts +201 -0
  189. package/src/security/contracts/types.ts +254 -0
  190. package/src/security/contracts/validation.ts +65 -0
  191. package/src/security/coordinator.ts +708 -0
  192. package/src/security/importers/codex-security.ts +387 -0
  193. package/src/security/importers/index.ts +2 -0
  194. package/src/security/importers/sarif.ts +357 -0
  195. package/src/security/index.ts +13 -0
  196. package/src/security/preflight.ts +405 -0
  197. package/src/security/provenance.ts +106 -0
  198. package/src/security/publication.ts +326 -0
  199. package/src/security/remediation.ts +93 -0
  200. package/src/security/resource-output.ts +50 -0
  201. package/src/security/sarif.ts +78 -0
  202. package/src/security/store.ts +430 -0
  203. package/src/session/agent-session-events.ts +1 -0
  204. package/src/session/agent-session-types.ts +40 -1
  205. package/src/session/agent-session.ts +302 -175
  206. package/src/session/claude-session-store.ts +426 -0
  207. package/src/session/codex-session-store.ts +673 -0
  208. package/src/session/credential-pin.ts +93 -0
  209. package/src/session/foreign-session-import.ts +52 -0
  210. package/src/session/foreign-session-jsonl.ts +29 -0
  211. package/src/session/foreign-session-store.ts +26 -0
  212. package/src/session/model-controls.ts +11 -3
  213. package/src/session/session-advisors.ts +140 -16
  214. package/src/session/session-entries.ts +21 -1
  215. package/src/session/session-handoff.ts +20 -3
  216. package/src/session/session-loader.ts +25 -9
  217. package/src/session/session-maintenance.ts +59 -11
  218. package/src/session/session-manager.ts +61 -0
  219. package/src/session/session-tools.ts +107 -5
  220. package/src/session/settings-stream-fn.ts +1 -0
  221. package/src/session/turn-recovery.ts +97 -51
  222. package/src/slash-commands/builtin-registry.ts +152 -3
  223. package/src/slash-commands/helpers/security.ts +451 -0
  224. package/src/system-prompt.ts +7 -2
  225. package/src/task/agents.ts +2 -0
  226. package/src/task/executor.ts +20 -4
  227. package/src/task/render.ts +20 -4
  228. package/src/task/structured-subagent.ts +6 -4
  229. package/src/thinking.ts +29 -10
  230. package/src/tools/acp-bridge.ts +52 -8
  231. package/src/tools/browser.ts +5 -0
  232. package/src/tools/builtin-names.ts +1 -0
  233. package/src/tools/checkpoint.ts +0 -13
  234. package/src/tools/grep.ts +64 -8
  235. package/src/tools/hub/launch.ts +10 -15
  236. package/src/tools/index.ts +37 -5
  237. package/src/tools/path-utils.ts +91 -0
  238. package/src/tools/read.ts +3 -0
  239. package/src/tools/security-scan.ts +287 -0
  240. package/src/tools/write.ts +9 -3
  241. package/src/tools/xdev.ts +39 -14
  242. package/src/utils/changelog.ts +144 -4
  243. package/src/utils/git.ts +61 -14
  244. package/src/utils/jj.ts +19 -13
  245. package/src/web/search/providers/codex.ts +34 -0
  246. package/src/web/search/types.ts +1 -1
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
2
3
  import type { Component } from "@oh-my-pi/pi-tui";
3
4
  import { Container, Text } from "@oh-my-pi/pi-tui";
4
5
  import { InternalUrlRouter, XD_URL_PREFIX } from "../../internal-urls";
@@ -6,7 +7,9 @@ import { getLanguageFromPath, theme } from "../../modes/theme/theme";
6
7
  import { parseLineRanges, selectorLineRanges, splitPathAndSel } from "../../tools/path-utils";
7
8
  import { PREVIEW_LIMITS, shortenPath } from "../../tools/render-utils";
8
9
  import { fileHyperlink, renderCodeCell, tryResolveInternalUrlSync } from "../../tui";
10
+ import { canonicalizeMessage } from "../../utils/thinking-display";
9
11
  import type { ToolExecutionHandle } from "./tool-execution";
12
+ import { formatUsageRow } from "./usage-row";
10
13
 
11
14
  /**
12
15
  * Extract the read call's target path. `path` is the canonical arg; `file_path`
@@ -39,6 +42,33 @@ export function readArgsCollapseIntoGroup(args: unknown): boolean {
39
42
  return target.startsWith(XD_URL_PREFIX) || !InternalUrlRouter.instance().canHandle(target);
40
43
  }
41
44
 
45
+ /**
46
+ * Return the collapsed read calls that can own a turn's usage row. Mixed-tool
47
+ * turns and visible content after a read keep the standalone row so request
48
+ * metrics retain their transcript ordering.
49
+ */
50
+ export function groupedReadUsageCallIds(message: AssistantMessage): string[] | undefined {
51
+ const toolCallIds: string[] = [];
52
+ let sawToolCall = false;
53
+ for (const content of message.content) {
54
+ if (content.type === "toolCall") {
55
+ if (content.name !== "read" || !readArgsCollapseIntoGroup(content.arguments)) return undefined;
56
+ sawToolCall = true;
57
+ toolCallIds.push(content.id);
58
+ continue;
59
+ }
60
+ if (
61
+ sawToolCall &&
62
+ (content.type === "image" ||
63
+ (content.type === "text" && canonicalizeMessage(content.text)) ||
64
+ (content.type === "thinking" && canonicalizeMessage(content.thinking)))
65
+ ) {
66
+ return undefined;
67
+ }
68
+ }
69
+ return toolCallIds.length > 0 ? toolCallIds : undefined;
70
+ }
71
+
42
72
  type ReadRenderArgs = {
43
73
  path?: string;
44
74
  file_path?: string;
@@ -94,6 +124,14 @@ type ReadEntry = {
94
124
  codeLineNumbers?: Array<number | null>;
95
125
  };
96
126
 
127
+ type ReadUsageRow = {
128
+ toolCallIds: readonly string[];
129
+ usage: Usage;
130
+ durationMs?: number;
131
+ ttftMs?: number;
132
+ timestamp?: number;
133
+ };
134
+
97
135
  /** Number of code lines to show in collapsed preview mode */
98
136
  const COLLAPSED_PREVIEW_LINES = PREVIEW_LIMITS.OUTPUT_COLLAPSED;
99
137
 
@@ -290,6 +328,8 @@ function formatMergedSelectorParts(selectors: string[]): string {
290
328
 
291
329
  export class ReadToolGroupComponent extends Container implements ToolExecutionHandle {
292
330
  #entries = new Map<string, ReadEntry>();
331
+ #usageRows = new Map<string, ReadUsageRow>();
332
+ #usageBatchByToolCallId = new Map<string, string>();
293
333
  #text: Text;
294
334
  #expanded = false;
295
335
  #showContentPreview: boolean;
@@ -394,6 +434,33 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
394
434
  this.#updateDisplay();
395
435
  }
396
436
 
437
+ /**
438
+ * Nest one request's usage beneath the last visible read call from that
439
+ * request. Parallel reads share one row rather than duplicating request totals.
440
+ */
441
+ attachUsage(
442
+ toolCallIds: readonly string[],
443
+ usage: Usage,
444
+ durationMs?: number,
445
+ ttftMs?: number,
446
+ timestamp?: number,
447
+ ): boolean {
448
+ const attachedToolCallIds: string[] = [];
449
+ let anchorId: string | undefined;
450
+ for (const toolCallId of toolCallIds) {
451
+ if (!this.#entries.has(toolCallId)) continue;
452
+ attachedToolCallIds.push(toolCallId);
453
+ anchorId = toolCallId;
454
+ }
455
+ if (!anchorId) return false;
456
+ for (const toolCallId of attachedToolCallIds) {
457
+ this.#usageBatchByToolCallId.set(toolCallId, anchorId);
458
+ }
459
+ this.#usageRows.set(anchorId, { toolCallIds: attachedToolCallIds, usage, durationMs, ttftMs, timestamp });
460
+ this.#updateDisplay();
461
+ return true;
462
+ }
463
+
397
464
  setArgsComplete(_toolCallId?: string): void {
398
465
  this.#updateDisplay();
399
466
  }
@@ -427,13 +494,15 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
427
494
  if (!this.#shouldRenderPreviewRow(row)) {
428
495
  const statusSymbol = this.#formatStatus(this.#statusForTargets(row.targets));
429
496
  const pathDisplay = this.#formatRowPath(row);
430
- this.#text.setText(
431
- ` ${statusSymbol} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay}`.trimEnd(),
432
- );
497
+ const lines = [` ${statusSymbol} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay}`.trimEnd()];
498
+ const usageRows = this.#usageRowsBySummaryRow(displayRows).get(0) ?? [];
499
+ this.#appendUsageRows(lines, usageRows, " ");
500
+ this.#text.setText(lines.join("\n"));
433
501
  this.addChild(this.#text);
434
502
  }
435
503
  for (const entry of this.#previewEntriesForRow(row)) {
436
504
  this.#addContentPreview(entry);
505
+ this.#addPreviewUsage(entry);
437
506
  }
438
507
  return;
439
508
  }
@@ -443,8 +512,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
443
512
  const entriesWithoutPreview = entries.filter(entry => !this.#shouldRenderPreview(entry));
444
513
  const summaryTargets = this.#displayTargetsForEntries(entriesWithoutPreview);
445
514
  const rows = this.#buildSummaryRows(summaryTargets);
515
+ const usageRowsBySummaryRow = this.#usageRowsBySummaryRow(rows);
446
516
  for (const [index, row] of rows.entries()) {
447
- this.#appendSummaryRow(lines, row, index, rows.length);
517
+ this.#appendSummaryRow(lines, row, index, rows.length, usageRowsBySummaryRow.get(index) ?? []);
448
518
  }
449
519
 
450
520
  this.#text.setText(lines.join("\n"));
@@ -453,6 +523,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
453
523
  for (const entry of entries) {
454
524
  if (this.#shouldRenderPreview(entry)) {
455
525
  this.#addContentPreview(entry);
526
+ this.#addPreviewUsage(entry);
456
527
  }
457
528
  }
458
529
  }
@@ -480,27 +551,37 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
480
551
  }
481
552
 
482
553
  #buildSummaryRows(targets: ReadDisplayTarget[]): ReadSummaryRow[] {
483
- const selectorTargetsByBasePath = new Map<string, ReadDisplayTarget[]>();
554
+ const selectorTargetsByBasePathAndBatch = new Map<string, Map<string | undefined, ReadDisplayTarget[]>>();
484
555
  for (const target of targets) {
485
556
  if (!target.selector) continue;
486
- const existing = selectorTargetsByBasePath.get(target.basePath);
557
+ let targetsByBatch = selectorTargetsByBasePathAndBatch.get(target.basePath);
558
+ if (!targetsByBatch) {
559
+ targetsByBatch = new Map<string | undefined, ReadDisplayTarget[]>();
560
+ selectorTargetsByBasePathAndBatch.set(target.basePath, targetsByBatch);
561
+ }
562
+ const batchId = this.#usageBatchByToolCallId.get(target.entry.toolCallId);
563
+ const existing = targetsByBatch.get(batchId);
487
564
  if (existing) existing.push(target);
488
- else selectorTargetsByBasePath.set(target.basePath, [target]);
565
+ else targetsByBatch.set(batchId, [target]);
489
566
  }
490
567
 
491
- const mergeableBasePaths = new Set<string>();
492
- for (const [basePath, baseTargets] of selectorTargetsByBasePath) {
493
- if (basePath && baseTargets.length > 1) {
494
- mergeableBasePaths.add(basePath);
568
+ const mergedTargetsByTarget = new Map<ReadDisplayTarget, ReadDisplayTarget[]>();
569
+ for (const [basePath, targetsByBatch] of selectorTargetsByBasePathAndBatch) {
570
+ if (!basePath) continue;
571
+ for (const groupedTargets of targetsByBatch.values()) {
572
+ if (groupedTargets.length <= 1) continue;
573
+ for (const target of groupedTargets) {
574
+ mergedTargetsByTarget.set(target, groupedTargets);
575
+ }
495
576
  }
496
577
  }
497
578
 
498
- const emittedMergedRows = new Set<string>();
579
+ const emittedMergedTargets = new Set<ReadDisplayTarget[]>();
499
580
  const rows: ReadSummaryRow[] = [];
500
581
  for (const target of targets) {
501
- if (target.selector && mergeableBasePaths.has(target.basePath)) {
502
- if (!emittedMergedRows.has(target.basePath)) {
503
- const mergedTargets = selectorTargetsByBasePath.get(target.basePath) ?? [target];
582
+ const mergedTargets = mergedTargetsByTarget.get(target);
583
+ if (mergedTargets) {
584
+ if (!emittedMergedTargets.has(mergedTargets)) {
504
585
  rows.push({
505
586
  targetPath: `${target.basePath}:${formatMergedSelectorParts(
506
587
  mergedTargets
@@ -510,7 +591,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
510
591
  basePath: target.basePath,
511
592
  targets: mergedTargets,
512
593
  });
513
- emittedMergedRows.add(target.basePath);
594
+ emittedMergedTargets.add(mergedTargets);
514
595
  }
515
596
  continue;
516
597
  }
@@ -519,9 +600,58 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
519
600
  return rows;
520
601
  }
521
602
 
522
- #appendSummaryRow(lines: string[], row: ReadSummaryRow, index: number, total: number): void {
603
+ #appendSummaryRow(
604
+ lines: string[],
605
+ row: ReadSummaryRow,
606
+ index: number,
607
+ total: number,
608
+ usageRows: ReadUsageRow[],
609
+ ): void {
523
610
  const connector = index === total - 1 ? theme.tree.last : theme.tree.branch;
524
611
  lines.push(` ${theme.fg("dim", connector)} ${this.#formatRow(row)}`.trimEnd());
612
+
613
+ const connectorWidth = Bun.stringWidth(connector);
614
+ const continuation =
615
+ index === total - 1
616
+ ? " ".repeat(connectorWidth)
617
+ : `${theme.tree.vertical}${" ".repeat(Math.max(0, connectorWidth - Bun.stringWidth(theme.tree.vertical)))}`;
618
+ this.#appendUsageRows(lines, usageRows, ` ${continuation} `);
619
+ }
620
+
621
+ #usageRowsBySummaryRow(rows: ReadSummaryRow[]): Map<number, ReadUsageRow[]> {
622
+ const lastRowIndexByToolCallId = new Map<string, number>();
623
+ for (const [index, row] of rows.entries()) {
624
+ for (const target of row.targets) {
625
+ lastRowIndexByToolCallId.set(target.entry.toolCallId, index);
626
+ }
627
+ }
628
+
629
+ const usageRowsByIndex = new Map<number, ReadUsageRow[]>();
630
+ for (const usageRow of this.#usageRows.values()) {
631
+ let lastRowIndex: number | undefined;
632
+ for (const toolCallId of usageRow.toolCallIds) {
633
+ const index = lastRowIndexByToolCallId.get(toolCallId);
634
+ if (index !== undefined && (lastRowIndex === undefined || index > lastRowIndex)) {
635
+ lastRowIndex = index;
636
+ }
637
+ }
638
+ if (lastRowIndex === undefined) continue;
639
+ const usageRows = usageRowsByIndex.get(lastRowIndex);
640
+ if (usageRows) usageRows.push(usageRow);
641
+ else usageRowsByIndex.set(lastRowIndex, [usageRow]);
642
+ }
643
+ return usageRowsByIndex;
644
+ }
645
+
646
+ #appendUsageRows(lines: string[], usageRows: ReadUsageRow[], prefix: string): void {
647
+ for (const usageRow of usageRows) {
648
+ lines.push(
649
+ theme.fg(
650
+ "dim",
651
+ `${prefix}${formatUsageRow(usageRow.usage, usageRow.durationMs, usageRow.ttftMs, usageRow.timestamp)}`,
652
+ ),
653
+ );
654
+ }
525
655
  }
526
656
 
527
657
  #formatRow(row: ReadSummaryRow): string {
@@ -659,6 +789,18 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
659
789
  this.addChild(component);
660
790
  }
661
791
 
792
+ #addPreviewUsage(entry: ReadEntry): void {
793
+ const usageRow = this.#usageRows.get(entry.toolCallId);
794
+ if (!usageRow) return;
795
+ this.addChild(
796
+ new Text(
797
+ theme.fg("dim", formatUsageRow(usageRow.usage, usageRow.durationMs, usageRow.ttftMs, usageRow.timestamp)),
798
+ 3,
799
+ 0,
800
+ ),
801
+ );
802
+ }
803
+
662
804
  #shouldRenderPreview(entry: ReadEntry): boolean {
663
805
  return this.#showContentPreview && entry.contentText !== undefined;
664
806
  }
@@ -724,6 +724,12 @@ export interface SessionSelectorOptions {
724
724
  loadAllSessions?: () => Promise<SessionInfo[]>;
725
725
  /** Preloaded all-projects list; cached so the first Tab toggle is instant. */
726
726
  allSessions?: SessionInfo[];
727
+ /** Picker heading; defaults to "Resume Session". */
728
+ title?: string;
729
+ /** Fixed scope label, or false to omit the scope suffix. */
730
+ scopeLabel?: string | false;
731
+ /** Show each session's working directory in the list. */
732
+ showCwd?: boolean;
727
733
  /**
728
734
  * Reads the live terminal height so the visible window fits the viewport.
729
735
  * Omitted only in tests; defaults to a conservative 24 rows.
@@ -773,6 +779,8 @@ export class SessionSelectorComponent extends Container {
773
779
  readonly #getTerminalRows: () => number;
774
780
  readonly #fillHeight: boolean;
775
781
  readonly #bottomBorder = new DynamicBorder();
782
+ readonly #title: string;
783
+ readonly #scopeLabel: string | false | undefined;
776
784
 
777
785
  constructor(
778
786
  sessions: SessionInfo[],
@@ -790,6 +798,8 @@ export class SessionSelectorComponent extends Container {
790
798
  this.#globalSessions = options.allSessions ?? null;
791
799
  this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
792
800
  this.#fillHeight = options.fillHeight ?? false;
801
+ this.#title = options.title ?? "Resume Session";
802
+ this.#scopeLabel = options.scopeLabel;
793
803
  // Add header
794
804
  this.addChild(new Spacer(1));
795
805
  this.#headerText = new Text(this.#headerLabel(), 1, 0);
@@ -801,7 +811,12 @@ export class SessionSelectorComponent extends Container {
801
811
  // Create session list in folder scope; the empty-state hint invites the
802
812
  // user to Tab into all-projects rather than silently surfacing other
803
813
  // projects' history (issue #3099).
804
- this.#sessionList = new SessionList(sessions, false, options.historyMatcher, options.getTerminalRows);
814
+ this.#sessionList = new SessionList(
815
+ sessions,
816
+ options.showCwd ?? false,
817
+ options.historyMatcher,
818
+ options.getTerminalRows,
819
+ );
805
820
  // Every exit path cancels the list's pending history merge, so a stale
806
821
  // debounce timer can never run its SQLite lookup after the picker closed.
807
822
  this.#sessionList.onSelect = session => {
@@ -831,8 +846,9 @@ export class SessionSelectorComponent extends Container {
831
846
  }
832
847
 
833
848
  #headerLabel(): string {
834
- const scopeLabel = this.#scope === "all" ? "all projects" : "current folder";
835
- return `${theme.bold("Resume Session")} ${theme.fg("muted", `(${scopeLabel})`)}`;
849
+ if (this.#scopeLabel === false) return theme.bold(this.#title);
850
+ const scopeLabel = this.#scopeLabel ?? (this.#scope === "all" ? "all projects" : "current folder");
851
+ return `${theme.bold(this.#title)} ${theme.fg("muted", `(${scopeLabel})`)}`;
836
852
  }
837
853
 
838
854
  /**