@oh-my-pi/pi-coding-agent 13.15.3 → 13.16.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 (50) hide show
  1. package/CHANGELOG.md +30 -16
  2. package/package.json +7 -7
  3. package/src/commit/agentic/tools/analyze-file.ts +1 -0
  4. package/src/config/model-registry.ts +215 -57
  5. package/src/config/settings-schema.ts +27 -0
  6. package/src/extensibility/custom-tools/types.ts +3 -0
  7. package/src/extensibility/extensions/runner.ts +7 -0
  8. package/src/extensibility/extensions/types.ts +10 -1
  9. package/src/extensibility/hooks/types.ts +1 -1
  10. package/src/internal-urls/docs-index.generated.ts +1 -1
  11. package/src/ipy/cancellation.ts +28 -0
  12. package/src/ipy/executor.ts +252 -77
  13. package/src/ipy/kernel.ts +181 -35
  14. package/src/ipy/modules.ts +39 -4
  15. package/src/modes/acp/acp-agent.ts +1 -0
  16. package/src/modes/components/hook-editor.ts +57 -8
  17. package/src/modes/components/model-selector.ts +48 -29
  18. package/src/modes/components/settings-defs.ts +10 -1
  19. package/src/modes/components/settings-selector.ts +92 -5
  20. package/src/modes/controllers/extension-ui-controller.ts +35 -4
  21. package/src/modes/controllers/input-controller.ts +4 -3
  22. package/src/modes/controllers/selector-controller.ts +2 -2
  23. package/src/modes/interactive-mode.ts +7 -2
  24. package/src/modes/print-mode.ts +1 -0
  25. package/src/modes/prompt-action-autocomplete.ts +5 -3
  26. package/src/modes/rpc/rpc-mode.ts +79 -30
  27. package/src/modes/rpc/rpc-types.ts +9 -1
  28. package/src/modes/theme/theme.ts +70 -0
  29. package/src/modes/types.ts +6 -1
  30. package/src/prompts/system/custom-system-prompt.md +5 -0
  31. package/src/prompts/system/system-prompt.md +6 -0
  32. package/src/prompts/tools/ask.md +1 -0
  33. package/src/prompts/tools/grep.md +1 -1
  34. package/src/prompts/tools/hashline.md +20 -5
  35. package/src/sdk.ts +26 -2
  36. package/src/session/agent-session.ts +18 -11
  37. package/src/system-prompt.ts +63 -2
  38. package/src/task/executor.ts +4 -0
  39. package/src/task/index.ts +2 -0
  40. package/src/tools/ask.ts +109 -61
  41. package/src/tools/ast-edit.ts +2 -16
  42. package/src/tools/ast-grep.ts +2 -17
  43. package/src/tools/browser.ts +35 -17
  44. package/src/tools/find.ts +1 -0
  45. package/src/tools/grep.ts +25 -34
  46. package/src/tools/index.ts +3 -0
  47. package/src/tools/path-utils.ts +7 -0
  48. package/src/tools/python.ts +3 -2
  49. package/src/tools/render-utils.ts +27 -0
  50. package/src/tui/tree-list.ts +51 -22
