From 0f4c4e69a9d140c6f24e52b40f96f3ab6b94ee56 Mon Sep 17 00:00:00 2001
From: Etienne <45695613+etiennejouan@users.noreply.github.com>
Date: Tue, 23 Jun 2026 19:18:46 +0200
Subject: [PATCH] fix(ai-tool): make search_output a raw-text occurrence search
(#22034)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
`search_output` (the spilled-output navigation tool) was built around a
JSON-centric, line-based model that breaks for the data it actually
receives. Spilled outputs are written as compact
`JSON.stringify(output)` (single line, escaped newlines), so the tool's
line-by-line matching collapsed to at most one match, and its schema
described searching "the indented JSON representation" even though it
falls back to raw text for non-JSON. It also ran arbitrary,
model-supplied regexes through the native engine with no ReDoS
protection.
This reworks the tool into a `grep -o` style search over the raw file
bytes: it finds every occurrence of a pattern regardless of newlines and
returns a character window around each hit. It works uniformly for
compact/pretty JSON, CSV, HTML, and plain text.
## Changes
- **Occurrence-based matching** (`search-output.util.ts`): search the
raw content for every match via a global-regex `exec` loop (with a
zero-width-match guard), bounded by `offset + maxMatches`. Results are
now `{ charOffset, match, context }` with a character window around each
occurrence and a centered-ellipsis cap for very long single matches. The
line model (`split`, line numbers, line context) is removed.
- **ReDoS hardening**: matching now uses `re2` (already a dependency)
with the global flag, guaranteeing linear-time matching. Unsupported
regex features (lookahead/backreferences) and invalid patterns fall back
to escaped-literal search instead of throwing.
- **No more reserialization** (`search-output-tool.ts`): the
`JSON.stringify(JSON.parse(...))` round-trip is gone; the tool searches
the exact bytes on disk, so there is no coordinate divergence with
`extract_json_paths`.
- **API** (`search-output-tool.schema.ts`): `contextLines` →
`contextChars` (default 100, max 2000); honest descriptions reflecting
raw-text occurrence search and the regex-or-literal fallback. The result
message reports occurrence counts.
- **Cleanup**: removed unused constants
(`default-search-output-context-lines`,
`search-output-max-line-length`); added
`default-search-output-context-chars` and
`search-output-max-match-length`.
`extract_json_paths` and the spill service are untouched.
## Tradeoff
Results use character offsets/windows rather than line numbers and line
context. For an LLM extracting values from a spilled blob this is more
robust (works on single-line content); the cost is no line-based context
for genuinely line-structured content.
## Test plan
- [x] `search-output.util.spec.ts` rewritten for occurrence semantics:
multiple hits on a single newline-free line, zero-width-pattern
termination, catastrophic-backtracking pattern stays fast (RE2),
lookahead/invalid-regex literal fallback, char-window clipping, offset
pagination, long-match truncation. 12/12 pass.
- [x] `npx nx typecheck twenty-server` clean.
- [x] `npx nx lint:diff-with-main twenty-server` clean (lint + format).
## Deploy note
`re2` is a native addon. It was declared in `package.json` but never
imported/built before this PR, so its binary may be absent in some
environments (local install required `npm rebuild re2`). Confirm the
install/build pipeline (CI, Docker images) compiles native modules so
the tool doesn't throw `Cannot find module 're2.node'` at runtime.
---
packages/twenty-server/package.json | 1 -
...lt-search-output-context-chars.constant.ts | 1 +
...lt-search-output-context-lines.constant.ts | 1 -
.../search-output-max-line-length.constant.ts | 1 -
...search-output-max-match-length.constant.ts | 1 +
.../search-output-tool.schema.ts | 13 ++-
.../search-output-tool.ts | 45 ++++----
.../__tests__/search-output.util.spec.ts | 108 ++++++++++++++----
.../utils/search-output.util.ts | 102 +++++++++++------
yarn.lock | 33 +-----
10 files changed, 188 insertions(+), 118 deletions(-)
create mode 100644 packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-chars.constant.ts
delete mode 100644 packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-lines.constant.ts
delete mode 100644 packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-line-length.constant.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant.ts
diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json
index 926c93947b..29b3fba641 100644
--- a/packages/twenty-server/package.json
+++ b/packages/twenty-server/package.json
@@ -152,7 +152,6 @@
"pluralize": "8.0.0",
"postal-mime": "^2.6.1",
"psl": "^1.9.0",
- "re2": "^1.25.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"redis": "^4.7.0",
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-chars.constant.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-chars.constant.ts
new file mode 100644
index 0000000000..9675e95963
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-chars.constant.ts
@@ -0,0 +1 @@
+export const DEFAULT_SEARCH_OUTPUT_CONTEXT_CHARS = 100;
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-lines.constant.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-lines.constant.ts
deleted file mode 100644
index fc793d8987..0000000000
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-lines.constant.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const DEFAULT_SEARCH_OUTPUT_CONTEXT_LINES = 2;
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-line-length.constant.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-line-length.constant.ts
deleted file mode 100644
index 15a9414cde..0000000000
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-line-length.constant.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const SEARCH_OUTPUT_MAX_LINE_LENGTH = 500;
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant.ts
new file mode 100644
index 0000000000..24d5387646
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant.ts
@@ -0,0 +1 @@
+export const SEARCH_OUTPUT_MAX_MATCH_LENGTH = 500;
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.schema.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.schema.ts
index 990494b3d2..887bf093cc 100644
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.schema.ts
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.schema.ts
@@ -8,8 +8,9 @@ export const SearchOutputInputZodSchema = z.object({
),
pattern: z
.string()
+ .min(1)
.describe(
- 'Text or regular expression to search for. Matched line by line against the indented JSON representation of the file.',
+ 'Text or regular expression to search for. Treated as a regular expression when valid, otherwise as a literal string. Matched against the raw file text.',
),
maxMatches: z
.number()
@@ -17,22 +18,22 @@ export const SearchOutputInputZodSchema = z.object({
.positive()
.max(100)
.optional()
- .describe('Maximum number of matches to return (default 10).'),
+ .describe('Maximum number of occurrences to return (default 10).'),
offset: z
.number()
.int()
.nonnegative()
.optional()
.describe(
- 'Number of matches to skip before returning results, for stateless pagination (default 0).',
+ 'Number of occurrences to skip before returning results, for stateless pagination (default 0).',
),
- contextLines: z
+ contextChars: z
.number()
.int()
.nonnegative()
- .max(10)
+ .max(2000)
.optional()
.describe(
- 'Lines of context to include before and after each match (default 2).',
+ 'Number of characters of context to include before and after each occurrence (default 100).',
),
});
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.ts
index e056e765ae..1f68fe481d 100644
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.ts
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.ts
@@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { FileFolder } from 'twenty-shared/types';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
-import { DEFAULT_SEARCH_OUTPUT_CONTEXT_LINES } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-lines.constant';
+import { DEFAULT_SEARCH_OUTPUT_CONTEXT_CHARS } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-context-chars.constant';
import { DEFAULT_SEARCH_OUTPUT_MAX_MATCHES } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/default-search-output-max-matches.constant';
import { SearchOutputInputZodSchema } from 'src/engine/core-modules/tool/tools/output-navigation-tool/search-output-tool.schema';
import { searchOutput } from 'src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util';
@@ -13,20 +13,12 @@ import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import { isDefined } from 'twenty-shared/utils';
-type SearchOutputInput = {
- fileId: string;
- pattern: string;
- maxMatches?: number;
- offset?: number;
- contextLines?: number;
-};
-
@Injectable()
export class SearchOutputTool implements Tool {
private readonly logger = new Logger(SearchOutputTool.name);
description =
- 'Search (grep-like) within a large spilled tool output for a text or regex pattern, returning matching lines with surrounding context. Supports stateless pagination via offset. Use this to locate an error message or key in a file too large to inline.';
+ 'Search (grep -o style) within a large spilled tool output for a text or regex pattern, returning every occurrence with surrounding characters of context. Works on raw text of any shape (CSV, HTML, stringified JSON, plain text), including single-line content. Supports stateless pagination via offset. Use this to locate an error message or key in a file too large to inline.';
inputSchema = SearchOutputInputZodSchema;
@@ -37,8 +29,23 @@ export class SearchOutputTool implements Tool {
context: ToolExecutionContext,
): Promise {
const { workspaceId } = context;
- const { fileId, pattern, maxMatches, offset, contextLines } =
- parameters as SearchOutputInput;
+
+ const parseResult = SearchOutputInputZodSchema.safeParse(parameters);
+
+ if (!parseResult.success) {
+ return {
+ success: false,
+ message: 'Invalid input for search output',
+ error: parseResult.error.issues
+ .map(
+ (issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`,
+ )
+ .join('; '),
+ };
+ }
+
+ const { fileId, pattern, maxMatches, offset, contextChars } =
+ parseResult.data;
let fileContent: { buffer: Buffer; mimeType: string } | null;
@@ -66,22 +73,14 @@ export class SearchOutputTool implements Tool {
};
}
- const rawContent = fileContent.buffer.toString('utf-8');
-
- let content: string;
-
- try {
- content = JSON.stringify(JSON.parse(rawContent), null, 2);
- } catch {
- content = rawContent;
- }
+ const content = fileContent.buffer.toString('utf-8');
const result = searchOutput({
content,
pattern,
maxMatches: maxMatches ?? DEFAULT_SEARCH_OUTPUT_MAX_MATCHES,
offset: offset ?? 0,
- contextLines: contextLines ?? DEFAULT_SEARCH_OUTPUT_CONTEXT_LINES,
+ contextChars: contextChars ?? DEFAULT_SEARCH_OUTPUT_CONTEXT_CHARS,
});
return {
@@ -89,7 +88,7 @@ export class SearchOutputTool implements Tool {
message:
result.totalMatches === 0
? `No matches for "${pattern}"`
- : `Found ${result.totalMatches} match(es) for "${pattern}"`,
+ : `Found ${result.totalMatches} occurrence(s) for "${pattern}"`,
result,
};
}
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/__tests__/search-output.util.spec.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/__tests__/search-output.util.spec.ts
index 3e3abcda26..b1c529a4f0 100644
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/__tests__/search-output.util.spec.ts
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/__tests__/search-output.util.spec.ts
@@ -1,6 +1,7 @@
+import { SEARCH_OUTPUT_MAX_MATCH_LENGTH } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant';
import { searchOutput } from 'src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util';
-const content = [
+const multiLineContent = [
'line 0 ok',
'line 1 error: boom',
'line 2 ok',
@@ -10,31 +11,53 @@ const content = [
].join('\n');
describe('searchOutput', () => {
- it('returns matches with context and line numbers', () => {
+ it('returns each occurrence with a character window and offset', () => {
const result = searchOutput({
- content,
+ content: multiLineContent,
pattern: 'error',
maxMatches: 10,
offset: 0,
- contextLines: 1,
+ contextChars: 5,
});
expect(result.totalMatches).toBe(3);
expect(result.hasMore).toBe(false);
- expect(result.matches[0]).toEqual({
- lineNumber: 2,
- match: 'line 1 error: boom',
- context: '1: line 0 ok\n2: line 1 error: boom\n3: line 2 ok',
+ expect(result.matches).toHaveLength(3);
+ expect(result.matches[0].match).toBe('error');
+ expect(result.matches[0].charOffset).toBe(
+ multiLineContent.indexOf('error'),
+ );
+ expect(result.matches[0].context).toContain('error');
+ expect(result.matches[0].context.endsWith('…')).toBe(true);
+ });
+
+ it('finds every occurrence on a single newline-free line', () => {
+ const compact = '{"a":"error","b":"error","c":"error"}';
+
+ const result = searchOutput({
+ content: compact,
+ pattern: 'error',
+ maxMatches: 10,
+ offset: 0,
+ contextChars: 0,
});
+
+ expect(result.totalMatches).toBe(3);
+ expect(result.matches).toHaveLength(3);
+ expect(result.matches.map((match) => match.charOffset)).toEqual([
+ compact.indexOf('error'),
+ compact.indexOf('error', compact.indexOf('error') + 1),
+ compact.lastIndexOf('error'),
+ ]);
});
it('caps results at maxMatches and reports hasMore', () => {
const result = searchOutput({
- content,
+ content: multiLineContent,
pattern: 'error',
maxMatches: 2,
offset: 0,
- contextLines: 0,
+ contextChars: 0,
});
expect(result.totalMatches).toBe(3);
@@ -44,26 +67,28 @@ describe('searchOutput', () => {
it('paginates with offset', () => {
const result = searchOutput({
- content,
+ content: multiLineContent,
pattern: 'error',
maxMatches: 2,
offset: 2,
- contextLines: 0,
+ contextChars: 0,
});
expect(result.totalMatches).toBe(3);
expect(result.matches).toHaveLength(1);
- expect(result.matches[0].lineNumber).toBe(6);
+ expect(result.matches[0].charOffset).toBe(
+ multiLineContent.lastIndexOf('error'),
+ );
expect(result.hasMore).toBe(false);
});
it('returns an empty result for zero matches', () => {
const result = searchOutput({
- content,
+ content: multiLineContent,
pattern: 'nonexistent',
maxMatches: 10,
offset: 0,
- contextLines: 2,
+ contextChars: 2,
});
expect(result).toEqual({ matches: [], totalMatches: 0, hasMore: false });
@@ -71,26 +96,69 @@ describe('searchOutput', () => {
it('supports regex patterns', () => {
const result = searchOutput({
- content,
+ content: multiLineContent,
pattern: 'splat|boom',
maxMatches: 10,
offset: 0,
- contextLines: 0,
+ contextChars: 0,
});
expect(result.totalMatches).toBe(3);
});
+ it('terminates on a zero-width pattern instead of looping forever', () => {
+ const result = searchOutput({
+ content: 'abc',
+ pattern: 'x*',
+ maxMatches: 10,
+ offset: 0,
+ contextChars: 0,
+ });
+
+ expect(result.totalMatches).toBeGreaterThan(0);
+ expect(result.matches.every((match) => match.match === '')).toBe(true);
+ });
+
it('falls back to literal matching for invalid regex', () => {
const result = searchOutput({
- content: 'a (b c\nd e f',
+ content: 'a (b c d e f',
pattern: '(b',
maxMatches: 10,
offset: 0,
- contextLines: 0,
+ contextChars: 0,
});
expect(result.totalMatches).toBe(1);
- expect(result.matches[0].match).toBe('a (b c');
+ expect(result.matches[0].match).toBe('(b');
+ });
+
+ it('truncates an overly long single match with a centered ellipsis', () => {
+ const longMatch = 'z'.repeat(SEARCH_OUTPUT_MAX_MATCH_LENGTH + 200);
+
+ const result = searchOutput({
+ content: longMatch,
+ pattern: 'z+',
+ maxMatches: 10,
+ offset: 0,
+ contextChars: 0,
+ });
+
+ expect(result.totalMatches).toBe(1);
+ expect(result.matches[0].match).toContain('…');
+ expect(result.matches[0].match.length).toBeLessThanOrEqual(
+ SEARCH_OUTPUT_MAX_MATCH_LENGTH,
+ );
+ });
+
+ it('throws for an empty pattern instead of matching everything', () => {
+ expect(() =>
+ searchOutput({
+ content: multiLineContent,
+ pattern: '',
+ maxMatches: 10,
+ offset: 0,
+ contextChars: 0,
+ }),
+ ).toThrow('Search pattern must be a non-empty string.');
});
});
diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util.ts
index 6bfafc3347..edc8620590 100644
--- a/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util.ts
+++ b/packages/twenty-server/src/engine/core-modules/tool/tools/output-navigation-tool/utils/search-output.util.ts
@@ -1,7 +1,10 @@
-import { SEARCH_OUTPUT_MAX_LINE_LENGTH } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-line-length.constant';
+import { isNonEmptyString } from '@sniptt/guards';
+
+import { SEARCH_OUTPUT_MAX_MATCH_LENGTH } from 'src/engine/core-modules/tool/tools/output-navigation-tool/constants/search-output-max-match-length.constant';
+import { isDefined } from 'twenty-shared/utils';
export type SearchMatch = {
- lineNumber: number;
+ charOffset: number;
match: string;
context: string;
};
@@ -17,64 +20,95 @@ const escapeRegExp = (value: string): string =>
const compilePattern = (pattern: string): RegExp => {
try {
- return new RegExp(pattern);
+ return new RegExp(pattern, 'g');
} catch {
- return new RegExp(escapeRegExp(pattern));
+ return new RegExp(escapeRegExp(pattern), 'g');
}
};
-const truncateLine = (line: string): string =>
- line.length > SEARCH_OUTPUT_MAX_LINE_LENGTH
- ? `${line.slice(0, SEARCH_OUTPUT_MAX_LINE_LENGTH)}…`
- : line;
+const truncateMatch = (match: string): string => {
+ if (match.length <= SEARCH_OUTPUT_MAX_MATCH_LENGTH) {
+ return match;
+ }
+
+ const half = Math.floor((SEARCH_OUTPUT_MAX_MATCH_LENGTH - 1) / 2);
+
+ return `${match.slice(0, half)}…${match.slice(match.length - half)}`;
+};
+
+const buildContext = ({
+ content,
+ index,
+ length,
+ contextChars,
+}: {
+ content: string;
+ index: number;
+ length: number;
+ contextChars: number;
+}): string => {
+ const start = Math.max(0, index - contextChars);
+ const end = Math.min(content.length, index + length + contextChars);
+
+ const prefix = start > 0 ? '…' : '';
+ const suffix = end < content.length ? '…' : '';
+
+ return `${prefix}${content.slice(start, end)}${suffix}`;
+};
export const searchOutput = ({
content,
pattern,
maxMatches,
offset,
- contextLines,
+ contextChars,
}: {
content: string;
pattern: string;
maxMatches: number;
offset: number;
- contextLines: number;
+ contextChars: number;
}): SearchOutputResult => {
- const lines = content.split('\n');
- const regex = compilePattern(pattern);
-
- const matchLineIndices: number[] = [];
-
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
- if (regex.test(lines[lineIndex])) {
- matchLineIndices.push(lineIndex);
- }
+ if (!isNonEmptyString(pattern)) {
+ throw new Error('Search pattern must be a non-empty string.');
}
- const totalMatches = matchLineIndices.length;
- const selected = matchLineIndices.slice(offset, offset + maxMatches);
+ const regex = compilePattern(pattern);
- const matches: SearchMatch[] = selected.map((lineIndex) => {
- const start = Math.max(0, lineIndex - contextLines);
- const end = Math.min(lines.length - 1, lineIndex + contextLines);
+ const matches: SearchMatch[] = [];
+ let totalMatches = 0;
- const contextBlock: string[] = [];
+ let execResult = regex.exec(content);
- for (let cursor = start; cursor <= end; cursor++) {
- contextBlock.push(`${cursor + 1}: ${truncateLine(lines[cursor])}`);
+ while (isDefined(execResult)) {
+ const index = execResult.index;
+ const matched = execResult[0];
+
+ if (totalMatches >= offset && matches.length < maxMatches) {
+ matches.push({
+ charOffset: index,
+ match: truncateMatch(matched),
+ context: buildContext({
+ content,
+ index,
+ length: matched.length,
+ contextChars,
+ }),
+ });
}
- return {
- lineNumber: lineIndex + 1,
- match: truncateLine(lines[lineIndex]),
- context: contextBlock.join('\n'),
- };
- });
+ totalMatches += 1;
+
+ if (matched.length === 0) {
+ regex.lastIndex += 1;
+ }
+
+ execResult = regex.exec(content);
+ }
return {
matches,
totalMatches,
- hasMore: offset + selected.length < totalMatches,
+ hasMore: offset + matches.length < totalMatches,
};
};
diff --git a/yarn.lock b/yarn.lock
index 584fb56b2f..88253e1b80 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -36351,16 +36351,6 @@ __metadata:
languageName: node
linkType: hard
-"install-artifact-from-github@npm:^1.6.0":
- version: 1.6.0
- resolution: "install-artifact-from-github@npm:1.6.0"
- bin:
- install-from-cache: bin/install-from-cache.js
- save-to-github-cache: bin/save-to-github-cache.js
- checksum: 10c0/e480c994a6230ba9c24b76e29908dcfb29977d3ee5b4a193b5c793139065aa0deb9bc5f475d11fd347c516b88ddb1a0f02935aa11fd1d3585761a4a4ccc1885c
- languageName: node
- linkType: hard
-
"internal-slot@npm:^1.1.0":
version: 1.1.0
resolution: "internal-slot@npm:1.1.0"
@@ -42148,15 +42138,6 @@ __metadata:
languageName: node
linkType: hard
-"nan@npm:^2.27.0":
- version: 2.27.0
- resolution: "nan@npm:2.27.0"
- dependencies:
- node-gyp: "npm:latest"
- checksum: 10c0/38eb00b06e40f0c65b6a98d75795f17d651a8b7b52f03873dff6902d0053f12e7638d7f64fc52bda6c8f8ec454d69636e3988c8a9eb2bc749c2d5c255ba55f4c
- languageName: node
- linkType: hard
-
"nan@npm:^2.4.0":
version: 2.26.2
resolution: "nan@npm:2.26.2"
@@ -42687,7 +42668,7 @@ __metadata:
languageName: node
linkType: hard
-"node-gyp@npm:^13.0.0, node-gyp@npm:latest":
+"node-gyp@npm:latest":
version: 13.0.0
resolution: "node-gyp@npm:13.0.0"
dependencies:
@@ -46361,17 +46342,6 @@ __metadata:
languageName: node
linkType: hard
-"re2@npm:^1.25.0":
- version: 1.25.0
- resolution: "re2@npm:1.25.0"
- dependencies:
- install-artifact-from-github: "npm:^1.6.0"
- nan: "npm:^2.27.0"
- node-gyp: "npm:^13.0.0"
- checksum: 10c0/a1b89e4d6c594d3fe675fc18f5ee3ce06ea83d66ebc5b3b2d66abf8c93c57757478969d1c934db4379ffba0575154c149d4168a567d192a936fced369d7c3ecf
- languageName: node
- linkType: hard
-
"react-aria@npm:^3.48.0":
version: 3.49.0
resolution: "react-aria@npm:3.49.0"
@@ -52860,7 +52830,6 @@ __metadata:
postal-mime: "npm:^2.6.1"
prettier: "npm:^3.1.1"
psl: "npm:^1.9.0"
- re2: "npm:^1.25.0"
react: "npm:^19.2.0"
react-dom: "npm:^19.2.0"
redis: "npm:^4.7.0"