@agimon-ai/doompi-grep 0.0.1-alpha.55 → 0.0.1-alpha.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/mcp.cjs +2 -2
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.d.mts.map +1 -1
- package/dist/extensions/mcp.mjs +2 -2
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/pi.cjs +2 -2
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.mts.map +1 -1
- package/dist/extensions/pi.mjs +2 -2
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/extensions/server.cjs +2 -2
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +2 -2
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +2 -2
- package/dist/extensions/web.mjs.map +1 -1
- package/package.json +6 -6
package/dist/extensions/mcp.cjs
CHANGED
|
@@ -308,10 +308,10 @@ var grep_mcp_default = (0, _agimon_ai_doompi_core_mcpFacet.defineMcpTool)(create
|
|
|
308
308
|
//#endregion
|
|
309
309
|
//#region generated/mcp.ts
|
|
310
310
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
311
|
-
const via = (identity, value) =>
|
|
311
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
312
312
|
...identity,
|
|
313
313
|
...value
|
|
314
|
-
}
|
|
314
|
+
};
|
|
315
315
|
const mcp = (0, _agimon_ai_doompi_core_mcpFacet.defineMcpPlugin)({
|
|
316
316
|
name: "@agimon-ai/doompi-grep",
|
|
317
317
|
session: (context) => ({ get tools() {
|
|
@@ -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 => ({ ...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\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"mcp.d.mts","names":[],"sources":["../../generated/mcp.ts"],"mappings":";qBAWa,+CAAG"}
|
package/dist/extensions/mcp.mjs
CHANGED
|
@@ -304,10 +304,10 @@ var grep_mcp_default = defineMcpTool(createHeadlessGrepTool);
|
|
|
304
304
|
//#endregion
|
|
305
305
|
//#region generated/mcp.ts
|
|
306
306
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
307
|
-
const via = (identity, value) =>
|
|
307
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
308
308
|
...identity,
|
|
309
309
|
...value
|
|
310
|
-
}
|
|
310
|
+
};
|
|
311
311
|
const mcp = defineMcpPlugin({
|
|
312
312
|
name: "@agimon-ai/doompi-grep",
|
|
313
313
|
session: (context) => ({ get tools() {
|
|
@@ -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 => ({ ...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\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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"}
|
package/dist/extensions/pi.cjs
CHANGED
|
@@ -49,10 +49,10 @@ var grep_cli_default = (0, _agimon_ai_doompi_core_piExtension.definePiToolRender
|
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region generated/pi.ts
|
|
51
51
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
52
|
-
const via = (identity, value) =>
|
|
52
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
53
53
|
...identity,
|
|
54
54
|
...value
|
|
55
|
-
}
|
|
55
|
+
};
|
|
56
56
|
const extension = (0, _agimon_ai_doompi_core_piExtension.definePiExtension)("@agimon-ai/doompi-grep", (context) => ({ ...(0, _agimon_ai_doompi_core_piExtension.piToolContributions)("@agimon-ai/doompi-grep", [via({ name: "grep" }, (0, _agimon_ai_doompi_core_piExtension.withPiRenderers)(at(grep_cli_default$1, context), at(grep_cli_default, context)))]) }));
|
|
57
57
|
//#endregion
|
|
58
58
|
exports.default = extension;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi.cjs","names":["isWritableFile","GrepParamsSchema","createGrepToolDefinition","tagGrepResult","definePiTool","renderHashlineCall","renderHashlineResult","definePiToolRenderer","definePiExtension","piToolContributions","withPiRenderers","toolGrep","renderGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/_lib/grepTool.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepRenderer.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli.ts","../../generated/pi.ts"],"sourcesContent":["import { isWritableFile } from '@agimon-ai/doompi-hashline/files';\nimport { createGrepToolDefinition, type AgentToolResult, type ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\nimport { assertNotAborted, type GrepResult, tagGrepResult } from '../../../../../../services/grepTool';\n\ntype WritableCheck = (path: string) => Promise<boolean>;\n\nexport function createHashlineGrepTool(\n writable: WritableCheck = isWritableFile,\n): ToolDefinition<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using Pi-compatible ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Non-writable matches retain native Pi output. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const input = params as GrepParams;\n const nativeGrep = createGrepToolDefinition(ctx.cwd);\n const nativeResult = await nativeGrep.execute(toolCallId, input, signal, onUpdate, ctx);\n assertNotAborted(signal);\n const tagged = await tagGrepResult(nativeResult as GrepResult, input, ctx.cwd, signal, writable);\n return tagged as unknown as AgentToolResult<unknown>;\n },\n };\n}\n","import { definePiTool } from '@agimon-ai/doompi-core/piExtension';\n\nimport { createHashlineGrepTool } from './_lib/grepTool';\n\nexport default definePiTool(createHashlineGrepTool(), { overrides: true });\n","import type { PiToolRenderers } from '@agimon-ai/doompi-core/piExtension';\nimport { renderHashlineCall, renderHashlineResult } from '@agimon-ai/doompi-ui/hashlineRendering';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\n\nexport const grepRenderer: PiToolRenderers<typeof GrepParamsSchema> = {\n renderShell: 'self',\n renderCall(args, theme) {\n const input = args as GrepParams;\n const details = [\n input.path ?? '.',\n input.glob,\n input.ignoreCase === true ? 'ignore case' : undefined,\n input.limit === undefined ? undefined : `${input.limit} matches`,\n ].filter((value): value is string => value !== undefined);\n return renderHashlineCall('grep', input.pattern, details, theme);\n },\n renderResult(result, options, theme, context) {\n return renderHashlineResult(result, options, theme, context, 'grep');\n },\n};\n","import { definePiToolRenderer } from '@agimon-ai/doompi-core/piExtension';\n\nimport { grepRenderer } from './_lib/grepRenderer.cli';\n\nexport default definePiToolRenderer(grepRenderer);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { definePiExtension, piToolContributions, withPiRenderers, type PiPluginContext } from '@agimon-ai/doompi-core/piExtension';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.cli';\nimport renderGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli';\n\ntype OptionsOf<T> = T extends (context: PiPluginContext<infer Options>) => unknown ? Options : never;\ntype InferredOptions = Exclude<OptionsOf<typeof toolGrep>, undefined>;\ntype Intersect<Union> =\n (Union extends unknown ? (value: Union) => void : never) extends (value: infer Value) => void ? Value : never;\ntype ExtensionOptions = [InferredOptions] extends [never] ? undefined : Intersect<InferredOptions>;\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
|
|
1
|
+
{"version":3,"file":"pi.cjs","names":["isWritableFile","GrepParamsSchema","createGrepToolDefinition","tagGrepResult","definePiTool","renderHashlineCall","renderHashlineResult","definePiToolRenderer","definePiExtension","piToolContributions","withPiRenderers","toolGrep","renderGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/_lib/grepTool.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepRenderer.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli.ts","../../generated/pi.ts"],"sourcesContent":["import { isWritableFile } from '@agimon-ai/doompi-hashline/files';\nimport { createGrepToolDefinition, type AgentToolResult, type ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\nimport { assertNotAborted, type GrepResult, tagGrepResult } from '../../../../../../services/grepTool';\n\ntype WritableCheck = (path: string) => Promise<boolean>;\n\nexport function createHashlineGrepTool(\n writable: WritableCheck = isWritableFile,\n): ToolDefinition<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using Pi-compatible ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Non-writable matches retain native Pi output. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const input = params as GrepParams;\n const nativeGrep = createGrepToolDefinition(ctx.cwd);\n const nativeResult = await nativeGrep.execute(toolCallId, input, signal, onUpdate, ctx);\n assertNotAborted(signal);\n const tagged = await tagGrepResult(nativeResult as GrepResult, input, ctx.cwd, signal, writable);\n return tagged as unknown as AgentToolResult<unknown>;\n },\n };\n}\n","import { definePiTool } from '@agimon-ai/doompi-core/piExtension';\n\nimport { createHashlineGrepTool } from './_lib/grepTool';\n\nexport default definePiTool(createHashlineGrepTool(), { overrides: true });\n","import type { PiToolRenderers } from '@agimon-ai/doompi-core/piExtension';\nimport { renderHashlineCall, renderHashlineResult } from '@agimon-ai/doompi-ui/hashlineRendering';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\n\nexport const grepRenderer: PiToolRenderers<typeof GrepParamsSchema> = {\n renderShell: 'self',\n renderCall(args, theme) {\n const input = args as GrepParams;\n const details = [\n input.path ?? '.',\n input.glob,\n input.ignoreCase === true ? 'ignore case' : undefined,\n input.limit === undefined ? undefined : `${input.limit} matches`,\n ].filter((value): value is string => value !== undefined);\n return renderHashlineCall('grep', input.pattern, details, theme);\n },\n renderResult(result, options, theme, context) {\n return renderHashlineResult(result, options, theme, context, 'grep');\n },\n};\n","import { definePiToolRenderer } from '@agimon-ai/doompi-core/piExtension';\n\nimport { grepRenderer } from './_lib/grepRenderer.cli';\n\nexport default definePiToolRenderer(grepRenderer);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { definePiExtension, piToolContributions, withPiRenderers, type PiPluginContext } from '@agimon-ai/doompi-core/piExtension';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.cli';\nimport renderGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli';\n\ntype OptionsOf<T> = T extends (context: PiPluginContext<infer Options>) => unknown ? Options : never;\ntype InferredOptions = Exclude<OptionsOf<typeof toolGrep>, undefined>;\ntype Intersect<Union> =\n (Union extends unknown ? (value: Union) => void : never) extends (value: infer Value) => void ? Value : never;\ntype ExtensionOptions = [InferredOptions] extends [never] ? undefined : Intersect<InferredOptions>;\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 extension = definePiExtension<ExtensionOptions>('@agimon-ai/doompi-grep', (context) => ({\n ...piToolContributions('@agimon-ai/doompi-grep', [via({ name: 'grep' }, withPiRenderers(at(toolGrep, context), at(renderGrep, context)))]),\n}));\n\nexport default extension;\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,uBACd,WAA0BA,iCAAAA,gBACe;CACzC,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,YAAYC,iBAAAA;EACZ,eAAe;EACf,MAAM,QAAQ,YAAY,QAAQ,QAAQ,UAAU,KAAK;GACvD,MAAM,QAAQ;GAEd,MAAM,eAAe,OAAA,GADFC,gCAAAA,yBAAAA,CAAyB,IAAI,GACZ,CAAC,CAAC,QAAQ,YAAY,OAAO,QAAQ,UAAU,GAAG;GACtF,mBAAA,iBAAiB,MAAM;GAEvB,OAAO,MADcC,mBAAAA,cAAc,cAA4B,OAAO,IAAI,KAAK,QAAQ,QAAQ;EAEjG;CACF;AACF;;;ACxBA,IAAA,sBAAA,GAAeC,mCAAAA,aAAAA,CAAa,uBAAuB,GAAG,EAAE,WAAW,KAAK,CAAC;;;AEAzE,IAAA,oBAAA,GAAeG,mCAAAA,qBAAAA,CAAqB;CDElC,aAAa;CACb,WAAW,MAAM,OAAO;EACtB,MAAM,QAAQ;EACd,MAAM,UAAU;GACd,MAAM,QAAQ;GACd,MAAM;GACN,MAAM,eAAe,OAAO,gBAAgB,KAAA;GAC5C,MAAM,UAAU,KAAA,IAAY,KAAA,IAAY,GAAG,MAAM,MAAM;EACzD,CAAC,CAAC,QAAQ,UAA2B,UAAU,KAAA,CAAS;EACxD,QAAA,GAAOF,uCAAAA,mBAAAA,CAAmB,QAAQ,MAAM,SAAS,SAAS,KAAK;CACjE;CACA,aAAa,QAAQ,SAAS,OAAO,SAAS;EAC5C,QAAA,GAAOC,uCAAAA,qBAAAA,CAAqB,QAAQ,SAAS,OAAO,SAAS,MAAM;CACrE;ACfkC,CAAY;;;ACShD,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,aAAA,GAAYE,mCAAAA,kBAAAA,CAAoC,2BAA2B,aAAa,EACnG,IAAA,GAAGC,mCAAAA,oBAAAA,CAAoB,0BAA0B,CAAC,IAAI,EAAE,MAAM,OAAO,IAAA,GAAGC,mCAAAA,gBAAAA,CAAgB,GAAGC,oBAAU,OAAO,GAAG,GAAGC,kBAAY,OAAO,CAAC,CAAC,CAAC,CAAC,EAC3I,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.mts","names":[],"sources":["../../generated/pi.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"pi.d.mts","names":[],"sources":["../../generated/pi.ts"],"mappings":";qBAkBa,+DAAS"}
|
package/dist/extensions/pi.mjs
CHANGED
|
@@ -45,10 +45,10 @@ var grep_cli_default = definePiToolRenderer({
|
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region generated/pi.ts
|
|
47
47
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
48
|
-
const via = (identity, value) =>
|
|
48
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
49
49
|
...identity,
|
|
50
50
|
...value
|
|
51
|
-
}
|
|
51
|
+
};
|
|
52
52
|
const extension = definePiExtension("@agimon-ai/doompi-grep", (context) => ({ ...piToolContributions("@agimon-ai/doompi-grep", [via({ name: "grep" }, withPiRenderers(at(grep_cli_default$1, context), at(grep_cli_default, context)))]) }));
|
|
53
53
|
//#endregion
|
|
54
54
|
export { extension as default, extension };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pi.mjs","names":["toolGrep","renderGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/_lib/grepTool.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepRenderer.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli.ts","../../generated/pi.ts"],"sourcesContent":["import { isWritableFile } from '@agimon-ai/doompi-hashline/files';\nimport { createGrepToolDefinition, type AgentToolResult, type ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\nimport { assertNotAborted, type GrepResult, tagGrepResult } from '../../../../../../services/grepTool';\n\ntype WritableCheck = (path: string) => Promise<boolean>;\n\nexport function createHashlineGrepTool(\n writable: WritableCheck = isWritableFile,\n): ToolDefinition<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using Pi-compatible ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Non-writable matches retain native Pi output. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const input = params as GrepParams;\n const nativeGrep = createGrepToolDefinition(ctx.cwd);\n const nativeResult = await nativeGrep.execute(toolCallId, input, signal, onUpdate, ctx);\n assertNotAborted(signal);\n const tagged = await tagGrepResult(nativeResult as GrepResult, input, ctx.cwd, signal, writable);\n return tagged as unknown as AgentToolResult<unknown>;\n },\n };\n}\n","import { definePiTool } from '@agimon-ai/doompi-core/piExtension';\n\nimport { createHashlineGrepTool } from './_lib/grepTool';\n\nexport default definePiTool(createHashlineGrepTool(), { overrides: true });\n","import type { PiToolRenderers } from '@agimon-ai/doompi-core/piExtension';\nimport { renderHashlineCall, renderHashlineResult } from '@agimon-ai/doompi-ui/hashlineRendering';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\n\nexport const grepRenderer: PiToolRenderers<typeof GrepParamsSchema> = {\n renderShell: 'self',\n renderCall(args, theme) {\n const input = args as GrepParams;\n const details = [\n input.path ?? '.',\n input.glob,\n input.ignoreCase === true ? 'ignore case' : undefined,\n input.limit === undefined ? undefined : `${input.limit} matches`,\n ].filter((value): value is string => value !== undefined);\n return renderHashlineCall('grep', input.pattern, details, theme);\n },\n renderResult(result, options, theme, context) {\n return renderHashlineResult(result, options, theme, context, 'grep');\n },\n};\n","import { definePiToolRenderer } from '@agimon-ai/doompi-core/piExtension';\n\nimport { grepRenderer } from './_lib/grepRenderer.cli';\n\nexport default definePiToolRenderer(grepRenderer);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { definePiExtension, piToolContributions, withPiRenderers, type PiPluginContext } from '@agimon-ai/doompi-core/piExtension';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.cli';\nimport renderGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli';\n\ntype OptionsOf<T> = T extends (context: PiPluginContext<infer Options>) => unknown ? Options : never;\ntype InferredOptions = Exclude<OptionsOf<typeof toolGrep>, undefined>;\ntype Intersect<Union> =\n (Union extends unknown ? (value: Union) => void : never) extends (value: infer Value) => void ? Value : never;\ntype ExtensionOptions = [InferredOptions] extends [never] ? undefined : Intersect<InferredOptions>;\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
|
|
1
|
+
{"version":3,"file":"pi.mjs","names":["toolGrep","renderGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/_lib/grepTool.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepRenderer.cli.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli.ts","../../generated/pi.ts"],"sourcesContent":["import { isWritableFile } from '@agimon-ai/doompi-hashline/files';\nimport { createGrepToolDefinition, type AgentToolResult, type ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\nimport { assertNotAborted, type GrepResult, tagGrepResult } from '../../../../../../services/grepTool';\n\ntype WritableCheck = (path: string) => Promise<boolean>;\n\nexport function createHashlineGrepTool(\n writable: WritableCheck = isWritableFile,\n): ToolDefinition<typeof GrepParamsSchema> {\n return {\n name: 'grep',\n label: 'grep',\n description:\n 'Search file contents using Pi-compatible ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Non-writable matches retain native Pi output. Respects .gitignore.',\n promptSnippet: 'Search file contents and return editable hashline anchors',\n parameters: GrepParamsSchema,\n executionMode: 'parallel',\n async execute(toolCallId, params, signal, onUpdate, ctx) {\n const input = params as GrepParams;\n const nativeGrep = createGrepToolDefinition(ctx.cwd);\n const nativeResult = await nativeGrep.execute(toolCallId, input, signal, onUpdate, ctx);\n assertNotAborted(signal);\n const tagged = await tagGrepResult(nativeResult as GrepResult, input, ctx.cwd, signal, writable);\n return tagged as unknown as AgentToolResult<unknown>;\n },\n };\n}\n","import { definePiTool } from '@agimon-ai/doompi-core/piExtension';\n\nimport { createHashlineGrepTool } from './_lib/grepTool';\n\nexport default definePiTool(createHashlineGrepTool(), { overrides: true });\n","import type { PiToolRenderers } from '@agimon-ai/doompi-core/piExtension';\nimport { renderHashlineCall, renderHashlineResult } from '@agimon-ai/doompi-ui/hashlineRendering';\n\nimport { GrepParamsSchema, type GrepParams } from '../../../../../../schemas/grepTool';\n\nexport const grepRenderer: PiToolRenderers<typeof GrepParamsSchema> = {\n renderShell: 'self',\n renderCall(args, theme) {\n const input = args as GrepParams;\n const details = [\n input.path ?? '.',\n input.glob,\n input.ignoreCase === true ? 'ignore case' : undefined,\n input.limit === undefined ? undefined : `${input.limit} matches`,\n ].filter((value): value is string => value !== undefined);\n return renderHashlineCall('grep', input.pattern, details, theme);\n },\n renderResult(result, options, theme, context) {\n return renderHashlineResult(result, options, theme, context, 'grep');\n },\n};\n","import { definePiToolRenderer } from '@agimon-ai/doompi-core/piExtension';\n\nimport { grepRenderer } from './_lib/grepRenderer.cli';\n\nexport default definePiToolRenderer(grepRenderer);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { definePiExtension, piToolContributions, withPiRenderers, type PiPluginContext } from '@agimon-ai/doompi-core/piExtension';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(backend)/tool/grep.cli';\nimport renderGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.cli';\n\ntype OptionsOf<T> = T extends (context: PiPluginContext<infer Options>) => unknown ? Options : never;\ntype InferredOptions = Exclude<OptionsOf<typeof toolGrep>, undefined>;\ntype Intersect<Union> =\n (Union extends unknown ? (value: Union) => void : never) extends (value: infer Value) => void ? Value : never;\ntype ExtensionOptions = [InferredOptions] extends [never] ? undefined : Intersect<InferredOptions>;\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 extension = definePiExtension<ExtensionOptions>('@agimon-ai/doompi-grep', (context) => ({\n ...piToolContributions('@agimon-ai/doompi-grep', [via({ name: 'grep' }, withPiRenderers(at(toolGrep, context), at(renderGrep, context)))]),\n}));\n\nexport default extension;\n"],"mappings":";;;;;;;AAQA,SAAgB,uBACd,WAA0B,gBACe;CACzC,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,YAAY;EACZ,eAAe;EACf,MAAM,QAAQ,YAAY,QAAQ,QAAQ,UAAU,KAAK;GACvD,MAAM,QAAQ;GAEd,MAAM,eAAe,MADF,yBAAyB,IAAI,GACZ,CAAC,CAAC,QAAQ,YAAY,OAAO,QAAQ,UAAU,GAAG;GACtF,iBAAiB,MAAM;GAEvB,OAAO,MADc,cAAc,cAA4B,OAAO,IAAI,KAAK,QAAQ,QAAQ;EAEjG;CACF;AACF;;;ACxBA,IAAA,qBAAe,aAAa,uBAAuB,GAAG,EAAE,WAAW,KAAK,CAAC;;;AEAzE,IAAA,mBAAe,qBAAqB;CDElC,aAAa;CACb,WAAW,MAAM,OAAO;EACtB,MAAM,QAAQ;EACd,MAAM,UAAU;GACd,MAAM,QAAQ;GACd,MAAM;GACN,MAAM,eAAe,OAAO,gBAAgB,KAAA;GAC5C,MAAM,UAAU,KAAA,IAAY,KAAA,IAAY,GAAG,MAAM,MAAM;EACzD,CAAC,CAAC,QAAQ,UAA2B,UAAU,KAAA,CAAS;EACxD,OAAO,mBAAmB,QAAQ,MAAM,SAAS,SAAS,KAAK;CACjE;CACA,aAAa,QAAQ,SAAS,OAAO,SAAS;EAC5C,OAAO,qBAAqB,QAAQ,SAAS,OAAO,SAAS,MAAM;CACrE;ACfkC,CAAY;;;ACShD,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,YAAY,kBAAoC,2BAA2B,aAAa,EACnG,GAAG,oBAAoB,0BAA0B,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,gBAAgB,GAAGA,oBAAU,OAAO,GAAG,GAAGC,kBAAY,OAAO,CAAC,CAAC,CAAC,CAAC,EAC3I,EAAE"}
|
|
@@ -9,10 +9,10 @@ var grep_server_default = (0, require("@agimon-ai/doompi-core/extensionFile").de
|
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region generated/server.ts
|
|
11
11
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
12
|
-
const via = (identity, value) =>
|
|
12
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
13
13
|
...identity,
|
|
14
14
|
...value
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
const facet = (0, _agimon_ai_doompi_core_serverFacet.defineServerPlugin)({
|
|
17
17
|
name: "@agimon-ai/doompi-grep",
|
|
18
18
|
session: (context) => ({ get tools() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":["defineServerTool","createHeadlessGrepTool","defineServerPlugin","toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { defineServerTool } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\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
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["defineServerTool","createHeadlessGrepTool","defineServerPlugin","toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { defineServerTool } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\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 facet = defineServerPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default facet;\n"],"mappings":";;;;;;;AAIA,IAAA,uBAAA,iDAAeA,CAAAA,CAAAA,iBAAAA,CAAiBC,iBAAAA,sBAAsB;;;ACEtD,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,SAAA,GAAQC,mCAAAA,mBAAAA,CAAmB;CACtC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAA0C;EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGC,qBAAU,OAAO,CAAC,CAAC;CAAG,EACzG;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAWa,oDAAK"}
|
|
@@ -6,10 +6,10 @@ var grep_server_default = defineServerTool(createHeadlessGrepTool);
|
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region generated/server.ts
|
|
8
8
|
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
9
|
-
const via = (identity, value) =>
|
|
9
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
10
10
|
...identity,
|
|
11
11
|
...value
|
|
12
|
-
}
|
|
12
|
+
};
|
|
13
13
|
const facet = defineServerPlugin({
|
|
14
14
|
name: "@agimon-ai/doompi-grep",
|
|
15
15
|
session: (context) => ({ get tools() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":["toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { defineServerTool } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\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
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { defineServerTool } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\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 facet = defineServerPlugin({\n name: '@agimon-ai/doompi-grep',\n session: (context) => ({\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default facet;\n"],"mappings":";;;;AAIA,IAAA,sBAAe,iBAAiB,sBAAsB;;;ACEtD,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,QAAQ,mBAAmB;CACtC,MAAM;CACN,UAAU,aAAa,EACrB,IAAI,QAA0C;EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,GAAG,GAAGA,qBAAU,OAAO,CAAC,CAAC;CAAG,EACzG;AACF,CAAC"}
|
package/dist/extensions/web.mjs
CHANGED
|
@@ -86,10 +86,10 @@ function GrepToolMessage({ args, result, output, running, isError }) {
|
|
|
86
86
|
var grep_web_default = defineToolRenderer({ message: GrepToolMessage });
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region generated/web.ts
|
|
89
|
-
const via = (identity, value) =>
|
|
89
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
90
90
|
...identity,
|
|
91
91
|
...value
|
|
92
|
-
}
|
|
92
|
+
};
|
|
93
93
|
const webPlugin = defineWebPlugin({
|
|
94
94
|
id: "grep",
|
|
95
95
|
session: { get toolRenderers() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.mjs","names":["toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepToolView.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/GrepToolMessage.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.web.tsx","../../generated/web.ts"],"sourcesContent":["import { compactDetails } from '@agimon-ai/doompi-web-components';\n\nexport interface GrepCallView {\n readonly pattern: string;\n /** Search path, glob, case flag, and limit, as the TUI's call heading lists them. */\n readonly details: readonly string[];\n}\n\n/** What the grep call heading shows: the pattern, then where and how it searched. */\nexport function grepCallView(args: Readonly<Record<string, unknown>>): GrepCallView {\n const limit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? args.limit : undefined;\n return {\n pattern: typeof args.pattern === 'string' ? args.pattern : '',\n details: compactDetails([\n typeof args.path === 'string' && args.path.length > 0 ? args.path : '.',\n typeof args.glob === 'string' ? args.glob : undefined,\n args.ignoreCase === true ? 'ignore case' : undefined,\n limit === undefined ? undefined : `${limit} matches`,\n ]),\n };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/doompi-core/web';\nimport {\n HashlineLines,\n hashlineBody,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n resultTextLines,\n toolTone,\n} from '@agimon-ai/doompi-web-components';\n\nimport { grepCallView } from '../_lib/grepToolView';\n\n/**\n * The grep tool's timeline item, the web half of its renderCall and\n * renderResult: the call summary in the header and, when expanded, the\n * anchored matches with a line-number gutter in the body. A failure shows the\n * tool's message in red inside the expanded body instead.\n */\nexport function GrepToolMessage({ args, result, output, running, isError }: ToolMessageRenderProps) {\n const view = grepCallView(args);\n const body = result === null || isError ? null : hashlineBody(result, output, 'grep', true);\n return (\n <MessageItem tone={toolTone({ running, isError })} expandable defaultExpanded={false}>\n {({ expanded }) => {\n return (\n <>\n <MessageItemHeader title=\"grep\">\n <span data-testid=\"tool-call-grep\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"truncate text-doom-text\">{view.pattern}</span>\n {view.details.length > 0 ? (\n <span className=\"shrink-0 text-doom-faint\">· {view.details.join(' · ')}</span>\n ) : null}\n </span>\n </MessageItemHeader>\n {expanded ? (\n isError ? (\n <MessageItemBody data-testid=\"tool-result-grep\" className=\"flex flex-col\">\n <MessageItemStatus tone=\"error\">failed</MessageItemStatus>\n {resultTextLines(result, output).map((line, index) => (\n <span key={`${String(index)}-${line}`} className=\"whitespace-pre-wrap break-words text-doom-red\">\n {line}\n </span>\n ))}\n </MessageItemBody>\n ) : body === null ? (\n running ? (\n <MessageItemBody data-testid=\"tool-result-grep\">\n <MessageItemStatus tone=\"running\">running</MessageItemStatus>\n </MessageItemBody>\n ) : null\n ) : body.shown.length === 0 && body.notice === undefined ? null : (\n <MessageItemBody data-testid=\"tool-result-grep\" className=\"flex flex-col gap-1\">\n <HashlineLines lines={body.shown} gutter={body.gutter} />\n {body.notice !== undefined ? <span className=\"text-doom-faint\">{body.notice}</span> : null}\n </MessageItemBody>\n )\n ) : null}\n </>\n );\n }}\n </MessageItem>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/doompi-core/web';\n\nimport { GrepToolMessage } from './_components/GrepToolMessage';\n\n/**\n * The grep tool's timeline card, the web half of the TUI's renderCall and\n * renderResult for hashline searches. The filename binds it to the tool.\n */\nexport default defineToolRenderer({ message: GrepToolMessage });\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T
|
|
1
|
+
{"version":3,"file":"web.mjs","names":["toolGrep"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepToolView.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/GrepToolMessage.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/grep.web.tsx","../../generated/web.ts"],"sourcesContent":["import { compactDetails } from '@agimon-ai/doompi-web-components';\n\nexport interface GrepCallView {\n readonly pattern: string;\n /** Search path, glob, case flag, and limit, as the TUI's call heading lists them. */\n readonly details: readonly string[];\n}\n\n/** What the grep call heading shows: the pattern, then where and how it searched. */\nexport function grepCallView(args: Readonly<Record<string, unknown>>): GrepCallView {\n const limit = typeof args.limit === 'number' && Number.isFinite(args.limit) ? args.limit : undefined;\n return {\n pattern: typeof args.pattern === 'string' ? args.pattern : '',\n details: compactDetails([\n typeof args.path === 'string' && args.path.length > 0 ? args.path : '.',\n typeof args.glob === 'string' ? args.glob : undefined,\n args.ignoreCase === true ? 'ignore case' : undefined,\n limit === undefined ? undefined : `${limit} matches`,\n ]),\n };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/doompi-core/web';\nimport {\n HashlineLines,\n hashlineBody,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n resultTextLines,\n toolTone,\n} from '@agimon-ai/doompi-web-components';\n\nimport { grepCallView } from '../_lib/grepToolView';\n\n/**\n * The grep tool's timeline item, the web half of its renderCall and\n * renderResult: the call summary in the header and, when expanded, the\n * anchored matches with a line-number gutter in the body. A failure shows the\n * tool's message in red inside the expanded body instead.\n */\nexport function GrepToolMessage({ args, result, output, running, isError }: ToolMessageRenderProps) {\n const view = grepCallView(args);\n const body = result === null || isError ? null : hashlineBody(result, output, 'grep', true);\n return (\n <MessageItem tone={toolTone({ running, isError })} expandable defaultExpanded={false}>\n {({ expanded }) => {\n return (\n <>\n <MessageItemHeader title=\"grep\">\n <span data-testid=\"tool-call-grep\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"truncate text-doom-text\">{view.pattern}</span>\n {view.details.length > 0 ? (\n <span className=\"shrink-0 text-doom-faint\">· {view.details.join(' · ')}</span>\n ) : null}\n </span>\n </MessageItemHeader>\n {expanded ? (\n isError ? (\n <MessageItemBody data-testid=\"tool-result-grep\" className=\"flex flex-col\">\n <MessageItemStatus tone=\"error\">failed</MessageItemStatus>\n {resultTextLines(result, output).map((line, index) => (\n <span key={`${String(index)}-${line}`} className=\"whitespace-pre-wrap break-words text-doom-red\">\n {line}\n </span>\n ))}\n </MessageItemBody>\n ) : body === null ? (\n running ? (\n <MessageItemBody data-testid=\"tool-result-grep\">\n <MessageItemStatus tone=\"running\">running</MessageItemStatus>\n </MessageItemBody>\n ) : null\n ) : body.shown.length === 0 && body.notice === undefined ? null : (\n <MessageItemBody data-testid=\"tool-result-grep\" className=\"flex flex-col gap-1\">\n <HashlineLines lines={body.shown} gutter={body.gutter} />\n {body.notice !== undefined ? <span className=\"text-doom-faint\">{body.notice}</span> : null}\n </MessageItemBody>\n )\n ) : null}\n </>\n );\n }}\n </MessageItem>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/doompi-core/web';\n\nimport { GrepToolMessage } from './_components/GrepToolMessage';\n\n/**\n * The grep tool's timeline card, the web half of the TUI's renderCall and\n * renderResult for hashline searches. The filename binds it to the tool.\n */\nexport default defineToolRenderer({ message: GrepToolMessage });\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/doompi-core/web';\n\nimport toolGrep from '../src/extensions/workspaces/sessions/(frontend)/tool/grep.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'grep',\n session: {\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['grep'] }, toolGrep)]; },\n },\n});\n"],"mappings":";;;;;AASA,SAAgB,aAAa,MAAuD;CAClF,MAAM,QAAQ,OAAO,KAAK,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,IAAI,KAAK,QAAQ,KAAA;CAC3F,OAAO;EACL,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;EAC3D,SAAS,eAAe;GACtB,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;GACpE,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO,KAAA;GAC5C,KAAK,eAAe,OAAO,gBAAgB,KAAA;GAC3C,UAAU,KAAA,IAAY,KAAA,IAAY,GAAG,MAAM;EAC7C,CAAC;CACH;AACF;;;;;;;;;ACAA,SAAgB,gBAAgB,EAAE,MAAM,QAAQ,QAAQ,SAAS,WAAmC;CAClG,MAAM,OAAO,aAAa,IAAI;CAC9B,MAAM,OAAO,WAAW,QAAQ,UAAU,OAAO,aAAa,QAAQ,QAAQ,QAAQ,IAAI;CAC1F,OACE,oBAAC,aAAD;EAAa,MAAM,SAAS;GAAE;GAAS;EAAQ,CAAC;EAAG,YAAA;EAAW,iBAAiB;EAC3E,WAAA,EAAE,eAAe;GACjB,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,mBAAD;IAAmB,OAAM;IACvB,UAAA,qBAAC,QAAD;KAAM,eAAY;KAAiB,WAAU;KAA7C,UAAA,CACE,oBAAC,QAAD;MAAM,WAAU;MAA2B,UAAA,KAAK;KAAc,CAAA,GAC7D,KAAK,QAAQ,SAAS,IACrB,qBAAC,QAAD;MAAM,WAAU;MAAhB,UAAA,CAA2C,MAAG,KAAK,QAAQ,KAAK,KAAK,CAAQ;KAC3E,CAAA,IAAA,IACA;;GACW,CAAA,GAClB,WACC,UACE,qBAAC,iBAAD;IAAiB,eAAY;IAAmB,WAAU;IAA1D,UAAA,CACE,oBAAC,mBAAD;KAAmB,MAAK;KAAQ,UAAA;IAAyB,CAAA,GACxD,gBAAgB,QAAQ,MAAM,CAAC,CAAC,KAAK,MAAM,UAC1C,oBAAC,QAAD;KAAuC,WAAU;KAC9C,UAAA;IACG,GAFK,GAAG,OAAO,KAAK,EAAE,GAAG,MAEzB,CACP,CACc;GACf,CAAA,IAAA,SAAS,OACX,UACE,oBAAC,iBAAD;IAAiB,eAAY;IAC3B,UAAA,oBAAC,mBAAD;KAAmB,MAAK;KAAU,UAAA;IAA0B,CAAA;GAC7C,CAAA,IACf,OACF,KAAK,MAAM,WAAW,KAAK,KAAK,WAAW,KAAA,IAAY,OACzD,qBAAC,iBAAD;IAAiB,eAAY;IAAmB,WAAU;IAA1D,UAAA,CACE,oBAAC,eAAD;KAAe,OAAO,KAAK;KAAO,QAAQ,KAAK;IAAS,CAAA,GACvD,KAAK,WAAW,KAAA,IAAY,oBAAC,QAAD;KAAM,WAAU;KAAmB,UAAA,KAAK;IAAa,CAAA,IAAI,IACvE;GAEjB,CAAA,IAAA,IACJ,EAAA,CAAA;EAEN;CACW,CAAA;AAEjB;;;;;;;ACxDA,IAAA,mBAAe,mBAAmB,EAAE,SAAS,gBAAgB,CAAC;;;ACH9D,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAa,YAAY,gBAAgB;CACvC,IAAI;CACJ,SAAS,EACP,IAAI,gBAAyD;EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,GAAGA,gBAAQ,CAAC;CAAG,EAC9G;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-grep",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.56",
|
|
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.
|
|
66
|
-
"@agimon-ai/doompi-hashline": "0.0.1-alpha.
|
|
67
|
-
"@agimon-ai/doompi-ui": "0.0.1-alpha.
|
|
68
|
-
"@agimon-ai/doompi-web-components": "0.0.1-alpha.
|
|
65
|
+
"@agimon-ai/doompi-core": "0.0.1-alpha.84",
|
|
66
|
+
"@agimon-ai/doompi-hashline": "0.0.1-alpha.52",
|
|
67
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.84",
|
|
68
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.42",
|
|
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.
|
|
73
|
+
"@agimon-ai/doompi-build": "0.0.1-alpha.12",
|
|
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",
|