@oh-my-pi/pi-coding-agent 13.15.3 → 13.16.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.
- package/CHANGELOG.md +16 -16
- package/package.json +7 -7
- package/src/config/model-registry.ts +215 -57
- package/src/config/settings-schema.ts +27 -0
- package/src/extensibility/extensions/types.ts +6 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/hook-editor.ts +57 -8
- package/src/modes/components/model-selector.ts +48 -29
- package/src/modes/components/settings-defs.ts +10 -1
- package/src/modes/components/settings-selector.ts +92 -5
- package/src/modes/controllers/extension-ui-controller.ts +32 -4
- package/src/modes/controllers/input-controller.ts +3 -3
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/rpc/rpc-mode.ts +78 -30
- package/src/modes/rpc/rpc-types.ts +9 -1
- package/src/modes/theme/theme.ts +70 -0
- package/src/modes/types.ts +6 -1
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/tools/ask.md +1 -0
- package/src/prompts/tools/hashline.md +20 -5
- package/src/sdk.ts +9 -1
- package/src/session/agent-session.ts +12 -11
- package/src/system-prompt.ts +63 -2
- package/src/tools/ask.ts +109 -61
- package/src/tools/ast-edit.ts +2 -16
- package/src/tools/ast-grep.ts +2 -17
- package/src/tools/browser.ts +35 -17
- package/src/tools/grep.ts +4 -17
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/render-utils.ts +27 -0
- package/src/tui/tree-list.ts +51 -22
package/src/tools/grep.ts
CHANGED
|
@@ -457,6 +457,7 @@ export const grepToolRenderer = {
|
|
|
457
457
|
items: lines,
|
|
458
458
|
expanded,
|
|
459
459
|
maxCollapsed: COLLAPSED_TEXT_LIMIT,
|
|
460
|
+
maxCollapsedLines: COLLAPSED_TEXT_LIMIT,
|
|
460
461
|
itemType: "item",
|
|
461
462
|
renderItem: line => uiTheme.fg("toolOutput", line),
|
|
462
463
|
},
|
|
@@ -522,19 +523,6 @@ export const grepToolRenderer = {
|
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
|
|
525
|
-
const getCollapsedMatchLimit = (groups: string[][], maxLines: number): number => {
|
|
526
|
-
if (groups.length === 0) return 0;
|
|
527
|
-
let usedLines = 0;
|
|
528
|
-
let count = 0;
|
|
529
|
-
for (const group of groups) {
|
|
530
|
-
if (count > 0 && usedLines + group.length > maxLines) break;
|
|
531
|
-
usedLines += group.length;
|
|
532
|
-
count += 1;
|
|
533
|
-
if (usedLines >= maxLines) break;
|
|
534
|
-
}
|
|
535
|
-
return count;
|
|
536
|
-
};
|
|
537
|
-
|
|
538
526
|
const truncationReasons: string[] = [];
|
|
539
527
|
if (limits?.matchLimit) truncationReasons.push(`limit ${limits.matchLimit.reached} matches`);
|
|
540
528
|
if (limits?.resultLimit) truncationReasons.push(`limit ${limits.resultLimit.reached} results`);
|
|
@@ -551,14 +539,13 @@ export const grepToolRenderer = {
|
|
|
551
539
|
const { expanded } = options;
|
|
552
540
|
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
553
541
|
if (cached?.key === key) return cached.lines;
|
|
554
|
-
const
|
|
555
|
-
? matchGroups.length
|
|
556
|
-
: getCollapsedMatchLimit(matchGroups, COLLAPSED_TEXT_LIMIT);
|
|
542
|
+
const collapsedMatchLineBudget = Math.max(COLLAPSED_TEXT_LIMIT - extraLines.length, 0);
|
|
557
543
|
const matchLines = renderTreeList(
|
|
558
544
|
{
|
|
559
545
|
items: matchGroups,
|
|
560
546
|
expanded,
|
|
561
|
-
maxCollapsed,
|
|
547
|
+
maxCollapsed: matchGroups.length,
|
|
548
|
+
maxCollapsedLines: collapsedMatchLineBudget,
|
|
562
549
|
itemType: "match",
|
|
563
550
|
renderItem: group =>
|
|
564
551
|
group.map(line => {
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -102,9 +102,16 @@ export function expandPath(filePath: string): string {
|
|
|
102
102
|
/**
|
|
103
103
|
* Resolve a path relative to the given cwd.
|
|
104
104
|
* Handles ~ expansion and absolute paths.
|
|
105
|
+
*
|
|
106
|
+
* A bare root slash is treated as a workspace-root alias for tool inputs. Users
|
|
107
|
+
* often pass `/` to mean “search from here”, and letting tools escape to the
|
|
108
|
+
* filesystem root is almost never what they intended.
|
|
105
109
|
*/
|
|
106
110
|
export function resolveToCwd(filePath: string, cwd: string): string {
|
|
107
111
|
const expanded = expandPath(filePath);
|
|
112
|
+
if (/^\/+$/.test(expanded)) {
|
|
113
|
+
return cwd;
|
|
114
|
+
}
|
|
108
115
|
if (path.isAbsolute(expanded)) {
|
|
109
116
|
return expanded;
|
|
110
117
|
}
|
|
@@ -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
|
}
|
package/src/tui/tree-list.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 (
|
|
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
|
|