@agimon-ai/doompi-grep 0.0.1-alpha.59 → 0.0.1-alpha.60

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.
@@ -0,0 +1,24 @@
1
+ import { defineMcpWidget } from "@agimon-ai/doompi-core/web";
2
+ import { McpToolFields, McpToolFrame, McpToolOutput } from "@agimon-ai/doompi-web-components";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/grep.mcp.tsx
5
+ function GrepMcpWidget(props) {
6
+ return /* @__PURE__ */ jsxs(McpToolFrame, {
7
+ ...props,
8
+ title: "Search file contents",
9
+ children: [/* @__PURE__ */ jsx(McpToolFields, { fields: [
10
+ ["Pattern", props.args.pattern],
11
+ ["Path", props.args.path],
12
+ ["Glob", props.args.glob],
13
+ ["Ignore case", props.args.ignoreCase],
14
+ ["Limit", props.args.limit]
15
+ ] }), /* @__PURE__ */ jsx(McpToolOutput, { result: props.result })]
16
+ });
17
+ }
18
+ //#endregion
19
+ //#region generated/mcp-ui.ts
20
+ const widgets = { "@agimon-ai/doompi-grep/grep": defineMcpWidget(GrepMcpWidget) };
21
+ //#endregion
22
+ export { widgets };
23
+
24
+ //# sourceMappingURL=mcp-ui.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-ui.mjs","names":["Widget0"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/grep.mcp.tsx","../../generated/mcp-ui.ts"],"sourcesContent":["import { defineMcpWidget, type DoomMcpWidgetProps } from '@agimon-ai/doompi-core/web';\nimport { McpToolFrame, McpToolFields, McpToolOutput } from '@agimon-ai/doompi-web-components';\n\nfunction GrepMcpWidget(props: DoomMcpWidgetProps) {\n return (\n <McpToolFrame {...props} title=\"Search file contents\">\n <McpToolFields\n fields={[\n ['Pattern', props.args.pattern],\n ['Path', props.args.path],\n ['Glob', props.args.glob],\n ['Ignore case', props.args.ignoreCase],\n ['Limit', props.args.limit],\n ]}\n />\n <McpToolOutput result={props.result} />\n </McpToolFrame>\n );\n}\n\nexport default defineMcpWidget(GrepMcpWidget);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport type { ComponentType } from 'react';\nimport type { DoomMcpWidgetProps } from '@agimon-ai/doompi-core/mcpWidget';\nimport Widget0 from \"../src/extensions/workspaces/sessions/(frontend)/tool/grep.mcp\";\n\nexport const widgets: Readonly<Record<string, ComponentType<DoomMcpWidgetProps>>> = {\n \"@agimon-ai/doompi-grep/grep\": Widget0,\n};\n"],"mappings":";;;;AAGA,SAAS,cAAc,OAA2B;CAChD,OACE,qBAAC,cAAD;EAAc,GAAI;EAAO,OAAM;EAA/B,UAAA,CACE,oBAAC,eAAD,EACE,QAAQ;GACN,CAAC,WAAW,MAAM,KAAK,OAAO;GAC9B,CAAC,QAAQ,MAAM,KAAK,IAAI;GACxB,CAAC,QAAQ,MAAM,KAAK,IAAI;GACxB,CAAC,eAAe,MAAM,KAAK,UAAU;GACrC,CAAC,SAAS,MAAM,KAAK,KAAK;EAC5B,EACD,CAAA,GACD,oBAAC,eAAD,EAAe,QAAQ,MAAM,OAAS,CAAA,CAC1B;;AAElB;;;ACbA,MAAa,UAAuE,EAClF,+BDca,gBAAgB,aCdEA,EACjC"}
@@ -312,10 +312,17 @@ const via = (identity, value) => value === void 0 ? void 0 : {
312
312
  ...identity,
313
313
  ...value
314
314
  };
315
+ const withWidget = (key, value) => value === void 0 ? value : {
316
+ ...value,
317
+ _meta: {
318
+ ...value._meta,
319
+ ["doompi/widget"]: key
320
+ }
321
+ };
315
322
  const mcp = (0, _agimon_ai_doompi_core_mcpFacet.defineMcpPlugin)({
316
323
  name: "@agimon-ai/doompi-grep",
317
324
  session: (context) => ({ get tools() {
318
- return [via({ name: "grep" }, at(grep_mcp_default, context))];
325
+ return [withWidget("@agimon-ai/doompi-grep/grep", via({ name: "grep" }, at(grep_mcp_default, context)))];
319
326
  } })
320
327
  });
