langflower 0.0.6 → 0.0.8

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.
@@ -407,19 +407,42 @@ const invokeTool = (state, call, options, cancelSignal) => {
407
407
  ? `Error: Sub-Agent ${payload.callId} timed out after ${options.recovery.subagentTimeoutMs}ms.`
408
408
  : `Error: ${classifyLlmFailure(error).message}`)), mergeMap((result) => toolResultPackets(state, call, result, options, []))));
409
409
  }
410
- const invocation$ = defer(() => invokeInventoryTool(options.harness, options.inventoryTools, call, options.toolCtx, options.policy.toolNotAllowedText === undefined
411
- ? undefined
412
- : {
413
- notInAllowlistText: options.policy.toolNotAllowedText,
414
- }));
410
+ const toolAbort = new AbortController();
411
+ const abortTool = () => {
412
+ toolAbort.abort();
413
+ };
414
+ cancelSignal.addEventListener('abort', abortTool);
415
+ const invokeOptions = {
416
+ ...(options.policy.toolNotAllowedText === undefined
417
+ ? {}
418
+ : {
419
+ notInAllowlistText: options.policy.toolNotAllowedText,
420
+ }),
421
+ signal: toolAbort.signal,
422
+ };
423
+ const invocation$ = defer(() => invokeInventoryTool(options.harness, options.inventoryTools, call, options.toolCtx, invokeOptions).catch((error) => {
424
+ // Avoid unhandled rejection when timeout/cancel aborts in-flight work.
425
+ if (toolAbort.signal.aborted) {
426
+ return {
427
+ ok: false,
428
+ text: 'Tool aborted.',
429
+ };
430
+ }
431
+ throw error;
432
+ })).pipe(finalize(() => {
433
+ cancelSignal.removeEventListener('abort', abortTool);
434
+ }));
415
435
  const boundedInvocation$ = options.recovery.toolTimeoutMs > 0
416
436
  ? invocation$.pipe(timeout({
417
437
  first: options.recovery.toolTimeoutMs,
418
438
  }))
419
439
  : invocation$;
420
- return concat(of(callLog), boundedInvocation$.pipe(map((result) => result.ok ? result.text : `Error: ${result.text}`), catchError((error) => of(error instanceof TimeoutError
421
- ? `Error: Tool ${call.name} timed out after ${options.recovery.toolTimeoutMs}ms.`
422
- : `Error: ${classifyLlmFailure(error).message}`)), mergeMap((result) => toolResultPackets(state, call, result, options, []))));
440
+ return concat(of(callLog), boundedInvocation$.pipe(map((result) => result.ok ? result.text : `Error: ${result.text}`), catchError((error) => {
441
+ abortTool();
442
+ return of(error instanceof TimeoutError
443
+ ? `Error: Tool ${call.name} timed out after ${options.recovery.toolTimeoutMs}ms.`
444
+ : `Error: ${classifyLlmFailure(error).message}`);
445
+ }), mergeMap((result) => toolResultPackets(state, call, result, options, []))));
423
446
  };
