@letta-ai/dreams 0.0.1 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,510 @@
1
+ "use strict";
2
+ /**
3
+ * Codex source adapter — discovery and harness detection ([LET-10314] / [LET-10317]).
4
+ *
5
+ * On-disk layout (fail closed when unrecognized):
6
+ * ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<sessionId>.jsonl
7
+ *
8
+ * Identity chain (upstream openai/codex@fb6aad9ae341; ThreadId is UUIDv7 Display string):
9
+ * - Filename suffix = `conversation_id` / ThreadId
10
+ * (`codex-rs/rollout/src/recorder.rs` `precompute_log_file_info`)
11
+ * - First JSONL item is RolloutLine{type:session_meta,payload:SessionMetaLine}
12
+ * (`codex-rs/protocol/src/protocol.rs` RolloutItem + RolloutLine)
13
+ * - `payload.id` and `payload.session_id` are the same ThreadId uuid
14
+ * (`SessionMeta { session_id: id.into(), id: thread_id, ... }`)
15
+ * - Hook stdin `session_id` is `ThreadId.to_string()` from `sess.session_id()`
16
+ * (`codex-rs/hooks/src/events/session_start.rs`,
17
+ * `codex-rs/core/src/hook_runtime.rs`,
18
+ * `codex-rs/hooks/schema/generated/session-start.command.input.schema.json`)
19
+ * Pin: https://github.com/openai/codex/tree/fb6aad9ae34116128e537696c24e35fe6548e1c2
20
+ *
21
+ * Subagent rollouts are skipped. Classification shares one path with discovery
22
+ * so status diagnostics and ingest agree. Paths stay private to this module.
23
+ * Registration lives in `source-adapters.ts` (not the default).
24
+ */
25
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ var desc = Object.getOwnPropertyDescriptor(m, k);
28
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
29
+ desc = { enumerable: true, get: function() { return m[k]; } };
30
+ }
31
+ Object.defineProperty(o, k2, desc);
32
+ }) : (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ o[k2] = m[k];
35
+ }));
36
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
37
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
38
+ }) : function(o, v) {
39
+ o["default"] = v;
40
+ });
41
+ var __importStar = (this && this.__importStar) || (function () {
42
+ var ownKeys = function(o) {
43
+ ownKeys = Object.getOwnPropertyNames || function (o) {
44
+ var ar = [];
45
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
46
+ return ar;
47
+ };
48
+ return ownKeys(o);
49
+ };
50
+ return function (mod) {
51
+ if (mod && mod.__esModule) return mod;
52
+ var result = {};
53
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
54
+ __setModuleDefault(result, mod);
55
+ return result;
56
+ };
57
+ })();
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.codexSourceAdapter = exports.CODEX_DISPLAY_NAME = exports.CODEX_SOURCE_ID = exports.CODEX_ADAPTER_VERSION = exports.CODEX_SOURCE_TYPE = void 0;
60
+ exports.codexSessionsDir = codexSessionsDir;
61
+ exports.validateCodexSessionId = validateCodexSessionId;
62
+ exports.detectCodexHarness = detectCodexHarness;
63
+ exports.classifyCodexRollout = classifyCodexRollout;
64
+ exports.inspectCodexDiscovery = inspectCodexDiscovery;
65
+ exports.discoverSessions = discoverSessions;
66
+ const fs = __importStar(require("node:fs"));
67
+ const os = __importStar(require("node:os"));
68
+ const path = __importStar(require("node:path"));
69
+ const config_1 = require("./config");
70
+ const transcript_bytes_1 = require("./transcript-bytes");
71
+ exports.CODEX_SOURCE_TYPE = "codex";
72
+ exports.CODEX_ADAPTER_VERSION = "codex-1";
73
+ exports.CODEX_SOURCE_ID = `src_${exports.CODEX_SOURCE_TYPE}`;
74
+ exports.CODEX_DISPLAY_NAME = "Codex";
75
+ /** Opaque safe identifiers (not UUID-bound; not permanently tied to thr_). */
76
+ const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
77
+ const HEAD_READ_BYTES = 256 * 1024;
78
+ /** Timestamp uses hyphens instead of colons: YYYY-MM-DDTHH-MM-SS. */
79
+ const ROLLOUT_RE = /^rollout-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-([A-Za-z0-9][A-Za-z0-9_-]{0,127})\.jsonl$/;
80
+ /** Broader candidate gate for diagnostics — drifted names still count as candidates. */
81
+ const ROLLOUT_CANDIDATE_RE = /^rollout-.*\.jsonl$/i;
82
+ const MAX_EXAMPLES = 5;
83
+ function codexSessionsDir() {
84
+ return path.join(os.homedir(), ".codex", "sessions");
85
+ }
86
+ function validateCodexSessionId(value) {
87
+ return SESSION_ID_RE.test(value);
88
+ }
89
+ /**
90
+ * Deterministic harness-owned filesystem facts only. Never opens transcript
91
+ * files or walks session contents.
92
+ */
93
+ function detectCodexHarness(home = os.homedir()) {
94
+ const codexDir = path.join(home, ".codex");
95
+ const evidence = [];
96
+ try {
97
+ if (!fs.statSync(codexDir).isDirectory()) {
98
+ return { present: false, evidence };
99
+ }
100
+ }
101
+ catch {
102
+ return { present: false, evidence };
103
+ }
104
+ evidence.push(codexDir);
105
+ const sessionsDir = path.join(codexDir, "sessions");
106
+ try {
107
+ if (fs.statSync(sessionsDir).isDirectory())
108
+ evidence.push(sessionsDir);
109
+ }
110
+ catch {
111
+ // optional
112
+ }
113
+ for (const name of ["config.toml", "hooks.json"]) {
114
+ const candidate = path.join(codexDir, name);
115
+ try {
116
+ if (fs.statSync(candidate).isFile())
117
+ evidence.push(candidate);
118
+ }
119
+ catch {
120
+ // ignore
121
+ }
122
+ }
123
+ return { present: true, evidence };
124
+ }
125
+ function readFirstRecordDetailed(filePath) {
126
+ let fd;
127
+ try {
128
+ fd = fs.openSync(filePath, "r");
129
+ const buf = Buffer.alloc(HEAD_READ_BYTES);
130
+ const read = fs.readSync(fd, buf, 0, HEAD_READ_BYTES, 0);
131
+ if (read === 0)
132
+ return { ok: false, reason: "empty" };
133
+ const head = buf.subarray(0, read);
134
+ const nl = head.indexOf(0x0a);
135
+ const lineBytes = nl === -1 ? head : head.subarray(0, nl);
136
+ const line = lineBytes.toString("utf8").trim();
137
+ if (line === "")
138
+ return { ok: false, reason: "empty" };
139
+ try {
140
+ const parsed = JSON.parse(line);
141
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
142
+ return { ok: false, reason: "unsupported_metadata" };
143
+ }
144
+ return { ok: true, record: parsed };
145
+ }
146
+ catch {
147
+ return { ok: false, reason: "malformed_json" };
148
+ }
149
+ }
150
+ catch {
151
+ return { ok: false, reason: "unreadable" };
152
+ }
153
+ finally {
154
+ if (fd !== undefined) {
155
+ try {
156
+ fs.closeSync(fd);
157
+ }
158
+ catch {
159
+ // ignore
160
+ }
161
+ }
162
+ }
163
+ }
164
+ function isSubagentMeta(payload) {
165
+ if (typeof payload.parent_thread_id === "string" && payload.parent_thread_id.length > 0)
166
+ return true;
167
+ const source = payload.source;
168
+ if (source && typeof source === "object" && !Array.isArray(source) && "subagent" in source)
169
+ return true;
170
+ return false;
171
+ }
172
+ function underRoot(rootReal, candidateReal) {
173
+ const prefix = rootReal.endsWith(path.sep) ? rootReal : rootReal + path.sep;
174
+ return candidateReal === rootReal || candidateReal.startsWith(prefix);
175
+ }
176
+ function statSignals(filePath) {
177
+ const stat = fs.statSync(filePath);
178
+ const inode = stat.ino ? String(stat.ino) : null;
179
+ const birthtime = Number.isFinite(stat.birthtimeMs) && stat.birthtimeMs > 0 ? Math.floor(stat.birthtimeMs) : null;
180
+ const mtime = Number.isFinite(stat.mtimeMs) ? Math.floor(stat.mtimeMs) : null;
181
+ return { size: stat.size, inode, birthtime_ms: birthtime, mtime_ms: mtime };
182
+ }
183
+ function homeRelative(filePath, home = os.homedir()) {
184
+ const homeReal = (() => {
185
+ try {
186
+ return (0, config_1.canonicalize)(home);
187
+ }
188
+ catch {
189
+ return path.resolve(home);
190
+ }
191
+ })();
192
+ const fileReal = (() => {
193
+ try {
194
+ return (0, config_1.canonicalize)(filePath);
195
+ }
196
+ catch {
197
+ return path.resolve(filePath);
198
+ }
199
+ })();
200
+ if (fileReal === homeReal || fileReal.startsWith(homeReal + path.sep)) {
201
+ return path.join("~", path.relative(homeReal, fileReal));
202
+ }
203
+ return path.basename(filePath);
204
+ }
205
+ /**
206
+ * Classify one rollout candidate. Shared by discovery ingest and status
207
+ * diagnostics — a "supported" result is exactly what `discoverSessions` emits.
208
+ */
209
+ function classifyCodexRollout(filePath, filenameId, options) {
210
+ const basename = path.basename(filePath);
211
+ const relative_path = homeRelative(filePath, options.home);
212
+ if (!validateCodexSessionId(filenameId)) {
213
+ return { kind: "unsupported", reason: "invalid_session_id", basename, relative_path };
214
+ }
215
+ let realPath;
216
+ try {
217
+ realPath = (0, config_1.canonicalize)(filePath);
218
+ }
219
+ catch {
220
+ return { kind: "unsupported", reason: "unreadable", basename, relative_path };
221
+ }
222
+ if (!underRoot(options.sessionsRootReal, realPath)) {
223
+ return { kind: "excluded", reason: "escaped_path", basename, relative_path };
224
+ }
225
+ const first = readFirstRecordDetailed(filePath);
226
+ if (!first.ok) {
227
+ return { kind: "unsupported", reason: first.reason, basename, relative_path };
228
+ }
229
+ const record = first.record;
230
+ if (record.type !== "session_meta") {
231
+ return { kind: "unsupported", reason: "unsupported_metadata", basename, relative_path };
232
+ }
233
+ const payload = record.payload;
234
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
235
+ return { kind: "unsupported", reason: "unsupported_metadata", basename, relative_path };
236
+ }
237
+ const meta = payload;
238
+ if (isSubagentMeta(meta)) {
239
+ return { kind: "excluded", reason: "subagent", basename, relative_path };
240
+ }
241
+ const sessionId = typeof meta.id === "string" ? meta.id : typeof meta.session_id === "string" ? meta.session_id : null;
242
+ if (!sessionId) {
243
+ return { kind: "unsupported", reason: "unsupported_metadata", basename, relative_path };
244
+ }
245
+ if (sessionId !== filenameId) {
246
+ return { kind: "unsupported", reason: "session_id_mismatch", basename, relative_path };
247
+ }
248
+ if (typeof meta.session_id === "string" && typeof meta.id === "string" && meta.session_id !== meta.id) {
249
+ return { kind: "unsupported", reason: "session_id_mismatch", basename, relative_path };
250
+ }
251
+ if (!validateCodexSessionId(sessionId)) {
252
+ return { kind: "unsupported", reason: "invalid_session_id", basename, relative_path };
253
+ }
254
+ let cwd = null;
255
+ if (typeof meta.cwd === "string") {
256
+ try {
257
+ cwd = (0, config_1.canonicalize)(meta.cwd);
258
+ }
259
+ catch {
260
+ cwd = null;
261
+ }
262
+ }
263
+ try {
264
+ const signals = statSignals(filePath);
265
+ return {
266
+ kind: "supported",
267
+ session: {
268
+ path: filePath,
269
+ real_path: realPath,
270
+ session_id: sessionId,
271
+ cwd,
272
+ size: signals.size,
273
+ inode: signals.inode,
274
+ birthtime_ms: signals.birthtime_ms,
275
+ mtime_ms: signals.mtime_ms,
276
+ first_chunk_fp: (0, transcript_bytes_1.firstChunkFingerprint)(filePath),
277
+ },
278
+ };
279
+ }
280
+ catch {
281
+ return { kind: "unsupported", reason: "unreadable", basename, relative_path };
282
+ }
283
+ }
284
+ function* walkRolloutCandidates(sessionsRootReal) {
285
+ let yearEntries;
286
+ try {
287
+ yearEntries = fs.readdirSync(sessionsRootReal, { withFileTypes: true });
288
+ }
289
+ catch {
290
+ return;
291
+ }
292
+ for (const yearEntry of yearEntries) {
293
+ if (!yearEntry.isDirectory() || !/^\d{4}$/.test(yearEntry.name))
294
+ continue;
295
+ const yearDir = path.join(sessionsRootReal, yearEntry.name);
296
+ let monthEntries;
297
+ try {
298
+ monthEntries = fs.readdirSync(yearDir, { withFileTypes: true });
299
+ }
300
+ catch {
301
+ continue;
302
+ }
303
+ for (const monthEntry of monthEntries) {
304
+ if (!monthEntry.isDirectory() || !/^\d{2}$/.test(monthEntry.name))
305
+ continue;
306
+ const monthDir = path.join(yearDir, monthEntry.name);
307
+ let dayEntries;
308
+ try {
309
+ dayEntries = fs.readdirSync(monthDir, { withFileTypes: true });
310
+ }
311
+ catch {
312
+ continue;
313
+ }
314
+ for (const dayEntry of dayEntries) {
315
+ if (!dayEntry.isDirectory() || !/^\d{2}$/.test(dayEntry.name))
316
+ continue;
317
+ const dayDir = path.join(monthDir, dayEntry.name);
318
+ let files;
319
+ try {
320
+ files = fs.readdirSync(dayDir, { withFileTypes: true });
321
+ }
322
+ catch {
323
+ continue;
324
+ }
325
+ for (const file of files) {
326
+ if (!file.isFile())
327
+ continue;
328
+ if (!ROLLOUT_CANDIDATE_RE.test(file.name))
329
+ continue;
330
+ const match = ROLLOUT_RE.exec(file.name);
331
+ if (match) {
332
+ yield { filePath: path.join(dayDir, file.name), filenameId: match[1], supportedName: true };
333
+ }
334
+ else {
335
+ yield { filePath: path.join(dayDir, file.name), filenameId: null, supportedName: false };
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+ function actionableDriftMessage() {
343
+ return [
344
+ "Codex rollout files were found but none use a supported format.",
345
+ "Repair/upgrade with `npx @letta-ai/dreams@latest onboard --json`, then rerun `dreams sync status --source codex --json`.",
346
+ "If still unsupported, preserve a sample rollout basename and report the new format.",
347
+ ].join(" ");
348
+ }
349
+ /**
350
+ * Walk Codex rollout candidates and classify each. Never throws for bad files.
351
+ */
352
+ function inspectCodexDiscovery(sessionsDir = codexSessionsDir(), home = os.homedir()) {
353
+ const harness = detectCodexHarness(home);
354
+ const by_reason = {};
355
+ const examples = [];
356
+ let candidates = 0;
357
+ let supported = 0;
358
+ let unsupported = 0;
359
+ let excluded_subagent = 0;
360
+ let excluded_other = 0;
361
+ if (!harness.present) {
362
+ return {
363
+ harness_present: false,
364
+ sessions_dir_present: false,
365
+ candidates: 0,
366
+ supported: 0,
367
+ unsupported: 0,
368
+ excluded_subagent: 0,
369
+ excluded_other: 0,
370
+ by_reason: {},
371
+ examples: [],
372
+ status: "absent",
373
+ message: "Codex was not detected under ~/.codex",
374
+ };
375
+ }
376
+ let sessionsRootReal;
377
+ let sessionsDirPresent = false;
378
+ try {
379
+ sessionsRootReal = (0, config_1.canonicalize)(sessionsDir);
380
+ sessionsDirPresent = fs.statSync(sessionsRootReal).isDirectory();
381
+ }
382
+ catch {
383
+ return {
384
+ harness_present: true,
385
+ sessions_dir_present: false,
386
+ candidates: 0,
387
+ supported: 0,
388
+ unsupported: 0,
389
+ excluded_subagent: 0,
390
+ excluded_other: 0,
391
+ by_reason: {},
392
+ examples: [],
393
+ status: "empty",
394
+ message: "Codex is present but no sessions directory was found",
395
+ };
396
+ }
397
+ if (!sessionsDirPresent) {
398
+ return {
399
+ harness_present: true,
400
+ sessions_dir_present: false,
401
+ candidates: 0,
402
+ supported: 0,
403
+ unsupported: 0,
404
+ excluded_subagent: 0,
405
+ excluded_other: 0,
406
+ by_reason: {},
407
+ examples: [],
408
+ status: "empty",
409
+ message: "Codex is present but no sessions directory was found",
410
+ };
411
+ }
412
+ for (const { filePath, filenameId, supportedName } of walkRolloutCandidates(sessionsRootReal)) {
413
+ candidates++;
414
+ const result = supportedName
415
+ ? classifyCodexRollout(filePath, filenameId, { sessionsRootReal, home })
416
+ : {
417
+ kind: "unsupported",
418
+ reason: "invalid_session_id",
419
+ basename: path.basename(filePath),
420
+ relative_path: homeRelative(filePath, home),
421
+ };
422
+ if (result.kind === "supported") {
423
+ supported++;
424
+ continue;
425
+ }
426
+ if (result.kind === "excluded") {
427
+ if (result.reason === "subagent")
428
+ excluded_subagent++;
429
+ else
430
+ excluded_other++;
431
+ continue;
432
+ }
433
+ unsupported++;
434
+ by_reason[result.reason] = (by_reason[result.reason] ?? 0) + 1;
435
+ if (examples.length < MAX_EXAMPLES) {
436
+ examples.push({
437
+ reason: result.reason,
438
+ basename: result.basename,
439
+ relative_path: result.relative_path,
440
+ });
441
+ }
442
+ }
443
+ let status = "ok";
444
+ let message = null;
445
+ if (candidates === 0) {
446
+ status = "empty";
447
+ message = "Codex is present but no rollout files were found";
448
+ }
449
+ else if (supported === 0 && unsupported > 0) {
450
+ status = "warning";
451
+ message = actionableDriftMessage();
452
+ }
453
+ else if (supported > 0 && unsupported > 0) {
454
+ status = "warning";
455
+ message =
456
+ "Some Codex rollout files use an unsupported format and were skipped. " +
457
+ "Rerun `dreams sync status --source codex --json` after upgrading Dreams; " +
458
+ "supported sessions continue to sync.";
459
+ }
460
+ return {
461
+ harness_present: true,
462
+ sessions_dir_present: true,
463
+ candidates,
464
+ supported,
465
+ unsupported,
466
+ excluded_subagent,
467
+ excluded_other,
468
+ by_reason,
469
+ examples,
470
+ status,
471
+ message,
472
+ };
473
+ }
474
+ /**
475
+ * Enumerate top-level Codex rollout files. Malformed, truncated, escaped,
476
+ * identity-mismatched, and subagent files are skipped (fail closed).
477
+ *
478
+ * Optional `sessionsDir` is Codex-private (tests / direct callers).
479
+ */
480
+ function discoverSessions(sessionsDir = codexSessionsDir()) {
481
+ const sessions = [];
482
+ let rootReal;
483
+ try {
484
+ rootReal = (0, config_1.canonicalize)(sessionsDir);
485
+ if (!fs.statSync(rootReal).isDirectory())
486
+ return sessions;
487
+ }
488
+ catch {
489
+ return sessions;
490
+ }
491
+ for (const { filePath, filenameId, supportedName } of walkRolloutCandidates(rootReal)) {
492
+ if (!supportedName || filenameId === null)
493
+ continue;
494
+ const result = classifyCodexRollout(filePath, filenameId, { sessionsRootReal: rootReal });
495
+ if (result.kind === "supported")
496
+ sessions.push(result.session);
497
+ }
498
+ sessions.sort((a, b) => a.path.localeCompare(b.path));
499
+ return sessions;
500
+ }
501
+ exports.codexSourceAdapter = {
502
+ sourceType: exports.CODEX_SOURCE_TYPE,
503
+ sourceKey: exports.CODEX_SOURCE_TYPE,
504
+ sourceId: exports.CODEX_SOURCE_ID,
505
+ adapterVersion: exports.CODEX_ADAPTER_VERSION,
506
+ displayName: exports.CODEX_DISPLAY_NAME,
507
+ validateSessionId: validateCodexSessionId,
508
+ discoverSessions: () => discoverSessions(),
509
+ detect: () => detectCodexHarness(),
510
+ };