@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.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 (266) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +15 -1
  4. package/dist/types/cli/gallery-cli.d.ts +43 -0
  5. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  6. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  7. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  8. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  9. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  10. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  11. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  12. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  13. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  14. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  15. package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
  16. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  17. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  18. package/dist/types/commands/gallery.d.ts +47 -0
  19. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  20. package/dist/types/commit/analysis/summary.d.ts +2 -2
  21. package/dist/types/commit/changelog/generate.d.ts +2 -2
  22. package/dist/types/commit/changelog/index.d.ts +2 -2
  23. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  26. package/dist/types/commit/model-selection.d.ts +10 -4
  27. package/dist/types/config/api-key-resolver.d.ts +34 -0
  28. package/dist/types/config/keybindings.d.ts +6 -1
  29. package/dist/types/config/model-id-affixes.d.ts +2 -0
  30. package/dist/types/config/model-registry.d.ts +25 -2
  31. package/dist/types/config/settings-schema.d.ts +41 -6
  32. package/dist/types/dap/config.d.ts +14 -1
  33. package/dist/types/dap/types.d.ts +10 -0
  34. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  35. package/dist/types/lsp/types.d.ts +10 -0
  36. package/dist/types/lsp/utils.d.ts +3 -2
  37. package/dist/types/main.d.ts +3 -2
  38. package/dist/types/memory-backend/index.d.ts +2 -1
  39. package/dist/types/memory-backend/resolve.d.ts +1 -1
  40. package/dist/types/memory-backend/types.d.ts +1 -1
  41. package/dist/types/modes/components/chat-block.d.ts +64 -0
  42. package/dist/types/modes/components/custom-editor.d.ts +5 -1
  43. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  44. package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
  45. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  47. package/dist/types/modes/components/tool-execution.d.ts +18 -0
  48. package/dist/types/modes/components/transcript-container.d.ts +11 -0
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/controllers/event-controller.d.ts +0 -1
  51. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  52. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  53. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  54. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  55. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  56. package/dist/types/modes/index.d.ts +5 -4
  57. package/dist/types/modes/interactive-mode.d.ts +16 -6
  58. package/dist/types/modes/setup-version.d.ts +11 -0
  59. package/dist/types/modes/setup-wizard/index.d.ts +2 -1
  60. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
  61. package/dist/types/modes/theme/theme.d.ts +1 -1
  62. package/dist/types/modes/types.d.ts +19 -6
  63. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  64. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  65. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  66. package/dist/types/sdk.d.ts +3 -1
  67. package/dist/types/session/agent-session.d.ts +21 -0
  68. package/dist/types/session/messages.d.ts +12 -0
  69. package/dist/types/session/session-manager.d.ts +3 -1
  70. package/dist/types/slash-commands/types.d.ts +4 -6
  71. package/dist/types/task/executor.d.ts +14 -0
  72. package/dist/types/task/index.d.ts +1 -0
  73. package/dist/types/task/render.d.ts +3 -2
  74. package/dist/types/telemetry-export.d.ts +1 -1
  75. package/dist/types/tools/archive-reader.d.ts +5 -0
  76. package/dist/types/tools/ast-edit.d.ts +3 -0
  77. package/dist/types/tools/ast-grep.d.ts +3 -0
  78. package/dist/types/tools/bash.d.ts +1 -0
  79. package/dist/types/tools/eval-render.d.ts +1 -8
  80. package/dist/types/tools/fetch.d.ts +15 -7
  81. package/dist/types/tools/find.d.ts +8 -4
  82. package/dist/types/tools/grouped-file-output.d.ts +95 -12
  83. package/dist/types/tools/memory-render.d.ts +4 -1
  84. package/dist/types/tools/plan-mode-guard.d.ts +8 -9
  85. package/dist/types/tools/render-utils.d.ts +13 -9
  86. package/dist/types/tools/renderers.d.ts +16 -2
  87. package/dist/types/tools/search.d.ts +5 -1
  88. package/dist/types/tools/sqlite-reader.d.ts +1 -0
  89. package/dist/types/tools/todo.d.ts +3 -2
  90. package/dist/types/tools/write.d.ts +5 -0
  91. package/dist/types/tui/output-block.d.ts +16 -4
  92. package/dist/types/tui/status-line.d.ts +3 -0
  93. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  94. package/dist/types/web/scrapers/github.d.ts +22 -0
  95. package/dist/types/web/search/providers/kimi.d.ts +1 -1
  96. package/dist/types/web/search/providers/perplexity.d.ts +8 -1
  97. package/dist/types/web/search/types.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/scripts/dev-launch +42 -0
  100. package/scripts/dev-launch-preload.ts +19 -0
  101. package/src/auto-thinking/classifier.ts +5 -1
  102. package/src/cli/args.ts +2 -2
  103. package/src/cli/dry-balance-cli.ts +52 -17
  104. package/src/cli/gallery-cli.ts +226 -0
  105. package/src/cli/gallery-fixtures/agentic.ts +292 -0
  106. package/src/cli/gallery-fixtures/codeintel.ts +188 -0
  107. package/src/cli/gallery-fixtures/edit.ts +194 -0
  108. package/src/cli/gallery-fixtures/fs.ts +153 -0
  109. package/src/cli/gallery-fixtures/index.ts +40 -0
  110. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  111. package/src/cli/gallery-fixtures/memory.ts +81 -0
  112. package/src/cli/gallery-fixtures/misc.ts +250 -0
  113. package/src/cli/gallery-fixtures/search.ts +213 -0
  114. package/src/cli/gallery-fixtures/shell.ts +167 -0
  115. package/src/cli/gallery-fixtures/types.ts +41 -0
  116. package/src/cli/gallery-fixtures/web.ts +158 -0
  117. package/src/cli/gallery-screenshot.ts +279 -0
  118. package/src/cli-commands.ts +1 -0
  119. package/src/commands/gallery.ts +52 -0
  120. package/src/commands/launch.ts +1 -1
  121. package/src/commit/analysis/conventional.ts +2 -2
  122. package/src/commit/analysis/summary.ts +2 -2
  123. package/src/commit/changelog/generate.ts +2 -2
  124. package/src/commit/changelog/index.ts +2 -2
  125. package/src/commit/map-reduce/index.ts +3 -3
  126. package/src/commit/map-reduce/map-phase.ts +2 -2
  127. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  128. package/src/commit/model-selection.ts +33 -9
  129. package/src/commit/pipeline.ts +4 -4
  130. package/src/config/api-key-resolver.ts +58 -0
  131. package/src/config/keybindings.ts +15 -6
  132. package/src/config/model-equivalence.ts +35 -12
  133. package/src/config/model-id-affixes.ts +39 -22
  134. package/src/config/model-registry.ts +41 -18
  135. package/src/config/settings-schema.ts +28 -5
  136. package/src/config/settings.ts +31 -2
  137. package/src/dap/client.ts +14 -16
  138. package/src/dap/config.ts +41 -2
  139. package/src/dap/defaults.json +1 -0
  140. package/src/dap/session.ts +1 -0
  141. package/src/dap/types.ts +10 -0
  142. package/src/debug/index.ts +40 -54
  143. package/src/edit/renderer.ts +111 -119
  144. package/src/eval/__tests__/agent-bridge.test.ts +75 -32
  145. package/src/eval/__tests__/llm-bridge.test.ts +90 -31
  146. package/src/eval/agent-bridge.ts +34 -7
  147. package/src/eval/llm-bridge.ts +8 -3
  148. package/src/extensibility/extensions/runner.ts +1 -0
  149. package/src/extensibility/plugins/doctor.ts +0 -1
  150. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  151. package/src/goals/tools/goal-tool.ts +37 -27
  152. package/src/internal-urls/docs-index.generated.ts +10 -10
  153. package/src/lsp/client.ts +104 -55
  154. package/src/lsp/types.ts +10 -0
  155. package/src/lsp/utils.ts +3 -2
  156. package/src/main.ts +53 -56
  157. package/src/memories/index.ts +12 -5
  158. package/src/memory-backend/index.ts +13 -1
  159. package/src/memory-backend/resolve.ts +3 -5
  160. package/src/memory-backend/types.ts +1 -1
  161. package/src/mnemopi/backend.ts +5 -1
  162. package/src/modes/acp/acp-agent.ts +33 -26
  163. package/src/modes/components/assistant-message.ts +2 -9
  164. package/src/modes/components/chat-block.ts +111 -0
  165. package/src/modes/components/copy-selector.ts +1 -44
  166. package/src/modes/components/custom-editor.ts +33 -1
  167. package/src/modes/components/custom-message.ts +1 -3
  168. package/src/modes/components/execution-shared.ts +1 -2
  169. package/src/modes/components/hook-message.ts +1 -3
  170. package/src/modes/components/overlay-box.ts +108 -0
  171. package/src/modes/components/plan-review-overlay.ts +799 -0
  172. package/src/modes/components/plan-toc.ts +138 -0
  173. package/src/modes/components/read-tool-group.ts +20 -4
  174. package/src/modes/components/skill-message.ts +0 -1
  175. package/src/modes/components/status-line.ts +3 -5
  176. package/src/modes/components/tips.txt +1 -0
  177. package/src/modes/components/todo-reminder.ts +0 -2
  178. package/src/modes/components/tool-execution.ts +115 -90
  179. package/src/modes/components/transcript-container.ts +84 -24
  180. package/src/modes/components/user-message.ts +1 -2
  181. package/src/modes/controllers/command-controller-shared.ts +7 -6
  182. package/src/modes/controllers/command-controller.ts +70 -57
  183. package/src/modes/controllers/event-controller.ts +41 -40
  184. package/src/modes/controllers/extension-ui-controller.ts +10 -73
  185. package/src/modes/controllers/input-controller.ts +135 -122
  186. package/src/modes/controllers/mcp-command-controller.ts +69 -60
  187. package/src/modes/controllers/selector-controller.ts +25 -27
  188. package/src/modes/controllers/streaming-reveal.ts +212 -0
  189. package/src/modes/controllers/tan-command-controller.ts +173 -0
  190. package/src/modes/index.ts +5 -4
  191. package/src/modes/interactive-mode.ts +171 -82
  192. package/src/modes/setup-version.ts +11 -0
  193. package/src/modes/setup-wizard/index.ts +3 -2
  194. package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
  195. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  196. package/src/modes/theme/theme-schema.json +1 -1
  197. package/src/modes/theme/theme.ts +8 -4
  198. package/src/modes/types.ts +19 -8
  199. package/src/modes/utils/context-usage.ts +10 -6
  200. package/src/modes/utils/copy-targets.ts +133 -27
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  202. package/src/modes/utils/ui-helpers.ts +44 -46
  203. package/src/plan-mode/approved-plan.ts +66 -43
  204. package/src/plan-mode/plan-protection.ts +4 -4
  205. package/src/prompts/system/background-tan-dispatch.md +8 -0
  206. package/src/prompts/system/plan-mode-active.md +67 -58
  207. package/src/prompts/system/plan-mode-approved.md +1 -1
  208. package/src/sdk.ts +32 -60
  209. package/src/session/agent-session.ts +89 -13
  210. package/src/session/messages.ts +26 -0
  211. package/src/session/session-manager.ts +13 -5
  212. package/src/slash-commands/builtin-registry.ts +37 -10
  213. package/src/slash-commands/helpers/usage-report.ts +2 -0
  214. package/src/slash-commands/types.ts +4 -6
  215. package/src/task/executor.ts +25 -4
  216. package/src/task/index.ts +4 -0
  217. package/src/task/render.ts +212 -148
  218. package/src/telemetry-export.ts +25 -7
  219. package/src/tools/archive-reader.ts +64 -0
  220. package/src/tools/ask.ts +119 -164
  221. package/src/tools/ast-edit.ts +98 -71
  222. package/src/tools/ast-grep.ts +37 -43
  223. package/src/tools/bash.ts +50 -6
  224. package/src/tools/debug.ts +20 -8
  225. package/src/tools/eval-backends.ts +6 -17
  226. package/src/tools/eval-render.ts +21 -18
  227. package/src/tools/eval.ts +5 -4
  228. package/src/tools/fetch.ts +391 -91
  229. package/src/tools/find.ts +44 -30
  230. package/src/tools/gh-renderer.ts +81 -42
  231. package/src/tools/grouped-file-output.ts +272 -48
  232. package/src/tools/image-gen.ts +150 -103
  233. package/src/tools/inspect-image-renderer.ts +63 -41
  234. package/src/tools/inspect-image.ts +8 -1
  235. package/src/tools/job.ts +3 -4
  236. package/src/tools/memory-render.ts +4 -1
  237. package/src/tools/plan-mode-guard.ts +21 -39
  238. package/src/tools/read.ts +23 -16
  239. package/src/tools/render-utils.ts +38 -40
  240. package/src/tools/renderers.ts +16 -1
  241. package/src/tools/report-tool-issue.ts +1 -1
  242. package/src/tools/resolve.ts +14 -0
  243. package/src/tools/search-tool-bm25.ts +36 -23
  244. package/src/tools/search.ts +189 -95
  245. package/src/tools/sqlite-reader.ts +9 -12
  246. package/src/tools/todo.ts +138 -59
  247. package/src/tools/write.ts +100 -60
  248. package/src/tui/output-block.ts +60 -13
  249. package/src/tui/status-line.ts +5 -1
  250. package/src/utils/commit-message-generator.ts +9 -1
  251. package/src/utils/enhanced-paste.ts +202 -0
  252. package/src/utils/title-generator.ts +2 -1
  253. package/src/web/scrapers/github.ts +255 -3
  254. package/src/web/scrapers/youtube.ts +3 -2
  255. package/src/web/search/providers/anthropic.ts +25 -19
  256. package/src/web/search/providers/exa.ts +11 -3
  257. package/src/web/search/providers/kimi.ts +28 -17
  258. package/src/web/search/providers/parallel.ts +35 -24
  259. package/src/web/search/providers/perplexity.ts +199 -51
  260. package/src/web/search/providers/synthetic.ts +8 -6
  261. package/src/web/search/providers/tavily.ts +9 -8
  262. package/src/web/search/providers/zai.ts +8 -6
  263. package/src/web/search/render.ts +39 -54
  264. package/src/web/search/types.ts +5 -1
  265. package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
  266. package/src/eval/__tests__/shared-executors.test.ts +0 -609
