@oh-my-pi/pi-coding-agent 3.13.1337 → 3.15.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 +88 -0
- package/docs/theme.md +38 -5
- package/examples/sdk/11-sessions.ts +2 -2
- package/package.json +7 -4
- package/src/cli/file-processor.ts +51 -2
- package/src/cli/plugin-cli.ts +25 -19
- package/src/cli/update-cli.ts +4 -3
- package/src/core/agent-session.ts +31 -4
- package/src/core/compaction/branch-summarization.ts +4 -32
- package/src/core/compaction/compaction.ts +6 -84
- package/src/core/compaction/utils.ts +2 -3
- package/src/core/custom-tools/types.ts +2 -0
- package/src/core/export-html/index.ts +1 -1
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +0 -1
- package/src/core/hooks/types.ts +2 -2
- package/src/core/plugins/doctor.ts +9 -1
- package/src/core/sdk.ts +2 -1
- package/src/core/session-manager.ts +552 -41
- package/src/core/settings-manager.ts +174 -0
- package/src/core/system-prompt.ts +9 -14
- package/src/core/title-generator.ts +2 -8
- package/src/core/tools/ask.ts +19 -37
- package/src/core/tools/bash.ts +2 -37
- package/src/core/tools/edit.ts +2 -9
- package/src/core/tools/exa/render.ts +52 -48
- package/src/core/tools/find.ts +10 -8
- package/src/core/tools/grep.ts +45 -17
- package/src/core/tools/ls.ts +22 -2
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +3 -0
- package/src/core/tools/lsp/index.ts +107 -55
- package/src/core/tools/lsp/render.ts +192 -79
- package/src/core/tools/lsp/types.ts +27 -0
- package/src/core/tools/lsp/utils.ts +62 -22
- package/src/core/tools/notebook.ts +9 -1
- package/src/core/tools/output.ts +37 -14
- package/src/core/tools/read.ts +349 -34
- package/src/core/tools/renderers.ts +290 -89
- package/src/core/tools/review.ts +12 -5
- package/src/core/tools/task/agents.ts +5 -5
- package/src/core/tools/task/commands.ts +3 -3
- package/src/core/tools/task/executor.ts +33 -1
- package/src/core/tools/task/index.ts +93 -6
- package/src/core/tools/task/render.ts +147 -66
- package/src/core/tools/task/types.ts +14 -9
- package/src/core/tools/web-fetch.ts +242 -103
- package/src/core/tools/web-search/index.ts +64 -20
- package/src/core/tools/web-search/providers/exa.ts +68 -172
- package/src/core/tools/web-search/render.ts +264 -74
- package/src/core/tools/write.ts +2 -8
- package/src/main.ts +10 -6
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +9 -4
- package/src/modes/interactive/components/bash-execution.ts +6 -3
- package/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
- package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
- package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
- package/src/modes/interactive/components/hook-message.ts +2 -2
- package/src/modes/interactive/components/hook-selector.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +22 -9
- package/src/modes/interactive/components/oauth-selector.ts +20 -4
- package/src/modes/interactive/components/plugin-settings.ts +4 -2
- package/src/modes/interactive/components/session-selector.ts +9 -6
- package/src/modes/interactive/components/settings-defs.ts +285 -1
- package/src/modes/interactive/components/settings-selector.ts +176 -3
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +169 -233
- package/src/modes/interactive/components/tool-execution.ts +446 -211
- package/src/modes/interactive/components/tree-selector.ts +17 -6
- package/src/modes/interactive/components/ttsr-notification.ts +4 -4
- package/src/modes/interactive/components/welcome.ts +27 -19
- package/src/modes/interactive/interactive-mode.ts +98 -13
- package/src/modes/interactive/theme/dark.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +3 -2
- package/src/modes/interactive/theme/theme-schema.json +120 -4
- package/src/modes/interactive/theme/theme.ts +1228 -14
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/init.md +30 -0
- package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/commands/init.md +0 -20
- /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
|
@@ -11,17 +11,11 @@ import type { RenderResultOptions } from "../../custom-tools/types";
|
|
|
11
11
|
import { logger } from "../../logger";
|
|
12
12
|
import type { ExaRenderDetails } from "./types";
|
|
13
13
|
|
|
14
|
-
// Tree formatting constants
|
|
15
|
-
const TREE_MID = "├─";
|
|
16
|
-
const TREE_END = "└─";
|
|
17
|
-
const TREE_PIPE = "│";
|
|
18
|
-
const TREE_SPACE = " ";
|
|
19
|
-
const TREE_HOOK = "⎿";
|
|
20
|
-
|
|
21
14
|
/** Truncate text to max length with ellipsis */
|
|
22
|
-
function truncate(text: string, maxLen: number): string {
|
|
15
|
+
function truncate(text: string, maxLen: number, ellipsis: string): string {
|
|
23
16
|
if (text.length <= maxLen) return text;
|
|
24
|
-
|
|
17
|
+
const sliceLen = Math.max(0, maxLen - ellipsis.length);
|
|
18
|
+
return `${text.slice(0, sliceLen)}${ellipsis}`;
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
/** Extract domain from URL */
|
|
@@ -35,16 +29,16 @@ function getDomain(url: string): string {
|
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
/** Get first N lines of text as preview */
|
|
38
|
-
function getPreviewLines(text: string, maxLines: number, maxLineLen: number): string[] {
|
|
32
|
+
function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis: string): string[] {
|
|
39
33
|
const lines = text.split("\n").filter((l) => l.trim());
|
|
40
|
-
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen));
|
|
34
|
+
return lines.slice(0, maxLines).map((l) => truncate(l.trim(), maxLineLen, ellipsis));
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
/** Render Exa result with tree-based layout */
|
|
44
38
|
export function renderExaResult(
|
|
45
39
|
result: { content: Array<{ type: string; text?: string }>; details?: ExaRenderDetails },
|
|
46
40
|
options: RenderResultOptions,
|
|
47
|
-
|
|
41
|
+
uiTheme: Theme,
|
|
48
42
|
): Component {
|
|
49
43
|
const { expanded } = options;
|
|
50
44
|
const details = result.details;
|
|
@@ -52,7 +46,7 @@ export function renderExaResult(
|
|
|
52
46
|
// Handle error case
|
|
53
47
|
if (details?.error) {
|
|
54
48
|
logger.error("Exa render error", { error: details.error, toolName: details.toolName });
|
|
55
|
-
return new Text(
|
|
49
|
+
return new Text(uiTheme.fg("error", `Error: ${details.error}`), 0, 0);
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
const response = details?.response;
|
|
@@ -60,15 +54,15 @@ export function renderExaResult(
|
|
|
60
54
|
// Non-search response: show raw result
|
|
61
55
|
if (details?.raw) {
|
|
62
56
|
const rawText = typeof details.raw === "string" ? details.raw : JSON.stringify(details.raw, null, 2);
|
|
63
|
-
const preview = expanded ? rawText : truncate(rawText, 200);
|
|
57
|
+
const preview = expanded ? rawText : truncate(rawText, 200, uiTheme.format.ellipsis);
|
|
64
58
|
const toolLabel = details?.toolName ?? "Exa";
|
|
65
59
|
return new Text(
|
|
66
|
-
`${
|
|
60
|
+
`${uiTheme.fg("success", uiTheme.format.bullet)} ${uiTheme.fg("toolTitle", toolLabel)}\n ${uiTheme.fg("dim", uiTheme.tree.vertical)} ${preview}`,
|
|
67
61
|
0,
|
|
68
62
|
0,
|
|
69
63
|
);
|
|
70
64
|
}
|
|
71
|
-
return new Text(
|
|
65
|
+
return new Text(uiTheme.fg("error", "No response data"), 0, 0);
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
const results = response.results ?? [];
|
|
@@ -76,21 +70,22 @@ export function renderExaResult(
|
|
|
76
70
|
const cost = response.costDollars?.total;
|
|
77
71
|
const time = response.searchTime;
|
|
78
72
|
|
|
79
|
-
// Build header:
|
|
80
|
-
const icon =
|
|
81
|
-
|
|
73
|
+
// Build header: Exa Search · N results · $X.XX · Xs
|
|
74
|
+
const icon =
|
|
75
|
+
resultCount > 0 ? uiTheme.fg("success", uiTheme.format.bullet) : uiTheme.fg("warning", uiTheme.format.bullet);
|
|
76
|
+
const expandHint = expanded ? "" : uiTheme.fg("dim", " (Ctrl+O for full results)");
|
|
82
77
|
const toolLabel = details?.toolName ?? "Exa Search";
|
|
83
78
|
|
|
84
|
-
let headerParts = `${icon} ${
|
|
79
|
+
let headerParts = `${icon} ${uiTheme.fg("toolTitle", toolLabel)}${uiTheme.sep.dot}${uiTheme.fg(
|
|
85
80
|
"dim",
|
|
86
81
|
`${resultCount} result${resultCount !== 1 ? "s" : ""}`,
|
|
87
82
|
)}`;
|
|
88
83
|
|
|
89
84
|
if (cost !== undefined) {
|
|
90
|
-
headerParts +=
|
|
85
|
+
headerParts += `${uiTheme.sep.dot}${uiTheme.fg("muted", `$${cost.toFixed(4)}`)}`;
|
|
91
86
|
}
|
|
92
87
|
if (time !== undefined) {
|
|
93
|
-
headerParts +=
|
|
88
|
+
headerParts += `${uiTheme.sep.dot}${uiTheme.fg("muted", `${time.toFixed(2)}s`)}`;
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
let text = headerParts + expandHint;
|
|
@@ -100,19 +95,22 @@ export function renderExaResult(
|
|
|
100
95
|
if (resultCount > 0) {
|
|
101
96
|
const first = results[0];
|
|
102
97
|
const previewText = first.text ?? first.title ?? "";
|
|
103
|
-
const previewLines = getPreviewLines(previewText, 3, 100);
|
|
98
|
+
const previewLines = getPreviewLines(previewText, 3, 100, uiTheme.format.ellipsis);
|
|
104
99
|
|
|
105
100
|
for (const line of previewLines) {
|
|
106
|
-
text += `\n ${
|
|
101
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.vertical)} ${uiTheme.fg("dim", line)}`;
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
const totalLines = previewText.split("\n").filter((l) => l.trim()).length;
|
|
110
105
|
if (totalLines > 3) {
|
|
111
|
-
text += `\n ${
|
|
106
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.vertical)} ${uiTheme.fg(
|
|
107
|
+
"muted",
|
|
108
|
+
`${uiTheme.format.ellipsis} ${totalLines - 3} more lines`,
|
|
109
|
+
)}`;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
if (resultCount > 1) {
|
|
115
|
-
text += `\n ${
|
|
113
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg(
|
|
116
114
|
"muted",
|
|
117
115
|
`${resultCount - 1} more result${resultCount !== 2 ? "s" : ""}`,
|
|
118
116
|
)}`;
|
|
@@ -121,38 +119,35 @@ export function renderExaResult(
|
|
|
121
119
|
} else {
|
|
122
120
|
// Expanded view: full results tree
|
|
123
121
|
if (resultCount > 0) {
|
|
124
|
-
text += `\n ${
|
|
125
|
-
text += `\n ${
|
|
122
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.vertical)}`;
|
|
123
|
+
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.fg("accent", "Results")}`;
|
|
126
124
|
|
|
127
125
|
for (let i = 0; i < results.length; i++) {
|
|
128
126
|
const res = results[i];
|
|
129
127
|
const isLast = i === results.length - 1;
|
|
130
|
-
const branch = isLast ?
|
|
131
|
-
const cont = isLast ?
|
|
128
|
+
const branch = isLast ? uiTheme.tree.last : uiTheme.tree.branch;
|
|
129
|
+
const cont = isLast ? " " : uiTheme.tree.vertical;
|
|
132
130
|
|
|
133
131
|
// Title + domain
|
|
134
|
-
const title = truncate(res.title ?? "Untitled", 60);
|
|
132
|
+
const title = truncate(res.title ?? "Untitled", 60, uiTheme.format.ellipsis);
|
|
135
133
|
const domain = res.url ? getDomain(res.url) : "";
|
|
136
|
-
const domainPart = domain ?
|
|
134
|
+
const domainPart = domain ? uiTheme.fg("dim", ` (${domain})`) : "";
|
|
137
135
|
|
|
138
|
-
text += `\n ${
|
|
139
|
-
"accent",
|
|
140
|
-
title,
|
|
141
|
-
)}${domainPart}`;
|
|
136
|
+
text += `\n ${uiTheme.fg("dim", " ")} ${uiTheme.fg("dim", branch)} ${uiTheme.fg("accent", title)}${domainPart}`;
|
|
142
137
|
|
|
143
138
|
// URL
|
|
144
139
|
if (res.url) {
|
|
145
|
-
text += `\n ${
|
|
140
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("dim", uiTheme.tree.hook)} ${uiTheme.fg("mdLinkUrl", res.url)}`;
|
|
146
141
|
}
|
|
147
142
|
|
|
148
143
|
// Author
|
|
149
144
|
if (res.author) {
|
|
150
|
-
text += `\n ${
|
|
145
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("muted", `Author: ${res.author}`)}`;
|
|
151
146
|
}
|
|
152
147
|
|
|
153
148
|
// Published date
|
|
154
149
|
if (res.publishedDate) {
|
|
155
|
-
text += `\n ${
|
|
150
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("muted", `Published: ${res.publishedDate}`)}`;
|
|
156
151
|
}
|
|
157
152
|
|
|
158
153
|
// Text content
|
|
@@ -160,22 +155,31 @@ export function renderExaResult(
|
|
|
160
155
|
const textLines = res.text.split("\n").filter((l) => l.trim());
|
|
161
156
|
const displayLines = textLines.slice(0, 5); // Show first 5 lines
|
|
162
157
|
for (const line of displayLines) {
|
|
163
|
-
text += `\n ${
|
|
158
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${truncate(line.trim(), 90, uiTheme.format.ellipsis)}`;
|
|
164
159
|
}
|
|
165
160
|
if (textLines.length > 5) {
|
|
166
|
-
text += `\n ${
|
|
161
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg(
|
|
162
|
+
"muted",
|
|
163
|
+
`${uiTheme.format.ellipsis} ${textLines.length - 5} more lines`,
|
|
164
|
+
)}`;
|
|
167
165
|
}
|
|
168
166
|
}
|
|
169
167
|
|
|
170
168
|
// Highlights
|
|
171
169
|
if (res.highlights?.length) {
|
|
172
|
-
text += `\n ${
|
|
170
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg("accent", "Highlights:")}`;
|
|
173
171
|
for (let j = 0; j < Math.min(res.highlights.length, 3); j++) {
|
|
174
172
|
const h = res.highlights[j];
|
|
175
|
-
text += `\n ${
|
|
173
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg(
|
|
174
|
+
"muted",
|
|
175
|
+
`${uiTheme.format.bullet} ${truncate(h, 80, uiTheme.format.ellipsis)}`,
|
|
176
|
+
)}`;
|
|
176
177
|
}
|
|
177
178
|
if (res.highlights.length > 3) {
|
|
178
|
-
text += `\n ${
|
|
179
|
+
text += `\n ${uiTheme.fg("dim", cont)} ${uiTheme.fg(
|
|
180
|
+
"muted",
|
|
181
|
+
`${uiTheme.format.ellipsis} ${res.highlights.length - 3} more`,
|
|
182
|
+
)}`;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
}
|
|
@@ -186,11 +190,11 @@ export function renderExaResult(
|
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/** Render Exa call (query/args preview) */
|
|
189
|
-
export function renderExaCall(args: Record<string, unknown>, toolName: string,
|
|
190
|
-
const query = typeof args.query === "string" ? truncate(args.query, 80) : "";
|
|
193
|
+
export function renderExaCall(args: Record<string, unknown>, toolName: string, uiTheme: Theme): Component {
|
|
194
|
+
const query = typeof args.query === "string" ? truncate(args.query, 80, uiTheme.format.ellipsis) : "";
|
|
191
195
|
const numResults = typeof args.num_results === "number" ? args.num_results : undefined;
|
|
192
|
-
const detail = numResults ?
|
|
196
|
+
const detail = numResults ? uiTheme.fg("dim", ` (${numResults} results)`) : "";
|
|
193
197
|
|
|
194
|
-
const text = `${
|
|
198
|
+
const text = `${uiTheme.fg("toolTitle", toolName)} ${uiTheme.fg("muted", query)}${detail}`;
|
|
195
199
|
return new Text(text, 0, 0);
|
|
196
200
|
}
|
package/src/core/tools/find.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
import { globSync } from "glob";
|
|
6
|
+
import findDescription from "../../prompts/tools/find.md" with { type: "text" };
|
|
6
7
|
import { ensureTool } from "../../utils/tools-manager";
|
|
7
8
|
import { untilAborted } from "../utils";
|
|
8
9
|
import { resolveToCwd } from "./path-utils";
|
|
@@ -32,6 +33,7 @@ export interface FindToolDetails {
|
|
|
32
33
|
truncation?: TruncationResult;
|
|
33
34
|
resultLimitReached?: number;
|
|
34
35
|
// Fields for TUI rendering
|
|
36
|
+
scopePath?: string;
|
|
35
37
|
fileCount?: number;
|
|
36
38
|
files?: string[];
|
|
37
39
|
truncated?: boolean;
|
|
@@ -42,12 +44,7 @@ export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
|
|
42
44
|
return {
|
|
43
45
|
name: "find",
|
|
44
46
|
label: "Find",
|
|
45
|
-
description:
|
|
46
|
-
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
|
47
|
-
- Returns matching file paths sorted by modification time
|
|
48
|
-
- Use this tool when you need to find files by name patterns
|
|
49
|
-
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
|
|
50
|
-
- You can call multiple tools in a single response. It is always better to speculatively perform multiple searches in parallel if they are potentially useful.`,
|
|
47
|
+
description: findDescription,
|
|
51
48
|
parameters: findSchema,
|
|
52
49
|
execute: async (
|
|
53
50
|
_toolCallId: string,
|
|
@@ -76,6 +73,10 @@ export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
|
76
|
+
const scopePath = (() => {
|
|
77
|
+
const relative = path.relative(cwd, searchPath).replace(/\\/g, "/");
|
|
78
|
+
return relative.length === 0 ? "." : relative;
|
|
79
|
+
})();
|
|
79
80
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
80
81
|
const effectiveType = type ?? "all";
|
|
81
82
|
const includeHidden = hidden ?? false;
|
|
@@ -148,7 +149,7 @@ export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
|
|
148
149
|
if (!output) {
|
|
149
150
|
return {
|
|
150
151
|
content: [{ type: "text", text: "No files found matching pattern" }],
|
|
151
|
-
details: { fileCount: 0, files: [], truncated: false },
|
|
152
|
+
details: { scopePath, fileCount: 0, files: [], truncated: false },
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -205,8 +206,9 @@ export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
|
|
|
205
206
|
|
|
206
207
|
let resultOutput = truncation.content;
|
|
207
208
|
const details: FindToolDetails = {
|
|
209
|
+
scopePath,
|
|
208
210
|
fileCount: relativized.length,
|
|
209
|
-
files: relativized
|
|
211
|
+
files: relativized,
|
|
210
212
|
truncated: resultLimitReached || truncation.truncated,
|
|
211
213
|
};
|
|
212
214
|
|
package/src/core/tools/grep.ts
CHANGED
|
@@ -3,6 +3,7 @@ import nodePath from "node:path";
|
|
|
3
3
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
import type { Subprocess } from "bun";
|
|
6
|
+
import grepDescription from "../../prompts/tools/grep.md" with { type: "text" };
|
|
6
7
|
import { ensureTool } from "../../utils/tools-manager";
|
|
7
8
|
import { resolveToCwd } from "./path-utils";
|
|
8
9
|
import {
|
|
@@ -50,11 +51,14 @@ const DEFAULT_LIMIT = 100;
|
|
|
50
51
|
export interface GrepToolDetails {
|
|
51
52
|
truncation?: TruncationResult;
|
|
52
53
|
matchLimitReached?: number;
|
|
54
|
+
headLimitReached?: number;
|
|
53
55
|
linesTruncated?: boolean;
|
|
54
56
|
// Fields for TUI rendering
|
|
57
|
+
scopePath?: string;
|
|
55
58
|
matchCount?: number;
|
|
56
59
|
fileCount?: number;
|
|
57
60
|
files?: string[];
|
|
61
|
+
fileMatches?: Array<{ path: string; count: number }>;
|
|
58
62
|
mode?: "content" | "files_with_matches" | "count";
|
|
59
63
|
truncated?: boolean;
|
|
60
64
|
error?: string;
|
|
@@ -64,17 +68,7 @@ export function createGrepTool(cwd: string): AgentTool<typeof grepSchema> {
|
|
|
64
68
|
return {
|
|
65
69
|
name: "grep",
|
|
66
70
|
label: "Grep",
|
|
67
|
-
description:
|
|
68
|
-
|
|
69
|
-
Usage:
|
|
70
|
-
- ALWAYS use grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a bash command. The grep tool has been optimized for correct permissions and access.
|
|
71
|
-
- Searches recursively by default - no need for -r flag
|
|
72
|
-
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
73
|
-
- Filter files with glob parameter (e.g., "*.ts", "**/*.spec.ts") or type parameter (e.g., "ts", "py", "rust") - equivalent to grep's --include
|
|
74
|
-
- Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
|
|
75
|
-
- Pagination: Use headLimit to limit results (like \`| head -N\`), offset to skip first N results
|
|
76
|
-
- Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
|
|
77
|
-
- Multiline matching: By default patterns match within single lines only. For cross-line patterns like \`struct \\{[\\s\\S]*?field\`, use \`multiline: true\``,
|
|
71
|
+
description: grepDescription,
|
|
78
72
|
parameters: grepSchema,
|
|
79
73
|
execute: async (
|
|
80
74
|
_toolCallId: string,
|
|
@@ -119,6 +113,10 @@ Usage:
|
|
|
119
113
|
}
|
|
120
114
|
|
|
121
115
|
const searchPath = resolveToCwd(searchDir || ".", cwd);
|
|
116
|
+
const scopePath = (() => {
|
|
117
|
+
const relative = nodePath.relative(cwd, searchPath).replace(/\\/g, "/");
|
|
118
|
+
return relative.length === 0 ? "." : relative;
|
|
119
|
+
})();
|
|
122
120
|
let searchStat: Stats;
|
|
123
121
|
try {
|
|
124
122
|
searchStat = statSync(searchPath);
|
|
@@ -210,6 +208,7 @@ Usage:
|
|
|
210
208
|
const outputLines: string[] = [];
|
|
211
209
|
const files = new Set<string>();
|
|
212
210
|
const fileList: string[] = [];
|
|
211
|
+
const fileMatchCounts = new Map<string, number>();
|
|
213
212
|
|
|
214
213
|
const recordFile = (filePath: string) => {
|
|
215
214
|
const relative = formatPath(filePath);
|
|
@@ -219,6 +218,11 @@ Usage:
|
|
|
219
218
|
}
|
|
220
219
|
};
|
|
221
220
|
|
|
221
|
+
const recordFileMatch = (filePath: string) => {
|
|
222
|
+
const relative = formatPath(filePath);
|
|
223
|
+
fileMatchCounts.set(relative, (fileMatchCounts.get(relative) ?? 0) + 1);
|
|
224
|
+
};
|
|
225
|
+
|
|
222
226
|
const stopChild = (dueToLimit: boolean = false) => {
|
|
223
227
|
killedDueToLimit = dueToLimit;
|
|
224
228
|
child.kill();
|
|
@@ -281,6 +285,7 @@ Usage:
|
|
|
281
285
|
return {
|
|
282
286
|
content: [{ type: "text", text: "No matches found" }],
|
|
283
287
|
details: {
|
|
288
|
+
scopePath,
|
|
284
289
|
matchCount: 0,
|
|
285
290
|
fileCount: 0,
|
|
286
291
|
files: [],
|
|
@@ -303,6 +308,7 @@ Usage:
|
|
|
303
308
|
let fileCount = 0;
|
|
304
309
|
const simpleFiles = new Set<string>();
|
|
305
310
|
const simpleFileList: string[] = [];
|
|
311
|
+
const simpleFileMatchCounts = new Map<string, number>();
|
|
306
312
|
|
|
307
313
|
const recordSimpleFile = (filePath: string) => {
|
|
308
314
|
const relative = formatPath(filePath);
|
|
@@ -312,6 +318,11 @@ Usage:
|
|
|
312
318
|
}
|
|
313
319
|
};
|
|
314
320
|
|
|
321
|
+
const recordSimpleFileMatch = (filePath: string, count: number) => {
|
|
322
|
+
const relative = formatPath(filePath);
|
|
323
|
+
simpleFileMatchCounts.set(relative, count);
|
|
324
|
+
};
|
|
325
|
+
|
|
315
326
|
if (effectiveOutputMode === "files_with_matches") {
|
|
316
327
|
for (const line of lines) {
|
|
317
328
|
recordSimpleFile(line);
|
|
@@ -327,12 +338,13 @@ Usage:
|
|
|
327
338
|
recordSimpleFile(filePart);
|
|
328
339
|
if (!Number.isNaN(count)) {
|
|
329
340
|
simpleMatchCount += count;
|
|
341
|
+
recordSimpleFileMatch(filePart, count);
|
|
330
342
|
}
|
|
331
343
|
}
|
|
332
344
|
fileCount = simpleFiles.size;
|
|
333
345
|
}
|
|
334
346
|
|
|
335
|
-
const
|
|
347
|
+
const truncatedByHeadLimit = hasHeadLimit && processedLines.length < lines.length;
|
|
336
348
|
|
|
337
349
|
// For count mode, format as "path:count"
|
|
338
350
|
if (effectiveOutputMode === "count") {
|
|
@@ -346,11 +358,17 @@ Usage:
|
|
|
346
358
|
return {
|
|
347
359
|
content: [{ type: "text", text: output }],
|
|
348
360
|
details: {
|
|
361
|
+
scopePath,
|
|
349
362
|
matchCount: simpleMatchCount,
|
|
350
363
|
fileCount,
|
|
351
|
-
files: simpleFileList
|
|
364
|
+
files: simpleFileList,
|
|
365
|
+
fileMatches: simpleFileList.map((path) => ({
|
|
366
|
+
path,
|
|
367
|
+
count: simpleFileMatchCounts.get(path) ?? 0,
|
|
368
|
+
})),
|
|
352
369
|
mode: effectiveOutputMode,
|
|
353
|
-
truncated,
|
|
370
|
+
truncated: truncatedByHeadLimit,
|
|
371
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
354
372
|
},
|
|
355
373
|
};
|
|
356
374
|
}
|
|
@@ -361,11 +379,13 @@ Usage:
|
|
|
361
379
|
return {
|
|
362
380
|
content: [{ type: "text", text: output }],
|
|
363
381
|
details: {
|
|
382
|
+
scopePath,
|
|
364
383
|
matchCount: simpleMatchCount,
|
|
365
384
|
fileCount,
|
|
366
|
-
files: simpleFileList
|
|
385
|
+
files: simpleFileList,
|
|
367
386
|
mode: effectiveOutputMode,
|
|
368
|
-
truncated,
|
|
387
|
+
truncated: truncatedByHeadLimit,
|
|
388
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
369
389
|
},
|
|
370
390
|
};
|
|
371
391
|
}
|
|
@@ -421,6 +441,7 @@ Usage:
|
|
|
421
441
|
|
|
422
442
|
if (filePath && typeof lineNumber === "number") {
|
|
423
443
|
recordFile(filePath);
|
|
444
|
+
recordFileMatch(filePath);
|
|
424
445
|
outputLines.push(...formatBlock(filePath, lineNumber));
|
|
425
446
|
}
|
|
426
447
|
|
|
@@ -488,6 +509,7 @@ Usage:
|
|
|
488
509
|
return {
|
|
489
510
|
content: [{ type: "text", text: "No matches found" }],
|
|
490
511
|
details: {
|
|
512
|
+
scopePath,
|
|
491
513
|
matchCount: 0,
|
|
492
514
|
fileCount: 0,
|
|
493
515
|
files: [],
|
|
@@ -513,11 +535,17 @@ Usage:
|
|
|
513
535
|
let output = truncation.content;
|
|
514
536
|
const truncatedByHeadLimit = hasHeadLimit && processedLines.length < outputLines.length;
|
|
515
537
|
const details: GrepToolDetails = {
|
|
538
|
+
scopePath,
|
|
516
539
|
matchCount,
|
|
517
540
|
fileCount: files.size,
|
|
518
|
-
files: fileList
|
|
541
|
+
files: fileList,
|
|
542
|
+
fileMatches: fileList.map((path) => ({
|
|
543
|
+
path,
|
|
544
|
+
count: fileMatchCounts.get(path) ?? 0,
|
|
545
|
+
})),
|
|
519
546
|
mode: effectiveOutputMode,
|
|
520
547
|
truncated: matchLimitReached || truncation.truncated || truncatedByHeadLimit,
|
|
548
|
+
headLimitReached: truncatedByHeadLimit ? headLimit : undefined,
|
|
521
549
|
};
|
|
522
550
|
|
|
523
551
|
// Build notices
|
package/src/core/tools/ls.ts
CHANGED
|
@@ -14,7 +14,11 @@ const lsSchema = Type.Object({
|
|
|
14
14
|
const DEFAULT_LIMIT = 500;
|
|
15
15
|
|
|
16
16
|
export interface LsToolDetails {
|
|
17
|
+
entries?: string[];
|
|
18
|
+
dirCount?: number;
|
|
19
|
+
fileCount?: number;
|
|
17
20
|
truncation?: TruncationResult;
|
|
21
|
+
truncationReasons?: Array<"entryLimit" | "byteLimit">;
|
|
18
22
|
entryLimitReached?: number;
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -58,6 +62,8 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
58
62
|
// Format entries with directory indicators
|
|
59
63
|
const results: string[] = [];
|
|
60
64
|
let entryLimitReached = false;
|
|
65
|
+
let dirCount = 0;
|
|
66
|
+
let fileCount = 0;
|
|
61
67
|
|
|
62
68
|
for (const entry of entries) {
|
|
63
69
|
if (results.length >= effectiveLimit) {
|
|
@@ -72,6 +78,9 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
72
78
|
const entryStat = statSync(fullPath);
|
|
73
79
|
if (entryStat.isDirectory()) {
|
|
74
80
|
suffix = "/";
|
|
81
|
+
dirCount += 1;
|
|
82
|
+
} else {
|
|
83
|
+
fileCount += 1;
|
|
75
84
|
}
|
|
76
85
|
} catch {
|
|
77
86
|
// Skip entries we can't stat
|
|
@@ -90,7 +99,12 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
90
99
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
91
100
|
|
|
92
101
|
let output = truncation.content;
|
|
93
|
-
const details: LsToolDetails = {
|
|
102
|
+
const details: LsToolDetails = {
|
|
103
|
+
entries: results,
|
|
104
|
+
dirCount,
|
|
105
|
+
fileCount,
|
|
106
|
+
};
|
|
107
|
+
const truncationReasons: Array<"entryLimit" | "byteLimit"> = [];
|
|
94
108
|
|
|
95
109
|
// Build notices
|
|
96
110
|
const notices: string[] = [];
|
|
@@ -98,11 +112,17 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
98
112
|
if (entryLimitReached) {
|
|
99
113
|
notices.push(`${effectiveLimit} entries limit reached. Use limit=${effectiveLimit * 2} for more`);
|
|
100
114
|
details.entryLimitReached = effectiveLimit;
|
|
115
|
+
truncationReasons.push("entryLimit");
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
if (truncation.truncated) {
|
|
104
119
|
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
105
120
|
details.truncation = truncation;
|
|
121
|
+
truncationReasons.push("byteLimit");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (truncationReasons.length > 0) {
|
|
125
|
+
details.truncationReasons = truncationReasons;
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
if (notices.length > 0) {
|
|
@@ -111,7 +131,7 @@ export function createLsTool(cwd: string): AgentTool<typeof lsSchema> {
|
|
|
111
131
|
|
|
112
132
|
return {
|
|
113
133
|
content: [{ type: "text", text: output }],
|
|
114
|
-
details
|
|
134
|
+
details,
|
|
115
135
|
};
|
|
116
136
|
});
|
|
117
137
|
},
|