sidekick-shared 0.29.1 → 0.30.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/CHANGELOG.md +26 -0
- package/README.md +171 -60
- package/dist/aggregation/EventAggregator.d.ts +79 -4
- package/dist/aggregation/EventAggregator.js +391 -37
- package/dist/aggregation/types.d.ts +27 -3
- package/dist/browser.d.ts +7 -3
- package/dist/browser.js +23 -3
- package/dist/context/sessionContext.js +5 -1
- package/dist/esm/aggregation/EventAggregator.js +392 -38
- package/dist/esm/browser.js +3 -1
- package/dist/esm/context/sessionContext.js +6 -2
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/index.js +5 -2
- package/dist/esm/node.js +2 -0
- package/dist/esm/parsers/codexParser.js +192 -3
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +4 -0
- package/dist/esm/parsers/subagentTraceParser.js +6 -0
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +50 -27
- package/dist/esm/providers/codex.js +153 -163
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +32 -4
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +206 -6
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +6 -2
- package/dist/esm/turns/assistantTurn.js +6 -3
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +31 -1
- package/dist/esm/watchers/jsonlTail.js +11 -42
- package/dist/esm/watchers/jsonlWatcher.js +19 -9
- package/dist/esm/watchers/providerReaderWatcher.js +5 -4
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.js +39 -10
- package/dist/node.d.ts +4 -0
- package/dist/node.js +16 -2
- package/dist/parsers/codexParser.d.ts +44 -0
- package/dist/parsers/codexParser.js +192 -3
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.js +4 -0
- package/dist/parsers/subagentTraceParser.d.ts +8 -0
- package/dist/parsers/subagentTraceParser.js +6 -0
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +50 -27
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +150 -160
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +22 -1
- package/dist/schemas/sessionEvent.d.ts +26 -2
- package/dist/schemas/sessionEvent.js +31 -3
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +100 -5
- package/dist/subagentSpawn.js +218 -7
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +5 -1
- package/dist/turns/assistantTurn.d.ts +4 -1
- package/dist/turns/assistantTurn.js +5 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +30 -0
- package/dist/watchers/jsonlTail.js +11 -42
- package/dist/watchers/jsonlWatcher.d.ts +5 -1
- package/dist/watchers/jsonlWatcher.js +18 -8
- package/dist/watchers/providerReaderWatcher.js +5 -4
- package/package.json +1 -1
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
* answering call brings model or usage (`metadataStatus` says how settled).
|
|
25
25
|
*/
|
|
26
26
|
import { createHash } from 'crypto';
|
|
27
|
-
import * as fs from 'fs';
|
|
28
27
|
import * as path from 'path';
|
|
29
28
|
import { HUMAN_CODEX_SOURCES, humanPromptText } from './humanPrompt';
|
|
30
29
|
import { listClaudeSessionFilesAsync } from './providers/claudeListing';
|
|
31
30
|
import { listCodexSessionFilesAsync } from './providers/codexRollouts';
|
|
31
|
+
import { canReadCompressedRollouts, openRolloutReadHandle, statRolloutContent, } from './providers/compressedRollouts';
|
|
32
32
|
import { DirectoryListingCache } from './providers/directoryListingCache';
|
|
33
33
|
import { realpathOrNull, resolveWorkspaceRoots } from './workspaceRoots';
|
|
34
34
|
import { fileHeadOf, HEAD_BYTES, headMatches } from './fileHead';
|
|
@@ -105,7 +105,9 @@ export async function collectPromptHistory(options) {
|
|
|
105
105
|
return finish();
|
|
106
106
|
const sinceMs = options.since ? options.since.getTime() : null;
|
|
107
107
|
const providers = options.providers ?? ALL_PROVIDERS;
|
|
108
|
-
const candidates = rotate(await listCandidates(providers, listingRoots, sinceMs
|
|
108
|
+
const candidates = rotate(await listCandidates(providers, listingRoots, sinceMs, {
|
|
109
|
+
includeCompressedRollouts: options.includeCompressedRollouts,
|
|
110
|
+
}), previousNext);
|
|
109
111
|
const moved = movedSessionKeys(previous, candidates);
|
|
110
112
|
const context = {
|
|
111
113
|
roots,
|
|
@@ -141,7 +143,8 @@ export async function collectPromptHistory(options) {
|
|
|
141
143
|
}
|
|
142
144
|
let stat;
|
|
143
145
|
try {
|
|
144
|
-
|
|
146
|
+
// A compressed rollout's size is its decompressed content's.
|
|
147
|
+
stat = await statRolloutContent(candidate.filePath);
|
|
145
148
|
}
|
|
146
149
|
catch {
|
|
147
150
|
continue;
|
|
@@ -246,7 +249,7 @@ export async function collectPromptHistory(options) {
|
|
|
246
249
|
*/
|
|
247
250
|
export async function readPromptOrdinalMarks(provider, sessionId, filePath) {
|
|
248
251
|
const marks = [];
|
|
249
|
-
const stat = await
|
|
252
|
+
const stat = await statRolloutContent(filePath);
|
|
250
253
|
const unbounded = Number.MAX_SAFE_INTEGER;
|
|
251
254
|
const context = {
|
|
252
255
|
roots: [],
|
|
@@ -435,19 +438,24 @@ function scopedCwd(cwd, roots, realpaths) {
|
|
|
435
438
|
return null;
|
|
436
439
|
}
|
|
437
440
|
/** @internal */
|
|
438
|
-
export async function listCandidates(providers, listingRoots, sinceMs) {
|
|
441
|
+
export async function listCandidates(providers, listingRoots, sinceMs, options = {}) {
|
|
439
442
|
const byPath = new Map();
|
|
440
443
|
const listOptions = sinceMs !== null ? { since: sinceMs } : {};
|
|
441
444
|
for (const provider of [...new Set(providers)]) {
|
|
442
445
|
if (!ALL_PROVIDERS.includes(provider))
|
|
443
446
|
continue;
|
|
444
447
|
// Listings are only candidate supersets; scope is re-checked per prompt.
|
|
445
|
-
const list = provider === 'claude-code' ? listClaudeSessionFilesAsync : listCodexSessionFilesAsync;
|
|
446
448
|
const cache = new DirectoryListingCache();
|
|
447
449
|
for (const root of listingRoots) {
|
|
448
450
|
let files;
|
|
449
451
|
try {
|
|
450
|
-
files =
|
|
452
|
+
files =
|
|
453
|
+
provider === 'claude-code'
|
|
454
|
+
? await listClaudeSessionFilesAsync(root, listOptions, cache)
|
|
455
|
+
: await listCodexSessionFilesAsync(root, {
|
|
456
|
+
...listOptions,
|
|
457
|
+
includeCompressed: options.includeCompressedRollouts === true && canReadCompressedRollouts(),
|
|
458
|
+
}, cache);
|
|
451
459
|
}
|
|
452
460
|
catch {
|
|
453
461
|
files = [];
|
|
@@ -469,11 +477,11 @@ export async function listCandidates(providers, listingRoots, sinceMs) {
|
|
|
469
477
|
compareStrings(left.sessionId, right.sessionId) ||
|
|
470
478
|
compareStrings(left.key, right.key));
|
|
471
479
|
}
|
|
472
|
-
/** True when the file's first bytes still match a recorded head. */
|
|
480
|
+
/** True when the file's first bytes (a compressed rollout's content) still match a recorded head. */
|
|
473
481
|
async function fileStartsWith(filePath, head) {
|
|
474
482
|
let handle;
|
|
475
483
|
try {
|
|
476
|
-
handle = await
|
|
484
|
+
({ handle } = await openRolloutReadHandle(filePath));
|
|
477
485
|
return headMatches(head, await readAt(handle, 0, Math.min(HEAD_BYTES, head.bytes)));
|
|
478
486
|
}
|
|
479
487
|
catch {
|
|
@@ -546,8 +554,11 @@ async function verifyFile(handle, size, session) {
|
|
|
546
554
|
return { head: fileHeadOf(head), bytesRead };
|
|
547
555
|
}
|
|
548
556
|
/** @internal */
|
|
549
|
-
export async function scanFile(candidate,
|
|
550
|
-
|
|
557
|
+
export async function scanFile(candidate, fileStat, prior, budget, context) {
|
|
558
|
+
// A compressed rollout is scanned over its decompressed content.
|
|
559
|
+
const opened = await openRolloutReadHandle(candidate.filePath);
|
|
560
|
+
const handle = opened.handle;
|
|
561
|
+
const stat = opened.size === null ? fileStat : { size: opened.size, mtimeMs: fileStat.mtimeMs };
|
|
551
562
|
try {
|
|
552
563
|
let bytesRead = 0;
|
|
553
564
|
const verified = await verifyFile(handle, stat.size, prior);
|
|
@@ -13,6 +13,7 @@ import { StringDecoder } from 'string_decoder';
|
|
|
13
13
|
import { readSessionContextSnapshot } from '../context/sessionContext';
|
|
14
14
|
import { readSessionFileStats } from '../sessionStats';
|
|
15
15
|
import { JsonlParser } from '../parsers/jsonl';
|
|
16
|
+
import { FileContinuity } from '../fileContinuity';
|
|
16
17
|
import { ClaudeUsageDeduper, normalizeClaudeUsage, } from '../usage/claudeUsageDedupe';
|
|
17
18
|
import { ProviderDiagnosticTracker } from './diagnostics';
|
|
18
19
|
import { DirectoryListingCache, splitWatchedPath } from './directoryListingCache';
|
|
@@ -110,7 +111,10 @@ class ClaudeCodeReader {
|
|
|
110
111
|
/** Cursor after the last complete line, safe to persist across restarts. */
|
|
111
112
|
committedPosition = 0;
|
|
112
113
|
events = [];
|
|
113
|
-
|
|
114
|
+
/** Why the last `readNew()` started over from offset 0, if it did. */
|
|
115
|
+
_lastReset = null;
|
|
116
|
+
/** Whether the file is still the one the positions refer to. */
|
|
117
|
+
continuity = new FileContinuity();
|
|
114
118
|
decoder = new StringDecoder('utf8');
|
|
115
119
|
/** Split lines of one response repeat its usage; count each response once. */
|
|
116
120
|
usageDeduper;
|
|
@@ -131,37 +135,28 @@ class ClaudeCodeReader {
|
|
|
131
135
|
}
|
|
132
136
|
readNew() {
|
|
133
137
|
this.events = [];
|
|
134
|
-
this.
|
|
138
|
+
this._lastReset = null;
|
|
139
|
+
let fd = null;
|
|
135
140
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const stats = fs.
|
|
141
|
+
// Open first and stat the descriptor: the size and identity checked
|
|
142
|
+
// are those of the file actually read, even if the path is replaced.
|
|
143
|
+
fd = fs.openSync(this.sessionPath, 'r');
|
|
144
|
+
const stats = fs.fstatSync(fd);
|
|
145
|
+
const reason = this.continuity.check(fd, stats, this.filePosition, this.committedPosition);
|
|
146
|
+
// Truncated, rewritten, or replaced: what was read no longer describes
|
|
147
|
+
// the file, so start over from its beginning.
|
|
148
|
+
if (reason !== null)
|
|
149
|
+
this.restart(reason);
|
|
150
|
+
this.continuity.adopt(stats);
|
|
140
151
|
const currentSize = stats.size;
|
|
141
|
-
// Handle truncation
|
|
142
|
-
if (currentSize < this.filePosition) {
|
|
143
|
-
this._wasTruncated = true;
|
|
144
|
-
this.filePosition = 0;
|
|
145
|
-
this.committedPosition = 0;
|
|
146
|
-
this.parser.reset();
|
|
147
|
-
this.decoder = new StringDecoder('utf8');
|
|
148
|
-
this.usageDeduper.reset();
|
|
149
|
-
}
|
|
150
152
|
// No new content
|
|
151
153
|
if (currentSize <= this.filePosition) {
|
|
152
|
-
return
|
|
154
|
+
return this.events;
|
|
153
155
|
}
|
|
154
156
|
// Read new bytes from last position
|
|
155
|
-
const fd = fs.openSync(this.sessionPath, 'r');
|
|
156
157
|
const bufferSize = currentSize - this.filePosition;
|
|
157
158
|
const buffer = Buffer.alloc(bufferSize);
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
|
|
161
|
-
}
|
|
162
|
-
finally {
|
|
163
|
-
fs.closeSync(fd);
|
|
164
|
-
}
|
|
159
|
+
const bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
|
|
165
160
|
const bytes = buffer.subarray(0, bytesRead);
|
|
166
161
|
const newline = bytes.lastIndexOf(0x0a);
|
|
167
162
|
if (newline >= 0)
|
|
@@ -171,10 +166,31 @@ class ClaudeCodeReader {
|
|
|
171
166
|
this.filePosition += bytesRead;
|
|
172
167
|
}
|
|
173
168
|
catch (error) {
|
|
174
|
-
|
|
169
|
+
if (error?.code !== 'ENOENT') {
|
|
170
|
+
console.error(`ClaudeCodeReader: error reading ${this.sessionPath}: ${error}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
if (fd !== null) {
|
|
175
|
+
try {
|
|
176
|
+
fs.closeSync(fd);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Ignore close errors.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
175
182
|
}
|
|
176
183
|
return this.events;
|
|
177
184
|
}
|
|
185
|
+
/** Starts over from offset 0 of a file that was truncated, rewritten, or replaced. */
|
|
186
|
+
restart(reason) {
|
|
187
|
+
this._lastReset = { reason, previousPosition: this.committedPosition };
|
|
188
|
+
this.filePosition = 0;
|
|
189
|
+
this.committedPosition = 0;
|
|
190
|
+
this.parser.reset();
|
|
191
|
+
this.decoder = new StringDecoder('utf8');
|
|
192
|
+
this.usageDeduper.reset();
|
|
193
|
+
}
|
|
178
194
|
readAll() {
|
|
179
195
|
this.reset();
|
|
180
196
|
return this.readNew();
|
|
@@ -185,7 +201,8 @@ class ClaudeCodeReader {
|
|
|
185
201
|
this.parser.reset();
|
|
186
202
|
this.decoder = new StringDecoder('utf8');
|
|
187
203
|
this.usageDeduper.reset();
|
|
188
|
-
this.
|
|
204
|
+
this._lastReset = null;
|
|
205
|
+
this.continuity.forget();
|
|
189
206
|
}
|
|
190
207
|
exists() {
|
|
191
208
|
return fs.existsSync(this.sessionPath);
|
|
@@ -205,9 +222,15 @@ class ClaudeCodeReader {
|
|
|
205
222
|
this.committedPosition = position;
|
|
206
223
|
this.parser.reset();
|
|
207
224
|
this.decoder = new StringDecoder('utf8');
|
|
225
|
+
// The position comes from elsewhere (a snapshot): adopt the file the next
|
|
226
|
+
// read finds rather than compare it with one read before.
|
|
227
|
+
this.continuity.forget();
|
|
208
228
|
}
|
|
209
229
|
wasTruncated() {
|
|
210
|
-
return this.
|
|
230
|
+
return this._lastReset !== null;
|
|
231
|
+
}
|
|
232
|
+
lastReset() {
|
|
233
|
+
return this._lastReset ? { ...this._lastReset } : null;
|
|
211
234
|
}
|
|
212
235
|
/** The usage deduper's recent message ids, so a resumed reader does not recount a split response. */
|
|
213
236
|
getResumeState() {
|