@@ -39,6 +39,26 @@ function isBlockAppendOnly(child: Component): boolean {
39
39
  return fn ? fn.call(child) : false;
40
40
  }
41
41
 
42
+ // A "plain blank" row is empty or whitespace-only with no ANSI bytes. It marks
43
+ // separation padding (a `Spacer`, or a no-background `paddingY` row) as opposed
44
+ // to a background-colored padding row, whose escape sequences contain `\S` and
45
+ // are therefore preserved as part of a block's visual design.
46
+ const NON_WHITESPACE = /\S/;
47
+ function isPlainBlank(line: string): boolean {
48
+ return !NON_WHITESPACE.test(line);
49
+ }
50
+
51
+ // Strip leading/trailing plain-blank rows so each block contributes only its
52
+ // visible body; the container owns the gaps between blocks. Returns the input
53
+ // array unchanged when there is nothing to trim (no allocation on the hot path).
54
+ function stripPlainBlankEdges(lines: string[]): string[] {
55
+ let start = 0;
56
+ let end = lines.length;
57
+ while (start < end && isPlainBlank(lines[start]!)) start++;
58
+ while (end > start && isPlainBlank(lines[end - 1]!)) end--;
59
+ return start === 0 && end === lines.length ? lines : lines.slice(start, end);
60
+ }
61
+
42
62
  /**
43
63
  * Transcript container that freezes the rendered output of every block except
44
64
  * the bottom-most (live) one on terminals where committed native scrollback is
@@ -118,9 +138,13 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
118
138
  width = Math.max(1, width);
119
139
  this.#nativeScrollbackLiveRegionStart = undefined;
120
140
  this.#nativeScrollbackCommitSafeEnd = undefined;
121
- if (!TERMINAL.eagerEraseScrollbackRisk) return super.render(width);
122
141
 
142
+ // Freezing/snapshotting only applies on ED3-risk terminals; elsewhere every
143
+ // block renders live. Inter-block spacing applies on BOTH paths so the gap
144
+ // between blocks is identical regardless of terminal.
145
+ const risk = TERMINAL.eagerEraseScrollbackRisk;
123
146
  const count = this.children.length;
147
+
124
148
  // The live region spans from the earliest still-mutating block through the
125
149
  // bottom. A block that has not finalized must stay repaintable: out-of-band
126
150
  // inserts (TTSR/todo cards) can append a finalized block *below* a tool that
@@ -137,45 +161,81 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
137
161
  // recompute them so they freeze at their final content. Everything below
138
162
  // the lower of the two cutoffs was already frozen last frame and replays.
139
163
  const replayCutoff = Math.min(liveStartIndex, this.#prevLiveStartIndex);
140
- this.#prevLiveStartIndex = liveStartIndex;
164
+ if (risk) this.#prevLiveStartIndex = liveStartIndex;
141
165
 
142
166
  const lines: string[] = [];
143
167
  // Tracks whether we are still inside the leading run of append-only live
144
- // blocks. The first non-append-only live block (or a finalized block below
145
- // the live region's start, which cannot happen for a leading run) closes it.
168
+ // blocks. The first non-append-only live block closes it.
146
169
  let commitSafeOpen = true;
170
+ // The live-region start is recorded at the first visible row at/after the
171
+ // cutoff; empty leading blocks (or a separator) must not claim it early.
172
+ let liveRecorded = false;
147
173
  for (let i = 0; i < count; i++) {
148
174
  const child = this.children[i]! as Component & SnapshotCarrier;
149
- if (i >= liveStartIndex) {
150
- if (i === liveStartIndex) this.#nativeScrollbackLiveRegionStart = lines.length;
151
- } else {
175
+
176
+ // Resolve this child's contribution its visible body with plain-blank
177
+ // top/bottom edges stripped (the container owns inter-block gaps). On
178
+ // ED3-risk terminals a frozen, scrolled-off block replays its snapshot
179
+ // instead of recomputing; a stale generation (post-thaw) or width
180
+ // mismatch (resize) recomputes, as does a block still live last frame.
181
+ let contribution: string[] | undefined;
182
+ if (risk && i < liveStartIndex && i < replayCutoff) {
152
183
  const snapshot = child[kSnapshot];
153
- // Replay a frozen block's last live render. A stale generation
154
- // (post-thaw) or width mismatch (resize, explicit rebuild) recomputes
155
- // instead, as does a block that was still live last frame (i >= cutoff).
156
- if (i < replayCutoff && snapshot && snapshot.generation === this.#generation && snapshot.width === width) {
157
- lines.push(...snapshot.lines);
158
- continue;
184
+ if (snapshot && snapshot.generation === this.#generation && snapshot.width === width) {
185
+ contribution = snapshot.lines;
159
186
  }
160
187
  }
161
- const rendered = child.render(width);
188
+ if (contribution === undefined) {
189
+ const rendered = child.render(width);
190
+ contribution = stripPlainBlankEdges(rendered);
191
+ // Cache every block's latest contribution. While a block is in the
192
+ // live region this keeps its snapshot current; on the frame it crosses
193
+ // out, the recompute above refreshes it before it freezes.
194
+ if (risk) child[kSnapshot] = { width, lines: contribution, generation: this.#generation };
195
+ }
196
+
197
+ // Empty (or stripped-to-nothing) children contribute nothing and never
198
+ // affect spacing or the live-region offsets.
199
+ if (contribution.length === 0) continue;
200
+
201
+ // Every block is separated from preceding visible content by exactly one
202
+ // blank row — skipped when it opens the transcript or the prior row is
203
+ // already a plain blank (a fragment's own trailing pad), never doubling.
204
+ const sep = lines.length > 0 && !isPlainBlank(lines[lines.length - 1]!) ? 1 : 0;
205
+
206
+ // The separator before the first live block stays in the committed prefix
207
+ // (it is deterministic and never changes once the prior block is frozen),
208
+ // so the live region begins at the block's first content row.
209
+ if (risk && !liveRecorded && i >= liveStartIndex) {
210
+ this.#nativeScrollbackLiveRegionStart = lines.length + sep;
211
+ liveRecorded = true;
212
+ }
213
+
214
+ if (sep) lines.push("");
215
+ for (let j = 0; j < contribution.length; j++) lines.push(contribution[j]!);
216
+
162
217
  // Extend the commit-safe boundary through each leading append-only live
163
- // block. `lines.length` here is this block's start offset; the boundary
164
- // runs to the end of its rendered rows. The first volatile live block
165
- // closes the run so its mutable rows stay deferred.
166
- if (i >= liveStartIndex && commitSafeOpen) {
218
+ // block. The first volatile live block closes the run so its mutable
219
+ // rows stay deferred.
220
+ if (risk && i >= liveStartIndex && commitSafeOpen) {
167
221
  if (isBlockAppendOnly(child)) {
168
- this.#nativeScrollbackCommitSafeEnd = lines.length + rendered.length;
222
+ this.#nativeScrollbackCommitSafeEnd = lines.length;
169
223
  } else {
170
224
  commitSafeOpen = false;
171
225
  }
172
226
  }
173
- // Cache every block's latest render. While a block is in the live region
174
- // this keeps its snapshot current; on the frame it crosses out, the
175
- // recompute above refreshes it to the final state before it freezes.
176
- child[kSnapshot] = { width, lines: rendered, generation: this.#generation };
177
- lines.push(...rendered);
178
227
  }
179
228
  return lines;
180
229
  }
181
230
  }
231
+
232
+ /**
233
+ * Groups a run of sibling rows (an IRC card's header + body, a file-mention
234
+ * list, a bordered command/version panel) into a single transcript child so the
235
+ * container spaces it as one block — one blank line above, none injected between
236
+ * its rows. Without this wrapper the rows would be top-level children and the
237
+ * container would put a blank line between each (and inside any border box).
238
+ * It is a plain {@link Container}; the named subclass documents intent and makes
239
+ * every manual block grouping greppable.
240
+ */
241
+ export class TranscriptBlock extends Container {}
@@ -1,4 +1,4 @@
1
- import { Container, Markdown, Spacer } from "@oh-my-pi/pi-tui";
1
+ import { Container, Markdown } from "@oh-my-pi/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../../modes/theme/theme";
3
3
  import { imageReferenceHyperlink, renderImageReferences } from "../image-references";