321
328
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.cjs","names":["Type","resolveInputPath","relative","spawn","stat","readFile","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","basename","defineMcpTool","defineMcpPlugin","toolGrep"],"sources":["../../src/schemas/grepTool.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const GrepParamsSchema = Type.Object({\n pattern: Type.String({ description: 'Regular expression or literal text to find.' }),\n path: Type.Optional(Type.String({ description: 'File or directory to search. Defaults to the working directory.' })),\n glob: Type.Optional(Type.String({ description: 'Glob filter for files to search.' })),\n ignoreCase: Type.Optional(Type.Boolean({ description: 'Match without case sensitivity.' })),\n literal: Type.Optional(Type.Boolean({ description: 'Treat pattern as literal text.' })),\n context: Type.Optional(Type.Integer({ minimum: 0, description: 'Context lines around every match.' })),\n limit: Type.Optional(Type.Integer({ minimum: 1, description: 'Maximum number of matches.' })),\n});\n\nexport type GrepParams = Static<typeof GrepParamsSchema>;\n","import { spawn } from 'node:child_process';\nimport { relative } from 'node:path';\n\nimport { resolveInputPath } from '@agimon-ai/doompi-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/**\n * Searching with ripgrep, with no host in it.\n *\n * The interactive host delegates to Pi's own grep and only re-tags the result,\n * so today this has one caller. It still belongs here rather than beside that\n * caller: spawning a process and parsing its output is the same work whichever\n * host asked for it, and a routed file's job is the shape a host expects, not\n * the search underneath it.\n */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const targetArg = relative(cwd, target) || '.';\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, targetArg);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/doompi-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/doompi-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+): /gu;\nconst CONTEXT_DELIMITER = /-(\\d+)- /gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): DoomHeadlessTool<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: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(result, params, context.cwd, signal);\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\n/**\n * The Node adapters both hosts hand to `tagGrepResult`.\n *\n * Neither is host specific, and both were previously declared twice, once\n * beside each tool. A routed file composes platform-specific output; the\n * service owns everything underneath it, including the plumbing.\n */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath)) rows.push({ ...candidate, native: value });\n break;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcpFacet';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-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\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp.cjs","names":["Type","resolveInputPath","relative","spawn","stat","readFile","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","basename","defineMcpTool","defineMcpPlugin","toolGrep"],"sources":["../../src/schemas/grepTool.ts","../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const GrepParamsSchema = Type.Object({\n pattern: Type.String({ description: 'Regular expression or literal text to find.' }),\n path: Type.Optional(Type.String({ description: 'File or directory to search. Defaults to the working directory.' })),\n glob: Type.Optional(Type.String({ description: 'Glob filter for files to search.' })),\n ignoreCase: Type.Optional(Type.Boolean({ description: 'Match without case sensitivity.' })),\n literal: Type.Optional(Type.Boolean({ description: 'Treat pattern as literal text.' })),\n context: Type.Optional(Type.Integer({ minimum: 0, description: 'Context lines around every match.' })),\n limit: Type.Optional(Type.Integer({ minimum: 1, description: 'Maximum number of matches.' })),\n});\n\nexport type GrepParams = Static<typeof GrepParamsSchema>;\n","import { spawn } from 'node:child_process';\nimport { relative } from 'node:path';\n\nimport { resolveInputPath } from '@agimon-ai/doompi-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/**\n * Searching with ripgrep, with no host in it.\n *\n * The interactive host delegates to Pi's own grep and only re-tags the result,\n * so today this has one caller. It still belongs here rather than beside that\n * caller: spawning a process and parsing its output is the same work whichever\n * host asked for it, and a routed file's job is the shape a host expects, not\n * the search underneath it.\n */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const targetArg = relative(cwd, target) || '.';\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, targetArg);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/doompi-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/doompi-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+): /gu;\nconst CONTEXT_DELIMITER = /-(\\d+)- /gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): DoomHeadlessTool<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: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(result, params, context.cwd, signal);\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\n/**\n * The Node adapters both hosts hand to `tagGrepResult`.\n *\n * Neither is host specific, and both were previously declared twice, once\n * beside each tool. A routed file composes platform-specific output; the\n * service owns everything underneath it, including the plumbing.\n */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath)) rows.push({ ...candidate, native: value });\n break;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcpFacet';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-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, [\"doompi/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/doompi-grep/grep\", via({ name: 'grep' }, at(toolGrep, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAWa,+CAAG"}
1
+ {"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAea,+CAAG"}
@@ -308,10 +308,17 @@ const via = (identity, value) => value === void 0 ? void 0 : {
308
308
  ...identity,
309
309
  ...value
310
310
  };
311
+ const withWidget = (key, value) => value === void 0 ? value : {
312
+ ...value,
313
+ _meta: {
314
+ ...value._meta,
315
+ ["doompi/widget"]: key
316
+ }
317
+ };
311
318
  const mcp = defineMcpPlugin({
312
319
  name: "@agimon-ai/doompi-grep",
313
320
  session: (context) => ({ get tools() {
314
- return [via({ name: "grep" }, at(grep_mcp_default, context))];
321
+ return [withWidget("@agimon-ai/doompi-grep/grep", via({ name: "grep" }, at(grep_mcp_default, context)))];
315
322
  } })
316
323
  });
317
324
  //#endregion