@@ -8,6 +8,7 @@ import * as os from "node:os";
8
8
  import { type Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
9
9
  import { getIndentation, pluralize } from "@oh-my-pi/pi-utils";
10
10
  import type { Theme } from "../modes/theme/theme";
11
+ import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
11
12
 
12
13
  export { Ellipsis, truncateToWidth } from "@oh-my-pi/pi-tui";
13
14
 
@@ -527,6 +528,32 @@ export function shortenPath(filePath: string, homeDir?: string): string {
527
528
  return filePath;
528
529
  }
529
530
 
531
+ export function formatScreenshot(opts: {
532
+ saveFullRes: boolean;
533
+ savedMimeType: string;
534
+ savedByteLength: number;
535
+ dest: string;
536
+ resized: ResizedImage;
537
+ }): string[] {
538
+ const lines = ["Screenshot captured"];
539
+ if (opts.saveFullRes) {
540
+ lines.push(
541
+ `Saved: ${opts.savedMimeType} (${(opts.savedByteLength / 1024).toFixed(2)} KB) to ${shortenPath(opts.dest)}`,
542
+ );
543
+ lines.push(
544
+ `Model: ${opts.resized.mimeType} (${(opts.resized.buffer.length / 1024).toFixed(2)} KB, ${opts.resized.width}x${opts.resized.height})`,
545
+ );
546
+ } else {
547
+ lines.push(`Format: ${opts.resized.mimeType} (${(opts.resized.buffer.length / 1024).toFixed(2)} KB)`);
548
+ lines.push(`Dimensions: ${opts.resized.width}x${opts.resized.height}`);
549
+ }
550
+ const dimensionNote = formatDimensionNote(opts.resized);
551
+ if (dimensionNote) {
552
+ lines.push(dimensionNote);
553
+ }
554
+ return lines;
555
+ }
556
+
530
557
  export function wrapBrackets(text: string, theme: Theme): string {
531
558
  return `${theme.format.bracketLeft}${text}${theme.format.bracketRight}`;
532
559
  }
@@ -10,42 +10,71 @@ export interface TreeListOptions<T> {
10
10
  items: T[];
11
11
  expanded?: boolean;
12
12
  maxCollapsed?: number;
13
+ /** Strict total-line budget for collapsed mode. When set (and not expanded),
14
+ * rendered item lines plus the trailing summary line must fit within this budget.
15
+ */
16
+ maxCollapsedLines?: number;
13
17
  itemType?: string;
18
+ /** Called once per item with `isLast: false` during budget calculation;
19
+ * line count MUST NOT vary based on `isLast`. */
14
20
  renderItem: (item: T, context: TreeContext) => string | string[];
15
21
  }
16
22
 
17
23
  export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): string[] {
18
- const { items, expanded = false, maxCollapsed = 8, itemType = "item", renderItem } = options;
19
- const lines: string[] = [];
24
+ const { items, expanded = false, maxCollapsed = 8, maxCollapsedLines, itemType = "item", renderItem } = options;
20
25
  const maxItems = expanded ? items.length : Math.min(items.length, maxCollapsed);
26
+ const linesBudget = !expanded && maxCollapsedLines !== undefined ? maxCollapsedLines : Infinity;
21
27
 
28
+ // Pre-render each candidate item once.
29
+ // isLast cannot be known at this point (fittingCount is not yet determined);
30
+ // renderItem implementations MUST NOT vary line count based on isLast.
31
+ const preRendered: string[][] = [];
22
32
  for (let i = 0; i < maxItems; i++) {
23
- const isLast = i === maxItems - 1 && (expanded || items.length <= maxCollapsed);
24
- const branch = getTreeBranch(isLast, theme);
25
- const prefix = `${theme.fg("dim", branch)} `;
26
- const continuePrefix = `${theme.fg("dim", getTreeContinuePrefix(isLast, theme))}`;
27
- const context: TreeContext = {
33
+ const rendered = renderItem(items[i], {
28
34
  index: i,
29
- isLast,
35
+ isLast: false,
30
36
  depth: 0,
31
37
  theme,
32
- prefix,
33
- continuePrefix,
34
- };
35
- const rendered = renderItem(items[i], context);
36
- if (Array.isArray(rendered)) {
37
- if (rendered.length === 0) continue;
38
- lines.push(`${prefix}${replaceTabs(rendered[0])}`);
39
- for (let j = 1; j < rendered.length; j++) {
40
- lines.push(`${continuePrefix}${replaceTabs(rendered[j])}`);
41
- }
42
- } else {
43
- lines.push(`${prefix}${replaceTabs(rendered)}`);
38
+ prefix: "",
39
+ continuePrefix: "",
40
+ });
41
+ preRendered.push(Array.isArray(rendered) ? rendered : rendered ? [rendered] : []);
42
+ }
43
+
44
+ // Determine how many items fit within the line budget.
45
+ let fittingCount = maxItems;
46
+ let fittedLineCount = 0;
47
+ if (linesBudget !== Infinity) {
48
+ fittingCount = 0;
49
+ for (let i = 0; i < maxItems; i++) {
50
+ const count = preRendered[i]!.length;
51
+ const remainingAfter = items.length - (i + 1);
52
+ const reservedSummaryLines = remainingAfter > 0 ? 1 : 0;
53
+ if (fittedLineCount + count + reservedSummaryLines > linesBudget) break;
54
+ fittedLineCount += count;
55
+ fittingCount = i + 1;
56
+ }
57
+ }
58
+
59
+ const remaining = items.length - fittingCount;
60
+ const hasSummary = !expanded && remaining > 0 && (linesBudget === Infinity || fittedLineCount < linesBudget);
61
+
62
+ // Emit pre-rendered content with correct isLast-based branch prefixes.
63
+ const lines: string[] = [];
64
+ for (let i = 0; i < fittingCount; i++) {
65
+ const isLast = !hasSummary && i === fittingCount - 1;
66
+ const branch = getTreeBranch(isLast, theme);
67
+ const prefix = `${theme.fg("dim", branch)} `;
68
+ const continuePrefix = `${theme.fg("dim", getTreeContinuePrefix(isLast, theme))}`;
69
+ const itemLines = preRendered[i]!;
70
+ if (itemLines.length === 0) continue;
71
+ lines.push(`${prefix}${replaceTabs(itemLines[0]!)}`);
72
+ for (let j = 1; j < itemLines.length; j++) {
73
+ lines.push(`${continuePrefix}${replaceTabs(itemLines[j]!)}`);
44
74
  }
45
75
  }
46
76
 
47
- if (!expanded && items.length > maxItems) {
48
- const remaining = items.length - maxItems;
77
+ if (hasSummary) {
49
78
  lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
50
79
  }
51
80