4
4
  import { highlightMagicKeywords } from "../magic-keywords";
@@ -30,7 +30,6 @@ export class UserMessageComponent extends Container {
30
30
  renderText: baseText,
31
31
  renderReference: (label, index) => imageReferenceHyperlink(label, index, imageLinks, imageLabel),
32
32
  });
33
- this.addChild(new Spacer(1));
34
33
  this.addChild(
35
34
  new Markdown(text, 1, 1, getMarkdownTheme(), {
36
35
  bgColor,
@@ -7,10 +7,11 @@
7
7
  * wording, and add-flow logic stay in the per-controller files because they
8
8
  * diverge in workflow.
9
9
  */
10
- import { Spacer, Text } from "@oh-my-pi/pi-tui";
10
+ import { Text } from "@oh-my-pi/pi-tui";
11
11
  import type { SourceMeta } from "../../capability/types";
12
12
  import { shortenPath } from "../../tools/render-utils";
13
13
  import { DynamicBorder } from "../components/dynamic-border";
14
+ import { TranscriptBlock } from "../components/transcript-container";
14
15
  import { parseCommandArgs } from "../shared";
15
16
  import type { InteractiveModeContext } from "../types";
16
17
 
@@ -100,9 +101,9 @@ export function* groupBySource<T>(
100
101
  * container and request a render.
101
102
  */
102
103
  export function showCommandMessage(ctx: InteractiveModeContext, text: string): void {
103
- ctx.chatContainer.addChild(new Spacer(1));
104
- ctx.chatContainer.addChild(new DynamicBorder());
105
- ctx.chatContainer.addChild(new Text(text, 1, 1));
106
- ctx.chatContainer.addChild(new DynamicBorder());
107
- ctx.ui.requestRender();
104
+ const block = new TranscriptBlock();
105
+ block.addChild(new DynamicBorder());
106
+ block.addChild(new Text(text, 1, 1));
107
+ block.addChild(new DynamicBorder());
108
+ ctx.present(block);
108
109
  }
@@ -13,7 +13,6 @@ import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi
13
13
  import { formatDuration, Snowflake } from "@oh-my-pi/pi-utils";
14
14
  import { $ } from "bun";
15
15
  import { shouldEnableAppendOnlyContext } from "../../config/append-only-context-mode";
16
- import { loadCustomShare } from "../../export/custom-share";
17
16
  import type { CompactOptions } from "../../extensibility/extensions/types";
18
17
  import {
19
18
  diffMentalModelContent,
@@ -29,6 +28,7 @@ import { BashExecutionComponent } from "../../modes/components/bash-execution";
29
28
  import { BorderedLoader } from "../../modes/components/bordered-loader";
30
29
  import { DynamicBorder } from "../../modes/components/dynamic-border";
31
30
  import { EvalExecutionComponent } from "../../modes/components/eval-execution";
31
+ import { TranscriptBlock } from "../../modes/components/transcript-container";
32
32
  import { getMarkdownTheme, getSymbolTheme, theme } from "../../modes/theme/theme";
33
33
  import type { InteractiveModeContext } from "../../modes/types";
34
34
  import { computeContextBreakdown, renderContextUsage } from "../../modes/utils/context-usage";
@@ -47,13 +47,13 @@ import { openPath } from "../../utils/open";
47
47
  import { setSessionTerminalTitle } from "../../utils/title-generator";
48
48
 
49
49
  function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
50
- ctx.chatContainer.addChild(new Spacer(1));
51
- ctx.chatContainer.addChild(new DynamicBorder());
52
- ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
53
- ctx.chatContainer.addChild(new Spacer(1));
54
- ctx.chatContainer.addChild(new Markdown(markdown.trim(), 1, 1, getMarkdownTheme()));
55
- ctx.chatContainer.addChild(new DynamicBorder());
56
- ctx.ui.requestRender();
50
+ const block = new TranscriptBlock();
51
+ block.addChild(new DynamicBorder());
52
+ block.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
53
+ block.addChild(new Spacer(1));
54
+ block.addChild(new Markdown(markdown.trim(), 1, 1, getMarkdownTheme()));
55
+ block.addChild(new DynamicBorder());
56
+ ctx.present(block);
57
57
  }
58
58
 
59
59
  export class CommandController {
@@ -131,6 +131,7 @@ export class CommandController {
131
131
  }
132
132
 
133
133
  try {
134
+ const { loadCustomShare } = await import("../../export/custom-share");
134
135
  const customShare = await loadCustomShare();
135
136
  if (customShare) {
136
137
  const loader = new BorderedLoader(this.ctx.ui, theme, "Sharing...");
@@ -334,9 +335,7 @@ export class CommandController {
334
335
  }
335
336
  }
336
337
 
337
- this.ctx.chatContainer.addChild(new Spacer(1));
338
- this.ctx.chatContainer.addChild(new Text(info, 1, 0));
339
- this.ctx.ui.requestRender();
338
+ this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
340
339
  }
341
340
 
342
341
  async handleJobsCommand(): Promise<void> {
@@ -353,9 +352,7 @@ export class CommandController {
353
352
 
354
353
  if (snapshot.running.length === 0 && snapshot.recent.length === 0) {
355
354
  info += `\n${theme.fg("dim", "No async jobs yet.")}\n`;
356
- this.ctx.chatContainer.addChild(new Spacer(1));
357
- this.ctx.chatContainer.addChild(new Text(info, 1, 0));
358
- this.ctx.ui.requestRender();
355
+ this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
359
356
  return;
360
357
  }
361
358
 
@@ -375,9 +372,7 @@ export class CommandController {
375
372
  }
376
373
  }
377
374
 
378
- this.ctx.chatContainer.addChild(new Spacer(1));
379
- this.ctx.chatContainer.addChild(new Text(info.trimEnd(), 1, 0));
380
- this.ctx.ui.requestRender();
375
+ this.ctx.present([new Spacer(1), new Text(info.trimEnd(), 1, 0)]);
381
376
  }
382
377
 
383
378
  async handleUsageCommand(reports?: UsageReport[] | null): Promise<void> {
@@ -403,9 +398,7 @@ export class CommandController {
403
398
 
404
399
  const availableWidth = Math.max(40, (this.ctx.ui.terminal.columns ?? 100) - 2);
405
400
  const output = renderUsageReports(usageReports, theme, Date.now(), availableWidth);
406
- this.ctx.chatContainer.addChild(new Spacer(1));
407
- this.ctx.chatContainer.addChild(new Text(output, 1, 0));
408
- this.ctx.ui.requestRender();
401
+ this.ctx.present([new Spacer(1), new Text(output, 1, 0)]);
409
402
  }
410
403
 
411
404
  async handleChangelogCommand(showFull = false): Promise<void> {
@@ -426,13 +419,13 @@ export class CommandController {
426
419
  ? ""
427
420
  : `\n\n${theme.fg("dim", "Use")} ${theme.bold("/changelog full")} ${theme.fg("dim", "to view the complete changelog.")}`;
428
421
 
429
- this.ctx.chatContainer.addChild(new Spacer(1));
430
- this.ctx.chatContainer.addChild(new DynamicBorder());
431
- this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
432
- this.ctx.chatContainer.addChild(new Spacer(1));
433
- this.ctx.chatContainer.addChild(new Markdown(changelogMarkdown + hint, 1, 1, getMarkdownTheme()));
434
- this.ctx.chatContainer.addChild(new DynamicBorder());
435
- this.ctx.ui.requestRender();
422
+ const block = new TranscriptBlock();
423
+ block.addChild(new DynamicBorder());
424
+ block.addChild(new Text(theme.bold(theme.fg("accent", title)), 1, 0));
425
+ block.addChild(new Spacer(1));
426
+ block.addChild(new Markdown(changelogMarkdown + hint, 1, 1, getMarkdownTheme()));
427
+ block.addChild(new DynamicBorder());
428
+ this.ctx.present(block);
436
429
  }
437
430
 
438
431
  handleHotkeysCommand(): void {
@@ -452,20 +445,20 @@ export class CommandController {
452
445
  return;
453
446
  }
454
447
  const output = renderContextUsage(breakdown, theme);
455
- this.ctx.chatContainer.addChild(new Spacer(1));
456
- this.ctx.chatContainer.addChild(new DynamicBorder());
457
- this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Context Usage")), 1, 0));
458
- this.ctx.chatContainer.addChild(new Spacer(1));
459
- this.ctx.chatContainer.addChild(new Text(output, 1, 0));
460
- this.ctx.chatContainer.addChild(new DynamicBorder());
461
- this.ctx.ui.requestRender();
448
+ const block = new TranscriptBlock();
449
+ block.addChild(new DynamicBorder());
450
+ block.addChild(new Text(theme.bold(theme.fg("accent", "Context Usage")), 1, 0));
451
+ block.addChild(new Spacer(1));
452
+ block.addChild(new Text(output, 1, 0));
453
+ block.addChild(new DynamicBorder());
454
+ this.ctx.present(block);
462
455
  }
463
456
 
464
457
  async handleMemoryCommand(text: string): Promise<void> {
465
458
  const argumentText = text.slice(7).trim();
466
459
  const action = argumentText.split(/\s+/, 1)[0]?.toLowerCase() || "view";
467
460
  const agentDir = this.ctx.settings.getAgentDir();
468
- const backend = resolveMemoryBackend(this.ctx.settings);
461
+ const backend = await resolveMemoryBackend(this.ctx.settings);
469
462
 
470
463
  if (action === "view") {
471
464
  const payload = await backend.buildDeveloperInstructions(agentDir, this.ctx.settings, this.ctx.session);
@@ -473,13 +466,13 @@ export class CommandController {
473
466
  this.ctx.showWarning("Memory payload is empty (memory backend off, disabled, or no memory available).");
474
467
  return;
475
468
  }
476
- this.ctx.chatContainer.addChild(new Spacer(1));
477
- this.ctx.chatContainer.addChild(new DynamicBorder());
478
- this.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Memory Injection Payload")), 1, 0));
479
- this.ctx.chatContainer.addChild(new Spacer(1));
480
- this.ctx.chatContainer.addChild(new Markdown(payload, 1, 1, getMarkdownTheme()));
481
- this.ctx.chatContainer.addChild(new DynamicBorder());
482
- this.ctx.ui.requestRender();
469
+ const block = new TranscriptBlock();
470
+ block.addChild(new DynamicBorder());
471
+ block.addChild(new Text(theme.bold(theme.fg("accent", "Memory Injection Payload")), 1, 0));
472
+ block.addChild(new Spacer(1));
473
+ block.addChild(new Markdown(payload, 1, 1, getMarkdownTheme()));
474
+ block.addChild(new DynamicBorder());
475
+ this.ctx.present(block);
483
476
  return;
484
477
  }
485
478
 
@@ -800,8 +793,7 @@ export class CommandController {
800
793
  this.ctx.streamingMessage = undefined;
801
794
  this.ctx.pendingTools.clear();
802
795
 
803
- this.ctx.chatContainer.addChild(new Spacer(1));
804
- this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
796
+ this.ctx.present([new Spacer(1), new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1)]);
805
797
  await this.ctx.reloadTodos();
806
798
  this.ctx.ui.requestRender(true, { clearScrollback: true });
807
799
  }
@@ -810,6 +802,18 @@ export class CommandController {
810
802
  await this.#runNewSessionFlow();
811
803
  }
812
804
 
805
+ async handleFreshCommand(): Promise<void> {
806
+ const result = this.ctx.session.freshSession();
807
+ if (!result) {
808
+ this.ctx.showWarning("Wait for the current response to finish or abort it before refreshing provider state.");
809
+ return;
810
+ }
811
+ const stateLabel = result.closedProviderSessions === 1 ? "provider state" : "provider states";
812
+ this.ctx.statusLine.invalidate();
813
+ this.ctx.updateEditorTopBorder();
814
+ this.ctx.showStatus(`Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`);
815
+ }
816
+
813
817
  async handleDropCommand(): Promise<void> {
814
818
  if (!this.ctx.sessionManager.getSessionFile()) {
815
819
  this.ctx.showError("Nothing to drop (in-memory session)");
@@ -840,11 +844,10 @@ export class CommandController {
840
844
 
841
845
  const sessionFile = this.ctx.session.sessionFile;
842
846
  const shortPath = sessionFile ? sessionFile.split("/").pop() : "new session";
843
- this.ctx.chatContainer.addChild(new Spacer(1));
844
- this.ctx.chatContainer.addChild(
847
+ this.ctx.present([
848
+ new Spacer(1),
845
849
  new Text(`${theme.fg("accent", `${theme.status.success} Session forked to ${shortPath}`)}`, 1, 1),
846
- );
847
- this.ctx.ui.requestRender();
850
+ ]);
848
851
  }
849
852
 
850
853
  async handleMoveCommand(targetPath: string): Promise<void> {
@@ -878,11 +881,10 @@ export class CommandController {
878
881
  await this.ctx.sessionManager.moveTo(resolvedPath);
879
882
  await this.ctx.applyCwdChange(resolvedPath);
880
883
 
881
- this.ctx.chatContainer.addChild(new Spacer(1));
882
- this.ctx.chatContainer.addChild(
884
+ this.ctx.present([
885
+ new Spacer(1),
883
886
  new Text(`${theme.fg("accent", `${theme.status.success} Session moved to ${resolvedPath}`)}`, 1, 1),
884
- );
885
- this.ctx.ui.requestRender();
887
+ ]);
886
888
  } catch (err) {
887
889
  this.ctx.showError(`Move failed: ${err instanceof Error ? err.message : String(err)}`);
888
890
  }
@@ -913,7 +915,7 @@ export class CommandController {
913
915
  this.ctx.pendingMessagesContainer.addChild(this.ctx.bashComponent);
914
916
  this.ctx.pendingBashComponents.push(this.ctx.bashComponent);
915
917
  } else {
916
- this.ctx.chatContainer.addChild(this.ctx.bashComponent);
918
+ this.ctx.present(this.ctx.bashComponent);
917
919
  }
918
920
  this.ctx.ui.requestRender();
919
921
 
@@ -954,7 +956,7 @@ export class CommandController {
954
956
  this.ctx.pendingMessagesContainer.addChild(this.ctx.pythonComponent);
955
957
  this.ctx.pendingPythonComponents.push(this.ctx.pythonComponent);
956
958
  } else {
957
- this.ctx.chatContainer.addChild(this.ctx.pythonComponent);
959
+ this.ctx.present(this.ctx.pythonComponent);
958
960
  }
959
961
  this.ctx.ui.requestRender();
960
962
 
@@ -1143,10 +1145,10 @@ export class CommandController {
1143
1145
  this.ctx.updateEditorBorderColor();
1144
1146
  await this.ctx.reloadTodos();
1145
1147
 
1146
- this.ctx.chatContainer.addChild(new Spacer(1));
1147
- this.ctx.chatContainer.addChild(
1148
+ this.ctx.present([
1149
+ new Spacer(1),
1148
1150
  new Text(`${theme.fg("accent", `${theme.status.success} New session started with handoff context`)}`, 1, 1),
1149
- );
1151
+ ]);
1150
1152
  if (result.savedPath) {
1151
1153
  this.ctx.showStatus(`Handoff document saved to: ${result.savedPath}`);
1152
1154
  }
@@ -1272,6 +1274,8 @@ function formatAccountLabel(limit: UsageLimit, report: UsageReport, index: numbe
1272
1274
  if (email) return email;
1273
1275
  const accountId = (report.metadata?.accountId as string | undefined) ?? limit.scope.accountId;
1274
1276
  if (accountId) return accountId;
1277
+ const projectId = (report.metadata?.projectId as string | undefined) ?? limit.scope.projectId;
1278
+ if (projectId) return projectId;
1275
1279
  return `account ${index + 1}`;
1276
1280
  }
1277
1281
 
@@ -1280,6 +1284,8 @@ function formatUnlimitedReportLabel(report: UsageReport, index: number): string
1280
1284
  if (email) return email;
1281
1285
  const accountId = report.metadata?.accountId as string | undefined;
1282
1286
  if (accountId) return accountId;
1287
+ const projectId = report.metadata?.projectId as string | undefined;
1288
+ if (projectId) return projectId;
1283
1289
  return `account ${index + 1}`;
1284
1290
  }
1285
1291
 
@@ -1365,6 +1371,13 @@ function formatAggregateAmount(limits: UsageLimit[]): string {
1365
1371
  return `${formatNumber(remainingPct)}% free`;
1366
1372
  }
1367
1373
 
1374
+ // Count unique accounts from limit scopes — not limits.length.
1375
+ const uniqueAccountIds = new Set(
1376
+ limits.map(limit => limit.scope.accountId).filter((id): id is string => typeof id === "string" && id.length > 0),
1377
+ );
1378
+ if (uniqueAccountIds.size > 0) return `${uniqueAccountIds.size} ${uniqueAccountIds.size === 1 ? "acct" : "accts"}`;
1379
+ // No account IDs available — keep the pre-existing fallback so providers
1380
+ // that don't populate scope.accountId still show a summary.
1368
1381
  return `${limits.length} accts`;
1369
1382
  }
1370
1383