@oh-my-pi/pi-coding-agent 3.14.0 → 3.15.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 (213) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/docs/theme.md +38 -5
  3. package/examples/sdk/11-sessions.ts +2 -2
  4. package/package.json +7 -4
  5. package/src/cli/file-processor.ts +51 -2
  6. package/src/cli/plugin-cli.ts +25 -19
  7. package/src/cli/update-cli.ts +4 -3
  8. package/src/core/agent-session.ts +31 -4
  9. package/src/core/compaction/branch-summarization.ts +4 -32
  10. package/src/core/compaction/compaction.ts +6 -84
  11. package/src/core/compaction/utils.ts +2 -3
  12. package/src/core/custom-tools/types.ts +2 -0
  13. package/src/core/export-html/index.ts +1 -1
  14. package/src/core/hooks/tool-wrapper.ts +0 -1
  15. package/src/core/hooks/types.ts +2 -2
  16. package/src/core/plugins/doctor.ts +9 -1
  17. package/src/core/sdk.ts +2 -1
  18. package/src/core/session-manager.ts +518 -40
  19. package/src/core/settings-manager.ts +174 -0
  20. package/src/core/system-prompt.ts +9 -14
  21. package/src/core/title-generator.ts +2 -8
  22. package/src/core/tools/ask.ts +19 -37
  23. package/src/core/tools/bash.ts +2 -37
  24. package/src/core/tools/edit.ts +2 -9
  25. package/src/core/tools/exa/render.ts +52 -48
  26. package/src/core/tools/find.ts +10 -8
  27. package/src/core/tools/grep.ts +45 -17
  28. package/src/core/tools/ls.ts +22 -2
  29. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  30. package/src/core/tools/lsp/clients/index.ts +49 -0
  31. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  32. package/src/core/tools/lsp/config.ts +3 -0
  33. package/src/core/tools/lsp/index.ts +107 -55
  34. package/src/core/tools/lsp/render.ts +192 -79
  35. package/src/core/tools/lsp/types.ts +27 -0
  36. package/src/core/tools/lsp/utils.ts +62 -22
  37. package/src/core/tools/notebook.ts +9 -1
  38. package/src/core/tools/output.ts +37 -14
  39. package/src/core/tools/read.ts +349 -34
  40. package/src/core/tools/renderers.ts +290 -89
  41. package/src/core/tools/review.ts +12 -5
  42. package/src/core/tools/task/agents.ts +5 -5
  43. package/src/core/tools/task/commands.ts +3 -3
  44. package/src/core/tools/task/executor.ts +33 -1
  45. package/src/core/tools/task/index.ts +93 -6
  46. package/src/core/tools/task/render.ts +147 -66
  47. package/src/core/tools/task/types.ts +14 -9
  48. package/src/core/tools/web-fetch.ts +242 -103
  49. package/src/core/tools/web-search/index.ts +64 -20
  50. package/src/core/tools/web-search/providers/exa.ts +68 -172
  51. package/src/core/tools/web-search/render.ts +264 -74
  52. package/src/core/tools/write.ts +2 -8
  53. package/src/main.ts +10 -6
  54. package/src/modes/cleanup.ts +23 -0
  55. package/src/modes/index.ts +9 -4
  56. package/src/modes/interactive/components/bash-execution.ts +6 -3
  57. package/src/modes/interactive/components/branch-summary-message.ts +1 -1
  58. package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
  59. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  60. package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
  61. package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
  62. package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
  63. package/src/modes/interactive/components/hook-editor.ts +1 -0
  64. package/src/modes/interactive/components/hook-message.ts +2 -2
  65. package/src/modes/interactive/components/hook-selector.ts +1 -1
  66. package/src/modes/interactive/components/model-selector.ts +22 -9
  67. package/src/modes/interactive/components/oauth-selector.ts +20 -4
  68. package/src/modes/interactive/components/plugin-settings.ts +4 -2
  69. package/src/modes/interactive/components/session-selector.ts +9 -6
  70. package/src/modes/interactive/components/settings-defs.ts +285 -1
  71. package/src/modes/interactive/components/settings-selector.ts +176 -3
  72. package/src/modes/interactive/components/status-line/index.ts +4 -0
  73. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  74. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  75. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  76. package/src/modes/interactive/components/status-line/types.ts +81 -0
  77. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  78. package/src/modes/interactive/components/status-line.ts +172 -223
  79. package/src/modes/interactive/components/tool-execution.ts +446 -211
  80. package/src/modes/interactive/components/tree-selector.ts +17 -6
  81. package/src/modes/interactive/components/ttsr-notification.ts +4 -4
  82. package/src/modes/interactive/components/welcome.ts +27 -19
  83. package/src/modes/interactive/interactive-mode.ts +99 -13
  84. package/src/modes/interactive/theme/dark.json +3 -2
  85. package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
  86. package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
  87. package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
  88. package/src/modes/interactive/theme/defaults/basalt.json +90 -0
  89. package/src/modes/interactive/theme/defaults/birch.json +101 -0
  90. package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
  91. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  92. package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
  93. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  94. package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
  95. package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
  96. package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
  97. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  98. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  99. package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
  100. package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
  101. package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
  102. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  103. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  104. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  105. package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
  106. package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
  107. package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
  108. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  109. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  110. package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
  111. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  112. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  113. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  114. package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
  115. package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
  116. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  117. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  118. package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
  119. package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
  120. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  121. package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
  122. package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
  123. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  124. package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
  125. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  126. package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
  127. package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
  128. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  129. package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
  130. package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
  131. package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
  132. package/src/modes/interactive/theme/defaults/graphite.json +99 -0
  133. package/src/modes/interactive/theme/defaults/index.ts +195 -0
  134. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  135. package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
  136. package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
  137. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  138. package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
  139. package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
  140. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  141. package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
  142. package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
  143. package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
  144. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  145. package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
  146. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  147. package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
  148. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  149. package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
  150. package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
  151. package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
  152. package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
  153. package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
  154. package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
  155. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  156. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  157. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  158. package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
  159. package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
  160. package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
  161. package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
  162. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  163. package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
  164. package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
  165. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  166. package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
  167. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  168. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  169. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  170. package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
  171. package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
  172. package/src/modes/interactive/theme/defaults/limestone.json +100 -0
  173. package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
  174. package/src/modes/interactive/theme/defaults/marble.json +99 -0
  175. package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
  176. package/src/modes/interactive/theme/defaults/onyx.json +90 -0
  177. package/src/modes/interactive/theme/defaults/pearl.json +99 -0
  178. package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
  179. package/src/modes/interactive/theme/defaults/quartz.json +102 -0
  180. package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
  181. package/src/modes/interactive/theme/defaults/titanium.json +89 -0
  182. package/src/modes/interactive/theme/light.json +3 -2
  183. package/src/modes/interactive/theme/theme-schema.json +120 -4
  184. package/src/modes/interactive/theme/theme.ts +1228 -14
  185. package/src/prompts/branch-summary-preamble.md +3 -0
  186. package/src/prompts/branch-summary.md +28 -0
  187. package/src/prompts/compaction-summary.md +34 -0
  188. package/src/prompts/compaction-turn-prefix.md +16 -0
  189. package/src/prompts/compaction-update-summary.md +41 -0
  190. package/src/prompts/init.md +30 -0
  191. package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
  192. package/src/prompts/summarization-system.md +3 -0
  193. package/src/prompts/system-prompt.md +27 -0
  194. package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
  195. package/src/prompts/title-system.md +8 -0
  196. package/src/prompts/tools/ask.md +24 -0
  197. package/src/prompts/tools/bash.md +23 -0
  198. package/src/prompts/tools/edit.md +9 -0
  199. package/src/prompts/tools/find.md +6 -0
  200. package/src/prompts/tools/grep.md +12 -0
  201. package/src/prompts/tools/lsp.md +14 -0
  202. package/src/prompts/tools/output.md +23 -0
  203. package/src/prompts/tools/read.md +25 -0
  204. package/src/prompts/tools/web-fetch.md +8 -0
  205. package/src/prompts/tools/web-search.md +10 -0
  206. package/src/prompts/tools/write.md +10 -0
  207. package/src/commands/init.md +0 -20
  208. /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
  209. /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
  210. /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
  211. /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
  212. /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
  213. /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