424
447
  const toolResultPackets = (state, call, result, options, prefix) => {
425
448
  const normalized = normalizeToolResult(result, options.recovery.maxToolResultChars);
@@ -15,6 +15,7 @@ export declare const parseToolArgs: (raw: string) => Readonly<Record<string, unk
15
15
  */
16
16
  export declare const invokeInventoryTool: (harness: Harness | undefined, tools: readonly ToolHandle[], call: ChatCompletionToolCall, toolCtx: ToolHandlerContext | undefined, options?: {
17
17
  readonly notInAllowlistText?: (toolName: string) => string;
18
+ readonly signal?: AbortSignal;
18
19
  }) => Promise<{
19
20
  readonly ok: boolean;
20
21
  readonly text: string;
@@ -60,8 +60,11 @@ export const invokeInventoryTool = async (harness, tools, call, toolCtx, options
60
60
  }
61
61
  const args = parseToolArgs(call.arguments);
62
62
  const toolId = handle.toolId.length > 0 ? handle.toolId : handle.name;
63
+ const ctx = options?.signal === undefined
64
+ ? toolCtx
65
+ : { ...toolCtx, signal: options.signal };
63
66
  if (isBuiltinToolId(toolId)) {
64
- const authorize = toolCtx.authorize ?? harness?.authorize;
67
+ const authorize = ctx.authorize ?? harness?.authorize;
65
68
  if (authorize === undefined) {
66
69
  return {
67
70
  ok: false,
@@ -77,7 +80,7 @@ export const invokeInventoryTool = async (harness, tools, call, toolCtx, options
77
80
  }
78
81
  }
79
82
  try {
80
- const text = await handle.invoke(args, toolCtx);
83
+ const text = await handle.invoke(args, ctx);
81
84
  return { ok: true, text };
82
85
  }
83
86
  catch (error) {
@@ -8,6 +8,30 @@ import { applyDraftPatch, buildDraftSnapshot, providerIndexesWithBaseUrl, seedSc
8
8
  import { bridgeEmit, clientEmit } from './bridge-outbound.js';
9
9
  import { buildLangflowerConfigSnapshot } from './build-langflower-config-snapshot.js';
10
10
  const isScope = (value) => value === 'project' || value === 'global';
11
+ /** Fill missing/empty Save keys from the session draft (write-only apiKey). */
12
+ const mergeProviderApiKeysFromSession = (payload, sessionDraft) => {
13
+ if (payload.provider === undefined || sessionDraft === undefined) {
14
+ return payload.providerApiKeys;
15
+ }
16
+ const merged = {
17
+ ...(payload.providerApiKeys ?? {}),
18
+ };
19
+ for (const row of sessionDraft.providers) {
20
+ const id = row.id.trim();
21
+ if (id.length === 0) {
22
+ continue;
23
+ }
24
+ const fromPayload = merged[id]?.trim() ?? '';
25
+ if (fromPayload.length > 0) {
26
+ continue;
27
+ }
28
+ const pending = row.apiKey.trim();
29
+ if (pending.length > 0) {
30
+ merged[id] = pending;
31
+ }
32
+ }
33
+ return Object.keys(merged).length > 0 ? merged : payload.providerApiKeys;
34
+ };
11
35
  const layerForScope = (layers, scope) => (scope === 'project' ? layers.project : layers.global);
12
36
  export const createSettingsDraftController = (bridge, context, session) => {
13
37
  const probeGeneration = new Map();
@@ -144,15 +168,14 @@ export const createSettingsDraftController = (bridge, context, session) => {
144
168
  if (payload === undefined || !isScope(payload.scope)) {
145
169
  return null;
146
170
  }
171
+ const providerApiKeys = mergeProviderApiKeysFromSession(payload, state?.draft);
147
172
  const layers = await context.langflowerConfigService.writeSettings({
148
173
  scope: payload.scope,
149
174
  ...(payload.model !== undefined ? { model: payload.model } : {}),
150
175
  ...(payload.provider !== undefined
151
176
  ? { provider: payload.provider }
152
177
  : {}),
153
- ...(payload.providerApiKeys !== undefined
154
- ? { providerApiKeys: payload.providerApiKeys }
155
- : {}),
178
+ ...(providerApiKeys !== undefined ? { providerApiKeys } : {}),
156
179
  ...('serverLogs' in payload
157
180
  ? { serverLogs: payload.serverLogs }
158
181
  : {}),
@@ -23,10 +23,15 @@ export const wrapBuiltinToolHandles = (harness, enabledToolIds, permission) => {
23
23
  name: reg.name,
24
24
  description: reg.description,
25
25
  inputSchema: reg.inputSchema,
26
- invoke: async (args) => {
26
+ invoke: async (args, toolCtx) => {
27
+ // Shell passes tools host bag (optional signal); SDK type stays identity-only.
28
+ const hostCtx = toolCtx;
27
29
  const result = await harness.invoke({
28
30
  toolId: reg.toolId,
29
31
  args,
32
+ ...(hostCtx?.signal !== undefined
33
+ ? { signal: hostCtx.signal }
34
+ : {}),
30
35
  });
31
36
  if (!result.ok) {
32
37
  throw new Error(result.text);
@@ -55,7 +55,17 @@
55
55
  },
56
56
  "options": {
57
57
  "type": "object",
58
- "additionalProperties": true
58
+ "additionalProperties": true,
59
+ "properties": {
60
+ "baseURL": {
61
+ "type": "string",
62
+ "description": "OpenAI-compatible API base URL (e.g. https://api.openai.com/v1 or http://127.0.0.1:1234/v1)."
63
+ },
64
+ "apiKey": {
65
+ "type": "string",
66
+ "description": "API key or `{env:VAR_NAME}` placeholder. Resolved on the server only; omitted from WebSocket snapshots. Prefer `{env:…}` over literals on disk."
67
+ }
68
+ }
59
69
  }
60
70
  }
61
71
  }
@@ -75,13 +75,13 @@ export declare const BUILTIN_TOOLS: readonly [{
75
75
  readonly registration: {
76
76
  readonly toolId: "grep";
77
77
  readonly name: "grep";
78
- readonly description: "Regex search across project files (gitignore-aware by default; Node walk not ripgrep). Optional postProcess.";
78
+ readonly description: "Regex search across project files (gitignore-aware by default; ripgrep when available, else grep, else bounded Node walk). Optional postProcess.";
79
79
  readonly inputSchema: {
80
80
  readonly type: "object";
81
81
  readonly properties: {
82
82
  readonly pattern: {
83
83
  readonly type: "string";
84
- readonly description: "JavaScript RegExp source";
84
+ readonly description: "Regex pattern (ripgrep dialect when rg is available; JavaScript RegExp on Node fallback)";
85
85
  };
86
86
  readonly path: {
87
87
  readonly type: "string";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * True when `cmd` resolves on PATH (`where` on Windows, `which` elsewhere).
3
+ * Result is cached per process.
4
+ */
5
+ export declare const commandExists: (cmd: string) => Promise<boolean>;
@@ -0,0 +1,27 @@
1
+ import { spawn } from 'node:child_process';
2
+ const cache = new Map();
3
+ /**
4
+ * True when `cmd` resolves on PATH (`where` on Windows, `which` elsewhere).
5
+ * Result is cached per process.
6
+ */
7
+ export const commandExists = async (cmd) => {
8
+ const cached = cache.get(cmd);
9
+ if (cached !== undefined) {
10
+ return cached;
11
+ }
12
+ const checkCmd = process.platform === 'win32' ? 'where' : 'which';
13
+ const exists = await new Promise((resolve) => {
14
+ const child = spawn(checkCmd, [cmd], {
15
+ stdio: 'ignore',
16
+ windowsHide: true,
17
+ });
18
+ child.on('error', () => {
19
+ resolve(false);
20
+ });
21
+ child.on('close', (code) => {
22
+ resolve(code === 0);
23
+ });
24
+ });
25
+ cache.set(cmd, exists);
26
+ return exists;
27
+ };
@@ -0,0 +1,43 @@
1
+ import { type SpawnCaptureOptions, type SpawnCaptureResult } from '../spawn-capture.js';
2
+ export declare const MAX_GREP_MATCHES = 100;
3
+ type GrepHit = {
4
+ readonly file: string;
5
+ readonly line: number;
6
+ readonly text: string;
7
+ };
8
+ export type GrepSearchInput = {
9
+ readonly pattern: string;
10
+ readonly caseInsensitive: boolean;
11
+ readonly respectGitignore: boolean;
12
+ /** Absolute search file or directory (already fenced). */
13
+ readonly searchAbsolute: string;
14
+ /** Fence root for relative display paths. */
15
+ readonly fenceRoot: string;
16
+ readonly displayPath: (absolute: string) => string;
17
+ readonly signal?: AbortSignal;
18
+ };
19
+ export type GrepSearchDeps = {
20
+ readonly commandExists?: (cmd: string) => Promise<boolean>;
21
+ readonly spawnCapture?: (command: string, args: readonly string[], options?: SpawnCaptureOptions) => Promise<SpawnCaptureResult>;
22
+ };
23
+ type GrepSearchOutcome = {
24
+ readonly ok: true;
25
+ readonly hits: readonly GrepHit[];
26
+ readonly truncated: boolean;
27
+ readonly backend: 'rg' | 'grep' | 'node';
28
+ } | {
29
+ readonly ok: false;
30
+ readonly reason: 'unavailable' | 'error';
31
+ readonly message: string;
32
+ };
33
+ export declare const searchWithNodeWalk: (input: GrepSearchInput) => Promise<GrepSearchOutcome>;
34
+ /**
35
+ * ts-scan-style cascade: rg → grep → bounded Node walk.
36
+ * Soft-falls through when a CLI binary is missing or fails to spawn;
37
+ * Node tier surfaces invalid JS RegExp errors.
38
+ */
39
+ export declare const runGrepCascade: (input: GrepSearchInput, deps?: GrepSearchDeps) => Promise<{
40
+ readonly body: string;
41
+ readonly backend: "rg" | "grep" | "node";
42
+ }>;
43
+ export {};
@@ -0,0 +1,315 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { commandExists } from '../command-exists.js';
4
+ import { spawnCapture, } from '../spawn-capture.js';
5
+ import { WALK_EXCLUDE_DIR_NAMES, walkFiles } from '../walk-files.js';
6
+ export const MAX_GREP_MATCHES = 100;
7
+ const MAX_GREP_FILES_SCANNED = 5_000;
8
+ const MAX_GREP_FILE_BYTES = 1_048_576;
9
+ const MAX_GREP_LINE_CHARS = 8_000;
10
+ const SEARCH_EXCLUDE_GLOBS = [
11
+ '!**/node_modules/**',
12
+ '!**/dist/**',
13
+ '!**/build/**',
14
+ '!**/.git/**',
15
+ ];
16
+ const throwIfAborted = (signal) => {
17
+ if (signal?.aborted) {
18
+ throw new Error('aborted');
19
+ }
20
+ };
21
+ const formatHits = (hits, truncated) => {
22
+ if (hits.length === 0) {
23
+ return '(no matches)';
24
+ }
25
+ const body = hits
26
+ .map((hit) => `${hit.file}:${hit.line}:${hit.text}`)
27
+ .join('\n');
28
+ return truncated
29
+ ? `${body}\n…[truncated at ${MAX_GREP_MATCHES}; refine pattern or path]`
30
+ : body;
31
+ };
32
+ const formatGrepBody = (hits, truncated) => formatHits(hits, truncated);
33
+ const parseLineHits = (stdout, displayPath, fenceRoot, maxHits) => {
34
+ const hits = [];
35
+ const lines = stdout.split(/\r?\n/);
36
+ for (const raw of lines) {
37
+ if (raw.length === 0) {
38
+ continue;
39
+ }
40
+ const match = /^(.+?):(\d+):(.*)$/.exec(raw);
41
+ if (match === null) {
42
+ continue;
43
+ }
44
+ const fileAbs = path.isAbsolute(match[1] ?? '')
45
+ ? (match[1] ?? '')
46
+ : path.join(fenceRoot, match[1] ?? '');
47
+ const line = Number(match[2]);
48
+ const text = match[3] ?? '';
49
+ if (!Number.isFinite(line)) {
50
+ continue;
51
+ }
52
+ hits.push({
53
+ file: displayPath(fileAbs),
54
+ line,
55
+ text: text.length > MAX_GREP_LINE_CHARS
56
+ ? `${text.slice(0, MAX_GREP_LINE_CHARS)}…`
57
+ : text,
58
+ });
59
+ if (hits.length >= maxHits) {
60
+ return { hits, truncated: true };
61
+ }
62
+ }
63
+ return { hits, truncated: false };
64
+ };
65
+ const isBinaryAbortError = (error) => error instanceof Error &&
66
+ (error.name === 'AbortError' || error.message === 'aborted');
67
+ const searchWithRipgrep = async (input, deps = {}) => {
68
+ const exists = deps.commandExists ?? commandExists;
69
+ const run = deps.spawnCapture ?? spawnCapture;
70
+ if (!(await exists('rg'))) {
71
+ return {
72
+ ok: false,
73
+ reason: 'unavailable',
74
+ message: 'ripgrep (rg) is not available on this system',
75
+ };
76
+ }
77
+ throwIfAborted(input.signal);
78
+ const args = [
79
+ '-n',
80
+ '--color',
81
+ 'never',
82
+ '--max-filesize',
83
+ `${MAX_GREP_FILE_BYTES}`,
84
+ ...(input.respectGitignore ? [] : ['--no-ignore']),
85
+ ...SEARCH_EXCLUDE_GLOBS.flatMap((glob) => ['--glob', glob]),
86
+ ...(input.caseInsensitive ? ['-i'] : []),
87
+ '-e',
88
+ input.pattern,
89
+ input.searchAbsolute,
90
+ ];
91
+ try {
92
+ const result = await run('rg', args, {
93
+ cwd: input.fenceRoot,
94
+ ...(input.signal !== undefined ? { signal: input.signal } : {}),
95
+ });
96
+ // rg: 0 = matches, 1 = no matches, 2 = error
97
+ if (result.code !== null && result.code >= 2) {
98
+ const detail = result.stderr.trim();
99
+ return {
100
+ ok: false,
101
+ reason: 'error',
102
+ message: detail.length > 0
103
+ ? `ripgrep failed: ${detail}`
104
+ : 'ripgrep failed',
105
+ };
106
+ }
107
+ const parsed = parseLineHits(result.stdout, input.displayPath, input.fenceRoot, MAX_GREP_MATCHES);
108
+ return {
109
+ ok: true,
110
+ hits: parsed.hits,
111
+ truncated: parsed.truncated,
112
+ backend: 'rg',
113
+ };
114
+ }
115
+ catch (error) {
116
+ if (isBinaryAbortError(error)) {
117
+ throw new Error('aborted');
118
+ }
119
+ return {
120
+ ok: false,
121
+ reason: 'unavailable',
122
+ message: error instanceof Error
123
+ ? error.message
124
+ : 'Error executing ripgrep',
125
+ };
126
+ }
127
+ };
128
+ const searchWithGrep = async (input, deps = {}) => {
129
+ const exists = deps.commandExists ?? commandExists;
130
+ const run = deps.spawnCapture ?? spawnCapture;
131
+ if (!(await exists('grep'))) {
132
+ return {
133
+ ok: false,
134
+ reason: 'unavailable',
135
+ message: 'grep is not available on this system',
136
+ };
137
+ }
138
+ throwIfAborted(input.signal);
139
+ const args = [
140
+ '-r',
141
+ '-n',
142
+ '-E',
143
+ '-I',
144
+ ...(input.caseInsensitive ? ['-i'] : []),
145
+ ...WALK_EXCLUDE_DIR_NAMES.flatMap((name) => ['--exclude-dir', name]),
146
+ '-e',
147
+ input.pattern,
148
+ input.searchAbsolute,
149
+ ];
150
+ try {
151
+ const result = await run('grep', args, {
152
+ cwd: input.fenceRoot,
153
+ ...(input.signal !== undefined ? { signal: input.signal } : {}),
154
+ });
155
+ // grep: 0 = matches, 1 = no matches, ≥2 = error
156
+ if (result.code !== null && result.code >= 2) {
157
+ const detail = result.stderr.trim();
158
+ return {
159
+ ok: false,
160
+ reason: 'error',
161
+ message: detail.length > 0
162
+ ? `grep failed: ${detail}`
163
+ : 'grep failed',
164
+ };
165
+ }
166
+ const parsed = parseLineHits(result.stdout, input.displayPath, input.fenceRoot, MAX_GREP_MATCHES);
167
+ return {
168
+ ok: true,
169
+ hits: parsed.hits,
170
+ truncated: parsed.truncated,
171
+ backend: 'grep',
172
+ };
173
+ }
174
+ catch (error) {
175
+ if (isBinaryAbortError(error)) {
176
+ throw new Error('aborted');
177
+ }
178
+ return {
179
+ ok: false,
180
+ reason: 'unavailable',
181
+ message: error instanceof Error ? error.message : 'Error executing grep',
182
+ };
183
+ }
184
+ };
185
+ const looksBinary = (buf) => {
186
+ const sample = buf.subarray(0, Math.min(buf.length, 8_192));
187
+ return sample.includes(0);
188
+ };
189
+ const yieldEventLoop = () => new Promise((resolve) => {
190
+ setImmediate(resolve);
191
+ });
192
+ export const searchWithNodeWalk = async (input) => {
193
+ throwIfAborted(input.signal);
194
+ let regex;
195
+ try {
196
+ regex = new RegExp(input.pattern, input.caseInsensitive ? 'i' : '');
197
+ }
198
+ catch (error) {
199
+ const message = error instanceof Error ? error.message : String(error);
200
+ return {
201
+ ok: false,
202
+ reason: 'error',
203
+ message: `Invalid regex «${input.pattern}»: ${message}. Escape special characters or simplify the pattern.`,
204
+ };
205
+ }
206
+ const stat = await fs.stat(input.searchAbsolute).catch(() => null);
207
+ if (stat === null) {
208
+ return {
209
+ ok: false,
210
+ reason: 'error',
211
+ message: `Path not found: ${input.searchAbsolute}`,
212
+ };
213
+ }
214
+ const files = stat.isFile()
215
+ ? [input.searchAbsolute]
216
+ : (await walkFiles(input.fenceRoot, input.searchAbsolute, {
217
+ respectGitignore: input.respectGitignore,
218
+ ...(input.signal !== undefined
219
+ ? { signal: input.signal }
220
+ : {}),
221
+ maxFiles: MAX_GREP_FILES_SCANNED,
222
+ })).map((relative) => path.join(input.fenceRoot, relative));
223
+ const hits = [];
224
+ let matchCapHit = false;
225
+ let scanned = 0;
226
+ for (const fileAbs of files) {
227
+ throwIfAborted(input.signal);
228
+ scanned += 1;
229
+ if (scanned % 16 === 0) {
230
+ await yieldEventLoop();
231
+ throwIfAborted(input.signal);
232
+ }
233
+ if (hits.length >= MAX_GREP_MATCHES) {
234
+ matchCapHit = true;
235
+ break;
236
+ }
237
+ let buf;
238
+ try {
239
+ const handle = await fs.open(fileAbs, 'r');
240
+ try {
241
+ const st = await handle.stat();
242
+ if (st.size > MAX_GREP_FILE_BYTES) {
243
+ continue;
244
+ }
245
+ buf = Buffer.alloc(Number(st.size));
246
+ await handle.read(buf, 0, buf.length, 0);
247
+ }
248
+ finally {
249
+ await handle.close();
250
+ }
251
+ }
252
+ catch {
253
+ continue;
254
+ }
255
+ if (looksBinary(buf)) {
256
+ continue;
257
+ }
258
+ const text = buf.toString('utf8');
259
+ const lines = text.split(/\r?\n/);
260
+ for (let i = 0; i < lines.length; i += 1) {
261
+ let line = lines[i] ?? '';
262
+ if (line.length > MAX_GREP_LINE_CHARS) {
263
+ line = line.slice(0, MAX_GREP_LINE_CHARS);
264
+ }
265
+ if (regex.test(line)) {
266
+ hits.push({
267
+ file: input.displayPath(fileAbs),
268
+ line: i + 1,
269
+ text: line,
270
+ });
271
+ if (hits.length >= MAX_GREP_MATCHES) {
272
+ matchCapHit = true;
273
+ break;
274
+ }
275
+ }
276
+ }
277
+ }
278
+ const fileCapHit = !stat.isFile() && files.length >= MAX_GREP_FILES_SCANNED;
279
+ return {
280
+ ok: true,
281
+ hits,
282
+ truncated: matchCapHit || fileCapHit,
283
+ backend: 'node',
284
+ };
285
+ };
286
+ /**
287
+ * ts-scan-style cascade: rg → grep → bounded Node walk.
288
+ * Soft-falls through when a CLI binary is missing or fails to spawn;
289
+ * Node tier surfaces invalid JS RegExp errors.
290
+ */
291
+ export const runGrepCascade = async (input, deps = {}) => {
292
+ throwIfAborted(input.signal);
293
+ const rg = await searchWithRipgrep(input, deps);
294
+ if (rg.ok) {
295
+ return {
296
+ body: formatGrepBody(rg.hits, rg.truncated),
297
+ backend: rg.backend,
298
+ };
299
+ }
300
+ const grep = await searchWithGrep(input, deps);
301
+ if (grep.ok) {
302
+ return {
303
+ body: formatGrepBody(grep.hits, grep.truncated),
304
+ backend: grep.backend,
305
+ };
306
+ }
307
+ const node = await searchWithNodeWalk(input);
308
+ if (!node.ok) {
309
+ throw new Error(node.message);
310
+ }
311
+ return {
312
+ body: formatGrepBody(node.hits, node.truncated),
313
+ backend: node.backend,
314
+ };
315
+ };
@@ -4,13 +4,13 @@ export declare const grepTool: {
4
4
  readonly registration: {
5
5
  readonly toolId: "grep";
6
6
  readonly name: "grep";
7
- readonly description: "Regex search across project files (gitignore-aware by default; Node walk not ripgrep). Optional postProcess.";
7
+ readonly description: "Regex search across project files (gitignore-aware by default; ripgrep when available, else grep, else bounded Node walk). Optional postProcess.";
8
8
  readonly inputSchema: {
9
9
  readonly type: "object";
10
10
  readonly properties: {
11
11
  readonly pattern: {
12
12
  readonly type: "string";
13
- readonly description: "JavaScript RegExp source";
13
+ readonly description: "Regex pattern (ripgrep dialect when rg is available; JavaScript RegExp on Node fallback)";
14
14
  };
15
15
  readonly path: {
16
16
  readonly type: "string";