@oh-my-pi/pi-coding-agent 8.0.16 → 8.1.0

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 (166) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/package.json +14 -11
  3. package/scripts/generate-wasm-b64.ts +24 -0
  4. package/src/capability/context-file.ts +1 -1
  5. package/src/capability/extension-module.ts +1 -1
  6. package/src/capability/extension.ts +1 -1
  7. package/src/capability/hook.ts +1 -1
  8. package/src/capability/instruction.ts +1 -1
  9. package/src/capability/mcp.ts +1 -1
  10. package/src/capability/prompt.ts +1 -1
  11. package/src/capability/rule.ts +1 -1
  12. package/src/capability/settings.ts +1 -1
  13. package/src/capability/skill.ts +1 -1
  14. package/src/capability/slash-command.ts +1 -1
  15. package/src/capability/ssh.ts +1 -1
  16. package/src/capability/system-prompt.ts +1 -1
  17. package/src/capability/tool.ts +1 -1
  18. package/src/cli/args.ts +1 -1
  19. package/src/cli/plugin-cli.ts +1 -5
  20. package/src/commit/agentic/agent.ts +309 -0
  21. package/src/commit/agentic/fallback.ts +96 -0
  22. package/src/commit/agentic/index.ts +359 -0
  23. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  24. package/src/commit/agentic/prompts/session-user.md +26 -0
  25. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  26. package/src/commit/agentic/prompts/system.md +40 -0
  27. package/src/commit/agentic/state.ts +74 -0
  28. package/src/commit/agentic/tools/analyze-file.ts +131 -0
  29. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  30. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  31. package/src/commit/agentic/tools/git-overview.ts +84 -0
  32. package/src/commit/agentic/tools/index.ts +56 -0
  33. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  34. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  35. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  36. package/src/commit/agentic/tools/split-commit.ts +284 -0
  37. package/src/commit/agentic/topo-sort.ts +44 -0
  38. package/src/commit/agentic/trivial.ts +51 -0
  39. package/src/commit/agentic/validation.ts +200 -0
  40. package/src/commit/analysis/conventional.ts +169 -0
  41. package/src/commit/analysis/index.ts +4 -0
  42. package/src/commit/analysis/scope.ts +242 -0
  43. package/src/commit/analysis/summary.ts +114 -0
  44. package/src/commit/analysis/validation.ts +66 -0
  45. package/src/commit/changelog/detect.ts +36 -0
  46. package/src/commit/changelog/generate.ts +112 -0
  47. package/src/commit/changelog/index.ts +233 -0
  48. package/src/commit/changelog/parse.ts +44 -0
  49. package/src/commit/cli.ts +93 -0
  50. package/src/commit/git/diff.ts +148 -0
  51. package/src/commit/git/errors.ts +11 -0
  52. package/src/commit/git/index.ts +217 -0
  53. package/src/commit/git/operations.ts +53 -0
  54. package/src/commit/index.ts +5 -0
  55. package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
  56. package/src/commit/map-reduce/index.ts +63 -0
  57. package/src/commit/map-reduce/map-phase.ts +193 -0
  58. package/src/commit/map-reduce/reduce-phase.ts +147 -0
  59. package/src/commit/map-reduce/utils.ts +9 -0
  60. package/src/commit/message.ts +11 -0
  61. package/src/commit/model-selection.ts +84 -0
  62. package/src/commit/pipeline.ts +242 -0
  63. package/src/commit/prompts/analysis-system.md +155 -0
  64. package/src/commit/prompts/analysis-user.md +41 -0
  65. package/src/commit/prompts/changelog-system.md +56 -0
  66. package/src/commit/prompts/changelog-user.md +19 -0
  67. package/src/commit/prompts/file-observer-system.md +26 -0
  68. package/src/commit/prompts/file-observer-user.md +9 -0
  69. package/src/commit/prompts/reduce-system.md +60 -0
  70. package/src/commit/prompts/reduce-user.md +17 -0
  71. package/src/commit/prompts/summary-retry.md +4 -0
  72. package/src/commit/prompts/summary-system.md +52 -0
  73. package/src/commit/prompts/summary-user.md +13 -0
  74. package/src/commit/prompts/types-description.md +2 -0
  75. package/src/commit/types.ts +109 -0
  76. package/src/commit/utils/exclusions.ts +42 -0
  77. package/src/config/file-lock.ts +111 -0
  78. package/src/config/model-registry.ts +16 -7
  79. package/src/config/settings-manager.ts +115 -40
  80. package/src/config.ts +5 -5
  81. package/src/discovery/agents-md.ts +1 -1
  82. package/src/discovery/builtin.ts +1 -1
  83. package/src/discovery/claude.ts +1 -1
  84. package/src/discovery/cline.ts +1 -1
  85. package/src/discovery/codex.ts +1 -1
  86. package/src/discovery/cursor.ts +1 -1
  87. package/src/discovery/gemini.ts +1 -1
  88. package/src/discovery/github.ts +1 -1
  89. package/src/discovery/index.ts +11 -11
  90. package/src/discovery/mcp-json.ts +1 -1
  91. package/src/discovery/ssh.ts +1 -1
  92. package/src/discovery/vscode.ts +1 -1
  93. package/src/discovery/windsurf.ts +1 -1
  94. package/src/extensibility/custom-commands/loader.ts +1 -1
  95. package/src/extensibility/custom-commands/types.ts +1 -1
  96. package/src/extensibility/custom-tools/loader.ts +1 -1
  97. package/src/extensibility/custom-tools/types.ts +1 -1
  98. package/src/extensibility/extensions/loader.ts +1 -1
  99. package/src/extensibility/extensions/types.ts +1 -1
  100. package/src/extensibility/hooks/loader.ts +1 -1
  101. package/src/extensibility/hooks/types.ts +3 -3
  102. package/src/index.ts +10 -10
  103. package/src/ipy/executor.ts +97 -1
  104. package/src/lsp/index.ts +1 -1
  105. package/src/lsp/render.ts +90 -46
  106. package/src/main.ts +16 -3
  107. package/src/mcp/loader.ts +3 -3
  108. package/src/migrations.ts +3 -3
  109. package/src/modes/components/assistant-message.ts +29 -1
  110. package/src/modes/components/tool-execution.ts +5 -3
  111. package/src/modes/components/tree-selector.ts +1 -1
  112. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  113. package/src/modes/controllers/selector-controller.ts +1 -1
  114. package/src/modes/interactive-mode.ts +5 -3
  115. package/src/modes/rpc/rpc-client.ts +1 -1
  116. package/src/modes/rpc/rpc-mode.ts +1 -4
  117. package/src/modes/rpc/rpc-types.ts +1 -1
  118. package/src/modes/theme/mermaid-cache.ts +89 -0
  119. package/src/modes/theme/theme.ts +2 -0
  120. package/src/modes/types.ts +2 -2
  121. package/src/patch/index.ts +3 -9
  122. package/src/patch/shared.ts +33 -5
  123. package/src/prompts/tools/task.md +2 -0
  124. package/src/sdk.ts +60 -22
  125. package/src/session/agent-session.ts +3 -3
  126. package/src/session/agent-storage.ts +32 -28
  127. package/src/session/artifacts.ts +24 -1
  128. package/src/session/auth-storage.ts +25 -10
  129. package/src/session/storage-migration.ts +12 -53
  130. package/src/system-prompt.ts +2 -2
  131. package/src/task/.executor.ts.kate-swp +0 -0
  132. package/src/task/executor.ts +1 -1
  133. package/src/task/index.ts +10 -1
  134. package/src/task/output-manager.ts +94 -0
  135. package/src/task/render.ts +7 -12
  136. package/src/task/worker.ts +1 -1
  137. package/src/tools/ask.ts +35 -13
  138. package/src/tools/bash.ts +80 -87
  139. package/src/tools/calculator.ts +42 -40
  140. package/src/tools/complete.ts +1 -1
  141. package/src/tools/fetch.ts +67 -104
  142. package/src/tools/find.ts +83 -86
  143. package/src/tools/grep.ts +80 -96
  144. package/src/tools/index.ts +10 -7
  145. package/src/tools/ls.ts +39 -65
  146. package/src/tools/notebook.ts +48 -64
  147. package/src/tools/output-utils.ts +1 -1
  148. package/src/tools/python.ts +71 -183
  149. package/src/tools/read.ts +74 -15
  150. package/src/tools/render-utils.ts +1 -15
  151. package/src/tools/ssh.ts +43 -24
  152. package/src/tools/todo-write.ts +27 -15
  153. package/src/tools/write.ts +93 -64
  154. package/src/tui/code-cell.ts +115 -0
  155. package/src/tui/file-list.ts +48 -0
  156. package/src/tui/index.ts +11 -0
  157. package/src/tui/output-block.ts +73 -0
  158. package/src/tui/status-line.ts +40 -0
  159. package/src/tui/tree-list.ts +56 -0
  160. package/src/tui/types.ts +17 -0
  161. package/src/tui/utils.ts +49 -0
  162. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  163. package/src/web/search/auth.ts +1 -1
  164. package/src/web/search/index.ts +1 -1
  165. package/src/web/search/render.ts +119 -163
  166. package/tsconfig.json +0 -42