@@ -22,13 +22,6 @@ import { renderWebFetchCall, renderWebFetchResult, type WebFetchToolDetails } fr
22
22
  import { renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./web-search/render";
23
23
 
24
24
  // Tree drawing characters
25
- const TREE_MID = "├─";
26
- const TREE_END = "└─";
27
-
28
- // Icons
29
- const ICON_SUCCESS = "●";
30
- const ICON_WARNING = "●";
31
- const ICON_ERROR = "●";
32
25
 
33
26
  interface ToolRenderer<TArgs = any, TDetails = any> {
34
27
  renderCall(args: TArgs, theme: Theme): Component;
@@ -86,29 +79,43 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
86
79
 
87
80
  // Error case
88
81
  if (details?.error) {
89
- return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", details.error)}`, 0, 0);
82
+ return new Text(`${theme.styledSymbol("status.error", "error")} ${theme.fg("error", details.error)}`, 0, 0);
90
83
  }
91
84
 
92
- // Check for detailed rendering data - fall back to raw output if not available
85
+ // Check for detailed rendering data - fall back to structured output if not available
93
86
  const hasDetailedData = details?.matchCount !== undefined || details?.fileCount !== undefined;
94
87
 
95
88
  if (!hasDetailedData) {
96
- // Fall back to showing raw text content
97
89
  const textContent = result.content?.find((c) => c.type === "text")?.text;
98
90
  if (!textContent || textContent === "No matches found") {
99
- return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No matches found")}`, 0, 0);
91
+ return new Text(
92
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No matches found")}`,
93
+ 0,
94
+ 0,
95
+ );
100
96
  }
101
97
 
102
- // Show abbreviated output
103
- const lines = textContent.split("\n");
98
+ const lines = textContent.split("\n").filter((line) => line.trim() !== "");
104
99
  const maxLines = expanded ? lines.length : 10;
105
100
  const displayLines = lines.slice(0, maxLines);
106
101
  const remaining = lines.length - maxLines;
107
102
 
108
- let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("toolTitle", "grep")}`;
109
- text += `\n${displayLines.map((l) => theme.fg("toolOutput", l)).join("\n")}`;
103
+ let text = `${theme.styledSymbol("status.success", "success")} ${theme.fg("toolTitle", "grep")} ${theme.fg(
104
+ "dim",
105
+ `${lines.length} item${lines.length !== 1 ? "s" : ""}`,
106
+ )}`;
107
+
108
+ for (let i = 0; i < displayLines.length; i++) {
109
+ const isLast = i === displayLines.length - 1 && remaining === 0;
110
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
111
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("toolOutput", displayLines[i])}`;
112
+ }
113
+
110
114
  if (remaining > 0) {
111
- text += `\n${theme.fg("muted", `... ${remaining} more lines`)}`;
115
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
116
+ "muted",
117
+ `${theme.format.ellipsis} ${remaining} more items`,
118
+ )}`;
112
119
  }
113
120
  return new Text(text, 0, 0);
114
121
  }
@@ -121,11 +128,15 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
121
128
 
122
129
  // No matches
123
130
  if (matchCount === 0) {
124
- return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No matches found")}`, 0, 0);
131
+ return new Text(
132
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No matches found")}`,
133
+ 0,
134
+ 0,
135
+ );
125
136
  }
