@agimon-ai/po-grep 0.0.0-alpha.30 → 0.0.0-alpha.32
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/dist/extensions/mcp.cjs +69 -38
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.mjs +69 -38
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/server.cjs +75 -35
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +75 -35
- package/dist/extensions/server.mjs.map +1 -1
- package/package.json +9 -8
package/dist/extensions/mcp.cjs
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
let _agimon_ai_po_core_mcpFacet = require("@agimon-ai/po-core/mcpFacet");
|
|
6
|
-
let
|
|
6
|
+
let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
let _agimon_ai_po_hashline = require("@agimon-ai/po-hashline");
|
|
9
9
|
let _agimon_ai_po_hashline_files = require("@agimon-ai/po-hashline/files");
|
|
@@ -31,15 +31,14 @@ const GrepParamsSchema = typebox.Type.Object({
|
|
|
31
31
|
/** Search with ripgrep independently of the host. */
|
|
32
32
|
async function runRipgrep(request) {
|
|
33
33
|
const { params, cwd, signal } = request;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}] };
|
|
34
|
+
const { stdout, truncated } = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: stdout.trim() === "" ? "No matches found" : limitMatches(stdout, params.limit)
|
|
39
|
+
}],
|
|
40
|
+
...truncated ? { details: { truncated: true } } : {}
|
|
41
|
+
};
|
|
43
42
|
}
|
|
44
43
|
/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */
|
|
45
44
|
function argumentsFor(params, cwd) {
|
|
@@ -88,13 +87,28 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
88
87
|
"pipe"
|
|
89
88
|
]
|
|
90
89
|
});
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const stdout = [];
|
|
91
|
+
const stderr = [];
|
|
92
|
+
let stdoutBytes = 0;
|
|
93
|
+
let stderrBytes = 0;
|
|
94
|
+
let truncated = false;
|
|
95
|
+
const collect = (chunks, chunk, bytes, cap) => {
|
|
96
|
+
if (truncated) return bytes;
|
|
97
|
+
const retained = Math.min(chunk.length, cap - bytes);
|
|
98
|
+
if (retained > 0) chunks.push(Buffer.from(chunk.subarray(0, retained)));
|
|
99
|
+
(0, _agimon_ai_po_core_platform.recordRipgrepOutput)(retained, bytes + retained >= cap);
|
|
100
|
+
bytes += retained;
|
|
101
|
+
if (bytes >= cap) {
|
|
102
|
+
truncated = true;
|
|
103
|
+
child.kill("SIGTERM");
|
|
104
|
+
}
|
|
105
|
+
return bytes;
|
|
106
|
+
};
|
|
93
107
|
child.stdout.on("data", (chunk) => {
|
|
94
|
-
stdout
|
|
108
|
+
stdoutBytes = collect(stdout, chunk, stdoutBytes, 8388608);
|
|
95
109
|
});
|
|
96
110
|
child.stderr.on("data", (chunk) => {
|
|
97
|
-
stderr
|
|
111
|
+
stderrBytes = collect(stderr, chunk, stderrBytes, 65536);
|
|
98
112
|
});
|
|
99
113
|
const abort = () => {
|
|
100
114
|
child.kill("SIGTERM");
|
|
@@ -111,11 +125,14 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
111
125
|
});
|
|
112
126
|
child.once("close", (code) => {
|
|
113
127
|
signal?.removeEventListener("abort", abort);
|
|
114
|
-
if (code === 0 || code === 1) {
|
|
115
|
-
resolveOutput(
|
|
128
|
+
if (truncated || code === 0 || code === 1) {
|
|
129
|
+
resolveOutput({
|
|
130
|
+
stdout: Buffer.concat(stdout, stdoutBytes).toString("utf8"),
|
|
131
|
+
truncated
|
|
132
|
+
});
|
|
116
133
|
return;
|
|
117
134
|
}
|
|
118
|
-
reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? "unknown"}`));
|
|
135
|
+
reject(new Error(Buffer.concat(stderr, stderrBytes).toString("utf8").trim() || `ripgrep exited with code ${code ?? "unknown"}`));
|
|
119
136
|
});
|
|
120
137
|
});
|
|
121
138
|
}
|
|
@@ -124,7 +141,7 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
124
141
|
const MATCH_DELIMITER = /:(\d+):/gu;
|
|
125
142
|
const CONTEXT_DELIMITER = /-(\d+)-/gu;
|
|
126
143
|
const NO_MATCHES = "No matches found";
|
|
127
|
-
function createHeadlessGrepTool() {
|
|
144
|
+
function createHeadlessGrepTool(filesystem) {
|
|
128
145
|
return {
|
|
129
146
|
name: "grep",
|
|
130
147
|
label: "grep",
|
|
@@ -137,35 +154,32 @@ function createHeadlessGrepTool() {
|
|
|
137
154
|
params,
|
|
138
155
|
cwd: context.cwd,
|
|
139
156
|
signal
|
|
140
|
-
}), params, context.cwd, signal);
|
|
157
|
+
}), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
|
|
141
158
|
}
|
|
142
159
|
};
|
|
143
160
|
}
|
|
144
|
-
/** Node adapters for hashline post-processing. */
|
|
145
|
-
const nodeGrepFileSystem = {
|
|
146
|
-
stat: (path) => (0, node_fs_promises.stat)(path),
|
|
147
|
-
readFile: (path) => (0, node_fs_promises.readFile)(path)
|
|
148
|
-
};
|
|
149
161
|
const defaultGrepOutput = {
|
|
150
162
|
maxBytes: _earendil_works_pi_coding_agent.DEFAULT_MAX_BYTES,
|
|
151
163
|
formatSize: _earendil_works_pi_coding_agent.formatSize,
|
|
152
164
|
truncateHead: _earendil_works_pi_coding_agent.truncateHead,
|
|
153
165
|
truncateLine: _earendil_works_pi_coding_agent.truncateLine
|
|
154
166
|
};
|
|
155
|
-
async function tagGrepResult(nativeResult, params, cwd, signal, writable = _agimon_ai_po_hashline_files.isWritableFile,
|
|
167
|
+
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = _agimon_ai_po_hashline_files.isWritableFile, outputOperations = defaultGrepOutput) {
|
|
156
168
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
157
169
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
158
170
|
const noticeIndex = textPart.text.lastIndexOf("\n\n[");
|
|
159
171
|
const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);
|
|
160
172
|
const notice = noticeIndex === -1 ? "" : textPart.text.slice(noticeIndex);
|
|
161
173
|
const searchPath = (0, _agimon_ai_po_hashline_files.resolveInputPath)(params.path ?? ".", cwd);
|
|
162
|
-
const searchStats = await filesystem.stat(searchPath);
|
|
163
174
|
assertNotAborted(signal);
|
|
164
|
-
const
|
|
175
|
+
const context = signal ? (0, _agimon_ai_po_core_platform.withAbortSignal)(signal, _agimon_ai_po_core_platform.BACKGROUND_CONTEXT) : _agimon_ai_po_core_platform.BACKGROUND_CONTEXT;
|
|
176
|
+
const searchStats = await grepFileInfo(filesystem, searchPath, signal);
|
|
177
|
+
assertNotAborted(signal);
|
|
178
|
+
const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.kind === "directory", signal, filesystem);
|
|
165
179
|
if (rows.length === 0) return nativeResult;
|
|
166
|
-
const groups = groupGrepRows(rows, searchPath, searchStats.
|
|
180
|
+
const groups = groupGrepRows(rows, searchPath, searchStats.kind === "directory", cwd);
|
|
167
181
|
const blocks = [];
|
|
168
|
-
let
|
|
182
|
+
let changedFiles = 0;
|
|
169
183
|
let linesTruncated = nativeResult.details?.linesTruncated ?? false;
|
|
170
184
|
for (const group of groups.values()) {
|
|
171
185
|
assertNotAborted(signal);
|
|
@@ -177,8 +191,14 @@ async function tagGrepResult(nativeResult, params, cwd, signal, writable = _agim
|
|
|
177
191
|
}
|
|
178
192
|
let bytes;
|
|
179
193
|
try {
|
|
180
|
-
bytes = await
|
|
181
|
-
} catch {
|
|
194
|
+
bytes = await (0, _agimon_ai_po_core_platform.readBoundedFile)(filesystem, group.absolutePath, 8388608, context);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
assertNotAborted(signal);
|
|
197
|
+
if (error instanceof _agimon_ai_po_core_platform.FileTooLargeError) {
|
|
198
|
+
blocks.push(`${group.nativeRows.join("\n")}\n[${group.path}: anchors omitted (file over 8 MiB)]`);
|
|
199
|
+
changedFiles++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
182
202
|
blocks.push(group.nativeRows.join("\n"));
|
|
183
203
|
continue;
|
|
184
204
|
}
|
|
@@ -194,10 +214,10 @@ async function tagGrepResult(nativeResult, params, cwd, signal, writable = _agim
|
|
|
194
214
|
}
|
|
195
215
|
if (output.length > 1) {
|
|
196
216
|
blocks.push(output.join("\n"));
|
|
197
|
-
|
|
217
|
+
changedFiles++;
|
|
198
218
|
} else blocks.push(group.nativeRows.join("\n"));
|
|
199
219
|
}
|
|
200
|
-
if (blocks.length === 0 ||
|
|
220
|
+
if (blocks.length === 0 || changedFiles === 0) return nativeResult;
|
|
201
221
|
const truncation = outputOperations.truncateHead(blocks.join("\n"), { maxLines: Number.MAX_SAFE_INTEGER });
|
|
202
222
|
const extraNotice = truncation.truncated ? `\n\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]` : "";
|
|
203
223
|
const details = {
|
|
@@ -234,6 +254,13 @@ function groupGrepRows(rows, searchPath, directory, cwd) {
|
|
|
234
254
|
}
|
|
235
255
|
return groups;
|
|
236
256
|
}
|
|
257
|
+
async function grepFileInfo(filesystem, path, signal) {
|
|
258
|
+
const context = signal ? (0, _agimon_ai_po_core_platform.withAbortSignal)(signal, _agimon_ai_po_core_platform.BACKGROUND_CONTEXT) : _agimon_ai_po_core_platform.BACKGROUND_CONTEXT;
|
|
259
|
+
const info = (0, _agimon_ai_po_core_platform.getOrThrow)(await filesystem.fileInfo(path, context));
|
|
260
|
+
if (info.kind !== "symlink") return info;
|
|
261
|
+
const target = (0, _agimon_ai_po_core_platform.getOrThrow)(await filesystem.canonicalPath(path, context));
|
|
262
|
+
return (0, _agimon_ai_po_core_platform.getOrThrow)(await filesystem.fileInfo(target, context));
|
|
263
|
+
}
|
|
237
264
|
async function parseValidatedGrepRows(rowsText, searchPath, directory, signal, filesystem) {
|
|
238
265
|
const pathCache = /* @__PURE__ */ new Map();
|
|
239
266
|
const rows = [];
|
|
@@ -254,7 +281,7 @@ async function parseValidatedGrepRows(rowsText, searchPath, directory, signal, f
|
|
|
254
281
|
const absolutePath = (0, node_path.resolve)(searchPath, candidate.path);
|
|
255
282
|
let exists = pathCache.get(absolutePath);
|
|
256
283
|
if (exists === void 0) {
|
|
257
|
-
exists = await filesystem
|
|
284
|
+
exists = await grepFileInfo(filesystem, absolutePath, signal).then((value) => value.kind === "file").catch(() => false);
|
|
258
285
|
assertNotAborted(signal);
|
|
259
286
|
pathCache.set(absolutePath, exists);
|
|
260
287
|
}
|
|
@@ -292,10 +319,14 @@ function assertNotAborted(signal) {
|
|
|
292
319
|
}
|
|
293
320
|
//#endregion
|
|
294
321
|
//#region src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts
|
|
295
|
-
var grep_mcp_default = (0, _agimon_ai_po_core_mcpFacet.defineMcpTool)(() =>
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
322
|
+
var grep_mcp_default = (0, _agimon_ai_po_core_mcpFacet.defineMcpTool)((context) => {
|
|
323
|
+
const filesystem = context.services.get(_agimon_ai_po_core_platform.PO_FS_SERVICE);
|
|
324
|
+
if (!filesystem) throw new Error("Grep filesystem service is unavailable.");
|
|
325
|
+
return {
|
|
326
|
+
...createHeadlessGrepTool(filesystem),
|
|
327
|
+
description: "Search file contents in the Po session's repository on the host machine (ripgrep, regex or literal, code search). Respects .gitignore. Matches include file hashes and line anchors for this server's edit tool."
|
|
328
|
+
};
|
|
329
|
+
});
|
|
299
330
|
//#endregion
|
|
300
331
|
//#region generated/mcp.ts
|
|
301
332
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.cjs","names":["Type","resolveInputPath","spawn","stat","readFile","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","basename","defineMcpTool","defineMcpPlugin","toolGrep"],"sources":["../../src/schemas/grepTool.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const GrepParamsSchema = Type.Object({\n pattern: Type.String({ description: 'Regular expression or literal text to find.' }),\n path: Type.Optional(Type.String({ description: 'File or directory to search. Defaults to the working directory.' })),\n glob: Type.Optional(Type.String({ description: 'Glob filter for files to search.' })),\n ignoreCase: Type.Optional(Type.Boolean({ description: 'Match without case sensitivity.' })),\n literal: Type.Optional(Type.Boolean({ description: 'Treat pattern as literal text.' })),\n context: Type.Optional(Type.Integer({ minimum: 0, description: 'Context lines around every match.' })),\n limit: Type.Optional(Type.Integer({ minimum: 1, description: 'Maximum number of matches.' })),\n});\n\nexport type GrepParams = Static<typeof GrepParamsSchema>;\n","import { spawn } from 'node:child_process';\n\nimport { resolveInputPath } from '@agimon-ai/po-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/** Search with ripgrep independently of the host. */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const args = ['--color', 'never', '--no-heading', '--with-filename', '--line-number'];\n if (params.ignoreCase === true) args.push('--ignore-case');\n if (params.literal === true) args.push('--fixed-strings');\n if (params.context !== undefined) args.push('--context', String(params.context));\n if (params.glob !== undefined) args.push('--glob', params.glob);\n args.push('--', params.pattern, target);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type { PoHeadlessExecutionContext, PoHeadlessTool, PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/po-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/po-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+):/gu;\nconst CONTEXT_DELIMITER = /-(\\d+)-/gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): PoHeadlessTool<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n execute: async (\n _toolCallId: string,\n params: GrepParams,\n signal: AbortSignal | undefined,\n _onUpdate: ((result: PoHeadlessToolResult) => void) | undefined,\n context: PoHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(result, params, context.cwd, signal);\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\n/** Node adapters for hashline post-processing. */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath) || candidate.path === searchPath) {\n rows.push({ ...candidate, native: value });\n break;\n }\n continue;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool } from '@agimon-ai/po-core/mcpFacet';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool(() => ({\n ...createHeadlessGrepTool(),\n description:\n \"Search file contents in the Po session's repository on the host machine (ripgrep, regex or literal, code search). Respects .gitignore. Matches include file hashes and line anchors for this server's edit tool.\",\n}));\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type PoMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-grep/grep\", via({ name: 'grep' }, at(toolGrep, context)))]; },\n }) satisfies PoMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp.cjs","names":["Type","resolveInputPath","spawn","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","withAbortSignal","BACKGROUND_CONTEXT","readBoundedFile","FileTooLargeError","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","getOrThrow","basename","defineMcpTool","PO_FS_SERVICE","defineMcpPlugin","toolGrep"],"sources":["../../src/schemas/grepTool.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const GrepParamsSchema = Type.Object({\n pattern: Type.String({ description: 'Regular expression or literal text to find.' }),\n path: Type.Optional(Type.String({ description: 'File or directory to search. Defaults to the working directory.' })),\n glob: Type.Optional(Type.String({ description: 'Glob filter for files to search.' })),\n ignoreCase: Type.Optional(Type.Boolean({ description: 'Match without case sensitivity.' })),\n literal: Type.Optional(Type.Boolean({ description: 'Treat pattern as literal text.' })),\n context: Type.Optional(Type.Integer({ minimum: 0, description: 'Context lines around every match.' })),\n limit: Type.Optional(Type.Integer({ minimum: 1, description: 'Maximum number of matches.' })),\n});\n\nexport type GrepParams = Static<typeof GrepParamsSchema>;\n","import { spawn } from 'node:child_process';\n\nimport { recordRipgrepOutput } from '@agimon-ai/po-core/platform';\nimport { resolveInputPath } from '@agimon-ai/po-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/** Search with ripgrep independently of the host. */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const { stdout, truncated } = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n return {\n content: [{ type: 'text', text: stdout.trim() === '' ? 'No matches found' : limitMatches(stdout, params.limit) }],\n ...(truncated ? { details: { truncated: true } } : {}),\n };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const args = ['--color', 'never', '--no-heading', '--with-filename', '--line-number'];\n if (params.ignoreCase === true) args.push('--ignore-case');\n if (params.literal === true) args.push('--fixed-strings');\n if (params.context !== undefined) args.push('--context', String(params.context));\n if (params.glob !== undefined) args.push('--glob', params.glob);\n args.push('--', params.pattern, target);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(\n args: readonly string[],\n cwd: string,\n signal: AbortSignal | undefined,\n): Promise<{ stdout: string; truncated: boolean }> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n let stdoutBytes = 0;\n let stderrBytes = 0;\n let truncated = false;\n const collect = (chunks: Buffer[], chunk: Buffer, bytes: number, cap: number): number => {\n if (truncated) return bytes;\n const retained = Math.min(chunk.length, cap - bytes);\n if (retained > 0) chunks.push(Buffer.from(chunk.subarray(0, retained)));\n recordRipgrepOutput(retained, bytes + retained >= cap);\n bytes += retained;\n if (bytes >= cap) {\n truncated = true;\n child.kill('SIGTERM');\n }\n return bytes;\n };\n child.stdout.on('data', (chunk: Buffer) => {\n stdoutBytes = collect(stdout, chunk, stdoutBytes, 8 * 1024 * 1024);\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderrBytes = collect(stderr, chunk, stderrBytes, 64 * 1024);\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (truncated || code === 0 || code === 1) {\n resolveOutput({ stdout: Buffer.concat(stdout, stdoutBytes).toString('utf8'), truncated });\n return;\n }\n reject(\n new Error(\n Buffer.concat(stderr, stderrBytes).toString('utf8').trim() || `ripgrep exited with code ${code ?? 'unknown'}`,\n ),\n );\n });\n });\n}\n","import { basename, resolve } from 'node:path';\n\nimport type { PoHeadlessExecutionContext, PoHeadlessTool, PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\nimport {\n BACKGROUND_CONTEXT,\n FileTooLargeError,\n getOrThrow,\n readBoundedFile,\n withAbortSignal,\n type FileSystem,\n} from '@agimon-ai/po-core/platform';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/po-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/po-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+):/gu;\nconst CONTEXT_DELIMITER = /-(\\d+)-/gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(\n filesystem: FileSystem | (() => FileSystem),\n): PoHeadlessTool<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n execute: async (\n _toolCallId: string,\n params: GrepParams,\n signal: AbortSignal | undefined,\n _onUpdate: ((result: PoHeadlessToolResult) => void) | undefined,\n context: PoHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(\n result,\n params,\n context.cwd,\n signal,\n typeof filesystem === 'function' ? filesystem() : filesystem,\n );\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n filesystem: FileSystem,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n assertNotAborted(signal);\n const context = signal ? withAbortSignal(signal, BACKGROUND_CONTEXT) : BACKGROUND_CONTEXT;\n const searchStats = await grepFileInfo(filesystem, searchPath, signal);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.kind === 'directory', signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.kind === 'directory', cwd);\n const blocks: string[] = [];\n let changedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await readBoundedFile(filesystem, group.absolutePath, 8 * 1024 * 1024, context);\n } catch (error) {\n assertNotAborted(signal);\n if (error instanceof FileTooLargeError) {\n blocks.push(`${group.nativeRows.join('\\n')}\\n[${group.path}: anchors omitted (file over 8 MiB)]`);\n changedFiles++;\n continue;\n }\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n changedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || changedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\n// fileInfo reports links themselves; grep validation historically follows their targets.\nasync function grepFileInfo(filesystem: FileSystem, path: string, signal: AbortSignal | undefined) {\n const context = signal ? withAbortSignal(signal, BACKGROUND_CONTEXT) : BACKGROUND_CONTEXT;\n const info = getOrThrow(await filesystem.fileInfo(path, context));\n if (info.kind !== 'symlink') return info;\n const target = getOrThrow(await filesystem.canonicalPath(path, context));\n return getOrThrow(await filesystem.fileInfo(target, context));\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: FileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath) || candidate.path === searchPath) {\n rows.push({ ...candidate, native: value });\n break;\n }\n continue;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await grepFileInfo(filesystem, absolutePath, signal)\n .then((value) => value.kind === 'file')\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool, type PoMcpPluginContext } from '@agimon-ai/po-core/mcpFacet';\nimport { PO_FS_SERVICE, type FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool((context: PoMcpPluginContext) => {\n const filesystem = context.services.get<FileSystem>(PO_FS_SERVICE);\n if (!filesystem) throw new Error('Grep filesystem service is unavailable.');\n return {\n ...createHeadlessGrepTool(filesystem),\n description:\n \"Search file contents in the Po session's repository on the host machine (ripgrep, regex or literal, code search). Respects .gitignore. Matches include file hashes and line anchors for this server's edit tool.\",\n };\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineMcpPlugin, type PoMcpSessionPlugin } from '@agimon-ai/po-core/mcpFacet';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nconst withWidget = <T>(key: string, value: T): T => value === undefined ? value : ({\n ...value, _meta: { ...(value as { _meta?: object })._meta, [\"po/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/po-grep/grep\", via({ name: 'grep' }, at(toolGrep, context)))]; },\n }) satisfies PoMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
|
package/dist/extensions/mcp.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineMcpPlugin, defineMcpTool } from "@agimon-ai/po-core/mcpFacet";
|
|
2
|
-
import {
|
|
2
|
+
import { BACKGROUND_CONTEXT, FileTooLargeError, PO_FS_SERVICE, getOrThrow, readBoundedFile, recordRipgrepOutput, withAbortSignal } from "@agimon-ai/po-core/platform";
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { formatFileHeader, formatTaggedLine, splitLines } from "@agimon-ai/po-hashline";
|
|
5
5
|
import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath } from "@agimon-ai/po-hashline/files";
|
|
@@ -27,15 +27,14 @@ const GrepParamsSchema = Type.Object({
|
|
|
27
27
|
/** Search with ripgrep independently of the host. */
|
|
28
28
|
async function runRipgrep(request) {
|
|
29
29
|
const { params, cwd, signal } = request;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}] };
|
|
30
|
+
const { stdout, truncated } = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);
|
|
31
|
+
return {
|
|
32
|
+
content: [{
|
|
33
|
+
type: "text",
|
|
34
|
+
text: stdout.trim() === "" ? "No matches found" : limitMatches(stdout, params.limit)
|
|
35
|
+
}],
|
|
36
|
+
...truncated ? { details: { truncated: true } } : {}
|
|
37
|
+
};
|
|
39
38
|
}
|
|
40
39
|
/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */
|
|
41
40
|
function argumentsFor(params, cwd) {
|
|
@@ -84,13 +83,28 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
84
83
|
"pipe"
|
|
85
84
|
]
|
|
86
85
|
});
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
const stdout = [];
|
|
87
|
+
const stderr = [];
|
|
88
|
+
let stdoutBytes = 0;
|
|
89
|
+
let stderrBytes = 0;
|
|
90
|
+
let truncated = false;
|
|
91
|
+
const collect = (chunks, chunk, bytes, cap) => {
|
|
92
|
+
if (truncated) return bytes;
|
|
93
|
+
const retained = Math.min(chunk.length, cap - bytes);
|
|
94
|
+
if (retained > 0) chunks.push(Buffer.from(chunk.subarray(0, retained)));
|
|
95
|
+
recordRipgrepOutput(retained, bytes + retained >= cap);
|
|
96
|
+
bytes += retained;
|
|
97
|
+
if (bytes >= cap) {
|
|
98
|
+
truncated = true;
|
|
99
|
+
child.kill("SIGTERM");
|
|
100
|
+
}
|
|
101
|
+
return bytes;
|
|
102
|
+
};
|
|
89
103
|
child.stdout.on("data", (chunk) => {
|
|
90
|
-
stdout
|
|
104
|
+
stdoutBytes = collect(stdout, chunk, stdoutBytes, 8388608);
|
|
91
105
|
});
|
|
92
106
|
child.stderr.on("data", (chunk) => {
|
|
93
|
-
stderr
|
|
107
|
+
stderrBytes = collect(stderr, chunk, stderrBytes, 65536);
|
|
94
108
|
});
|
|
95
109
|
const abort = () => {
|
|
96
110
|
child.kill("SIGTERM");
|
|
@@ -107,11 +121,14 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
107
121
|
});
|
|
108
122
|
child.once("close", (code) => {
|
|
109
123
|
signal?.removeEventListener("abort", abort);
|
|
110
|
-
if (code === 0 || code === 1) {
|
|
111
|
-
resolveOutput(
|
|
124
|
+
if (truncated || code === 0 || code === 1) {
|
|
125
|
+
resolveOutput({
|
|
126
|
+
stdout: Buffer.concat(stdout, stdoutBytes).toString("utf8"),
|
|
127
|
+
truncated
|
|
128
|
+
});
|
|
112
129
|
return;
|
|
113
130
|
}
|
|
114
|
-
reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? "unknown"}`));
|
|
131
|
+
reject(new Error(Buffer.concat(stderr, stderrBytes).toString("utf8").trim() || `ripgrep exited with code ${code ?? "unknown"}`));
|
|
115
132
|
});
|
|
116
133
|
});
|
|
117
134
|
}
|
|
@@ -120,7 +137,7 @@ function spawnRipgrep(args, cwd, signal) {
|
|
|
120
137
|
const MATCH_DELIMITER = /:(\d+):/gu;
|
|
121
138
|
const CONTEXT_DELIMITER = /-(\d+)-/gu;
|
|
122
139
|
const NO_MATCHES = "No matches found";
|
|
123
|
-
function createHeadlessGrepTool() {
|
|
140
|
+
function createHeadlessGrepTool(filesystem) {
|
|
124
141
|
return {
|
|
125
142
|
name: "grep",
|
|
126
143
|
label: "grep",
|
|
@@ -133,35 +150,32 @@ function createHeadlessGrepTool() {
|
|
|
133
150
|
params,
|
|
134
151
|
cwd: context.cwd,
|
|
135
152
|
signal
|
|
136
|
-
}), params, context.cwd, signal);
|
|
153
|
+
}), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
|
|
137
154
|
}
|
|
138
155
|
};
|
|
139
156
|
}
|
|
140
|
-
/** Node adapters for hashline post-processing. */
|
|
141
|
-
const nodeGrepFileSystem = {
|
|
142
|
-
stat: (path) => stat(path),
|
|
143
|
-
readFile: (path) => readFile(path)
|
|
144
|
-
};
|
|
145
157
|
const defaultGrepOutput = {
|
|
146
158
|
maxBytes: DEFAULT_MAX_BYTES,
|
|
147
159
|
formatSize,
|
|
148
160
|
truncateHead,
|
|
149
161
|
truncateLine
|
|
150
162
|
};
|
|
151
|
-
async function tagGrepResult(nativeResult, params, cwd, signal, writable = isWritableFile,
|
|
163
|
+
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = isWritableFile, outputOperations = defaultGrepOutput) {
|
|
152
164
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
153
165
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
154
166
|
const noticeIndex = textPart.text.lastIndexOf("\n\n[");
|
|
155
167
|
const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);
|
|
156
168
|
const notice = noticeIndex === -1 ? "" : textPart.text.slice(noticeIndex);
|
|
157
169
|
const searchPath = resolveInputPath(params.path ?? ".", cwd);
|
|
158
|
-
const searchStats = await filesystem.stat(searchPath);
|
|
159
170
|
assertNotAborted(signal);
|
|
160
|
-
const
|
|
171
|
+
const context = signal ? withAbortSignal(signal, BACKGROUND_CONTEXT) : BACKGROUND_CONTEXT;
|
|
172
|
+
const searchStats = await grepFileInfo(filesystem, searchPath, signal);
|
|
173
|
+
assertNotAborted(signal);
|
|
174
|
+
const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.kind === "directory", signal, filesystem);
|
|
161
175
|
if (rows.length === 0) return nativeResult;
|
|
162
|
-
const groups = groupGrepRows(rows, searchPath, searchStats.
|
|
176
|
+
const groups = groupGrepRows(rows, searchPath, searchStats.kind === "directory", cwd);
|
|
163
177
|
const blocks = [];
|
|
164
|
-
let
|
|
178
|
+
let changedFiles = 0;
|
|
165
179
|
let linesTruncated = nativeResult.details?.linesTruncated ?? false;
|
|
166
180
|
for (const group of groups.values()) {
|
|
167
181
|
assertNotAborted(signal);
|
|
@@ -173,8 +187,14 @@ async function tagGrepResult(nativeResult, params, cwd, signal, writable = isWri
|
|
|
173
187
|
}
|
|
174
188
|
let bytes;
|
|
175
189
|
try {
|
|
176
|
-
bytes = await filesystem
|
|
177
|
-
} catch {
|
|
190
|
+
bytes = await readBoundedFile(filesystem, group.absolutePath, 8388608, context);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
assertNotAborted(signal);
|
|
193
|
+
if (error instanceof FileTooLargeError) {
|
|
194
|
+
blocks.push(`${group.nativeRows.join("\n")}\n[${group.path}: anchors omitted (file over 8 MiB)]`);
|
|
195
|
+
changedFiles++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
178
198
|
blocks.push(group.nativeRows.join("\n"));
|
|
179
199
|
continue;
|
|
180
200
|
}
|
|
@@ -190,10 +210,10 @@ async function tagGrepResult(nativeResult, params, cwd, signal, writable = isWri
|
|
|
190
210
|
}
|
|
191
211
|
if (output.length > 1) {
|
|
192
212
|
blocks.push(output.join("\n"));
|
|
193
|
-
|
|
213
|
+
changedFiles++;
|
|
194
214
|
} else blocks.push(group.nativeRows.join("\n"));
|
|
195
215
|
}
|
|
196
|
-
if (blocks.length === 0 ||
|
|
216
|
+
if (blocks.length === 0 || changedFiles === 0) return nativeResult;
|
|
197
217
|
const truncation = outputOperations.truncateHead(blocks.join("\n"), { maxLines: Number.MAX_SAFE_INTEGER });
|
|
198
218
|
const extraNotice = truncation.truncated ? `\n\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]` : "";
|
|
199
219
|
const details = {
|
|
@@ -230,6 +250,13 @@ function groupGrepRows(rows, searchPath, directory, cwd) {
|
|
|
230
250
|
}
|
|
231
251
|
return groups;
|
|
232
252
|
}
|
|
253
|
+
async function grepFileInfo(filesystem, path, signal) {
|
|
254
|
+
const context = signal ? withAbortSignal(signal, BACKGROUND_CONTEXT) : BACKGROUND_CONTEXT;
|
|
255
|
+
const info = getOrThrow(await filesystem.fileInfo(path, context));
|
|
256
|
+
if (info.kind !== "symlink") return info;
|
|
257
|
+
const target = getOrThrow(await filesystem.canonicalPath(path, context));
|
|
258
|
+
return getOrThrow(await filesystem.fileInfo(target, context));
|
|
259
|
+
}
|
|
233
260
|
async function parseValidatedGrepRows(rowsText, searchPath, directory, signal, filesystem) {
|
|
234
261
|
const pathCache = /* @__PURE__ */ new Map();
|
|
235
262
|
const rows = [];
|
|
@@ -250,7 +277,7 @@ async function parseValidatedGrepRows(rowsText, searchPath, directory, signal, f
|
|
|
250
277
|
const absolutePath = resolve(searchPath, candidate.path);
|
|
251
278
|
let exists = pathCache.get(absolutePath);
|
|
252
279
|
if (exists === void 0) {
|
|
253
|
-
exists = await filesystem
|
|
280
|
+
exists = await grepFileInfo(filesystem, absolutePath, signal).then((value) => value.kind === "file").catch(() => false);
|
|
254
281
|
assertNotAborted(signal);
|
|
255
282
|
pathCache.set(absolutePath, exists);
|
|
256
283
|
}
|
|
@@ -288,10 +315,14 @@ function assertNotAborted(signal) {
|
|
|
288
315
|
}
|
|
289
316
|
//#endregion
|
|
290
317
|
//#region src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts
|
|
291
|
-
var grep_mcp_default = defineMcpTool(() =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
318
|
+
var grep_mcp_default = defineMcpTool((context) => {
|
|
319
|
+
const filesystem = context.services.get(PO_FS_SERVICE);
|
|
320
|
+
if (!filesystem) throw new Error("Grep filesystem service is unavailable.");
|
|
321
|
+
return {
|
|
322
|
+
...createHeadlessGrepTool(filesystem),
|
|
323
|
+
description: "Search file contents in the Po session's repository on the host machine (ripgrep, regex or literal, code search). Respects .gitignore. Matches include file hashes and line anchors for this server's edit tool."
|
|
324
|
+
};
|
|
325
|
+
});
|
|
295
326
|
//#endregion
|
|
296
327
|
//#region generated/mcp.ts
|
|
297
328
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|