sidekick-shared 0.26.6 → 0.27.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,616 @@
1
+ "use strict";
2
+ /**
3
+ * Node-only collection of human-typed prompts from Claude Code and Codex
4
+ * session logs, scoped to a set of workspace roots.
5
+ *
6
+ * Parse-only: no redaction and no network I/O. Scope fails closed — a prompt
7
+ * is returned only when the working directory recorded on its log line (or,
8
+ * for Codex, its session/turn context) realpath-resolves to an allowed root
9
+ * or somewhere inside one. Every bound stops cleanly and is reported in
10
+ * `boundsHit`; nothing is silently truncated and no bound throws.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.collectPromptHistory = collectPromptHistory;
47
+ const crypto_1 = require("crypto");
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const humanPrompt_1 = require("./humanPrompt");
51
+ const sessionPathResolver_1 = require("./parsers/sessionPathResolver");
52
+ const claudeCode_1 = require("./providers/claudeCode");
53
+ const codex_1 = require("./providers/codex");
54
+ const DEFAULT_BOUNDS = {
55
+ maxSessions: 1000,
56
+ maxFileBytes: 16 * 1024 * 1024,
57
+ maxTotalBytes: 256 * 1024 * 1024,
58
+ deadlineMs: 30_000,
59
+ };
60
+ const CHECK_EVERY_LINES = 500;
61
+ const ALL_PROVIDERS = ['claude-code', 'codex'];
62
+ /** Collect human-typed prompts from Claude Code and Codex logs under the given roots. */
63
+ async function collectPromptHistory(options) {
64
+ const bounds = { ...DEFAULT_BOUNDS, ...definedBounds(options.bounds) };
65
+ const startedAt = Date.now();
66
+ const boundsHit = new Set();
67
+ const stats = {
68
+ sessionsScanned: 0,
69
+ sessionsSkippedUnchanged: 0,
70
+ sessionsOutOfScope: 0,
71
+ filesOverSizeLimit: 0,
72
+ sessionsNotInteractive: 0,
73
+ promptsMissingTimestamp: 0,
74
+ promptsOutOfScope: 0,
75
+ };
76
+ const previous = options.cursor?.version === 1 ? options.cursor.sessions : {};
77
+ const cursor = { version: 1, sessions: { ...previous } };
78
+ const entries = [];
79
+ const shouldStop = () => {
80
+ if (options.signal?.aborted)
81
+ return 'aborted';
82
+ if (Date.now() - startedAt >= bounds.deadlineMs)
83
+ return 'deadline';
84
+ return null;
85
+ };
86
+ const finish = () => ({
87
+ entries: entries.sort(compareEntries),
88
+ cursor,
89
+ stats,
90
+ boundsHit: [...boundsHit],
91
+ });
92
+ const realpaths = new Map();
93
+ const { roots, listingRoots } = resolveRoots(options.workspacePaths, options.includeWorktrees !== false, realpaths);
94
+ if (roots.length === 0)
95
+ return finish();
96
+ const sinceMs = options.since ? options.since.getTime() : null;
97
+ const providers = options.providers ?? ALL_PROVIDERS;
98
+ const candidates = await listCandidates(providers, listingRoots, sinceMs);
99
+ const context = { roots, sinceMs, stats, realpaths, shouldStop };
100
+ let totalBytes = 0;
101
+ for (const candidate of candidates) {
102
+ const stop = shouldStop();
103
+ if (stop) {
104
+ boundsHit.add(stop);
105
+ break;
106
+ }
107
+ let stat;
108
+ try {
109
+ stat = await fs.promises.stat(candidate.filePath);
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ const prior = previous[candidate.key];
115
+ if (prior && prior.size === stat.size && prior.mtimeMs === stat.mtimeMs) {
116
+ stats.sessionsSkippedUnchanged++;
117
+ continue;
118
+ }
119
+ // A file that shrank was rewritten; its old offsets and ordinals are meaningless.
120
+ const resume = prior && prior.offset <= stat.size ? prior : undefined;
121
+ const offset = resume?.offset ?? 0;
122
+ const bytesToRead = stat.size - offset;
123
+ if (resume?.state?.excluded) {
124
+ cursor.sessions[candidate.key] = {
125
+ ...resume,
126
+ size: stat.size,
127
+ mtimeMs: stat.mtimeMs,
128
+ offset: stat.size,
129
+ };
130
+ stats.sessionsNotInteractive++;
131
+ continue;
132
+ }
133
+ if (bytesToRead > bounds.maxFileBytes) {
134
+ stats.filesOverSizeLimit++;
135
+ boundsHit.add('maxFileBytes');
136
+ continue;
137
+ }
138
+ if (stats.sessionsScanned >= bounds.maxSessions) {
139
+ boundsHit.add('maxSessions');
140
+ break;
141
+ }
142
+ if (totalBytes + bytesToRead > bounds.maxTotalBytes) {
143
+ boundsHit.add('maxTotalBytes');
144
+ break;
145
+ }
146
+ let buffer;
147
+ try {
148
+ buffer = await readRange(candidate.filePath, offset, bytesToRead);
149
+ }
150
+ catch {
151
+ continue;
152
+ }
153
+ totalBytes += buffer.length;
154
+ stats.sessionsScanned++;
155
+ const outcome = await scanBuffer(candidate, buffer, offset, resume, context);
156
+ if (outcome.stoppedBy) {
157
+ // Partially processed file: drop its entries and leave its cursor untouched.
158
+ boundsHit.add(outcome.stoppedBy);
159
+ break;
160
+ }
161
+ if (outcome.notInteractive)
162
+ stats.sessionsNotInteractive++;
163
+ else if (outcome.inScope === 0 && outcome.outOfScope > 0)
164
+ stats.sessionsOutOfScope++;
165
+ entries.push(...outcome.entries);
166
+ cursor.sessions[candidate.key] = {
167
+ provider: candidate.provider,
168
+ sessionId: candidate.sessionId,
169
+ size: stat.size,
170
+ mtimeMs: stat.mtimeMs,
171
+ lastOrdinal: outcome.lastOrdinal,
172
+ offset: outcome.offset,
173
+ ...(outcome.state ? { state: outcome.state } : {}),
174
+ };
175
+ await yieldToEventLoop();
176
+ }
177
+ return finish();
178
+ }
179
+ function definedBounds(bounds) {
180
+ const result = {};
181
+ if (!bounds)
182
+ return result;
183
+ for (const key of Object.keys(DEFAULT_BOUNDS)) {
184
+ const value = bounds[key];
185
+ if (typeof value === 'number' && !Number.isNaN(value))
186
+ result[key] = Math.max(0, value);
187
+ }
188
+ return result;
189
+ }
190
+ function compareEntries(left, right) {
191
+ return (compareStrings(left.provider, right.provider) ||
192
+ compareStrings(left.sessionId, right.sessionId) ||
193
+ left.ordinal - right.ordinal);
194
+ }
195
+ function compareStrings(left, right) {
196
+ return left < right ? -1 : left > right ? 1 : 0;
197
+ }
198
+ function realpathOrNull(input, cache) {
199
+ const cached = cache.get(input);
200
+ if (cached !== undefined)
201
+ return cached;
202
+ let resolved = null;
203
+ try {
204
+ resolved = path.isAbsolute(input) ? fs.realpathSync.native(input) : null;
205
+ }
206
+ catch {
207
+ resolved = null;
208
+ }
209
+ cache.set(input, resolved);
210
+ return resolved;
211
+ }
212
+ function resolveRoots(workspacePaths, includeWorktrees, realpaths) {
213
+ const roots = new Set();
214
+ const listingRoots = new Set();
215
+ const add = (candidate) => {
216
+ const real = realpathOrNull(candidate, realpaths);
217
+ if (!real)
218
+ return;
219
+ roots.add(real);
220
+ listingRoots.add(real);
221
+ listingRoots.add(path.resolve(candidate));
222
+ };
223
+ for (const workspacePath of workspacePaths) {
224
+ if (typeof workspacePath !== 'string' || !path.isAbsolute(workspacePath))
225
+ continue;
226
+ add(workspacePath);
227
+ if (!includeWorktrees)
228
+ continue;
229
+ const real = realpathOrNull(workspacePath, realpaths);
230
+ if (!real)
231
+ continue;
232
+ const mainRepo = (0, sessionPathResolver_1.resolveWorktreeMainRepo)(real) ?? real;
233
+ add(mainRepo);
234
+ for (const sibling of (0, sessionPathResolver_1.discoverWorktreeSiblings)(mainRepo))
235
+ add(sibling);
236
+ }
237
+ return { roots: [...roots], listingRoots: [...listingRoots] };
238
+ }
239
+ /** Fail-closed scope check on a recorded working directory. */
240
+ function scopedCwd(cwd, roots, realpaths) {
241
+ if (typeof cwd !== 'string' || cwd.length === 0)
242
+ return null;
243
+ const real = realpathOrNull(cwd, realpaths);
244
+ if (!real)
245
+ return null;
246
+ for (const root of roots) {
247
+ if (real === root)
248
+ return real;
249
+ const prefix = root.endsWith(path.sep) ? root : root + path.sep;
250
+ if (real.startsWith(prefix))
251
+ return real;
252
+ }
253
+ return null;
254
+ }
255
+ async function listCandidates(providers, listingRoots, sinceMs) {
256
+ const byPath = new Map();
257
+ const listOptions = sinceMs !== null ? { since: sinceMs } : {};
258
+ for (const provider of [...new Set(providers)]) {
259
+ if (!ALL_PROVIDERS.includes(provider))
260
+ continue;
261
+ // Listings are only candidate supersets; scope is re-checked per prompt.
262
+ const source = provider === 'claude-code' ? new claudeCode_1.ClaudeCodeProvider() : new codex_1.CodexProvider();
263
+ for (const root of listingRoots) {
264
+ let files;
265
+ try {
266
+ files = (await source.listSessionFilesAsync?.(root, listOptions)) ?? [];
267
+ }
268
+ catch {
269
+ files = [];
270
+ }
271
+ for (const file of files) {
272
+ if (byPath.has(file.path))
273
+ continue;
274
+ const sessionId = file.sessionId ?? path.basename(file.path, '.jsonl');
275
+ byPath.set(file.path, {
276
+ provider,
277
+ sessionId,
278
+ filePath: file.path,
279
+ key: `${provider}:${sessionId}:${hashPath(file.path)}`,
280
+ });
281
+ }
282
+ }
283
+ source.dispose();
284
+ }
285
+ return [...byPath.values()].sort((left, right) => compareStrings(left.provider, right.provider) ||
286
+ compareStrings(left.sessionId, right.sessionId) ||
287
+ compareStrings(left.key, right.key));
288
+ }
289
+ function hashPath(filePath) {
290
+ return (0, crypto_1.createHash)('sha256').update(filePath).digest('hex').slice(0, 12);
291
+ }
292
+ async function readRange(filePath, offset, length) {
293
+ const buffer = Buffer.alloc(length);
294
+ if (length === 0)
295
+ return buffer;
296
+ const handle = await fs.promises.open(filePath, 'r');
297
+ try {
298
+ let read = 0;
299
+ while (read < length) {
300
+ const { bytesRead } = await handle.read(buffer, read, length - read, offset + read);
301
+ if (bytesRead === 0)
302
+ break;
303
+ read += bytesRead;
304
+ }
305
+ return read === length ? buffer : buffer.subarray(0, read);
306
+ }
307
+ finally {
308
+ await handle.close();
309
+ }
310
+ }
311
+ function yieldToEventLoop() {
312
+ return new Promise((resolve) => setImmediate(resolve));
313
+ }
314
+ async function scanBuffer(candidate, buffer, startOffset, resume, context) {
315
+ const lastNewline = buffer.lastIndexOf(0x0a);
316
+ const complete = lastNewline >= 0 ? buffer.subarray(0, lastNewline + 1) : Buffer.alloc(0);
317
+ const lines = complete.toString('utf8').split('\n');
318
+ const scanner = candidate.provider === 'claude-code'
319
+ ? new ClaudeLineScanner(candidate, resume, context)
320
+ : new CodexLineScanner(candidate, resume, context);
321
+ for (let index = 0; index < lines.length; index++) {
322
+ if (index > 0 && index % CHECK_EVERY_LINES === 0) {
323
+ await yieldToEventLoop();
324
+ const stop = context.shouldStop();
325
+ if (stop)
326
+ return { ...scanner.outcome(startOffset), stoppedBy: stop };
327
+ }
328
+ const line = lines[index];
329
+ if (!line.trim())
330
+ continue;
331
+ let record;
332
+ try {
333
+ record = JSON.parse(line);
334
+ }
335
+ catch {
336
+ continue;
337
+ }
338
+ if (typeof record !== 'object' || record === null)
339
+ continue;
340
+ if (scanner.accept(record) === 'excluded') {
341
+ // Non-interactive Codex session: nothing in it will ever count.
342
+ return {
343
+ ...scanner.outcome(startOffset + buffer.length),
344
+ entries: [],
345
+ notInteractive: true,
346
+ stoppedBy: null,
347
+ };
348
+ }
349
+ }
350
+ return { ...scanner.outcome(startOffset + complete.length), stoppedBy: null };
351
+ }
352
+ /** Shared per-file prompt bookkeeping for both providers. */
353
+ class LineScanner {
354
+ candidate;
355
+ context;
356
+ entries = [];
357
+ nextOrdinal;
358
+ inScope = 0;
359
+ outOfScope = 0;
360
+ pending = null;
361
+ constructor(candidate, resume, context) {
362
+ this.candidate = candidate;
363
+ this.context = context;
364
+ this.nextOrdinal = (resume?.lastOrdinal ?? -1) + 1;
365
+ }
366
+ state() {
367
+ return undefined;
368
+ }
369
+ outcome(offset) {
370
+ const state = this.state();
371
+ return {
372
+ entries: this.entries,
373
+ lastOrdinal: this.nextOrdinal - 1,
374
+ offset,
375
+ ...(state ? { state } : {}),
376
+ inScope: this.inScope,
377
+ outOfScope: this.outOfScope,
378
+ notInteractive: false,
379
+ };
380
+ }
381
+ /** Assign an ordinal to a human prompt and emit it when in scope. */
382
+ recordPrompt(text, timestamp, cwd, gitBranch) {
383
+ const ordinal = this.nextOrdinal++;
384
+ this.pending = null;
385
+ if (typeof timestamp !== 'string' || Number.isNaN(Date.parse(timestamp))) {
386
+ this.context.stats.promptsMissingTimestamp++;
387
+ return;
388
+ }
389
+ const scoped = scopedCwd(cwd, this.context.roots, this.context.realpaths);
390
+ if (!scoped) {
391
+ this.outOfScope++;
392
+ this.context.stats.promptsOutOfScope++;
393
+ return;
394
+ }
395
+ this.inScope++;
396
+ if (this.context.sinceMs !== null && Date.parse(timestamp) < this.context.sinceMs)
397
+ return;
398
+ const entry = {
399
+ provider: this.candidate.provider,
400
+ sessionId: this.candidate.sessionId,
401
+ ordinal,
402
+ timestamp,
403
+ text,
404
+ cwd: scoped,
405
+ ...(gitBranch ? { gitBranch } : {}),
406
+ };
407
+ this.entries.push(entry);
408
+ this.pending = entry;
409
+ }
410
+ }
411
+ function transcriptMessage(candidate, record, role, content, extra, eventIndex) {
412
+ return {
413
+ role: 'user',
414
+ timestamp: typeof record.timestamp === 'string' ? record.timestamp : '',
415
+ content,
416
+ text: content
417
+ .filter((block) => block.type === 'text')
418
+ .map((block) => block.text ?? '')
419
+ .join('\n'),
420
+ tools: [],
421
+ commands: [],
422
+ source: {
423
+ provider: candidate.provider,
424
+ sessionId: candidate.sessionId,
425
+ originalRole: role,
426
+ eventIndex,
427
+ eventType: 'user',
428
+ ...extra,
429
+ },
430
+ };
431
+ }
432
+ function stringField(value) {
433
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
434
+ }
435
+ function tokenCount(value) {
436
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
437
+ }
438
+ function compactUsage(usage) {
439
+ const result = {};
440
+ for (const [key, value] of Object.entries(usage)) {
441
+ if (typeof value === 'number')
442
+ result[key] = value;
443
+ }
444
+ return Object.keys(result).length > 0 ? result : undefined;
445
+ }
446
+ class ClaudeLineScanner extends LineScanner {
447
+ lineIndex = 0;
448
+ answerId = null;
449
+ accept(record) {
450
+ const index = this.lineIndex++;
451
+ const message = record.message;
452
+ if (typeof message !== 'object' || message === null)
453
+ return 'ok';
454
+ if (record.type === 'user') {
455
+ const origin = record.origin;
456
+ const canonical = transcriptMessage(this.candidate, record, typeof message.role === 'string' ? message.role : 'user', claudeBlocks(message.content), {
457
+ entrypoint: stringField(record.entrypoint),
458
+ isMeta: record.isMeta === true ? true : undefined,
459
+ isSidechain: record.isSidechain === true ? true : undefined,
460
+ isCompactSummary: record.isCompactSummary === true ? true : undefined,
461
+ originKind: origin && typeof origin.kind === 'string' ? origin.kind : undefined,
462
+ promptSource: stringField(record.promptSource),
463
+ }, index);
464
+ const text = (0, humanPrompt_1.humanPromptText)(canonical, 'claude-code');
465
+ if (text === null)
466
+ return 'ok';
467
+ this.answerId = null;
468
+ this.recordPrompt(text, record.timestamp, record.cwd, stringField(record.gitBranch));
469
+ return 'ok';
470
+ }
471
+ if (record.type === 'assistant' && this.pending && record.isSidechain !== true) {
472
+ const id = stringField(message.id) ?? `line:${index}`;
473
+ if (this.answerId === null)
474
+ this.answerId = id;
475
+ if (id !== this.answerId) {
476
+ this.pending = null;
477
+ return 'ok';
478
+ }
479
+ const model = stringField(message.model);
480
+ if (model && model !== '<synthetic>')
481
+ this.pending.model = model;
482
+ const usage = message.usage;
483
+ if (usage && typeof usage === 'object') {
484
+ const mapped = compactUsage({
485
+ inputTokens: tokenCount(usage.input_tokens),
486
+ outputTokens: tokenCount(usage.output_tokens),
487
+ cacheReadTokens: tokenCount(usage.cache_read_input_tokens),
488
+ cacheWriteTokens: tokenCount(usage.cache_creation_input_tokens),
489
+ });
490
+ if (mapped)
491
+ this.pending.usage = mapped;
492
+ }
493
+ }
494
+ return 'ok';
495
+ }
496
+ }
497
+ function claudeBlocks(content) {
498
+ if (typeof content === 'string')
499
+ return [{ type: 'text', text: content }];
500
+ if (!Array.isArray(content))
501
+ return [];
502
+ const blocks = [];
503
+ for (const item of content) {
504
+ if (typeof item !== 'object' || item === null)
505
+ continue;
506
+ const block = item;
507
+ if (block.type === 'text' && typeof block.text === 'string') {
508
+ blocks.push({ type: 'text', text: block.text });
509
+ }
510
+ else if (block.type === 'tool_result') {
511
+ blocks.push({ type: 'tool_result' });
512
+ }
513
+ else if (block.type === 'image') {
514
+ blocks.push({ type: 'image' });
515
+ }
516
+ else {
517
+ blocks.push({ type: 'unknown' });
518
+ }
519
+ }
520
+ return blocks;
521
+ }
522
+ class CodexLineScanner extends LineScanner {
523
+ lineIndex = 0;
524
+ resumeState;
525
+ constructor(candidate, resume, context) {
526
+ super(candidate, resume, context);
527
+ this.resumeState = { ...(resume?.state ?? {}) };
528
+ }
529
+ state() {
530
+ return { ...this.resumeState };
531
+ }
532
+ accept(record) {
533
+ const index = this.lineIndex++;
534
+ const payload = record.payload;
535
+ if (typeof payload !== 'object' || payload === null)
536
+ return 'ok';
537
+ const state = this.resumeState;
538
+ if (record.type === 'session_meta') {
539
+ // Forked sessions embed their parent's session_meta after their own; only the first counts.
540
+ if (state.meta)
541
+ return 'ok';
542
+ state.meta = true;
543
+ const source = typeof payload.source === 'string' ? payload.source : null;
544
+ if (!source || !humanPrompt_1.HUMAN_CODEX_SOURCES.includes(source) || stringField(payload.forked_from_id)) {
545
+ state.excluded = true;
546
+ return 'excluded';
547
+ }
548
+ state.cwd = stringField(payload.cwd);
549
+ const git = payload.git;
550
+ state.gitBranch = git && typeof git === 'object' ? stringField(git.branch) : undefined;
551
+ return 'ok';
552
+ }
553
+ if (record.type === 'turn_context') {
554
+ const cwd = stringField(payload.cwd);
555
+ if (cwd)
556
+ state.cwd = cwd;
557
+ const model = stringField(payload.model);
558
+ if (model)
559
+ state.model = model;
560
+ return 'ok';
561
+ }
562
+ if (record.type === 'response_item' && payload.type === 'message' && payload.role === 'user') {
563
+ // Without session metadata the source (and thus interactivity) is unknown: fail closed.
564
+ if (!state.meta) {
565
+ state.excluded = true;
566
+ return 'excluded';
567
+ }
568
+ const canonical = transcriptMessage(this.candidate, record, 'user', codexBlocks(payload.content), {}, index);
569
+ const text = (0, humanPrompt_1.humanPromptText)(canonical, 'codex');
570
+ if (text === null)
571
+ return 'ok';
572
+ this.recordPrompt(text, record.timestamp, state.cwd, state.gitBranch);
573
+ return 'ok';
574
+ }
575
+ if (record.type === 'event_msg' && payload.type === 'token_count' && this.pending) {
576
+ const info = payload.info;
577
+ const last = info && typeof info === 'object' ? info.last_token_usage : undefined;
578
+ if (!last || typeof last !== 'object')
579
+ return 'ok';
580
+ const input = tokenCount(last.input_tokens);
581
+ const cached = tokenCount(last.cached_input_tokens);
582
+ const mapped = compactUsage({
583
+ inputTokens: input !== undefined ? Math.max(0, input - (cached ?? 0)) : undefined,
584
+ outputTokens: tokenCount(last.output_tokens),
585
+ cacheReadTokens: cached,
586
+ cacheWriteTokens: tokenCount(last.cache_write_input_tokens),
587
+ });
588
+ if (mapped)
589
+ this.pending.usage = mapped;
590
+ if (state.model)
591
+ this.pending.model = state.model;
592
+ this.pending = null;
593
+ }
594
+ return 'ok';
595
+ }
596
+ }
597
+ function codexBlocks(content) {
598
+ if (!Array.isArray(content))
599
+ return [];
600
+ const blocks = [];
601
+ for (const item of content) {
602
+ if (typeof item !== 'object' || item === null)
603
+ continue;
604
+ const block = item;
605
+ if ((block.type === 'input_text' || block.type === 'text') && typeof block.text === 'string') {
606
+ blocks.push({ type: 'text', text: block.text });
607
+ }
608
+ else if (block.type === 'input_image') {
609
+ blocks.push({ type: 'image' });
610
+ }
611
+ else {
612
+ blocks.push({ type: 'unknown' });
613
+ }
614
+ }
615
+ return blocks;
616
+ }
@@ -65,6 +65,11 @@ export declare const userSessionEventSchema: z.ZodObject<{
65
65
  entrypoint: z.ZodOptional<z.ZodString>;
66
66
  isMeta: z.ZodOptional<z.ZodBoolean>;
67
67
  isSidechain: z.ZodOptional<z.ZodBoolean>;
68
+ isCompactSummary: z.ZodOptional<z.ZodBoolean>;
69
+ origin: z.ZodOptional<z.ZodObject<{
70
+ kind: z.ZodOptional<z.ZodString>;
71
+ }, z.core.$strip>>;
72
+ promptSource: z.ZodOptional<z.ZodString>;
68
73
  cwd: z.ZodOptional<z.ZodString>;
69
74
  gitBranch: z.ZodOptional<z.ZodString>;
70
75
  permissionMode: z.ZodOptional<z.ZodEnum<{
@@ -145,6 +150,11 @@ export declare const assistantSessionEventSchema: z.ZodObject<{
145
150
  entrypoint: z.ZodOptional<z.ZodString>;
146
151
  isMeta: z.ZodOptional<z.ZodBoolean>;
147
152
  isSidechain: z.ZodOptional<z.ZodBoolean>;
153
+ isCompactSummary: z.ZodOptional<z.ZodBoolean>;
154
+ origin: z.ZodOptional<z.ZodObject<{
155
+ kind: z.ZodOptional<z.ZodString>;
156
+ }, z.core.$strip>>;
157
+ promptSource: z.ZodOptional<z.ZodString>;
148
158
  cwd: z.ZodOptional<z.ZodString>;
149
159
  gitBranch: z.ZodOptional<z.ZodString>;
150
160
  permissionMode: z.ZodOptional<z.ZodEnum<{
@@ -226,6 +236,11 @@ export declare const systemSessionEventSchema: z.ZodObject<{
226
236
  entrypoint: z.ZodOptional<z.ZodString>;
227
237
  isMeta: z.ZodOptional<z.ZodBoolean>;
228
238
  isSidechain: z.ZodOptional<z.ZodBoolean>;
239
+ isCompactSummary: z.ZodOptional<z.ZodBoolean>;
240
+ origin: z.ZodOptional<z.ZodObject<{
241
+ kind: z.ZodOptional<z.ZodString>;
242
+ }, z.core.$strip>>;
243
+ promptSource: z.ZodOptional<z.ZodString>;
229
244
  cwd: z.ZodOptional<z.ZodString>;
230
245
  gitBranch: z.ZodOptional<z.ZodString>;
231
246
  permissionMode: z.ZodOptional<z.ZodEnum<{
@@ -310,6 +325,11 @@ export declare const toolUseSessionEventSchema: z.ZodObject<{
310
325
  entrypoint: z.ZodOptional<z.ZodString>;
311
326
  isMeta: z.ZodOptional<z.ZodBoolean>;
312
327
  isSidechain: z.ZodOptional<z.ZodBoolean>;
328
+ isCompactSummary: z.ZodOptional<z.ZodBoolean>;
329
+ origin: z.ZodOptional<z.ZodObject<{
330
+ kind: z.ZodOptional<z.ZodString>;
331
+ }, z.core.$strip>>;
332
+ promptSource: z.ZodOptional<z.ZodString>;
313
333
  cwd: z.ZodOptional<z.ZodString>;
314
334
  gitBranch: z.ZodOptional<z.ZodString>;
315
335
  permissionMode: z.ZodOptional<z.ZodEnum<{
@@ -395,6 +415,11 @@ export declare const toolResultSessionEventSchema: z.ZodObject<{
395
415
  entrypoint: z.ZodOptional<z.ZodString>;
396
416
  isMeta: z.ZodOptional<z.ZodBoolean>;
397
417
  isSidechain: z.ZodOptional<z.ZodBoolean>;
418
+ isCompactSummary: z.ZodOptional<z.ZodBoolean>;
419
+ origin: z.ZodOptional<z.ZodObject<{
420
+ kind: z.ZodOptional<z.ZodString>;
421
+ }, z.core.$strip>>;
422
+ promptSource: z.ZodOptional<z.ZodString>;
398
423
  cwd: z.ZodOptional<z.ZodString>;
399
424
  gitBranch: z.ZodOptional<z.ZodString>;
400
425
  permissionMode: z.ZodOptional<z.ZodEnum<{
@@ -45,6 +45,9 @@ const eventBaseShape = {
45
45
  entrypoint: zod_1.z.string().optional(),
46
46
  isMeta: zod_1.z.boolean().optional(),
47
47
  isSidechain: zod_1.z.boolean().optional(),
48
+ isCompactSummary: zod_1.z.boolean().optional(),
49
+ origin: zod_1.z.object({ kind: zod_1.z.string().optional() }).optional(),
50
+ promptSource: zod_1.z.string().optional(),
48
51
  cwd: zod_1.z.string().optional(),
49
52
  gitBranch: zod_1.z.string().optional(),
50
53
  permissionMode: exports.permissionModeSchema.optional(),
@@ -10,6 +10,12 @@ export interface TranscriptSourceProvenance {
10
10
  entrypoint?: string;
11
11
  isMeta?: boolean;
12
12
  isSidechain?: boolean;
13
+ /** Claude Code compaction/continuation summary flag. */
14
+ isCompactSummary?: boolean;
15
+ /** Claude Code `origin.kind` (e.g. `human`, `task-notification`, `coordinator`). */
16
+ originKind?: string;
17
+ /** Claude Code `promptSource` (e.g. `typed`, `queued`, `system`). */
18
+ promptSource?: string;
13
19
  originalRole?: string;
14
20
  eventIndex: number;
15
21
  eventType: SessionEvent['type'];