126
137
 
127
138
  // Build summary
128
- const icon = theme.fg("success", ICON_SUCCESS);
139
+ const icon = theme.styledSymbol("status.success", "success");
129
140
  let summary: string;
130
141
  if (mode === "files_with_matches") {
131
142
  summary = `${fileCount} file${fileCount !== 1 ? "s" : ""}`;
@@ -140,22 +151,56 @@ const grepRenderer: ToolRenderer<GrepArgs, GrepToolDetails> = {
140
151
  }
141
152
 
142
153
  const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
143
- let text = `${icon} ${theme.fg("toolTitle", "grep")} ${theme.fg("dim", summary)}${expandHint}`;
154
+ const scopeLabel = details?.scopePath ? ` ${theme.fg("muted", `in ${details.scopePath}`)}` : "";
155
+ let text = `${icon} ${theme.fg("toolTitle", "grep")} ${theme.fg("dim", summary)}${scopeLabel}${expandHint}`;
156
+
157
+ const truncationReasons: string[] = [];
158
+ if (details?.matchLimitReached) {
159
+ truncationReasons.push(`limit ${details.matchLimitReached} matches`);
160
+ }
161
+ if (details?.headLimitReached) {
162
+ truncationReasons.push(`head limit ${details.headLimitReached}`);
163
+ }
164
+ if (details?.truncation?.truncated) {
165
+ truncationReasons.push("size limit");
166
+ }
167
+ if (details?.linesTruncated) {
168
+ truncationReasons.push("line length");
169
+ }
170
+
171
+ const fileEntries: Array<{ path: string; count?: number }> = details?.fileMatches?.length
172
+ ? details.fileMatches.map((entry) => ({ path: entry.path, count: entry.count }))
173
+ : files.map((path) => ({ path }));
144
174
 
145
175
  // Show file tree if we have files
146
- if (files.length > 0) {
147
- const maxFiles = expanded ? files.length : Math.min(files.length, 8);
176
+ if (fileEntries.length > 0) {
177
+ const maxFiles = expanded ? fileEntries.length : Math.min(fileEntries.length, 8);
148
178
  for (let i = 0; i < maxFiles; i++) {
149
- const isLast = i === maxFiles - 1 && (expanded || files.length <= 8);
150
- const branch = isLast ? TREE_END : TREE_MID;
151
- text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", files[i])}`;
179
+ const entry = fileEntries[i];
180
+ const isLast = i === maxFiles - 1 && (expanded || fileEntries.length <= 8);
181
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
182
+ const countLabel =
183
+ entry.count !== undefined
184
+ ? ` ${theme.fg("dim", `(${entry.count} match${entry.count !== 1 ? "es" : ""})`)}`
185
+ : "";
186
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", entry.path)}${countLabel}`;
152
187
  }
153
188
 
154
- if (!expanded && files.length > 8) {
155
- text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${files.length - 8} more files`)}`;
189
+ if (!expanded && fileEntries.length > 8) {
190
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
191
+ "muted",
192
+ `${theme.format.ellipsis} ${fileEntries.length - 8} more files`,
193
+ )}`;
156
194
  }
157
195
  }
158
196
 
197
+ if (truncationReasons.length > 0) {
198
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
199
+ "warning",
200
+ `truncated: ${truncationReasons.join(", ")}`,
201
+ )}`;
202
+ }
203
+
159
204
  return new Text(text, 0, 0);
160
205
  },
161
206
  };
@@ -195,7 +240,7 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
195
240
 
196
241
  // Error case
