pi-readseek 0.4.16 → 0.4.18
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/index.ts +5 -4
- package/package.json +2 -2
- package/src/edit.ts +9 -20
- package/src/fs-error.ts +30 -0
- package/src/grep-render-helpers.ts +2 -2
- package/src/grep-symbol-scope.ts +13 -26
- package/src/grep.ts +1 -2
- package/src/hashline.ts +2 -2
- package/src/pending-diff-preview.ts +1 -1
- package/src/read-local-bundle.ts +2 -14
- package/src/read.ts +15 -19
- package/src/readseek/symbol-lookup.ts +2 -14
- package/src/readseek/symbol-tree.ts +21 -0
- package/src/readseek-client.ts +20 -20
- package/src/readseek-params.ts +1 -1
- package/src/readseek-value.ts +20 -0
- package/src/refs-output.ts +2 -11
- package/src/refs.ts +1 -1
- package/src/register-tool.ts +10 -0
- package/src/sg-output.ts +2 -10
- package/src/sg.ts +1 -5
- package/src/stat-search-path.ts +3 -3
- package/src/write.ts +11 -18
package/index.ts
CHANGED
|
@@ -2,10 +2,11 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { registerReadTool } from "./src/read.js";
|
|
3
3
|
import { registerEditTool } from "./src/edit.js";
|
|
4
4
|
import { registerGrepTool } from "./src/grep.js";
|
|
5
|
-
import { registerSgTool
|
|
5
|
+
import { registerSgTool } from "./src/sg.js";
|
|
6
6
|
import { registerRefsTool } from "./src/refs.js";
|
|
7
7
|
import { registerWriteTool } from "./src/write.js";
|
|
8
8
|
import { SessionAnchors } from "./src/session-anchors.js";
|
|
9
|
+
import { isReadSeekAvailable } from "./src/readseek-client.js";
|
|
9
10
|
|
|
10
11
|
export default function piReadSeekExtension(pi: ExtensionAPI): void {
|
|
11
12
|
const sessionAnchors = new SessionAnchors();
|
|
@@ -14,10 +15,10 @@ export default function piReadSeekExtension(pi: ExtensionAPI): void {
|
|
|
14
15
|
|
|
15
16
|
registerReadTool(pi, { onSuccessfulRead: markAnchored });
|
|
16
17
|
registerEditTool(pi, { wasReadInSession: hasFreshAnchors });
|
|
17
|
-
const
|
|
18
|
-
const searchGuideline =
|
|
18
|
+
const searchAvailable = isReadSeekAvailable();
|
|
19
|
+
const searchGuideline = searchAvailable
|
|
19
20
|
? "Use grep summary for counts; use search for structural code patterns."
|
|
20
|
-
: "Use grep summary for counts;
|
|
21
|
+
: "Use grep summary for counts; search is unavailable (readseek native backend not loaded).";
|
|
21
22
|
|
|
22
23
|
registerGrepTool(pi, { searchGuideline, onFileAnchored: markAnchored });
|
|
23
24
|
registerSgTool(pi, { onFileAnchored: markAnchored });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-readseek",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"description": "Pi extension for readseek-backed hash-anchored read/edit/grep, structural code maps, structural search, and file exploration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=20.0.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@jarkkojs/readseek": "^0.4.
|
|
42
|
+
"@jarkkojs/readseek": "^0.4.21",
|
|
43
43
|
"diff": "^8.0.3",
|
|
44
44
|
"xxhash-wasm": "^1.1.0"
|
|
45
45
|
},
|
package/src/edit.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { HashlineMismatchError, applyHashlineEdits, computeLineHash, ensureHashI
|
|
|
12
12
|
import { resolveToCwd } from "./path-utils.js";
|
|
13
13
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
14
14
|
import { throwIfAborted } from "./runtime.js";
|
|
15
|
+
import { classifyFsError } from "./fs-error.js";
|
|
15
16
|
import { buildEditOutput } from "./edit-output.js";
|
|
16
17
|
import { classifyEdit, isDifftAvailable, runDifftastic } from "./edit-classify.js";
|
|
17
18
|
import type { SemanticSummary } from "./readseek-value.js";
|
|
@@ -26,7 +27,7 @@ import { buildDiffData, type DiffBlockRange } from "./diff-data.js";
|
|
|
26
27
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
27
28
|
import { upsertDiffComponent, upsertTextComponent } from "./tui-diff-component.js";
|
|
28
29
|
import type { FreshAnchorsPredicate } from "./tool-types.js";
|
|
29
|
-
import { registerReadSeekTool } from "./register-tool.js";
|
|
30
|
+
import { filePathParam, registerReadSeekTool } from "./register-tool.js";
|
|
30
31
|
|
|
31
32
|
import { resolveEditDiffDisplay } from "./readseek-settings.js";
|
|
32
33
|
|
|
@@ -46,7 +47,7 @@ const hashlineEditItemSchema = Type.Union([
|
|
|
46
47
|
{ replace_lines: Type.Object({ start_anchor: Type.String(), end_anchor: Type.String(), new_text: Type.String() }) },
|
|
47
48
|
{ additionalProperties: true },
|
|
48
49
|
),
|
|
49
|
-
Type.Object({ insert_after: Type.Object({ anchor: Type.String(), new_text: Type.String()
|
|
50
|
+
Type.Object({ insert_after: Type.Object({ anchor: Type.String(), new_text: Type.String() }) }, { additionalProperties: true }),
|
|
50
51
|
Type.Object(
|
|
51
52
|
{ replace: Type.Object({ old_text: Type.String(), new_text: Type.String(), all: Type.Optional(Type.Boolean()), fuzzy: Type.Optional(Type.Boolean()) }) },
|
|
52
53
|
{ additionalProperties: true },
|
|
@@ -55,18 +56,11 @@ const hashlineEditItemSchema = Type.Union([
|
|
|
55
56
|
{ replace_symbol: Type.Object({ symbol: Type.String(), new_body: Type.String() }) },
|
|
56
57
|
{ additionalProperties: true },
|
|
57
58
|
),
|
|
58
|
-
Type.Object(
|
|
59
|
-
{ old_text: Type.String(), new_text: Type.String() },
|
|
60
|
-
{
|
|
61
|
-
additionalProperties: true,
|
|
62
|
-
description: "Do not use — Wrap as { replace: {old_text, new_text} }.",
|
|
63
|
-
},
|
|
64
|
-
),
|
|
65
59
|
]);
|
|
66
60
|
|
|
67
61
|
const hashlineEditSchema = Type.Object(
|
|
68
62
|
{
|
|
69
|
-
path:
|
|
63
|
+
path: filePathParam(),
|
|
70
64
|
edits: Type.Optional(Type.Array(hashlineEditItemSchema, { description: "Edit operations" })),
|
|
71
65
|
postEditVerify: Type.Optional(Type.Boolean({
|
|
72
66
|
description: "Verify persisted content after write",
|
|
@@ -112,17 +106,12 @@ function buildEditError(
|
|
|
112
106
|
|
|
113
107
|
function mapEditFileError(err: any, filePath: string, displayPath: string, phase: "read" | "write"): ReturnType<typeof buildEditError> {
|
|
114
108
|
const code = err?.code;
|
|
115
|
-
if (code === "
|
|
116
|
-
return buildEditError(filePath, "
|
|
117
|
-
`Path is a directory: ${displayPath}`,
|
|
118
|
-
`Use ls(${JSON.stringify(displayPath)}) to inspect directories.`);
|
|
119
|
-
}
|
|
120
|
-
if (code === "ENOENT") {
|
|
121
|
-
return buildEditError(filePath, "file-not-found",
|
|
122
|
-
`${phase === "write" ? "Failed to write file" : "File not found"}: ${displayPath}`);
|
|
109
|
+
if (code === "ENOENT" && phase === "write") {
|
|
110
|
+
return buildEditError(filePath, "file-not-found", `Failed to write file: ${displayPath}`);
|
|
123
111
|
}
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
const fsError = classifyFsError(err, displayPath);
|
|
113
|
+
if (fsError) {
|
|
114
|
+
return buildEditError(filePath, fsError.code, fsError.message, fsError.hint);
|
|
126
115
|
}
|
|
127
116
|
const prefix = phase === "write" ? "Failed to write file" : "File not readable";
|
|
128
117
|
const message = `${prefix}: ${displayPath}${err?.message ? ` — ${err.message}` : ""}`;
|
package/src/fs-error.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type FsErrorCode = "path-is-directory" | "permission-denied" | "file-not-found";
|
|
2
|
+
|
|
3
|
+
export interface FsErrorInfo {
|
|
4
|
+
code: FsErrorCode;
|
|
5
|
+
message: string;
|
|
6
|
+
hint?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Translate a Node `fs` errno into the shared readseek error taxonomy with a
|
|
11
|
+
* canonical message. Returns null for errno values without a dedicated code so
|
|
12
|
+
* callers fall back to their own `fs-error` handling.
|
|
13
|
+
*/
|
|
14
|
+
export function classifyFsError(err: { code?: unknown }, path: string): FsErrorInfo | null {
|
|
15
|
+
switch (err?.code) {
|
|
16
|
+
case "EISDIR":
|
|
17
|
+
return {
|
|
18
|
+
code: "path-is-directory",
|
|
19
|
+
message: `Path is a directory: ${path}`,
|
|
20
|
+
hint: `Use ls(${JSON.stringify(path)}) to inspect directories.`,
|
|
21
|
+
};
|
|
22
|
+
case "EACCES":
|
|
23
|
+
case "EPERM":
|
|
24
|
+
return { code: "permission-denied", message: `Permission denied: ${path}` };
|
|
25
|
+
case "ENOENT":
|
|
26
|
+
return { code: "file-not-found", message: `File not found: ${path}` };
|
|
27
|
+
default:
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -22,7 +22,7 @@ export function formatGrepCallText(
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const GREP_TRUNCATION_THRESHOLD = 50;
|
|
25
|
+
export const GREP_TRUNCATION_THRESHOLD = 50;
|
|
26
26
|
|
|
27
27
|
interface GrepResultTextInput {
|
|
28
28
|
totalMatches: number;
|
|
@@ -56,7 +56,7 @@ export function formatGrepResultText(input: GrepResultTextInput): GrepResultText
|
|
|
56
56
|
|
|
57
57
|
const { totalMatches, summary, fileCount } = input;
|
|
58
58
|
const noMatches = totalMatches === 0;
|
|
59
|
-
const truncated = totalMatches
|
|
59
|
+
const truncated = totalMatches > GREP_TRUNCATION_THRESHOLD;
|
|
60
60
|
|
|
61
61
|
const matchWord = totalMatches === 1 ? "match" : "matches";
|
|
62
62
|
const fileWord = fileCount === 1 ? "file" : "files";
|
package/src/grep-symbol-scope.ts
CHANGED
|
@@ -1,38 +1,25 @@
|
|
|
1
1
|
import { buildReadSeekLine } from "./readseek-value.js";
|
|
2
2
|
import type { GrepOutputEntry, GrepOutputGroup, GrepOutputScopeSymbol, GrepScopeWarning } from "./grep-output.js";
|
|
3
|
-
import type { FileMap
|
|
4
|
-
|
|
5
|
-
interface FlatSymbol extends GrepOutputScopeSymbol {
|
|
6
|
-
rangeSize: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function flattenSymbols(symbols: FileSymbol[], parentName?: string): FlatSymbol[] {
|
|
10
|
-
const flattened: FlatSymbol[] = [];
|
|
11
|
-
for (const symbol of symbols) {
|
|
12
|
-
flattened.push({
|
|
13
|
-
name: symbol.name,
|
|
14
|
-
kind: symbol.kind,
|
|
15
|
-
startLine: symbol.startLine,
|
|
16
|
-
endLine: symbol.endLine,
|
|
17
|
-
parentName,
|
|
18
|
-
rangeSize: symbol.endLine - symbol.startLine,
|
|
19
|
-
});
|
|
20
|
-
if (symbol.children?.length) flattened.push(...flattenSymbols(symbol.children, symbol.name));
|
|
21
|
-
}
|
|
22
|
-
return flattened;
|
|
23
|
-
}
|
|
3
|
+
import type { FileMap } from "./readseek/types.js";
|
|
4
|
+
import { traverseSymbolTree } from "./readseek/symbol-tree.js";
|
|
24
5
|
|
|
25
6
|
function findEnclosingSymbol(map: FileMap, lineNumber: number): GrepOutputScopeSymbol | null {
|
|
26
|
-
const candidates =
|
|
7
|
+
const candidates = traverseSymbolTree(map.symbols, (symbol, parentName): GrepOutputScopeSymbol => ({
|
|
8
|
+
name: symbol.name,
|
|
9
|
+
kind: symbol.kind,
|
|
10
|
+
startLine: symbol.startLine,
|
|
11
|
+
endLine: symbol.endLine,
|
|
12
|
+
parentName,
|
|
13
|
+
}))
|
|
27
14
|
.filter((s) => lineNumber >= s.startLine && lineNumber <= s.endLine)
|
|
28
15
|
.sort((a, b) => {
|
|
29
|
-
|
|
16
|
+
const rangeA = a.endLine - a.startLine;
|
|
17
|
+
const rangeB = b.endLine - b.startLine;
|
|
18
|
+
if (rangeA !== rangeB) return rangeA - rangeB;
|
|
30
19
|
if (a.startLine !== b.startLine) return a.startLine - b.startLine;
|
|
31
20
|
return a.name.localeCompare(b.name);
|
|
32
21
|
});
|
|
33
|
-
|
|
34
|
-
const { rangeSize: _rangeSize, ...symbol } = candidates[0];
|
|
35
|
-
return symbol;
|
|
22
|
+
return candidates.length ? candidates[0] : null;
|
|
36
23
|
}
|
|
37
24
|
|
|
38
25
|
function firstLineNumber(group: GrepOutputGroup): number {
|
package/src/grep.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { getOrGenerateMap } from "./file-map.js";
|
|
|
17
17
|
import { scopeGrepGroupsToSymbols } from "./grep-symbol-scope.js";
|
|
18
18
|
import { resolveToCwd } from "./path-utils.js";
|
|
19
19
|
import { throwIfAborted } from "./runtime.js";
|
|
20
|
-
import { formatGrepCallText, formatGrepResultText } from "./grep-render-helpers.js";
|
|
20
|
+
import { formatGrepCallText, formatGrepResultText, GREP_TRUNCATION_THRESHOLD } from "./grep-render-helpers.js";
|
|
21
21
|
import { coerceObviousBase10Int } from "./coerce-obvious-int.js";
|
|
22
22
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderErrorResult, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
23
23
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
@@ -89,7 +89,6 @@ function parseGrepOutputLine(line: string):
|
|
|
89
89
|
return null;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const GREP_TRUNCATION_THRESHOLD = 50;
|
|
93
92
|
const GREP_MAX_MATCHES_PER_FILE = 10;
|
|
94
93
|
|
|
95
94
|
type GrepAnchoredEntry = Extract<GrepOutputEntry, { kind: "match" | "context" }>;
|
package/src/hashline.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { CONFUSABLE_HYPHENS_RE } from "./confusable-hyphens.js";
|
|
|
14
14
|
export type HashlineEditItem =
|
|
15
15
|
| { set_line: { anchor: string; new_text: string } }
|
|
16
16
|
| { replace_lines: { start_anchor: string; end_anchor: string; new_text: string } }
|
|
17
|
-
| { insert_after: { anchor: string; new_text: string
|
|
17
|
+
| { insert_after: { anchor: string; new_text: string } }
|
|
18
18
|
| { replace: { old_text: string; new_text: string; all?: boolean } };
|
|
19
19
|
|
|
20
20
|
interface HashMismatch {
|
|
@@ -382,7 +382,7 @@ function parseHashlineEditItem(edit: HashlineEditItem): ParsedEdit {
|
|
|
382
382
|
if ("insert_after" in edit) {
|
|
383
383
|
return {
|
|
384
384
|
spec: { kind: "insertAfter", after: parseLineRef(edit.insert_after.anchor) },
|
|
385
|
-
dstLines: stripNewLinePrefixes(splitDst(edit.insert_after.new_text ??
|
|
385
|
+
dstLines: stripNewLinePrefixes(splitDst(edit.insert_after.new_text ?? "")),
|
|
386
386
|
};
|
|
387
387
|
}
|
|
388
388
|
throw new Error("replace edits are applied separately");
|
|
@@ -94,7 +94,7 @@ function isReplaceEdit(edit: unknown): edit is ReplaceEdit {
|
|
|
94
94
|
type AnchorEdit =
|
|
95
95
|
| { set_line: { anchor: string; new_text: string } }
|
|
96
96
|
| { replace_lines: { start_anchor: string; end_anchor: string; new_text: string } }
|
|
97
|
-
| { insert_after: { anchor: string; new_text: string
|
|
97
|
+
| { insert_after: { anchor: string; new_text: string } };
|
|
98
98
|
|
|
99
99
|
function isAnchorEdit(edit: unknown): edit is AnchorEdit {
|
|
100
100
|
return !!edit && typeof edit === "object" && ("set_line" in edit || "replace_lines" in edit || "insert_after" in edit);
|
package/src/read-local-bundle.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SymbolKind } from "./readseek/enums.js";
|
|
2
2
|
import type { SymbolMatch } from "./readseek/symbol-lookup.js";
|
|
3
3
|
import type { FileMap, FileSymbol } from "./readseek/types.js";
|
|
4
|
+
import { traverseSymbolTree } from "./readseek/symbol-tree.js";
|
|
4
5
|
|
|
5
6
|
export interface LocalBundleSupport {
|
|
6
7
|
symbol: SymbolMatch;
|
|
@@ -23,19 +24,6 @@ const CONTAINER_KINDS = new Set<SymbolKind>([
|
|
|
23
24
|
SymbolKind.Namespace,
|
|
24
25
|
]);
|
|
25
26
|
|
|
26
|
-
function flattenSymbols(symbols: FileSymbol[], parentName?: string): FlatSymbol[] {
|
|
27
|
-
const flattened: FlatSymbol[] = [];
|
|
28
|
-
|
|
29
|
-
for (const symbol of symbols) {
|
|
30
|
-
flattened.push({ symbol, parentName });
|
|
31
|
-
if (symbol.children?.length) {
|
|
32
|
-
flattened.push(...flattenSymbols(symbol.children, symbol.name));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return flattened;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
27
|
export function buildLocalBundle(
|
|
40
28
|
fileMap: FileMap,
|
|
41
29
|
requested: SymbolMatch,
|
|
@@ -44,7 +32,7 @@ export function buildLocalBundle(
|
|
|
44
32
|
const requestedText = allLines.slice(requested.startLine - 1, requested.endLine).join("\n");
|
|
45
33
|
const identifiers = new Set(requestedText.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? []);
|
|
46
34
|
|
|
47
|
-
const candidates =
|
|
35
|
+
const candidates = traverseSymbolTree(fileMap.symbols, (symbol, parentName): FlatSymbol => ({ symbol, parentName })).filter(({ symbol }) => {
|
|
48
36
|
if (CONTAINER_KINDS.has(symbol.kind as SymbolKind)) return false;
|
|
49
37
|
return !(symbol.name === requested.name && symbol.startLine === requested.startLine && symbol.endLine === requested.endLine);
|
|
50
38
|
});
|
package/src/read.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { buildReadSeekWarning, buildToolErrorResult, renderReadSeekLines, type R
|
|
|
17
17
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
18
18
|
import { resolveToCwd } from "./path-utils.js";
|
|
19
19
|
import { throwIfAborted } from "./runtime.js";
|
|
20
|
+
import { classifyFsError } from "./fs-error.js";
|
|
20
21
|
import { getOrGenerateMap } from "./file-map.js";
|
|
21
22
|
import { formatFileMapWithBudget } from "./readseek/formatter.js";
|
|
22
23
|
import { findSymbol, type SymbolMatch } from "./readseek/symbol-lookup.js";
|
|
@@ -29,7 +30,7 @@ import { readseekRead, readseekDetect, type ReadSeekDetection } from "./readseek
|
|
|
29
30
|
import { formatReadCallText, formatReadResultText } from "./read-render-helpers.js";
|
|
30
31
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine, wrapReadHashlinesForWidth } from "./tui-render-utils.js";
|
|
31
32
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
32
|
-
import { optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
|
|
33
|
+
import { filePathParam, mapParam, optionalIntOrString, registerReadSeekTool } from "./register-tool.js";
|
|
33
34
|
|
|
34
35
|
const READ_PROMPT_METADATA = defineToolPromptMetadata({
|
|
35
36
|
promptUrl: new URL("../prompts/read.md", import.meta.url),
|
|
@@ -144,17 +145,12 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
144
145
|
rawBuffer = await fsReadFile(absolutePath);
|
|
145
146
|
} catch (err: any) {
|
|
146
147
|
const code = err?.code;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return buildToolErrorResult("read",
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return buildToolErrorResult("read", "permission-denied", message, { path: rawParams.path });
|
|
154
|
-
}
|
|
155
|
-
if (code === "ENOENT") {
|
|
156
|
-
const message = `File not found: ${rawPath}`;
|
|
157
|
-
return buildToolErrorResult("read", "file-not-found", message, { path: rawParams.path });
|
|
148
|
+
const fsError = classifyFsError(err, rawPath);
|
|
149
|
+
if (fsError) {
|
|
150
|
+
return buildToolErrorResult("read", fsError.code, fsError.message, {
|
|
151
|
+
path: rawParams.path,
|
|
152
|
+
...(fsError.hint ? { hint: fsError.hint } : {}),
|
|
153
|
+
});
|
|
158
154
|
}
|
|
159
155
|
const message = `File not readable: ${rawPath}${err?.message ? ` — ${err.message}` : ""}`;
|
|
160
156
|
return buildToolErrorResult("read", "fs-error", message, { path: rawParams.path, details: { fsCode: code, fsMessage: err?.message } });
|
|
@@ -162,23 +158,23 @@ export async function executeRead(opts: ExecuteReadOptions): Promise<AgentToolRe
|
|
|
162
158
|
|
|
163
159
|
const hasBinaryContent = looksLikeBinary(rawBuffer);
|
|
164
160
|
if (hasBinaryContent) {
|
|
165
|
-
// Images are always binary; classify and
|
|
161
|
+
// Images are always binary; classify and transcribe in a single readseek detect call.
|
|
166
162
|
let detection: ReadSeekDetection | undefined;
|
|
167
163
|
try {
|
|
168
|
-
detection = await readseekDetect(absolutePath, {
|
|
164
|
+
detection = await readseekDetect(absolutePath, { transcribe: true, signal });
|
|
169
165
|
} catch {
|
|
170
166
|
// detect unavailable — fall through to binary-as-text handling below
|
|
171
167
|
}
|
|
172
168
|
if (detection?.type === "image") {
|
|
173
169
|
const builtinRead = createReadTool(cwd);
|
|
174
170
|
const builtinResult = await builtinRead.execute(toolCallId, p, signal, onUpdate);
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
171
|
+
const transcript = detection.transcribe?.text?.trim();
|
|
172
|
+
if (transcript) {
|
|
177
173
|
return succeed({
|
|
178
174
|
...builtinResult,
|
|
179
175
|
content: [
|
|
180
176
|
...(builtinResult.content ?? []),
|
|
181
|
-
{ type: "text" as const, text: `
|
|
177
|
+
{ type: "text" as const, text: `Transcribed text from image:\n${transcript}` },
|
|
182
178
|
],
|
|
183
179
|
});
|
|
184
180
|
}
|
|
@@ -450,11 +446,11 @@ export function registerReadTool(pi: ExtensionAPI, options: ReadToolOptions = {}
|
|
|
450
446
|
promptSnippet: READ_PROMPT_METADATA.promptSnippet,
|
|
451
447
|
promptGuidelines: READ_PROMPT_METADATA.promptGuidelines,
|
|
452
448
|
parameters: Type.Object({
|
|
453
|
-
path:
|
|
449
|
+
path: filePathParam(),
|
|
454
450
|
offset: optionalIntOrString("Start line (1-indexed)"),
|
|
455
451
|
limit: optionalIntOrString("Max lines"),
|
|
456
452
|
symbol: Type.Optional(Type.String({ description: "Symbol name to read" })),
|
|
457
|
-
map:
|
|
453
|
+
map: mapParam(),
|
|
458
454
|
bundle: Type.Optional(
|
|
459
455
|
Type.Literal("local", {
|
|
460
456
|
description: "Include same-file local support",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FileMap, FileSymbol } from "./types.js";
|
|
2
2
|
import type { SymbolKind } from "./enums.js";
|
|
3
|
+
import { traverseSymbolTree } from "./symbol-tree.js";
|
|
3
4
|
|
|
4
5
|
export interface SymbolMatch {
|
|
5
6
|
name: string;
|
|
@@ -40,20 +41,7 @@ function toMatches(candidates: SymbolCandidate[]): SymbolMatch[] {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
function flattenSymbols(symbols: FileSymbol[]): SymbolCandidate[] {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const visit = (symbol: FileSymbol, parentName?: string): void => {
|
|
46
|
-
flattened.push({ symbol, parentName });
|
|
47
|
-
for (const child of symbol.children ?? []) {
|
|
48
|
-
visit(child, symbol.name);
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
for (const symbol of symbols) {
|
|
53
|
-
visit(symbol);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return flattened;
|
|
44
|
+
return traverseSymbolTree(symbols, (symbol, parentName) => ({ symbol, parentName }));
|
|
57
45
|
}
|
|
58
46
|
|
|
59
47
|
function resolveDotPath(symbols: FileSymbol[], parts: string[]): SymbolCandidate[] {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileSymbol } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Depth-first walk over a readseek symbol tree. {@link visit} is called for
|
|
5
|
+
* every symbol with the enclosing symbol's name as `parentName` (undefined at
|
|
6
|
+
* the top level); its results are returned flattened in pre-order.
|
|
7
|
+
*/
|
|
8
|
+
export function traverseSymbolTree<T>(
|
|
9
|
+
symbols: FileSymbol[],
|
|
10
|
+
visit: (symbol: FileSymbol, parentName?: string) => T,
|
|
11
|
+
): T[] {
|
|
12
|
+
const result: T[] = [];
|
|
13
|
+
const walk = (nodes: FileSymbol[], parentName?: string) => {
|
|
14
|
+
for (const symbol of nodes) {
|
|
15
|
+
result.push(visit(symbol, parentName));
|
|
16
|
+
if (symbol.children?.length) walk(symbol.children, symbol.name);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
walk(symbols);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
package/src/readseek-client.ts
CHANGED
|
@@ -108,14 +108,14 @@ export interface ReadSeekCheckOutput {
|
|
|
108
108
|
diagnostics: ReadSeekDiagnostic[];
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export interface
|
|
111
|
+
export interface ReadSeekTranscriptRegion {
|
|
112
112
|
text: string;
|
|
113
|
-
|
|
113
|
+
quad: [number, number, number, number, number, number, number, number];
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export interface
|
|
116
|
+
export interface ReadSeekTranscript {
|
|
117
117
|
text: string;
|
|
118
|
-
|
|
118
|
+
regions: ReadSeekTranscriptRegion[];
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
export type ReadSeekDetection =
|
|
@@ -136,7 +136,7 @@ export type ReadSeekDetection =
|
|
|
136
136
|
width: number;
|
|
137
137
|
height: number;
|
|
138
138
|
animated: boolean;
|
|
139
|
-
|
|
139
|
+
transcribe?: ReadSeekTranscript;
|
|
140
140
|
}
|
|
141
141
|
| {
|
|
142
142
|
type: "binary";
|
|
@@ -641,21 +641,21 @@ export async function readseekCheck(
|
|
|
641
641
|
);
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
function
|
|
644
|
+
function parseTranscript(value: unknown): ReadSeekTranscript | undefined {
|
|
645
645
|
if (value === undefined || value === null) return undefined;
|
|
646
|
-
if (typeof value !== "object") throw new Error("invalid readseek detect
|
|
647
|
-
const
|
|
648
|
-
if (!Array.isArray(
|
|
646
|
+
if (typeof value !== "object") throw new Error("invalid readseek detect transcribe");
|
|
647
|
+
const transcribe = value as Record<string, unknown>;
|
|
648
|
+
if (!Array.isArray(transcribe.regions)) throw new Error("invalid readseek detect transcribe.regions");
|
|
649
649
|
return {
|
|
650
|
-
text: requireString(
|
|
651
|
-
|
|
652
|
-
if (!
|
|
653
|
-
const item =
|
|
654
|
-
const
|
|
655
|
-
if (!Array.isArray(
|
|
650
|
+
text: requireString(transcribe.text, "transcribe.text"),
|
|
651
|
+
regions: transcribe.regions.map((region) => {
|
|
652
|
+
if (!region || typeof region !== "object") throw new Error("invalid readseek detect transcribe region");
|
|
653
|
+
const item = region as Record<string, unknown>;
|
|
654
|
+
const quad = item.quad;
|
|
655
|
+
if (!Array.isArray(quad) || quad.length !== 8) throw new Error("invalid readseek detect transcribe quad");
|
|
656
656
|
return {
|
|
657
|
-
text: requireString(item.text, "
|
|
658
|
-
|
|
657
|
+
text: requireString(item.text, "transcribe.region.text"),
|
|
658
|
+
quad: quad.map((n, i) => requireNumber(n, `transcribe.region.quad[${i}]`)) as ReadSeekTranscriptRegion["quad"],
|
|
659
659
|
};
|
|
660
660
|
}),
|
|
661
661
|
};
|
|
@@ -687,7 +687,7 @@ function parseDetectOutput(value: unknown): ReadSeekDetection {
|
|
|
687
687
|
width: requireNumber(output.width, "width"),
|
|
688
688
|
height: requireNumber(output.height, "height"),
|
|
689
689
|
animated: requireBoolean(output.animated, "animated"),
|
|
690
|
-
|
|
690
|
+
transcribe: parseTranscript(output.transcribe),
|
|
691
691
|
};
|
|
692
692
|
case "binary":
|
|
693
693
|
case "text":
|
|
@@ -699,10 +699,10 @@ function parseDetectOutput(value: unknown): ReadSeekDetection {
|
|
|
699
699
|
|
|
700
700
|
export async function readseekDetect(
|
|
701
701
|
filePath: string,
|
|
702
|
-
options: {
|
|
702
|
+
options: { transcribe?: boolean; signal?: AbortSignal } = {},
|
|
703
703
|
): Promise<ReadSeekDetection> {
|
|
704
704
|
const args = ["detect"];
|
|
705
|
-
if (options.
|
|
705
|
+
if (options.transcribe) args.push("--transcribe");
|
|
706
706
|
args.push(filePath);
|
|
707
707
|
return parseDetectOutput(await runReadSeek(args, { signal: options.signal }));
|
|
708
708
|
}
|
package/src/readseek-params.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function validateIgnoredRequiresOthers(
|
|
|
33
33
|
params: { others?: boolean; ignored?: boolean },
|
|
34
34
|
): ToolErrorResult | null {
|
|
35
35
|
if (params.ignored && !params.others) {
|
|
36
|
-
return buildToolErrorResult(tool, "invalid-parameter",
|
|
36
|
+
return buildToolErrorResult(tool, "invalid-parameter", `${tool} parameter 'ignored' requires 'others'`);
|
|
37
37
|
}
|
|
38
38
|
return null;
|
|
39
39
|
}
|
package/src/readseek-value.ts
CHANGED
|
@@ -86,6 +86,26 @@ export function renderReadSeekLines(lines: ReadSeekLine[]): string {
|
|
|
86
86
|
return lines.map(renderReadSeekLine).join("\n");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Render the anchored-file block format shared by the `search` and `refs`
|
|
91
|
+
* tools: a `--- <displayPath> ---` header per file followed by one
|
|
92
|
+
* `>><anchor>|<display>` line per match. {@link lineSuffix} appends tool
|
|
93
|
+
* specific trailers (e.g. refs' enclosing-symbol annotation).
|
|
94
|
+
*/
|
|
95
|
+
export function formatAnchoredFileBlocks<T extends ReadSeekLine>(
|
|
96
|
+
files: Array<{ displayPath: string; lines: T[] }>,
|
|
97
|
+
lineSuffix?: (line: T) => string,
|
|
98
|
+
): string {
|
|
99
|
+
const blocks: string[] = [];
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
blocks.push(`--- ${file.displayPath} ---`);
|
|
102
|
+
for (const line of file.lines) {
|
|
103
|
+
blocks.push(`>>${line.anchor}|${line.display}${lineSuffix?.(line) ?? ""}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return blocks.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
export function buildReadSeekWarning(
|
|
90
110
|
code: string,
|
|
91
111
|
message: string,
|
package/src/refs-output.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { formatAnchoredFileBlocks, type ReadSeekLine } from "./readseek-value.js";
|
|
2
2
|
|
|
3
3
|
export interface RefsOutputLine extends ReadSeekLine {
|
|
4
4
|
enclosingSymbol?: string;
|
|
@@ -34,17 +34,8 @@ export function buildRefsOutput(input: BuildRefsOutputInput): RefsOutputResult {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const blocks: string[] = [];
|
|
38
|
-
for (const file of input.files) {
|
|
39
|
-
blocks.push(`--- ${file.displayPath} ---`);
|
|
40
|
-
for (const line of file.lines) {
|
|
41
|
-
const suffix = line.enclosingSymbol ? ` (in ${line.enclosingSymbol})` : "";
|
|
42
|
-
blocks.push(`>>${line.anchor}|${line.display}${suffix}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
37
|
return {
|
|
47
|
-
text:
|
|
38
|
+
text: formatAnchoredFileBlocks(input.files, (line) => (line.enclosingSymbol ? ` (in ${line.enclosingSymbol})` : "")),
|
|
48
39
|
readseekValue: {
|
|
49
40
|
tool: "refs",
|
|
50
41
|
files: input.files.map((file) => ({
|
package/src/refs.ts
CHANGED
|
@@ -86,7 +86,7 @@ export async function executeRefs(opts: ExecuteRefsOptions): Promise<any> {
|
|
|
86
86
|
const ignoredError = validateIgnoredRequiresOthers("refs", p);
|
|
87
87
|
if (ignoredError) return ignoredError;
|
|
88
88
|
if (p.scope && p.line === undefined) {
|
|
89
|
-
return buildToolErrorResult("refs", "invalid-parameter", "
|
|
89
|
+
return buildToolErrorResult("refs", "invalid-parameter", "refs parameter 'scope' requires 'line'");
|
|
90
90
|
}
|
|
91
91
|
const searchPath = resolveToCwd(p.path ?? ".", cwd);
|
|
92
92
|
|
package/src/register-tool.ts
CHANGED
|
@@ -10,6 +10,16 @@ export function optionalIntOrString(description: string) {
|
|
|
10
10
|
return Type.Optional(Type.Union([Type.Number({ description }), Type.String({ description })]));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** Required file-path parameter shared by the read, edit, and write tools. */
|
|
14
|
+
export function filePathParam() {
|
|
15
|
+
return Type.String({ description: "File path" });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Optional structural-map toggle shared by the read and write tools. */
|
|
19
|
+
export function mapParam() {
|
|
20
|
+
return Type.Optional(Type.Boolean({ description: "Append structural map" }));
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
export type ReadSeekToolPolicy = "read-only" | "mutating";
|
|
14
24
|
|
|
15
25
|
export type ReadSeekToolExposure = "safe-by-default" | "opt-in" | "not-safe-by-default";
|
package/src/sg-output.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { formatAnchoredFileBlocks, type ReadSeekLine, type ReadSeekRange } from "./readseek-value.js";
|
|
2
2
|
|
|
3
3
|
export interface SgOutputFile {
|
|
4
4
|
displayPath: string;
|
|
@@ -36,16 +36,8 @@ export function buildSgOutput(input: BuildSgOutputInput): SgOutputResult {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const blocks: string[] = [];
|
|
40
|
-
for (const file of input.files) {
|
|
41
|
-
blocks.push(`--- ${file.displayPath} ---`);
|
|
42
|
-
for (const line of file.lines) {
|
|
43
|
-
blocks.push(`>>${line.anchor}|${line.display}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
39
|
return {
|
|
48
|
-
text:
|
|
40
|
+
text: formatAnchoredFileBlocks(input.files),
|
|
49
41
|
readseekValue: {
|
|
50
42
|
tool: "search",
|
|
51
43
|
files: input.files.map((file) => ({
|
package/src/sg.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { defineToolPromptMetadata } from "./tool-prompt-metadata.js";
|
|
|
7
7
|
import { buildReadSeekLineWithHash, buildToolErrorResult, type ReadSeekLine } from "./readseek-value.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
9
|
import { statSearchPathOrError } from "./stat-search-path.js";
|
|
10
|
-
import { classifyReadSeekFailure,
|
|
10
|
+
import { classifyReadSeekFailure, readseekSearch, type ReadSeekHashline, type ReadSeekSearchFileOutput } from "./readseek-client.js";
|
|
11
11
|
import { buildSgOutput } from "./sg-output.js";
|
|
12
12
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
13
13
|
import { langParam, readseekGitSearchParams, searchPathParam, validateIgnoredRequiresOthers } from "./readseek-params.js";
|
|
@@ -52,10 +52,6 @@ const SG_PROMPT_METADATA = defineToolPromptMetadata({
|
|
|
52
52
|
promptSnippet: "Search code structurally with readseek and return edit-ready anchors",
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
export function isSgAvailable(): boolean {
|
|
56
|
-
return isReadSeekAvailable();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
55
|
interface SgToolOptions {
|
|
60
56
|
onFileAnchored?: FileAnchoredCallback;
|
|
61
57
|
}
|
package/src/stat-search-path.ts
CHANGED
|
@@ -23,12 +23,12 @@ export async function statSearchPathOrError(
|
|
|
23
23
|
const display = rawPath ?? ".";
|
|
24
24
|
const path = rawPath ?? searchPath;
|
|
25
25
|
if (err?.code === "ENOENT") {
|
|
26
|
-
return { ok: false, error: buildToolErrorResult(tool, "path-not-found", `
|
|
26
|
+
return { ok: false, error: buildToolErrorResult(tool, "path-not-found", `Path '${display}' does not exist`, { path }) };
|
|
27
27
|
}
|
|
28
28
|
if (err?.code === "EACCES" || err?.code === "EPERM") {
|
|
29
|
-
return { ok: false, error: buildToolErrorResult(tool, "permission-denied", `
|
|
29
|
+
return { ok: false, error: buildToolErrorResult(tool, "permission-denied", `Permission denied for path '${display}'`, { path }) };
|
|
30
30
|
}
|
|
31
|
-
const message = `
|
|
31
|
+
const message = `Could not access path '${display}': ${err?.message ?? String(err)}`;
|
|
32
32
|
return { ok: false, error: buildToolErrorResult(tool, "fs-error", message, { path, details: { fsCode: err?.code, fsMessage: err?.message } }) };
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/write.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { resolveToCwd } from "./path-utils.js";
|
|
|
9
9
|
import { ensureHashInit, formatHashlineDisplay } from "./hashline.js";
|
|
10
10
|
import { buildReadSeekError, buildReadSeekLine, buildReadSeekWarning, buildToolErrorResult, type ReadSeekLine, type ReadSeekWarning } from "./readseek-value.js";
|
|
11
11
|
import { looksLikeBinary } from "./binary-detect.js";
|
|
12
|
+
import { classifyFsError, type FsErrorCode } from "./fs-error.js";
|
|
12
13
|
import { getOrGenerateMap } from "./file-map.js";
|
|
13
14
|
import { formatFileMapWithBudget } from "./readseek/formatter.js";
|
|
14
15
|
|
|
@@ -19,7 +20,7 @@ import { buildDiffData, type DiffData } from "./diff-data.js";
|
|
|
19
20
|
import { clampLineToWidth, clampLinesToWidth, linkToolPath, renderErrorResult, renderPendingResult, renderToolLabel, resolveRenderResultContext, summaryLine } from "./tui-render-utils.js";
|
|
20
21
|
import { upsertDiffComponent, upsertTextComponent } from "./tui-diff-component.js";
|
|
21
22
|
import type { FileAnchoredCallback } from "./tool-types.js";
|
|
22
|
-
import { registerReadSeekTool } from "./register-tool.js";
|
|
23
|
+
import { filePathParam, mapParam, registerReadSeekTool } from "./register-tool.js";
|
|
23
24
|
|
|
24
25
|
const WRITE_PENDING_PREVIEW_STATE_KEY = "hashline-write-pending-preview";
|
|
25
26
|
|
|
@@ -120,8 +121,9 @@ export interface WriteToolOptions {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
type MappedFsError = {
|
|
123
|
-
code:
|
|
124
|
+
code: FsErrorCode | "fs-error";
|
|
124
125
|
message: string;
|
|
126
|
+
hint?: string;
|
|
125
127
|
includeMeta: boolean;
|
|
126
128
|
};
|
|
127
129
|
|
|
@@ -129,20 +131,6 @@ function mapFsWriteError(err: any, path: string): MappedFsError {
|
|
|
129
131
|
const phase: "mkdir" | "write" | undefined = err?.__phase;
|
|
130
132
|
const fsCode = err?.code as string | undefined;
|
|
131
133
|
|
|
132
|
-
if (fsCode === "EACCES" || fsCode === "EPERM") {
|
|
133
|
-
return {
|
|
134
|
-
code: "permission-denied",
|
|
135
|
-
message: `Permission denied — cannot write: ${path}`,
|
|
136
|
-
includeMeta: false,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (fsCode === "EISDIR") {
|
|
140
|
-
return {
|
|
141
|
-
code: "path-is-directory",
|
|
142
|
-
message: `Path is a directory — cannot overwrite: ${path}`,
|
|
143
|
-
includeMeta: false,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
134
|
if (fsCode === "ENOENT" && phase === "mkdir") {
|
|
147
135
|
return {
|
|
148
136
|
code: "fs-error",
|
|
@@ -164,6 +152,10 @@ function mapFsWriteError(err: any, path: string): MappedFsError {
|
|
|
164
152
|
includeMeta: true,
|
|
165
153
|
};
|
|
166
154
|
}
|
|
155
|
+
const fsError = classifyFsError(err, path);
|
|
156
|
+
if (fsError) {
|
|
157
|
+
return { code: fsError.code, message: fsError.message, hint: fsError.hint, includeMeta: false };
|
|
158
|
+
}
|
|
167
159
|
return {
|
|
168
160
|
code: "fs-error",
|
|
169
161
|
message: `Error writing ${path}: ${err?.message ?? String(err)}`,
|
|
@@ -175,6 +167,7 @@ function buildWriteFsErrorResult(err: any, absolutePath: string) {
|
|
|
175
167
|
const mapped = mapFsWriteError(err, absolutePath);
|
|
176
168
|
return buildToolErrorResult("write", mapped.code, mapped.message, {
|
|
177
169
|
path: absolutePath,
|
|
170
|
+
hint: mapped.hint,
|
|
178
171
|
extra: { lines: [], warnings: [] },
|
|
179
172
|
details: mapped.includeMeta ? { fsCode: err?.code, fsMessage: err?.message } : undefined,
|
|
180
173
|
});
|
|
@@ -321,9 +314,9 @@ export function registerWriteTool(pi: ExtensionAPI, options: WriteToolOptions =
|
|
|
321
314
|
promptSnippet: WRITE_PROMPT_METADATA.promptSnippet,
|
|
322
315
|
promptGuidelines: WRITE_PROMPT_METADATA.promptGuidelines,
|
|
323
316
|
parameters: Type.Object({
|
|
324
|
-
path:
|
|
317
|
+
path: filePathParam(),
|
|
325
318
|
content: Type.String({ description: "File content" }),
|
|
326
|
-
map:
|
|
319
|
+
map: mapParam(),
|
|
327
320
|
}),
|
|
328
321
|
async execute(_toolCallId: string, params: { path: string; content: string; map?: boolean }, _signal: AbortSignal | undefined, _onUpdate: any, ctx: any): Promise<any> {
|
|
329
322
|
const cwd = ctx?.cwd ?? process.cwd();
|