@@ -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';\nimport { relative } from 'node:path';\n\nimport { resolveInputPath } from '@agimon-ai/doompi-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/**\n * Searching with ripgrep, with no host in it.\n *\n * The interactive host delegates to Pi's own grep and only re-tags the result,\n * so today this has one caller. It still belongs here rather than beside that\n * caller: spawning a process and parsing its output is the same work whichever\n * host asked for it, and a routed file's job is the shape a host expects, not\n * the search underneath it.\n */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const targetArg = relative(cwd, target) || '.';\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, targetArg);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/doompi-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/doompi-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+): /gu;\nconst CONTEXT_DELIMITER = /-(\\d+)- /gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): DoomHeadlessTool<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: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(result, params, context.cwd, signal);\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\n/**\n * The Node adapters both hosts hand to `tagGrepResult`.\n *\n * Neither is host specific, and both were previously declared twice, once\n * beside each tool. A routed file composes platform-specific output; the\n * service owns everything underneath it, including the plumbing.\n */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath)) rows.push({ ...candidate, native: value });\n break;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcpFacet';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-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\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
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';\nimport { relative } from 'node:path';\n\nimport { resolveInputPath } from '@agimon-ai/doompi-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/**\n * Searching with ripgrep, with no host in it.\n *\n * The interactive host delegates to Pi's own grep and only re-tags the result,\n * so today this has one caller. It still belongs here rather than beside that\n * caller: spawning a process and parsing its output is the same work whichever\n * host asked for it, and a routed file's job is the shape a host expects, not\n * the search underneath it.\n */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const targetArg = relative(cwd, target) || '.';\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, targetArg);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/doompi-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/doompi-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+): /gu;\nconst CONTEXT_DELIMITER = /-(\\d+)- /gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): DoomHeadlessTool<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: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const result = await runRipgrep({ params, cwd: context.cwd, signal });\n return tagGrepResult(result, params, context.cwd, signal);\n },\n };\n}\n\nexport function parseGrepRow(value: string): GrepRow[] {\n return grepRowCandidates(value)\n .slice(0, 1)\n .map(({ line, match, path }) => ({ line, match, path }));\n}\n\n/**\n * The Node adapters both hosts hand to `tagGrepResult`.\n *\n * Neither is host specific, and both were previously declared twice, once\n * beside each tool. A routed file composes platform-specific output; the\n * service owns everything underneath it, including the plumbing.\n */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath)) rows.push({ ...candidate, native: value });\n break;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcpFacet';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineMcpTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-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, [\"doompi/widget\"]: key },\n});\n\nexport const mcp = defineMcpPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomMcpSessionPlugin['tools'] { return [withWidget(\"@agimon-ai/doompi-grep/grep\", via({ name: 'grep' }, at(toolGrep, context)))]; },\n }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-grep",
3
- "version": "0.0.1-alpha.59",
3
+ "version": "0.0.1-alpha.60",
4
4
  "description": "Snapshot-bound hashline grep for Pi and DoomPi.",
5
5
  "keywords": [
6
6
  "ai",
@@ -62,15 +62,15 @@
62
62
  "access": "public"
63
63
  },
64
64
  "dependencies": {
65
- "@agimon-ai/doompi-core": "0.0.1-alpha.87",
66
- "@agimon-ai/doompi-hashline": "0.0.1-alpha.53",
67
- "@agimon-ai/doompi-ui": "0.0.1-alpha.87",
68
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.45",
65
+ "@agimon-ai/doompi-core": "0.0.1-alpha.88",
66
+ "@agimon-ai/doompi-hashline": "0.0.1-alpha.54",
67
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.88",
68
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.46",
69
69
  "@deepseek-ai/cordis": "4.0.2",
70
70
  "typebox": "1.3.34"
71
71
  },
72
72
  "devDependencies": {
73
- "@agimon-ai/doompi-build": "0.0.1-alpha.13",
73
+ "@agimon-ai/doompi-build": "0.0.1-alpha.14",
74
74
  "@earendil-works/pi-coding-agent": "0.87.1",
75
75
  "@earendil-works/pi-tui": "0.87.1",
76
76
  "@tanstack/react-store": "0.11.1",
@@ -105,7 +105,14 @@
105
105
  "dist": "./dist/extensions/mcp.mjs",
106
106
  "scopes": [
107
107
  "session"
108
- ]
108
+ ],
109
+ "ui": {
110
+ "entry": "./generated/mcp-ui.ts",
111
+ "dist": "./dist/extensions/mcp-ui.mjs",
112
+ "widgets": [
113
+ "@agimon-ai/doompi-grep/grep"
114
+ ]
115
+ }
109
116
  },
110
117
  "doompiServer": {
111
118
  "entry": "./generated/server.ts",
@@ -135,7 +142,7 @@
135
142
  "build": "tsdown && tsdown --config tsdown.mcp.config.ts",
136
143
  "build:mcp": "tsdown --config tsdown.mcp.config.ts",
137
144
  "test": "vitest --run",
138
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
145
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json && tsc --noEmit -p tsconfig.mcp.json",
139
146
  "lint": "oxlint . && oxfmt . --check",
140
147
  "fixcode": "oxlint . --fix && oxfmt ."
141
148
  }