197
242
  if (details?.error) {
198
- return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", details.error)}`, 0, 0);
243
+ return new Text(`${theme.styledSymbol("status.error", "error")} ${theme.fg("error", details.error)}`, 0, 0);
199
244
  }
200
245
 
201
246
  // Check for detailed rendering data - fall back to parsing raw output if not available
@@ -206,7 +251,11 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
206
251
 
207
252
  if (!hasDetailedData) {
208
253
  if (!textContent || textContent.includes("No files matching") || textContent.trim() === "") {
209
- return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No files found")}`, 0, 0);
254
+ return new Text(
255
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No files found")}`,
256
+ 0,
257
+ 0,
258
+ );
210
259
  }
211
260
 
212
261
  // Parse the raw output as file list
@@ -215,17 +264,20 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
215
264
  const displayLines = lines.slice(0, maxLines);
216
265
  const remaining = lines.length - maxLines;
217
266
 
218
- let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("toolTitle", "find")} ${theme.fg(
267
+ let text = `${theme.styledSymbol("status.success", "success")} ${theme.fg("toolTitle", "find")} ${theme.fg(
219
268
  "dim",
220
269
  `${lines.length} file${lines.length !== 1 ? "s" : ""}`,
221
270
  )}`;
222
271
  for (let i = 0; i < displayLines.length; i++) {
223
272
  const isLast = i === displayLines.length - 1 && remaining === 0;
224
- const branch = isLast ? TREE_END : TREE_MID;
273
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
225
274
  text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", displayLines[i])}`;
226
275
  }
227
276
  if (remaining > 0) {
228
- text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${remaining} more files`)}`;
277
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
278
+ "muted",
279
+ `${theme.format.ellipsis} ${remaining} more files`,
280
+ )}`;
229
281
  }
230
282
  return new Text(text, 0, 0);
231
283
  }
@@ -236,11 +288,15 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
236
288
 
237
289
  // No matches
238
290
  if (fileCount === 0) {
239
- return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "No files found")}`, 0, 0);
291
+ return new Text(
292
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "No files found")}`,
293
+ 0,
294
+ 0,
295
+ );
240
296
  }
241
297
 
242
298
  // Build summary
243
- const icon = theme.fg("success", ICON_SUCCESS);
299
+ const icon = theme.styledSymbol("status.success", "success");
244
300
  let summary = `${fileCount} file${fileCount !== 1 ? "s" : ""}`;
245
301
 
246
302
  if (truncated) {
@@ -248,22 +304,41 @@ const findRenderer: ToolRenderer<FindArgs, FindToolDetails> = {
248
304
  }
249
305
 
250
306
  const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
251
- let text = `${icon} ${theme.fg("toolTitle", "find")} ${theme.fg("dim", summary)}${expandHint}`;
307
+ const scopeLabel = details?.scopePath ? ` ${theme.fg("muted", `in ${details.scopePath}`)}` : "";
308
+ let text = `${icon} ${theme.fg("toolTitle", "find")} ${theme.fg("dim", summary)}${scopeLabel}${expandHint}`;
309
+
310
+ const truncationReasons: string[] = [];
311
+ if (details?.resultLimitReached) {
312
+ truncationReasons.push(`limit ${details.resultLimitReached} results`);
313
+ }
314
+ if (details?.truncation?.truncated) {
315
+ truncationReasons.push("size limit");
316
+ }
252
317
 
253
318
  // Show file tree if we have files
254
319
  if (files.length > 0) {
255
320
  const maxFiles = expanded ? files.length : Math.min(files.length, 8);
256
321
  for (let i = 0; i < maxFiles; i++) {
257
322
  const isLast = i === maxFiles - 1 && (expanded || files.length <= 8);
258
- const branch = isLast ? TREE_END : TREE_MID;
323
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
259
324
  text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", files[i])}`;
260
325
  }
261
326
 
262
327
  if (!expanded && files.length > 8) {
263
- text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${files.length - 8} more files`)}`;
328
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
329
+ "muted",
330
+ `${theme.format.ellipsis} ${files.length - 8} more files`,
331
+ )}`;
264
332
  }
265
333
  }
266
334
 
335
+ if (truncationReasons.length > 0) {
336
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
337
+ "warning",
338
+ `truncated: ${truncationReasons.join(", ")}`,
339
+ )}`;
340
+ }
341
+
267
342
  return new Text(text, 0, 0);
268
343
  },
269
344
  };
@@ -280,6 +355,37 @@ interface NotebookArgs {
280
355
  content?: string;
281
356
  }
282
357
 
358
+ function normalizeCellLines(lines: string[]): string[] {
359
+ return lines.map((line) => (line.endsWith("\n") ? line.slice(0, -1) : line));
360
+ }
361
+
362
+ function renderCellPreview(lines: string[], expanded: boolean, theme: Theme): string {
363
+ const normalized = normalizeCellLines(lines);
364
+ if (normalized.length === 0) {
365
+ return `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", "(empty cell)")}`;
366
+ }
367
+
368
+ const maxLines = expanded ? normalized.length : Math.min(normalized.length, 6);
369
+ let text = "";
370
+
371
+ for (let i = 0; i < maxLines; i++) {
372
+ const isLast = i === maxLines - 1 && (expanded || normalized.length <= maxLines);
373
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
374
+ const line = normalized[i];
375
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("toolOutput", line)}`;
376
+ }
377
+
378
+ const remaining = normalized.length - maxLines;
379
+ if (remaining > 0) {
380
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
381
+ "muted",
382
+ `${theme.format.ellipsis} ${remaining} more lines`,
383
+ )}`;
384
+ }
385
+
386
+ return text;
387
+ }
388
+
283
389
  const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
