@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
package/src/tools/find.ts CHANGED
@@ -13,6 +13,7 @@ import findDescription from "../prompts/tools/find.md" with { type: "text" };
13
13
  import { type TruncationResult, truncateHead } from "../session/streaming-output";
14
14
  import { Ellipsis, fileHyperlink, renderFileList, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
15
15
  import type { ToolSession } from ".";
16
+ import { buildPathTree, walkPathTree } from "./grouped-file-output";
16
17
  import { applyListLimit } from "./list-limit";
17
18
  import { formatFullOutputReference, type OutputMeta } from "./output-meta";
18
19
  import {
@@ -54,34 +55,27 @@ const MIN_GLOB_TIMEOUT_MS = 500;
54
55
  const MAX_GLOB_TIMEOUT_MS = 60_000;
55
56
 
56
57
  /**
57
- * Group find matches by their directory so the model doesn't pay repeated
58
- * tokens for shared path prefixes. Preserves the input order: groups appear in
59
- * the order their first member was emitted (mtime-desc for native glob), and
60
- * within a group entries keep their relative order.
58
+ * Group find matches into a multi-level directory tree so the model doesn't pay
59
+ * repeated tokens for shared path prefixes. Single-child directory chains fold
60
+ * into one header (`# a/b/c/`), so a common prefix including an absolute root
61
+ * for out-of-cwd results — collapses to a single line. Each level adds one `#`;
62
+ * files are listed bare under the deepest directory header that owns them.
63
+ *
64
+ * Order follows the input (mtime-desc for native glob): a directory appears when
65
+ * its first member is emitted, and a node's own files precede its subdirectories.
61
66
  */
62
67
  export function formatFindGroupedOutput(paths: readonly string[]): string {
63
68
  if (paths.length === 0) return "";
64
- const groups = new Map<string, string[]>();
65
- for (const entry of paths) {
66
- const hasTrailingSlash = entry.endsWith("/");
67
- const trimmed = hasTrailingSlash ? entry.slice(0, -1) : entry;
68
- const slash = trimmed.lastIndexOf("/");
69
- const dir = slash === -1 ? "" : trimmed.slice(0, slash);
70
- const base = slash === -1 ? trimmed : trimmed.slice(slash + 1);
71
- const label = hasTrailingSlash ? `${base}/` : base;
72
- const list = groups.get(dir);
73
- if (list) list.push(label);
74
- else groups.set(dir, [label]);
75
- }
76
- const sections: string[] = [];
77
- for (const [dir, entries] of groups) {
78
- if (dir === "") {
79
- sections.push(entries.join("\n"));
69
+ const tree = buildPathTree(paths.map(entry => ({ path: entry, isDir: entry.endsWith("/") })));
70
+ const lines: string[] = [];
71
+ for (const event of walkPathTree(tree)) {
72
+ if (event.kind === "dir") {
73
+ lines.push(`${"#".repeat(event.depth + 1)} ${event.name}/`);
80
74
  } else {
81
- sections.push(`# ${dir}/\n${entries.join("\n")}`);
75
+ lines.push(event.name);
82
76
  }
83
77
  }
84
- return sections.join("\n\n");
78
+ return lines.join("\n");
85
79
  }
86
80
 
87
81
  export interface FindToolDetails {
@@ -434,6 +428,10 @@ function formatFindRenderPaths(paths: FindRenderArgs["paths"]): string | undefin
434
428
 
435
429
  const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
436
430
 
431
+ function findStatusIcon(uiTheme: Theme): string {
432
+ return uiTheme.fg("toolTitle", uiTheme.symbol("icon.search"));
433
+ }
434
+
437
435
  export const findToolRenderer = {
438
436
  inline: true,
439
437
  renderCall(args: FindRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
@@ -441,10 +439,16 @@ export const findToolRenderer = {
441
439
  if (args.limit !== undefined) meta.push(`limit:${args.limit}`);
442
440
 
443
441
  const text = renderStatusLine(
444
- { icon: "pending", title: "Find", description: formatFindRenderPaths(args.paths) || "*", meta },
442
+ {
443
+ icon: "pending",
444
+ title: "Find",
445
+ titleColor: "toolTitle",
446
+ description: formatFindRenderPaths(args.paths) || "*",
447
+ meta,
448
+ },
445
449
  uiTheme,
446
450
  );
447
- return new Text(text, 0, 0);
451
+ return new Text(text, 1, 0);
448
452
  },
449
453
 
450
454
  renderResult(
@@ -457,7 +461,7 @@ export const findToolRenderer = {
457
461
 
458
462
  if (result.isError || details?.error) {
459
463
  const errorText = details?.error || result.content?.find(c => c.type === "text")?.text || "Unknown error";
460
- return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
464
+ return new Text(formatErrorMessage(errorText, uiTheme), 1, 0);
461
465
  }
462
466
 
463
467
  const hasDetailedData = details?.fileCount !== undefined;
@@ -470,14 +474,15 @@ export const findToolRenderer = {
470
474
  textContent.includes("No files found") ||
471
475
  textContent.trim() === ""
472
476
  ) {
473
- return new Text(formatEmptyMessage("No files found", uiTheme), 0, 0);
477
+ return new Text(formatEmptyMessage("No files found", uiTheme), 1, 0);
474
478
  }
475
479
 
476
480
  const lines = textContent.split("\n").filter(l => l.trim());
477
481
  const header = renderStatusLine(
478
482
  {
479
- icon: "success",
483
+ iconOverride: findStatusIcon(uiTheme),
480
484
  title: "Find",
485
+ titleColor: "toolTitle",
481
486
  description: formatFindRenderPaths(args?.paths),
482
487
  meta: [formatCount("file", lines.length)],
483
488
  },
@@ -498,6 +503,7 @@ export const findToolRenderer = {
498
503
  );
499
504
  return [header, ...listLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
500
505
  },
506
+ { paddingX: 1 },
501
507
  );
502
508
  }
503
509
 
@@ -513,20 +519,27 @@ export const findToolRenderer = {
513
519
 
514
520
  if (fileCount === 0) {
515
521
  const header = renderStatusLine(
516
- { icon: "warning", title: "Find", description: formatFindRenderPaths(args?.paths), meta: ["0 files"] },
522
+ {
523
+ icon: "warning",
524
+ title: "Find",
525
+ titleColor: "toolTitle",
526
+ description: formatFindRenderPaths(args?.paths),
527
+ meta: ["0 files"],
528
+ },
517
529
  uiTheme,
518
530
  );
519
531
  const lines = [header, formatEmptyMessage("No files found", uiTheme)];
520
532
  if (missingNote) lines.push(missingNote);
521
- return new Text(lines.join("\n"), 0, 0);
533
+ return new Text(lines.join("\n"), 1, 0);
522
534
  }
523
535
  const meta: string[] = [formatCount("file", fileCount)];
524
536
  if (details?.scopePath) meta.push(`in ${details.scopePath}`);
525
537
  if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
526
538
  const header = renderStatusLine(
527
539
  {
528
- icon: truncated ? "warning" : "success",
540
+ ...(truncated ? { icon: "warning" as const } : { iconOverride: findStatusIcon(uiTheme) }),
529
541
  title: "Find",
542
+ titleColor: "toolTitle",
530
543
  description: formatFindRenderPaths(args?.paths),
531
544
  meta,
532
545
  },
@@ -565,6 +578,7 @@ export const findToolRenderer = {
565
578
  );
566
579
  return [header, ...fileLines, ...extraLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
567
580
  },
581
+ { paddingX: 1 },
568
582
  );
569
583
  },
570
584
  mergeCallAndResult: true,
@@ -1,7 +1,7 @@
1
- import { type Component, padding, Text, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
1
+ import { type Component, padding, Text, visibleWidth } from "@oh-my-pi/pi-tui";
2
2
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
3
3
  import type { Theme, ThemeColor } from "../modes/theme/theme";
4
- import { renderStatusLine } from "../tui";
4
+ import { framedBlock, renderStatusLine } from "../tui";
5
5
  import type {
6
6
  GhRunWatchFailedLogDetails,
7
7
  GhRunWatchJobDetails,
@@ -239,7 +239,7 @@ function renderFailedLogs(
239
239
  return [];
240
240
  }
241
241
 
242
- const lines = ["", theme.fg("error", "failed logs")];
242
+ const lines: string[] = [];
243
243
  for (const entry of failedLogs) {
244
244
  const context = entry.workflowName ? `${entry.workflowName} #${entry.runId}` : `run #${entry.runId}`;
245
245
  lines.push(
@@ -268,36 +268,45 @@ function renderFailedLogs(
268
268
  return lines;
269
269
  }
270
270
 
271
- function buildWatchLines(
271
+ function buildWatchSections(
272
272
  watch: GhRunWatchViewDetails,
273
273
  theme: Theme,
274
274
  options: RenderResultOptions,
275
275
  width: number,
276
- ): string[] {
277
- const lines = [theme.fg("muted", getWatchHeader(watch))];
276
+ ): Array<{ label?: string; lines: string[] }> {
277
+ const main: string[] = [];
278
278
 
279
279
  if (watch.note) {
280
- lines.push(theme.fg("dim", replaceTabs(watch.note)));
280
+ main.push(theme.fg("dim", replaceTabs(watch.note)));
281
281
  }
282
282
 
283
283
  if (watch.mode === "run" && watch.run) {
284
- lines.push(...renderRunBlock(watch.run, width, theme));
284
+ main.push(...renderRunBlock(watch.run, width, theme));
285
285
  } else if (watch.mode === "commit") {
286
286
  const runs = watch.runs ?? [];
287
287
  if (runs.length === 0) {
288
- lines.push(theme.fg("dim", "waiting for workflow runs..."));
288
+ main.push(theme.fg("dim", "waiting for workflow runs..."));
289
289
  } else {
290
290
  runs.forEach((run, index) => {
291
291
  if (index > 0) {
292
- lines.push("");
292
+ main.push("");
293
293
  }
294
- lines.push(...renderRunBlock(run, width, theme));
294
+ main.push(...renderRunBlock(run, width, theme));
295
295
  });
296
296
  }
297
297
  }
298
298
 
299
- lines.push(...renderFailedLogs(watch.failedLogs ?? [], width, theme, options.expanded));
300
- return lines;
299
+ const sections: Array<{ label?: string; lines: string[] }> = [];
300
+ if (main.length > 0) {
301
+ sections.push({ lines: main });
302
+ }
303
+
304
+ const failed = renderFailedLogs(watch.failedLogs ?? [], width, theme, options.expanded);
305
+ if (failed.length > 0) {
306
+ sections.push({ label: "failed logs", lines: failed });
307
+ }
308
+
309
+ return sections;
301
310
  }
302
311
 
303
312
  function extractText(content: Array<{ type: string; text?: string }>): string {
@@ -335,29 +344,44 @@ function renderFallbackComponent(
335
344
  }
336
345
 
337
346
  const allLines = replaceTabs(text).split("\n");
347
+ while (allLines.length > 0 && allLines[0].trim() === "") allLines.shift();
348
+ while (allLines.length > 0 && allLines[allLines.length - 1].trim() === "") allLines.pop();
349
+
350
+ // Trivial one-line *success* result: a clean status line beats an almost-empty box.
351
+ // Errors always frame so the message reads as a structured block, never a raw red wrap.
352
+ if (allLines.length <= 1 && !isError) {
353
+ const body = allLines[0];
354
+ if (!body) return new Text(header, 0, 0);
355
+ const colored = isError ? theme.fg("error", body) : theme.fg("toolOutput", body);
356
+ return new Text(`${header}\n${colored}`, 0, 0);
357
+ }
338
358
 
339
- return {
340
- render(width: number): string[] {
341
- const lineWidth = Math.max(24, width || FALLBACK_WIDTH);
342
- const expanded = options.expanded;
343
- const limit = expanded ? allLines.length : Math.min(allLines.length, PREVIEW_LIMITS.OUTPUT_EXPANDED);
344
- const visible = allLines.slice(0, limit);
345
- const remaining = allLines.length - visible.length;
346
-
347
- const out: string[] = [header];
348
- for (const line of visible) {
349
- const colored = isError ? theme.fg("error", line) : theme.fg("toolOutput", line);
350
- out.push(truncateVisualWidth(colored, lineWidth));
351
- }
352
- if (!expanded && remaining > 0) {
353
- const hint = formatExpandHint(theme, expanded, true);
354
- const more = `${formatMoreItems(remaining, "line")}${hint ? ` ${hint}` : ""}`;
355
- out.push(theme.fg("dim", more));
356
- }
357
- return out.map(line => truncateToWidth(line, lineWidth));
358
- },
359
- invalidate() {},
360
- };
359
+ return framedBlock(theme, width => {
360
+ const lineWidth = Math.max(1, (width || FALLBACK_WIDTH) - 3);
361
+ const expanded = options.expanded;
362
+ const limit = expanded ? allLines.length : Math.min(allLines.length, PREVIEW_LIMITS.OUTPUT_EXPANDED);
363
+ const visible = allLines.slice(0, limit);
364
+ const remaining = allLines.length - visible.length;
365
+
366
+ const out: string[] = [];
367
+ for (const line of visible) {
368
+ const colored = isError ? theme.fg("error", line) : theme.fg("toolOutput", line);
369
+ out.push(truncateVisualWidth(colored, lineWidth));
370
+ }
371
+ if (!expanded && remaining > 0) {
372
+ const hint = formatExpandHint(theme, expanded, true);
373
+ const more = `${formatMoreItems(remaining, "line")}${hint ? ` ${hint}` : ""}`;
374
+ out.push(theme.fg("dim", more));
375
+ }
376
+ return {
377
+ header,
378
+ sections: out.length > 0 ? [{ lines: out }] : [],
379
+ state: isError ? "error" : "success",
380
+ borderColor: isError ? "error" : "borderMuted",
381
+ applyBg: false,
382
+ width,
383
+ };
384
+ });
361
385
  }
362
386
 
363
387
  function renderWatchCall(args: GithubToolRenderArgs, options: RenderResultOptions, theme: Theme): Component {
@@ -380,7 +404,7 @@ function renderWatchCall(args: GithubToolRenderArgs, options: RenderResultOption
380
404
  }
381
405
 
382
406
  const header = `${icon} ${titleText} ${metaText}`;
383
- const wait = theme.fg("dim", " waiting for workflow data...");
407
+ const wait = theme.fg("dim", "waiting for workflow data...");
384
408
  return new Text(`${header}\n${wait}`, 0, 0);
385
409
  }
386
410
 
@@ -412,13 +436,28 @@ export const githubToolRenderer = {
412
436
  ): Component {
413
437
  const watch = result.details?.watch;
414
438
  if (watch) {
415
- return {
416
- render(width: number): string[] {
417
- const lineWidth = Math.max(24, width || FALLBACK_WIDTH);
418
- return buildWatchLines(watch, uiTheme, options, lineWidth).map(line => truncateToWidth(line, lineWidth));
439
+ const isError = result.isError === true;
440
+ const header = renderStatusLine(
441
+ {
442
+ icon: isError ? "error" : "success",
443
+ title: "GitHub Run Watch",
444
+ titleColor: isError ? "error" : "accent",
445
+ meta: [getWatchHeader(watch)],
419
446
  },
420
- invalidate() {},
421
- };
447
+ uiTheme,
448
+ );
449
+ return framedBlock(uiTheme, width => {
450
+ const innerWidth = Math.max(1, (width || FALLBACK_WIDTH) - 3);
451
+ const sections = buildWatchSections(watch, uiTheme, options, innerWidth);
452
+ return {
453
+ header,
454
+ sections,
455
+ state: isError ? "error" : "success",
456
+ borderColor: isError ? "error" : "borderMuted",
457
+ applyBg: false,
458
+ width,
459
+ };
460
+ });
422
461
  }
423
462
 
424
463
  return renderFallbackComponent(result, options, uiTheme, args ?? {});