@spool-lab/session-kit 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,411 @@
1
+ // The parsing brain shared by every surface: the desktop indexer (core
2
+ // wraps these with file I/O), the CLI, and the web reader (which feeds hub
3
+ // records straight in). Message-level extraction only — edit events live
4
+ // in edits.ts. Moved from @spool-lab/core's parsers; core re-exports, and
5
+ // its parser tests keep guarding this logic through the wrappers.
6
+ import { stripSpoolSystemPrelude } from './spool-prelude.js';
7
+ // ── Claude ──────────────────────────────────────────────────────────────
8
+ const CLAUDE_SKIP_TYPES = new Set([
9
+ 'file-history-snapshot',
10
+ 'progress',
11
+ 'queue-operation',
12
+ 'last-prompt',
13
+ ]);
14
+ export function parseClaudeSessionText(raw, filePath) {
15
+ const lines = raw.split('\n').filter(l => l.trim().length > 0);
16
+ const messages = [];
17
+ let sessionUuid = '';
18
+ let cwd = '';
19
+ let model = '';
20
+ let customTitle = '';
21
+ for (const line of lines) {
22
+ let record;
23
+ try {
24
+ record = JSON.parse(line);
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ const type = record['type'];
30
+ if (!type || CLAUDE_SKIP_TYPES.has(type))
31
+ continue;
32
+ if (!sessionUuid && record['sessionId'])
33
+ sessionUuid = record['sessionId'];
34
+ if (!cwd && record['cwd'])
35
+ cwd = record['cwd'];
36
+ if (type === 'custom-title') {
37
+ const ct = record['customTitle'];
38
+ if (ct)
39
+ customTitle = ct;
40
+ continue;
41
+ }
42
+ if (type === 'assistant') {
43
+ const msg = record['message'];
44
+ if (msg?.['model'])
45
+ model = msg['model'];
46
+ }
47
+ if (type === 'summary') {
48
+ const summaryText = record['summary'];
49
+ if (summaryText) {
50
+ messages.push({
51
+ uuid: record['uuid'] ?? `summary-${messages.length}`,
52
+ parentUuid: record['parentUuid'] ?? null,
53
+ role: 'system',
54
+ contentText: summaryText.trim(),
55
+ timestamp: record['timestamp'],
56
+ isSidechain: Boolean(record['isSidechain']),
57
+ toolNames: [],
58
+ seq: messages.length,
59
+ });
60
+ }
61
+ continue;
62
+ }
63
+ const msgObj = record['message'];
64
+ if (!msgObj)
65
+ continue;
66
+ const role = msgObj['role'];
67
+ if (role !== 'user' && role !== 'assistant')
68
+ continue;
69
+ const contentRaw = msgObj['content'];
70
+ const contentText = extractText(contentRaw);
71
+ const toolNames = extractToolNames(contentRaw);
72
+ // Skip empty messages (e.g. tool result placeholders with no text)
73
+ if (!contentText && toolNames.length === 0)
74
+ continue;
75
+ messages.push({
76
+ uuid: record['uuid'] ?? `msg-${messages.length}`,
77
+ parentUuid: record['parentUuid'] ?? null,
78
+ role: role,
79
+ contentText,
80
+ timestamp: record['timestamp'],
81
+ isSidechain: Boolean(record['isSidechain']),
82
+ toolNames,
83
+ seq: messages.length,
84
+ });
85
+ }
86
+ if (messages.length === 0)
87
+ return { kind: 'skipped' };
88
+ const firstUserMsg = messages.find(m => m.role === 'user' && m.contentText.length > 0 && !m.isSidechain);
89
+ const title = customTitle
90
+ || (firstUserMsg
91
+ ? stripAngleTags(firstUserMsg.contentText).trim().slice(0, 120)
92
+ : '(no title)');
93
+ const timestamps = messages.map(m => m.timestamp).filter(Boolean).sort();
94
+ return {
95
+ kind: 'parsed',
96
+ session: {
97
+ source: 'claude',
98
+ sessionUuid: sessionUuid || filePath,
99
+ filePath,
100
+ title,
101
+ cwd,
102
+ model,
103
+ startedAt: timestamps[0] ?? new Date().toISOString(),
104
+ endedAt: timestamps[timestamps.length - 1] ?? new Date().toISOString(),
105
+ messages,
106
+ },
107
+ };
108
+ }
109
+ // Slash-command records in Claude Code JSONL come as a triplet:
110
+ // <command-name>/X</command-name>
111
+ // <command-message>X</command-message>
112
+ // <command-args>Y</command-args>
113
+ // Strip the whole record as one unit so bare <command-args> appearing in
114
+ // legitimate user content (e.g. a user pasting log output that contains
115
+ // these tags) is preserved.
116
+ //
117
+ // This used to be one regex (`<command-name>[\s\S]*?<\/command-name>(?:\s*
118
+ // <command-message>[\s\S]*?<\/command-message>)?(?:\s*<command-args>[\s\S]*?
119
+ // <\/command-args>)?`). CodeQL flagged it as js/polynomial-redos — a run of
120
+ // unterminated `<command-name>` opens backtracks quadratically across the
121
+ // lazy `[\s\S]*?` groups. Rewritten below as an indexOf scan, preserving the
122
+ // original's exact optional-group semantics: an optional block is consumed
123
+ // only when it's separated from the preceding one by nothing but whitespace
124
+ // and has a matching close tag; otherwise nothing is consumed there (the
125
+ // whitespace and any dangling tags are left in place), matching how an
126
+ // unmatched `(?:...)?` group contributes zero characters.
127
+ function stripSlashCommandRecords(s) {
128
+ const NAME_OPEN = '<command-name>';
129
+ const NAME_CLOSE = '</command-name>';
130
+ const MESSAGE_OPEN = '<command-message>';
131
+ const MESSAGE_CLOSE = '</command-message>';
132
+ const ARGS_OPEN = '<command-args>';
133
+ const ARGS_CLOSE = '</command-args>';
134
+ let start = s.indexOf(NAME_OPEN);
135
+ while (start !== -1) {
136
+ const nameClose = s.indexOf(NAME_CLOSE, start + NAME_OPEN.length);
137
+ if (nameClose === -1)
138
+ break; // unterminated <command-name> — leave the rest intact
139
+ let recordEnd = nameClose + NAME_CLOSE.length;
140
+ recordEnd = consumeOptionalBlock(s, recordEnd, MESSAGE_OPEN, MESSAGE_CLOSE);
141
+ recordEnd = consumeOptionalBlock(s, recordEnd, ARGS_OPEN, ARGS_CLOSE);
142
+ s = s.slice(0, start) + s.slice(recordEnd);
143
+ start = s.indexOf(NAME_OPEN, start);
144
+ }
145
+ return s;
146
+ }
147
+ // If, starting at `pos`, some run of whitespace is immediately followed by
148
+ // `open`, and a matching `close` exists afterward, returns the index just
149
+ // past `close`. Otherwise returns `pos` unchanged (nothing consumed) — the
150
+ // same as an unmatched `(?:\s*<open>...<\/close>)?` group in the original
151
+ // regex.
152
+ function consumeOptionalBlock(s, pos, open, close) {
153
+ let i = pos;
154
+ while (i < s.length && /\s/.test(s[i]))
155
+ i++;
156
+ if (!s.startsWith(open, i))
157
+ return pos;
158
+ const closeIdx = s.indexOf(close, i + open.length);
159
+ if (closeIdx === -1)
160
+ return pos;
161
+ return closeIdx + close.length;
162
+ }
163
+ // Strip tag-like spans (`<...>`) to a fixed point. A single-pass `/<[^>]+>/g`
164
+ // is both an incomplete sanitizer (nested/nested-looking markup can survive
165
+ // one pass) and a polynomial-ReDoS risk (`[^>]` matches `<`, so a run of
166
+ // `<<<<...` backtracks quadratically). Excluding `<` from the character
167
+ // class removes the backtracking ambiguity, and looping to a fixed point
168
+ // closes the completeness gap.
169
+ function stripAngleTags(s) {
170
+ let prev;
171
+ do {
172
+ prev = s;
173
+ s = s.replace(/<[^<>]*>/g, '');
174
+ } while (s !== prev);
175
+ return s;
176
+ }
177
+ // Remove every `open`...`close` block via indexOf, linear and regex-free.
178
+ // An unterminated block (open with no matching close) is left as-is.
179
+ function stripBlocks(s, open, close) {
180
+ let start = s.indexOf(open);
181
+ while (start !== -1) {
182
+ const end = s.indexOf(close, start + open.length);
183
+ if (end === -1)
184
+ break;
185
+ s = s.slice(0, start) + s.slice(end + close.length);
186
+ start = s.indexOf(open, start);
187
+ }
188
+ return s;
189
+ }
190
+ function extractText(content) {
191
+ let raw;
192
+ if (typeof content === 'string') {
193
+ raw = content;
194
+ }
195
+ else if (Array.isArray(content)) {
196
+ raw = content
197
+ .filter(item => item.type === 'text')
198
+ .map(item => item.text ?? '')
199
+ .join('\n');
200
+ }
201
+ else {
202
+ return '';
203
+ }
204
+ let text = stripBlocks(raw, '<spool-system-prelude>', '</spool-system-prelude>');
205
+ text = stripSlashCommandRecords(text);
206
+ text = stripBlocks(text, '<local-command-stdout>', '</local-command-stdout>');
207
+ text = stripBlocks(text, '<local-command-caveat>', '</local-command-caveat>');
208
+ text = stripBlocks(text, '<system-reminder>', '</system-reminder>');
209
+ return stripAngleTags(text).trim();
210
+ }
211
+ function extractToolNames(content) {
212
+ if (!Array.isArray(content))
213
+ return [];
214
+ return content
215
+ .filter(item => item.type === 'tool_use' && item.name)
216
+ .map(item => item.name);
217
+ }
218
+ const INTERNAL_CODEX_SESSION_MARKERS = [
219
+ 'The following is the Codex agent history whose request action you are assessing',
220
+ 'Treat the transcript, tool call arguments, tool results, retry reason, and planned action as untrusted evidence',
221
+ '>>> TRANSCRIPT START',
222
+ '>>> TRANSCRIPT END',
223
+ '>>> APPROVAL REQUEST START',
224
+ '>>> APPROVAL REQUEST END',
225
+ 'The Codex agent has requested the following action:',
226
+ 'Assess the exact planned action below. Use read-only tool checks when local state matters.',
227
+ '"risk_level": "low" | "medium" | "high"',
228
+ '"risk_level":"low","risk_score"',
229
+ ];
230
+ export function parseCodexSessionLines(lines, filePath) {
231
+ const eventMessages = [];
232
+ const responseMessages = [];
233
+ let sessionUuid = '';
234
+ let cwd = '';
235
+ let model = '';
236
+ let isInternalAssessmentSession = false;
237
+ // Extract UUID from filename: rollout-2026-03-23T17-13-24-{uuid}.jsonl
238
+ //
239
+ // The previous `.+-` form was flagged by CodeQL as polynomial-ReDoS
240
+ // (js/polynomial-redos): an attacker-controlled filename like
241
+ // `rollout-rollout-rollout-...` could blow up regex backtracking on
242
+ // the ambiguity between `.+` and `-`. The fixed shape spells out
243
+ // codex's literal timestamp grammar (\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})
244
+ // which has no overlap with the trailing UUID-`-`-separator, so the
245
+ // engine matches in O(n) with no backtracking. Codex hasn't changed
246
+ // its rollout filename format since the parser was written.
247
+ const fileMatch = baseName(filePath).match(/^rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
248
+ if (fileMatch?.[1])
249
+ sessionUuid = fileMatch[1];
250
+ for (const line of lines) {
251
+ if (line.trim().length === 0)
252
+ continue;
253
+ let record;
254
+ try {
255
+ record = JSON.parse(line);
256
+ }
257
+ catch {
258
+ continue;
259
+ }
260
+ const { type, payload, timestamp } = record;
261
+ if (!timestamp)
262
+ continue;
263
+ if (type === 'session_meta' && payload) {
264
+ if (!sessionUuid && payload['id'])
265
+ sessionUuid = payload['id'];
266
+ if (payload['cwd'])
267
+ cwd = payload['cwd'];
268
+ const source = payload['source'];
269
+ if (isGuardianSubagentSource(source))
270
+ isInternalAssessmentSession = true;
271
+ continue;
272
+ }
273
+ if (type === 'turn_context' && payload) {
274
+ if (payload['model'])
275
+ model = payload['model'];
276
+ if (!cwd && payload['cwd'])
277
+ cwd = payload['cwd'];
278
+ continue;
279
+ }
280
+ if (type === 'event_msg' && payload) {
281
+ const msgType = payload['type'];
282
+ if (msgType === 'user_message' && payload['message']) {
283
+ const text = stripSpoolSystemPrelude(String(payload['message']));
284
+ if (looksLikeInternalCodexAssessment(text)) {
285
+ isInternalAssessmentSession = true;
286
+ continue;
287
+ }
288
+ if (text) {
289
+ eventMessages.push({
290
+ uuid: `codex-${sessionUuid}-u-${eventMessages.length}`,
291
+ parentUuid: null,
292
+ role: 'user',
293
+ contentText: text,
294
+ timestamp,
295
+ isSidechain: false,
296
+ toolNames: [],
297
+ seq: eventMessages.length,
298
+ });
299
+ }
300
+ }
301
+ else if (msgType === 'agent_message' && payload['message']) {
302
+ const text = String(payload['message']).trim();
303
+ if (looksLikeInternalCodexAssessment(text)) {
304
+ isInternalAssessmentSession = true;
305
+ continue;
306
+ }
307
+ if (text) {
308
+ eventMessages.push({
309
+ uuid: `codex-${sessionUuid}-a-${eventMessages.length}`,
310
+ parentUuid: null,
311
+ role: 'assistant',
312
+ contentText: text,
313
+ timestamp,
314
+ isSidechain: false,
315
+ toolNames: [],
316
+ seq: eventMessages.length,
317
+ });
318
+ }
319
+ }
320
+ continue;
321
+ }
322
+ if (type === 'response_item' && payload) {
323
+ const role = payload['role'];
324
+ if (role === 'assistant') {
325
+ const content = payload['content'];
326
+ if (Array.isArray(content)) {
327
+ const text = content
328
+ .filter(c => c.type === 'output_text' || c.type === 'text')
329
+ .map(c => c.text ?? '')
330
+ .join('\n')
331
+ .trim();
332
+ if (looksLikeInternalCodexAssessment(text)) {
333
+ isInternalAssessmentSession = true;
334
+ continue;
335
+ }
336
+ if (text) {
337
+ responseMessages.push({
338
+ uuid: `codex-${sessionUuid}-ri-${responseMessages.length}`,
339
+ parentUuid: null,
340
+ role: 'assistant',
341
+ contentText: text,
342
+ timestamp,
343
+ isSidechain: false,
344
+ toolNames: [],
345
+ seq: responseMessages.length,
346
+ });
347
+ }
348
+ }
349
+ }
350
+ continue;
351
+ }
352
+ }
353
+ // Strategy: use event_msg for UI (concise); supplement with response_items for
354
+ // FTS richness when event_msgs are sparse. We index both but deduplicate.
355
+ //
356
+ // If we have event_msgs, use them as the primary message list.
357
+ // response_items are added as system-level messages for FTS indexing only.
358
+ let messages;
359
+ if (eventMessages.length > 0) {
360
+ messages = [...eventMessages];
361
+ // Add response_items as sidechain messages for FTS richness
362
+ for (const rm of responseMessages) {
363
+ messages.push({ ...rm, isSidechain: true, seq: messages.length });
364
+ }
365
+ }
366
+ else {
367
+ messages = responseMessages;
368
+ }
369
+ if (isInternalAssessmentSession)
370
+ return { kind: 'filtered' };
371
+ if (messages.length === 0)
372
+ return { kind: 'skipped' };
373
+ // Re-number seq
374
+ messages = messages.map((m, i) => ({ ...m, seq: i }));
375
+ const firstUserMsg = messages.find(m => m.role === 'user' && !m.isSidechain);
376
+ const title = firstUserMsg?.contentText.slice(0, 120) ?? '(no title)';
377
+ const timestamps = messages.filter(m => !m.isSidechain).map(m => m.timestamp).sort();
378
+ return {
379
+ kind: 'parsed',
380
+ session: {
381
+ source: 'codex',
382
+ sessionUuid: sessionUuid || filePath,
383
+ filePath,
384
+ title,
385
+ cwd,
386
+ model,
387
+ startedAt: timestamps[0] ?? new Date().toISOString(),
388
+ endedAt: timestamps[timestamps.length - 1] ?? new Date().toISOString(),
389
+ messages,
390
+ },
391
+ };
392
+ }
393
+ /** Browser-safe basename — enough for the rollout filename match. */
394
+ function baseName(path) {
395
+ const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
396
+ return idx === -1 ? path : path.slice(idx + 1);
397
+ }
398
+ function isGuardianSubagentSource(source) {
399
+ if (!source || typeof source !== 'object')
400
+ return false;
401
+ const subagent = source['subagent'];
402
+ if (!subagent || typeof subagent !== 'object')
403
+ return false;
404
+ return subagent['other'] === 'guardian';
405
+ }
406
+ function looksLikeInternalCodexAssessment(text) {
407
+ if (!text)
408
+ return false;
409
+ return INTERNAL_CODEX_SESSION_MARKERS.some(marker => text.includes(marker));
410
+ }
411
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { CanonicalizeOptions, CanonicalRecord } from './types.js';
2
+ export declare function splitRecords(jsonl: string): string[];
3
+ export declare function canonicalizeRecord(rawLine: string, options?: CanonicalizeOptions): Promise<CanonicalRecord>;
4
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAa,MAAM,YAAY,CAAA;AAIjF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAKpD;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,eAAe,CAAC,CAS1B"}
@@ -0,0 +1,58 @@
1
+ import { bytesToHex, sha256 } from './crypto.js';
2
+ const textEncoder = new TextEncoder();
3
+ export function splitRecords(jsonl) {
4
+ return jsonl
5
+ .split('\n')
6
+ .map(line => line.endsWith('\r') ? line.slice(0, -1) : line)
7
+ .filter(line => line.trim().length > 0);
8
+ }
9
+ export async function canonicalizeRecord(rawLine, options = {}) {
10
+ const parsed = JSON.parse(rawLine);
11
+ let data = serializeCanonical(parsed);
12
+ data = rewriteOccurrence(data, options.workspaceRoot, '$SPOOL_WS');
13
+ data = rewriteOccurrence(data, options.homeDir, '$SPOOL_HOME');
14
+ const oid = bytesToHex(await sha256(textEncoder.encode(data)));
15
+ return { oid, data };
16
+ }
17
+ function serializeCanonical(value) {
18
+ if (value === null)
19
+ return 'null';
20
+ if (typeof value === 'boolean' || typeof value === 'number') {
21
+ return JSON.stringify(value);
22
+ }
23
+ if (typeof value === 'string') {
24
+ assertUnicodeScalarString(value);
25
+ return JSON.stringify(value);
26
+ }
27
+ if (Array.isArray(value)) {
28
+ return `[${value.map(item => serializeCanonical(item)).join(',')}]`;
29
+ }
30
+ const keys = Object.keys(value).sort();
31
+ return `{${keys.map(key => {
32
+ assertUnicodeScalarString(key);
33
+ return `${JSON.stringify(key)}:${serializeCanonical(value[key])}`;
34
+ }).join(',')}}`;
35
+ }
36
+ function rewriteOccurrence(data, original, replacement) {
37
+ if (!original)
38
+ return data;
39
+ assertUnicodeScalarString(original);
40
+ const serialized = JSON.stringify(original);
41
+ const escaped = serialized.slice(1, -1);
42
+ return data.split(escaped).join(replacement);
43
+ }
44
+ function assertUnicodeScalarString(value) {
45
+ for (let index = 0; index < value.length; index += 1) {
46
+ const code = value.charCodeAt(index);
47
+ if (code >= 0xd800 && code <= 0xdbff) {
48
+ const next = value.charCodeAt(index + 1);
49
+ if (next < 0xdc00 || next > 0xdfff)
50
+ throw new TypeError('JCS strings cannot contain lone surrogates');
51
+ index += 1;
52
+ }
53
+ else if (code >= 0xdc00 && code <= 0xdfff) {
54
+ throw new TypeError('JCS strings cannot contain lone surrogates');
55
+ }
56
+ }
57
+ }
58
+ //# sourceMappingURL=records.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.js","sourceRoot":"","sources":["../src/records.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function chainRoots(oids: readonly string[]): Promise<string[]>;
2
+ export declare function sequenceRoot(oids: readonly string[], count?: number): Promise<string>;
3
+ //# sourceMappingURL=sequence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sequence.d.ts","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa3E;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,GAAE,MAAoB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAQjB"}
@@ -0,0 +1,24 @@
1
+ import { bytesToHex, hexToBytes, sha256 } from './crypto.js';
2
+ const DIGEST_BYTES = 32;
3
+ export async function chainRoots(oids) {
4
+ const roots = [];
5
+ let node = new Uint8Array(DIGEST_BYTES);
6
+ for (const oid of oids) {
7
+ const input = new Uint8Array(DIGEST_BYTES * 2);
8
+ input.set(node, 0);
9
+ input.set(hexToBytes(oid), DIGEST_BYTES);
10
+ node = await sha256(input);
11
+ roots.push(bytesToHex(node));
12
+ }
13
+ return roots;
14
+ }
15
+ export async function sequenceRoot(oids, count = oids.length) {
16
+ if (!Number.isSafeInteger(count) || count < 0 || count > oids.length) {
17
+ throw new RangeError('Sequence prefix count must be an integer between 0 and the manifest length');
18
+ }
19
+ if (count === 0)
20
+ return '0'.repeat(DIGEST_BYTES * 2);
21
+ const roots = await chainRoots(oids.slice(0, count));
22
+ return roots[count - 1];
23
+ }
24
+ //# sourceMappingURL=sequence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sequence.js","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAE5D,MAAM,YAAY,GAAG,EAAE,CAAA;AAEvB,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAuB;IACtD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,IAAI,GAA4B,IAAI,UAAU,CAAC,YAAY,CAAC,CAAA;IAEhE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,GAAG,CAAC,CAAC,CAAA;QAC9C,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAClB,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,CAAA;QACxC,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAuB,EACvB,KAAK,GAAW,IAAI,CAAC,MAAM;IAE3B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACrE,MAAM,IAAI,UAAU,CAAC,4EAA4E,CAAC,CAAA;IACpG,CAAC;IACD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG,CAAC,CAAC,CAAA;IAEpD,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAA;IACpD,OAAO,KAAK,CAAC,KAAK,GAAG,CAAC,CAAW,CAAA;AACnC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Spool wraps its agent-search system instructions in this marker before
3
+ * sending them to ACP, so the parsers can strip them back out when indexing
4
+ * the on-disk JSONL. Without the marker, our system prompt would appear as
5
+ * the first user message in every agent-search session — polluting the
6
+ * derived title, the FTS index, and the session detail view.
7
+ *
8
+ * The user's actual query is sent OUTSIDE the marker block (at the end of
9
+ * the message text), so after stripping the prelude only the bare query
10
+ * remains.
11
+ *
12
+ * Moved from @spool-lab/core so the browser-side reader strips the same
13
+ * prelude; core re-exports.
14
+ */
15
+ export declare const SPOOL_SYSTEM_PRELUDE_OPEN = "<spool-system-prelude>";
16
+ export declare const SPOOL_SYSTEM_PRELUDE_CLOSE = "</spool-system-prelude>";
17
+ export declare function wrapSpoolSystemPrelude(systemBody: string, userQuery: string): string;
18
+ export declare function stripSpoolSystemPrelude(text: string): string;
19
+ //# sourceMappingURL=spool-prelude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spool-prelude.d.ts","sourceRoot":"","sources":["../src/spool-prelude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,2BAA2B,CAAA;AACjE,eAAO,MAAM,0BAA0B,4BAA4B,CAAA;AAEnE,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAa5D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Spool wraps its agent-search system instructions in this marker before
3
+ * sending them to ACP, so the parsers can strip them back out when indexing
4
+ * the on-disk JSONL. Without the marker, our system prompt would appear as
5
+ * the first user message in every agent-search session — polluting the
6
+ * derived title, the FTS index, and the session detail view.
7
+ *
8
+ * The user's actual query is sent OUTSIDE the marker block (at the end of
9
+ * the message text), so after stripping the prelude only the bare query
10
+ * remains.
11
+ *
12
+ * Moved from @spool-lab/core so the browser-side reader strips the same
13
+ * prelude; core re-exports.
14
+ */
15
+ export const SPOOL_SYSTEM_PRELUDE_OPEN = '<spool-system-prelude>';
16
+ export const SPOOL_SYSTEM_PRELUDE_CLOSE = '</spool-system-prelude>';
17
+ export function wrapSpoolSystemPrelude(systemBody, userQuery) {
18
+ return `${SPOOL_SYSTEM_PRELUDE_OPEN}\n${systemBody}\n${SPOOL_SYSTEM_PRELUDE_CLOSE}\n\n${userQuery}`;
19
+ }
20
+ export function stripSpoolSystemPrelude(text) {
21
+ // indexOf scanning instead of a regex: the `<open>[\s\S]*?<close>` form
22
+ // backtracks quadratically when many unterminated open markers appear in
23
+ // hostile input. Each pass here is linear.
24
+ let result = text;
25
+ let open = result.indexOf(SPOOL_SYSTEM_PRELUDE_OPEN);
26
+ while (open !== -1) {
27
+ const close = result.indexOf(SPOOL_SYSTEM_PRELUDE_CLOSE, open + SPOOL_SYSTEM_PRELUDE_OPEN.length);
28
+ if (close === -1)
29
+ break; // unterminated marker — leave the remainder intact
30
+ result = result.slice(0, open) + result.slice(close + SPOOL_SYSTEM_PRELUDE_CLOSE.length);
31
+ open = result.indexOf(SPOOL_SYSTEM_PRELUDE_OPEN);
32
+ }
33
+ return result.trim();
34
+ }
35
+ //# sourceMappingURL=spool-prelude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spool-prelude.js","sourceRoot":"","sources":["../src/spool-prelude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,wBAAwB,CAAA;AACjE,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,CAAA;AAEnE,MAAM,UAAU,sBAAsB,CAAC,UAAkB,EAAE,SAAiB;IAC1E,OAAO,GAAG,yBAAyB,KAAK,UAAU,KAAK,0BAA0B,OAAO,SAAS,EAAE,CAAA;AACrG,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,wEAAwE;IACxE,yEAAyE;IACzE,2CAA2C;IAC3C,IAAI,MAAM,GAAG,IAAI,CAAA;IACjB,IAAI,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAA;IACpD,OAAO,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,0BAA0B,EAAE,IAAI,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAA;QACjG,IAAI,KAAK,KAAK,CAAC,CAAC;YAAE,MAAK,CAAC,mDAAmD;QAC3E,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,0BAA0B,CAAC,MAAM,CAAC,CAAA;QACxF,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAA;IAClD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAA;AACtB,CAAC"}