284
390
  renderCall(args, theme) {
285
391
  let text = theme.fg("toolTitle", theme.bold("notebook "));
@@ -297,22 +403,25 @@ const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
297
403
  return new Text(text, 0, 0);
298
404
  },
299
405
 
300
- renderResult(result, _options, theme) {
406
+ renderResult(result, { expanded }, theme) {
301
407
  const details = result.details;
302
408
 
303
409
  // Error case - check for error in content
304
410
  const content = result.content?.[0];
305
411
  if (content?.type === "text" && content.text?.startsWith("Error:")) {
306
- return new Text(`${theme.fg("error", ICON_ERROR)} ${theme.fg("error", content.text)}`, 0, 0);
412
+ return new Text(`${theme.styledSymbol("status.error", "error")} ${theme.fg("error", content.text)}`, 0, 0);
307
413
  }
308
414
 
309
415
  const action = details?.action ?? "edit";
310
416
  const cellIndex = details?.cellIndex;
311
417
  const cellType = details?.cellType;
312
418
  const totalCells = details?.totalCells;
419
+ const cellSource = details?.cellSource;
420
+ const lineCount = cellSource?.length;
421
+ const canExpand = cellSource !== undefined && cellSource.length > 6;
313
422
 
314
423
  // Build summary
315
- const icon = theme.fg("success", ICON_SUCCESS);
424
+ const icon = theme.styledSymbol("status.success", "success");
316
425
  let summary: string;
317
426
 
318
427
  switch (action) {
@@ -326,11 +435,22 @@ const notebookRenderer: ToolRenderer<NotebookArgs, NotebookToolDetails> = {
326
435
  summary = `Edited ${cellType || "cell"} at index ${cellIndex}`;
327
436
  }
328
437
 
438
+ if (lineCount !== undefined) {
439
+ summary += ` (${lineCount} line${lineCount !== 1 ? "s" : ""})`;
440
+ }
441
+
329
442
  if (totalCells !== undefined) {
330
443
  summary += ` (${totalCells} total)`;
331
444
  }
332
445
 
333
- return new Text(`${icon} ${theme.fg("toolTitle", "notebook")} ${theme.fg("dim", summary)}`, 0, 0);
446
+ const expandHint = !expanded && canExpand ? theme.fg("dim", " (Ctrl+O to expand)") : "";
447
+ let text = `${icon} ${theme.fg("toolTitle", "notebook")} ${theme.fg("dim", summary)}${expandHint}`;
448
+
449
+ if (cellSource) {
450
+ text += renderCellPreview(cellSource, expanded, theme);
451
+ }
452
+
453
+ return new Text(text, 0, 0);
334
454
  },
335
455
  };
336
456
 
@@ -355,9 +475,8 @@ const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
355
475
 
356
476
  if (args.options?.length) {
357
477
  for (const opt of args.options) {
358
- text += `\n${theme.fg("dim", " ")}${theme.fg("muted", opt.label)}`;
478
+ text += `\n${theme.fg("dim", ` ${theme.checkbox.unchecked} `)}${theme.fg("muted", opt.label)}`;
359
479
  }
360
- text += `\n${theme.fg("dim", " ○ ")}${theme.fg("muted", "Other (custom input)")}`;
361
480
  }
362
481
 
363
482
  return new Text(text, 0, 0);
@@ -370,26 +489,33 @@ const askRenderer: ToolRenderer<AskArgs, AskToolDetails> = {
370
489
  return new Text(txt?.type === "text" && txt.text ? txt.text : "", 0, 0);
371
490
  }
372
491
 
373
- let text = theme.fg("toolTitle", "? ") + theme.fg("accent", details.question);
492
+ const hasSelection = details.customInput || details.selectedOptions.length > 0;
493
+ const statusIcon = hasSelection
494
+ ? theme.styledSymbol("status.success", "success")
495
+ : theme.styledSymbol("status.warning", "warning");
496
+
497
+ let text = `${statusIcon} ${theme.fg("toolTitle", "ask")} ${theme.fg("accent", details.question)}`;
374
498
 
375
499
  if (details.customInput) {
376
- // Custom input provided
377
- text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("success", details.customInput)}`;
500
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.styledSymbol(
501
+ "status.success",
502
+ "success",
503
+ )} ${theme.fg("toolOutput", details.customInput)}`;
378
504
  } else if (details.selectedOptions.length > 0) {
379
- // Show only selected options
380
505
  const selected = details.selectedOptions;
381
- if (selected.length === 1) {
382
- text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("success", selected[0])}`;
383
- } else {
384
- // Multiple selections - tree format
385
- for (let i = 0; i < selected.length; i++) {
386
- const isLast = i === selected.length - 1;
387
- const branch = isLast ? TREE_END : TREE_MID;
388
- text += `\n${theme.fg("dim", ` ${branch} `)}${theme.fg("success", selected[i])}`;
389
- }
506
+ for (let i = 0; i < selected.length; i++) {
507
+ const isLast = i === selected.length - 1;
508
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
509
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg(
510
+ "success",
511
+ theme.checkbox.checked,
512
+ )} ${theme.fg("toolOutput", selected[i])}`;
390
513
  }
