@oh-my-pi/pi-coding-agent 14.5.3 → 14.5.6
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 +49 -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 +103 -8
- 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 +1039 -841
- 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 +7 -7
- 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/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 +116 -60
- package/src/prompts/tools/apply-patch.md +0 -2
- package/src/prompts/tools/atom.md +81 -63
- 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 +4 -4
- 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 +65 -0
- 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 +4 -6
- package/src/tools/ast-grep.ts +4 -6
- 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/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} +32 -40
- package/src/tools/write.ts +8 -4
- package/src/web/search/index.ts +1 -1
- package/src/edit/block.ts +0 -308
- package/src/edit/indent.ts +0 -150
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,16 +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
17
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
18
18
|
import { formatMatchLine } from "./match-line-format";
|
|
19
19
|
import { formatFullOutputReference, type OutputMeta } from "./output-meta";
|
|
20
20
|
import {
|
|
21
|
+
formatPathRelativeToCwd,
|
|
21
22
|
hasGlobPathChars,
|
|
22
23
|
normalizePathLikeInput,
|
|
23
24
|
parseSearchPath,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
import { ToolError } from "./tool-errors";
|
|
35
36
|
import { toolResult } from "./tool-result";
|
|
36
37
|
|
|
37
|
-
const
|
|
38
|
+
const searchSchema = Type.Object({
|
|
38
39
|
pattern: Type.String({ description: "regex pattern", examples: ["function\\s+\\w+", "TODO"] }),
|
|
39
40
|
path: Type.String({
|
|
40
41
|
description: "file, directory, glob, comma-separated paths, or internal URL to search",
|
|
@@ -45,11 +46,11 @@ const grepSchema = Type.Object({
|
|
|
45
46
|
skip: Type.Optional(Type.Number({ description: "matches to skip", default: 0 })),
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
export type
|
|
49
|
+
export type SearchToolInput = Static<typeof searchSchema>;
|
|
49
50
|
|
|
50
51
|
const DEFAULT_MATCH_LIMIT = 20;
|
|
51
52
|
|
|
52
|
-
export interface
|
|
53
|
+
export interface SearchToolDetails {
|
|
53
54
|
truncation?: TruncationResult;
|
|
54
55
|
matchLimitReached?: number;
|
|
55
56
|
resultLimitReached?: number;
|
|
@@ -68,18 +69,18 @@ export interface GrepToolDetails {
|
|
|
68
69
|
displayContent?: string;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
type
|
|
72
|
+
type SearchParams = Static<typeof searchSchema>;
|
|
72
73
|
|
|
73
|
-
export class
|
|
74
|
-
readonly name = "
|
|
75
|
-
readonly label = "
|
|
74
|
+
export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
|
|
75
|
+
readonly name = "search";
|
|
76
|
+
readonly label = "Search";
|
|
76
77
|
readonly description: string;
|
|
77
|
-
readonly parameters =
|
|
78
|
+
readonly parameters = searchSchema;
|
|
78
79
|
readonly strict = true;
|
|
79
80
|
|
|
80
81
|
constructor(private readonly session: ToolSession) {
|
|
81
82
|
const displayMode = resolveFileDisplayMode(session);
|
|
82
|
-
this.description = prompt.render(
|
|
83
|
+
this.description = prompt.render(searchDescription, {
|
|
83
84
|
IS_HASHLINE_MODE: displayMode.hashLines,
|
|
84
85
|
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
85
86
|
});
|
|
@@ -87,11 +88,11 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
87
88
|
|
|
88
89
|
async execute(
|
|
89
90
|
_toolCallId: string,
|
|
90
|
-
params:
|
|
91
|
+
params: SearchParams,
|
|
91
92
|
signal?: AbortSignal,
|
|
92
|
-
_onUpdate?: AgentToolUpdateCallback<
|
|
93
|
+
_onUpdate?: AgentToolUpdateCallback<SearchToolDetails>,
|
|
93
94
|
_toolContext?: AgentToolContext,
|
|
94
|
-
): Promise<AgentToolResult<
|
|
95
|
+
): Promise<AgentToolResult<SearchToolDetails>> {
|
|
95
96
|
const { pattern, path: searchDir, i, gitignore, skip } = params;
|
|
96
97
|
|
|
97
98
|
return untilAborted(signal, async () => {
|
|
@@ -104,18 +105,15 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
104
105
|
if (normalizedSkip < 0 || !Number.isFinite(normalizedSkip)) {
|
|
105
106
|
throw new ToolError("Skip must be a non-negative number");
|
|
106
107
|
}
|
|
107
|
-
const normalizedContextBefore = this.session.settings.get("
|
|
108
|
-
const normalizedContextAfter = this.session.settings.get("
|
|
108
|
+
const normalizedContextBefore = this.session.settings.get("search.contextBefore");
|
|
109
|
+
const normalizedContextAfter = this.session.settings.get("search.contextAfter");
|
|
109
110
|
const ignoreCase = i ?? false;
|
|
110
111
|
const useGitignore = gitignore ?? true;
|
|
111
112
|
const patternHasNewline = normalizedPattern.includes("\n") || normalizedPattern.includes("\\n");
|
|
112
113
|
const effectiveMultiline = patternHasNewline;
|
|
113
114
|
|
|
114
115
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
|
115
|
-
const formatScopePath = (targetPath: string): string =>
|
|
116
|
-
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
|
117
|
-
return relative.length === 0 ? "." : relative;
|
|
118
|
-
};
|
|
116
|
+
const formatScopePath = (targetPath: string): string => formatPathRelativeToCwd(targetPath, this.session.cwd);
|
|
119
117
|
let searchPath: string;
|
|
120
118
|
let scopePath: string;
|
|
121
119
|
let exactFilePaths: string[] | undefined;
|
|
@@ -131,7 +129,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
131
129
|
}
|
|
132
130
|
const resource = await internalRouter.resolve(rawPath);
|
|
133
131
|
if (!resource.sourcePath) {
|
|
134
|
-
throw new ToolError(`Cannot
|
|
132
|
+
throw new ToolError(`Cannot search internal URL without a backing file: ${rawPath}`);
|
|
135
133
|
}
|
|
136
134
|
searchPath = resource.sourcePath;
|
|
137
135
|
scopePath = formatScopePath(searchPath);
|
|
@@ -225,14 +223,8 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
225
223
|
throw err;
|
|
226
224
|
}
|
|
227
225
|
|
|
228
|
-
const formatPath = (filePath: string): string =>
|
|
229
|
-
|
|
230
|
-
const cleanPath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
231
|
-
if (isDirectory) {
|
|
232
|
-
return cleanPath.replace(/\\/g, "/");
|
|
233
|
-
}
|
|
234
|
-
return path.basename(cleanPath);
|
|
235
|
-
};
|
|
226
|
+
const formatPath = (filePath: string): string =>
|
|
227
|
+
formatResultPath(filePath, isDirectory, searchPath, this.session.cwd);
|
|
236
228
|
|
|
237
229
|
// Build output
|
|
238
230
|
const roundRobinSelect = (matches: GrepMatch[], limit: number): GrepMatch[] => {
|
|
@@ -273,7 +265,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
273
265
|
const { record: recordFile, list: fileList } = createFileRecorder();
|
|
274
266
|
const fileMatchCounts = new Map<string, number>();
|
|
275
267
|
if (selectedMatches.length === 0) {
|
|
276
|
-
const details:
|
|
268
|
+
const details: SearchToolDetails = {
|
|
277
269
|
scopePath,
|
|
278
270
|
matchCount: 0,
|
|
279
271
|
fileCount: 0,
|
|
@@ -356,7 +348,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
356
348
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
357
349
|
const output = truncation.content;
|
|
358
350
|
const truncated = Boolean(matchLimitReached || result.limitReached || truncation.truncated || linesTruncated);
|
|
359
|
-
const details:
|
|
351
|
+
const details: SearchToolDetails = {
|
|
360
352
|
scopePath,
|
|
361
353
|
matchCount: selectedMatches.length,
|
|
362
354
|
fileCount: fileList.length,
|
|
@@ -387,7 +379,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
|
|
387
379
|
// TUI Renderer
|
|
388
380
|
// =============================================================================
|
|
389
381
|
|
|
390
|
-
interface
|
|
382
|
+
interface SearchRenderArgs {
|
|
391
383
|
pattern: string;
|
|
392
384
|
path?: string;
|
|
393
385
|
i?: boolean;
|
|
@@ -397,9 +389,9 @@ interface GrepRenderArgs {
|
|
|
397
389
|
|
|
398
390
|
const COLLAPSED_TEXT_LIMIT = PREVIEW_LIMITS.COLLAPSED_LINES * 2;
|
|
399
391
|
|
|
400
|
-
export const
|
|
392
|
+
export const searchToolRenderer = {
|
|
401
393
|
inline: true,
|
|
402
|
-
renderCall(args:
|
|
394
|
+
renderCall(args: SearchRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
403
395
|
const meta: string[] = [];
|
|
404
396
|
if (args.path) meta.push(`in ${args.path}`);
|
|
405
397
|
if (args.i) meta.push("case:insensitive");
|
|
@@ -407,17 +399,17 @@ export const grepToolRenderer = {
|
|
|
407
399
|
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
408
400
|
|
|
409
401
|
const text = renderStatusLine(
|
|
410
|
-
{ icon: "pending", title: "
|
|
402
|
+
{ icon: "pending", title: "Search", description: args.pattern || "?", meta },
|
|
411
403
|
uiTheme,
|
|
412
404
|
);
|
|
413
405
|
return new Text(text, 0, 0);
|
|
414
406
|
},
|
|
415
407
|
|
|
416
408
|
renderResult(
|
|
417
|
-
result: { content: Array<{ type: string; text?: string }>; details?:
|
|
409
|
+
result: { content: Array<{ type: string; text?: string }>; details?: SearchToolDetails; isError?: boolean },
|
|
418
410
|
options: RenderResultOptions,
|
|
419
411
|
uiTheme: Theme,
|
|
420
|
-
args?:
|
|
412
|
+
args?: SearchRenderArgs,
|
|
421
413
|
): Component {
|
|
422
414
|
const details = result.details;
|
|
423
415
|
|
|
@@ -436,7 +428,7 @@ export const grepToolRenderer = {
|
|
|
436
428
|
const lines = textContent.split("\n").filter(line => line.trim() !== "");
|
|
437
429
|
const description = args?.pattern ?? undefined;
|
|
438
430
|
const header = renderStatusLine(
|
|
439
|
-
{ icon: "success", title: "
|
|
431
|
+
{ icon: "success", title: "Search", description, meta: [formatCount("item", lines.length)] },
|
|
440
432
|
uiTheme,
|
|
441
433
|
);
|
|
442
434
|
let cached: RenderCache | undefined;
|
|
@@ -476,7 +468,7 @@ export const grepToolRenderer = {
|
|
|
476
468
|
|
|
477
469
|
if (matchCount === 0) {
|
|
478
470
|
const header = renderStatusLine(
|
|
479
|
-
{ icon: "warning", title: "
|
|
471
|
+
{ icon: "warning", title: "Search", description: args?.pattern, meta: ["0 matches"] },
|
|
480
472
|
uiTheme,
|
|
481
473
|
);
|
|
482
474
|
return new Text([header, formatEmptyMessage("No matches found", uiTheme)].join("\n"), 0, 0);
|
|
@@ -488,7 +480,7 @@ export const grepToolRenderer = {
|
|
|
488
480
|
if (truncated) meta.push(uiTheme.fg("warning", "truncated"));
|
|
489
481
|
const description = args?.pattern ?? undefined;
|
|
490
482
|
const header = renderStatusLine(
|
|
491
|
-
{ icon: truncated ? "warning" : "success", title: "
|
|
483
|
+
{ icon: truncated ? "warning" : "success", title: "Search", description, meta },
|
|
492
484
|
uiTheme,
|
|
493
485
|
);
|
|
494
486
|
|
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;
|