@agimon-ai/po-grep 0.0.0-alpha.111 → 0.0.0-alpha.112
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 +35 -29
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.mjs +35 -29
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/server.cjs +34 -28
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +34 -28
- package/dist/extensions/server.mjs.map +1 -1
- package/package.json +8 -8
package/dist/extensions/mcp.cjs
CHANGED
|
@@ -7,8 +7,8 @@ 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");
|
|
10
|
-
let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
|
|
11
10
|
let typebox = require("typebox");
|
|
11
|
+
let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
|
|
12
12
|
let node_child_process = require("node:child_process");
|
|
13
13
|
//#region src/schemas/grepTool.ts
|
|
14
14
|
const GrepParamsSchema = typebox.Type.Object({
|
|
@@ -27,6 +27,36 @@ const GrepParamsSchema = typebox.Type.Object({
|
|
|
27
27
|
}))
|
|
28
28
|
});
|
|
29
29
|
//#endregion
|
|
30
|
+
//#region src/services/grepHost/index.ts
|
|
31
|
+
const defaultGrepOutput = {
|
|
32
|
+
maxBytes: _earendil_works_pi_coding_agent.DEFAULT_MAX_BYTES,
|
|
33
|
+
formatSize: _earendil_works_pi_coding_agent.formatSize,
|
|
34
|
+
truncateHead: _earendil_works_pi_coding_agent.truncateHead,
|
|
35
|
+
truncateLine: _earendil_works_pi_coding_agent.truncateLine
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/services/matchLimit/index.ts
|
|
39
|
+
/**
|
|
40
|
+
* Keeps at most `limit` matching lines, and every context line around them.
|
|
41
|
+
*
|
|
42
|
+
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
43
|
+
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
44
|
+
* asked for.
|
|
45
|
+
*/
|
|
46
|
+
function limitMatches(output, limit) {
|
|
47
|
+
if (limit === void 0) return output.trimEnd();
|
|
48
|
+
let matches = 0;
|
|
49
|
+
const selected = [];
|
|
50
|
+
for (const line of output.trimEnd().split("\n")) {
|
|
51
|
+
if (/:(\d+):/u.test(line)) {
|
|
52
|
+
matches++;
|
|
53
|
+
if (matches > limit) continue;
|
|
54
|
+
}
|
|
55
|
+
selected.push(line);
|
|
56
|
+
}
|
|
57
|
+
return selected.join("\n");
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
30
60
|
//#region src/services/ripgrep/index.ts
|
|
31
61
|
/** Search with ripgrep independently of the host. */
|
|
32
62
|
async function runRipgrep(request) {
|
|
@@ -57,26 +87,6 @@ function argumentsFor(params, cwd) {
|
|
|
57
87
|
args.push("--", params.pattern, target);
|
|
58
88
|
return args;
|
|
59
89
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Keeps at most `limit` matching lines, and every context line around them.
|
|
62
|
-
*
|
|
63
|
-
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
64
|
-
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
65
|
-
* asked for.
|
|
66
|
-
*/
|
|
67
|
-
function limitMatches(output, limit) {
|
|
68
|
-
if (limit === void 0) return output.trimEnd();
|
|
69
|
-
let matches = 0;
|
|
70
|
-
const selected = [];
|
|
71
|
-
for (const line of output.trimEnd().split("\n")) {
|
|
72
|
-
if (/:(\d+):/u.test(line)) {
|
|
73
|
-
matches++;
|
|
74
|
-
if (matches > limit) continue;
|
|
75
|
-
}
|
|
76
|
-
selected.push(line);
|
|
77
|
-
}
|
|
78
|
-
return selected.join("\n");
|
|
79
|
-
}
|
|
80
90
|
function spawnRipgrep(args, cwd, signal) {
|
|
81
91
|
return new Promise((resolveOutput, reject) => {
|
|
82
92
|
const child = (0, node_child_process.spawn)("rg", args, {
|
|
@@ -150,20 +160,16 @@ function createHeadlessGrepTool(filesystem) {
|
|
|
150
160
|
parameters: GrepParamsSchema,
|
|
151
161
|
executionMode: "parallel",
|
|
152
162
|
execute: async (_toolCallId, params, signal, _onUpdate, context) => {
|
|
163
|
+
const session = typeof filesystem === "function" ? filesystem() : filesystem;
|
|
153
164
|
return tagGrepResult(await runRipgrep({
|
|
154
165
|
params,
|
|
155
166
|
cwd: context.cwd,
|
|
156
|
-
signal
|
|
157
|
-
|
|
167
|
+
signal,
|
|
168
|
+
filesystem: session
|
|
169
|
+
}), params, context.cwd, signal, session);
|
|
158
170
|
}
|
|
159
171
|
};
|
|
160
172
|
}
|
|
161
|
-
const defaultGrepOutput = {
|
|
162
|
-
maxBytes: _earendil_works_pi_coding_agent.DEFAULT_MAX_BYTES,
|
|
163
|
-
formatSize: _earendil_works_pi_coding_agent.formatSize,
|
|
164
|
-
truncateHead: _earendil_works_pi_coding_agent.truncateHead,
|
|
165
|
-
truncateLine: _earendil_works_pi_coding_agent.truncateLine
|
|
166
|
-
};
|
|
167
173
|
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = _agimon_ai_po_hashline_files.isWritableFile, outputOperations = defaultGrepOutput) {
|
|
168
174
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
169
175
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
1
|
+
{"version":3,"file":"mcp.cjs","names":["Type","DEFAULT_MAX_BYTES","resolveInputPath","spawn","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/grepHost/index.ts","../../src/services/matchLimit/index.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 { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\n// The edge build replaces this host composition with portable output operations.\nexport const defaultGrepOutput = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\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","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 { limitMatches } from '../matchLimit';\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\nexport { limitMatches };\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';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { defaultGrepOutput } from '../grepHost';\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 session = typeof filesystem === 'function' ? filesystem() : filesystem;\n const result = await runRipgrep({ params, cwd: context.cwd, signal, filesystem: session });\n return tagGrepResult(result, params, context.cwd, signal, session);\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 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
|
@@ -3,8 +3,8 @@ import { BACKGROUND_CONTEXT, FileTooLargeError, PO_FS_SERVICE, getOrThrow, readB
|
|
|
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";
|
|
6
|
-
import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
|
|
7
6
|
import { Type } from "typebox";
|
|
7
|
+
import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
//#region src/schemas/grepTool.ts
|
|
10
10
|
const GrepParamsSchema = Type.Object({
|
|
@@ -23,6 +23,36 @@ const GrepParamsSchema = Type.Object({
|
|
|
23
23
|
}))
|
|
24
24
|
});
|
|
25
25
|
//#endregion
|
|
26
|
+
//#region src/services/grepHost/index.ts
|
|
27
|
+
const defaultGrepOutput = {
|
|
28
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
29
|
+
formatSize,
|
|
30
|
+
truncateHead,
|
|
31
|
+
truncateLine
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/services/matchLimit/index.ts
|
|
35
|
+
/**
|
|
36
|
+
* Keeps at most `limit` matching lines, and every context line around them.
|
|
37
|
+
*
|
|
38
|
+
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
39
|
+
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
40
|
+
* asked for.
|
|
41
|
+
*/
|
|
42
|
+
function limitMatches(output, limit) {
|
|
43
|
+
if (limit === void 0) return output.trimEnd();
|
|
44
|
+
let matches = 0;
|
|
45
|
+
const selected = [];
|
|
46
|
+
for (const line of output.trimEnd().split("\n")) {
|
|
47
|
+
if (/:(\d+):/u.test(line)) {
|
|
48
|
+
matches++;
|
|
49
|
+
if (matches > limit) continue;
|
|
50
|
+
}
|
|
51
|
+
selected.push(line);
|
|
52
|
+
}
|
|
53
|
+
return selected.join("\n");
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
26
56
|
//#region src/services/ripgrep/index.ts
|
|
27
57
|
/** Search with ripgrep independently of the host. */
|
|
28
58
|
async function runRipgrep(request) {
|
|
@@ -53,26 +83,6 @@ function argumentsFor(params, cwd) {
|
|
|
53
83
|
args.push("--", params.pattern, target);
|
|
54
84
|
return args;
|
|
55
85
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Keeps at most `limit` matching lines, and every context line around them.
|
|
58
|
-
*
|
|
59
|
-
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
60
|
-
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
61
|
-
* asked for.
|
|
62
|
-
*/
|
|
63
|
-
function limitMatches(output, limit) {
|
|
64
|
-
if (limit === void 0) return output.trimEnd();
|
|
65
|
-
let matches = 0;
|
|
66
|
-
const selected = [];
|
|
67
|
-
for (const line of output.trimEnd().split("\n")) {
|
|
68
|
-
if (/:(\d+):/u.test(line)) {
|
|
69
|
-
matches++;
|
|
70
|
-
if (matches > limit) continue;
|
|
71
|
-
}
|
|
72
|
-
selected.push(line);
|
|
73
|
-
}
|
|
74
|
-
return selected.join("\n");
|
|
75
|
-
}
|
|
76
86
|
function spawnRipgrep(args, cwd, signal) {
|
|
77
87
|
return new Promise((resolveOutput, reject) => {
|
|
78
88
|
const child = spawn("rg", args, {
|
|
@@ -146,20 +156,16 @@ function createHeadlessGrepTool(filesystem) {
|
|
|
146
156
|
parameters: GrepParamsSchema,
|
|
147
157
|
executionMode: "parallel",
|
|
148
158
|
execute: async (_toolCallId, params, signal, _onUpdate, context) => {
|
|
159
|
+
const session = typeof filesystem === "function" ? filesystem() : filesystem;
|
|
149
160
|
return tagGrepResult(await runRipgrep({
|
|
150
161
|
params,
|
|
151
162
|
cwd: context.cwd,
|
|
152
|
-
signal
|
|
153
|
-
|
|
163
|
+
signal,
|
|
164
|
+
filesystem: session
|
|
165
|
+
}), params, context.cwd, signal, session);
|
|
154
166
|
}
|
|
155
167
|
};
|
|
156
168
|
}
|
|
157
|
-
const defaultGrepOutput = {
|
|
158
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
159
|
-
formatSize,
|
|
160
|
-
truncateHead,
|
|
161
|
-
truncateLine
|
|
162
|
-
};
|
|
163
169
|
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = isWritableFile, outputOperations = defaultGrepOutput) {
|
|
164
170
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
165
171
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.mjs","names":["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":";;;;;;;;;AAEA,MAAa,mBAAmB,KAAK,OAAO;CAC1C,SAAS,KAAK,OAAO,EAAE,aAAa,8CAA8C,CAAC;CACnF,MAAM,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kEAAkE,CAAC,CAAC;CACnH,MAAM,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mCAAmC,CAAC,CAAC;CACpF,YAAY,KAAK,SAAS,KAAK,QAAQ,EAAE,aAAa,kCAAkC,CAAC,CAAC;CAC1F,SAAS,KAAK,SAAS,KAAK,QAAQ,EAAE,aAAa,iCAAiC,CAAC,CAAC;CACtF,SAAS,KAAK,SAAS,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAAoC,CAAC,CAAC;CACrG,OAAO,KAAK,SAAS,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAA6B,CAAC,CAAC;AAC9F,CAAC;;;;ACDD,eAAsB,WAAW,SAAiD;CAChF,MAAM,EAAE,QAAQ,KAAK,WAAW;CAChC,MAAM,EAAE,QAAQ,cAAc,MAAM,aAAa,aAAa,QAAQ,GAAG,GAAG,KAAK,MAAM;CACvF,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,OAAO,KAAK,MAAM,KAAK,qBAAqB,aAAa,QAAQ,OAAO,KAAK;EAAE,CAAC;EAChH,GAAI,YAAY,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,IAAI,CAAC;CACtD;AACF;;AAGA,SAAgB,aAAa,QAAoB,KAAuB;CACtE,MAAM,SAAS,iBAAiB,OAAO,QAAQ,KAAK,GAAG;CACvD,MAAM,OAAO;EAAC;EAAW;EAAS;EAAgB;EAAmB;CAAe;CACpF,IAAI,OAAO,eAAe,MAAM,KAAK,KAAK,eAAe;CACzD,IAAI,OAAO,YAAY,MAAM,KAAK,KAAK,iBAAiB;CACxD,IAAI,OAAO,YAAY,KAAA,GAAW,KAAK,KAAK,aAAa,OAAO,OAAO,OAAO,CAAC;CAC/E,IAAI,OAAO,SAAS,KAAA,GAAW,KAAK,KAAK,UAAU,OAAO,IAAI;CAC9D,KAAK,KAAK,MAAM,OAAO,SAAS,MAAM;CACtC,OAAO;AACT;;;;;;;;AASA,SAAgB,aAAa,QAAgB,OAAmC;CAC9E,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,QAAQ;CAC/C,IAAI,UAAU;CACd,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,GAAG;EAC/C,IAAI,WAAW,KAAK,IAAI,GAAG;GACzB;GACA,IAAI,UAAU,OAAO;EACvB;EACA,SAAS,KAAK,IAAI;CACpB;CACA,OAAO,SAAS,KAAK,IAAI;AAC3B;AAEA,SAAS,aACP,MACA,KACA,QACiD;CACjD,OAAO,IAAI,SAAS,eAAe,WAAW;EAC5C,MAAM,QAAQ,MAAM,MAAM,MAAM;GAAE;GAAK,OAAO;IAAC;IAAU;IAAQ;GAAM;EAAE,CAAC;EAC1E,MAAM,SAAmB,CAAC;EAC1B,MAAM,SAAmB,CAAC;EAC1B,IAAI,cAAc;EAClB,IAAI,cAAc;EAClB,IAAI,YAAY;EAChB,MAAM,WAAW,QAAkB,OAAe,OAAe,QAAwB;GACvF,IAAI,WAAW,OAAO;GACtB,MAAM,WAAW,KAAK,IAAI,MAAM,QAAQ,MAAM,KAAK;GACnD,IAAI,WAAW,GAAG,OAAO,KAAK,OAAO,KAAK,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC;GACtE,oBAAoB,UAAU,QAAQ,YAAY,GAAG;GACrD,SAAS;GACT,IAAI,SAAS,KAAK;IAChB,YAAY;IACZ,MAAM,KAAK,SAAS;GACtB;GACA,OAAO;EACT;EACA,MAAM,OAAO,GAAG,SAAS,UAAkB;GACzC,cAAc,QAAQ,QAAQ,OAAO,aAAa,OAAe;EACnE,CAAC;EACD,MAAM,OAAO,GAAG,SAAS,UAAkB;GACzC,cAAc,QAAQ,QAAQ,OAAO,aAAa,KAAS;EAC7D,CAAC;EACD,MAAM,cAAoB;GACxB,MAAM,KAAK,SAAS;GACpB,uBAAO,IAAI,MAAM,mBAAmB,CAAC;EACvC;EACA,IAAI,QAAQ,SAAS;GACnB,MAAM;GACN;EACF;EACA,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;EACvD,MAAM,KAAK,UAAU,UAAU;GAC7B,QAAQ,oBAAoB,SAAS,KAAK;GAC1C,OAAO,IAAI,MAAM,4BAA4B,MAAM,WAAW,EAAE,OAAO,MAAM,CAAC,CAAC;EACjF,CAAC;EACD,MAAM,KAAK,UAAU,SAAS;GAC5B,QAAQ,oBAAoB,SAAS,KAAK;GAE1C,IAAI,aAAa,SAAS,KAAK,SAAS,GAAG;IACzC,cAAc;KAAE,QAAQ,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM;KAAG;IAAU,CAAC;IACxF;GACF;GACA,OACE,IAAI,MACF,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,KAAK,4BAA4B,QAAQ,WACpG,CACF;EACF,CAAC;CACH,CAAC;AACH;;;ACpFA,MAAM,kBAAkB;AACxB,MAAM,oBAAoB;AAC1B,MAAM,aAAa;AAsCnB,SAAgB,uBACd,YACyC;CACzC,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,YAAY;EACZ,eAAe;EACf,SAAS,OACP,aACA,QACA,QACA,WACA,YACG;GAEH,OAAO,cACL,MAFmB,WAAW;IAAE;IAAQ,KAAK,QAAQ;IAAK;GAAO,CAAC,GAGlE,QACA,QAAQ,KACR,QACA,OAAO,eAAe,aAAa,WAAW,IAAI,UACpD;EACF;CACF;AACF;AAQA,MAAa,oBAA0C;CACrD,UAAU;CACV;CACA;CACA;AACF;AAEA,eAAsB,cACpB,cACA,QACA,KACA,QACA,YACA,WAA+C,gBAC/C,mBAAyC,mBACpB;CACrB,MAAM,WAAW,aAAa,QAAQ,MAAM,SAAS,KAAK,SAAS,MAAM;CACzE,IAAI,CAAC,YAAY,SAAS,SAAS,YAAY,OAAO;CAEtD,MAAM,cAAc,SAAS,KAAK,YAAY,OAAO;CACrD,MAAM,WAAW,gBAAgB,KAAK,SAAS,OAAO,SAAS,KAAK,MAAM,GAAG,WAAW;CACxF,MAAM,SAAS,gBAAgB,KAAK,KAAK,SAAS,KAAK,MAAM,WAAW;CACxE,MAAM,aAAa,iBAAiB,OAAO,QAAQ,KAAK,GAAG;CAC3D,iBAAiB,MAAM;CACvB,MAAM,UAAU,SAAS,gBAAgB,QAAQ,kBAAkB,IAAI;CACvE,MAAM,cAAc,MAAM,aAAa,YAAY,YAAY,MAAM;CACrE,iBAAiB,MAAM;CACvB,MAAM,OAAO,MAAM,uBAAuB,UAAU,YAAY,YAAY,SAAS,aAAa,QAAQ,UAAU;CACpH,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,MAAM,SAAS,cAAc,MAAM,YAAY,YAAY,SAAS,aAAa,GAAG;CACpF,MAAM,SAAmB,CAAC;CAC1B,IAAI,eAAe;CACnB,IAAI,iBAAiB,aAAa,SAAS,kBAAkB;CAE7D,KAAK,MAAM,SAAS,OAAO,OAAO,GAAG;EACnC,iBAAiB,MAAM;EACvB,MAAM,UAAU,MAAM,SAAS,MAAM,YAAY;EACjD,iBAAiB,MAAM;EACvB,IAAI,CAAC,SAAS;GACZ,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;GACvC;EACF;EACA,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,gBAAgB,YAAY,MAAM,cAAc,SAAiB,OAAO;EACxF,SAAS,OAAO;GACd,iBAAiB,MAAM;GACvB,IAAI,iBAAiB,mBAAmB;IACtC,OAAO,KAAK,GAAG,MAAM,WAAW,KAAK,IAAI,EAAE,KAAK,MAAM,KAAK,qCAAqC;IAChG;IACA;GACF;GACA,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;GACvC;EACF;EACA,iBAAiB,MAAM;EACvB,MAAM,QAAQ,WAAW,WAAW,OAAO,MAAM,IAAI,CAAC;EACtD,MAAM,SAAS,CAAC,iBAAiB,MAAM,MAAM,eAAe,KAAK,CAAC,CAAC;EACnE,KAAK,MAAM,CAAC,YAAY,YAAY,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,OAAO,KAAK,GAAG;GAC3F,MAAM,OAAO,MAAM,aAAa;GAChC,IAAI,SAAS,KAAA,GAAW;GACxB,MAAM,UAAU,iBAAiB,aAAa,IAAI;GAClD,IAAI,QAAQ,cAAc,iBAAiB;GAC3C,OAAO,KAAK,iBAAiB,QAAQ,MAAM,YAAY,UAAU,QAAQ,OAAO,IAAI,CAAC;EACvF;EACA,IAAI,OAAO,SAAS,GAAG;GACrB,OAAO,KAAK,OAAO,KAAK,IAAI,CAAC;GAC7B;EACF,OACE,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;CAE3C;CAEA,IAAI,OAAO,WAAW,KAAK,iBAAiB,GAAG,OAAO;CACtD,MAAM,aAAa,iBAAiB,aAAa,OAAO,KAAK,IAAI,GAAG,EAAE,UAAU,OAAO,iBAAiB,CAAC;CACzG,MAAM,cAAc,WAAW,YAC3B,QAAQ,iBAAiB,WAAW,iBAAiB,QAAQ,EAAE,iCAC/D;CACJ,MAAM,UAAU;EACd,GAAG,aAAa;EAChB,GAAI,WAAW,YAAY,EAAE,WAAW,IAAI,CAAC;EAC7C,GAAI,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD;CACA,iBAAiB,MAAM;CACvB,OAAO;EACL,GAAG;EACH,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,GAAG,WAAW,UAAU,SAAS;EAAc,CAAC;EAChF,SAAS,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,UAAU,KAAA;CACvD;AACF;AAEA,SAAgB,cACd,MACA,YACA,WACA,KACwB;CACxB,MAAM,yBAAS,IAAI,IAAuB;CAC1C,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,eAAe,YAAY,QAAQ,YAAY,IAAI,IAAI,IAAI;EACjE,IAAI,QAAQ,OAAO,IAAI,YAAY;EACnC,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE,MAAM,YAAY,cAAc,GAAG;IAAG;IAAc,YAAY,CAAC;IAAG,sBAAM,IAAI,IAAI;GAAE;GAC9F,OAAO,IAAI,cAAc,KAAK;EAChC;EACA,IAAI,IAAI,WAAW,KAAA,GAAW,MAAM,WAAW,KAAK,IAAI,MAAM;EAC9D,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,SAAS,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,IAAI;CACzE;CACA,OAAO;AACT;AAGA,eAAe,aAAa,YAAwB,MAAc,QAAiC;CACjG,MAAM,UAAU,SAAS,gBAAgB,QAAQ,kBAAkB,IAAI;CACvE,MAAM,OAAO,WAAW,MAAM,WAAW,SAAS,MAAM,OAAO,CAAC;CAChE,IAAI,KAAK,SAAS,WAAW,OAAO;CACpC,MAAM,SAAS,WAAW,MAAM,WAAW,cAAc,MAAM,OAAO,CAAC;CACvE,OAAO,WAAW,MAAM,WAAW,SAAS,QAAQ,OAAO,CAAC;AAC9D;AAEA,eAAe,uBACb,UACA,YACA,WACA,QACA,YACoB;CACpB,MAAM,4BAAY,IAAI,IAAqB;CAC3C,MAAM,OAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,SAAS,MAAM,IAAI,GAAG;EACxC,iBAAiB,MAAM;EACvB,IAAI;EACJ,KAAK,MAAM,aAAa,kBAAkB,KAAK,GAAG;GAChD,IAAI,CAAC,WAAW;IACd,IAAI,UAAU,SAAS,SAAS,UAAU,KAAK,UAAU,SAAS,YAAY;KAC5E,KAAK,KAAK;MAAE,GAAG;MAAW,QAAQ;KAAM,CAAC;KACzC;IACF;IACA;GACF;GACA,MAAM,eAAe,QAAQ,YAAY,UAAU,IAAI;GACvD,IAAI,SAAS,UAAU,IAAI,YAAY;GACvC,IAAI,WAAW,KAAA,GAAW;IACxB,SAAS,MAAM,aAAa,YAAY,cAAc,MAAM,CAAC,CAC1D,MAAM,UAAU,MAAM,SAAS,MAAM,CAAC,CACtC,YAAY,KAAK;IACpB,iBAAiB,MAAM;IACvB,UAAU,IAAI,cAAc,MAAM;GACpC;GACA,IAAI,QAAQ,oBAAoB;IAAE,GAAG;IAAW,QAAQ;GAAM;EAChE;EACA,IAAI,mBAAmB,KAAK,KAAK,iBAAiB;CACpD;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,OAA0B;CACnD,MAAM,aAAuE,CAAC;CAC9E,KAAK,MAAM,SAAS,MAAM,SAAS,eAAe,GAChD,WAAW,KAAK;EACd,OAAO,MAAM;EACb,KAAK;GAAE,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK;GAAG,MAAM,OAAO,SAAS,MAAM,MAAM,IAAI,EAAE;GAAG,OAAO;EAAK;CACnG,CAAC;CAEH,KAAK,MAAM,SAAS,MAAM,SAAS,iBAAiB,GAClD,WAAW,KAAK;EACd,OAAO,MAAM;EACb,KAAK;GAAE,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK;GAAG,MAAM,OAAO,SAAS,MAAM,MAAM,IAAI,EAAE;GAAG,OAAO;EAAM;CACpG,CAAC;CAEH,OAAO,WAAW,MAAM,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG;AACxF;AAEA,SAAgB,iBAAiB,QAAuC;CACtE,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;AAC1D;;;AC/QA,IAAA,mBAAe,eAAe,YAAgC;CAC5D,MAAM,aAAa,QAAQ,SAAS,IAAgB,aAAa;CACjE,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,yCAAyC;CAC1E,OAAO;EACL,GAAG,uBAAuB,UAAU;EACpC,aACE;CACJ;AACF,CAAC;;;ACPD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AACpE,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAM,cAAiB,KAAa,UAAgB,UAAU,KAAA,IAAY,QAAS;CACjF,GAAG;CAAO,OAAO;EAAE,GAAI,MAA6B;GAAQ,cAAc;CAAI;AAChF;AAEA,MAAa,MAAM,gBAAgB;CACjC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAAqC;EAAE,OAAO,CAAC,WAAW,2BAA2B,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGA,kBAAU,OAAO,CAAC,CAAC,CAAC;CAAG,EAC3I;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"mcp.mjs","names":["toolGrep"],"sources":["../../src/schemas/grepTool.ts","../../src/services/grepHost/index.ts","../../src/services/matchLimit/index.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 { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\n// The edge build replaces this host composition with portable output operations.\nexport const defaultGrepOutput = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\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","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 { limitMatches } from '../matchLimit';\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\nexport { limitMatches };\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';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { defaultGrepOutput } from '../grepHost';\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 session = typeof filesystem === 'function' ? filesystem() : filesystem;\n const result = await runRipgrep({ params, cwd: context.cwd, signal, filesystem: session });\n return tagGrepResult(result, params, context.cwd, signal, session);\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 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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"}
|
|
@@ -11,6 +11,36 @@ let _agimon_ai_po_hashline = require("@agimon-ai/po-hashline");
|
|
|
11
11
|
let _agimon_ai_po_hashline_files = require("@agimon-ai/po-hashline/files");
|
|
12
12
|
let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
|
|
13
13
|
let node_child_process = require("node:child_process");
|
|
14
|
+
//#region src/services/grepHost/index.ts
|
|
15
|
+
const defaultGrepOutput = {
|
|
16
|
+
maxBytes: _earendil_works_pi_coding_agent.DEFAULT_MAX_BYTES,
|
|
17
|
+
formatSize: _earendil_works_pi_coding_agent.formatSize,
|
|
18
|
+
truncateHead: _earendil_works_pi_coding_agent.truncateHead,
|
|
19
|
+
truncateLine: _earendil_works_pi_coding_agent.truncateLine
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/services/matchLimit/index.ts
|
|
23
|
+
/**
|
|
24
|
+
* Keeps at most `limit` matching lines, and every context line around them.
|
|
25
|
+
*
|
|
26
|
+
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
27
|
+
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
28
|
+
* asked for.
|
|
29
|
+
*/
|
|
30
|
+
function limitMatches(output, limit) {
|
|
31
|
+
if (limit === void 0) return output.trimEnd();
|
|
32
|
+
let matches = 0;
|
|
33
|
+
const selected = [];
|
|
34
|
+
for (const line of output.trimEnd().split("\n")) {
|
|
35
|
+
if (/:(\d+):/u.test(line)) {
|
|
36
|
+
matches++;
|
|
37
|
+
if (matches > limit) continue;
|
|
38
|
+
}
|
|
39
|
+
selected.push(line);
|
|
40
|
+
}
|
|
41
|
+
return selected.join("\n");
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
14
44
|
//#region src/services/ripgrep/index.ts
|
|
15
45
|
/** Search with ripgrep independently of the host. */
|
|
16
46
|
async function runRipgrep(request) {
|
|
@@ -41,26 +71,6 @@ function argumentsFor(params, cwd) {
|
|
|
41
71
|
args.push("--", params.pattern, target);
|
|
42
72
|
return args;
|
|
43
73
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Keeps at most `limit` matching lines, and every context line around them.
|
|
46
|
-
*
|
|
47
|
-
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
48
|
-
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
49
|
-
* asked for.
|
|
50
|
-
*/
|
|
51
|
-
function limitMatches(output, limit) {
|
|
52
|
-
if (limit === void 0) return output.trimEnd();
|
|
53
|
-
let matches = 0;
|
|
54
|
-
const selected = [];
|
|
55
|
-
for (const line of output.trimEnd().split("\n")) {
|
|
56
|
-
if (/:(\d+):/u.test(line)) {
|
|
57
|
-
matches++;
|
|
58
|
-
if (matches > limit) continue;
|
|
59
|
-
}
|
|
60
|
-
selected.push(line);
|
|
61
|
-
}
|
|
62
|
-
return selected.join("\n");
|
|
63
|
-
}
|
|
64
74
|
function spawnRipgrep(args, cwd, signal) {
|
|
65
75
|
return new Promise((resolveOutput, reject) => {
|
|
66
76
|
const child = (0, node_child_process.spawn)("rg", args, {
|
|
@@ -134,20 +144,16 @@ function createHeadlessGrepTool(filesystem) {
|
|
|
134
144
|
parameters: require_grepTool.GrepParamsSchema,
|
|
135
145
|
executionMode: "parallel",
|
|
136
146
|
execute: async (_toolCallId, params, signal, _onUpdate, context) => {
|
|
147
|
+
const session = typeof filesystem === "function" ? filesystem() : filesystem;
|
|
137
148
|
return tagGrepResult(await runRipgrep({
|
|
138
149
|
params,
|
|
139
150
|
cwd: context.cwd,
|
|
140
|
-
signal
|
|
141
|
-
|
|
151
|
+
signal,
|
|
152
|
+
filesystem: session
|
|
153
|
+
}), params, context.cwd, signal, session);
|
|
142
154
|
}
|
|
143
155
|
};
|
|
144
156
|
}
|
|
145
|
-
const defaultGrepOutput = {
|
|
146
|
-
maxBytes: _earendil_works_pi_coding_agent.DEFAULT_MAX_BYTES,
|
|
147
|
-
formatSize: _earendil_works_pi_coding_agent.formatSize,
|
|
148
|
-
truncateHead: _earendil_works_pi_coding_agent.truncateHead,
|
|
149
|
-
truncateLine: _earendil_works_pi_coding_agent.truncateLine
|
|
150
|
-
};
|
|
151
157
|
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = _agimon_ai_po_hashline_files.isWritableFile, outputOperations = defaultGrepOutput) {
|
|
152
158
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
153
159
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":["resolveInputPath","spawn","GrepParamsSchema","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","withAbortSignal","BACKGROUND_CONTEXT","readBoundedFile","FileTooLargeError","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","getOrThrow","basename","defineServerTool","PO_FS_SERVICE","requirePoFs","defineServerPlugin","toolGrep"],"sources":["../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["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 { defineServerTool } from '@agimon-ai/po-core/extensionFile';\nimport { PO_FS_SERVICE, requirePoFs, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(({ context }: PoServerPluginContext) => {\n let filesystem: FileSystem | undefined;\n context.inject([PO_FS_SERVICE], (scoped) => {\n const bound = requirePoFs(scoped);\n filesystem = bound;\n return () => {\n if (filesystem === bound) filesystem = undefined;\n };\n });\n return createHeadlessGrepTool(() => {\n if (!filesystem) throw new Error('Grep filesystem service is unavailable.');\n return filesystem;\n });\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.server';\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\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["DEFAULT_MAX_BYTES","resolveInputPath","spawn","GrepParamsSchema","isWritableFile","resolveInputPath","withAbortSignal","BACKGROUND_CONTEXT","readBoundedFile","FileTooLargeError","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","getOrThrow","basename","defineServerTool","PO_FS_SERVICE","requirePoFs","defineServerPlugin","toolGrep"],"sources":["../../src/services/grepHost/index.ts","../../src/services/matchLimit/index.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\n// The edge build replaces this host composition with portable output operations.\nexport const defaultGrepOutput = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\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","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 { limitMatches } from '../matchLimit';\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\nexport { limitMatches };\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';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { defaultGrepOutput } from '../grepHost';\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 session = typeof filesystem === 'function' ? filesystem() : filesystem;\n const result = await runRipgrep({ params, cwd: context.cwd, signal, filesystem: session });\n return tagGrepResult(result, params, context.cwd, signal, session);\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 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 { defineServerTool } from '@agimon-ai/po-core/extensionFile';\nimport { PO_FS_SERVICE, requirePoFs, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(({ context }: PoServerPluginContext) => {\n let filesystem: FileSystem | undefined;\n context.inject([PO_FS_SERVICE], (scoped) => {\n const bound = requirePoFs(scoped);\n filesystem = bound;\n return () => {\n if (filesystem === bound) filesystem = undefined;\n };\n });\n return createHeadlessGrepTool(() => {\n if (!filesystem) throw new Error('Grep filesystem service is unavailable.');\n return filesystem;\n });\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.server';\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\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
|
|
@@ -7,6 +7,36 @@ import { formatFileHeader, formatTaggedLine, splitLines } from "@agimon-ai/po-ha
|
|
|
7
7
|
import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath } from "@agimon-ai/po-hashline/files";
|
|
8
8
|
import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
|
+
//#region src/services/grepHost/index.ts
|
|
11
|
+
const defaultGrepOutput = {
|
|
12
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
13
|
+
formatSize,
|
|
14
|
+
truncateHead,
|
|
15
|
+
truncateLine
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/services/matchLimit/index.ts
|
|
19
|
+
/**
|
|
20
|
+
* Keeps at most `limit` matching lines, and every context line around them.
|
|
21
|
+
*
|
|
22
|
+
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
23
|
+
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
24
|
+
* asked for.
|
|
25
|
+
*/
|
|
26
|
+
function limitMatches(output, limit) {
|
|
27
|
+
if (limit === void 0) return output.trimEnd();
|
|
28
|
+
let matches = 0;
|
|
29
|
+
const selected = [];
|
|
30
|
+
for (const line of output.trimEnd().split("\n")) {
|
|
31
|
+
if (/:(\d+):/u.test(line)) {
|
|
32
|
+
matches++;
|
|
33
|
+
if (matches > limit) continue;
|
|
34
|
+
}
|
|
35
|
+
selected.push(line);
|
|
36
|
+
}
|
|
37
|
+
return selected.join("\n");
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
10
40
|
//#region src/services/ripgrep/index.ts
|
|
11
41
|
/** Search with ripgrep independently of the host. */
|
|
12
42
|
async function runRipgrep(request) {
|
|
@@ -37,26 +67,6 @@ function argumentsFor(params, cwd) {
|
|
|
37
67
|
args.push("--", params.pattern, target);
|
|
38
68
|
return args;
|
|
39
69
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Keeps at most `limit` matching lines, and every context line around them.
|
|
42
|
-
*
|
|
43
|
-
* A context line carries no `:<line>:` marker, which is what separates it from
|
|
44
|
-
* a match, so trimming by line count alone would drop the context ripgrep was
|
|
45
|
-
* asked for.
|
|
46
|
-
*/
|
|
47
|
-
function limitMatches(output, limit) {
|
|
48
|
-
if (limit === void 0) return output.trimEnd();
|
|
49
|
-
let matches = 0;
|
|
50
|
-
const selected = [];
|
|
51
|
-
for (const line of output.trimEnd().split("\n")) {
|
|
52
|
-
if (/:(\d+):/u.test(line)) {
|
|
53
|
-
matches++;
|
|
54
|
-
if (matches > limit) continue;
|
|
55
|
-
}
|
|
56
|
-
selected.push(line);
|
|
57
|
-
}
|
|
58
|
-
return selected.join("\n");
|
|
59
|
-
}
|
|
60
70
|
function spawnRipgrep(args, cwd, signal) {
|
|
61
71
|
return new Promise((resolveOutput, reject) => {
|
|
62
72
|
const child = spawn("rg", args, {
|
|
@@ -130,20 +140,16 @@ function createHeadlessGrepTool(filesystem) {
|
|
|
130
140
|
parameters: GrepParamsSchema,
|
|
131
141
|
executionMode: "parallel",
|
|
132
142
|
execute: async (_toolCallId, params, signal, _onUpdate, context) => {
|
|
143
|
+
const session = typeof filesystem === "function" ? filesystem() : filesystem;
|
|
133
144
|
return tagGrepResult(await runRipgrep({
|
|
134
145
|
params,
|
|
135
146
|
cwd: context.cwd,
|
|
136
|
-
signal
|
|
137
|
-
|
|
147
|
+
signal,
|
|
148
|
+
filesystem: session
|
|
149
|
+
}), params, context.cwd, signal, session);
|
|
138
150
|
}
|
|
139
151
|
};
|
|
140
152
|
}
|
|
141
|
-
const defaultGrepOutput = {
|
|
142
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
143
|
-
formatSize,
|
|
144
|
-
truncateHead,
|
|
145
|
-
truncateLine
|
|
146
|
-
};
|
|
147
153
|
async function tagGrepResult(nativeResult, params, cwd, signal, filesystem, writable = isWritableFile, outputOperations = defaultGrepOutput) {
|
|
148
154
|
const textPart = nativeResult.content.find((part) => part.type === "text");
|
|
149
155
|
if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":["toolGrep"],"sources":["../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["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 { defineServerTool } from '@agimon-ai/po-core/extensionFile';\nimport { PO_FS_SERVICE, requirePoFs, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(({ context }: PoServerPluginContext) => {\n let filesystem: FileSystem | undefined;\n context.inject([PO_FS_SERVICE], (scoped) => {\n const bound = requirePoFs(scoped);\n filesystem = bound;\n return () => {\n if (filesystem === bound) filesystem = undefined;\n };\n });\n return createHeadlessGrepTool(() => {\n if (!filesystem) throw new Error('Grep filesystem service is unavailable.');\n return filesystem;\n });\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.server';\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\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default serverPlugin;\n"],"mappings":";;;;;;;;;;;AASA,eAAsB,WAAW,SAAiD;CAChF,MAAM,EAAE,QAAQ,KAAK,WAAW;CAChC,MAAM,EAAE,QAAQ,cAAc,MAAM,aAAa,aAAa,QAAQ,GAAG,GAAG,KAAK,MAAM;CACvF,OAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,OAAO,KAAK,MAAM,KAAK,qBAAqB,aAAa,QAAQ,OAAO,KAAK;EAAE,CAAC;EAChH,GAAI,YAAY,EAAE,SAAS,EAAE,WAAW,KAAK,EAAE,IAAI,CAAC;CACtD;AACF;;AAGA,SAAgB,aAAa,QAAoB,KAAuB;CACtE,MAAM,SAAS,iBAAiB,OAAO,QAAQ,KAAK,GAAG;CACvD,MAAM,OAAO;EAAC;EAAW;EAAS;EAAgB;EAAmB;CAAe;CACpF,IAAI,OAAO,eAAe,MAAM,KAAK,KAAK,eAAe;CACzD,IAAI,OAAO,YAAY,MAAM,KAAK,KAAK,iBAAiB;CACxD,IAAI,OAAO,YAAY,KAAA,GAAW,KAAK,KAAK,aAAa,OAAO,OAAO,OAAO,CAAC;CAC/E,IAAI,OAAO,SAAS,KAAA,GAAW,KAAK,KAAK,UAAU,OAAO,IAAI;CAC9D,KAAK,KAAK,MAAM,OAAO,SAAS,MAAM;CACtC,OAAO;AACT;;;;;;;;AASA,SAAgB,aAAa,QAAgB,OAAmC;CAC9E,IAAI,UAAU,KAAA,GAAW,OAAO,OAAO,QAAQ;CAC/C,IAAI,UAAU;CACd,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,QAAQ,OAAO,QAAQ,CAAC,CAAC,MAAM,IAAI,GAAG;EAC/C,IAAI,WAAW,KAAK,IAAI,GAAG;GACzB;GACA,IAAI,UAAU,OAAO;EACvB;EACA,SAAS,KAAK,IAAI;CACpB;CACA,OAAO,SAAS,KAAK,IAAI;AAC3B;AAEA,SAAS,aACP,MACA,KACA,QACiD;CACjD,OAAO,IAAI,SAAS,eAAe,WAAW;EAC5C,MAAM,QAAQ,MAAM,MAAM,MAAM;GAAE;GAAK,OAAO;IAAC;IAAU;IAAQ;GAAM;EAAE,CAAC;EAC1E,MAAM,SAAmB,CAAC;EAC1B,MAAM,SAAmB,CAAC;EAC1B,IAAI,cAAc;EAClB,IAAI,cAAc;EAClB,IAAI,YAAY;EAChB,MAAM,WAAW,QAAkB,OAAe,OAAe,QAAwB;GACvF,IAAI,WAAW,OAAO;GACtB,MAAM,WAAW,KAAK,IAAI,MAAM,QAAQ,MAAM,KAAK;GACnD,IAAI,WAAW,GAAG,OAAO,KAAK,OAAO,KAAK,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC;GACtE,oBAAoB,UAAU,QAAQ,YAAY,GAAG;GACrD,SAAS;GACT,IAAI,SAAS,KAAK;IAChB,YAAY;IACZ,MAAM,KAAK,SAAS;GACtB;GACA,OAAO;EACT;EACA,MAAM,OAAO,GAAG,SAAS,UAAkB;GACzC,cAAc,QAAQ,QAAQ,OAAO,aAAa,OAAe;EACnE,CAAC;EACD,MAAM,OAAO,GAAG,SAAS,UAAkB;GACzC,cAAc,QAAQ,QAAQ,OAAO,aAAa,KAAS;EAC7D,CAAC;EACD,MAAM,cAAoB;GACxB,MAAM,KAAK,SAAS;GACpB,uBAAO,IAAI,MAAM,mBAAmB,CAAC;EACvC;EACA,IAAI,QAAQ,SAAS;GACnB,MAAM;GACN;EACF;EACA,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;EACvD,MAAM,KAAK,UAAU,UAAU;GAC7B,QAAQ,oBAAoB,SAAS,KAAK;GAC1C,OAAO,IAAI,MAAM,4BAA4B,MAAM,WAAW,EAAE,OAAO,MAAM,CAAC,CAAC;EACjF,CAAC;EACD,MAAM,KAAK,UAAU,SAAS;GAC5B,QAAQ,oBAAoB,SAAS,KAAK;GAE1C,IAAI,aAAa,SAAS,KAAK,SAAS,GAAG;IACzC,cAAc;KAAE,QAAQ,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM;KAAG;IAAU,CAAC;IACxF;GACF;GACA,OACE,IAAI,MACF,OAAO,OAAO,QAAQ,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,KAAK,KAAK,4BAA4B,QAAQ,WACpG,CACF;EACF,CAAC;CACH,CAAC;AACH;;;ACpFA,MAAM,kBAAkB;AACxB,MAAM,oBAAoB;AAC1B,MAAM,aAAa;AAsCnB,SAAgB,uBACd,YACyC;CACzC,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,YAAY;EACZ,eAAe;EACf,SAAS,OACP,aACA,QACA,QACA,WACA,YACG;GAEH,OAAO,cACL,MAFmB,WAAW;IAAE;IAAQ,KAAK,QAAQ;IAAK;GAAO,CAAC,GAGlE,QACA,QAAQ,KACR,QACA,OAAO,eAAe,aAAa,WAAW,IAAI,UACpD;EACF;CACF;AACF;AAQA,MAAa,oBAA0C;CACrD,UAAU;CACV;CACA;CACA;AACF;AAEA,eAAsB,cACpB,cACA,QACA,KACA,QACA,YACA,WAA+C,gBAC/C,mBAAyC,mBACpB;CACrB,MAAM,WAAW,aAAa,QAAQ,MAAM,SAAS,KAAK,SAAS,MAAM;CACzE,IAAI,CAAC,YAAY,SAAS,SAAS,YAAY,OAAO;CAEtD,MAAM,cAAc,SAAS,KAAK,YAAY,OAAO;CACrD,MAAM,WAAW,gBAAgB,KAAK,SAAS,OAAO,SAAS,KAAK,MAAM,GAAG,WAAW;CACxF,MAAM,SAAS,gBAAgB,KAAK,KAAK,SAAS,KAAK,MAAM,WAAW;CACxE,MAAM,aAAa,iBAAiB,OAAO,QAAQ,KAAK,GAAG;CAC3D,iBAAiB,MAAM;CACvB,MAAM,UAAU,SAAS,gBAAgB,QAAQ,kBAAkB,IAAI;CACvE,MAAM,cAAc,MAAM,aAAa,YAAY,YAAY,MAAM;CACrE,iBAAiB,MAAM;CACvB,MAAM,OAAO,MAAM,uBAAuB,UAAU,YAAY,YAAY,SAAS,aAAa,QAAQ,UAAU;CACpH,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,MAAM,SAAS,cAAc,MAAM,YAAY,YAAY,SAAS,aAAa,GAAG;CACpF,MAAM,SAAmB,CAAC;CAC1B,IAAI,eAAe;CACnB,IAAI,iBAAiB,aAAa,SAAS,kBAAkB;CAE7D,KAAK,MAAM,SAAS,OAAO,OAAO,GAAG;EACnC,iBAAiB,MAAM;EACvB,MAAM,UAAU,MAAM,SAAS,MAAM,YAAY;EACjD,iBAAiB,MAAM;EACvB,IAAI,CAAC,SAAS;GACZ,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;GACvC;EACF;EACA,IAAI;EACJ,IAAI;GACF,QAAQ,MAAM,gBAAgB,YAAY,MAAM,cAAc,SAAiB,OAAO;EACxF,SAAS,OAAO;GACd,iBAAiB,MAAM;GACvB,IAAI,iBAAiB,mBAAmB;IACtC,OAAO,KAAK,GAAG,MAAM,WAAW,KAAK,IAAI,EAAE,KAAK,MAAM,KAAK,qCAAqC;IAChG;IACA;GACF;GACA,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;GACvC;EACF;EACA,iBAAiB,MAAM;EACvB,MAAM,QAAQ,WAAW,WAAW,OAAO,MAAM,IAAI,CAAC;EACtD,MAAM,SAAS,CAAC,iBAAiB,MAAM,MAAM,eAAe,KAAK,CAAC,CAAC;EACnE,KAAK,MAAM,CAAC,YAAY,YAAY,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,OAAO,KAAK,GAAG;GAC3F,MAAM,OAAO,MAAM,aAAa;GAChC,IAAI,SAAS,KAAA,GAAW;GACxB,MAAM,UAAU,iBAAiB,aAAa,IAAI;GAClD,IAAI,QAAQ,cAAc,iBAAiB;GAC3C,OAAO,KAAK,iBAAiB,QAAQ,MAAM,YAAY,UAAU,QAAQ,OAAO,IAAI,CAAC;EACvF;EACA,IAAI,OAAO,SAAS,GAAG;GACrB,OAAO,KAAK,OAAO,KAAK,IAAI,CAAC;GAC7B;EACF,OACE,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI,CAAC;CAE3C;CAEA,IAAI,OAAO,WAAW,KAAK,iBAAiB,GAAG,OAAO;CACtD,MAAM,aAAa,iBAAiB,aAAa,OAAO,KAAK,IAAI,GAAG,EAAE,UAAU,OAAO,iBAAiB,CAAC;CACzG,MAAM,cAAc,WAAW,YAC3B,QAAQ,iBAAiB,WAAW,iBAAiB,QAAQ,EAAE,iCAC/D;CACJ,MAAM,UAAU;EACd,GAAG,aAAa;EAChB,GAAI,WAAW,YAAY,EAAE,WAAW,IAAI,CAAC;EAC7C,GAAI,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACnD;CACA,iBAAiB,MAAM;CACvB,OAAO;EACL,GAAG;EACH,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,GAAG,WAAW,UAAU,SAAS;EAAc,CAAC;EAChF,SAAS,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,UAAU,KAAA;CACvD;AACF;AAEA,SAAgB,cACd,MACA,YACA,WACA,KACwB;CACxB,MAAM,yBAAS,IAAI,IAAuB;CAC1C,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,eAAe,YAAY,QAAQ,YAAY,IAAI,IAAI,IAAI;EACjE,IAAI,QAAQ,OAAO,IAAI,YAAY;EACnC,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE,MAAM,YAAY,cAAc,GAAG;IAAG;IAAc,YAAY,CAAC;IAAG,sBAAM,IAAI,IAAI;GAAE;GAC9F,OAAO,IAAI,cAAc,KAAK;EAChC;EACA,IAAI,IAAI,WAAW,KAAA,GAAW,MAAM,WAAW,KAAK,IAAI,MAAM;EAC9D,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,SAAS,MAAM,KAAK,IAAI,IAAI,IAAI,MAAM,IAAI;CACzE;CACA,OAAO;AACT;AAGA,eAAe,aAAa,YAAwB,MAAc,QAAiC;CACjG,MAAM,UAAU,SAAS,gBAAgB,QAAQ,kBAAkB,IAAI;CACvE,MAAM,OAAO,WAAW,MAAM,WAAW,SAAS,MAAM,OAAO,CAAC;CAChE,IAAI,KAAK,SAAS,WAAW,OAAO;CACpC,MAAM,SAAS,WAAW,MAAM,WAAW,cAAc,MAAM,OAAO,CAAC;CACvE,OAAO,WAAW,MAAM,WAAW,SAAS,QAAQ,OAAO,CAAC;AAC9D;AAEA,eAAe,uBACb,UACA,YACA,WACA,QACA,YACoB;CACpB,MAAM,4BAAY,IAAI,IAAqB;CAC3C,MAAM,OAAkB,CAAC;CACzB,KAAK,MAAM,SAAS,SAAS,MAAM,IAAI,GAAG;EACxC,iBAAiB,MAAM;EACvB,IAAI;EACJ,KAAK,MAAM,aAAa,kBAAkB,KAAK,GAAG;GAChD,IAAI,CAAC,WAAW;IACd,IAAI,UAAU,SAAS,SAAS,UAAU,KAAK,UAAU,SAAS,YAAY;KAC5E,KAAK,KAAK;MAAE,GAAG;MAAW,QAAQ;KAAM,CAAC;KACzC;IACF;IACA;GACF;GACA,MAAM,eAAe,QAAQ,YAAY,UAAU,IAAI;GACvD,IAAI,SAAS,UAAU,IAAI,YAAY;GACvC,IAAI,WAAW,KAAA,GAAW;IACxB,SAAS,MAAM,aAAa,YAAY,cAAc,MAAM,CAAC,CAC1D,MAAM,UAAU,MAAM,SAAS,MAAM,CAAC,CACtC,YAAY,KAAK;IACpB,iBAAiB,MAAM;IACvB,UAAU,IAAI,cAAc,MAAM;GACpC;GACA,IAAI,QAAQ,oBAAoB;IAAE,GAAG;IAAW,QAAQ;GAAM;EAChE;EACA,IAAI,mBAAmB,KAAK,KAAK,iBAAiB;CACpD;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,OAA0B;CACnD,MAAM,aAAuE,CAAC;CAC9E,KAAK,MAAM,SAAS,MAAM,SAAS,eAAe,GAChD,WAAW,KAAK;EACd,OAAO,MAAM;EACb,KAAK;GAAE,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK;GAAG,MAAM,OAAO,SAAS,MAAM,MAAM,IAAI,EAAE;GAAG,OAAO;EAAK;CACnG,CAAC;CAEH,KAAK,MAAM,SAAS,MAAM,SAAS,iBAAiB,GAClD,WAAW,KAAK;EACd,OAAO,MAAM;EACb,KAAK;GAAE,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK;GAAG,MAAM,OAAO,SAAS,MAAM,MAAM,IAAI,EAAE;GAAG,OAAO;EAAM;CACpG,CAAC;CAEH,OAAO,WAAW,MAAM,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG;AACxF;AAEA,SAAgB,iBAAiB,QAAuC;CACtE,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;AAC1D;;;AC9QA,IAAA,sBAAe,kBAAkB,EAAE,cAAqC;CACtE,IAAI;CACJ,QAAQ,OAAO,CAAC,aAAa,IAAI,WAAW;EAC1C,MAAM,QAAQ,YAAY,MAAM;EAChC,aAAa;EACb,aAAa;GACX,IAAI,eAAe,OAAO,aAAa,KAAA;EACzC;CACF,CAAC;CACD,OAAO,6BAA6B;EAClC,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,yCAAyC;EAC1E,OAAO;CACT,CAAC;AACH,CAAC;;;ACbD,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AACpE,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAa,eAAe,mBAAmB;CAC7C,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAAwC;EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGA,qBAAU,OAAO,CAAC,CAAC;CAAG,EACvG;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["toolGrep"],"sources":["../../src/services/grepHost/index.ts","../../src/services/matchLimit/index.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\n// The edge build replaces this host composition with portable output operations.\nexport const defaultGrepOutput = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\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","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 { limitMatches } from '../matchLimit';\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\nexport { limitMatches };\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';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { defaultGrepOutput } from '../grepHost';\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 session = typeof filesystem === 'function' ? filesystem() : filesystem;\n const result = await runRipgrep({ params, cwd: context.cwd, signal, filesystem: session });\n return tagGrepResult(result, params, context.cwd, signal, session);\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 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 { defineServerTool } from '@agimon-ai/po-core/extensionFile';\nimport { PO_FS_SERVICE, requirePoFs, type FileSystem } from '@agimon-ai/po-core/platform';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(({ context }: PoServerPluginContext) => {\n let filesystem: FileSystem | undefined;\n context.inject([PO_FS_SERVICE], (scoped) => {\n const bound = requirePoFs(scoped);\n filesystem = bound;\n return () => {\n if (filesystem === bound) filesystem = undefined;\n };\n });\n return createHeadlessGrepTool(() => {\n if (!filesystem) throw new Error('Grep filesystem service is unavailable.');\n return filesystem;\n });\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.server';\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\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-grep',\n session: (context) => ({\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/po-grep",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.112",
|
|
4
4
|
"description": "Snapshot-bound hashline grep for Pi and Po.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@agimon-ai/po-core": "0.0.0-alpha.
|
|
64
|
-
"@agimon-ai/po-hashline": "0.0.0-alpha.
|
|
65
|
-
"@agimon-ai/po-ui": "0.0.0-alpha.
|
|
66
|
-
"@agimon-ai/po-web-components": "0.0.0-alpha.
|
|
63
|
+
"@agimon-ai/po-core": "0.0.0-alpha.112",
|
|
64
|
+
"@agimon-ai/po-hashline": "0.0.0-alpha.112",
|
|
65
|
+
"@agimon-ai/po-ui": "0.0.0-alpha.112",
|
|
66
|
+
"@agimon-ai/po-web-components": "0.0.0-alpha.112",
|
|
67
67
|
"@deepseek-ai/cordis": "4.0.5",
|
|
68
68
|
"typebox": "1.4.2"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@agimon-ai/po-build": "0.0.0-alpha.
|
|
72
|
-
"@agimon-ai/po-native-components": "0.0.0-alpha.
|
|
71
|
+
"@agimon-ai/po-build": "0.0.0-alpha.112",
|
|
72
|
+
"@agimon-ai/po-native-components": "0.0.0-alpha.112",
|
|
73
73
|
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
74
74
|
"@earendil-works/pi-durable": "1.5.0",
|
|
75
75
|
"@tanstack/react-store": "0.11.2",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"vitest": "5.0.4"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@agimon-ai/po-native-components": "0.0.0-alpha.
|
|
90
|
+
"@agimon-ai/po-native-components": "0.0.0-alpha.112",
|
|
91
91
|
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
92
92
|
"react-native": "0.88.1",
|
|
93
93
|
"react-native-unistyles": "^3.5.3"
|