391
514
  } else {
392
- text += `\n${theme.fg("dim", " ⎿ ")}${theme.fg("warning", "Cancelled")}`;
515
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.styledSymbol(
516
+ "status.warning",
517
+ "warning",
518
+ )} ${theme.fg("warning", "Cancelled")}`;
393
519
  }
394
520
 
395
521
  return new Text(text, 0, 0);
@@ -433,6 +559,22 @@ function formatBytes(bytes: number): string {
433
559
  return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
434
560
  }
435
561
 
562
+ function truncateLine(text: string, maxLen: number, ellipsis: string): string {
563
+ if (text.length <= maxLen) return text;
564
+ const sliceLen = Math.max(0, maxLen - ellipsis.length);
565
+ return `${text.slice(0, sliceLen)}${ellipsis}`;
566
+ }
567
+
568
+ type OutputEntry = OutputToolDetails["outputs"][number];
569
+
570
+ function formatOutputMeta(entry: OutputEntry, theme: Theme): string {
571
+ const metaParts = [`${entry.lineCount} lines, ${formatBytes(entry.charCount)}`];
572
+ if (entry.provenance) {
573
+ metaParts.push(`agent ${entry.provenance.agent}(${entry.provenance.index})`);
574
+ }
575
+ return theme.fg("dim", metaParts.join(theme.sep.dot));
576
+ }
577
+
436
578
  const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
437
579
  renderCall(args, theme) {
438
580
  const ids = args.ids?.join(", ") ?? "?";
@@ -446,7 +588,7 @@ const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
446
588
 
447
589
  // Error case: some IDs not found
448
590
  if (details?.notFound?.length) {
449
- let text = `${theme.fg("error", ICON_ERROR)} Not found: ${details.notFound.join(", ")}`;
591
+ let text = `${theme.styledSymbol("status.error", "error")} Not found: ${details.notFound.join(", ")}`;
450
592
  if (details.availableIds?.length) {
451
593
  text += `\n${theme.fg("dim", "Available:")} ${details.availableIds.join(", ")}`;
452
594
  } else {
@@ -459,35 +601,47 @@ const outputRenderer: ToolRenderer<OutputArgs, OutputToolDetails> = {
459
601
 
460
602
  // No session case
461
603
  if (outputs.length === 0) {
462
- const textContent = result.content?.find((c: any) => c.type === "text")?.text;
604
+ const textContent = result.content?.find((c) => c.type === "text")?.text;
463
605
  return new Text(
464
- `${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", textContent || "No outputs")}`,
606
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", textContent || "No outputs")}`,
465
607
  0,
466
608
  0,
467
609
  );
468
610
  }
469
611
 
470
- // Success: single output
471
- if (outputs.length === 1) {
472
- const o = outputs[0];
473
- const summary = `read ${o.id}.out.md (${o.lineCount} lines, ${formatBytes(o.charCount)})`;
474
- return new Text(`${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", summary)}`, 0, 0);
475
- }
476
-
477
- // Success: multiple outputs (tree display)
612
+ // Success: summary + tree display
478
613
  const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
479
- let text = `${theme.fg("success", ICON_SUCCESS)} ${theme.fg("dim", `read ${outputs.length} outputs`)}${expandHint}`;
614
+ const icon = theme.styledSymbol("status.success", "success");
615
+ const summary = `read ${outputs.length} output${outputs.length !== 1 ? "s" : ""}`;
616
+ let text = `${icon} ${theme.fg("toolTitle", "output")} ${theme.fg("dim", summary)}${expandHint}`;
480
617
 
618
+ const previewLimit = expanded ? 3 : 1;
481
619
  const maxOutputs = expanded ? outputs.length : Math.min(outputs.length, 5);