@@ -148,7 +148,7 @@ async function readAnthropicOAuthCredentials(configDir: string): Promise<Anthrop
148
148
  authPaths: [path.join(configDir, "auth.json")],
149
149
  });
150
150
 
151
- const storage = AgentStorage.open(getAgentDbPath(configDir));
151
+ const storage = await AgentStorage.open(getAgentDbPath(configDir));
152
152
  const records = storage.listAuthCredentials("anthropic");
153
153
  const credentials: AnthropicOAuthCredential[] = [];
154
154
  for (const record of records) {
@@ -31,7 +31,7 @@ import type {
31
31
  import type { Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
32
32
  import webSearchSystemPrompt from "@oh-my-pi/pi-coding-agent/prompts/system/web-search.md" with { type: "text" };
33
33
  import webSearchDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/web-search.md" with { type: "text" };
34
- import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools/index";
34
+ import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools";
35
35
  import { formatAge } from "@oh-my-pi/pi-coding-agent/tools/render-utils";
36
36
  import { Type } from "@sinclair/typebox";
37
37
  import { findAnthropicAuth } from "./auth";
@@ -18,6 +18,7 @@ import {
18
18
  TRUNCATE_LENGTHS,
19
19
  truncate,
20
20
  } from "@oh-my-pi/pi-coding-agent/tools/render-utils";
21
+ import { renderOutputBlock, renderStatusLine, renderTreeList } from "@oh-my-pi/pi-coding-agent/tui";
21
22
  import type { Component } from "@oh-my-pi/pi-tui";
22
23
  import { Text } from "@oh-my-pi/pi-tui";
23
24
  import type { WebSearchResponse } from "./types";
@@ -27,7 +28,7 @@ const MAX_EXPANDED_ANSWER_LINES = PREVIEW_LIMITS.EXPANDED_LINES;
27
28
  const MAX_ANSWER_LINE_LEN = TRUNCATE_LENGTHS.LINE;
28
29
  const MAX_SNIPPET_LINES = 2;
29
30
  const MAX_SNIPPET_LINE_LEN = TRUNCATE_LENGTHS.LINE;
30
- const MAX_RELATED_QUESTIONS = 6;
31
+ const MAX_COLLAPSED_ITEMS = PREVIEW_LIMITS.COLLAPSED_ITEMS;
31
32
  const MAX_QUERY_PREVIEW = 2;
32
33
  const MAX_QUERY_LEN = 90;
33
34
  const MAX_REQUEST_ID_LEN = 36;
@@ -70,6 +71,7 @@ export function renderWebSearchResult(
70
71
  result: { content: Array<{ type: string; text?: string }>; details?: WebSearchRenderDetails },
71
72
  options: RenderResultOptions,
72
73
  theme: Theme,
74
+ args?: { query?: string; provider?: string },
73
75
  ): Component {
74
76
  const { expanded } = options;
75
77
  const details = result.details;
@@ -107,7 +109,6 @@ export function renderWebSearchResult(
107
109
  ? getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN, theme.format.ellipsis)
108
110
  : [];
109
111
 
110
- // Build header: status icon Web Search (provider) · counts
111
112
  const providerLabel =
112
113
  provider === "anthropic"
113
114
  ? "Anthropic"
@@ -116,144 +117,111 @@ export function renderWebSearchResult(
116
117
  : provider === "exa"
117
118
  ? "Exa"
118
119
  : "Unknown";
119
- const headerIcon = formatStatusIcon(sourceCount > 0 ? "success" : "warning", theme);
120
- const hasMore =
121
- totalAnswerLines > answerPreview.length ||
122
- sourceCount > 0 ||
123
- citationCount > 0 ||
124
- relatedCount > 0 ||
125
- searchQueries.length > 0;
126
- const expandHint = formatExpandHint(theme, expanded, hasMore);
127
- let text = `${headerIcon} ${theme.fg("dim", `(${providerLabel})`)}${theme.sep.dot}${theme.fg(
128
- "dim",
129
- formatCount("source", sourceCount),
130
- )}${expandHint}`;
131
-
132
- if (!expanded) {
133
- const answerTitle = `${theme.fg("accent", theme.status.info)} ${theme.fg("accent", "Answer")}`;
134
- text += `\n ${theme.fg("dim", theme.tree.branch)} ${answerTitle}`;
135
-
136
- const remaining = totalAnswerLines - answerPreview.length;
137
- const allLines: Array<{ text: string; style: "dim" | "muted" }> = [];
138
-
139
- if (answerPreview.length === 0) {
140
- allLines.push({ text: "No answer text returned", style: "muted" });
141
- } else {
142
- for (const line of answerPreview) {
143
- allLines.push({ text: line, style: "dim" });
144
- }
145
- }
146
- if (remaining > 0) {
147
- allLines.push({ text: formatMoreItems(remaining, "line", theme), style: "muted" });
148
- }
149
-
150
- for (let i = 0; i < allLines.length; i++) {
151
- const { text: lineText, style } = allLines[i];
152
- const isLastLine = i === allLines.length - 1;
153
- const lineBranch = isLastLine ? theme.tree.last : theme.tree.branch;
154
- text += `\n ${theme.fg("dim", theme.tree.vertical)} ${theme.fg("dim", lineBranch)} ${theme.fg(style, lineText)}`;
155
- }
156
-
157
- const summary = [
158
- formatCount("source", sourceCount),
159
- formatCount("citation", citationCount),
160
- formatCount("related question", relatedCount),
161
- ].join(theme.sep.dot);
162
- text += `\n ${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", summary)}`;
163
- return new Text(text, 0, 0);
164
- }
120
+ const queryPreview = args?.query
121
+ ? truncate(args.query, 80, theme.format.ellipsis)
122
+ : searchQueries[0]
123
+ ? truncate(searchQueries[0], 80, theme.format.ellipsis)
124
+ : undefined;
125
+ const header = renderStatusLine(
126
+ {
127
+ icon: sourceCount > 0 ? "success" : "warning",
128
+ title: "Web Search",
129
+ description: providerLabel,
130
+ meta: [formatCount("source", sourceCount)],
131
+ },
132
+ theme,
133
+ );
165
134
 
135
+ const remainingAnswer = totalAnswerLines - answerPreview.length;
166
136
  const answerLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
167
- const answerSectionLines = answerLines.map((line) =>
168
- line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("text", line),
137
+ const answerTree = renderTreeList(
138
+ {
139
+ items: answerLines,
140
+ expanded: true,
141
+ maxCollapsed: answerLines.length,
142
+ itemType: "line",
143
+ renderItem: (line) => (line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("dim", line)),
144
+ },
145
+ theme,
169
146
  );
170
- const remainingAnswer = totalAnswerLines - answerPreview.length;
171
147
  if (remainingAnswer > 0) {
172
- answerSectionLines.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line", theme)));
148
+ answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line", theme)));
173
149
  }
174
150
 
175
- const sourceLines: string[] = [];
176
- if (sourceCount === 0) {
177
- sourceLines.push(theme.fg("muted", "No sources returned"));
178
- } else {
179
- for (const src of sources) {
180
- const titleText =
181
- typeof src.title === "string" && src.title.trim()
182
- ? src.title
183
- : typeof src.url === "string" && src.url.trim()
184
- ? src.url
185
- : "Untitled";
186
- const title = truncate(titleText, 70, theme.format.ellipsis);
187
- const url = typeof src.url === "string" ? src.url : "";
188
- const domain = url ? getDomain(url) : "";
189
- const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
190
- const metaParts: string[] = [];
191
- if (domain) {
192
- metaParts.push(theme.fg("dim", `(${domain})`));
193
- }
194
- if (typeof src.author === "string" && src.author.trim()) {
195
- metaParts.push(theme.fg("muted", src.author));
196
- }
197
- if (age) {
198
- metaParts.push(theme.fg("muted", age));
199
- }
200
- const metaSep = theme.fg("dim", theme.sep.dot);
201
- const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
202
- sourceLines.push(`${theme.fg("accent", title)}${metaSuffix}`);
203
-
204
- const snippetText = typeof src.snippet === "string" ? src.snippet : "";
205
- if (snippetText.trim()) {
206
- const snippetLines = getPreviewLines(
207
- snippetText,
208
- MAX_SNIPPET_LINES,
209
- MAX_SNIPPET_LINE_LEN,
210
- theme.format.ellipsis,
211
- );
212
- for (const snippetLine of snippetLines) {
213
- sourceLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
151
+ const sourceTree = renderTreeList(
152
+ {
153
+ items: sources,
154
+ expanded,
155
+ maxCollapsed: MAX_COLLAPSED_ITEMS,
156
+ itemType: "source",
157
+ renderItem: (src) => {
158
+ const titleText =
159
+ typeof src.title === "string" && src.title.trim()
160
+ ? src.title
161
+ : typeof src.url === "string" && src.url.trim()
162
+ ? src.url
163
+ : "Untitled";
164
+ const title = truncate(titleText, 70, theme.format.ellipsis);
165
+ const url = typeof src.url === "string" ? src.url : "";
166
+ const domain = url ? getDomain(url) : "";
167
+ const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
168
+ const metaParts: string[] = [];
169
+ if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
170
+ if (typeof src.author === "string" && src.author.trim()) metaParts.push(theme.fg("muted", src.author));
171
+ if (age) metaParts.push(theme.fg("muted", age));
172
+ const metaSep = theme.fg("dim", theme.sep.dot);
173
+ const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
174
+ const lines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
175
+ const snippetText = typeof src.snippet === "string" ? src.snippet : "";
176
+ if (snippetText.trim()) {
177
+ const snippetLines = getPreviewLines(
178
+ snippetText,
179
+ MAX_SNIPPET_LINES,
180
+ MAX_SNIPPET_LINE_LEN,
181
+ theme.format.ellipsis,
182
+ );
183
+ for (const snippetLine of snippetLines) {
184
+ lines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
185
+ }
214
186
  }
215
- }
216
-
217
- if (url) {
218
- sourceLines.push(theme.fg("mdLinkUrl", url));
219
- }
220
- }
221
- }
187
+ if (url) lines.push(theme.fg("mdLinkUrl", url));
188
+ return lines;
189
+ },
190
+ },
191
+ theme,
192
+ );
222
193
 
223
- const relatedLines: string[] = [];
224
- if (relatedCount === 0) {
225
- relatedLines.push(theme.fg("muted", "No related questions"));
226
- } else {
227
- const maxRelated = Math.min(MAX_RELATED_QUESTIONS, related.length);
228
- for (let i = 0; i < maxRelated; i++) {
229
- relatedLines.push(theme.fg("muted", `${theme.format.dash} ${related[i]}`));
230
- }
231
- if (relatedCount > maxRelated) {
232
- relatedLines.push(theme.fg("muted", formatMoreItems(relatedCount - maxRelated, "question", theme)));
233
- }
194
+ const relatedLines = related.length > 0 ? related : ["No related questions"];
195
+ const relatedTree = renderTreeList(
196
+ {
197
+ items: relatedLines,
198
+ expanded,
199
+ maxCollapsed: MAX_COLLAPSED_ITEMS,
200
+ itemType: "question",
201
+ renderItem: (line) =>
202
+ theme.fg("muted", line === "No related questions" ? line : `${theme.format.dash} ${line}`),
203
+ },
204
+ theme,
205
+ );
206
+ if (!expanded && relatedCount > MAX_COLLAPSED_ITEMS) {
207
+ relatedTree.push(theme.fg("muted", formatMoreItems(relatedCount - MAX_COLLAPSED_ITEMS, "question", theme)));
234
208
  }
235
209
 
236
210
  const metaLines: string[] = [];
237
211
  metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
238
- if (response.model) {
239
- metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
240
- }
212
+ if (response.model) metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
241
213
  metaLines.push(`${theme.fg("muted", "Sources:")} ${theme.fg("text", String(sourceCount))}`);
242
- if (citationCount > 0) {
214
+ if (citationCount > 0)
243
215
  metaLines.push(`${theme.fg("muted", "Citations:")} ${theme.fg("text", String(citationCount))}`);
244
- }
245
- if (relatedCount > 0) {
246
- metaLines.push(`${theme.fg("muted", "Related:")} ${theme.fg("text", String(relatedCount))}`);
247
- }
216
+ if (relatedCount > 0) metaLines.push(`${theme.fg("muted", "Related:")} ${theme.fg("text", String(relatedCount))}`);
248
217
  if (response.usage) {
249
218
  const usageParts: string[] = [];
250
219
  if (response.usage.inputTokens !== undefined) usageParts.push(`in ${response.usage.inputTokens}`);
251
220
  if (response.usage.outputTokens !== undefined) usageParts.push(`out ${response.usage.outputTokens}`);
252
221
  if (response.usage.totalTokens !== undefined) usageParts.push(`total ${response.usage.totalTokens}`);
253
222
  if (response.usage.searchRequests !== undefined) usageParts.push(`search ${response.usage.searchRequests}`);
254
- if (usageParts.length > 0) {
223
+ if (usageParts.length > 0)
255
224
  metaLines.push(`${theme.fg("muted", "Usage:")} ${theme.fg("text", usageParts.join(theme.sep.dot))}`);
256
- }
257
225
  }
258
226
  if (response.requestId) {
259
227
  metaLines.push(
@@ -264,55 +232,42 @@ export function renderWebSearchResult(
264
232
  );
265
233
  }
266
234
  if (searchQueries.length > 0) {
267
- metaLines.push(`${theme.fg("muted", "Search queries:")} ${theme.fg("text", String(searchQueries.length))}`);
268
- const queryPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
269
- for (const q of queryPreview) {
270
- metaLines.push(theme.fg("muted", `${theme.format.dash} ${truncate(q, MAX_QUERY_LEN, theme.format.ellipsis)}`));
271
- }
272
- if (searchQueries.length > MAX_QUERY_PREVIEW) {
273
- metaLines.push(theme.fg("muted", formatMoreItems(searchQueries.length - MAX_QUERY_PREVIEW, "query", theme)));
274
- }
235
+ const queriesPreview = searchQueries.slice(0, MAX_QUERY_PREVIEW);
236
+ const queryList = queriesPreview.map((q) => truncate(q, MAX_QUERY_LEN, theme.format.ellipsis));
237
+ const suffix = searchQueries.length > queriesPreview.length ? theme.format.ellipsis : "";
238
+ metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
275
239
  }
276
240
 
277
- const sections: Array<{ title: string; icon: string; lines: string[] }> = [
278
- {
279
- title: "Answer",
280
- icon: formatStatusIcon("info", theme),
281
- lines: answerSectionLines,
282
- },
241
+ const sections = [
242
+ ...(queryPreview
243
+ ? [
244
+ {
245
+ lines: [`${theme.fg("muted", "Query:")} ${theme.fg("text", queryPreview)}`],
246
+ },
247
+ ]
248
+ : []),
249
+ { label: theme.fg("toolTitle", "Answer"), lines: answerTree },
283
250
  {
284
- title: "Sources",
285
- icon: formatStatusIcon(sourceCount > 0 ? "success" : "warning", theme),
286
- lines: sourceLines,
287
- },
288
- {
289
- title: "Related",
290
- icon: formatStatusIcon(relatedCount > 0 ? "info" : "warning", theme),
291
- lines: relatedLines,
292
- },
293
- {
294
- title: "Meta",
295
- icon: formatStatusIcon("info", theme),
296
- lines: metaLines,
251
+ label: theme.fg("toolTitle", "Sources"),
252
+ lines: sourceTree.length > 0 ? sourceTree : [theme.fg("muted", "No sources returned")],
297
253
  },
254
+ { label: theme.fg("toolTitle", "Related"), lines: relatedTree },
255
+ { label: theme.fg("toolTitle", "Metadata"), lines: metaLines },
298
256
  ];
299
257
 
300
- for (let i = 0; i < sections.length; i++) {
301
- const section = sections[i];
302
- const isLast = i === sections.length - 1;
303
- const branch = isLast ? theme.tree.last : theme.tree.branch;
304
- const indent = isLast ? " " : `${theme.tree.vertical} `;
305
-
306
- text += `\n ${theme.fg("dim", branch)} ${section.icon} ${theme.fg("accent", section.title)}`;
307
- for (let j = 0; j < section.lines.length; j++) {
308
- const line = section.lines[j];
309
- const isLastLine = j === section.lines.length - 1;
310
- const lineBranch = isLastLine ? theme.tree.last : theme.tree.branch;
311
- text += `\n ${theme.fg("dim", indent)}${theme.fg("dim", lineBranch)} ${line}`;
312
- }
313
- }
314
-
315
- return new Text(text, 0, 0);
258
+ return {
259
+ render: (width: number) =>
260
+ renderOutputBlock(
261
+ {
262
+ header,
263
+ state: sourceCount > 0 ? "success" : "warning",
264
+ sections,
265
+ width,
266
+ },
267
+ theme,
268
+ ),
269
+ invalidate: () => {},
270
+ };
316
271
  }
317
272
 
318
273
  /** Render web search call (query preview) */
@@ -322,11 +277,12 @@ export function renderWebSearchCall(
322
277
  ): Component {
323
278
  const provider = args.provider ?? "auto";
324
279
  const query = truncate(args.query, 80, theme.format.ellipsis);
325
- const text = `${theme.fg("toolTitle", "Web Search")} ${theme.fg("dim", `(${provider})`)} ${theme.fg("muted", query)}`;
280
+ const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query, meta: [provider] }, theme);
326
281
  return new Text(text, 0, 0);
327
282
  }
328
283
 
329
284
  export const webSearchToolRenderer = {
330
285
  renderCall: renderWebSearchCall,
331
286
  renderResult: renderWebSearchResult,
287
+ mergeCallAndResult: true,
332
288
  };
package/tsconfig.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2024",
4
- "module": "ESNext",
5
- "lib": [
6
- "ES2024"
7
- ],
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "moduleResolution": "Bundler",
13
- "resolveJsonModule": true,
14
- "allowImportingTsExtensions": true,
15
- "experimentalDecorators": true,
16
- "emitDecoratorMetadata": true,
17
- "useDefineForClassFields": false,
18
- "types": [
19
- "bun",
20
- "node"
21
- ],
22
- "noEmit": true,
23
- "baseUrl": ".",
24
- "paths": {
25
- "@oh-my-pi/pi-coding-agent": [
26
- "./src/index.ts"
27
- ],
28
- "@oh-my-pi/pi-coding-agent/*": [
29
- "./src/*"
30
- ]
31
- }
32
- },
33
- "include": [
34
- "src/**/*.ts"
35
- ],
36
- "exclude": [
37
- "node_modules",
38
- "dist",
39
- "**/*.test.ts",
40
- "test/**"
41
- ]
42
- }