@oh-my-pi/pi-coding-agent 11.2.3 → 11.3.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 (92) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/examples/extensions/plan-mode.ts +1 -1
  3. package/examples/hooks/qna.ts +1 -1
  4. package/examples/hooks/status-line.ts +1 -1
  5. package/examples/sdk/11-sessions.ts +1 -1
  6. package/package.json +8 -8
  7. package/src/cli/args.ts +9 -6
  8. package/src/cli/update-cli.ts +2 -2
  9. package/src/commands/index/index.ts +2 -5
  10. package/src/commit/agentic/agent.ts +1 -1
  11. package/src/commit/changelog/index.ts +2 -2
  12. package/src/config/keybindings.ts +16 -1
  13. package/src/config/model-registry.ts +25 -20
  14. package/src/config/model-resolver.ts +8 -8
  15. package/src/config/resolve-config-value.ts +92 -0
  16. package/src/config/settings-schema.ts +9 -0
  17. package/src/config.ts +14 -1
  18. package/src/export/html/template.css +7 -0
  19. package/src/export/html/template.generated.ts +1 -1
  20. package/src/export/html/template.js +33 -16
  21. package/src/extensibility/custom-commands/bundled/review/index.ts +1 -1
  22. package/src/extensibility/extensions/index.ts +18 -0
  23. package/src/extensibility/extensions/loader.ts +15 -0
  24. package/src/extensibility/extensions/runner.ts +78 -1
  25. package/src/extensibility/extensions/types.ts +131 -5
  26. package/src/extensibility/extensions/wrapper.ts +1 -1
  27. package/src/extensibility/plugins/git-url.ts +270 -0
  28. package/src/extensibility/plugins/index.ts +2 -0
  29. package/src/extensibility/slash-commands.ts +45 -0
  30. package/src/index.ts +7 -0
  31. package/src/lsp/render.ts +50 -43
  32. package/src/lsp/utils.ts +2 -2
  33. package/src/main.ts +11 -10
  34. package/src/mcp/transports/stdio.ts +3 -5
  35. package/src/modes/components/custom-message.ts +0 -8
  36. package/src/modes/components/diff.ts +1 -7
  37. package/src/modes/components/footer.ts +4 -4
  38. package/src/modes/components/model-selector.ts +4 -0
  39. package/src/modes/components/todo-display.ts +13 -3
  40. package/src/modes/components/tool-execution.ts +30 -16
  41. package/src/modes/components/tree-selector.ts +50 -19
  42. package/src/modes/controllers/event-controller.ts +1 -0
  43. package/src/modes/controllers/extension-ui-controller.ts +34 -2
  44. package/src/modes/controllers/input-controller.ts +47 -33
  45. package/src/modes/controllers/selector-controller.ts +10 -15
  46. package/src/modes/interactive-mode.ts +50 -38
  47. package/src/modes/print-mode.ts +6 -0
  48. package/src/modes/rpc/rpc-client.ts +4 -4
  49. package/src/modes/rpc/rpc-mode.ts +17 -2
  50. package/src/modes/rpc/rpc-types.ts +2 -2
  51. package/src/modes/types.ts +1 -0
  52. package/src/modes/utils/ui-helpers.ts +3 -1
  53. package/src/patch/applicator.ts +2 -3
  54. package/src/patch/fuzzy.ts +1 -1
  55. package/src/patch/shared.ts +74 -61
  56. package/src/prompts/system/system-prompt.md +1 -0
  57. package/src/prompts/tools/task.md +6 -0
  58. package/src/sdk.ts +15 -11
  59. package/src/session/agent-session.ts +72 -23
  60. package/src/session/auth-storage.ts +2 -1
  61. package/src/session/blob-store.ts +105 -0
  62. package/src/session/session-manager.ts +107 -44
  63. package/src/task/executor.ts +19 -9
  64. package/src/task/render.ts +80 -58
  65. package/src/tools/ask.ts +28 -5
  66. package/src/tools/bash.ts +47 -39
  67. package/src/tools/browser.ts +248 -26
  68. package/src/tools/calculator.ts +42 -23
  69. package/src/tools/fetch.ts +33 -16
  70. package/src/tools/find.ts +57 -22
  71. package/src/tools/grep.ts +54 -25
  72. package/src/tools/index.ts +5 -5
  73. package/src/tools/notebook.ts +19 -6
  74. package/src/tools/path-utils.ts +26 -1
  75. package/src/tools/python.ts +20 -14
  76. package/src/tools/read.ts +21 -8
  77. package/src/tools/render-utils.ts +5 -45
  78. package/src/tools/ssh.ts +59 -53
  79. package/src/tools/submit-result.ts +2 -2
  80. package/src/tools/todo-write.ts +32 -14
  81. package/src/tools/truncate.ts +1 -1
  82. package/src/tools/write.ts +39 -24
  83. package/src/tui/output-block.ts +61 -3
  84. package/src/tui/tree-list.ts +4 -4
  85. package/src/tui/utils.ts +71 -1
  86. package/src/utils/frontmatter.ts +1 -1
  87. package/src/utils/title-generator.ts +1 -1
  88. package/src/utils/tools-manager.ts +18 -2
  89. package/src/web/scrapers/osv.ts +4 -1
  90. package/src/web/scrapers/youtube.ts +1 -1
  91. package/src/web/search/index.ts +1 -1
  92. package/src/web/search/render.ts +96 -90