482
620
  for (let i = 0; i < maxOutputs; i++) {
483
621
  const o = outputs[i];
484
622
  const isLast = i === maxOutputs - 1 && (expanded || outputs.length <= 5);
485
- const branch = isLast ? TREE_END : TREE_MID;
486
- text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${theme.fg("dim", `(${o.lineCount} lines)`)}`;
623
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
624
+ text += `\n ${theme.fg("dim", branch)} ${theme.fg("accent", o.id)} ${formatOutputMeta(o, theme)}`;
625
+
626
+ const previewLines = o.previewLines ?? [];
627
+ const shownPreview = previewLines.slice(0, previewLimit);
628
+ if (shownPreview.length > 0) {
629
+ const childPrefix = isLast ? " " : ` ${theme.fg("dim", theme.tree.vertical)} `;
630
+ for (const line of shownPreview) {
631
+ const previewText = truncateLine(line, 80, theme.format.ellipsis);
632
+ text += `\n${childPrefix}${theme.fg("dim", theme.tree.hook)} ${theme.fg("muted", "preview:")} ${theme.fg(
633
+ "toolOutput",
634
+ previewText,
635
+ )}`;
636
+ }
637
+ }
487
638
  }
488
639
 
489
640
  if (!expanded && outputs.length > 5) {
490
- text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${outputs.length - 5} more outputs`)}`;
641
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
642
+ "muted",
643
+ `${theme.format.ellipsis} ${outputs.length - 5} more outputs`,
644
+ )}`;
491
645
  }
492
646
 
493
647
  return new Text(text, 0, 0);
