@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
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { Component } from "@oh-my-pi/pi-tui";
5
5
  import { ImageProtocol, padding, TERMINAL, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
6
- import type { Theme } from "../modes/theme/theme";
6
+ import type { Theme, ThemeColor } from "../modes/theme/theme";
7
7
  import { getSixelLineMask } from "../utils/sixel";
8
8
  import type { State } from "./types";
9
9
  import type { RenderCache } from "./utils";
@@ -13,11 +13,15 @@ export interface OutputBlockOptions {
13
13
  header?: string;
14
14
  headerMeta?: string;
15
15
  state?: State;
16
- sections?: Array<{ label?: string; lines: string[] }>;
16
+ sections?: Array<{ label?: string; lines: string[]; separator?: boolean }>;
17
17
  width: number;
18
18
  applyBg?: boolean;
19
+ contentPaddingLeft?: number;
19
20
  /** Animate the border with a sweeping dark segment (pending/running state). */
20
21
  animate?: boolean;
22
+ /** Override the state-derived border color. Used for muted "legacy" tool
23
+ * frames that should not visually compete with framed-output tools. */
24
+ borderColor?: ThemeColor;
21
25
  }
22
26
 
23
27
  const FRAMED_BLOCK_COMPONENT = Symbol("framedBlockComponent");
@@ -33,7 +37,7 @@ export function isFramedBlockComponent(component: Component): boolean {
33
37
  return (component as FramedBlockComponent)[FRAMED_BLOCK_COMPONENT] === true;
34
38
  }
35
39
 
36
- const BORDER_SHIMMER_TICK_MS = 16;
40
+ const BORDER_SHIMMER_TICK_MS = 1000 / 30;
37
41
  /** Duration of one full left↔right↔left bounce of the bottom-edge segment, in
38
42
  * ms. Position is derived from the wall clock against this fixed cycle so a
39
43
  * resize only nudges the segment proportionally instead of teleporting it. */
@@ -42,9 +46,9 @@ const BORDER_BOUNCE_MS = 3000;
42
46
  const BORDER_SEGMENT_LEN = 8;
43
47
 
44
48
  /**
45
- * Monotonic frame counter for animated borders, quantized to the TUI's ~16ms
46
- * render cap so the cache key advances once per ~60fps frame — fine enough for a
47
- * smooth segment sweep, coarse enough to coalesce multiple render passes that
49
+ * Monotonic frame counter for animated borders, quantized to the TUI's ~30fps
50
+ * render cap so the cache key advances once per animation frame — fine enough
51
+ * for a smooth segment sweep, coarse enough to coalesce multiple render passes
48
52
  * land inside the same frame.
49
53
  */
50
54
  export function borderShimmerTick(): number {
@@ -92,6 +96,11 @@ type BlockRow =
92
96
  | { kind: "content"; inner: string }
93
97
  | { kind: "sixel"; raw: string };
94
98
 
99
+ function normalizeContentPaddingLeft(value: number | undefined): number {
100
+ if (value === undefined || !Number.isFinite(value)) return 1;
101
+ return Math.max(0, Math.floor(value));
102
+ }
103
+
95
104
  export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[] {
96
105
  const { header, headerMeta, state, sections = [], width, applyBg = true } = options;
97
106
  const h = theme.boxSharp.horizontal;
@@ -99,14 +108,15 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
99
108
  const cap = h.repeat(3);
100
109
  const lineWidth = Math.max(0, width);
101
110
  // Border colors: running/pending use accent, success uses dim (gray), error/warning keep their colors
102
- const borderColor: "error" | "warning" | "accent" | "dim" =
103
- state === "error"
111
+ const borderColor: ThemeColor =
112
+ options.borderColor ??
113
+ (state === "error"
104
114
  ? "error"
105
115
  : state === "warning"
106
116
  ? "warning"
107
117
  : state === "running" || state === "pending"
108
118
  ? "accent"
109
- : "dim";
119
+ : "dim");
110
120
  const border = (text: string) => theme.fg(borderColor, text);
111
121
  const bgFn = (() => {
112
122
  if (!state || !applyBg) return undefined;
@@ -121,7 +131,9 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
121
131
  };
122
132
  })();
123
133
 
124
- const contentWidth = Math.max(0, lineWidth - visibleWidth(`${v} `) - visibleWidth(v));
134
+ const contentPaddingLeft = normalizeContentPaddingLeft(options.contentPaddingLeft);
135
+ const contentWidth = Math.max(0, lineWidth - visibleWidth(v) - contentPaddingLeft - visibleWidth(v));
136
+ const contentLeftPadding = contentPaddingLeft > 0 ? padding(contentPaddingLeft) : "";
125
137
 
126
138
  // ── Layout pass: collect row descriptors so the border perimeter length is
127
139
  // known before the moving segment is positioned. ──
@@ -135,7 +147,11 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
135
147
  });
