@brunobracaioli/aibrain 0.2.0-beta.1 → 0.3.0-beta.13
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/README.md +67 -4
- package/dist/assets/claude/.claude-plugin/plugin.json +49 -1
- package/dist/assets/claude/hooks/context-compaction.ts +6062 -734
- package/dist/assets/claude/hooks/provider-window.js +263 -0
- package/dist/assets/claude/hooks/state-fitting.d.mts +47 -0
- package/dist/assets/claude/hooks/state-fitting.mjs +324 -0
- package/dist/assets/codex-context-compaction.mjs +2690 -266
- package/dist/assets/context-selection-fitting.mjs +194 -0
- package/dist/assets/context-state-fitting.mjs +324 -0
- package/dist/assets/context-telemetry-reporter.mjs +305 -0
- package/dist/assets/jev-paid-pilot-terminal.mjs +591 -125
- package/dist/claude-checkpoint.mjs +1338 -0
- package/dist/claude-observation.mjs +1336 -0
- package/dist/cli.mjs +250 -20
- package/dist/codex-compatibility.mjs +467 -0
- package/dist/codex-diagnostics.mjs +426 -0
- package/dist/codex-permissions.mjs +590 -0
- package/dist/codex-protocol-fingerprint.json +131 -0
- package/dist/context-telemetry.mjs +257 -0
- package/dist/launch.mjs +341 -22
- package/dist/mcp.mjs +329 -7
- package/dist/monitor.mjs +11 -0
- package/dist/package.json +1 -1
- package/dist/platform.mjs +33 -3
- package/dist/state.mjs +107 -4
- package/dist/telemetry-report.mjs +133 -0
- package/package.json +1 -1
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { chmod, lstat, mkdir, open, readFile, readdir, rename, rm, stat } from 'node:fs/promises';
|
|
5
|
+
import { constants } from 'node:fs';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
|
|
8
|
+
export const CODEX_DIAGNOSTIC_SCHEMA_VERSION = 1;
|
|
9
|
+
export const CODEX_DIAGNOSTIC_ADAPTER_VERSION = '0.3.0-beta.13';
|
|
10
|
+
export const CODEX_DIAGNOSTIC_MAX_LOG_BYTES = 512 * 1024;
|
|
11
|
+
export const CODEX_DIAGNOSTIC_MAX_LOG_FILES = 3;
|
|
12
|
+
export const CODEX_DIAGNOSTIC_MAX_RECORDS = 128;
|
|
13
|
+
const MAX_DIAGNOSTIC_BYTES = 256 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
const LOCK_WAIT_MS = 250;
|
|
16
|
+
const LOCK_STALE_MS = 30_000;
|
|
17
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
18
|
+
const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/u;
|
|
19
|
+
const STAGES = new Set([
|
|
20
|
+
'capture',
|
|
21
|
+
'budget',
|
|
22
|
+
'selection',
|
|
23
|
+
'notes',
|
|
24
|
+
'native',
|
|
25
|
+
'recovery',
|
|
26
|
+
'transport',
|
|
27
|
+
'unknown',
|
|
28
|
+
]);
|
|
29
|
+
const PHASES = new Set([
|
|
30
|
+
'capture',
|
|
31
|
+
'budget',
|
|
32
|
+
'selection',
|
|
33
|
+
'selection_accepted',
|
|
34
|
+
'candidate_window_bounded',
|
|
35
|
+
'notes',
|
|
36
|
+
'notes_written',
|
|
37
|
+
'native',
|
|
38
|
+
'native_started',
|
|
39
|
+
'native_item_completed',
|
|
40
|
+
'native_completed',
|
|
41
|
+
'recovery',
|
|
42
|
+
'recovery_attached',
|
|
43
|
+
'recovery_uncertain',
|
|
44
|
+
'transport',
|
|
45
|
+
'operation_failed',
|
|
46
|
+
'thread_failure',
|
|
47
|
+
'transport_end',
|
|
48
|
+
'process_error',
|
|
49
|
+
'transport_close',
|
|
50
|
+
]);
|
|
51
|
+
const REASONS = new Set([
|
|
52
|
+
'requested',
|
|
53
|
+
'completed',
|
|
54
|
+
'candidate_window_bounded',
|
|
55
|
+
'candidate_over_bound',
|
|
56
|
+
'protected_over_bound',
|
|
57
|
+
'history_unavailable',
|
|
58
|
+
'stale_snapshot',
|
|
59
|
+
'unavailable',
|
|
60
|
+
'admission_denied',
|
|
61
|
+
'invalid_response',
|
|
62
|
+
'request_failed',
|
|
63
|
+
'source_changed',
|
|
64
|
+
'timeout',
|
|
65
|
+
'thread_closed',
|
|
66
|
+
'protocol_error',
|
|
67
|
+
'capture_size_exceeded',
|
|
68
|
+
'capture_item_count_exceeded',
|
|
69
|
+
'private_storage_failed',
|
|
70
|
+
'storage_capacity_exceeded',
|
|
71
|
+
'attached',
|
|
72
|
+
'uncertain',
|
|
73
|
+
'disconnected',
|
|
74
|
+
]);
|
|
75
|
+
/* Kept for records produced before the Codex stage contract was closed. */
|
|
76
|
+
const LEGACY_STAGES = new Set([
|
|
77
|
+
'connected',
|
|
78
|
+
'thread_read',
|
|
79
|
+
'selection',
|
|
80
|
+
'selection_accepted',
|
|
81
|
+
'notes_persistence',
|
|
82
|
+
'notes_written',
|
|
83
|
+
'native_compaction',
|
|
84
|
+
'native_completed',
|
|
85
|
+
'recovery_attachment',
|
|
86
|
+
'recovery_attached',
|
|
87
|
+
'recovery_uncertain',
|
|
88
|
+
'thread_failure',
|
|
89
|
+
'transport_end',
|
|
90
|
+
'process_error',
|
|
91
|
+
'transport_close',
|
|
92
|
+
'unknown',
|
|
93
|
+
]);
|
|
94
|
+
const ERROR_CODES = new Set([
|
|
95
|
+
'rpc_error',
|
|
96
|
+
'request_failed',
|
|
97
|
+
'request_timeout',
|
|
98
|
+
'admission_denied',
|
|
99
|
+
'shared_admission_denied',
|
|
100
|
+
'account_request_limit',
|
|
101
|
+
'account_token_limit',
|
|
102
|
+
'global_request_limit',
|
|
103
|
+
'global_token_limit',
|
|
104
|
+
'account_in_flight',
|
|
105
|
+
'database_unavailable',
|
|
106
|
+
'unknown',
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
/** @param {unknown} value @returns {number|null} */
|
|
110
|
+
function safeRpcCode(value) {
|
|
111
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value)) return null;
|
|
112
|
+
return value >= -32_768 && value <= 32_767 ? value : null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @param {unknown} value @returns {value is Record<string, unknown>} */
|
|
116
|
+
function record(value) {
|
|
117
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {string} value */
|
|
121
|
+
function pseudonym(value) {
|
|
122
|
+
return createHash('sha256').update(JSON.stringify({ value }), 'utf8').digest('hex').slice(0, 32);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @param {string} value */
|
|
126
|
+
function safeVersion(value) {
|
|
127
|
+
return value.length <= 64 && VERSION.test(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @param {string} target @param {boolean} allowMissing */
|
|
131
|
+
async function assertNoSymlinkAncestors(target, allowMissing) {
|
|
132
|
+
const absolute = resolve(target);
|
|
133
|
+
let current = absolute.startsWith('/') ? '/' : '';
|
|
134
|
+
for (const part of absolute.split('/').filter(Boolean)) {
|
|
135
|
+
current = current === '/' ? `/${part}` : join(current, part);
|
|
136
|
+
try {
|
|
137
|
+
const entry = await lstat(current);
|
|
138
|
+
if (entry.isSymbolicLink()) throw new Error('codex_diagnostics_symlink_rejected');
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
141
|
+
if (!allowMissing) throw new Error('codex_diagnostics_path_missing');
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @param {string} path @param {boolean} [create] */
|
|
150
|
+
async function ownedDirectory(path, create = true) {
|
|
151
|
+
const target = resolve(path);
|
|
152
|
+
await assertNoSymlinkAncestors(target, create);
|
|
153
|
+
if (create) await mkdir(target, { recursive: true, mode: 0o700 });
|
|
154
|
+
await assertNoSymlinkAncestors(target, false);
|
|
155
|
+
const entry = await lstat(target);
|
|
156
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
157
|
+
throw new Error('codex_diagnostics_path_invalid');
|
|
158
|
+
if (typeof process.getuid === 'function' && entry.uid !== process.getuid())
|
|
159
|
+
throw new Error('codex_diagnostics_owner_mismatch');
|
|
160
|
+
await chmod(target, 0o700);
|
|
161
|
+
return target;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @param {string} path */
|
|
165
|
+
async function ownedFile(path) {
|
|
166
|
+
const entry = await lstat(path);
|
|
167
|
+
if (!entry.isFile() || entry.isSymbolicLink()) throw new Error('codex_diagnostics_file_invalid');
|
|
168
|
+
if (typeof process.getuid === 'function' && entry.uid !== process.getuid())
|
|
169
|
+
throw new Error('codex_diagnostics_owner_mismatch');
|
|
170
|
+
if ((entry.mode & 0o077) !== 0) throw new Error('codex_diagnostics_file_permissions_invalid');
|
|
171
|
+
if (entry.size > CODEX_DIAGNOSTIC_MAX_LOG_BYTES)
|
|
172
|
+
throw new Error('codex_diagnostics_file_size_invalid');
|
|
173
|
+
return entry;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @param {string} path */
|
|
177
|
+
/** @param {string} path @param {() => Promise<unknown>} task */
|
|
178
|
+
async function withLock(path, task) {
|
|
179
|
+
const started = Date.now();
|
|
180
|
+
while (true) {
|
|
181
|
+
try {
|
|
182
|
+
const handle = await open(
|
|
183
|
+
path,
|
|
184
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
|
|
185
|
+
0o600,
|
|
186
|
+
);
|
|
187
|
+
try {
|
|
188
|
+
return await task();
|
|
189
|
+
} finally {
|
|
190
|
+
await handle.close();
|
|
191
|
+
await rm(path, { force: true });
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'EEXIST') throw error;
|
|
195
|
+
try {
|
|
196
|
+
const lock = await stat(path);
|
|
197
|
+
if (Date.now() - lock.mtimeMs > LOCK_STALE_MS) await rm(path, { force: true });
|
|
198
|
+
} catch {
|
|
199
|
+
// A concurrent owner can remove the lock between stat and retry.
|
|
200
|
+
}
|
|
201
|
+
if (Date.now() - started >= LOCK_WAIT_MS) throw new Error('codex_diagnostics_lock_timeout');
|
|
202
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** @param {string} directory @param {Record<string, unknown>[]} records */
|
|
208
|
+
async function appendRecords(directory, records) {
|
|
209
|
+
const lock = join(directory, '.events.lock');
|
|
210
|
+
return withLock(lock, async () => {
|
|
211
|
+
const active = join(directory, 'events.0.jsonl');
|
|
212
|
+
let size = 0;
|
|
213
|
+
try {
|
|
214
|
+
size = (await ownedFile(active)).size;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error;
|
|
217
|
+
}
|
|
218
|
+
const payload = records.map((item) => `${JSON.stringify(item)}\n`).join('');
|
|
219
|
+
if (Buffer.byteLength(payload, 'utf8') > CODEX_DIAGNOSTIC_MAX_LOG_BYTES)
|
|
220
|
+
throw new Error('codex_diagnostics_record_too_large');
|
|
221
|
+
if (size + Buffer.byteLength(payload, 'utf8') > CODEX_DIAGNOSTIC_MAX_LOG_BYTES) {
|
|
222
|
+
for (let index = CODEX_DIAGNOSTIC_MAX_LOG_FILES - 1; index >= 1; index -= 1) {
|
|
223
|
+
const from = join(directory, `events.${index - 1}.jsonl`);
|
|
224
|
+
const to = join(directory, `events.${index}.jsonl`);
|
|
225
|
+
try {
|
|
226
|
+
await ownedFile(from);
|
|
227
|
+
await rename(from, to);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT')
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
size = 0;
|
|
234
|
+
}
|
|
235
|
+
const handle = await open(
|
|
236
|
+
active,
|
|
237
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW,
|
|
238
|
+
0o600,
|
|
239
|
+
);
|
|
240
|
+
try {
|
|
241
|
+
await handle.write(payload, undefined, 'utf8');
|
|
242
|
+
} finally {
|
|
243
|
+
await handle.close();
|
|
244
|
+
}
|
|
245
|
+
await chmod(active, 0o600);
|
|
246
|
+
return records.length;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** @param {unknown} event @param {string} clientVersion @param {string} sessionKey @param {string} instanceKey @param {() => number} now */
|
|
251
|
+
function normalize(event, clientVersion, sessionKey, instanceKey, now) {
|
|
252
|
+
const input = record(event) ? event : {};
|
|
253
|
+
/** @type {Record<string, string>} */
|
|
254
|
+
const phaseStage = {
|
|
255
|
+
capture: 'capture',
|
|
256
|
+
budget: 'budget',
|
|
257
|
+
selection: 'selection',
|
|
258
|
+
selection_accepted: 'selection',
|
|
259
|
+
candidate_window_bounded: 'selection',
|
|
260
|
+
notes: 'notes',
|
|
261
|
+
notes_written: 'notes',
|
|
262
|
+
native: 'native',
|
|
263
|
+
native_started: 'native',
|
|
264
|
+
native_item_completed: 'native',
|
|
265
|
+
native_completed: 'native',
|
|
266
|
+
recovery: 'recovery',
|
|
267
|
+
recovery_attached: 'recovery',
|
|
268
|
+
recovery_uncertain: 'recovery',
|
|
269
|
+
transport: 'transport',
|
|
270
|
+
operation_failed: 'native',
|
|
271
|
+
thread_failure: 'native',
|
|
272
|
+
transport_end: 'transport',
|
|
273
|
+
process_error: 'transport',
|
|
274
|
+
transport_close: 'transport',
|
|
275
|
+
};
|
|
276
|
+
const rawStage =
|
|
277
|
+
typeof input.stage === 'string'
|
|
278
|
+
? input.stage
|
|
279
|
+
: typeof input.phase === 'string'
|
|
280
|
+
? phaseStage[input.phase]
|
|
281
|
+
: undefined;
|
|
282
|
+
const stage =
|
|
283
|
+
rawStage !== undefined && STAGES.has(rawStage)
|
|
284
|
+
? rawStage
|
|
285
|
+
: rawStage !== undefined && LEGACY_STAGES.has(rawStage)
|
|
286
|
+
? 'native'
|
|
287
|
+
: 'unknown';
|
|
288
|
+
const rawPhase = typeof input.phase === 'string' ? input.phase : undefined;
|
|
289
|
+
const phase = rawPhase !== undefined && PHASES.has(rawPhase) ? rawPhase : undefined;
|
|
290
|
+
const rawReason =
|
|
291
|
+
typeof input.reason === 'string'
|
|
292
|
+
? input.reason
|
|
293
|
+
: typeof input.reasonCode === 'string'
|
|
294
|
+
? input.reasonCode
|
|
295
|
+
: undefined;
|
|
296
|
+
const reason = rawReason !== undefined && REASONS.has(rawReason) ? rawReason : undefined;
|
|
297
|
+
const rawError = typeof input.errorCode === 'string' ? input.errorCode : undefined;
|
|
298
|
+
const errorCode =
|
|
299
|
+
rawError !== undefined && ERROR_CODES.has(rawError)
|
|
300
|
+
? rawError
|
|
301
|
+
: rawError === undefined
|
|
302
|
+
? undefined
|
|
303
|
+
: 'unknown';
|
|
304
|
+
const observedAt = new Date(now()).toISOString();
|
|
305
|
+
/** @type {Record<string, unknown>} */
|
|
306
|
+
const output = {
|
|
307
|
+
schema_version: CODEX_DIAGNOSTIC_SCHEMA_VERSION,
|
|
308
|
+
client: 'codex-cli',
|
|
309
|
+
adapter_version: CODEX_DIAGNOSTIC_ADAPTER_VERSION,
|
|
310
|
+
session_key: sessionKey,
|
|
311
|
+
instance_id: instanceKey,
|
|
312
|
+
stage,
|
|
313
|
+
observed_at: observedAt,
|
|
314
|
+
};
|
|
315
|
+
if (phase !== undefined) output.phase = phase;
|
|
316
|
+
if (reason !== undefined) output.reason = reason;
|
|
317
|
+
if (errorCode !== undefined) output.error_code = errorCode;
|
|
318
|
+
const rpcCode = safeRpcCode(input.rpcCode ?? input.rpc_code);
|
|
319
|
+
if (rpcCode !== null) output.rpc_code = rpcCode;
|
|
320
|
+
if (safeVersion(clientVersion)) output.client_version = clientVersion;
|
|
321
|
+
const operationId =
|
|
322
|
+
typeof input.operation_id === 'string' ? input.operation_id : input.operationId;
|
|
323
|
+
if (typeof operationId === 'string' && UUID.test(operationId)) output.operation_id = operationId;
|
|
324
|
+
const correlationId = input.correlation_id;
|
|
325
|
+
if (typeof correlationId === 'string' && correlationId.length > 0 && correlationId.length <= 128)
|
|
326
|
+
output.correlation_id = correlationId;
|
|
327
|
+
/** @type {[string, string, number][]} */
|
|
328
|
+
const numericFields = [
|
|
329
|
+
['batchCount', 'batch_count', 4],
|
|
330
|
+
['batch_count', 'batch_count', 4],
|
|
331
|
+
['selectedCount', 'selected_count', 1_000_000],
|
|
332
|
+
['deferredCount', 'deferred_count', 1_000_000],
|
|
333
|
+
['pageCount', 'page_count', 32],
|
|
334
|
+
['captureBytes', 'capture_bytes', MAX_DIAGNOSTIC_BYTES],
|
|
335
|
+
['durationMs', 'duration_ms', 86_400_000],
|
|
336
|
+
];
|
|
337
|
+
for (const [source, target, max] of numericFields) {
|
|
338
|
+
const value = input[source];
|
|
339
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= max)
|
|
340
|
+
output[target] = value;
|
|
341
|
+
}
|
|
342
|
+
if (record(input.counts)) {
|
|
343
|
+
/** @type {Record<string, number>} */
|
|
344
|
+
const counts = {};
|
|
345
|
+
for (const [key, value] of Object.entries(input.counts)) {
|
|
346
|
+
if (
|
|
347
|
+
/^[a-z_]{1,48}$/u.test(key) &&
|
|
348
|
+
typeof value === 'number' &&
|
|
349
|
+
Number.isSafeInteger(value) &&
|
|
350
|
+
value >= 0 &&
|
|
351
|
+
value <= (/_bytes$/u.test(key) ? MAX_DIAGNOSTIC_BYTES : 1_000_000)
|
|
352
|
+
)
|
|
353
|
+
counts[key] = value;
|
|
354
|
+
}
|
|
355
|
+
if (Object.keys(counts).length > 0) output.counts = counts;
|
|
356
|
+
}
|
|
357
|
+
return output;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Create the private, content-free sink used by the Codex adapter. The returned
|
|
362
|
+
* callback is deliberately fire-and-forget: logger failures are swallowed and
|
|
363
|
+
* never write to the app-server stdout protocol or affect compaction.
|
|
364
|
+
* @param {{stateDir: string, sessionId: string, instanceId?: string, clientVersion: string, enabled?: boolean, now?: () => number}} options
|
|
365
|
+
*/
|
|
366
|
+
export function createCodexDiagnosticSink(options) {
|
|
367
|
+
const directoryPromise =
|
|
368
|
+
options.enabled === false
|
|
369
|
+
? null
|
|
370
|
+
: ownedDirectory(join(resolve(options.stateDir), 'diagnostics', 'codex'));
|
|
371
|
+
const sessionKey = pseudonym(options.sessionId);
|
|
372
|
+
const instanceKey = pseudonym(options.instanceId ?? `${process.pid}`);
|
|
373
|
+
const now = options.now ?? Date.now;
|
|
374
|
+
let queue = Promise.resolve();
|
|
375
|
+
/** @param {unknown} event */
|
|
376
|
+
const sink = (event) => {
|
|
377
|
+
if (directoryPromise === null) return;
|
|
378
|
+
const normalized = normalize(event, options.clientVersion, sessionKey, instanceKey, now);
|
|
379
|
+
queue = queue
|
|
380
|
+
.then(async () => {
|
|
381
|
+
const directory = await directoryPromise;
|
|
382
|
+
await appendRecords(directory, [normalized]);
|
|
383
|
+
})
|
|
384
|
+
.catch(() => undefined);
|
|
385
|
+
};
|
|
386
|
+
sink.flush = () => queue;
|
|
387
|
+
return sink;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** @param {string} stateDir @param {number} [tail] */
|
|
391
|
+
export async function readCodexDiagnostics(stateDir, tail = 20) {
|
|
392
|
+
const root = resolve(stateDir);
|
|
393
|
+
const directory = await ownedDirectory(join(root, 'diagnostics', 'codex'), false).catch(
|
|
394
|
+
(error) => {
|
|
395
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return null;
|
|
396
|
+
if (error instanceof Error && error.message === 'codex_diagnostics_path_missing') return null;
|
|
397
|
+
throw error;
|
|
398
|
+
},
|
|
399
|
+
);
|
|
400
|
+
if (directory === null) return [];
|
|
401
|
+
const files = (await readdir(directory))
|
|
402
|
+
.filter((file) => /^events\.[0-2]\.jsonl$/u.test(file))
|
|
403
|
+
.sort((a, b) => Number(b[7]) - Number(a[7]));
|
|
404
|
+
/** @type {Record<string, unknown>[]} */
|
|
405
|
+
const records = [];
|
|
406
|
+
for (const file of files) {
|
|
407
|
+
const path = join(directory, file);
|
|
408
|
+
await ownedFile(path);
|
|
409
|
+
for (const line of (await readFile(path, 'utf8')).split('\n').filter(Boolean)) {
|
|
410
|
+
try {
|
|
411
|
+
const value = JSON.parse(line);
|
|
412
|
+
if (
|
|
413
|
+
record(value) &&
|
|
414
|
+
value.schema_version === CODEX_DIAGNOSTIC_SCHEMA_VERSION &&
|
|
415
|
+
value.client === 'codex-cli'
|
|
416
|
+
)
|
|
417
|
+
records.push(value);
|
|
418
|
+
} catch {
|
|
419
|
+
// Ignore malformed private lines without exposing their contents.
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return records.slice(-Math.max(1, Math.min(CODEX_DIAGNOSTIC_MAX_RECORDS, tail)));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export const createPrivateCodexDiagnosticSink = createCodexDiagnosticSink;
|