@oh-my-pi/pi-coding-agent 14.5.2 → 14.5.5
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 +70 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +7 -7
- package/src/config/prompt-templates.ts +104 -6
- package/src/config/settings-schema.ts +14 -13
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +4 -4
- package/src/edit/index.ts +111 -109
- package/src/edit/line-hash.ts +33 -3
- package/src/edit/modes/apply-patch.ts +6 -4
- package/src/edit/modes/atom.lark +27 -0
- package/src/edit/modes/atom.ts +1094 -642
- package/src/edit/modes/hashline.ts +9 -10
- package/src/edit/modes/patch.ts +23 -19
- package/src/edit/modes/replace.ts +19 -15
- package/src/edit/renderer.ts +65 -8
- package/src/edit/streaming.ts +47 -77
- package/src/extensibility/extensions/types.ts +11 -11
- package/src/extensibility/hooks/types.ts +6 -6
- package/src/lsp/edits.ts +8 -5
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +13 -43
- package/src/mcp/discoverable-tool-metadata.ts +1 -1
- package/src/mcp/manager.ts +3 -3
- package/src/mcp/tool-bridge.ts +4 -4
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-defs.ts +3 -3
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/controllers/event-controller.ts +12 -0
- package/src/modes/utils/ui-helpers.ts +31 -7
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/system-prompt.md +34 -31
- package/src/prompts/tools/apply-patch.md +0 -2
- package/src/prompts/tools/atom.md +88 -97
- package/src/prompts/tools/bash.md +7 -4
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/find.md +6 -1
- package/src/prompts/tools/hashline.md +10 -11
- package/src/prompts/tools/patch.md +13 -13
- package/src/prompts/tools/read.md +5 -5
- package/src/prompts/tools/replace.md +3 -3
- package/src/prompts/tools/{grep.md → search.md} +4 -4
- package/src/sdk.ts +19 -9
- package/src/session/agent-session.ts +69 -1
- package/src/system-prompt.ts +15 -5
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +10 -1
- package/src/tools/ast-edit.ts +27 -50
- package/src/tools/ast-grep.ts +22 -48
- package/src/tools/bash.ts +1 -1
- package/src/tools/file-recorder.ts +6 -6
- package/src/tools/find.ts +11 -13
- package/src/tools/grouped-file-output.ts +96 -0
- package/src/tools/index.ts +7 -7
- package/src/tools/path-utils.ts +31 -4
- package/src/tools/read.ts +12 -6
- package/src/tools/renderers.ts +2 -2
- package/src/tools/{grep.ts → search.ts} +43 -86
- package/src/tools/todo-write.ts +0 -1
- package/src/tools/write.ts +8 -4
- package/src/web/search/index.ts +1 -1
package/src/tools/path-utils.ts
CHANGED
|
@@ -157,6 +157,27 @@ export function resolveToCwd(filePath: string, cwd: string): string {
|
|
|
157
157
|
return path.resolve(cwd, expanded);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
export function formatPathRelativeToCwd(
|
|
161
|
+
filePath: string,
|
|
162
|
+
cwd: string,
|
|
163
|
+
options: { trailingSlash?: boolean } = {},
|
|
164
|
+
): string {
|
|
165
|
+
const resolvedCwd = path.resolve(cwd);
|
|
166
|
+
const normalized = normalizeLocalScheme(filePath);
|
|
167
|
+
if (isInternalUrlPath(normalized)) {
|
|
168
|
+
return normalized;
|
|
169
|
+
}
|
|
170
|
+
const expanded = expandPath(normalized);
|
|
171
|
+
const resolvedPath = path.isAbsolute(expanded) ? path.resolve(expanded) : path.resolve(cwd, expanded);
|
|
172
|
+
const relative = path.relative(resolvedCwd, resolvedPath);
|
|
173
|
+
const isWithinCwd = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
174
|
+
let displayPath = normalizePosixPath(isWithinCwd ? relative || "." : resolvedPath);
|
|
175
|
+
if (options.trailingSlash && displayPath !== "." && !displayPath.endsWith("/")) {
|
|
176
|
+
displayPath += "/";
|
|
177
|
+
}
|
|
178
|
+
return displayPath;
|
|
179
|
+
}
|
|
180
|
+
|
|
160
181
|
/**
|
|
161
182
|
* Strip matching surrounding double quotes from a path string.
|
|
162
183
|
* Common when users paste quoted paths from Windows Explorer or shell copy-paste.
|
|
@@ -381,8 +402,14 @@ function findCommonBasePath(paths: string[]): string {
|
|
|
381
402
|
return joined || path.parse(path.resolve(paths[0])).root;
|
|
382
403
|
}
|
|
383
404
|
|
|
384
|
-
function toScopeDisplay(items: string[]): string {
|
|
385
|
-
return items
|
|
405
|
+
function toScopeDisplay(items: string[], cwd: string): string {
|
|
406
|
+
return items
|
|
407
|
+
.map(item =>
|
|
408
|
+
formatPathRelativeToCwd(item, cwd, {
|
|
409
|
+
trailingSlash: item.endsWith("/") || item.endsWith("\\"),
|
|
410
|
+
}),
|
|
411
|
+
)
|
|
412
|
+
.join(", ");
|
|
386
413
|
}
|
|
387
414
|
|
|
388
415
|
function looksLikeDelimitedPathToken(token: string): boolean {
|
|
@@ -533,7 +560,7 @@ export async function resolveMultiSearchPath(
|
|
|
533
560
|
return {
|
|
534
561
|
basePath: commonBasePath,
|
|
535
562
|
glob: buildBraceUnion(combinedPatterns),
|
|
536
|
-
scopePath: toScopeDisplay(pathItems),
|
|
563
|
+
scopePath: toScopeDisplay(pathItems, cwd),
|
|
537
564
|
exactFilePaths: allExactFiles ? parsedItems.map(item => item.absoluteBasePath) : undefined,
|
|
538
565
|
};
|
|
539
566
|
}
|
|
@@ -571,7 +598,7 @@ export async function resolveMultiFindPattern(
|
|
|
571
598
|
return {
|
|
572
599
|
basePath: commonBasePath,
|
|
573
600
|
globPattern: buildBraceUnion(combinedPatterns) ?? "**/*",
|
|
574
|
-
scopePath: toScopeDisplay(patternItems),
|
|
601
|
+
scopePath: toScopeDisplay(patternItems, cwd),
|
|
575
602
|
};
|
|
576
603
|
}
|
|
577
604
|
|
package/src/tools/read.ts
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from "./fetch";
|
|
41
41
|
import { applyListLimit } from "./list-limit";
|
|
42
42
|
import { formatFullOutputReference, formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
43
|
-
import { expandPath, resolveReadPath } from "./path-utils";
|
|
43
|
+
import { expandPath, formatPathRelativeToCwd, resolveReadPath } from "./path-utils";
|
|
44
44
|
import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-utils";
|
|
45
45
|
import {
|
|
46
46
|
executeReadQuery,
|
|
@@ -1046,7 +1046,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1046
1046
|
? "- Alpha: no"
|
|
1047
1047
|
: "- Alpha: unknown",
|
|
1048
1048
|
"",
|
|
1049
|
-
`If you want to analyze the image, call inspect_image with path="${
|
|
1049
|
+
`If you want to analyze the image, call inspect_image with path="${formatPathRelativeToCwd(
|
|
1050
|
+
absolutePath,
|
|
1051
|
+
this.session.cwd,
|
|
1052
|
+
)}" and a question describing what to inspect and the desired output format.`,
|
|
1050
1053
|
];
|
|
1051
1054
|
content = [{ type: "text", text: metadataLines.join("\n") }];
|
|
1052
1055
|
details = {};
|
|
@@ -1110,12 +1113,15 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1110
1113
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
1111
1114
|
const maxLinesToCollect = Math.min(effectiveLimit, DEFAULT_MAX_LINES);
|
|
1112
1115
|
const selectedLineLimit = effectiveLimit;
|
|
1116
|
+
// Scale byte budget with line limit so the configured line count actually fits.
|
|
1117
|
+
// Assume ~512 bytes/line average; never go below the shared default.
|
|
1118
|
+
const maxBytesForRead = Math.max(DEFAULT_MAX_BYTES, maxLinesToCollect * 512);
|
|
1113
1119
|
|
|
1114
1120
|
const streamResult = await streamLinesFromFile(
|
|
1115
1121
|
absolutePath,
|
|
1116
1122
|
startLine,
|
|
1117
1123
|
maxLinesToCollect,
|
|
1118
|
-
|
|
1124
|
+
maxBytesForRead,
|
|
1119
1125
|
selectedLineLimit,
|
|
1120
1126
|
signal,
|
|
1121
1127
|
);
|
|
@@ -1146,7 +1152,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1146
1152
|
const totalSelectedLines = totalFileLines - startLine;
|
|
1147
1153
|
const totalSelectedBytes = collectedBytes;
|
|
1148
1154
|
const wasTruncated = collectedLines.length < totalSelectedLines || stoppedByByteLimit;
|
|
1149
|
-
const firstLineExceedsLimit = firstLineByteLength !== undefined && firstLineByteLength >
|
|
1155
|
+
const firstLineExceedsLimit = firstLineByteLength !== undefined && firstLineByteLength > maxBytesForRead;
|
|
1150
1156
|
|
|
1151
1157
|
const truncation: TruncationResult = {
|
|
1152
1158
|
content: selectedContent,
|
|
@@ -1178,14 +1184,14 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1178
1184
|
if (shouldAddHashLines) {
|
|
1179
1185
|
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
1180
1186
|
firstLineBytes,
|
|
1181
|
-
)}, exceeds ${formatBytes(
|
|
1187
|
+
)}, exceeds ${formatBytes(maxBytesForRead)} limit. Hashline output requires full lines; cannot compute hashes for a truncated preview.]`;
|
|
1182
1188
|
} else {
|
|
1183
1189
|
outputText = formatText(snippet.text, startLineDisplay);
|
|
1184
1190
|
}
|
|
1185
1191
|
if (snippet.text.length === 0) {
|
|
1186
1192
|
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
1187
1193
|
firstLineBytes,
|
|
1188
|
-
)}, exceeds ${formatBytes(
|
|
1194
|
+
)}, exceeds ${formatBytes(maxBytesForRead)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
1189
1195
|
}
|
|
1190
1196
|
details = { truncation };
|
|
1191
1197
|
sourcePath = absolutePath;
|
package/src/tools/renderers.ts
CHANGED
|
@@ -18,13 +18,13 @@ import { calculatorToolRenderer } from "./calculator";
|
|
|
18
18
|
import { debugToolRenderer } from "./debug";
|
|
19
19
|
import { findToolRenderer } from "./find";
|
|
20
20
|
import { githubToolRenderer } from "./gh-renderer";
|
|
21
|
-
import { grepToolRenderer } from "./grep";
|
|
22
21
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
23
22
|
import { jobToolRenderer } from "./job";
|
|
24
23
|
import { notebookToolRenderer } from "./notebook";
|
|
25
24
|
import { pythonToolRenderer } from "./python";
|
|
26
25
|
import { readToolRenderer } from "./read";
|
|
27
26
|
import { resolveToolRenderer } from "./resolve";
|
|
27
|
+
import { searchToolRenderer } from "./search";
|
|
28
28
|
import { searchToolBm25Renderer } from "./search-tool-bm25";
|
|
29
29
|
import { sshToolRenderer } from "./ssh";
|
|
30
30
|
import { todoWriteToolRenderer } from "./todo-write";
|
|
@@ -54,7 +54,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
54
54
|
edit: editToolRenderer as ToolRenderer,
|
|
55
55
|
apply_patch: editToolRenderer as ToolRenderer,
|
|
56
56
|
find: findToolRenderer as ToolRenderer,
|
|
57
|
-
|
|
57
|
+
search: searchToolRenderer as ToolRenderer,
|
|
58
58
|
lsp: lspToolRenderer as ToolRenderer,
|
|
59
59
|
notebook: notebookToolRenderer as ToolRenderer,
|
|
60
60
|
inspect_image: inspectImageToolRenderer as ToolRenderer,
|
|
@@ -8,15 +8,17 @@ import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
8
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import type { Theme } from "../modes/theme/theme";
|
|
11
|
-
import
|
|
11
|
+
import searchDescription from "../prompts/tools/search.md" with { type: "text" };
|
|
12
12
|
import { DEFAULT_MAX_COLUMN, type TruncationResult, truncateHead } from "../session/streaming-output";
|
|
13
13
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
14
14
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
|
-
import { createFileRecorder } from "./file-recorder";
|
|
16
|
+
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
17
|
+
import { formatGroupedFiles } from "./grouped-file-output";
|
|
17
18
|
import { formatMatchLine } from "./match-line-format";
|
|
18
19
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
19
20
|
import {
|
|
21
|
+
formatPathRelativeToCwd,
|
|
20
22
|
hasGlobPathChars,
|
|
21
23
|
normalizePathLikeInput,
|
|
22
24
|
parseSearchPath,
|
|
@@ -33,7 +35,7 @@ import {
|
|
|
33
35
|
import { ToolError } from "./tool-errors";
|
|
34
36
|
import { toolResult } from "./tool-result";
|
|
35
37
|
|
|
36
|
-
const
|
|
38
|
+
const searchSchema = Type.Object({
|
|
37
39
|
pattern: Type.String({ description: "regex pattern", examples: ["function\\s+\\w+", "TODO"] }),
|
|
38
40
|
path: Type.String({
|
|
39
41
|
description: "file, directory, glob, comma-separated paths, or internal URL to search",
|
|
@@ -44,11 +46,11 @@ const grepSchema = Type.Object({
|
|
|
44
46
|
skip: Type.Optional(Type.Number({ description: "matches to skip", default: 0 })),
|
|
45
47
|
});
|
|
46
48
|
|
|
47
|
-
export type
|
|
49
|
+
export type SearchToolInput = Static<typeof searchSchema>;
|
|
48
50
|
|
|
49
51
|
const DEFAULT_MATCH_LIMIT = 20;
|
|
50
52
|
|
|
51
|
-
export interface
|
|
53
|
+
export interface SearchToolDetails {
|
|
52
54
|
truncation?: TruncationResult;
|
|
53
55
|
matchLimitReached?: number;
|
|
54
56
|
resultLimitReached?: number;
|
|
@@ -67,18 +69,18 @@ export interface GrepToolDetails {
|
|
|
67
69
|
displayContent?: string;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
type
|
|
72
|
+
type SearchParams = Static<typeof searchSchema>;
|
|
71
73
|
|
|
72
|
-
export class
|
|
73
|
-
readonly name = "
|
|
74
|
-
readonly label = "
|
|
74
|
+
export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
|
|
75
|
+
readonly name = "search";
|
|
76
|
+
readonly label = "Search";
|
|
75
77
|
readonly description: string;
|
|
76
|
-
readonly parameters =
|
|
78
|
+
readonly parameters = searchSchema;
|
|
77
79
|
readonly strict = true;
|
|
78
80
|
|
|
79
81
|
constructor(private readonly session: ToolSession) {
|
|
80
82
|
const displayMode = resolveFileDisplayMode(session);
|
|
81
|
-
this.description = prompt.render(
|
|
83
|
+
this.description = prompt.render(searchDescription, {
|
|
82
84
|
IS_HASHLINE_MODE: displayMode.hashLines,
|
|
83
85
|
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
84
86
|
});
|
|
@@ -86,11 +88,11 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
86
88
|
|
|
87
89
|
async execute(
|
|
88
90
|
_toolCallId: string,
|
|
89
|
-
params:
|
|
91
|
+
params: SearchParams,
|
|
90
92
|
signal?: AbortSignal,
|
|
91
|
-
_onUpdate?: AgentToolUpdateCallback<
|
|
93
|
+
_onUpdate?: AgentToolUpdateCallback<SearchToolDetails>,
|
|
92
94
|
_toolContext?: AgentToolContext,
|
|
93
|
-
): Promise<AgentToolResult<
|
|
95
|
+
): Promise<AgentToolResult<SearchToolDetails>> {
|
|
94
96
|
const { pattern, path: searchDir, i, gitignore, skip } = params;
|
|
95
97
|
|
|
96
98
|
return untilAborted(signal, async () => {
|
|
@@ -103,18 +105,15 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
103
105
|
if (normalizedSkip < 0 || !Number.isFinite(normalizedSkip)) {
|
|
104
106
|
throw new ToolError("Skip must be a non-negative number");
|
|
105
107
|
}
|
|
106
|
-
const normalizedContextBefore = this.session.settings.get("
|
|
107
|
-
const normalizedContextAfter = this.session.settings.get("
|
|
108
|
+
const normalizedContextBefore = this.session.settings.get("search.contextBefore");
|
|
109
|
+
const normalizedContextAfter = this.session.settings.get("search.contextAfter");
|
|
108
110
|
const ignoreCase = i ?? false;
|
|
109
111
|
const useGitignore = gitignore ?? true;
|
|
110
112
|
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
|
|
111
113
|
const effectiveMultiline = patternHasNewline;
|
|
112
114
|
|
|
113
115
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
|
114
|
-
const formatScopePath = (targetPath: string): string =>
|
|
115
|
-
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
116
|
-
return relative.length === 0 ? "." : relative;
|
|
117
|
-
};
|
|
116
|
+
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
118
117
|
let searchPath: string;
|
|
119
118
|
let scopePath: string;
|
|
120
119
|
let exactFilePaths: string[] | undefined;
|
|
@@ -130,7 +129,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
130
129
|
}
|
|
131
130
|
const resource = await internalRouter.resolve(rawPath);
|
|
132
131
|
if (!resource.sourcePath) {
|
|
133
|
-
throw new ToolError(`Cannot
|
|
132
|
+
throw new ToolError(`Cannot search internal URL without a backing file: ${rawPath}`);
|
|
134
133
|
}
|
|
135
134
|
searchPath = resource.sourcePath;
|
|
136
135
|
scopePath = formatScopePath(searchPath);
|
|
@@ -224,14 +223,8 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
224
223
|
throw err;
|
|
225
224
|
}
|
|
226
225
|
|
|
227
|
-
const formatPath = (filePath: string): string =>
|
|
228
|
-
|
|
229
|
-
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
230
|
-
if (isDirectory) {
|
|
231
|
-
return cleanPath.replace(/\\/g, "/");
|
|
232
|
-
}
|
|
233
|
-
return path.basename(cleanPath);
|
|
234
|
-
};
|
|
226
|
+
const formatPath = (filePath: string): string =>
|
|
227
|
+
formatResultPath(filePath, isDirectory, searchPath, this.session.cwd);
|
|
235
228
|
|
|
236
229
|
// Build output
|
|
237
230
|
const roundRobinSelect = (matches: GrepMatch[], limit: number): GrepMatch[] => {
|
|
@@ -272,7 +265,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
272
265
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
273
266
|
const fileMatchCounts = new Map<string, number>();
|
|
274
267
|
if (selectedMatches.length === 0) {
|
|
275
|
-
const details:
|
|
268
|
+
const details: SearchToolDetails = {
|
|
276
269
|
scopePath,
|
|
277
270
|
matchCount: 0,
|
|
278
271
|
fileCount: 0,
|
|
@@ -283,7 +276,6 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
283
276
|
}
|
|
284
277
|
const outputLines: string[] = [];
|
|
285
278
|
let linesTruncated = false;
|
|
286
|
-
const hasContextLines = normalizedContextBefore > 0 || normalizedContextAfter > 0;
|
|
287
279
|
const matchesByFile = new Map<string, GrepMatch[]>();
|
|
288
280
|
for (const match of selectedMatches) {
|
|
289
281
|
const relativePath = formatPath(match.path);
|
|
@@ -332,46 +324,16 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
332
324
|
return { model: modelOut, display: displayOut };
|
|
333
325
|
};
|
|
334
326
|
if (isDirectory) {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
for (const relativePath of directoryFiles) {
|
|
346
|
-
const rendered = renderMatchesForFile(relativePath);
|
|
347
|
-
if (rendered.model.length === 0) continue;
|
|
348
|
-
if (outputLines.length > 0) {
|
|
349
|
-
outputLines.push("");
|
|
350
|
-
displayLines.push("");
|
|
351
|
-
}
|
|
352
|
-
const header = `# ${path.basename(relativePath)}`;
|
|
353
|
-
outputLines.push(header, ...rendered.model);
|
|
354
|
-
displayLines.push(header, ...rendered.display);
|
|
355
|
-
}
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const renderedFiles = directoryFiles
|
|
359
|
-
.map(relativePath => ({ relativePath, rendered: renderMatchesForFile(relativePath) }))
|
|
360
|
-
.filter(file => file.rendered.model.length > 0);
|
|
361
|
-
if (renderedFiles.length === 0) continue;
|
|
362
|
-
if (outputLines.length > 0) {
|
|
363
|
-
outputLines.push("");
|
|
364
|
-
displayLines.push("");
|
|
365
|
-
}
|
|
366
|
-
const dirHeader = `# ${directory}`;
|
|
367
|
-
outputLines.push(dirHeader);
|
|
368
|
-
displayLines.push(dirHeader);
|
|
369
|
-
for (const { relativePath, rendered } of renderedFiles) {
|
|
370
|
-
const fileHeader = `## └─ ${path.basename(relativePath)}`;
|
|
371
|
-
outputLines.push(fileHeader, ...rendered.model);
|
|
372
|
-
displayLines.push(fileHeader, ...rendered.display);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
327
|
+
const grouped = formatGroupedFiles(fileList, relativePath => {
|
|
328
|
+
const rendered = renderMatchesForFile(relativePath);
|
|
329
|
+
return {
|
|
330
|
+
modelLines: rendered.model,
|
|
331
|
+
displayLines: rendered.display,
|
|
332
|
+
skip: rendered.model.length === 0,
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
outputLines.push(...grouped.model);
|
|
336
|
+
displayLines.push(...grouped.display);
|
|
375
337
|
} else {
|
|
376
338
|
for (const relativePath of fileList) {
|
|
377
339
|
const rendered = renderMatchesForFile(relativePath);
|
|
@@ -379,11 +341,6 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
379
341
|
displayLines.push(...rendered.display);
|
|
380
342
|
}
|
|
381
343
|
}
|
|
382
|
-
if (hasContextLines && outputLines.length > 0) {
|
|
383
|
-
outputLines.unshift(
|
|
384
|
-
"[grep] '*' marks match lines; leading space marks context. Anchor and content are separated by '|'.",
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
344
|
if (matchLimitReached || result.limitReached) {
|
|
388
345
|
outputLines.push("", limitMessage);
|
|
389
346
|
}
|
|
@@ -391,7 +348,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
391
348
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
392
349
|
const output = truncation.content;
|
|
393
350
|
const truncated = Boolean(matchLimitReached || result.limitReached || truncation.truncated || linesTruncated);
|
|
394
|
-
const details:
|
|
351
|
+
const details: SearchToolDetails = {
|
|
395
352
|
scopePath,
|
|
396
353
|
matchCount: selectedMatches.length,
|
|
397
354
|
fileCount: fileList.length,
|
|
@@ -422,7 +379,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
422
379
|
// TUI Renderer
|
|
423
380
|
// =============================================================================
|
|
424
381
|
|
|
425
|
-
interface
|
|
382
|
+
interface SearchRenderArgs {
|
|
426
383
|
pattern: string;
|
|
427
384
|
path?: string;
|
|
428
385
|
i?: boolean;
|
|
@@ -432,9 +389,9 @@ interface GrepRenderArgs {
|
|
|
432
389
|
|
|
433
390
|
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
434
391
|
|
|
435
|
-
export const
|
|
392
|
+
export const searchToolRenderer = {
|
|
436
393
|
inline: true,
|
|
437
|
-
renderCall(args:
|
|
394
|
+
renderCall(args: SearchRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
438
395
|
const meta: string[] = [];
|
|
439
396
|
if (args.path) meta.push(`in ${args.path}`);
|
|
440
397
|
if (args.i) meta.push("case:insensitive");
|
|
@@ -442,17 +399,17 @@ export const grepToolRenderer = {
|
|
|
442
399
|
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
443
400
|
|
|
444
401
|
const text = renderStatusLine(
|
|
445
|
-
{ icon: "pending", title: "
|
|
402
|
+
{ icon: "pending", title: "Search", description: args.pattern || "?", meta },
|
|
446
403
|
uiTheme,
|
|
447
404
|
);
|
|
448
405
|
return new Text(text, 0, 0);
|
|
449
406
|
},
|
|
450
407
|
|
|
451
408
|
renderResult(
|
|
452
|
-
result: { content: Array<{ type: string; text?: string }>; details?:
|
|
409
|
+
result: { content: Array<{ type: string; text?: string }>; details?: SearchToolDetails; isError?: boolean },
|
|
453
410
|
options: RenderResultOptions,
|
|
454
411
|
uiTheme: Theme,
|
|
455
|
-
args?:
|
|
412
|
+
args?: SearchRenderArgs,
|
|
456
413
|
): Component {
|
|
457
414
|
const details = result.details;
|
|
458
415
|
|
|
@@ -471,7 +428,7 @@ export const grepToolRenderer = {
|
|
|
471
428
|
const lines = textContent.split("\n").filter(line => line.trim() !== "");
|
|
472
429
|
const description = args?.pattern ?? undefined;
|
|
473
430
|
const header = renderStatusLine(
|
|
474
|
-
{ icon: "success", title: "
|
|
431
|
+
{ icon: "success", title: "Search", description, meta: [formatCount("item", lines.length)] },
|
|
475
432
|
uiTheme,
|
|
476
433
|
);
|
|
477
434
|
let cached: RenderCache | undefined;
|
|
@@ -511,7 +468,7 @@ export const grepToolRenderer = {
|
|
|
511
468
|
|
|
512
469
|
if (matchCount === 0) {
|
|
513
470
|
const header = renderStatusLine(
|
|
514
|
-
{ icon: "warning", title: "
|
|
471
|
+
{ icon: "warning", title: "Search", description: args?.pattern, meta: ["0 matches"] },
|
|
515
472
|
uiTheme,
|
|
516
473
|
);
|
|
517
474
|
return new Text([header, formatEmptyMessage("No matches found", uiTheme)].join("\n"), 0, 0);
|
|
@@ -523,7 +480,7 @@ export const grepToolRenderer = {
|
|
|
523
480
|
if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
|
|
524
481
|
const description = args?.pattern ?? undefined;
|
|
525
482
|
const header = renderStatusLine(
|
|
526
|
-
{ icon: truncated ? "warning" : "success", title: "
|
|
483
|
+
{ icon: truncated ? "warning" : "success", title: "Search", description, meta },
|
|
527
484
|
uiTheme,
|
|
528
485
|
);
|
|
529
486
|
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -717,7 +717,6 @@ export const todoWriteToolRenderer = {
|
|
|
717
717
|
const lines: string[] = [header];
|
|
718
718
|
for (let p = 0; p < phases.length; p++) {
|
|
719
719
|
const phase = phases[p];
|
|
720
|
-
if (p > 0) lines.push("");
|
|
721
720
|
if (phases.length > 1) {
|
|
722
721
|
lines.push(uiTheme.fg("accent", chalk.bold(` ${phase.name}`)));
|
|
723
722
|
}
|
package/src/tools/write.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { parseArchivePathCandidates } from "./archive-reader";
|
|
|
19
19
|
import { assertEditableFile } from "./auto-generated-guard";
|
|
20
20
|
import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
|
|
21
21
|
import { type OutputMeta, outputMeta } from "./output-meta";
|
|
22
|
+
import { formatPathRelativeToCwd } from "./path-utils";
|
|
22
23
|
import { enforcePlanModeWrite, resolvePlanPath } from "./plan-mode-guard";
|
|
23
24
|
import {
|
|
24
25
|
formatDiagnostics,
|
|
@@ -212,7 +213,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
async #writeArchiveEntry(
|
|
215
|
-
displayPath: string,
|
|
216
216
|
content: string,
|
|
217
217
|
resolvedArchivePath: ResolvedArchiveWritePath,
|
|
218
218
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
@@ -278,8 +278,11 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
invalidateFsScanAfterWrite(resolvedArchivePath.absolutePath);
|
|
281
|
+
const outputPath = `${formatPathRelativeToCwd(resolvedArchivePath.absolutePath, this.session.cwd)}:${
|
|
282
|
+
resolvedArchivePath.archiveSubPath
|
|
283
|
+
}`;
|
|
281
284
|
return {
|
|
282
|
-
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${
|
|
285
|
+
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${outputPath}` }],
|
|
283
286
|
details: {},
|
|
284
287
|
};
|
|
285
288
|
}
|
|
@@ -426,7 +429,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
426
429
|
op: resolvedArchivePath.exists ? "update" : "create",
|
|
427
430
|
});
|
|
428
431
|
|
|
429
|
-
const archiveResult = await this.#writeArchiveEntry(
|
|
432
|
+
const archiveResult = await this.#writeArchiveEntry(cleanContent, resolvedArchivePath);
|
|
430
433
|
if (stripped) {
|
|
431
434
|
const firstText = archiveResult.content.find(
|
|
432
435
|
(block): block is { type: "text"; text: string } =>
|
|
@@ -468,7 +471,8 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
468
471
|
const diagnostics = await this.#writethrough(absolutePath, cleanContent, signal, undefined, batchRequest);
|
|
469
472
|
invalidateFsScanAfterWrite(absolutePath);
|
|
470
473
|
|
|
471
|
-
|
|
474
|
+
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
475
|
+
let resultText = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
472
476
|
if (stripped) {
|
|
473
477
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
474
478
|
}
|
package/src/web/search/index.ts
CHANGED
|
@@ -205,7 +205,7 @@ export async function runSearchQuery(
|
|
|
205
205
|
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Z.AI, SearXNG, and Synthetic providers with automatic fallback.
|
|
206
206
|
* Session is accepted for interface consistency but not used.
|
|
207
207
|
*/
|
|
208
|
-
export class
|
|
208
|
+
export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
209
209
|
readonly name = "web_search";
|
|
210
210
|
readonly label = "Web Search";
|
|
211
211
|
readonly description: string;
|