sidekick-shared 0.24.4 → 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 +59 -0
- package/README.md +200 -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/accountManager.d.ts +13 -0
- package/dist/accountManager.js +60 -32
- package/dist/accountRegistry.d.ts +3 -1
- package/dist/accountRegistry.js +64 -41
- package/dist/accounts.js +40 -19
- package/dist/autoSwitch.d.ts +2 -1
- package/dist/autoSwitch.js +32 -7
- package/dist/browser.d.ts +10 -3
- package/dist/browser.js +17 -1
- package/dist/codexProfiles.d.ts +14 -0
- package/dist/codexProfiles.js +268 -116
- package/dist/codexQuotaWatcher.d.ts +6 -0
- package/dist/codexQuotaWatcher.js +101 -47
- package/dist/context/sessionContext.js +2 -4
- package/dist/ensureDefaultAccounts.js +4 -4
- package/dist/index.d.ts +32 -15
- package/dist/index.js +54 -9
- 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 +8 -2
- package/dist/node.js +11 -1
- package/dist/observedContextWindows.d.ts +13 -0
- package/dist/observedContextWindows.js +13 -0
- package/dist/observedSessionCollector.d.ts +75 -4
- package/dist/observedSessionCollector.js +452 -42
- package/dist/parsers/codexHistory.d.ts +32 -0
- package/dist/parsers/codexHistory.js +125 -0
- package/dist/parsers/mcpToolName.d.ts +26 -0
- package/dist/parsers/mcpToolName.js +31 -0
- package/dist/parsers/sessionActivityDetector.d.ts +6 -0
- package/dist/parsers/sessionActivityDetector.js +17 -0
- package/dist/parsers/sessionPathResolver.d.ts +11 -4
- package/dist/parsers/sessionPathResolver.js +11 -4
- package/dist/paths.d.ts +7 -3
- package/dist/paths.js +7 -3
- 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 +11 -1
- package/dist/providers/claudeCode.js +222 -39
- package/dist/providers/codex.d.ts +39 -1
- package/dist/providers/codex.js +326 -39
- 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 +57 -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 +3 -2
- package/dist/quotaHistory.js +2 -2
- package/dist/quotaPoller.d.ts +13 -1
- package/dist/quotaPoller.js +65 -3
- package/dist/quotaSnapshots.js +8 -77
- 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 +85 -0
- package/dist/sessionPreviews.js +549 -0
- package/dist/sessionTranscripts.js +10 -4
- package/dist/types/observedSessionV1.d.ts +6 -2
- package/dist/types/observedSessionV1.js +14 -8
- package/dist/writers/atomic.d.ts +20 -0
- package/dist/writers/atomic.js +190 -19
- package/package.json +22 -1
|
@@ -1,34 +1,200 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.ObservedSessionCollector = void 0;
|
|
4
37
|
exports.observedSessionSourceFromProvider = observedSessionSourceFromProvider;
|
|
38
|
+
exports.fileFingerprintParts = fileFingerprintParts;
|
|
5
39
|
exports.fileFingerprint = fileFingerprint;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
6
41
|
const node_fs_1 = require("node:fs");
|
|
7
42
|
const observedSessionV1_1 = require("./types/observedSessionV1");
|
|
43
|
+
const sessionActivityDetector_1 = require("./parsers/sessionActivityDetector");
|
|
44
|
+
const DEFAULT_MAX_CONCURRENT_READS = 4;
|
|
45
|
+
const MAX_CONCURRENT_READS = 16;
|
|
46
|
+
const DEFAULT_DEBOUNCE_MS = 100;
|
|
47
|
+
const DEFAULT_POLL_INTERVAL_MS = 30_000;
|
|
8
48
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
49
|
+
* Fail-soft observed-session collection with fingerprint-keyed parse caching,
|
|
50
|
+
* cooperative reads, and optional provider-level subscriptions.
|
|
11
51
|
*/
|
|
12
52
|
class ObservedSessionCollector {
|
|
13
53
|
options;
|
|
14
54
|
failures = new Map();
|
|
55
|
+
cache = new Map();
|
|
56
|
+
subscriptions = new Set();
|
|
15
57
|
initialBackoffMs;
|
|
16
58
|
maxBackoffMs;
|
|
17
59
|
clock;
|
|
60
|
+
maxConcurrentReads;
|
|
61
|
+
yieldBetweenReads;
|
|
18
62
|
constructor(options) {
|
|
19
63
|
this.options = options;
|
|
20
64
|
this.initialBackoffMs = Math.max(1, options.initialBackoffMs ?? 30_000);
|
|
21
65
|
this.maxBackoffMs = Math.max(this.initialBackoffMs, options.maxBackoffMs ?? 5 * 60_000);
|
|
22
66
|
this.clock = options.clock ?? { now: () => Date.now() };
|
|
67
|
+
this.maxConcurrentReads = Math.min(MAX_CONCURRENT_READS, Math.max(1, Math.floor(options.maxConcurrentReads ?? DEFAULT_MAX_CONCURRENT_READS)));
|
|
68
|
+
this.yieldBetweenReads = async () => {
|
|
69
|
+
if (options.yieldBetweenReads) {
|
|
70
|
+
await options.yieldBetweenReads();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
74
|
+
};
|
|
23
75
|
}
|
|
24
76
|
async collect() {
|
|
25
77
|
const nested = await Promise.all(this.options.sources.map((source) => this.collectSource(source)));
|
|
26
78
|
return nested.flat();
|
|
27
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe to debounced provider changes. The listener receives fingerprints
|
|
82
|
+
* only; call collect() to consume changed sessions through the parse cache.
|
|
83
|
+
*/
|
|
84
|
+
subscribe(listener, options = {}) {
|
|
85
|
+
const debounceMs = Math.max(0, options.debounceMs ?? DEFAULT_DEBOUNCE_MS);
|
|
86
|
+
const pollIntervalMs = Math.max(0, options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
|
|
87
|
+
const known = new Map();
|
|
88
|
+
for (const item of options.knownFingerprints ?? []) {
|
|
89
|
+
known.set(this.key(item.providerId, item.sessionId), {
|
|
90
|
+
fingerprint: item.fingerprint,
|
|
91
|
+
parts: item.fingerprintParts ?? null,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
for (const [key, entry] of this.cache) {
|
|
95
|
+
known.set(key, { fingerprint: entry.fingerprint, parts: entry.fingerprintParts });
|
|
96
|
+
}
|
|
97
|
+
let disposed = false;
|
|
98
|
+
let debounceTimer = null;
|
|
99
|
+
let reconciling = false;
|
|
100
|
+
let pending = false;
|
|
101
|
+
const sourceDisposables = [];
|
|
102
|
+
const reconcile = async () => {
|
|
103
|
+
if (disposed)
|
|
104
|
+
return;
|
|
105
|
+
if (reconciling) {
|
|
106
|
+
pending = true;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
reconciling = true;
|
|
110
|
+
try {
|
|
111
|
+
do {
|
|
112
|
+
pending = false;
|
|
113
|
+
for (const source of this.options.sources) {
|
|
114
|
+
if (disposed)
|
|
115
|
+
break;
|
|
116
|
+
const batch = await this.reconcileSource(source, known);
|
|
117
|
+
// Re-check after the await: disposal during the reconcile must
|
|
118
|
+
// not deliver the pre-disposal batch to the listener.
|
|
119
|
+
if (disposed)
|
|
120
|
+
break;
|
|
121
|
+
if (batch && batch.changes.length > 0) {
|
|
122
|
+
try {
|
|
123
|
+
listener(batch);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Subscriber failures cannot stop reconciliation.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} while (pending && !disposed);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
reconciling = false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const schedule = () => {
|
|
137
|
+
if (disposed)
|
|
138
|
+
return;
|
|
139
|
+
if (debounceTimer)
|
|
140
|
+
clearTimeout(debounceTimer);
|
|
141
|
+
debounceTimer = setTimeout(() => {
|
|
142
|
+
debounceTimer = null;
|
|
143
|
+
void reconcile();
|
|
144
|
+
}, debounceMs);
|
|
145
|
+
debounceTimer.unref?.();
|
|
146
|
+
};
|
|
147
|
+
let needsCollectorPoll = false;
|
|
148
|
+
for (const source of this.options.sources) {
|
|
149
|
+
if (source.subscribe) {
|
|
150
|
+
try {
|
|
151
|
+
sourceDisposables.push(source.subscribe(schedule, { pollIntervalMs }));
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// The collector-level poll below remains the correctness fallback.
|
|
155
|
+
needsCollectorPoll = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
needsCollectorPoll = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let pollTimer = null;
|
|
163
|
+
if (needsCollectorPoll && pollIntervalMs > 0) {
|
|
164
|
+
pollTimer = setInterval(schedule, pollIntervalMs);
|
|
165
|
+
pollTimer.unref?.();
|
|
166
|
+
}
|
|
167
|
+
schedule();
|
|
168
|
+
const disposable = {
|
|
169
|
+
dispose: () => {
|
|
170
|
+
if (disposed)
|
|
171
|
+
return;
|
|
172
|
+
disposed = true;
|
|
173
|
+
if (debounceTimer)
|
|
174
|
+
clearTimeout(debounceTimer);
|
|
175
|
+
if (pollTimer)
|
|
176
|
+
clearInterval(pollTimer);
|
|
177
|
+
for (const item of sourceDisposables) {
|
|
178
|
+
try {
|
|
179
|
+
item.dispose();
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Disposal is best effort.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
this.subscriptions.delete(disposable);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
this.subscriptions.add(disposable);
|
|
189
|
+
return disposable;
|
|
190
|
+
}
|
|
28
191
|
reset() {
|
|
29
192
|
this.failures.clear();
|
|
193
|
+
this.cache.clear();
|
|
30
194
|
}
|
|
31
195
|
dispose() {
|
|
196
|
+
for (const subscription of [...this.subscriptions])
|
|
197
|
+
subscription.dispose();
|
|
32
198
|
for (const source of this.options.sources) {
|
|
33
199
|
try {
|
|
34
200
|
source.dispose?.();
|
|
@@ -54,49 +220,170 @@ class ObservedSessionCollector {
|
|
|
54
220
|
return [];
|
|
55
221
|
}
|
|
56
222
|
const activeKeys = new Set(references.map((ref) => this.key(source.providerId, ref.sessionId)));
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
const rows = await Promise.all(references.map((reference) => this.collectSession(source, reference)));
|
|
223
|
+
this.evictMissing(source.providerId, activeKeys);
|
|
224
|
+
const rows = await this.mapWithConcurrency(references, async (reference) => {
|
|
225
|
+
const result = await this.collectSession(source, reference);
|
|
226
|
+
await this.yieldBetweenReads();
|
|
227
|
+
return result;
|
|
228
|
+
});
|
|
65
229
|
return rows.filter((row) => row !== null);
|
|
66
230
|
}
|
|
67
231
|
async collectSession(source, reference) {
|
|
68
232
|
const key = this.key(source.providerId, reference.sessionId);
|
|
69
233
|
const fingerprint = await this.fingerprint(source, reference);
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
234
|
+
const priorFailure = this.failures.get(key);
|
|
235
|
+
if (priorFailure &&
|
|
236
|
+
priorFailure.fingerprint === fingerprint.value &&
|
|
237
|
+
this.clock.now() < priorFailure.retryAt) {
|
|
72
238
|
return null;
|
|
239
|
+
}
|
|
240
|
+
const observedAt = new Date(this.clock.now()).toISOString();
|
|
241
|
+
const cached = fingerprint.value === null ? undefined : this.cache.get(key);
|
|
242
|
+
if (cached && cached.fingerprint === fingerprint.value) {
|
|
243
|
+
let value = cached.value;
|
|
244
|
+
try {
|
|
245
|
+
if (source.refreshCached) {
|
|
246
|
+
value = await source.refreshCached(reference, cached.value, observedAt);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
value = cached.value;
|
|
251
|
+
}
|
|
252
|
+
cached.value = value;
|
|
253
|
+
cached.fingerprintParts = fingerprint.parts;
|
|
254
|
+
const observation = this.makeObservation(source, reference, value, fingerprint, true, observedAt, cached.contentObservedAt);
|
|
255
|
+
this.emitObservation(observation);
|
|
256
|
+
return observation;
|
|
257
|
+
}
|
|
73
258
|
let value;
|
|
74
259
|
try {
|
|
75
260
|
value = await source.read(reference);
|
|
76
261
|
}
|
|
77
262
|
catch (error) {
|
|
78
|
-
this.fail(key, 'session-read-failed', source.providerId, reference.sessionId, fingerprint, error);
|
|
263
|
+
this.fail(key, 'session-read-failed', source.providerId, reference.sessionId, fingerprint.value, error);
|
|
79
264
|
return null;
|
|
80
265
|
}
|
|
81
266
|
this.recover(key, 'session-recovered', source.providerId, reference.sessionId);
|
|
82
|
-
const
|
|
267
|
+
const contentObservedAt = contentTimestamp(value) ?? observedAt;
|
|
268
|
+
if (fingerprint.value !== null) {
|
|
269
|
+
this.cache.set(key, {
|
|
270
|
+
value,
|
|
271
|
+
fingerprint: fingerprint.value,
|
|
272
|
+
fingerprintParts: fingerprint.parts,
|
|
273
|
+
contentObservedAt,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
const observation = this.makeObservation(source, reference, value, fingerprint, false, observedAt, contentObservedAt);
|
|
277
|
+
this.emitObservation(observation);
|
|
278
|
+
return observation;
|
|
279
|
+
}
|
|
280
|
+
makeObservation(source, reference, value, fingerprint, cacheHit, observedAt, contentObservedAt) {
|
|
281
|
+
return {
|
|
282
|
+
providerId: source.providerId,
|
|
283
|
+
sessionId: reference.sessionId,
|
|
284
|
+
value,
|
|
285
|
+
fingerprint: fingerprint.value,
|
|
286
|
+
fingerprintParts: fingerprint.parts,
|
|
287
|
+
cacheHit,
|
|
288
|
+
observedAt,
|
|
289
|
+
contentObservedAt,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
emitObservation(observation) {
|
|
83
293
|
try {
|
|
84
294
|
this.options.onObservation?.(observation);
|
|
85
295
|
}
|
|
86
296
|
catch {
|
|
87
297
|
// Host callbacks cannot turn a healthy provider read into a collection failure.
|
|
88
298
|
}
|
|
89
|
-
|
|
299
|
+
}
|
|
300
|
+
async reconcileSource(source, known) {
|
|
301
|
+
let references;
|
|
302
|
+
try {
|
|
303
|
+
references = await source.discover();
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
const changes = [];
|
|
309
|
+
const seen = new Set();
|
|
310
|
+
for (const reference of references) {
|
|
311
|
+
const key = this.key(source.providerId, reference.sessionId);
|
|
312
|
+
seen.add(key);
|
|
313
|
+
const current = await this.fingerprint(source, reference);
|
|
314
|
+
if (current.value === null)
|
|
315
|
+
continue;
|
|
316
|
+
const previous = known.get(key);
|
|
317
|
+
if (!previous || previous.fingerprint !== current.value) {
|
|
318
|
+
changes.push({
|
|
319
|
+
type: previous ? 'changed' : 'added',
|
|
320
|
+
reference,
|
|
321
|
+
previousFingerprint: previous?.fingerprint ?? null,
|
|
322
|
+
previousFingerprintParts: previous?.parts ?? null,
|
|
323
|
+
fingerprint: current.value,
|
|
324
|
+
fingerprintParts: current.parts,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
known.set(key, { fingerprint: current.value, parts: current.parts });
|
|
328
|
+
}
|
|
329
|
+
for (const [key, previous] of [...known]) {
|
|
330
|
+
if (!key.startsWith(`${source.providerId}\0`) || seen.has(key))
|
|
331
|
+
continue;
|
|
332
|
+
const sessionId = key.slice(source.providerId.length + 1);
|
|
333
|
+
changes.push({
|
|
334
|
+
type: 'removed',
|
|
335
|
+
reference: { sessionId },
|
|
336
|
+
previousFingerprint: previous.fingerprint,
|
|
337
|
+
previousFingerprintParts: previous.parts,
|
|
338
|
+
fingerprint: null,
|
|
339
|
+
fingerprintParts: null,
|
|
340
|
+
});
|
|
341
|
+
known.delete(key);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
providerId: source.providerId,
|
|
345
|
+
changes,
|
|
346
|
+
observedAt: new Date(this.clock.now()).toISOString(),
|
|
347
|
+
};
|
|
90
348
|
}
|
|
91
349
|
async fingerprint(source, reference) {
|
|
350
|
+
let value = reference.fingerprintHint ?? null;
|
|
92
351
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
: (reference.fingerprintHint ?? null);
|
|
352
|
+
if (this.options.fingerprint)
|
|
353
|
+
value = await this.options.fingerprint(source, reference);
|
|
96
354
|
}
|
|
97
355
|
catch {
|
|
98
|
-
|
|
356
|
+
// Retain the reference hint.
|
|
99
357
|
}
|
|
358
|
+
const parts = reference.fingerprintParts ?? parseFingerprintParts(value);
|
|
359
|
+
return { value, parts };
|
|
360
|
+
}
|
|
361
|
+
evictMissing(providerId, activeKeys) {
|
|
362
|
+
for (const key of this.failures.keys()) {
|
|
363
|
+
if (key.startsWith(`${providerId}\0`) &&
|
|
364
|
+
!key.endsWith('$discovery') &&
|
|
365
|
+
!activeKeys.has(key)) {
|
|
366
|
+
this.failures.delete(key);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const key of this.cache.keys()) {
|
|
370
|
+
if (key.startsWith(`${providerId}\0`) && !activeKeys.has(key))
|
|
371
|
+
this.cache.delete(key);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async mapWithConcurrency(values, mapper) {
|
|
375
|
+
const results = new Array(values.length);
|
|
376
|
+
let cursor = 0;
|
|
377
|
+
const workers = Array.from({ length: Math.min(this.maxConcurrentReads, values.length) }, async () => {
|
|
378
|
+
while (true) {
|
|
379
|
+
const index = cursor++;
|
|
380
|
+
if (index >= values.length)
|
|
381
|
+
return;
|
|
382
|
+
results[index] = await mapper(values[index]);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
await Promise.all(workers);
|
|
386
|
+
return results;
|
|
100
387
|
}
|
|
101
388
|
fail(key, kind, providerId, sessionId, fingerprint, error) {
|
|
102
389
|
const previous = this.failures.get(key);
|
|
@@ -106,14 +393,17 @@ class ObservedSessionCollector {
|
|
|
106
393
|
const delay = Math.min(this.maxBackoffMs, this.initialBackoffMs * 2 ** (attempt - 1));
|
|
107
394
|
const retryAt = this.clock.now() + delay;
|
|
108
395
|
const reportedSignature = previous?.reportedSignature ?? '';
|
|
109
|
-
this.failures.set(key, {
|
|
110
|
-
attempt,
|
|
111
|
-
retryAt,
|
|
112
|
-
fingerprint,
|
|
113
|
-
reportedSignature: signature,
|
|
114
|
-
});
|
|
396
|
+
this.failures.set(key, { attempt, retryAt, fingerprint, reportedSignature: signature });
|
|
115
397
|
if (signature !== reportedSignature || fingerprintChanged) {
|
|
116
|
-
this.emitDiagnostic({
|
|
398
|
+
this.emitDiagnostic({
|
|
399
|
+
kind,
|
|
400
|
+
severity: 'error',
|
|
401
|
+
phase: kind === 'provider-discovery-failed' ? 'discover' : 'read',
|
|
402
|
+
providerId,
|
|
403
|
+
sessionId,
|
|
404
|
+
attempt,
|
|
405
|
+
retryAt,
|
|
406
|
+
});
|
|
117
407
|
}
|
|
118
408
|
}
|
|
119
409
|
recover(key, kind, providerId, sessionId) {
|
|
@@ -121,7 +411,14 @@ class ObservedSessionCollector {
|
|
|
121
411
|
if (!prior)
|
|
122
412
|
return;
|
|
123
413
|
this.failures.delete(key);
|
|
124
|
-
this.emitDiagnostic({
|
|
414
|
+
this.emitDiagnostic({
|
|
415
|
+
kind,
|
|
416
|
+
severity: 'info',
|
|
417
|
+
phase: 'recover',
|
|
418
|
+
providerId,
|
|
419
|
+
sessionId,
|
|
420
|
+
attempt: prior.attempt,
|
|
421
|
+
});
|
|
125
422
|
}
|
|
126
423
|
emitDiagnostic(diagnostic) {
|
|
127
424
|
try {
|
|
@@ -140,38 +437,151 @@ exports.ObservedSessionCollector = ObservedSessionCollector;
|
|
|
140
437
|
* Adapt a provider using path-only discovery and independent adapter reads, so
|
|
141
438
|
* one malformed session cannot reject discovery of its healthy siblings.
|
|
142
439
|
*/
|
|
143
|
-
function observedSessionSourceFromProvider(provider, cwd) {
|
|
144
|
-
const adapter =
|
|
440
|
+
function observedSessionSourceFromProvider(provider, cwd, options = {}) {
|
|
441
|
+
const adapter = options.observationOnly
|
|
442
|
+
? (0, observedSessionV1_1.createProviderSessionAdapterV1)(provider, { observationOnly: true })
|
|
443
|
+
: (0, observedSessionV1_1.createProviderSessionAdapterV1)(provider);
|
|
444
|
+
const referenceFromPath = (sessionPath, sessionId = provider.getSessionId(sessionPath), suppliedParts) => {
|
|
445
|
+
const metadata = suppliedParts ? null : provider.getSessionMetadata?.(sessionPath);
|
|
446
|
+
const parts = suppliedParts ??
|
|
447
|
+
fileFingerprintParts(sessionPath) ??
|
|
448
|
+
(metadata ? { sizeBytes: 0, mtimeMs: metadata.mtime.getTime() } : null);
|
|
449
|
+
return {
|
|
450
|
+
sessionId,
|
|
451
|
+
sourceKey: sessionPath,
|
|
452
|
+
fingerprintHint: parts ? fingerprintString(parts) : undefined,
|
|
453
|
+
fingerprintParts: parts ?? undefined,
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
const discover = async () => {
|
|
457
|
+
if (provider.listSessionFilesAsync) {
|
|
458
|
+
try {
|
|
459
|
+
const files = await provider.listSessionFilesAsync(cwd);
|
|
460
|
+
return files.map((info) => {
|
|
461
|
+
const mtimeMs = info.mtime.getTime();
|
|
462
|
+
const suppliedParts = info.sizeBytes !== undefined && Number.isFinite(mtimeMs)
|
|
463
|
+
? { sizeBytes: info.sizeBytes, mtimeMs }
|
|
464
|
+
: undefined;
|
|
465
|
+
return referenceFromPath(info.path, info.sessionId ?? provider.getSessionId(info.path), suppliedParts);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Retain compatibility with third-party providers whose optional async
|
|
470
|
+
// enumerator is temporarily unavailable.
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return provider.findAllSessions(cwd).map((sessionPath) => {
|
|
474
|
+
const metadata = provider.getSessionMetadata?.(sessionPath);
|
|
475
|
+
const parts = fileFingerprintParts(sessionPath) ??
|
|
476
|
+
(metadata ? { sizeBytes: 0, mtimeMs: metadata.mtime.getTime() } : null);
|
|
477
|
+
return {
|
|
478
|
+
sessionId: provider.getSessionId(sessionPath),
|
|
479
|
+
sourceKey: sessionPath,
|
|
480
|
+
fingerprintHint: parts ? fingerprintString(parts) : undefined,
|
|
481
|
+
fingerprintParts: parts ?? undefined,
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
};
|
|
145
485
|
return {
|
|
146
486
|
providerId: provider.id,
|
|
147
487
|
capabilities: adapter.capabilities,
|
|
148
|
-
discover
|
|
149
|
-
|
|
150
|
-
sessionId: provider.getSessionId(sessionPath),
|
|
151
|
-
sourceKey: sessionPath,
|
|
152
|
-
fingerprintHint: provider.getSessionMetadata?.(sessionPath)?.mtime.getTime().toString() ??
|
|
153
|
-
fileFingerprint(sessionPath) ??
|
|
154
|
-
undefined,
|
|
155
|
-
}));
|
|
156
|
-
},
|
|
157
|
-
read(reference) {
|
|
488
|
+
discover,
|
|
489
|
+
async read(reference) {
|
|
158
490
|
if (!reference.sourceKey)
|
|
159
491
|
throw new Error('session source unavailable');
|
|
160
492
|
return adapter.read(reference.sourceKey, cwd);
|
|
161
493
|
},
|
|
494
|
+
refreshCached(reference, cached, observedAt) {
|
|
495
|
+
const mtimeMs = reference.fingerprintParts?.mtimeMs ?? Date.parse(cached.observedAt);
|
|
496
|
+
const activity = (0, sessionActivityDetector_1.refreshSessionActivityState)(cached.activity.value, mtimeMs);
|
|
497
|
+
return {
|
|
498
|
+
...cached,
|
|
499
|
+
activity: { ...cached.activity, value: activity },
|
|
500
|
+
observedAt,
|
|
501
|
+
};
|
|
502
|
+
},
|
|
503
|
+
subscribe(listener, subscribeOptions = {}) {
|
|
504
|
+
return subscribeToProviderRoot(provider, listener, subscribeOptions.pollIntervalMs ?? 0);
|
|
505
|
+
},
|
|
162
506
|
dispose: () => adapter.dispose(),
|
|
163
507
|
};
|
|
164
508
|
}
|
|
165
|
-
/**
|
|
166
|
-
function
|
|
509
|
+
/** Structured size/mtime fingerprint; returns null for inaccessible or synthetic paths. */
|
|
510
|
+
function fileFingerprintParts(sourcePath) {
|
|
167
511
|
try {
|
|
168
512
|
const stat = (0, node_fs_1.statSync)(sourcePath);
|
|
169
|
-
return
|
|
513
|
+
return { sizeBytes: stat.size, mtimeMs: stat.mtimeMs };
|
|
170
514
|
}
|
|
171
515
|
catch {
|
|
172
516
|
return null;
|
|
173
517
|
}
|
|
174
518
|
}
|
|
519
|
+
/** Size/mtime fingerprint suitable for retry bypass; returns null for synthetic DB locators. */
|
|
520
|
+
function fileFingerprint(sourcePath) {
|
|
521
|
+
const parts = fileFingerprintParts(sourcePath);
|
|
522
|
+
return parts ? fingerprintString(parts) : null;
|
|
523
|
+
}
|
|
524
|
+
function fingerprintString(parts) {
|
|
525
|
+
return `${parts.sizeBytes}:${parts.mtimeMs}`;
|
|
526
|
+
}
|
|
527
|
+
function parseFingerprintParts(value) {
|
|
528
|
+
if (!value)
|
|
529
|
+
return null;
|
|
530
|
+
const match = value.match(/^([0-9]+):([0-9]+(?:\.[0-9]+)?)$/);
|
|
531
|
+
if (!match)
|
|
532
|
+
return null;
|
|
533
|
+
const sizeBytes = Number(match[1]);
|
|
534
|
+
const mtimeMs = Number(match[2]);
|
|
535
|
+
return Number.isFinite(sizeBytes) && Number.isFinite(mtimeMs) ? { sizeBytes, mtimeMs } : null;
|
|
536
|
+
}
|
|
537
|
+
function contentTimestamp(value) {
|
|
538
|
+
if (!value || typeof value !== 'object')
|
|
539
|
+
return null;
|
|
540
|
+
const candidate = value.contentObservedAt;
|
|
541
|
+
return typeof candidate === 'string' ? candidate : null;
|
|
542
|
+
}
|
|
543
|
+
function subscribeToProviderRoot(provider, listener, pollIntervalMs) {
|
|
544
|
+
const watchers = new Set();
|
|
545
|
+
let pollTimer = null;
|
|
546
|
+
let roots;
|
|
547
|
+
try {
|
|
548
|
+
roots = provider.getWatchRoots?.() ?? [provider.getProjectsBaseDir()];
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
roots = [];
|
|
552
|
+
}
|
|
553
|
+
for (const root of new Set(roots)) {
|
|
554
|
+
try {
|
|
555
|
+
const watcher = fs.watch(root, { persistent: false, recursive: true }, listener);
|
|
556
|
+
watchers.add(watcher);
|
|
557
|
+
watcher.on('error', () => {
|
|
558
|
+
watchers.delete(watcher);
|
|
559
|
+
try {
|
|
560
|
+
watcher.close();
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
// Catch-up polling remains active.
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
// Polling remains the documented fallback.
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (pollIntervalMs > 0) {
|
|
572
|
+
pollTimer = setInterval(listener, pollIntervalMs);
|
|
573
|
+
pollTimer.unref?.();
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
dispose: () => {
|
|
577
|
+
for (const watcher of watchers)
|
|
578
|
+
watcher.close();
|
|
579
|
+
watchers.clear();
|
|
580
|
+
if (pollTimer)
|
|
581
|
+
clearInterval(pollTimer);
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
}
|
|
175
585
|
function failureSignature(error) {
|
|
176
586
|
if (error instanceof Error)
|
|
177
587
|
return `${error.name}\0${error.message}`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reader for Codex's append-only `~/.codex/history.jsonl` (Node-only).
|
|
3
|
+
*
|
|
4
|
+
* Each line is `{"session_id","ts","text"}` — one entry per user prompt. The
|
|
5
|
+
* file grows without bound, so this reader takes a bounded tail instead of the
|
|
6
|
+
* whole file, drops the leading partial line, and parses tolerantly. Pair an
|
|
7
|
+
* entry with {@link findCodexRolloutFile} (providers/codex) to jump from a
|
|
8
|
+
* history row to its full rollout transcript.
|
|
9
|
+
*
|
|
10
|
+
* @module codexHistory
|
|
11
|
+
*/
|
|
12
|
+
export interface CodexHistoryEntry {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
/** Timestamp exactly as Codex recorded it (epoch seconds). */
|
|
15
|
+
ts: number;
|
|
16
|
+
/** Normalized epoch milliseconds, ready for Date construction. */
|
|
17
|
+
tsMs: number;
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ReadCodexHistoryOptions {
|
|
21
|
+
/** Codex home containing `history.jsonl` (default: `$CODEX_HOME` or `~/.codex`). */
|
|
22
|
+
codexHome?: string;
|
|
23
|
+
/** Maximum entries returned, newest first (default 100). */
|
|
24
|
+
limit?: number;
|
|
25
|
+
/** Bytes read from the end. Defaults to max(512 KiB, limit × 4096). */
|
|
26
|
+
maxTailBytes?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the newest history entries, newest first. A missing or unreadable
|
|
30
|
+
* file yields `[]`; malformed lines are skipped. Never throws.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readCodexHistory(options?: ReadCodexHistoryOptions): CodexHistoryEntry[];
|