136
148
 
137
149
  const normalizedSections = sections.length > 0 ? sections : [{ lines: [] as string[] }];
138
- for (const section of normalizedSections) {
150
+ for (let sectionIndex = 0; sectionIndex < normalizedSections.length; sectionIndex++) {
151
+ const section = normalizedSections[sectionIndex]!;
152
+ // A labeled section always draws its titled separator bar. A label-less
153
+ // section can still request a plain divider via `separator`, but only
154
+ // between sections — leading with one would just double the header bar.
139
155
  if (section.label) {
140
156
  rows.push({
141
157
  kind: "bar",
@@ -143,6 +159,12 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
143
159
  rightChar: theme.boxSharp.teeLeft,
144
160
  label: section.label,
145
161
  });
162
+ } else if (section.separator && sectionIndex > 0) {
163
+ rows.push({
164
+ kind: "bar",
165
+ leftChar: theme.boxSharp.teeRight,
166
+ rightChar: theme.boxSharp.teeLeft,
167
+ });
146
168
  }
147
169
  const allLines = section.lines.flatMap(l => l.split("\n"));
148
170
  const sixelLineMask = TERMINAL.imageProtocol === ImageProtocol.Sixel ? getSixelLineMask(allLines) : undefined;
@@ -202,7 +224,12 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
202
224
  const rightGlyph = row.rightChar;
203
225
  if (lineWidth <= 0) return border(leftGlyphs) + border(rightGlyph);
204
226
  const labelText = [row.label, row.meta].filter(Boolean).join(theme.sep.dot);
205
- const rawLabel = labelText ? ` ${labelText} ` : " ";
227
+ if (!labelText) {
228
+ // No header: draw a clean, continuous top/separator bar (no 1-col gap).
229
+ const fillCount = Math.max(0, lineWidth - visibleWidth(leftGlyphs) - visibleWidth(rightGlyph));
230
+ return `${border(leftGlyphs)}${border(h.repeat(fillCount))}${border(rightGlyph)}`;
231
+ }
232
+ const rawLabel = ` ${labelText} `;
206
233
  const leftWidth = visibleWidth(leftGlyphs);
207
234
  const rightWidth = visibleWidth(rightGlyph);
208
235
  const maxLabelWidth = Math.max(0, lineWidth - leftWidth - rightWidth);
@@ -225,7 +252,7 @@ export function renderOutputBlock(options: OutputBlockOptions, theme: Theme): st
225
252
  return `${leftStr}${fillStr}${rightStr}`;
226
253
  };
227
254
 
228
- const renderContent = (inner: string): string => `${border(`${v} `)}${inner}${border(v)}`;
255
+ const renderContent = (inner: string): string => `${border(v)}${contentLeftPadding}${inner}${border(v)}`;
229
256
 
230
257
  const lines: string[] = [];
231
258
  for (let r = 0; r < H; r++) {
@@ -269,15 +296,18 @@ export class CachedOutputBlock {
269
296
  #buildKey(options: OutputBlockOptions): bigint {
270
297
  const h = new Hasher();
271
298
  h.u32(options.width);
299
+ h.u32(normalizeContentPaddingLeft(options.contentPaddingLeft));
272
300
  h.optional(options.header);
273
301
  h.optional(options.headerMeta);
274
302
  h.optional(options.state);
303
+ h.optional(options.borderColor);
275
304
  h.bool(options.applyBg ?? true);
276
305
  h.bool(options.animate ?? false);
277
306
  if (options.animate) h.u32(borderShimmerTick());
278
307
  if (options.sections) {
279
308
  for (const s of options.sections) {
280
309
  h.optional(s.label);
310
+ h.bool(s.separator ?? false);
281
311
  for (const line of s.lines) {
282
312
  h.str(line);
283
313
  }
@@ -286,3 +316,20 @@ export class CachedOutputBlock {
286
316
  return h.digest();
287
317
  }
288
318
  }
319
+
320
+ /**
321
+ * Build a self-framing tool component backed by a cached output block. The
322
+ * `build` callback returns the block options for a given width; the cache
323
+ * dedupes re-renders. Pass `borderColor: "borderMuted"` for the dim "legacy"
324
+ * look that does not compete with the state-colored framed tools.
325
+ */
326
+ export function framedBlock(theme: Theme, build: (width: number) => OutputBlockOptions): Component {
327
+ const block = new CachedOutputBlock();
328
+ // Marked so the tool-execution container treats it as self-framing (renders
329
+ // flush, no extra padding/background) the same way `markFramedBlockComponent`
330
+ // blocks are treated.
331
+ return markFramedBlockComponent({
332
+ render: (width: number): string[] => block.render(build(width), theme),
333
+ invalidate: () => block.invalidate(),
334
+ });
335
+ }
@@ -7,6 +7,9 @@ import { formatStatusIcon } from "../tools/render-utils";
7
7
 
8
8
  export interface StatusLineOptions {
9
9
  icon?: ToolUIStatus;
10
+ /** Pre-rendered glyph that replaces the status icon (e.g. a magnifier for
11
+ * search-family tools). Takes precedence over `icon`. */
12
+ iconOverride?: string;
10
13
  spinnerFrame?: number;
11
14
  title: string;
12
15
  titleColor?: ThemeColor;
@@ -27,7 +30,8 @@ function flattenForHeader(text: string): string {
27
30
  }
28
31
 
29
32
  export function renderStatusLine(options: StatusLineOptions, theme: Theme): string {
30
- const icon = options.icon ? formatStatusIcon(options.icon, theme, options.spinnerFrame) : "";
33
+ const icon =
34
+ options.iconOverride ?? (options.icon ? formatStatusIcon(options.icon, theme, options.spinnerFrame) : "");
31
35
  const titleColor = options.titleColor ?? "accent";
32
36
  const title = theme.fg(titleColor, flattenForHeader(options.title));
33
37
  let line = icon ? `${icon} ${title}` : title;
@@ -6,6 +6,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
6
  import type { Api, Model } from "@oh-my-pi/pi-ai";
7
7
  import { completeSimple } from "@oh-my-pi/pi-ai";
8
8
  import { logger, prompt } from "@oh-my-pi/pi-utils";
9
+
9
10
  import type { ModelRegistry } from "../config/model-registry";
10
11
  import { resolveModelRoleValue } from "../config/model-resolver";
11
12
  import type { Settings } from "../config/settings";
@@ -110,7 +111,14 @@ export async function generateCommitMessage(
110
111
  systemPrompt: [COMMIT_SYSTEM_PROMPT],
111
112
  messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
112
113
  },
113
- { apiKey, maxTokens, reasoning: toReasoningEffort(candidate.thinkingLevel) },
114
+ {
115
+ apiKey: registry.resolver(candidate.model.provider, {
116
+ sessionId,
117
+ baseUrl: candidate.model.baseUrl,
118
+ }),
119
+ maxTokens,
120
+ reasoning: toReasoningEffort(candidate.thinkingLevel),
121
+ },
114
122
  );
115
123
 
116
124
  if (response.stopReason === "error") {
@@ -0,0 +1,202 @@
1
+ import type { ImageContent } from "@oh-my-pi/pi-ai";
2
+
3
+ const OSC5522_PREFIX = "\x1b]5522;";
4
+ const OSC_TERMINATOR_ST = "\x1b\\";
5
+ const OSC_TERMINATOR_BEL = "\x07";
6
+ const PASTE_EVENT_NAME_BASE64 = Buffer.from("Paste event", "utf8").toString("base64");
7
+
8
+ const IMAGE_MIME_PRIORITY = ["image/png", "image/jpeg", "image/webp", "image/gif"] as const;
9
+ const TEXT_MIME_TYPE = "text/plain";
10
+
11
+ type PasteReadKind = "image" | "text";
12
+
13
+ export interface Osc5522Packet {
14
+ metadata: Map<string, string>;
15
+ payload: string;
16
+ }
17
+
18
+ interface PasteListingState {
19
+ phase: "listing";
20
+ mimes: string[];
21
+ pw?: string;
22
+ loc?: string;
23
+ }
24
+
25
+ interface PasteReadState {
26
+ phase: "reading";
27
+ kind: PasteReadKind;
28
+ mimeType: string;
29
+ chunks: string[];
30
+ }
31
+
32
+ type PasteState = PasteListingState | PasteReadState;
33
+
34
+ export interface EnhancedPasteHandlers {
35
+ write(data: string): void;
36
+ pasteText(text: string): void;
37
+ pasteImage(image: ImageContent): void | Promise<void>;
38
+ showStatus(message: string): void;
39
+ }
40
+
41
+ export function isOsc5522Packet(data: string): boolean {
42
+ return data.startsWith(OSC5522_PREFIX) && (data.endsWith(OSC_TERMINATOR_ST) || data.endsWith(OSC_TERMINATOR_BEL));
43
+ }
44
+
45
+ function decodeBase64Utf8(value: string): string | undefined {
46
+ try {
47
+ return Buffer.from(value, "base64").toString("utf8");
48
+ } catch {
49
+ return undefined;
50
+ }
51
+ }
52
+
53
+ function parseMetadata(raw: string): Map<string, string> {
54
+ const metadata = new Map<string, string>();
55
+ for (const part of raw.split(":")) {
56
+ const eq = part.indexOf("=");
57
+ if (eq <= 0) continue;
58
+ metadata.set(part.slice(0, eq), part.slice(eq + 1));
59
+ }
60
+ return metadata;
61
+ }
62
+
63
+ export function parseOsc5522Packet(data: string): Osc5522Packet | undefined {
64
+ if (!isOsc5522Packet(data)) return undefined;
65
+ const bodyEnd = data.endsWith(OSC_TERMINATOR_BEL) ? data.length - 1 : data.length - OSC_TERMINATOR_ST.length;
66
+ const body = data.slice(OSC5522_PREFIX.length, bodyEnd);
67
+ const separator = body.indexOf(";");
68
+ const metadataRaw = separator === -1 ? body : body.slice(0, separator);
69
+ const payload = separator === -1 ? "" : body.slice(separator + 1);
70
+ return { metadata: parseMetadata(metadataRaw), payload };
71
+ }
72
+
73
+ function choosePasteMime(mimes: readonly string[]): { kind: PasteReadKind; mimeType: string } | undefined {
74
+ for (const mimeType of IMAGE_MIME_PRIORITY) {
75
+ if (mimes.includes(mimeType)) return { kind: "image", mimeType };
76
+ }
77
+ return mimes.includes(TEXT_MIME_TYPE) ? { kind: "text", mimeType: TEXT_MIME_TYPE } : undefined;
78
+ }
79
+
80
+ export class EnhancedPasteController {
81
+ #state: PasteState | undefined;
82
+ #handlers: EnhancedPasteHandlers;
83
+
84
+ constructor(handlers: EnhancedPasteHandlers) {
85
+ this.#handlers = handlers;
86
+ }
87
+
88
+ enable(): void {
89
+ this.#handlers.write("\x1b[?5522h");
90
+ }
91
+
92
+ disable(): void {
93
+ this.#handlers.write("\x1b[?5522l");
94
+ this.#state = undefined;
95
+ }
96
+
97
+ handleInput(data: string): boolean {
98
+ const packet = parseOsc5522Packet(data);
99
+ if (!packet) return false;
100
+ void this.#handlePacket(packet);
101
+ return true;
102
+ }
103
+
104
+ async #handlePacket(packet: Osc5522Packet): Promise<void> {
105
+ const type = packet.metadata.get("type");
106
+ if (type !== "read") return;
107
+
108
+ const status = packet.metadata.get("status");
109
+ if (status === "OK") {
110
+ this.#handleOk(packet);
111
+ return;
112
+ }
113
+ if (status === "DATA") {
114
+ this.#handleData(packet);
115
+ return;
116
+ }
117
+ if (status === "DONE") {
118
+ await this.#handleDone();
119
+ return;
120
+ }
121
+ if (status) {
122
+ this.#state = undefined;
123
+ this.#handlers.showStatus(`Enhanced paste failed: ${status}`);
124
+ }
125
+ }
126
+
127
+ #handleOk(packet: Osc5522Packet): void {
128
+ if (this.#state?.phase === "reading") return;
129
+ const loc = packet.metadata.get("loc");
130
+ this.#state = {
131
+ phase: "listing",
132
+ mimes: [],
133
+ pw: packet.metadata.get("pw"),
134
+ loc: loc === "primary" ? loc : undefined,
135
+ };
136
+ }
137
+
138
+ #handleData(packet: Osc5522Packet): void {
139
+ const state = this.#state;
140
+ if (!state) return;
141
+ const encodedMime = packet.metadata.get("mime");
142
+ if (!encodedMime) return;
143
+ const mimeType = decodeBase64Utf8(encodedMime);
144
+ if (!mimeType) return;
145
+
146
+ if (state.phase === "listing") {
147
+ state.mimes.push(mimeType);
148
+ return;
149
+ }
150
+
151
+ if (state.mimeType === mimeType && packet.payload) {
152
+ state.chunks.push(packet.payload);
153
+ }
154
+ }
155
+
156
+ async #handleDone(): Promise<void> {
157
+ const state = this.#state;
158
+ if (!state) return;
159
+ if (state.phase === "listing") {
160
+ this.#finishListing(state);
161
+ return;
162
+ }
163
+ this.#state = undefined;
164
+ const bytes = Buffer.concat(state.chunks.map(chunk => Buffer.from(chunk, "base64")));
165
+ if (bytes.byteLength === 0) {
166
+ this.#handlers.showStatus("Clipboard paste was empty");
167
+ return;
168
+ }
169
+ if (state.kind === "text") {
170
+ this.#handlers.pasteText(bytes.toString("utf8"));
171
+ return;
172
+ }
173
+ await this.#handlers.pasteImage({
174
+ type: "image",
175
+ data: bytes.toString("base64"),
176
+ mimeType: state.mimeType,
177
+ });
178
+ }
179
+
180
+ #finishListing(state: PasteListingState): void {
181
+ const selected = choosePasteMime(state.mimes);
182
+ if (!selected) {
183
+ this.#state = undefined;
184
+ this.#handlers.showStatus("Clipboard paste has no supported text or image data");
185
+ return;
186
+ }
187
+
188
+ this.#state = {
189
+ phase: "reading",
190
+ kind: selected.kind,
191
+ mimeType: selected.mimeType,
192
+ chunks: [],
193
+ };
194
+
195
+ const metadata = [`type=read`, `mime=${Buffer.from(selected.mimeType, "utf8").toString("base64")}`];
196
+ if (state.loc) metadata.push(`loc=${state.loc}`);
197
+ if (state.pw) {
198
+ metadata.push(`pw=${state.pw}`, `name=${PASTE_EVENT_NAME_BASE64}`);
199
+ }
200
+ this.#handlers.write(`${OSC5522_PREFIX}${metadata.join(":")}${OSC_TERMINATOR_ST}`);
201
+ }
202
+ }
@@ -6,6 +6,7 @@ import * as path from "node:path";
6
6
  import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@oh-my-pi/pi-ai";
7
7
  import { logger, prompt } from "@oh-my-pi/pi-utils";
8
8
  import type { ModelRegistry } from "../config/model-registry";
9
+
9
10
  import { resolveRoleSelection } from "../config/model-resolver";
10
11
  import type { Settings } from "../config/settings";
11
12
  import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
@@ -238,7 +239,7 @@ export async function generateTitleOnline(
238
239
  tools: [setTitleTool],
239
240
  },
240
241
  {
241
- apiKey,
242
+ apiKey: registry.resolver(model.provider, { sessionId, baseUrl: model.baseUrl }),
242
243
  maxTokens,
243
244
  disableReasoning: true,
244
245
  toolChoice: { type: "tool", name: SET_TITLE_TOOL_NAME },