@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.
@@ -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
- }), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
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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"}
@@ -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
- }), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
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
- }), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
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":";;;;;;;;;;;;;;;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,UAAA,GAASA,6BAAAA,iBAAAA,CAAiB,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,SAAA,GAAQC,mBAAAA,MAAAA,CAAM,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,CAAA,GAAA,4BAAA,oBAAA,CAAoB,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,YAAYC,iBAAAA;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,UAAUC,gCAAAA;CACV,YAAA,gCAAA;CACA,cAAA,gCAAA;CACA,cAAA,gCAAA;AACF;AAEA,eAAsB,cACpB,cACA,QACA,KACA,QACA,YACA,WAA+CC,6BAAAA,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,cAAA,GAAaC,6BAAAA,iBAAAA,CAAiB,OAAO,QAAQ,KAAK,GAAG;CAC3D,iBAAiB,MAAM;CACvB,MAAM,UAAU,UAAA,GAASC,4BAAAA,gBAAAA,CAAgB,QAAQC,4BAAAA,kBAAkB,IAAIA,4BAAAA;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,OAAA,GAAMC,4BAAAA,gBAAAA,CAAgB,YAAY,MAAM,cAAc,SAAiB,OAAO;EACxF,SAAS,OAAO;GACd,iBAAiB,MAAM;GACvB,IAAI,iBAAiBC,4BAAAA,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,SAAA,GAAQC,uBAAAA,WAAAA,EAAAA,GAAWC,6BAAAA,WAAAA,CAAW,OAAO,MAAM,IAAI,CAAC;EACtD,MAAM,SAAS,EAAA,GAACC,uBAAAA,iBAAAA,CAAiB,MAAM,OAAA,GAAMC,6BAAAA,eAAAA,CAAe,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,MAAA,GAAKC,uBAAAA,iBAAAA,CAAiB,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,aAAA,GAAYC,UAAAA,QAAAA,CAAQ,YAAY,IAAI,IAAI,IAAI;EACjE,IAAI,QAAQ,OAAO,IAAI,YAAY;EACnC,IAAI,CAAC,OAAO;GACV,QAAQ;IAAE,OAAA,GAAMC,6BAAAA,YAAAA,CAAY,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,UAAA,GAASV,4BAAAA,gBAAAA,CAAgB,QAAQC,4BAAAA,kBAAkB,IAAIA,4BAAAA;CACvE,MAAM,QAAA,GAAOU,4BAAAA,WAAAA,CAAW,MAAM,WAAW,SAAS,MAAM,OAAO,CAAC;CAChE,IAAI,KAAK,SAAS,WAAW,OAAO;CACpC,MAAM,UAAA,GAASA,4BAAAA,WAAAA,CAAW,MAAM,WAAW,cAAc,MAAM,OAAO,CAAC;CACvE,QAAA,GAAOA,4BAAAA,WAAAA,CAAW,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,UAAA,GAASC,UAAAA,SAAAA,CAAS,UAAU,KAAK,UAAU,SAAS,YAAY;KAC5E,KAAK,KAAK;MAAE,GAAG;MAAW,QAAQ;KAAM,CAAC;KACzC;IACF;IACA;GACF;GACA,MAAM,gBAAA,GAAeH,UAAAA,QAAAA,CAAQ,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,uBAAA,GAAeI,iCAAAA,iBAAAA,EAAkB,EAAE,cAAqC;CACtE,IAAI;CACJ,QAAQ,OAAO,CAACC,4BAAAA,aAAa,IAAI,WAAW;EAC1C,MAAM,SAAA,GAAQC,4BAAAA,YAAAA,CAAY,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,gBAAA,GAAeC,gCAAAA,mBAAAA,CAAmB;CAC7C,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAAwC;EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGC,qBAAU,OAAO,CAAC,CAAC;CAAG,EACvG;AACF,CAAC"}
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
- }), params, context.cwd, signal, typeof filesystem === "function" ? filesystem() : filesystem);
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":"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"}
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.111",
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.111",
64
- "@agimon-ai/po-hashline": "0.0.0-alpha.111",
65
- "@agimon-ai/po-ui": "0.0.0-alpha.111",
66
- "@agimon-ai/po-web-components": "0.0.0-alpha.111",
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.111",
72
- "@agimon-ai/po-native-components": "0.0.0-alpha.111",
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.111",
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"