@animalabs/agent-framework 0.13.0 → 0.15.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.
- package/dist/src/agent.d.ts +10 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +39 -0
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +36 -6
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +451 -83
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +55 -1
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +41 -1
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +69 -0
- package/dist/src/modules/history/index.d.ts.map +1 -0
- package/dist/src/modules/history/index.js +519 -0
- package/dist/src/modules/history/index.js.map +1 -0
- package/dist/src/modules/history/search-regex-worker.d.ts +23 -0
- package/dist/src/modules/history/search-regex-worker.d.ts.map +1 -0
- package/dist/src/modules/history/search-regex-worker.js +57 -0
- package/dist/src/modules/history/search-regex-worker.js.map +1 -0
- package/dist/src/modules/index.d.ts +1 -0
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js +1 -0
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +42 -1
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +5 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts +175 -0
- package/dist/src/tool-image-ledger.d.ts.map +1 -0
- package/dist/src/tool-image-ledger.js +257 -0
- package/dist/src/tool-image-ledger.js.map +1 -0
- package/dist/src/tool-result-history.d.ts +15 -2
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +16 -9
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/agent.d.ts +10 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +4 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/event-gate.test.js +84 -1
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/history-module.test.d.ts +2 -0
- package/dist/test/history-module.test.d.ts.map +1 -0
- package/dist/test/history-module.test.js +449 -0
- package/dist/test/history-module.test.js.map +1 -0
- package/dist/test/kv-unified-open-flight-retry.test.d.ts +2 -0
- package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +1 -0
- package/dist/test/kv-unified-open-flight-retry.test.js +189 -0
- package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -0
- package/dist/test/kv-unified-wiring.test.js +39 -0
- package/dist/test/kv-unified-wiring.test.js.map +1 -1
- package/dist/test/puppet-tool-call.test.js +114 -1
- package/dist/test/puppet-tool-call.test.js.map +1 -1
- package/dist/test/puppet-turn-reservation.test.d.ts +2 -0
- package/dist/test/puppet-turn-reservation.test.d.ts.map +1 -0
- package/dist/test/puppet-turn-reservation.test.js +139 -0
- package/dist/test/puppet-turn-reservation.test.js.map +1 -0
- package/dist/test/save-recent-image.test.d.ts +26 -0
- package/dist/test/save-recent-image.test.d.ts.map +1 -0
- package/dist/test/save-recent-image.test.js +736 -0
- package/dist/test/save-recent-image.test.js.map +1 -0
- package/dist/test/tool-result-spill.test.js +114 -0
- package/dist/test/tool-result-spill.test.js.map +1 -1
- package/dist/test/turn-trigger-provenance.test.js +17 -0
- package/dist/test/turn-trigger-provenance.test.js.map +1 -1
- package/dist/test/workspace-read-text.test.d.ts +2 -0
- package/dist/test/workspace-read-text.test.d.ts.map +1 -0
- package/dist/test/workspace-read-text.test.js +101 -0
- package/dist/test/workspace-read-text.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/agent.ts +41 -0
- package/src/framework.ts +519 -95
- package/src/gate/event-gate.ts +71 -3
- package/src/gate/index.ts +1 -1
- package/src/index.ts +1 -1
- package/src/modules/history/index.ts +613 -0
- package/src/modules/history/search-regex-worker.ts +76 -0
- package/src/modules/index.ts +2 -0
- package/src/modules/workspace/index.ts +42 -1
- package/src/modules/workspace/types.ts +5 -0
- package/src/tool-image-ledger.ts +334 -0
- package/src/tool-result-history.ts +30 -9
- package/src/types/agent.ts +10 -0
- package/src/types/trace.ts +4 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HistoryModule — read-only access to an agent's full uncompressed message
|
|
3
|
+
* history, backed by context-manager's native chronicle secondary index
|
|
4
|
+
* (`queryMessagesByTime` / `queryMessagesByChannel` /
|
|
5
|
+
* `queryMessagesByTimeAndChannel` / `getChannelMessageCounts` /
|
|
6
|
+
* `getChannelTokenStats`, @animalabs/context-manager >= 0.6.0). Those calls
|
|
7
|
+
* are O(log n + k) against chronicle's `/timestamp` and
|
|
8
|
+
* `/metadata/external/channelId` field indexes, not a full-store scan, so
|
|
9
|
+
* this module can answer "what happened in #foo last Tuesday" against a
|
|
10
|
+
* multi-million-message store without walking the whole log.
|
|
11
|
+
*
|
|
12
|
+
* Three tools:
|
|
13
|
+
* - `stats` — per-channel message counts (all-time) + token totals
|
|
14
|
+
* (range-scoped), for orienting before pulling raw content.
|
|
15
|
+
* - `extract` — paginated raw messages for a time range and/or channel.
|
|
16
|
+
* - `search` — substring/regex match over a narrowed candidate window,
|
|
17
|
+
* with an explicit truncation signal when the caller's
|
|
18
|
+
* filter was too broad for `maxScan`.
|
|
19
|
+
*
|
|
20
|
+
* Kept deliberately read-only, same posture as HealthModule: no side
|
|
21
|
+
* effects, no message mutation. `onProcess` is a no-op.
|
|
22
|
+
*
|
|
23
|
+
* Every query dispatch collapses onto `queryMessagesByTimeAndChannel` — its
|
|
24
|
+
* own implementation already handles "only a time range", "only a channel",
|
|
25
|
+
* or "neither" by delegating to the single-filter native queries (see
|
|
26
|
+
* context-manager's `test/message-store-history-index.test.ts`), so this
|
|
27
|
+
* module doesn't need to re-decide which of the three query methods to call.
|
|
28
|
+
*/
|
|
29
|
+
import { Worker } from 'node:worker_threads';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
import { dirname, join } from 'node:path';
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Limits
|
|
34
|
+
// ============================================================================
|
|
35
|
+
const EXTRACT_DEFAULT_LIMIT = 50;
|
|
36
|
+
const EXTRACT_MAX_LIMIT = 200;
|
|
37
|
+
/**
|
|
38
|
+
* Real ceiling of the native chronicle pagination `offset` argument (a u32
|
|
39
|
+
* at the N-API boundary). `Number.MAX_SAFE_INTEGER` is NOT a safe ceiling
|
|
40
|
+
* for this field: a value between 0xFFFFFFFF and MAX_SAFE_INTEGER passes a
|
|
41
|
+
* naive upper-bound check unchanged, then wraps/truncates when it crosses
|
|
42
|
+
* into an unsigned 32-bit int on the native side — silently wrong
|
|
43
|
+
* pagination (repro: offset:4294967296 against a real store "succeeded"
|
|
44
|
+
* and returned page 0) instead of a clean failure. Clamping to this ceiling
|
|
45
|
+
* (same clamp-not-reject behavior `limit` already has) means anything
|
|
46
|
+
* beyond it lands on an offset no real store's message count will ever
|
|
47
|
+
* reach, i.e. a correctly-empty page, rather than wrapping onto a wrong one.
|
|
48
|
+
*/
|
|
49
|
+
const NATIVE_OFFSET_MAX = 0xffffffff; // 4294967295
|
|
50
|
+
const SEARCH_DEFAULT_LIMIT = 20;
|
|
51
|
+
const SEARCH_MAX_LIMIT = 100;
|
|
52
|
+
const SEARCH_DEFAULT_MAX_SCAN = 5000;
|
|
53
|
+
const SEARCH_MAX_MAX_SCAN = 50000;
|
|
54
|
+
/** Characters of surrounding context kept on each side of a search snippet. */
|
|
55
|
+
const SNIPPET_CONTEXT_CHARS = 80;
|
|
56
|
+
/** Fallback snippet length when a match position isn't meaningful to center on. */
|
|
57
|
+
const SNIPPET_FALLBACK_CHARS = 160;
|
|
58
|
+
/**
|
|
59
|
+
* Wall-clock deadline for a single regex-mode search call, enforced by
|
|
60
|
+
* forcibly terminating the worker thread doing the matching (see
|
|
61
|
+
* search-regex-worker.ts's header for why a worker — not a
|
|
62
|
+
* Promise.race/setTimeout — is required to actually interrupt a stuck
|
|
63
|
+
* synchronous RegExp.exec()). Generous enough for any legitimate pattern
|
|
64
|
+
* against a few thousand short strings; short enough that a catastrophic
|
|
65
|
+
* pattern doesn't tie up a worker (or an agent's turn) for long.
|
|
66
|
+
*/
|
|
67
|
+
const SEARCH_REGEX_TIMEOUT_MS = 2000;
|
|
68
|
+
/** Compiled sibling of search-regex-worker.ts — resolved at runtime the same
|
|
69
|
+
* way gate-script.ts locates gate-script-worker.js, so it tracks whatever
|
|
70
|
+
* directory this module's own compiled output lives in. */
|
|
71
|
+
const SEARCH_WORKER_PATH = join(dirname(fileURLToPath(import.meta.url)), 'search-regex-worker.js');
|
|
72
|
+
export class HistoryModule {
|
|
73
|
+
name = 'history';
|
|
74
|
+
ctx = null;
|
|
75
|
+
cm = null;
|
|
76
|
+
/**
|
|
77
|
+
* Wire the context-manager instance. Host calls this after
|
|
78
|
+
* ContextManager.open() so the module can issue the native index-backed
|
|
79
|
+
* queries — ModuleContext itself exposes no store/context-manager
|
|
80
|
+
* reference, only the narrower message CRUD surface (addMessage/getMessage/
|
|
81
|
+
* queryMessages), which can't do range or channel queries.
|
|
82
|
+
*/
|
|
83
|
+
bind(contextManager) {
|
|
84
|
+
this.cm = contextManager;
|
|
85
|
+
}
|
|
86
|
+
async start(ctx) {
|
|
87
|
+
this.ctx = ctx;
|
|
88
|
+
}
|
|
89
|
+
async stop() {
|
|
90
|
+
this.ctx = null;
|
|
91
|
+
}
|
|
92
|
+
getTools() {
|
|
93
|
+
return [
|
|
94
|
+
{
|
|
95
|
+
name: 'stats',
|
|
96
|
+
description: 'Orient before pulling raw history: per-channel message counts and token totals. ' +
|
|
97
|
+
'messageCountsAllTime is always all-time and all-channel-scope (the underlying index has no ' +
|
|
98
|
+
'range dimension) — it is NOT limited by from/to, only optionally filtered to one channelId. ' +
|
|
99
|
+
'tokenStatsForRange IS scoped to from/to when given. The two are reported separately, never ' +
|
|
100
|
+
'merged, because their range semantics differ.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
from: { type: 'string', description: 'ISO 8601 inclusive lower bound for tokenStatsForRange. Omit for open-ended.' },
|
|
105
|
+
to: { type: 'string', description: 'ISO 8601 inclusive upper bound for tokenStatsForRange. Omit for open-ended.' },
|
|
106
|
+
channelId: { type: 'string', description: 'Restrict both parts of the response to one channel.' },
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'extract',
|
|
112
|
+
description: 'Fetch raw, uncompressed messages for a time range and/or channel, paginated oldest-first. ' +
|
|
113
|
+
'Use after `stats` to pull the actual content. format:"text" flattens each message to a short ' +
|
|
114
|
+
'readable string (tool_use/tool_result/thinking/images rendered as bracketed labels); ' +
|
|
115
|
+
'format:"raw" returns the content blocks unmodified.',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
from: { type: 'string', description: 'ISO 8601 inclusive lower bound. Omit for open-ended.' },
|
|
120
|
+
to: { type: 'string', description: 'ISO 8601 inclusive upper bound. Omit for open-ended.' },
|
|
121
|
+
channelId: { type: 'string', description: 'Restrict to one channel.' },
|
|
122
|
+
limit: { type: 'number', description: `Max messages to return (default ${EXTRACT_DEFAULT_LIMIT}, hard cap ${EXTRACT_MAX_LIMIT}). Must be a non-negative integer.` },
|
|
123
|
+
offset: { type: 'number', description: `Number of matching messages to skip (default 0, capped to ${NATIVE_OFFSET_MAX}). Must be a non-negative integer.` },
|
|
124
|
+
format: { type: 'string', enum: ['text', 'raw'], description: 'Content rendering (default "text").' },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'search',
|
|
130
|
+
description: 'Search message history for a substring (default) or regex match, within an optional time ' +
|
|
131
|
+
'range/channel window. Narrows candidates via the same query as `extract` before matching, up ' +
|
|
132
|
+
'to maxScan candidates — if the narrowed window is larger than maxScan, the response reports ' +
|
|
133
|
+
'truncated:true up front rather than silently missing later matches; narrow the filter or raise ' +
|
|
134
|
+
'maxScan and retry. regex:true matching runs under a wall-clock deadline and is cleanly failed ' +
|
|
135
|
+
`(not silently empty) if a pattern is too slow — avoid nested-quantifier patterns like (a+)+.`,
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
query: { type: 'string', description: 'Substring (or regex source, when regex:true) to search for.' },
|
|
140
|
+
regex: { type: 'boolean', description: 'Treat query as a regular expression (default false).' },
|
|
141
|
+
caseSensitive: { type: 'boolean', description: 'Case-sensitive match (default false).' },
|
|
142
|
+
from: { type: 'string', description: 'ISO 8601 inclusive lower bound. Omit for open-ended.' },
|
|
143
|
+
to: { type: 'string', description: 'ISO 8601 inclusive upper bound. Omit for open-ended.' },
|
|
144
|
+
channelId: { type: 'string', description: 'Restrict to one channel.' },
|
|
145
|
+
limit: { type: 'number', description: `Max matches to return (default ${SEARCH_DEFAULT_LIMIT}, hard cap ${SEARCH_MAX_LIMIT}). Must be a non-negative integer.` },
|
|
146
|
+
maxScan: { type: 'number', description: `Max candidate messages to scan (default ${SEARCH_DEFAULT_MAX_SCAN}, hard cap ${SEARCH_MAX_MAX_SCAN}). Must be a non-negative integer.` },
|
|
147
|
+
},
|
|
148
|
+
required: ['query'],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
async handleToolCall(call) {
|
|
154
|
+
try {
|
|
155
|
+
if (!this.cm) {
|
|
156
|
+
throw new Error('HistoryModule not bound — host must call bind(contextManager) before tool dispatch.');
|
|
157
|
+
}
|
|
158
|
+
switch (call.name) {
|
|
159
|
+
case 'stats':
|
|
160
|
+
return this.handleStats((call.input ?? {}));
|
|
161
|
+
case 'extract':
|
|
162
|
+
return this.handleExtract((call.input ?? {}));
|
|
163
|
+
case 'search':
|
|
164
|
+
return await this.handleSearch((call.input ?? {}));
|
|
165
|
+
default:
|
|
166
|
+
return { success: false, isError: true, error: `Unknown tool: ${call.name}` };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Catches our own validation errors (bad ISO date, invalid regex,
|
|
171
|
+
// out-of-range limit/offset/maxScan, unbound module), a regex-search
|
|
172
|
+
// worker timeout or worker-side error (see handleSearch/
|
|
173
|
+
// searchWithRegexWorker — a ReDoS-shaped pattern surfaces here as a
|
|
174
|
+
// clean timeout error, never a hang), and context-manager's
|
|
175
|
+
// capability-absent error ("Chronicle history index unsupported...",
|
|
176
|
+
// thrown by queryMessagesByTime/queryMessagesByChannel/
|
|
177
|
+
// queryMessagesByTimeAndChannel/getChannelMessageCounts/
|
|
178
|
+
// getChannelTokenStats on a chronicle build that predates the native
|
|
179
|
+
// index-query capability) — all surfaced as a normal tool error
|
|
180
|
+
// rather than crashing the module.
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
isError: true,
|
|
184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async onProcess(_event, _state) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
// ==========================================================================
|
|
192
|
+
// stats
|
|
193
|
+
// ==========================================================================
|
|
194
|
+
handleStats(input) {
|
|
195
|
+
const fromMs = parseIsoDate(input.from, 'from');
|
|
196
|
+
const toMs = parseIsoDate(input.to, 'to');
|
|
197
|
+
const cm = this.cm;
|
|
198
|
+
let messageCountsAllTime = cm.getChannelMessageCounts();
|
|
199
|
+
let tokenStatsForRange = cm.getChannelTokenStats({ fromMs, toMs });
|
|
200
|
+
if (input.channelId) {
|
|
201
|
+
messageCountsAllTime = messageCountsAllTime.filter((c) => c.channelId === input.channelId);
|
|
202
|
+
tokenStatsForRange = {
|
|
203
|
+
// Totals stay whole-range (they're documented as store-wide-for-the-range,
|
|
204
|
+
// not per-channel); only the byChannel breakdown is narrowed. Labeling the
|
|
205
|
+
// field `byChannel` (rather than folding a channel-filtered total into
|
|
206
|
+
// totalMessages/totalTokensEstimate) keeps this from reading as a
|
|
207
|
+
// misleadingly-merged single-channel total.
|
|
208
|
+
totalMessages: tokenStatsForRange.totalMessages,
|
|
209
|
+
totalTokensEstimate: tokenStatsForRange.totalTokensEstimate,
|
|
210
|
+
byChannel: tokenStatsForRange.byChannel.filter((c) => c.channelId === input.channelId),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
data: {
|
|
216
|
+
query: { from: input.from ?? null, to: input.to ?? null, channelId: input.channelId ?? null },
|
|
217
|
+
messageCountsAllTime,
|
|
218
|
+
tokenStatsForRange,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// ==========================================================================
|
|
223
|
+
// extract
|
|
224
|
+
// ==========================================================================
|
|
225
|
+
handleExtract(input) {
|
|
226
|
+
const fromMs = parseIsoDate(input.from, 'from');
|
|
227
|
+
const toMs = parseIsoDate(input.to, 'to');
|
|
228
|
+
const limit = clampCount(input.limit, EXTRACT_DEFAULT_LIMIT, EXTRACT_MAX_LIMIT, 'limit');
|
|
229
|
+
const offset = clampCount(input.offset, 0, NATIVE_OFFSET_MAX, 'offset');
|
|
230
|
+
const format = input.format ?? 'text';
|
|
231
|
+
const cm = this.cm;
|
|
232
|
+
const result = cm.queryMessagesByTimeAndChannel({
|
|
233
|
+
fromMs,
|
|
234
|
+
toMs,
|
|
235
|
+
channelId: input.channelId,
|
|
236
|
+
limit,
|
|
237
|
+
offset,
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
data: {
|
|
242
|
+
matchedCount: result.matchedCount,
|
|
243
|
+
returned: result.messages.length,
|
|
244
|
+
messages: result.messages.map((msg) => projectMessage(msg, format)),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// ==========================================================================
|
|
249
|
+
// search
|
|
250
|
+
// ==========================================================================
|
|
251
|
+
async handleSearch(input) {
|
|
252
|
+
if (!input.query) {
|
|
253
|
+
throw new Error('search requires a non-empty "query".');
|
|
254
|
+
}
|
|
255
|
+
const fromMs = parseIsoDate(input.from, 'from');
|
|
256
|
+
const toMs = parseIsoDate(input.to, 'to');
|
|
257
|
+
const limit = clampCount(input.limit, SEARCH_DEFAULT_LIMIT, SEARCH_MAX_LIMIT, 'limit');
|
|
258
|
+
const maxScan = clampCount(input.maxScan, SEARCH_DEFAULT_MAX_SCAN, SEARCH_MAX_MAX_SCAN, 'maxScan');
|
|
259
|
+
const caseSensitive = input.caseSensitive ?? false;
|
|
260
|
+
const flags = caseSensitive ? '' : 'i';
|
|
261
|
+
// Validate regex SYNTAX up front — an invalid pattern is a clean tool
|
|
262
|
+
// error, not a crash mid-scan. This does NOT bound match TIME (a
|
|
263
|
+
// syntactically valid pattern can still backtrack catastrophically),
|
|
264
|
+
// which is why regex-mode matching itself runs on a worker below rather
|
|
265
|
+
// than here.
|
|
266
|
+
if (input.regex) {
|
|
267
|
+
try {
|
|
268
|
+
new RegExp(input.query, flags);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
throw new Error(`Invalid regex "${input.query}": ${error instanceof Error ? error.message : String(error)}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const needle = caseSensitive ? input.query : input.query.toLowerCase();
|
|
275
|
+
const cm = this.cm;
|
|
276
|
+
// Fetch one more than maxScan so an oversized candidate window is
|
|
277
|
+
// detected up front (before any matching is attempted), rather than
|
|
278
|
+
// silently scanning maxScan candidates and returning as if that were the
|
|
279
|
+
// whole window. See the tool description's `truncated` contract.
|
|
280
|
+
const probe = cm.queryMessagesByTimeAndChannel({
|
|
281
|
+
fromMs,
|
|
282
|
+
toMs,
|
|
283
|
+
channelId: input.channelId,
|
|
284
|
+
limit: maxScan + 1,
|
|
285
|
+
offset: 0,
|
|
286
|
+
});
|
|
287
|
+
const truncated = probe.messages.length > maxScan;
|
|
288
|
+
const candidates = truncated ? probe.messages.slice(0, maxScan) : probe.messages;
|
|
289
|
+
if (input.regex) {
|
|
290
|
+
// Regex matching against caller-supplied patterns is ReDoS-shaped: a
|
|
291
|
+
// pathological pattern (e.g. `(a+)+$`) can take catastrophically long
|
|
292
|
+
// against one candidate string, and a synchronous RegExp.exec() on
|
|
293
|
+
// this thread would block the WHOLE framework's event loop — every
|
|
294
|
+
// agent's turns, health checks, timers — for as long as it runs, with
|
|
295
|
+
// no way to interrupt it from this same thread. Route matching
|
|
296
|
+
// through a worker thread instead, which can be forcibly terminated
|
|
297
|
+
// on a deadline. See search-regex-worker.ts's header for the full
|
|
298
|
+
// rationale.
|
|
299
|
+
const { matches, scanned } = await this.searchWithRegexWorker(candidates, input.query, flags, limit);
|
|
300
|
+
return { success: true, data: { scanned, candidatePoolSize: candidates.length, truncated, matches } };
|
|
301
|
+
}
|
|
302
|
+
// Plain substring search (String.prototype.indexOf) is inherently
|
|
303
|
+
// linear in input length — no ReDoS-equivalent risk — so it stays
|
|
304
|
+
// in-process. This is also the common case, so it pays no worker-spawn
|
|
305
|
+
// overhead.
|
|
306
|
+
const matches = [];
|
|
307
|
+
let scanned = 0;
|
|
308
|
+
for (const msg of candidates) {
|
|
309
|
+
// Check the limit BEFORE doing any work for this candidate — not
|
|
310
|
+
// after pushing a match — so limit:0 (a valid clampCount value: it's
|
|
311
|
+
// >= 0) correctly yields zero matches instead of one. Checking
|
|
312
|
+
// post-push would always let through the match that first reaches
|
|
313
|
+
// the limit. Same fix mirrored in search-regex-worker.ts's loop.
|
|
314
|
+
if (matches.length >= limit)
|
|
315
|
+
break;
|
|
316
|
+
scanned++;
|
|
317
|
+
const text = flattenContent(msg.content);
|
|
318
|
+
const hit = matchSubstring(text, needle, caseSensitive);
|
|
319
|
+
if (!hit)
|
|
320
|
+
continue;
|
|
321
|
+
matches.push({
|
|
322
|
+
id: String(msg.id),
|
|
323
|
+
timestamp: msg.timestamp.toISOString(),
|
|
324
|
+
participant: msg.participant,
|
|
325
|
+
channelId: getChannelId(msg) ?? null,
|
|
326
|
+
snippet: snippetAround(text, hit.index, hit.length),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
data: { scanned, candidatePoolSize: candidates.length, truncated, matches },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Run regex matching for `search` on a worker thread with a hard
|
|
336
|
+
* wall-clock deadline, so a catastrophically-backtracking pattern can be
|
|
337
|
+
* forcibly killed instead of hanging the framework. One worker per call
|
|
338
|
+
* (not per candidate — spawn overhead would dominate at scale; not a
|
|
339
|
+
* persistent pool — a fresh worker per call means one bad pattern can
|
|
340
|
+
* never contaminate a later search). Always terminated on the way out,
|
|
341
|
+
* success or failure, so nothing lingers.
|
|
342
|
+
*
|
|
343
|
+
* On timeout or a worker-side error this THROWS (caught by
|
|
344
|
+
* handleToolCall's try/catch, same as every other error path in this
|
|
345
|
+
* module) rather than returning an empty match list — a timed-out search
|
|
346
|
+
* must never be indistinguishable from a clean "no matches" result.
|
|
347
|
+
*/
|
|
348
|
+
async searchWithRegexWorker(candidates, pattern, flags, limit) {
|
|
349
|
+
const texts = candidates.map((msg) => flattenContent(msg.content));
|
|
350
|
+
let worker;
|
|
351
|
+
try {
|
|
352
|
+
const { matches: rawMatches, scanned } = await new Promise((resolve, reject) => {
|
|
353
|
+
worker = new Worker(SEARCH_WORKER_PATH, { workerData: { texts, pattern, flags, limit } });
|
|
354
|
+
const timer = setTimeout(() => {
|
|
355
|
+
reject(new Error(`search timed out after ${SEARCH_REGEX_TIMEOUT_MS}ms while matching regex "${pattern}" against ` +
|
|
356
|
+
`up to ${texts.length} candidate(s) — the pattern may be catastrophically slow (exponential ` +
|
|
357
|
+
'backtracking) against this data; try a simpler pattern, a literal substring search ' +
|
|
358
|
+
'(regex:false), or a smaller maxScan.'));
|
|
359
|
+
}, SEARCH_REGEX_TIMEOUT_MS);
|
|
360
|
+
timer.unref?.();
|
|
361
|
+
worker.once('message', (msg) => {
|
|
362
|
+
clearTimeout(timer);
|
|
363
|
+
if (msg.type === 'error')
|
|
364
|
+
reject(new Error(msg.error));
|
|
365
|
+
else
|
|
366
|
+
resolve({ matches: msg.matches, scanned: msg.scanned });
|
|
367
|
+
});
|
|
368
|
+
worker.once('error', (err) => {
|
|
369
|
+
clearTimeout(timer);
|
|
370
|
+
reject(err);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
const matches = rawMatches.map(({ candidateIndex, matchIndex, matchLength }) => {
|
|
374
|
+
const msg = candidates[candidateIndex];
|
|
375
|
+
const text = texts[candidateIndex];
|
|
376
|
+
return {
|
|
377
|
+
id: String(msg.id),
|
|
378
|
+
timestamp: msg.timestamp.toISOString(),
|
|
379
|
+
participant: msg.participant,
|
|
380
|
+
channelId: getChannelId(msg) ?? null,
|
|
381
|
+
snippet: snippetAround(text, matchIndex, matchLength),
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
return { matches, scanned };
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
// Always kill the worker — whether it finished, errored, or is still
|
|
388
|
+
// stuck mid-backtrack when the deadline hit. terminate() on an
|
|
389
|
+
// already-exited worker is a harmless no-op.
|
|
390
|
+
if (worker)
|
|
391
|
+
void worker.terminate().catch(() => { });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// Helpers
|
|
397
|
+
// ============================================================================
|
|
398
|
+
/** Parse an ISO 8601 date string to Unix ms. Throws a clear error (not a
|
|
399
|
+
* crash) on an unparseable string; returns undefined for an omitted field
|
|
400
|
+
* (open-ended bound). */
|
|
401
|
+
function parseIsoDate(value, field) {
|
|
402
|
+
if (value === undefined)
|
|
403
|
+
return undefined;
|
|
404
|
+
const ms = Date.parse(value);
|
|
405
|
+
if (Number.isNaN(ms)) {
|
|
406
|
+
throw new Error(`Invalid ISO 8601 date for "${field}": ${JSON.stringify(value)}`);
|
|
407
|
+
}
|
|
408
|
+
return ms;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Validate a pagination-ish numeric input (limit/offset/maxScan) and clamp
|
|
412
|
+
* it to an upper bound. `Math.min(value, max)` alone is NOT sufficient here:
|
|
413
|
+
* it only enforces an upper bound, so `Math.min(-1, 200)` is `-1`, not a
|
|
414
|
+
* sane value — a negative limit/offset/maxScan would sail straight past the
|
|
415
|
+
* "hard cap" and reach a native chronicle call expecting an unsigned
|
|
416
|
+
* pagination argument (observed: `extract({limit:-1})` returning every
|
|
417
|
+
* message in the store instead of being capped). Rejects (rather than
|
|
418
|
+
* silently clamping) anything that isn't a finite non-negative integer, so
|
|
419
|
+
* a caller mistake is surfaced as a clean tool error instead of silently
|
|
420
|
+
* doing something other than what was asked.
|
|
421
|
+
*/
|
|
422
|
+
function clampCount(value, def, max, field) {
|
|
423
|
+
if (value === undefined)
|
|
424
|
+
return def;
|
|
425
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
426
|
+
throw new Error(`"${field}" must be a finite number, got ${JSON.stringify(value)}.`);
|
|
427
|
+
}
|
|
428
|
+
if (!Number.isInteger(value)) {
|
|
429
|
+
throw new Error(`"${field}" must be an integer, got ${value}.`);
|
|
430
|
+
}
|
|
431
|
+
if (value < 0) {
|
|
432
|
+
throw new Error(`"${field}" must be >= 0, got ${value}.`);
|
|
433
|
+
}
|
|
434
|
+
return Math.min(value, max);
|
|
435
|
+
}
|
|
436
|
+
/** channelId lives at metadata.external.channelId — same path context-manager's
|
|
437
|
+
* own native index reads from (message-store.ts's CHANNEL_FIELD). Metadata's
|
|
438
|
+
* index-signature type means this needs an explicit narrow, same as
|
|
439
|
+
* MessageStore.getChannelTokenStats does internally. */
|
|
440
|
+
/**
|
|
441
|
+
* Two channel-id metadata shapes exist in the wild: `metadata.channelId`
|
|
442
|
+
* (what agent-framework's real MCPL ingestion — `handleMcplChannelIncoming`
|
|
443
|
+
* — actually writes) and `metadata.external.channelId` (an older
|
|
444
|
+
* convention). context-manager 0.9.1+ indexes and queries BOTH (see
|
|
445
|
+
* MessageStore.extractChannelId there), so a channel-filtered result can
|
|
446
|
+
* come from either shape — check the same order here so a displayed
|
|
447
|
+
* `channelId` never reads `null` on a message that was, in fact, matched by
|
|
448
|
+
* channel.
|
|
449
|
+
*/
|
|
450
|
+
function getChannelId(msg) {
|
|
451
|
+
const metadata = msg.metadata;
|
|
452
|
+
if (typeof metadata?.channelId === 'string')
|
|
453
|
+
return metadata.channelId;
|
|
454
|
+
return typeof metadata?.external?.channelId === 'string' ? metadata.external.channelId : undefined;
|
|
455
|
+
}
|
|
456
|
+
function projectMessage(msg, format) {
|
|
457
|
+
return {
|
|
458
|
+
id: String(msg.id),
|
|
459
|
+
timestamp: msg.timestamp.toISOString(),
|
|
460
|
+
participant: msg.participant,
|
|
461
|
+
channelId: getChannelId(msg) ?? null,
|
|
462
|
+
content: format === 'raw' ? msg.content : flattenContent(msg.content),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/** Flatten a message's content blocks to one short, readable string. Text
|
|
466
|
+
* blocks verbatim; everything else (tool_use/tool_result/thinking/media) as
|
|
467
|
+
* a short bracketed label — this is for agent readability and search
|
|
468
|
+
* matching, not byte-faithful reconstruction. */
|
|
469
|
+
function flattenContent(content) {
|
|
470
|
+
return content.map(blockLabel).join(' ').trim();
|
|
471
|
+
}
|
|
472
|
+
function blockLabel(block) {
|
|
473
|
+
switch (block.type) {
|
|
474
|
+
case 'text':
|
|
475
|
+
return block.text;
|
|
476
|
+
case 'tool_use':
|
|
477
|
+
return `[tool_use: ${block.name}]`;
|
|
478
|
+
case 'tool_result':
|
|
479
|
+
return block.toolName ? `[tool_result: ${block.toolName}]` : '[tool_result]';
|
|
480
|
+
case 'thinking':
|
|
481
|
+
case 'redacted_thinking':
|
|
482
|
+
return '[thinking]';
|
|
483
|
+
case 'image':
|
|
484
|
+
return '[image]';
|
|
485
|
+
case 'generated_image':
|
|
486
|
+
return '[image]';
|
|
487
|
+
case 'document':
|
|
488
|
+
return '[document]';
|
|
489
|
+
case 'audio':
|
|
490
|
+
return '[audio]';
|
|
491
|
+
case 'video':
|
|
492
|
+
return '[video]';
|
|
493
|
+
default:
|
|
494
|
+
// Exhaustiveness guard: a future ContentBlock variant falls back to a
|
|
495
|
+
// generic label instead of a compile error at a call site far from here.
|
|
496
|
+
return `[${block.type}]`;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function matchSubstring(text, needle, caseSensitive) {
|
|
500
|
+
const haystack = caseSensitive ? text : text.toLowerCase();
|
|
501
|
+
const index = haystack.indexOf(needle);
|
|
502
|
+
if (index === -1)
|
|
503
|
+
return null;
|
|
504
|
+
return { index, length: needle.length };
|
|
505
|
+
}
|
|
506
|
+
/** ~SNIPPET_CONTEXT_CHARS of surrounding context on each side of a match, or
|
|
507
|
+
* the first ~SNIPPET_FALLBACK_CHARS of content when no meaningful match
|
|
508
|
+
* position is given (kept simple on purpose — this is a readability aid,
|
|
509
|
+
* not a highlighting engine). */
|
|
510
|
+
function snippetAround(text, index, length) {
|
|
511
|
+
if (index < 0)
|
|
512
|
+
return text.slice(0, SNIPPET_FALLBACK_CHARS);
|
|
513
|
+
const start = Math.max(0, index - SNIPPET_CONTEXT_CHARS);
|
|
514
|
+
const end = Math.min(text.length, index + length + SNIPPET_CONTEXT_CHARS);
|
|
515
|
+
const prefix = start > 0 ? '…' : '';
|
|
516
|
+
const suffix = end < text.length ? '…' : '';
|
|
517
|
+
return prefix + text.slice(start, end) + suffix;
|
|
518
|
+
}
|
|
519
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/modules/history/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAsC1C,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,MAAM,iBAAiB,GAAG,UAAU,CAAC,CAAC,aAAa;AAEnD,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAChC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAElC,+EAA+E;AAC/E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,mFAAmF;AACnF,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC;;;;;;;;GAQG;AACH,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAErC;;4DAE4D;AAC5D,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC;AAEnG,MAAM,OAAO,aAAa;IACf,IAAI,GAAG,SAAS,CAAC;IAElB,GAAG,GAAyB,IAAI,CAAC;IACjC,EAAE,GAA0B,IAAI,CAAC;IAEzC;;;;;;OAMG;IACH,IAAI,CAAC,cAA8B;QACjC,IAAI,CAAC,EAAE,GAAG,cAAc,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAkB;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,QAAQ;QACN,OAAO;YACL;gBACE,IAAI,EAAE,OAAO;gBACb,WAAW,EACT,kFAAkF;oBAClF,6FAA6F;oBAC7F,8FAA8F;oBAC9F,6FAA6F;oBAC7F,+CAA+C;gBACjD,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6EAA6E,EAAE;wBACpH,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6EAA6E,EAAE;wBAClH,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,qDAAqD,EAAE;qBAClG;iBACF;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,WAAW,EACT,4FAA4F;oBAC5F,+FAA+F;oBAC/F,uFAAuF;oBACvF,qDAAqD;gBACvD,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sDAAsD,EAAE;wBAC7F,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sDAAsD,EAAE;wBAC3F,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;wBACtE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,mCAAmC,qBAAqB,cAAc,iBAAiB,oCAAoC,EAAE;wBACnK,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,iBAAiB,oCAAoC,EAAE;wBAC3J,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,qCAAqC,EAAE;qBACtG;iBACF;aACF;YACD;gBACE,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,2FAA2F;oBAC3F,+FAA+F;oBAC/F,8FAA8F;oBAC9F,iGAAiG;oBACjG,gGAAgG;oBAChG,8FAA8F;gBAChG,WAAW,EAAE;oBACX,IAAI,EAAE,QAAiB;oBACvB,UAAU,EAAE;wBACV,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;wBACrG,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,sDAAsD,EAAE;wBAC/F,aAAa,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,uCAAuC,EAAE;wBACxF,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sDAAsD,EAAE;wBAC7F,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sDAAsD,EAAE;wBAC3F,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;wBACtE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kCAAkC,oBAAoB,cAAc,gBAAgB,oCAAoC,EAAE;wBAChK,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,2CAA2C,uBAAuB,cAAc,mBAAmB,oCAAoC,EAAE;qBAClL;oBACD,QAAQ,EAAE,CAAC,OAAO,CAAC;iBACpB;aACF;SACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAc;QACjC,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,qFAAqF,CAAC,CAAC;YACzG,CAAC;YACD,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;gBAClB,KAAK,OAAO;oBACV,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAe,CAAC,CAAC;gBAC5D,KAAK,SAAS;oBACZ,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAiB,CAAC,CAAC;gBAChE,KAAK,QAAQ;oBACX,OAAO,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAgB,CAAC,CAAC;gBACpE;oBACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAClF,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kEAAkE;YAClE,qEAAqE;YACrE,yDAAyD;YACzD,oEAAoE;YACpE,4DAA4D;YAC5D,qEAAqE;YACrE,wDAAwD;YACxD,yDAAyD;YACzD,qEAAqE;YACrE,gEAAgE;YAChE,mCAAmC;YACnC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAoB,EAAE,MAAoB;QACxD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,6EAA6E;IAC7E,QAAQ;IACR,6EAA6E;IAErE,WAAW,CAAC,KAAiB;QACnC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,EAAoB,CAAC;QAErC,IAAI,oBAAoB,GAAmB,EAAE,CAAC,uBAAuB,EAAE,CAAC;QACxE,IAAI,kBAAkB,GAAsB,EAAE,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtF,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,oBAAoB,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;YAC3F,kBAAkB,GAAG;gBACnB,2EAA2E;gBAC3E,2EAA2E;gBAC3E,uEAAuE;gBACvE,kEAAkE;gBAClE,4CAA4C;gBAC5C,aAAa,EAAE,kBAAkB,CAAC,aAAa;gBAC/C,mBAAmB,EAAE,kBAAkB,CAAC,mBAAmB;gBAC3D,SAAS,EAAE,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC;aACvF,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,EAAE;gBAC7F,oBAAoB;gBACpB,kBAAkB;aACnB;SACF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,UAAU;IACV,6EAA6E;IAErE,aAAa,CAAC,KAAmB;QACvC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC;QACzF,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,iBAAiB,EAAE,QAAQ,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC;QAEtC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAoB,CAAC;QACrC,MAAM,MAAM,GAAG,EAAE,CAAC,6BAA6B,CAAC;YAC9C,MAAM;YACN,IAAI;YACJ,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,KAAK;YACL,MAAM;SACP,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;gBAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;aACpE;SACF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,SAAS;IACT,6EAA6E;IAErE,KAAK,CAAC,YAAY,CAAC,KAAkB;QAC3C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACvF,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC;QACnG,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,KAAK,CAAC;QACnD,MAAM,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QAEvC,sEAAsE;QACtE,iEAAiE;QACjE,qEAAqE;QACrE,wEAAwE;QACxE,aAAa;QACb,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,CAAC,KAAK,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC/G,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAEvE,MAAM,EAAE,GAAG,IAAI,CAAC,EAAoB,CAAC;QACrC,kEAAkE;QAClE,oEAAoE;QACpE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,KAAK,GAAG,EAAE,CAAC,6BAA6B,CAAC;YAC7C,MAAM;YACN,IAAI;YACJ,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,KAAK,EAAE,OAAO,GAAG,CAAC;YAClB,MAAM,EAAE,CAAC;SACV,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC;QAClD,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;QAEjF,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,qEAAqE;YACrE,sEAAsE;YACtE,mEAAmE;YACnE,mEAAmE;YACnE,sEAAsE;YACtE,+DAA+D;YAC/D,oEAAoE;YACpE,kEAAkE;YAClE,aAAa;YACb,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACrG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC;QACxG,CAAC;QAED,kEAAkE;QAClE,kEAAkE;QAClE,uEAAuE;QACvE,YAAY;QACZ,MAAM,OAAO,GAAkB,EAAE,CAAC;QAClC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,iEAAiE;YACjE,qEAAqE;YACrE,+DAA+D;YAC/D,kEAAkE;YAClE,iEAAiE;YACjE,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK;gBAAE,MAAM;YACnC,OAAO,EAAE,CAAC;YACV,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACzC,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;YACxD,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,OAAO,CAAC,IAAI,CAAC;gBACX,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClB,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;gBACtC,WAAW,EAAE,GAAG,CAAC,WAAW;gBAC5B,SAAS,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,IAAI;gBACpC,OAAO,EAAE,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;aACpD,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE;SAC5E,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,KAAK,CAAC,qBAAqB,CACjC,UAA2B,EAC3B,OAAe,EACf,KAAa,EACb,KAAa;QAEb,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;QACnE,IAAI,MAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,OAAO,CACxD,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAClB,MAAM,GAAG,IAAI,MAAM,CAAC,kBAAkB,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC1F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,MAAM,CACJ,IAAI,KAAK,CACP,0BAA0B,uBAAuB,4BAA4B,OAAO,YAAY;wBAC9F,SAAS,KAAK,CAAC,MAAM,wEAAwE;wBAC7F,qFAAqF;wBACrF,sCAAsC,CACzC,CACF,CAAC;gBACJ,CAAC,EAAE,uBAAuB,CAAC,CAAC;gBAC5B,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAwB,EAAE,EAAE;oBAClD,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;wBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;;wBAClD,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/D,CAAC,CAAC,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBAC3B,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC,CAAC,CAAC;YACL,CAAC,CACF,CAAC;YAEF,MAAM,OAAO,GAAkB,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE;gBAC5F,MAAM,GAAG,GAAG,UAAU,CAAC,cAAc,CAAE,CAAC;gBACxC,MAAM,IAAI,GAAG,KAAK,CAAC,cAAc,CAAE,CAAC;gBACpC,OAAO;oBACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClB,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;oBACtC,WAAW,EAAE,GAAG,CAAC,WAAW;oBAC5B,SAAS,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,IAAI;oBACpC,OAAO,EAAE,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC;iBACtD,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAC9B,CAAC;gBAAS,CAAC;YACT,qEAAqE;YACrE,+DAA+D;YAC/D,6CAA6C;YAC7C,IAAI,MAAM;gBAAE,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E;;0BAE0B;AAC1B,SAAS,YAAY,CAAC,KAAyB,EAAE,KAAa;IAC5D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,UAAU,CAAC,KAAyB,EAAE,GAAW,EAAE,GAAW,EAAE,KAAa;IACpF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,kCAAkC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,6BAA6B,KAAK,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,KAAK,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED;;;yDAGyD;AACzD;;;;;;;;;GASG;AACH,SAAS,YAAY,CAAC,GAAkB;IACtC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAmF,CAAC;IACzG,IAAI,OAAO,QAAQ,EAAE,SAAS,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC,SAAS,CAAC;IACvE,OAAO,OAAO,QAAQ,EAAE,QAAQ,EAAE,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AACrG,CAAC;AAED,SAAS,cAAc,CAAC,GAAkB,EAAE,MAAsB;IAChE,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;QACtC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,SAAS,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,IAAI;QACpC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;KACtE,CAAC;AACJ,CAAC;AAED;;;kDAGkD;AAClD,SAAS,cAAc,CAAC,OAAuB;IAC7C,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,UAAU,CAAC,KAAmB;IACrC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,IAAI,CAAC;QACpB,KAAK,UAAU;YACb,OAAO,cAAc,KAAK,CAAC,IAAI,GAAG,CAAC;QACrC,KAAK,aAAa;YAChB,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,eAAe,CAAC;QAC/E,KAAK,UAAU,CAAC;QAChB,KAAK,mBAAmB;YACtB,OAAO,YAAY,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,iBAAiB;YACpB,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,YAAY,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB;YACE,sEAAsE;YACtE,yEAAyE;YACzE,OAAO,IAAK,KAA0B,CAAC,IAAI,GAAG,CAAC;IACnD,CAAC;AACH,CAAC;AAiBD,SAAS,cAAc,CAAC,IAAY,EAAE,MAAc,EAAE,aAAsB;IAC1E,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED;;;kCAGkC;AAClC,SAAS,aAAa,CAAC,IAAY,EAAE,KAAa,EAAE,MAAc;IAChE,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,qBAAqB,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,OAAO,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SearchWorkerJob {
|
|
2
|
+
/** Flattened candidate text, aligned by index with the parent's candidate array. */
|
|
3
|
+
texts: string[];
|
|
4
|
+
pattern: string;
|
|
5
|
+
flags: string;
|
|
6
|
+
/** Stop scanning once this many matches are found. */
|
|
7
|
+
limit: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchWorkerMatch {
|
|
10
|
+
/** Index into `texts` / the parent's candidate array. */
|
|
11
|
+
candidateIndex: number;
|
|
12
|
+
matchIndex: number;
|
|
13
|
+
matchLength: number;
|
|
14
|
+
}
|
|
15
|
+
export type SearchWorkerMessage = {
|
|
16
|
+
type: 'done';
|
|
17
|
+
matches: SearchWorkerMatch[];
|
|
18
|
+
scanned: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'error';
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=search-regex-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-regex-worker.d.ts","sourceRoot":"","sources":["../../../../src/modules/history/search-regex-worker.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search-regex-worker — runs a caller-supplied regex against a batch of
|
|
3
|
+
* message text on a separate thread, so HistoryModule's `search` tool can
|
|
4
|
+
* forcibly terminate a catastrophically-backtracking pattern (ReDoS)
|
|
5
|
+
* instead of blocking the framework's single-threaded event loop.
|
|
6
|
+
*
|
|
7
|
+
* Why a worker and not a Promise.race/setTimeout "timeout": JS is
|
|
8
|
+
* single-threaded, so nothing on the SAME thread can interrupt a
|
|
9
|
+
* synchronous RegExp.exec() call already in flight — a timeout callback
|
|
10
|
+
* racing it can't even fire until the blocking call returns, by which point
|
|
11
|
+
* it's too late. A worker thread is real OS-level concurrency: the parent's
|
|
12
|
+
* event loop keeps running, and `worker.terminate()` actually kills the
|
|
13
|
+
* stuck thread rather than just giving up on waiting for it.
|
|
14
|
+
*
|
|
15
|
+
* Only regex-mode search routes through this worker. Plain substring search
|
|
16
|
+
* (String.prototype.indexOf) is inherently linear in input length and has
|
|
17
|
+
* no equivalent risk, so it stays in-process — the common case pays no
|
|
18
|
+
* worker-spawn overhead.
|
|
19
|
+
*
|
|
20
|
+
* One-shot: the whole job (already-flattened text, already syntax-validated
|
|
21
|
+
* pattern/flags, match limit) arrives via `workerData` at construction; this
|
|
22
|
+
* script runs it to completion and posts exactly one message, then the
|
|
23
|
+
* parent always terminates the worker (whether it finished normally or had
|
|
24
|
+
* to be killed for running long) — no persistent pool, so a wedged worker
|
|
25
|
+
* from one bad pattern can never contaminate a later call.
|
|
26
|
+
*/
|
|
27
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
28
|
+
const { texts, pattern, flags, limit } = workerData;
|
|
29
|
+
try {
|
|
30
|
+
// Re-construct (not re-validate for safety — RegExp syntax is what it is;
|
|
31
|
+
// the parent already rejected an uncompilable pattern before ever getting
|
|
32
|
+
// here) the matcher on this thread. Compilation succeeding doesn't bound
|
|
33
|
+
// MATCHING time — that's exactly the risk this worker exists to contain.
|
|
34
|
+
const matcher = new RegExp(pattern, flags);
|
|
35
|
+
const matches = [];
|
|
36
|
+
let scanned = 0;
|
|
37
|
+
for (let i = 0; i < texts.length; i++) {
|
|
38
|
+
// Check the limit BEFORE doing any work for this candidate — not after
|
|
39
|
+
// pushing a match — so limit:0 (a valid clampCount value: it's >= 0)
|
|
40
|
+
// correctly yields zero matches instead of one. Checking post-push would
|
|
41
|
+
// always let through the match that first reaches the limit.
|
|
42
|
+
if (matches.length >= limit)
|
|
43
|
+
break;
|
|
44
|
+
scanned++;
|
|
45
|
+
const m = matcher.exec(texts[i] ?? '');
|
|
46
|
+
if (m) {
|
|
47
|
+
matches.push({ candidateIndex: i, matchIndex: m.index, matchLength: m[0].length });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const done = { type: 'done', matches, scanned };
|
|
51
|
+
parentPort?.postMessage(done);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
const errMsg = { type: 'error', error: e instanceof Error ? e.message : String(e) };
|
|
55
|
+
parentPort?.postMessage(errMsg);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=search-regex-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-regex-worker.js","sourceRoot":"","sources":["../../../../src/modules/history/search-regex-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAsB7D,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,UAA6B,CAAC;AAEvE,IAAI,CAAC;IACH,0EAA0E;IAC1E,0EAA0E;IAC1E,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,uEAAuE;QACvE,qEAAqE;QACrE,yEAAyE;QACzE,6DAA6D;QAC7D,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;QACnC,OAAO,EAAE,CAAC;QACV,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACrE,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAAC,OAAO,CAAC,EAAE,CAAC;IACX,MAAM,MAAM,GAAwB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACzG,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -5,6 +5,7 @@ export { ApiModule } from './api/index.js';
|
|
|
5
5
|
export type { ApiEvent } from './api/index.js';
|
|
6
6
|
export { HealthModule } from './health/index.js';
|
|
7
7
|
export type { HealthModuleConfig } from './health/index.js';
|
|
8
|
+
export { HistoryModule } from './history/index.js';
|
|
8
9
|
export { WorkspaceModule, WorkspaceReadError } from './workspace/index.js';
|
|
9
10
|
export type { WorkspaceReadErrorCode, WorkspaceReadStage, WorkspaceDiskReadResult, ReadFileFromDiskOptions } from './workspace/index.js';
|
|
10
11
|
export type { WorkspaceConfig, MountConfig, MountState, WorkspaceModuleState, ReadInput, WriteInput, EditInput, DeleteInput, LsInput, GlobInput, GrepInput, StatusInput, MaterializeInput, SyncInput, } from './workspace/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACzI,YAAY,EACV,eAAe,EACf,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,OAAO,EACP,SAAS,EACT,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,SAAS,GACV,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,IAAI,gBAAgB,EAC7B,WAAW,EACX,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC3E,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACzI,YAAY,EACV,eAAe,EACf,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,OAAO,EACP,SAAS,EACT,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,SAAS,GACV,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,IAAI,gBAAgB,EAC7B,WAAW,EACX,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC"}
|