@@ -524,41 +678,88 @@ const lsRenderer: ToolRenderer<LsArgs, LsToolDetails> = {
524
678
 
525
679
  renderResult(result, { expanded }, theme) {
526
680
  const details = result.details;
527
- const textContent = result.content?.find((c: any) => c.type === "text")?.text;
681
+ const textContent = result.content?.find((c: any) => c.type === "text")?.text ?? "";
528
682
 
529
- if (!textContent || textContent.trim() === "") {
530
- return new Text(`${theme.fg("warning", ICON_WARNING)} ${theme.fg("muted", "Empty directory")}`, 0, 0);
683
+ if (
684
+ (!textContent || textContent.trim() === "" || textContent.trim() === "(empty directory)") &&
685
+ (!details?.entries || details.entries.length === 0)
686
+ ) {
687
+ return new Text(
688
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "Empty directory")}`,
689
+ 0,
690
+ 0,
691
+ );
692
+ }
693
+
694
+ let entries: string[] = details?.entries ? [...details.entries] : [];
695
+ if (entries.length === 0) {
696
+ const rawLines = textContent.split("\n").filter((l: string) => l.trim());
697
+ entries = rawLines.filter((line) => !/^\[.*\]$/.test(line.trim()));
698
+ }
699
+
700
+ if (entries.length === 0) {
701
+ return new Text(
702
+ `${theme.styledSymbol("status.warning", "warning")} ${theme.fg("muted", "Empty directory")}`,
703
+ 0,
704
+ 0,
705
+ );
706
+ }
707
+
708
+ let dirCount = details?.dirCount;
709
+ let fileCount = details?.fileCount;
710
+ if (dirCount === undefined || fileCount === undefined) {
711
+ dirCount = 0;
712
+ fileCount = 0;
713
+ for (const entry of entries) {
714
+ if (entry.endsWith("/")) {
715
+ dirCount += 1;
716
+ } else {
717
+ fileCount += 1;
718
+ }
719
+ }
531
720
  }
532
721
 
533
- const entries = textContent.split("\n").filter((l: string) => l.trim());
534
- const dirs = entries.filter((e: string) => e.endsWith("/"));
535
- const files = entries.filter((e: string) => !e.endsWith("/"));
722
+ const truncated = Boolean(details?.truncation?.truncated || details?.entryLimitReached);
723
+ const icon = truncated
724
+ ? theme.styledSymbol("status.warning", "warning")
725
+ : theme.styledSymbol("status.success", "success");
536
726
 
537
- const truncated = details?.truncation?.truncated || details?.entryLimitReached;
538
- const icon = truncated ? theme.fg("warning", ICON_WARNING) : theme.fg("success", ICON_SUCCESS);
727
+ const dirLabel = `${dirCount} dir${dirCount !== 1 ? "s" : ""}`;
728
+ const fileLabel = `${fileCount} file${fileCount !== 1 ? "s" : ""}`;
729
+ let text = `${icon} ${theme.fg("toolTitle", "ls")} ${theme.fg("dim", `${dirLabel}, ${fileLabel}`)}`;
539
730
 
540
- let summary = `${dirs.length} dir${dirs.length !== 1 ? "s" : ""}, ${files.length} file${
541
- files.length !== 1 ? "s" : ""
542
- }`;
543
731
  if (truncated) {
544
- summary += theme.fg("warning", " (truncated)");
732
+ const reasonParts: string[] = [];
733
+ if (details?.entryLimitReached) {
734
+ reasonParts.push(`entry limit ${details.entryLimitReached}`);
735
+ }
736
+ if (details?.truncation?.truncated) {
737
+ reasonParts.push(`output cap ${formatBytes(details.truncation.maxBytes)}`);
738
+ }
739
+ const reasonText = reasonParts.length > 0 ? `truncated: ${reasonParts.join(", ")}` : "truncated";
740
+ text += ` ${theme.fg("warning", `(${reasonText})`)}`;
545
741
  }
546
742
 
547
- const expandHint = expanded ? "" : theme.fg("dim", " (Ctrl+O to expand)");
548
- let text = `${icon} ${theme.fg("toolTitle", "ls")} ${theme.fg("dim", summary)}${expandHint}`;
743
+ if (!expanded) {
744
+ text += `\n${theme.fg("dim", `${theme.nav.expand} Ctrl+O to expand list`)}`;
745
+ }
549
746
 
550
747
  const maxEntries = expanded ? entries.length : Math.min(entries.length, 12);
551
748
  for (let i = 0; i < maxEntries; i++) {
552
749
  const entry = entries[i];
553
750
  const isLast = i === maxEntries - 1 && (expanded || entries.length <= 12);
554
- const branch = isLast ? TREE_END : TREE_MID;
751
+ const branch = isLast ? theme.tree.last : theme.tree.branch;
555
752
  const isDir = entry.endsWith("/");
556
- const color = isDir ? "accent" : "toolOutput";
557
- text += `\n ${theme.fg("dim", branch)} ${theme.fg(color, entry)}`;
753
+ const entryIcon = isDir ? theme.fg("accent", theme.icon.folder) : theme.fg("muted", theme.icon.file);
754
+ const entryColor = isDir ? "accent" : "toolOutput";
755
+ text += `\n ${theme.fg("dim", branch)} ${entryIcon} ${theme.fg(entryColor, entry)}`;
558
756
  }
559
757
 
560
758
  if (!expanded && entries.length > 12) {
561
- text += `\n ${theme.fg("dim", TREE_END)} ${theme.fg("muted", `… ${entries.length - 12} more entries`)}`;
759
+ text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg(
760
+ "muted",
761
+ `${theme.format.ellipsis} ${entries.length - 12} more entries`,
762
+ )}`;
562
763
  }
563
764
 
564
765
  return new Text(text, 0, 0);
@@ -10,6 +10,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
10
10
  import { Container, Spacer, Text } from "@oh-my-pi/pi-tui";
11
11
  import { Type } from "@sinclair/typebox";
12
12
  import type { Theme } from "../../modes/interactive/theme/theme";
13
+ import { theme } from "../../modes/interactive/theme/theme";
13
14
 
14
15
  const PRIORITY_LABELS: Record<number, string> = {
15
16
  0: "P0",
@@ -101,7 +102,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
101
102
  const location = `${details.file_path}:${details.line_start}${details.line_end !== details.line_start ? `-${details.line_end}` : ""}`;
102
103
 
103
104
  return new Text(
104
- `${theme.fg("success", "✓")} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
105
+ `${theme.fg("success", theme.status.success)} ${theme.fg(color, `[${priority}]`)} ${theme.fg("dim", location)}`,
105
106
  0,
106
107
  0,
107
108
  );
@@ -140,7 +141,7 @@ export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReview
140
141
  const { overall_correctness, explanation, confidence } = params;
141
142
 
142
143
  let summary = `## Review Summary\n\n`;
143
- summary += `**Verdict:** ${overall_correctness === "correct" ? "✓ Patch is correct" : "✗ Patch is incorrect"}\n`;
144
+ summary += `**Verdict:** ${overall_correctness === "correct" ? `${theme.status.success} Patch is correct` : `${theme.status.error} Patch is incorrect`}\n`;
144
145
  summary += `**Confidence:** ${(confidence * 100).toFixed(0)}%\n\n`;
145
146
  summary += explanation;
146
147
 
@@ -169,7 +170,7 @@ export const submitReviewTool: AgentTool<typeof SubmitReviewParams, SubmitReview
169
170
 
170
171
  const container = new Container();
171
172
  const verdictColor = details.overall_correctness === "correct" ? "success" : "error";
172
- const verdictIcon = details.overall_correctness === "correct" ? "✓" : "✗";
173
+ const verdictIcon = details.overall_correctness === "correct" ? theme.status.success : theme.status.error;
173
174
 
174
175
  container.addChild(
175
176
  new Text(
@@ -239,7 +240,13 @@ subprocessToolRegistry.register<ReportFindingDetails>("report_finding", {
239
240
  }
240
241
 
241
242
  if (allData.length > displayCount) {
242
- container.addChild(new Text(theme.fg("dim", ` ... ${allData.length - displayCount} more findings`), 0, 0));
243
+ container.addChild(
244
+ new Text(
245
+ theme.fg("dim", ` ${theme.format.ellipsis} ${allData.length - displayCount} more findings`),
246
+ 0,
247
+ 0,
248
+ ),
249
+ );
243
250
  }
244
251
 
245
252
  return container;
@@ -255,7 +262,7 @@ subprocessToolRegistry.register<SubmitReviewDetails>("submit_review", {
255
262
 
256
263
  renderInline: (data, theme) => {
257
264
  const verdictColor = data.overall_correctness === "correct" ? "success" : "error";
258
- const verdictIcon = data.overall_correctness === "correct" ? "✓" : "✗";
265
+ const verdictIcon = data.overall_correctness === "correct" ? theme.status.success : theme.status.error;
259
266
  return new Text(
260
267
  `${theme.fg(verdictColor, verdictIcon)} Review: ${theme.fg(verdictColor, data.overall_correctness)} (${(data.confidence * 100).toFixed(0)}%)`,
261
268
  0,