@agimon-ai/po-grep 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.cjs","names":["resolveInputPath","spawn","GrepParamsSchema","stat","readFile","DEFAULT_MAX_BYTES","isWritableFile","resolveInputPath","splitLines","decodeUtf8","formatFileHeader","computeFileTag","formatTaggedLine","resolve","displayPath","basename","defineServerTool","defineServerPlugin","toolGrep"],"sources":["../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\n\nimport { resolveInputPath } from '@agimon-ai/po-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/** Search with ripgrep independently of the host. */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const args = ['--color', 'never', '--no-heading', '--with-filename', '--line-number'];\n if (params.ignoreCase === true) args.push('--ignore-case');\n if (params.literal === true) args.push('--fixed-strings');\n if (params.context !== undefined) args.push('--context', String(params.context));\n if (params.glob !== undefined) args.push('--glob', params.glob);\n args.push('--', params.pattern, target);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/po-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/po-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/po-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+):/gu;\nconst CONTEXT_DELIMITER = /-(\\d+)-/gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): 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/** Node adapters for hashline post-processing. */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath) || candidate.path === searchPath) {\n rows.push({ ...candidate, native: value });\n break;\n }\n continue;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineServerTool } from '@agimon-ai/po-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-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/po-grep',\n session: (context) => ({\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default facet;\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ //#region generated/server.d.ts
2
+ export declare const facet: import("@agimon-ai/po-core/serverFacet").DoomServerFacet;
3
+ //#endregion
4
+ export { facet as default };
5
+ //# sourceMappingURL=server.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../generated/server.ts"],"mappings":";qBAWa,gDAAK"}
@@ -0,0 +1,292 @@
1
+ import { t as GrepParamsSchema } from "../grepTool-D1RuNGXK.mjs";
2
+ import { defineServerPlugin } from "@agimon-ai/po-core/serverFacet";
3
+ import { defineServerTool } from "@agimon-ai/po-core/extensionFile";
4
+ import { readFile, stat } from "node:fs/promises";
5
+ import { basename, resolve } from "node:path";
6
+ import { formatFileHeader, formatTaggedLine, splitLines } from "@agimon-ai/po-hashline";
7
+ import { computeFileTag, decodeUtf8, displayPath, isWritableFile, resolveInputPath } from "@agimon-ai/po-hashline/files";
8
+ import { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
9
+ import { spawn } from "node:child_process";
10
+ //#region src/services/ripgrep/index.ts
11
+ /** Search with ripgrep independently of the host. */
12
+ async function runRipgrep(request) {
13
+ const { params, cwd, signal } = request;
14
+ const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);
15
+ if (output.trim() === "") return { content: [{
16
+ type: "text",
17
+ text: "No matches found"
18
+ }] };
19
+ return { content: [{
20
+ type: "text",
21
+ text: limitMatches(output, params.limit)
22
+ }] };
23
+ }
24
+ /** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */
25
+ function argumentsFor(params, cwd) {
26
+ const target = resolveInputPath(params.path ?? ".", cwd);
27
+ const args = [
28
+ "--color",
29
+ "never",
30
+ "--no-heading",
31
+ "--with-filename",
32
+ "--line-number"
33
+ ];
34
+ if (params.ignoreCase === true) args.push("--ignore-case");
35
+ if (params.literal === true) args.push("--fixed-strings");
36
+ if (params.context !== void 0) args.push("--context", String(params.context));
37
+ if (params.glob !== void 0) args.push("--glob", params.glob);
38
+ args.push("--", params.pattern, target);
39
+ return args;
40
+ }
41
+ /**
42
+ * Keeps at most `limit` matching lines, and every context line around them.
43
+ *
44
+ * A context line carries no `:<line>:` marker, which is what separates it from
45
+ * a match, so trimming by line count alone would drop the context ripgrep was
46
+ * asked for.
47
+ */
48
+ function limitMatches(output, limit) {
49
+ if (limit === void 0) return output.trimEnd();
50
+ let matches = 0;
51
+ const selected = [];
52
+ for (const line of output.trimEnd().split("\n")) {
53
+ if (/:(\d+):/u.test(line)) {
54
+ matches++;
55
+ if (matches > limit) continue;
56
+ }
57
+ selected.push(line);
58
+ }
59
+ return selected.join("\n");
60
+ }
61
+ function spawnRipgrep(args, cwd, signal) {
62
+ return new Promise((resolveOutput, reject) => {
63
+ const child = spawn("rg", args, {
64
+ cwd,
65
+ stdio: [
66
+ "ignore",
67
+ "pipe",
68
+ "pipe"
69
+ ]
70
+ });
71
+ let stdout = "";
72
+ let stderr = "";
73
+ child.stdout.on("data", (chunk) => {
74
+ stdout += chunk.toString();
75
+ });
76
+ child.stderr.on("data", (chunk) => {
77
+ stderr += chunk.toString();
78
+ });
79
+ const abort = () => {
80
+ child.kill("SIGTERM");
81
+ reject(/* @__PURE__ */ new Error("Operation aborted"));
82
+ };
83
+ if (signal?.aborted) {
84
+ abort();
85
+ return;
86
+ }
87
+ signal?.addEventListener("abort", abort, { once: true });
88
+ child.once("error", (error) => {
89
+ signal?.removeEventListener("abort", abort);
90
+ reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));
91
+ });
92
+ child.once("close", (code) => {
93
+ signal?.removeEventListener("abort", abort);
94
+ if (code === 0 || code === 1) {
95
+ resolveOutput(stdout);
96
+ return;
97
+ }
98
+ reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? "unknown"}`));
99
+ });
100
+ });
101
+ }
102
+ //#endregion
103
+ //#region src/services/grepTool/index.ts
104
+ const MATCH_DELIMITER = /:(\d+):/gu;
105
+ const CONTEXT_DELIMITER = /-(\d+)-/gu;
106
+ const NO_MATCHES = "No matches found";
107
+ function createHeadlessGrepTool() {
108
+ return {
109
+ name: "grep",
110
+ label: "grep",
111
+ description: "Search file contents using ripgrep semantics, then add exact-byte file tags and stable line anchors for writable matches. Respects .gitignore.",
112
+ promptSnippet: "Search file contents and return editable hashline anchors",
113
+ parameters: GrepParamsSchema,
114
+ executionMode: "parallel",
115
+ execute: async (_toolCallId, params, signal, _onUpdate, context) => {
116
+ return tagGrepResult(await runRipgrep({
117
+ params,
118
+ cwd: context.cwd,
119
+ signal
120
+ }), params, context.cwd, signal);
121
+ }
122
+ };
123
+ }
124
+ /** Node adapters for hashline post-processing. */
125
+ const nodeGrepFileSystem = {
126
+ stat: (path) => stat(path),
127
+ readFile: (path) => readFile(path)
128
+ };
129
+ const defaultGrepOutput = {
130
+ maxBytes: DEFAULT_MAX_BYTES,
131
+ formatSize,
132
+ truncateHead,
133
+ truncateLine
134
+ };
135
+ async function tagGrepResult(nativeResult, params, cwd, signal, writable = isWritableFile, filesystem = nodeGrepFileSystem, outputOperations = defaultGrepOutput) {
136
+ const textPart = nativeResult.content.find((part) => part.type === "text");
137
+ if (!textPart || textPart.text === NO_MATCHES) return nativeResult;
138
+ const noticeIndex = textPart.text.lastIndexOf("\n\n[");
139
+ const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);
140
+ const notice = noticeIndex === -1 ? "" : textPart.text.slice(noticeIndex);
141
+ const searchPath = resolveInputPath(params.path ?? ".", cwd);
142
+ const searchStats = await filesystem.stat(searchPath);
143
+ assertNotAborted(signal);
144
+ const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);
145
+ if (rows.length === 0) return nativeResult;
146
+ const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);
147
+ const blocks = [];
148
+ let taggedFiles = 0;
149
+ let linesTruncated = nativeResult.details?.linesTruncated ?? false;
150
+ for (const group of groups.values()) {
151
+ assertNotAborted(signal);
152
+ const canEdit = await writable(group.absolutePath);
153
+ assertNotAborted(signal);
154
+ if (!canEdit) {
155
+ blocks.push(group.nativeRows.join("\n"));
156
+ continue;
157
+ }
158
+ let bytes;
159
+ try {
160
+ bytes = await filesystem.readFile(group.absolutePath);
161
+ } catch {
162
+ blocks.push(group.nativeRows.join("\n"));
163
+ continue;
164
+ }
165
+ assertNotAborted(signal);
166
+ const lines = splitLines(decodeUtf8(bytes, group.path));
167
+ const output = [formatFileHeader(group.path, computeFileTag(bytes))];
168
+ for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {
169
+ const line = lines[lineNumber - 1];
170
+ if (line === void 0) continue;
171
+ const compact = outputOperations.truncateLine(line);
172
+ if (compact.wasTruncated) linesTruncated = true;
173
+ output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? ">> " : " ", line));
174
+ }
175
+ if (output.length > 1) {
176
+ blocks.push(output.join("\n"));
177
+ taggedFiles++;
178
+ } else blocks.push(group.nativeRows.join("\n"));
179
+ }
180
+ if (blocks.length === 0 || taggedFiles === 0) return nativeResult;
181
+ const truncation = outputOperations.truncateHead(blocks.join("\n"), { maxLines: Number.MAX_SAFE_INTEGER });
182
+ const extraNotice = truncation.truncated ? `\n\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]` : "";
183
+ const details = {
184
+ ...nativeResult.details,
185
+ ...truncation.truncated ? { truncation } : {},
186
+ ...linesTruncated ? { linesTruncated: true } : {}
187
+ };
188
+ assertNotAborted(signal);
189
+ return {
190
+ ...nativeResult,
191
+ content: [{
192
+ type: "text",
193
+ text: `${truncation.content}${notice}${extraNotice}`
194
+ }],
195
+ details: Object.keys(details).length > 0 ? details : void 0
196
+ };
197
+ }
198
+ function groupGrepRows(rows, searchPath, directory, cwd) {
199
+ const groups = /* @__PURE__ */ new Map();
200
+ for (const row of rows) {
201
+ const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;
202
+ let group = groups.get(absolutePath);
203
+ if (!group) {
204
+ group = {
205
+ path: displayPath(absolutePath, cwd),
206
+ absolutePath,
207
+ nativeRows: [],
208
+ rows: /* @__PURE__ */ new Map()
209
+ };
210
+ groups.set(absolutePath, group);
211
+ }
212
+ if (row.native !== void 0) group.nativeRows.push(row.native);
213
+ group.rows.set(row.line, row.match || group.rows.get(row.line) === true);
214
+ }
215
+ return groups;
216
+ }
217
+ async function parseValidatedGrepRows(rowsText, searchPath, directory, signal, filesystem) {
218
+ const pathCache = /* @__PURE__ */ new Map();
219
+ const rows = [];
220
+ for (const value of rowsText.split("\n")) {
221
+ assertNotAborted(signal);
222
+ let resolvedCandidate;
223
+ for (const candidate of grepRowCandidates(value)) {
224
+ if (!directory) {
225
+ if (candidate.path === basename(searchPath) || candidate.path === searchPath) {
226
+ rows.push({
227
+ ...candidate,
228
+ native: value
229
+ });
230
+ break;
231
+ }
232
+ continue;
233
+ }
234
+ const absolutePath = resolve(searchPath, candidate.path);
235
+ let exists = pathCache.get(absolutePath);
236
+ if (exists === void 0) {
237
+ exists = await filesystem.stat(absolutePath).then((value) => value.isFile()).catch(() => false);
238
+ assertNotAborted(signal);
239
+ pathCache.set(absolutePath, exists);
240
+ }
241
+ if (exists) resolvedCandidate = {
242
+ ...candidate,
243
+ native: value
244
+ };
245
+ }
246
+ if (resolvedCandidate) rows.push(resolvedCandidate);
247
+ }
248
+ return rows;
249
+ }
250
+ function grepRowCandidates(value) {
251
+ const candidates = [];
252
+ for (const match of value.matchAll(MATCH_DELIMITER)) candidates.push({
253
+ index: match.index,
254
+ row: {
255
+ path: value.slice(0, match.index),
256
+ line: Number.parseInt(match[1] ?? "", 10),
257
+ match: true
258
+ }
259
+ });
260
+ for (const match of value.matchAll(CONTEXT_DELIMITER)) candidates.push({
261
+ index: match.index,
262
+ row: {
263
+ path: value.slice(0, match.index),
264
+ line: Number.parseInt(match[1] ?? "", 10),
265
+ match: false
266
+ }
267
+ });
268
+ return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);
269
+ }
270
+ function assertNotAborted(signal) {
271
+ if (signal?.aborted) throw new Error("Operation aborted");
272
+ }
273
+ //#endregion
274
+ //#region src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts
275
+ var grep_server_default = defineServerTool(createHeadlessGrepTool);
276
+ //#endregion
277
+ //#region generated/server.ts
278
+ const at = (value, context) => typeof value === "function" ? value(context) : value;
279
+ const via = (identity, value) => value === void 0 ? void 0 : {
280
+ ...identity,
281
+ ...value
282
+ };
283
+ const facet = defineServerPlugin({
284
+ name: "@agimon-ai/po-grep",
285
+ session: (context) => ({ get tools() {
286
+ return [via({ name: "grep" }, at(grep_server_default, context))];
287
+ } })
288
+ });
289
+ //#endregion
290
+ export { facet as default, facet };
291
+
292
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.mjs","names":["toolGrep"],"sources":["../../src/services/ripgrep/index.ts","../../src/services/grepTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/grep.server.ts","../../generated/server.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\n\nimport { resolveInputPath } from '@agimon-ai/po-hashline/files';\n\nimport type { GrepParams } from '../../schemas/grepTool';\nimport type { RipgrepRequest, RipgrepResult } from './type';\n\n/** Search with ripgrep independently of the host. */\nexport async function runRipgrep(request: RipgrepRequest): Promise<RipgrepResult> {\n const { params, cwd, signal } = request;\n const output = await spawnRipgrep(argumentsFor(params, cwd), cwd, signal);\n if (output.trim() === '') return { content: [{ type: 'text', text: 'No matches found' }] };\n return { content: [{ type: 'text', text: limitMatches(output, params.limit) }] };\n}\n\n/** The ripgrep argv one search needs, exposed so a test can assert it without spawning. */\nexport function argumentsFor(params: GrepParams, cwd: string): string[] {\n const target = resolveInputPath(params.path ?? '.', cwd);\n const args = ['--color', 'never', '--no-heading', '--with-filename', '--line-number'];\n if (params.ignoreCase === true) args.push('--ignore-case');\n if (params.literal === true) args.push('--fixed-strings');\n if (params.context !== undefined) args.push('--context', String(params.context));\n if (params.glob !== undefined) args.push('--glob', params.glob);\n args.push('--', params.pattern, target);\n return args;\n}\n\n/**\n * Keeps at most `limit` matching lines, and every context line around them.\n *\n * A context line carries no `:<line>:` marker, which is what separates it from\n * a match, so trimming by line count alone would drop the context ripgrep was\n * asked for.\n */\nexport function limitMatches(output: string, limit: number | undefined): string {\n if (limit === undefined) return output.trimEnd();\n let matches = 0;\n const selected: string[] = [];\n for (const line of output.trimEnd().split('\\n')) {\n if (/:(\\d+):/u.test(line)) {\n matches++;\n if (matches > limit) continue;\n }\n selected.push(line);\n }\n return selected.join('\\n');\n}\n\nfunction spawnRipgrep(args: readonly string[], cwd: string, signal: AbortSignal | undefined): Promise<string> {\n return new Promise((resolveOutput, reject) => {\n const child = spawn('rg', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n const abort = (): void => {\n child.kill('SIGTERM');\n reject(new Error('Operation aborted'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n child.once('error', (error) => {\n signal?.removeEventListener('abort', abort);\n reject(new Error(`Could not start ripgrep: ${error.message}`, { cause: error }));\n });\n child.once('close', (code) => {\n signal?.removeEventListener('abort', abort);\n // 1 is ripgrep's \"no matches\", which is a result rather than a failure.\n if (code === 0 || code === 1) {\n resolveOutput(stdout);\n return;\n }\n reject(new Error(stderr.trim() || `ripgrep exited with code ${code ?? 'unknown'}`));\n });\n });\n}\n","import { readFile, stat } from 'node:fs/promises';\nimport { basename, resolve } from 'node:path';\n\nimport type {\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/po-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/po-hashline';\nimport {\n computeFileTag,\n decodeUtf8,\n displayPath,\n isWritableFile,\n resolveInputPath,\n} from '@agimon-ai/po-hashline/files';\nimport { DEFAULT_MAX_BYTES, formatSize, truncateHead, truncateLine } from '@earendil-works/pi-coding-agent';\n\nimport { GrepParamsSchema, type GrepParams } from '../../schemas/grepTool';\nimport { runRipgrep } from '../ripgrep';\n\nconst MATCH_DELIMITER = /:(\\d+):/gu;\nconst CONTEXT_DELIMITER = /-(\\d+)-/gu;\nconst NO_MATCHES = 'No matches found';\n\nexport interface GrepRow {\n readonly path: string;\n readonly line: number;\n readonly match: boolean;\n readonly native?: string;\n}\n\nexport interface GrepGroup {\n readonly path: string;\n readonly absolutePath: string;\n readonly nativeRows: string[];\n readonly rows: Map<number, boolean>;\n}\n\nexport interface GrepResult {\n content: Array<{ type: 'text'; text: string }>;\n details?: {\n readonly linesTruncated?: boolean;\n readonly truncation?: GrepTruncation;\n readonly [key: string]: unknown;\n };\n}\n\nexport interface GrepFileStats {\n isDirectory(): boolean;\n isFile(): boolean;\n}\n\nexport interface GrepFileSystem {\n stat(path: string): Promise<GrepFileStats>;\n readFile(path: string): Promise<Uint8Array>;\n}\n\nexport interface GrepTruncation {\n readonly content: string;\n readonly truncated: boolean;\n readonly truncatedBy: 'lines' | 'bytes' | null;\n readonly outputLines: number;\n}\n\nexport interface GrepOutputOperations {\n readonly maxBytes: number;\n formatSize(bytes: number): string;\n truncateHead(content: string, options: { readonly maxLines?: number }): GrepTruncation;\n truncateLine(line: string): { readonly text: string; readonly wasTruncated: boolean };\n}\nexport function createHeadlessGrepTool(): 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/** Node adapters for hashline post-processing. */\nexport const nodeGrepFileSystem: GrepFileSystem = {\n stat: (path) => stat(path),\n readFile: (path) => readFile(path),\n};\n\nexport const defaultGrepOutput: GrepOutputOperations = {\n maxBytes: DEFAULT_MAX_BYTES,\n formatSize,\n truncateHead,\n truncateLine,\n};\n\nexport async function tagGrepResult(\n nativeResult: GrepResult,\n params: GrepParams,\n cwd: string,\n signal: AbortSignal | undefined,\n writable: (path: string) => Promise<boolean> = isWritableFile,\n filesystem: GrepFileSystem = nodeGrepFileSystem,\n outputOperations: GrepOutputOperations = defaultGrepOutput,\n): Promise<GrepResult> {\n const textPart = nativeResult.content.find((part) => part.type === 'text');\n if (!textPart || textPart.text === NO_MATCHES) return nativeResult;\n\n const noticeIndex = textPart.text.lastIndexOf('\\n\\n[');\n const rowsText = noticeIndex === -1 ? textPart.text : textPart.text.slice(0, noticeIndex);\n const notice = noticeIndex === -1 ? '' : textPart.text.slice(noticeIndex);\n const searchPath = resolveInputPath(params.path ?? '.', cwd);\n const searchStats = await filesystem.stat(searchPath);\n assertNotAborted(signal);\n const rows = await parseValidatedGrepRows(rowsText, searchPath, searchStats.isDirectory(), signal, filesystem);\n if (rows.length === 0) return nativeResult;\n\n const groups = groupGrepRows(rows, searchPath, searchStats.isDirectory(), cwd);\n const blocks: string[] = [];\n let taggedFiles = 0;\n let linesTruncated = nativeResult.details?.linesTruncated ?? false;\n\n for (const group of groups.values()) {\n assertNotAborted(signal);\n const canEdit = await writable(group.absolutePath);\n assertNotAborted(signal);\n if (!canEdit) {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n let bytes: Uint8Array;\n try {\n bytes = await filesystem.readFile(group.absolutePath);\n } catch {\n blocks.push(group.nativeRows.join('\\n'));\n continue;\n }\n assertNotAborted(signal);\n const lines = splitLines(decodeUtf8(bytes, group.path));\n const output = [formatFileHeader(group.path, computeFileTag(bytes))];\n for (const [lineNumber, isMatch] of [...group.rows].sort(([left], [right]) => left - right)) {\n const line = lines[lineNumber - 1];\n if (line === undefined) continue;\n const compact = outputOperations.truncateLine(line);\n if (compact.wasTruncated) linesTruncated = true;\n output.push(formatTaggedLine(compact.text, lineNumber, isMatch ? '>> ' : ' ', line));\n }\n if (output.length > 1) {\n blocks.push(output.join('\\n'));\n taggedFiles++;\n } else {\n blocks.push(group.nativeRows.join('\\n'));\n }\n }\n\n if (blocks.length === 0 || taggedFiles === 0) return nativeResult;\n const truncation = outputOperations.truncateHead(blocks.join('\\n'), { maxLines: Number.MAX_SAFE_INTEGER });\n const extraNotice = truncation.truncated\n ? `\\n\\n[${outputOperations.formatSize(outputOperations.maxBytes)} tagged output limit reached]`\n : '';\n const details = {\n ...nativeResult.details,\n ...(truncation.truncated ? { truncation } : {}),\n ...(linesTruncated ? { linesTruncated: true } : {}),\n };\n assertNotAborted(signal);\n return {\n ...nativeResult,\n content: [{ type: 'text', text: `${truncation.content}${notice}${extraNotice}` }],\n details: Object.keys(details).length > 0 ? details : undefined,\n };\n}\n\nexport function groupGrepRows(\n rows: readonly GrepRow[],\n searchPath: string,\n directory: boolean,\n cwd: string,\n): Map<string, GrepGroup> {\n const groups = new Map<string, GrepGroup>();\n for (const row of rows) {\n const absolutePath = directory ? resolve(searchPath, row.path) : searchPath;\n let group = groups.get(absolutePath);\n if (!group) {\n group = { path: displayPath(absolutePath, cwd), absolutePath, nativeRows: [], rows: new Map() };\n groups.set(absolutePath, group);\n }\n if (row.native !== undefined) group.nativeRows.push(row.native);\n group.rows.set(row.line, row.match || group.rows.get(row.line) === true);\n }\n return groups;\n}\n\nasync function parseValidatedGrepRows(\n rowsText: string,\n searchPath: string,\n directory: boolean,\n signal: AbortSignal | undefined,\n filesystem: GrepFileSystem,\n): Promise<GrepRow[]> {\n const pathCache = new Map<string, boolean>();\n const rows: GrepRow[] = [];\n for (const value of rowsText.split('\\n')) {\n assertNotAborted(signal);\n let resolvedCandidate: GrepRow | undefined;\n for (const candidate of grepRowCandidates(value)) {\n if (!directory) {\n if (candidate.path === basename(searchPath) || candidate.path === searchPath) {\n rows.push({ ...candidate, native: value });\n break;\n }\n continue;\n }\n const absolutePath = resolve(searchPath, candidate.path);\n let exists = pathCache.get(absolutePath);\n if (exists === undefined) {\n exists = await filesystem\n .stat(absolutePath)\n .then((value) => value.isFile())\n .catch(() => false);\n assertNotAborted(signal);\n pathCache.set(absolutePath, exists);\n }\n if (exists) resolvedCandidate = { ...candidate, native: value };\n }\n if (resolvedCandidate) rows.push(resolvedCandidate);\n }\n return rows;\n}\n\nfunction grepRowCandidates(value: string): GrepRow[] {\n const candidates: Array<{ readonly index: number; readonly row: GrepRow }> = [];\n for (const match of value.matchAll(MATCH_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: true },\n });\n }\n for (const match of value.matchAll(CONTEXT_DELIMITER)) {\n candidates.push({\n index: match.index,\n row: { path: value.slice(0, match.index), line: Number.parseInt(match[1] ?? '', 10), match: false },\n });\n }\n return candidates.sort((left, right) => left.index - right.index).map(({ row }) => row);\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) throw new Error('Operation aborted');\n}\n","import { defineServerTool } from '@agimon-ai/po-core/extensionFile';\n\nimport { createHeadlessGrepTool } from '../../../../../services/grepTool';\n\nexport default defineServerTool(createHeadlessGrepTool);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/po-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/po-grep',\n session: (context) => ({\n get tools(): DoomServerSessionPlugin['tools'] { return [via({ name: 'grep' }, at(toolGrep, context))]; },\n }),\n});\n\nexport default facet;\n"],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import { defineToolRenderer, defineWebPlugin } from "@agimon-ai/po-core/web";
2
+ import { HashlineLines, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, compactDetails, hashlineBody, resultTextLines, toolTone } from "@agimon-ai/po-web-components";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/grepToolView.ts
5
+ /** What the grep call heading shows: the pattern, then where and how it searched. */
6
+ function grepCallView(args) {
7
+ const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : void 0;
8
+ return {
9
+ pattern: typeof args.pattern === "string" ? args.pattern : "",
10
+ details: compactDetails([
11
+ typeof args.path === "string" && args.path.length > 0 ? args.path : ".",
12
+ typeof args.glob === "string" ? args.glob : void 0,
13
+ args.ignoreCase === true ? "ignore case" : void 0,
14
+ limit === void 0 ? void 0 : `${limit} matches`
15
+ ])
16
+ };
17
+ }
18
+ //#endregion
19
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/GrepToolMessage.tsx
20
+ /**
21
+ * The grep tool's timeline item, the web half of its renderCall and
22
+ * renderResult: the call summary in the header and, when expanded, the
23
+ * anchored matches with a line-number gutter in the body. A failure shows the
24
+ * tool's message in red inside the expanded body instead.
25
+ */
26
+ function GrepToolMessage({ args, result, output, running, isError }) {
27
+ const view = grepCallView(args);
28
+ const body = result === null || isError ? null : hashlineBody(result, output, "grep", true);
29
+ return /* @__PURE__ */ jsx(MessageItem, {
30
+ tone: toolTone({
31
+ running,
32
+ isError
33
+ }),
34
+ expandable: true,
35
+ defaultExpanded: false,
36
+ children: ({ expanded }) => {
37
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
38
+ title: "grep",
39
+ children: /* @__PURE__ */ jsxs("span", {
40
+ "data-testid": "tool-call-grep",
41
+ className: "flex min-w-0 flex-1 items-center gap-2",
42
+ children: [/* @__PURE__ */ jsx("span", {
43
+ className: "truncate text-doom-text",
44
+ children: view.pattern
45
+ }), view.details.length > 0 ? /* @__PURE__ */ jsxs("span", {
46
+ className: "shrink-0 text-doom-faint",
47
+ children: ["· ", view.details.join(" · ")]
48
+ }) : null]
49
+ })
50
+ }), expanded ? isError ? /* @__PURE__ */ jsxs(MessageItemBody, {
51
+ "data-testid": "tool-result-grep",
52
+ className: "flex flex-col",
53
+ children: [/* @__PURE__ */ jsx(MessageItemStatus, {
54
+ tone: "error",
55
+ children: "failed"
56
+ }), resultTextLines(result, output).map((line, index) => /* @__PURE__ */ jsx("span", {
57
+ className: "whitespace-pre-wrap break-words text-doom-red",
58
+ children: line
59
+ }, `${String(index)}-${line}`))]
60
+ }) : body === null ? running ? /* @__PURE__ */ jsx(MessageItemBody, {
61
+ "data-testid": "tool-result-grep",
62
+ children: /* @__PURE__ */ jsx(MessageItemStatus, {
63
+ tone: "running",
64
+ children: "running"
65
+ })
66
+ }) : null : body.shown.length === 0 && body.notice === void 0 ? null : /* @__PURE__ */ jsxs(MessageItemBody, {
67
+ "data-testid": "tool-result-grep",
68
+ className: "flex flex-col gap-1",
69
+ children: [/* @__PURE__ */ jsx(HashlineLines, {
70
+ lines: body.shown,
71
+ gutter: body.gutter
72
+ }), body.notice !== void 0 ? /* @__PURE__ */ jsx("span", {
73
+ className: "text-doom-faint",
74
+ children: body.notice
75
+ }) : null]
76
+ }) : null] });
77
+ }
78
+ });
79
+ }
80
+ //#endregion
81
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/grep.web.tsx
82
+ /**
83
+ * The grep tool's timeline card, the web half of the TUI's renderCall and
84
+ * renderResult for hashline searches. The filename binds it to the tool.
85
+ */
86
+ var grep_web_default = defineToolRenderer({ message: GrepToolMessage });
87
+ //#endregion
88
+ //#region generated/web.ts
89
+ const via = (identity, value) => value === void 0 ? void 0 : {
90
+ ...identity,
91
+ ...value
92
+ };
93
+ const webPlugin = defineWebPlugin({
94
+ id: "grep",
95
+ session: { get toolRenderers() {
96
+ return [via({ tools: ["grep"] }, grep_web_default)];
97
+ } }
98
+ });
99
+ //#endregion
100
+ export { webPlugin };
101
+
102
+ //# sourceMappingURL=web.mjs.map
@@ -0,0 +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/po-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/po-core/web';\nimport {\n HashlineLines,\n hashlineBody,\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n resultTextLines,\n toolTone,\n} from '@agimon-ai/po-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/po-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/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-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"}
@@ -0,0 +1,26 @@
1
+ let typebox = require("typebox");
2
+ //#region src/schemas/grepTool.ts
3
+ const GrepParamsSchema = typebox.Type.Object({
4
+ pattern: typebox.Type.String({ description: "Regular expression or literal text to find." }),
5
+ path: typebox.Type.Optional(typebox.Type.String({ description: "File or directory to search. Defaults to the working directory." })),
6
+ glob: typebox.Type.Optional(typebox.Type.String({ description: "Glob filter for files to search." })),
7
+ ignoreCase: typebox.Type.Optional(typebox.Type.Boolean({ description: "Match without case sensitivity." })),
8
+ literal: typebox.Type.Optional(typebox.Type.Boolean({ description: "Treat pattern as literal text." })),
9
+ context: typebox.Type.Optional(typebox.Type.Integer({
10
+ minimum: 0,
11
+ description: "Context lines around every match."
12
+ })),
13
+ limit: typebox.Type.Optional(typebox.Type.Integer({
14
+ minimum: 1,
15
+ description: "Maximum number of matches."
16
+ }))
17
+ });
18
+ //#endregion
19
+ Object.defineProperty(exports, "GrepParamsSchema", {
20
+ enumerable: true,
21
+ get: function() {
22
+ return GrepParamsSchema;
23
+ }
24
+ });
25
+
26
+ //# sourceMappingURL=grepTool-C2GQiJl4.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grepTool-C2GQiJl4.cjs","names":["Type"],"sources":["../src/schemas/grepTool.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"],"mappings":";;AAEA,MAAa,mBAAmBA,QAAAA,KAAK,OAAO;CAC1C,SAASA,QAAAA,KAAK,OAAO,EAAE,aAAa,8CAA8C,CAAC;CACnF,MAAMA,QAAAA,KAAK,SAASA,QAAAA,KAAK,OAAO,EAAE,aAAa,kEAAkE,CAAC,CAAC;CACnH,MAAMA,QAAAA,KAAK,SAASA,QAAAA,KAAK,OAAO,EAAE,aAAa,mCAAmC,CAAC,CAAC;CACpF,YAAYA,QAAAA,KAAK,SAASA,QAAAA,KAAK,QAAQ,EAAE,aAAa,kCAAkC,CAAC,CAAC;CAC1F,SAASA,QAAAA,KAAK,SAASA,QAAAA,KAAK,QAAQ,EAAE,aAAa,iCAAiC,CAAC,CAAC;CACtF,SAASA,QAAAA,KAAK,SAASA,QAAAA,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAAoC,CAAC,CAAC;CACrG,OAAOA,QAAAA,KAAK,SAASA,QAAAA,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAA6B,CAAC,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { Type } from "typebox";
2
+ //#region src/schemas/grepTool.ts
3
+ const GrepParamsSchema = Type.Object({
4
+ pattern: Type.String({ description: "Regular expression or literal text to find." }),
5
+ path: Type.Optional(Type.String({ description: "File or directory to search. Defaults to the working directory." })),
6
+ glob: Type.Optional(Type.String({ description: "Glob filter for files to search." })),
7
+ ignoreCase: Type.Optional(Type.Boolean({ description: "Match without case sensitivity." })),
8
+ literal: Type.Optional(Type.Boolean({ description: "Treat pattern as literal text." })),
9
+ context: Type.Optional(Type.Integer({
10
+ minimum: 0,
11
+ description: "Context lines around every match."
12
+ })),
13
+ limit: Type.Optional(Type.Integer({
14
+ minimum: 1,
15
+ description: "Maximum number of matches."
16
+ }))
17
+ });
18
+ //#endregion
19
+ export { GrepParamsSchema as t };
20
+
21
+ //# sourceMappingURL=grepTool-D1RuNGXK.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grepTool-D1RuNGXK.mjs","names":[],"sources":["../src/schemas/grepTool.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"],"mappings":";;AAEA,MAAa,mBAAmB,KAAK,OAAO;CAC1C,SAAS,KAAK,OAAO,EAAE,aAAa,8CAA8C,CAAC;CACnF,MAAM,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,kEAAkE,CAAC,CAAC;CACnH,MAAM,KAAK,SAAS,KAAK,OAAO,EAAE,aAAa,mCAAmC,CAAC,CAAC;CACpF,YAAY,KAAK,SAAS,KAAK,QAAQ,EAAE,aAAa,kCAAkC,CAAC,CAAC;CAC1F,SAAS,KAAK,SAAS,KAAK,QAAQ,EAAE,aAAa,iCAAiC,CAAC,CAAC;CACtF,SAAS,KAAK,SAAS,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAAoC,CAAC,CAAC;CACrG,OAAO,KAAK,SAAS,KAAK,QAAQ;EAAE,SAAS;EAAG,aAAa;CAA6B,CAAC,CAAC;AAC9F,CAAC"}
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_grepTool = require("./grepTool-C2GQiJl4.cjs");
3
+ exports.GrepParamsSchema = require_grepTool.GrepParamsSchema;
@@ -0,0 +1,15 @@
1
+ import { Static, Type } from "typebox";
2
+ //#region src/schemas/grepTool.d.ts
3
+ export declare const GrepParamsSchema: Type.TObject<{
4
+ pattern: Type.TString;
5
+ path: Type.TOptional<Type.TString>;
6
+ glob: Type.TOptional<Type.TString>;
7
+ ignoreCase: Type.TOptional<Type.TBoolean>;
8
+ literal: Type.TOptional<Type.TBoolean>;
9
+ context: Type.TOptional<Type.TInteger>;
10
+ limit: Type.TOptional<Type.TInteger>;
11
+ }>;
12
+ type GrepParams = Static<typeof GrepParamsSchema>;
13
+ //#endregion
14
+ export type { GrepParams };
15
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/schemas/grepTool.ts"],"mappings":";;qBAEa,kBAAgB,KAAA;;;;;;;;;KAUjB,aAAa,cAAc"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { t as GrepParamsSchema } from "./grepTool-D1RuNGXK.mjs";
2
+ export { GrepParamsSchema };