sidekick-shared 0.24.5 → 0.25.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 +35 -0
- package/README.md +159 -52
- package/dist/accountChangeSignal.d.ts +3 -0
- package/dist/accountChangeSignal.js +19 -0
- package/dist/accountChanges.d.ts +21 -0
- package/dist/accountChanges.js +151 -0
- package/dist/accountRegistry.d.ts +3 -1
- package/dist/accountRegistry.js +5 -0
- package/dist/browser.d.ts +8 -3
- package/dist/browser.js +15 -1
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/index.d.ts +23 -13
- package/dist/index.js +34 -10
- package/dist/modelAliases.d.ts +13 -0
- package/dist/modelAliases.js +58 -0
- package/dist/modelCatalog.d.ts +32 -0
- package/dist/modelCatalog.js +145 -0
- package/dist/modelContext.d.ts +36 -3
- package/dist/modelContext.js +109 -34
- package/dist/modelInfo.d.ts +30 -1
- package/dist/modelInfo.js +116 -26
- package/dist/multiProviderQuotaService.d.ts +8 -0
- package/dist/multiProviderQuotaService.js +64 -3
- package/dist/node.d.ts +4 -4
- package/dist/node.js +4 -1
- package/dist/observedSessionCollector.d.ts +75 -4
- package/dist/observedSessionCollector.js +452 -42
- package/dist/parsers/codexHistory.d.ts +4 -2
- package/dist/parsers/codexHistory.js +9 -2
- package/dist/parsers/sessionActivityDetector.d.ts +6 -0
- package/dist/parsers/sessionActivityDetector.js +17 -0
- package/dist/providerIds.d.ts +11 -0
- package/dist/providerIds.js +9 -0
- package/dist/providerQuota.d.ts +3 -1
- package/dist/providerQuota.js +3 -0
- package/dist/providers/claudeCode.d.ts +10 -1
- package/dist/providers/claudeCode.js +204 -39
- package/dist/providers/codex.d.ts +18 -1
- package/dist/providers/codex.js +269 -40
- package/dist/providers/codexDatabase.d.ts +9 -0
- package/dist/providers/codexDatabase.js +107 -2
- package/dist/providers/diagnostics.d.ts +13 -0
- package/dist/providers/diagnostics.js +88 -0
- package/dist/providers/factory.d.ts +14 -0
- package/dist/providers/factory.js +57 -0
- package/dist/providers/openCode.d.ts +20 -1
- package/dist/providers/openCode.js +361 -11
- package/dist/providers/openCodeDatabase.d.ts +10 -0
- package/dist/providers/openCodeDatabase.js +69 -0
- package/dist/providers/types.d.ts +52 -2
- package/dist/providers/types.js +4 -0
- package/dist/quota.d.ts +4 -1
- package/dist/quota.js +3 -7
- package/dist/quotaHistory.d.ts +2 -1
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/schemas/accountManager.d.ts +2 -2
- package/dist/schemas/accountManager.js +2 -4
- package/dist/schemas/accountStatus.d.ts +3 -0
- package/dist/schemas/accountStatus.js +1 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/observedSessionV1.d.ts +27 -26
- package/dist/schemas/observedSessionV1.js +6 -4
- package/dist/schemas/quota.d.ts +2 -2
- package/dist/schemas/quota.js +3 -6
- package/dist/schemas/quotaHistory.d.ts +2 -2
- package/dist/schemas/tokenEstimation.d.ts +1 -1
- package/dist/sessionMonitor.d.ts +15 -0
- package/dist/sessionMonitor.js +142 -0
- package/dist/sessionPreviews.d.ts +26 -1
- package/dist/sessionPreviews.js +319 -4
- package/dist/types/observedSessionV1.d.ts +6 -2
- package/dist/types/observedSessionV1.js +14 -8
- package/package.json +22 -1
package/dist/sessionPreviews.js
CHANGED
|
@@ -53,26 +53,46 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
53
53
|
})();
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
55
|
exports.readSessionPreview = readSessionPreview;
|
|
56
|
+
exports.readSessionPreviewAsync = readSessionPreviewAsync;
|
|
56
57
|
exports.listSessionPreviews = listSessionPreviews;
|
|
58
|
+
exports.listSessionPreviewsAsync = listSessionPreviewsAsync;
|
|
57
59
|
const fs = __importStar(require("fs"));
|
|
58
60
|
const DEFAULT_PREFIX_BYTES = 16 * 1024;
|
|
59
61
|
const DEFAULT_LIMIT = 50;
|
|
62
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
63
|
+
const MAX_CONCURRENCY = 16;
|
|
60
64
|
/**
|
|
61
65
|
* Reads a bounded preview of one session file. Returns `null` when the file is
|
|
62
66
|
* missing or unreadable; malformed content degrades to `null` fields instead
|
|
63
67
|
* of throwing.
|
|
64
68
|
*/
|
|
65
69
|
function readSessionPreview(provider, sessionPath, options = {}) {
|
|
66
|
-
let
|
|
70
|
+
let sizeBytes;
|
|
67
71
|
let modifiedAt;
|
|
68
72
|
try {
|
|
69
|
-
stats = fs.statSync(sessionPath);
|
|
73
|
+
const stats = fs.statSync(sessionPath);
|
|
74
|
+
sizeBytes = stats.size;
|
|
70
75
|
// Inside the try: an out-of-range mtime RangeErrors on serialization,
|
|
71
76
|
// and this function's contract is to degrade, not throw.
|
|
72
77
|
modifiedAt = stats.mtime.toISOString();
|
|
73
78
|
}
|
|
74
79
|
catch {
|
|
75
|
-
|
|
80
|
+
let metadata;
|
|
81
|
+
try {
|
|
82
|
+
metadata = provider.getSessionMetadata?.(sessionPath);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
if (!metadata)
|
|
88
|
+
return null;
|
|
89
|
+
sizeBytes = 0;
|
|
90
|
+
try {
|
|
91
|
+
modifiedAt = metadata.mtime.toISOString();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
76
96
|
}
|
|
77
97
|
let firstUserPrompt = null;
|
|
78
98
|
try {
|
|
@@ -94,12 +114,42 @@ function readSessionPreview(provider, sessionPath, options = {}) {
|
|
|
94
114
|
sessionId,
|
|
95
115
|
filePath: sessionPath,
|
|
96
116
|
modifiedAt,
|
|
97
|
-
sizeBytes
|
|
117
|
+
sizeBytes,
|
|
98
118
|
firstUserPrompt,
|
|
99
119
|
firstTimestamp: prefix.firstTimestamp,
|
|
100
120
|
workspacePath: prefix.workspacePath,
|
|
101
121
|
};
|
|
102
122
|
}
|
|
123
|
+
/** Event-loop-safe counterpart to readSessionPreview with per-call degradation status. */
|
|
124
|
+
async function readSessionPreviewAsync(provider, sessionPath, options = {}) {
|
|
125
|
+
let labels;
|
|
126
|
+
const diagnostics = [];
|
|
127
|
+
try {
|
|
128
|
+
labels = await extractLabelsAsync(provider, [sessionPath], options);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
labels = new Map([[sessionPath, null]]);
|
|
132
|
+
diagnostics.push({
|
|
133
|
+
providerId: provider.id,
|
|
134
|
+
kind: 'read_failed',
|
|
135
|
+
severity: 'warning',
|
|
136
|
+
phase: 'read',
|
|
137
|
+
message: `${provider.id} session label was unavailable.`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const info = await statSessionFileAsync(provider, sessionPath);
|
|
141
|
+
if (!info) {
|
|
142
|
+
return {
|
|
143
|
+
preview: null,
|
|
144
|
+
diagnostics: dedupeDiagnostics([...diagnostics, ...providerDiagnostics(provider)]),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const preview = await readSessionPreviewFromInfoAsync(provider, info, labels.get(sessionPath) ?? null, options);
|
|
148
|
+
return {
|
|
149
|
+
preview,
|
|
150
|
+
diagnostics: dedupeDiagnostics([...diagnostics, ...providerDiagnostics(provider)]),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
103
153
|
/**
|
|
104
154
|
* Lists previews across providers, newest first. Enumeration is stat-only;
|
|
105
155
|
* content is read only for the post-`limit` survivors.
|
|
@@ -125,6 +175,75 @@ function listSessionPreviews(providers, options = {}) {
|
|
|
125
175
|
}
|
|
126
176
|
return previews;
|
|
127
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Lists previews without blocking the caller loop on corpus-wide synchronous
|
|
180
|
+
* work. SQLite-backed providers perform at most one subprocess per call.
|
|
181
|
+
*/
|
|
182
|
+
async function listSessionPreviewsAsync(providers, options = {}) {
|
|
183
|
+
const limit = Math.max(0, options.limit ?? DEFAULT_LIMIT);
|
|
184
|
+
const sinceMs = resolveSinceMs(options.since);
|
|
185
|
+
const candidates = [];
|
|
186
|
+
const operationDiagnostics = [];
|
|
187
|
+
await Promise.all(providers.map(async (provider) => {
|
|
188
|
+
let files = [];
|
|
189
|
+
try {
|
|
190
|
+
files = provider.listSessionFilesAsync
|
|
191
|
+
? await provider.listSessionFilesAsync(options.workspacePath)
|
|
192
|
+
: await enumerateSessionFilesAsyncFallback(provider, options.workspacePath, options);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
files = [];
|
|
196
|
+
operationDiagnostics.push({
|
|
197
|
+
providerId: provider.id,
|
|
198
|
+
kind: 'read_failed',
|
|
199
|
+
severity: 'error',
|
|
200
|
+
phase: 'enumerate',
|
|
201
|
+
message: `${provider.id} session enumeration failed.`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
for (const info of files) {
|
|
205
|
+
const mtimeMs = info.mtime.getTime();
|
|
206
|
+
if (sinceMs !== null && mtimeMs <= sinceMs)
|
|
207
|
+
continue;
|
|
208
|
+
candidates.push({ provider, info });
|
|
209
|
+
}
|
|
210
|
+
}));
|
|
211
|
+
candidates.sort((left, right) => right.info.mtime.getTime() - left.info.mtime.getTime());
|
|
212
|
+
const selected = candidates.slice(0, limit);
|
|
213
|
+
const labels = new Map();
|
|
214
|
+
await Promise.all(providers.map(async (provider) => {
|
|
215
|
+
const paths = selected
|
|
216
|
+
.filter((item) => item.provider === provider)
|
|
217
|
+
.map((item) => item.info.path);
|
|
218
|
+
if (paths.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
try {
|
|
221
|
+
labels.set(provider, await extractLabelsAsync(provider, paths, options));
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
labels.set(provider, new Map(paths.map((sessionPath) => [sessionPath, null])));
|
|
225
|
+
operationDiagnostics.push({
|
|
226
|
+
providerId: provider.id,
|
|
227
|
+
kind: 'read_failed',
|
|
228
|
+
severity: 'warning',
|
|
229
|
+
phase: 'read',
|
|
230
|
+
message: `${provider.id} session labels were unavailable.`,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}));
|
|
234
|
+
const previews = await mapWithConcurrency(selected, async ({ provider, info }) => {
|
|
235
|
+
const preview = await readSessionPreviewFromInfoAsync(provider, info, labels.get(provider)?.get(info.path) ?? info.label ?? null, options);
|
|
236
|
+
await yieldControl(options);
|
|
237
|
+
return preview;
|
|
238
|
+
}, concurrency(options));
|
|
239
|
+
return {
|
|
240
|
+
previews: previews.filter((preview) => preview !== null),
|
|
241
|
+
diagnostics: dedupeDiagnostics([
|
|
242
|
+
...operationDiagnostics,
|
|
243
|
+
...providers.flatMap(providerDiagnostics),
|
|
244
|
+
]),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
128
247
|
function resolveSinceMs(since) {
|
|
129
248
|
if (since === undefined)
|
|
130
249
|
return null;
|
|
@@ -169,6 +288,150 @@ function statSessionFile(sessionPath) {
|
|
|
169
288
|
return null;
|
|
170
289
|
}
|
|
171
290
|
}
|
|
291
|
+
async function statSessionFileAsync(provider, sessionPath) {
|
|
292
|
+
try {
|
|
293
|
+
const stat = await fs.promises.stat(sessionPath);
|
|
294
|
+
return {
|
|
295
|
+
path: sessionPath,
|
|
296
|
+
mtime: stat.mtime,
|
|
297
|
+
sizeBytes: stat.size,
|
|
298
|
+
sessionId: safeSessionId(provider, sessionPath),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
let metadata = null;
|
|
303
|
+
try {
|
|
304
|
+
// Async first: for DB-backed synthetic paths the sync variant can spawn
|
|
305
|
+
// a blocking sqlite3 subprocess, defeating this API's event-loop safety.
|
|
306
|
+
if (provider.getSessionMetadataAsync) {
|
|
307
|
+
metadata = await provider.getSessionMetadataAsync(sessionPath);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
metadata = provider.getSessionMetadata?.(sessionPath) ?? null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
metadata = null;
|
|
315
|
+
}
|
|
316
|
+
if (!metadata)
|
|
317
|
+
return null;
|
|
318
|
+
return {
|
|
319
|
+
path: sessionPath,
|
|
320
|
+
mtime: metadata.mtime,
|
|
321
|
+
sizeBytes: 0,
|
|
322
|
+
sessionId: safeSessionId(provider, sessionPath),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async function extractLabelsAsync(provider, sessionPaths, options) {
|
|
327
|
+
if (provider.extractSessionLabelsAsync) {
|
|
328
|
+
// Callers deliberately do not retry through the synchronous provider path
|
|
329
|
+
// if this batch fails: DB-backed providers could otherwise spawn once per
|
|
330
|
+
// session after their single async batch failed.
|
|
331
|
+
return provider.extractSessionLabelsAsync(sessionPaths);
|
|
332
|
+
}
|
|
333
|
+
const values = new Map();
|
|
334
|
+
for (const sessionPath of sessionPaths) {
|
|
335
|
+
values.set(sessionPath, await extractLabelWithYield(provider, sessionPath, options));
|
|
336
|
+
}
|
|
337
|
+
return values;
|
|
338
|
+
}
|
|
339
|
+
async function enumerateSessionFilesAsyncFallback(provider, workspacePath, options) {
|
|
340
|
+
await yieldControl(options);
|
|
341
|
+
const paths = workspacePath
|
|
342
|
+
? provider.findAllSessions(workspacePath)
|
|
343
|
+
: provider.listAllSessionFiles
|
|
344
|
+
? provider.listAllSessionFiles().map((item) => item.path)
|
|
345
|
+
: provider
|
|
346
|
+
.getAllProjectFolders()
|
|
347
|
+
.flatMap((folder) => provider.findSessionsInDirectory(folder.dir));
|
|
348
|
+
return (await mapWithConcurrency([...new Set(paths)], async (sessionPath) => {
|
|
349
|
+
const info = await statSessionFileAsync(provider, sessionPath);
|
|
350
|
+
await yieldControl(options);
|
|
351
|
+
return info;
|
|
352
|
+
}, concurrency(options))).filter((item) => item !== null);
|
|
353
|
+
}
|
|
354
|
+
async function readSessionPreviewFromInfoAsync(provider, info, firstUserPrompt, options) {
|
|
355
|
+
let modifiedAt;
|
|
356
|
+
try {
|
|
357
|
+
modifiedAt = info.mtime.toISOString();
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const prefix = await scanPrefixAsync(info.path, options.maxPrefixBytes ?? DEFAULT_PREFIX_BYTES);
|
|
363
|
+
return {
|
|
364
|
+
provider: provider.id,
|
|
365
|
+
sessionId: info.sessionId ?? safeSessionId(provider, info.path),
|
|
366
|
+
filePath: info.path,
|
|
367
|
+
modifiedAt,
|
|
368
|
+
sizeBytes: info.sizeBytes ?? 0,
|
|
369
|
+
firstUserPrompt,
|
|
370
|
+
firstTimestamp: prefix.firstTimestamp ?? info.createdAt?.toISOString() ?? null,
|
|
371
|
+
workspacePath: prefix.workspacePath ?? info.workspacePath ?? null,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async function extractLabelWithYield(provider, sessionPath, options) {
|
|
375
|
+
await yieldControl(options);
|
|
376
|
+
try {
|
|
377
|
+
return provider.extractSessionLabel(sessionPath);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function safeSessionId(provider, sessionPath) {
|
|
384
|
+
try {
|
|
385
|
+
return provider.getSessionId(sessionPath);
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return '';
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function concurrency(options) {
|
|
392
|
+
const requested = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
393
|
+
if (!Number.isFinite(requested))
|
|
394
|
+
return DEFAULT_CONCURRENCY;
|
|
395
|
+
return Math.min(MAX_CONCURRENCY, Math.max(1, Math.floor(requested)));
|
|
396
|
+
}
|
|
397
|
+
async function yieldControl(options) {
|
|
398
|
+
if (options.yieldBetweenReads) {
|
|
399
|
+
await options.yieldBetweenReads();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
403
|
+
}
|
|
404
|
+
async function mapWithConcurrency(values, mapper, maxConcurrency) {
|
|
405
|
+
const results = new Array(values.length);
|
|
406
|
+
let cursor = 0;
|
|
407
|
+
await Promise.all(Array.from({ length: Math.min(maxConcurrency, values.length) }, async () => {
|
|
408
|
+
while (true) {
|
|
409
|
+
const index = cursor++;
|
|
410
|
+
if (index >= values.length)
|
|
411
|
+
return;
|
|
412
|
+
results[index] = await mapper(values[index]);
|
|
413
|
+
}
|
|
414
|
+
}));
|
|
415
|
+
return results;
|
|
416
|
+
}
|
|
417
|
+
function providerDiagnostics(provider) {
|
|
418
|
+
try {
|
|
419
|
+
return [...(provider.getLastOperationStatus?.().diagnostics ?? [])];
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function dedupeDiagnostics(diagnostics) {
|
|
426
|
+
const seen = new Set();
|
|
427
|
+
return diagnostics.filter((diagnostic) => {
|
|
428
|
+
const key = `${diagnostic.providerId}\0${diagnostic.kind}\0${diagnostic.phase}`;
|
|
429
|
+
if (seen.has(key))
|
|
430
|
+
return false;
|
|
431
|
+
seen.add(key);
|
|
432
|
+
return true;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
172
435
|
/**
|
|
173
436
|
* Parses complete JSONL lines out of the first `maxPrefixBytes` of a file,
|
|
174
437
|
* taking the first top-level `timestamp` string and the first workspace path
|
|
@@ -232,3 +495,55 @@ function scanPrefix(sessionPath, maxPrefixBytes) {
|
|
|
232
495
|
}
|
|
233
496
|
return result;
|
|
234
497
|
}
|
|
498
|
+
async function scanPrefixAsync(sessionPath, maxPrefixBytes) {
|
|
499
|
+
const result = { firstTimestamp: null, workspacePath: null };
|
|
500
|
+
let handle = null;
|
|
501
|
+
try {
|
|
502
|
+
handle = await fs.promises.open(sessionPath, 'r');
|
|
503
|
+
const stats = await handle.stat();
|
|
504
|
+
const readBytes = Math.min(stats.size, Math.max(1, maxPrefixBytes));
|
|
505
|
+
const buffer = Buffer.alloc(readBytes);
|
|
506
|
+
const { bytesRead } = await handle.read(buffer, 0, readBytes, 0);
|
|
507
|
+
parsePrefixInto(result, buffer.toString('utf8', 0, bytesRead), bytesRead >= stats.size);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
await handle?.close().catch(() => undefined);
|
|
514
|
+
}
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
function parsePrefixInto(result, content, sawWholeFile) {
|
|
518
|
+
const lines = content.split('\n');
|
|
519
|
+
const completeLines = sawWholeFile ? lines : lines.slice(0, -1);
|
|
520
|
+
for (const line of completeLines) {
|
|
521
|
+
const trimmed = line.trim();
|
|
522
|
+
if (!trimmed)
|
|
523
|
+
continue;
|
|
524
|
+
let parsed;
|
|
525
|
+
try {
|
|
526
|
+
parsed = JSON.parse(trimmed);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (parsed === null || typeof parsed !== 'object')
|
|
532
|
+
continue;
|
|
533
|
+
if (result.firstTimestamp === null && typeof parsed.timestamp === 'string') {
|
|
534
|
+
result.firstTimestamp = parsed.timestamp;
|
|
535
|
+
}
|
|
536
|
+
if (result.workspacePath === null) {
|
|
537
|
+
if (typeof parsed.cwd === 'string' && parsed.cwd)
|
|
538
|
+
result.workspacePath = parsed.cwd;
|
|
539
|
+
else if (parsed.type === 'session_meta') {
|
|
540
|
+
const payload = parsed.payload;
|
|
541
|
+
if (payload && typeof payload.cwd === 'string' && payload.cwd) {
|
|
542
|
+
result.workspacePath = payload.cwd;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (result.firstTimestamp !== null && result.workspacePath !== null)
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
@@ -45,6 +45,8 @@ export interface ObservedAgentSessionV1 {
|
|
|
45
45
|
costUsd: ObservedValueV1<number | null>;
|
|
46
46
|
};
|
|
47
47
|
pendingUserRequest: PendingUserRequestV1 | null;
|
|
48
|
+
/** When model/usage content was last read; unchanged on collector cache hits. */
|
|
49
|
+
contentObservedAt?: string;
|
|
48
50
|
observedAt: string;
|
|
49
51
|
}
|
|
50
52
|
export interface ProviderCapabilitiesV1 {
|
|
@@ -67,6 +69,8 @@ export interface ProviderSessionAdapterV1 {
|
|
|
67
69
|
dispose(): void;
|
|
68
70
|
}
|
|
69
71
|
export declare function derivePendingUserRequestV1(provider: ProviderId, sessionId: string, sourcePath: string, events: SessionEvent[]): PendingUserRequestV1 | null;
|
|
70
|
-
export
|
|
72
|
+
export interface ProviderSessionAdapterV1Options {
|
|
71
73
|
pollIntervalMs?: number;
|
|
72
|
-
|
|
74
|
+
observationOnly?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export declare function createProviderSessionAdapterV1(sessionProvider: SessionProviderBase, options?: ProviderSessionAdapterV1Options): ProviderSessionAdapterV1;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.derivePendingUserRequestV1 = derivePendingUserRequestV1;
|
|
4
4
|
exports.createProviderSessionAdapterV1 = createProviderSessionAdapterV1;
|
|
5
5
|
const transcript_1 = require("../transcript");
|
|
6
|
+
const sessionActivityDetector_1 = require("../parsers/sessionActivityDetector");
|
|
6
7
|
function evidence(provider, sessionId, sourcePath, event, eventIndex) {
|
|
7
8
|
return [
|
|
8
9
|
{
|
|
@@ -98,13 +99,13 @@ function waitingToolPrompt(input, toolName) {
|
|
|
98
99
|
return 'Approve the proposed plan?';
|
|
99
100
|
return null;
|
|
100
101
|
}
|
|
101
|
-
function providerCapabilities(provider) {
|
|
102
|
+
function providerCapabilities(provider, observationOnly) {
|
|
102
103
|
const refs = [];
|
|
103
104
|
const known = (value) => observed(value, 'inferred', 1, refs);
|
|
104
105
|
return {
|
|
105
106
|
schemaVersion: 1,
|
|
106
107
|
provider,
|
|
107
|
-
resume: known(
|
|
108
|
+
resume: known(!observationOnly),
|
|
108
109
|
forkLineage: known(provider !== 'opencode'),
|
|
109
110
|
quotaSource: known(provider === 'claude-code' ? 'api' : provider === 'codex' ? 'mixed' : 'none'),
|
|
110
111
|
assetExtraction: known(true),
|
|
@@ -115,7 +116,7 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
115
116
|
const adapter = {
|
|
116
117
|
schemaVersion: 1,
|
|
117
118
|
provider: sessionProvider.id,
|
|
118
|
-
capabilities: providerCapabilities(sessionProvider.id),
|
|
119
|
+
capabilities: providerCapabilities(sessionProvider.id, options.observationOnly === true),
|
|
119
120
|
async discover(cwd) {
|
|
120
121
|
return Promise.all(sessionProvider.findAllSessions(cwd).map((sessionPath) => adapter.read(sessionPath, cwd)));
|
|
121
122
|
},
|
|
@@ -129,11 +130,15 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
129
130
|
});
|
|
130
131
|
const refs = evidence(sessionProvider.id, stats.sessionId, sessionPath);
|
|
131
132
|
const model = Object.entries(stats.modelUsage).sort((left, right) => right[1].calls - left[1].calls)[0]?.[0] ?? null;
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
133
|
+
const activityResult = (0, sessionActivityDetector_1.detectSessionActivity)(sessionPath);
|
|
134
|
+
const activity = activityResult.state === 'ongoing'
|
|
135
|
+
? 'active'
|
|
136
|
+
: activityResult.state === 'stale'
|
|
137
|
+
? 'idle'
|
|
138
|
+
: 'ended';
|
|
135
139
|
const allCostsReported = transcript.usage.costs.length > 0 &&
|
|
136
140
|
transcript.usage.costs.every((cost) => cost.source === 'provider-reported');
|
|
141
|
+
const observedAt = new Date().toISOString();
|
|
137
142
|
return {
|
|
138
143
|
schemaVersion: 1,
|
|
139
144
|
identity: {
|
|
@@ -143,7 +148,7 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
143
148
|
},
|
|
144
149
|
cwd: observed(cwd, 'inferred', cwd ? 0.95 : 0.4, refs),
|
|
145
150
|
model: observed(model, model ? 'reported' : 'inferred', model ? 1 : 0.2, refs),
|
|
146
|
-
activity: observed(
|
|
151
|
+
activity: observed(activity, 'inferred', 0.85, refs),
|
|
147
152
|
usage: {
|
|
148
153
|
inputTokens: observed(stats.tokens.input, 'reported', 1, refs),
|
|
149
154
|
outputTokens: observed(stats.tokens.output, 'reported', 1, refs),
|
|
@@ -157,7 +162,8 @@ function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
|
157
162
|
: 'estimated', allCostsReported ? 1 : transcript.usage.totalCostUsd === null ? 0.5 : 0.9, refs),
|
|
158
163
|
},
|
|
159
164
|
pendingUserRequest: derivePendingUserRequestV1(sessionProvider.id, stats.sessionId, sessionPath, events),
|
|
160
|
-
|
|
165
|
+
contentObservedAt: observedAt,
|
|
166
|
+
observedAt,
|
|
161
167
|
};
|
|
162
168
|
},
|
|
163
169
|
watch(sessionPath, listener, cwd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sidekick-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Shared data access layer for Sidekick — readers, types, providers, credentials, quota",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -35,42 +35,62 @@
|
|
|
35
35
|
"exports": {
|
|
36
36
|
".": {
|
|
37
37
|
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js",
|
|
39
|
+
"require": "./dist/index.js",
|
|
38
40
|
"default": "./dist/index.js"
|
|
39
41
|
},
|
|
40
42
|
"./browser": {
|
|
41
43
|
"types": "./dist/browser.d.ts",
|
|
44
|
+
"import": "./dist/browser.js",
|
|
45
|
+
"require": "./dist/browser.js",
|
|
42
46
|
"default": "./dist/browser.js"
|
|
43
47
|
},
|
|
44
48
|
"./node": {
|
|
45
49
|
"types": "./dist/node.d.ts",
|
|
50
|
+
"import": "./dist/node.js",
|
|
51
|
+
"require": "./dist/node.js",
|
|
46
52
|
"default": "./dist/node.js"
|
|
47
53
|
},
|
|
48
54
|
"./schemas": {
|
|
49
55
|
"types": "./dist/schemas/index.d.ts",
|
|
56
|
+
"import": "./dist/schemas/index.js",
|
|
57
|
+
"require": "./dist/schemas/index.js",
|
|
50
58
|
"default": "./dist/schemas/index.js"
|
|
51
59
|
},
|
|
52
60
|
"./phrases": {
|
|
53
61
|
"types": "./dist/phrases.d.ts",
|
|
62
|
+
"import": "./dist/phrases.js",
|
|
63
|
+
"require": "./dist/phrases.js",
|
|
54
64
|
"default": "./dist/phrases.js"
|
|
55
65
|
},
|
|
56
66
|
"./modelContext": {
|
|
57
67
|
"types": "./dist/modelContext.d.ts",
|
|
68
|
+
"import": "./dist/modelContext.js",
|
|
69
|
+
"require": "./dist/modelContext.js",
|
|
58
70
|
"default": "./dist/modelContext.js"
|
|
59
71
|
},
|
|
60
72
|
"./modelInfo": {
|
|
61
73
|
"types": "./dist/modelInfo.d.ts",
|
|
74
|
+
"import": "./dist/modelInfo.js",
|
|
75
|
+
"require": "./dist/modelInfo.js",
|
|
62
76
|
"default": "./dist/modelInfo.js"
|
|
63
77
|
},
|
|
64
78
|
"./formatting": {
|
|
65
79
|
"types": "./dist/formatting.d.ts",
|
|
80
|
+
"import": "./dist/formatting.js",
|
|
81
|
+
"require": "./dist/formatting.js",
|
|
66
82
|
"default": "./dist/formatting.js"
|
|
67
83
|
},
|
|
68
84
|
"./statusline": {
|
|
69
85
|
"types": "./dist/statusline/index.d.ts",
|
|
86
|
+
"import": "./dist/statusline/index.js",
|
|
87
|
+
"require": "./dist/statusline/index.js",
|
|
70
88
|
"default": "./dist/statusline/index.js"
|
|
71
89
|
},
|
|
72
90
|
"./dist/*": {
|
|
73
91
|
"types": "./dist/*.d.ts",
|
|
92
|
+
"import": "./dist/*.js",
|
|
93
|
+
"require": "./dist/*.js",
|
|
74
94
|
"default": "./dist/*.js"
|
|
75
95
|
},
|
|
76
96
|
"./package.json": "./package.json"
|
|
@@ -131,6 +151,7 @@
|
|
|
131
151
|
"prettier": "3.8.4",
|
|
132
152
|
"typescript": "^5.7.0",
|
|
133
153
|
"typescript-eslint": "^8.0.0",
|
|
154
|
+
"vite": "^8.1.0",
|
|
134
155
|
"vitest": "^4.1.9"
|
|
135
156
|
},
|
|
136
157
|
"license": "MIT",
|