@@ -21,7 +21,8 @@ import {
21
21
  TRUNCATE_LENGTHS,
22
22
  truncateToWidth,
23
23
  } from "../../tools/render-utils";
24
- import { renderOutputBlock, renderStatusLine, renderTreeList } from "../../tui";
24
+ import { renderStatusLine, renderTreeList } from "../../tui";
25
+ import { CachedOutputBlock } from "../../tui/output-block";
25
26
  import type { SearchResponse } from "./types";
26
27
 
27
28
  const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
@@ -79,7 +80,6 @@ export function renderSearchResult(
79
80
  maxAnswerLines?: number;
80
81
  },
81
82
  ): Component {
82
- const { expanded } = options;
83
83
  const details = result.details;
84
84
 
85
85
  // Handle error case
@@ -90,7 +90,7 @@ export function renderSearchResult(
90
90
  const rawText = result.content?.find(block => block.type === "text")?.text?.trim() ?? "";
91
91
  const response = details?.response;
92
92
  if (!response) {
93
- return renderFallbackText(rawText, expanded, theme);
93
+ return renderFallbackText(rawText, options.expanded, theme);
94
94
  }
95
95
 
96
96
  const sources = Array.isArray(response.sources) ? response.sources : [];
@@ -112,12 +112,6 @@ export function renderSearchResult(
112
112
  .map(l => l.trim())
113
113
  : [];
114
114
  const totalAnswerLines = answerLines.length;
115
- const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
116
- const answerPreview = contentText
117
- ? args?.allowLongAnswer
118
- ? answerLines.slice(0, args.maxAnswerLines ?? answerLines.length)
119
- : getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN)
120
- : [];
121
115
 
122
116
  const providerLabel = provider !== "none" ? getSearchProvider(provider).label : "None";
123
117
  const queryPreview = args?.query
@@ -135,46 +129,6 @@ export function renderSearchResult(
135
129
  theme,
136
130
  );
137
131
 
138
- const remainingAnswer = totalAnswerLines - answerPreview.length;
139
-
140
- const sourceTree = renderTreeList(
141
- {
142
- items: sources,
143
- expanded,
144
- maxCollapsed: MAX_COLLAPSED_ITEMS,
145
- itemType: "source",
146
- renderItem: src => {
147
- const titleText =
148
- typeof src.title === "string" && src.title.trim()
149
- ? src.title
150
- : typeof src.url === "string" && src.url.trim()
151
- ? src.url
152
- : "Untitled";
153
- const title = truncateToWidth(titleText, 70);
154
- const url = typeof src.url === "string" ? src.url : "";
155
- const domain = url ? getDomain(url) : "";
156
- const age = formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
157
- const metaParts: string[] = [];
158
- if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
159
- if (typeof src.author === "string" && src.author.trim()) metaParts.push(theme.fg("muted", src.author));
160
- if (age) metaParts.push(theme.fg("muted", age));
161
- const metaSep = theme.fg("dim", theme.sep.dot);
162
- const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
163
- const lines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
164
- const snippetText = typeof src.snippet === "string" ? src.snippet : "";
165
- if (snippetText.trim()) {
166
- const snippetLines = getPreviewLines(snippetText, MAX_SNIPPET_LINES, MAX_SNIPPET_LINE_LEN);
167
- for (const snippetLine of snippetLines) {
168
- lines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
169
- }
170
- }
171
- if (url) lines.push(theme.fg("mdLinkUrl", url));
172
- return lines;
173
- },
174
- },
175
- theme,
176
- );
177
-
178
132
  const metaLines: string[] = [];
179
133
  metaLines.push(`${theme.fg("muted", "Provider:")} ${theme.fg("text", providerLabel)}`);
180
134
  if (response.model) metaLines.push(`${theme.fg("muted", "Model:")} ${theme.fg("text", response.model)}`);
@@ -202,9 +156,94 @@ export function renderSearchResult(
202
156
  metaLines.push(`${theme.fg("muted", "Queries:")} ${theme.fg("text", queryList.join("; "))}${suffix}`);
203
157
  }
204
158
 
159
+ const outputBlock = new CachedOutputBlock();
160
+
205
161
  return {
206
- render: (width: number) =>
207
- renderOutputBlock(
162
+ render(width: number): string[] {
163
+ // Read mutable state at render time
164
+ const { expanded } = options;
165
+
166
+ // Expanded-dependent computations
167
+ const answerLimit = expanded ? MAX_EXPANDED_ANSWER_LINES : MAX_COLLAPSED_ANSWER_LINES;
168
+ const answerPreview = contentText
169
+ ? args?.allowLongAnswer
170
+ ? answerLines.slice(0, args.maxAnswerLines ?? answerLines.length)
171
+ : getPreviewLines(contentText, answerLimit, MAX_ANSWER_LINE_LEN)
172
+ : [];
173
+ const remainingAnswer = totalAnswerLines - answerPreview.length;
174
+
175
+ const sourceTree = renderTreeList(
176
+ {
177
+ items: sources,
178
+ expanded,
179
+ maxCollapsed: MAX_COLLAPSED_ITEMS,
180
+ itemType: "source",
181
+ renderItem: src => {
182
+ const titleText =
183
+ typeof src.title === "string" && src.title.trim()
184
+ ? src.title
185
+ : typeof src.url === "string" && src.url.trim()
186
+ ? src.url
187
+ : "Untitled";
188
+ const title = truncateToWidth(titleText, 70);
189
+ const url = typeof src.url === "string" ? src.url : "";
190
+ const domain = url ? getDomain(url) : "";
191
+ const age =
192
+ formatAge(src.ageSeconds) || (typeof src.publishedDate === "string" ? src.publishedDate : "");
193
+ const metaParts: string[] = [];
194
+ if (domain) metaParts.push(theme.fg("dim", `(${domain})`));
195
+ if (typeof src.author === "string" && src.author.trim())
196
+ metaParts.push(theme.fg("muted", src.author));
197
+ if (age) metaParts.push(theme.fg("muted", age));
198
+ const metaSep = theme.fg("dim", theme.sep.dot);
199
+ const metaSuffix = metaParts.length > 0 ? ` ${metaParts.join(metaSep)}` : "";
200
+ const srcLines: string[] = [`${theme.fg("accent", title)}${metaSuffix}`];
201
+ const snippetText = typeof src.snippet === "string" ? src.snippet : "";
202
+ if (snippetText.trim()) {
203
+ const snippetLines = getPreviewLines(snippetText, MAX_SNIPPET_LINES, MAX_SNIPPET_LINE_LEN);
204
+ for (const snippetLine of snippetLines) {
205
+ srcLines.push(theme.fg("muted", `${theme.format.dash} ${snippetLine}`));
206
+ }
207
+ }
208
+ if (url) srcLines.push(theme.fg("mdLinkUrl", url));
209
+ return srcLines;
210
+ },
211
+ },
212
+ theme,
213
+ );
214
+
215
+ // Build answer section
216
+ const answerState = sourceCount > 0 ? "success" : "warning";
217
+ const borderColor: "warning" | "dim" = answerState === "warning" ? "warning" : "dim";
218
+ const border = (t: string) => theme.fg(borderColor, t);
219
+ const contentPrefix = border(`${theme.boxSharp.vertical} `);
220
+ const contentSuffix = border(theme.boxSharp.vertical);
221
+ const contentWidth = Math.max(0, width - visibleWidth(contentPrefix) - visibleWidth(contentSuffix));
222
+ const answerTreeLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
223
+ const answerTree = renderTreeList(
224
+ {
225
+ items: answerTreeLines,
226
+ expanded: true,
227
+ maxCollapsed: answerTreeLines.length,
228
+ itemType: "line",
229
+ renderItem: (line, context) => {
230
+ const coloredLine =
231
+ line === "No answer text returned" ? theme.fg("muted", line) : theme.fg("dim", line);
232
+ if (!args?.allowLongAnswer) {
233
+ return coloredLine;
234
+ }
235
+ const prefixWidth = visibleWidth(context.continuePrefix);
236
+ const wrapWidth = Math.max(10, contentWidth - prefixWidth);
237
+ return wrapTextWithAnsi(coloredLine, wrapWidth);
238
+ },
239
+ },
240
+ theme,
241
+ );
242
+ if (remainingAnswer > 0) {
243
+ answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line")));
244
+ }
245
+
246
+ return outputBlock.render(
208
247
  {
209
248
  header,
210
249
  state: sourceCount > 0 ? "success" : "warning",
@@ -218,43 +257,7 @@ export function renderSearchResult(
218
257
  : []),
219
258
  {
220
259
  label: theme.fg("toolTitle", "Answer"),
221
- lines: (() => {
222
- const state = sourceCount > 0 ? "success" : "warning";
223
- const borderColor: "warning" | "dim" = state === "warning" ? "warning" : "dim";
224
- const border = (text: string) => theme.fg(borderColor, text);
225
- const contentPrefix = border(`${theme.boxSharp.vertical} `);
226
- const contentSuffix = border(theme.boxSharp.vertical);
227
- const contentWidth = Math.max(
228
- 0,
229
- width - visibleWidth(contentPrefix) - visibleWidth(contentSuffix),
230
- );
231
- const answerTreeLines = answerPreview.length > 0 ? answerPreview : ["No answer text returned"];
232
- const answerTree = renderTreeList(
233
- {
234
- items: answerTreeLines,
235
- expanded: true,
236
- maxCollapsed: answerTreeLines.length,
237
- itemType: "line",
238
- renderItem: (line, context) => {
239
- const coloredLine =
240
- line === "No answer text returned"
241
- ? theme.fg("muted", line)
242
- : theme.fg("dim", line);
243
- if (!args?.allowLongAnswer) {
244
- return coloredLine;
245
- }
246
- const prefixWidth = visibleWidth(context.continuePrefix);
247
- const wrapWidth = Math.max(10, contentWidth - prefixWidth);
248
- return wrapTextWithAnsi(coloredLine, wrapWidth);
249
- },
250
- },
251
- theme,
252
- );
253
- if (remainingAnswer > 0) {
254
- answerTree.push(theme.fg("muted", formatMoreItems(remainingAnswer, "line")));
255
- }
256
- return answerTree;
257
- })(),
260
+ lines: answerTree,
258
261
  },
259
262
  {
260
263
  label: theme.fg("toolTitle", "Sources"),
@@ -265,8 +268,11 @@ export function renderSearchResult(
265
268
  width,
266
269
  },
267
270
  theme,
268
- ),
269
- invalidate: () => {},
271
+ );
272
+ },
273
+ invalidate() {
274
+ outputBlock.invalidate();
275
+ },
270
276
  };
271
277
  }
272
278