@letta-ai/dreams 0.0.3 → 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.
- package/README.md +56 -34
- package/bin/dreams-codex-hook.cjs +91 -0
- package/bin/dreams.cjs +21 -0
- package/dist/auth/commands.js +21 -1
- package/dist/auth/credentials.js +73 -14
- package/dist/auth/index.js +106 -13
- package/dist/cli.js +48 -12
- package/dist/local/backfill.js +20 -3
- package/dist/local/claude-hooks.js +169 -0
- package/dist/local/claude.js +76 -20
- package/dist/local/codex-hooks.js +235 -0
- package/dist/local/codex.js +298 -101
- package/dist/local/commands.js +39 -24
- package/dist/local/db.js +4 -0
- package/dist/local/detect.js +6 -2
- package/dist/local/hooks-install.js +102 -0
- package/dist/local/source-adapters.js +13 -0
- package/dist/local/sync-control.js +73 -22
- package/dist/skill.js +202 -110
- package/dist/suppress-sqlite-warning.js +34 -0
- package/dist/sync.js +191 -31
- package/package.json +1 -1
package/dist/local/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Codex source adapter — discovery and harness detection ([LET-10314]).
|
|
3
|
+
* Codex source adapter — discovery and harness detection ([LET-10314] / [LET-10317]).
|
|
4
4
|
*
|
|
5
5
|
* On-disk layout (fail closed when unrecognized):
|
|
6
6
|
* ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<sessionId>.jsonl
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* `codex-rs/hooks/schema/generated/session-start.command.input.schema.json`)
|
|
19
19
|
* Pin: https://github.com/openai/codex/tree/fb6aad9ae34116128e537696c24e35fe6548e1c2
|
|
20
20
|
*
|
|
21
|
-
* Subagent rollouts are skipped.
|
|
21
|
+
* Subagent rollouts are skipped. Classification shares one path with discovery
|
|
22
|
+
* so status diagnostics and ingest agree. Paths stay private to this module.
|
|
22
23
|
* Registration lives in `source-adapters.ts` (not the default).
|
|
23
24
|
*/
|
|
24
25
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -59,6 +60,8 @@ exports.codexSourceAdapter = exports.CODEX_DISPLAY_NAME = exports.CODEX_SOURCE_I
|
|
|
59
60
|
exports.codexSessionsDir = codexSessionsDir;
|
|
60
61
|
exports.validateCodexSessionId = validateCodexSessionId;
|
|
61
62
|
exports.detectCodexHarness = detectCodexHarness;
|
|
63
|
+
exports.classifyCodexRollout = classifyCodexRollout;
|
|
64
|
+
exports.inspectCodexDiscovery = inspectCodexDiscovery;
|
|
62
65
|
exports.discoverSessions = discoverSessions;
|
|
63
66
|
const fs = __importStar(require("node:fs"));
|
|
64
67
|
const os = __importStar(require("node:os"));
|
|
@@ -74,6 +77,9 @@ const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
|
74
77
|
const HEAD_READ_BYTES = 256 * 1024;
|
|
75
78
|
/** Timestamp uses hyphens instead of colons: YYYY-MM-DDTHH-MM-SS. */
|
|
76
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;
|
|
77
83
|
function codexSessionsDir() {
|
|
78
84
|
return path.join(os.homedir(), ".codex", "sessions");
|
|
79
85
|
}
|
|
@@ -116,27 +122,43 @@ function detectCodexHarness(home = os.homedir()) {
|
|
|
116
122
|
}
|
|
117
123
|
return { present: true, evidence };
|
|
118
124
|
}
|
|
119
|
-
function
|
|
125
|
+
function readFirstRecordDetailed(filePath) {
|
|
120
126
|
let fd;
|
|
121
127
|
try {
|
|
122
128
|
fd = fs.openSync(filePath, "r");
|
|
123
129
|
const buf = Buffer.alloc(HEAD_READ_BYTES);
|
|
124
130
|
const read = fs.readSync(fd, buf, 0, HEAD_READ_BYTES, 0);
|
|
131
|
+
if (read === 0)
|
|
132
|
+
return { ok: false, reason: "empty" };
|
|
125
133
|
const head = buf.subarray(0, read);
|
|
126
134
|
const nl = head.indexOf(0x0a);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const line = head.subarray(0, nl).toString("utf8").trim();
|
|
135
|
+
const lineBytes = nl === -1 ? head : head.subarray(0, nl);
|
|
136
|
+
const line = lineBytes.toString("utf8").trim();
|
|
130
137
|
if (line === "")
|
|
131
|
-
return
|
|
132
|
-
|
|
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
|
+
}
|
|
133
149
|
}
|
|
134
150
|
catch {
|
|
135
|
-
return
|
|
151
|
+
return { ok: false, reason: "unreadable" };
|
|
136
152
|
}
|
|
137
153
|
finally {
|
|
138
|
-
if (fd !== undefined)
|
|
139
|
-
|
|
154
|
+
if (fd !== undefined) {
|
|
155
|
+
try {
|
|
156
|
+
fs.closeSync(fd);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// ignore
|
|
160
|
+
}
|
|
161
|
+
}
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
164
|
function isSubagentMeta(payload) {
|
|
@@ -158,36 +180,119 @@ function statSignals(filePath) {
|
|
|
158
180
|
const mtime = Number.isFinite(stat.mtimeMs) ? Math.floor(stat.mtimeMs) : null;
|
|
159
181
|
return { size: stat.size, inode, birthtime_ms: birthtime, mtime_ms: mtime };
|
|
160
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
|
+
}
|
|
161
205
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* Optional `sessionsDir` is Codex-private (tests / direct callers).
|
|
206
|
+
* Classify one rollout candidate. Shared by discovery ingest and status
|
|
207
|
+
* diagnostics — a "supported" result is exactly what `discoverSessions` emits.
|
|
166
208
|
*/
|
|
167
|
-
function
|
|
168
|
-
const
|
|
169
|
-
|
|
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;
|
|
170
216
|
try {
|
|
171
|
-
|
|
172
|
-
if (!fs.statSync(rootReal).isDirectory())
|
|
173
|
-
return sessions;
|
|
217
|
+
realPath = (0, config_1.canonicalize)(filePath);
|
|
174
218
|
}
|
|
175
219
|
catch {
|
|
176
|
-
return
|
|
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 };
|
|
177
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) {
|
|
178
285
|
let yearEntries;
|
|
179
286
|
try {
|
|
180
|
-
yearEntries = fs.readdirSync(
|
|
287
|
+
yearEntries = fs.readdirSync(sessionsRootReal, { withFileTypes: true });
|
|
181
288
|
}
|
|
182
289
|
catch {
|
|
183
|
-
return
|
|
290
|
+
return;
|
|
184
291
|
}
|
|
185
292
|
for (const yearEntry of yearEntries) {
|
|
186
|
-
if (!yearEntry.isDirectory())
|
|
187
|
-
continue;
|
|
188
|
-
if (!/^\d{4}$/.test(yearEntry.name))
|
|
293
|
+
if (!yearEntry.isDirectory() || !/^\d{4}$/.test(yearEntry.name))
|
|
189
294
|
continue;
|
|
190
|
-
const yearDir = path.join(
|
|
295
|
+
const yearDir = path.join(sessionsRootReal, yearEntry.name);
|
|
191
296
|
let monthEntries;
|
|
192
297
|
try {
|
|
193
298
|
monthEntries = fs.readdirSync(yearDir, { withFileTypes: true });
|
|
@@ -196,9 +301,7 @@ function discoverSessions(sessionsDir = codexSessionsDir()) {
|
|
|
196
301
|
continue;
|
|
197
302
|
}
|
|
198
303
|
for (const monthEntry of monthEntries) {
|
|
199
|
-
if (!monthEntry.isDirectory())
|
|
200
|
-
continue;
|
|
201
|
-
if (!/^\d{2}$/.test(monthEntry.name))
|
|
304
|
+
if (!monthEntry.isDirectory() || !/^\d{2}$/.test(monthEntry.name))
|
|
202
305
|
continue;
|
|
203
306
|
const monthDir = path.join(yearDir, monthEntry.name);
|
|
204
307
|
let dayEntries;
|
|
@@ -209,9 +312,7 @@ function discoverSessions(sessionsDir = codexSessionsDir()) {
|
|
|
209
312
|
continue;
|
|
210
313
|
}
|
|
211
314
|
for (const dayEntry of dayEntries) {
|
|
212
|
-
if (!dayEntry.isDirectory())
|
|
213
|
-
continue;
|
|
214
|
-
if (!/^\d{2}$/.test(dayEntry.name))
|
|
315
|
+
if (!dayEntry.isDirectory() || !/^\d{2}$/.test(dayEntry.name))
|
|
215
316
|
continue;
|
|
216
317
|
const dayDir = path.join(monthDir, dayEntry.name);
|
|
217
318
|
let files;
|
|
@@ -224,80 +325,176 @@ function discoverSessions(sessionsDir = codexSessionsDir()) {
|
|
|
224
325
|
for (const file of files) {
|
|
225
326
|
if (!file.isFile())
|
|
226
327
|
continue;
|
|
227
|
-
|
|
228
|
-
if (!match)
|
|
229
|
-
continue;
|
|
230
|
-
const filenameId = match[1];
|
|
231
|
-
if (!validateCodexSessionId(filenameId))
|
|
328
|
+
if (!ROLLOUT_CANDIDATE_RE.test(file.name))
|
|
232
329
|
continue;
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
realPath = (0, config_1.canonicalize)(filePath);
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (!underRoot(rootReal, realPath))
|
|
243
|
-
continue;
|
|
244
|
-
const record = readFirstRecord(filePath);
|
|
245
|
-
if (!record)
|
|
246
|
-
continue;
|
|
247
|
-
if (record.type !== "session_meta")
|
|
248
|
-
continue;
|
|
249
|
-
const payload = record.payload;
|
|
250
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
251
|
-
continue;
|
|
252
|
-
const meta = payload;
|
|
253
|
-
if (isSubagentMeta(meta))
|
|
254
|
-
continue;
|
|
255
|
-
// Prefer payload.id; accept payload.session_id when present (same ThreadId).
|
|
256
|
-
const sessionId = typeof meta.id === "string"
|
|
257
|
-
? meta.id
|
|
258
|
-
: typeof meta.session_id === "string"
|
|
259
|
-
? meta.session_id
|
|
260
|
-
: null;
|
|
261
|
-
if (!sessionId || sessionId !== filenameId)
|
|
262
|
-
continue;
|
|
263
|
-
if (typeof meta.session_id === "string" &&
|
|
264
|
-
typeof meta.id === "string" &&
|
|
265
|
-
meta.session_id !== meta.id) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (!validateCodexSessionId(sessionId))
|
|
269
|
-
continue;
|
|
270
|
-
let cwd = null;
|
|
271
|
-
if (typeof meta.cwd === "string") {
|
|
272
|
-
try {
|
|
273
|
-
cwd = (0, config_1.canonicalize)(meta.cwd);
|
|
274
|
-
}
|
|
275
|
-
catch {
|
|
276
|
-
// Unresolvable cwd → discover but deny-by-default at policy gate.
|
|
277
|
-
cwd = null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
const signals = statSignals(filePath);
|
|
281
|
-
sessions.push({
|
|
282
|
-
path: filePath,
|
|
283
|
-
real_path: realPath,
|
|
284
|
-
session_id: sessionId,
|
|
285
|
-
cwd,
|
|
286
|
-
size: signals.size,
|
|
287
|
-
inode: signals.inode,
|
|
288
|
-
birthtime_ms: signals.birthtime_ms,
|
|
289
|
-
mtime_ms: signals.mtime_ms,
|
|
290
|
-
first_chunk_fp: (0, transcript_bytes_1.firstChunkFingerprint)(filePath),
|
|
291
|
-
});
|
|
330
|
+
const match = ROLLOUT_RE.exec(file.name);
|
|
331
|
+
if (match) {
|
|
332
|
+
yield { filePath: path.join(dayDir, file.name), filenameId: match[1], supportedName: true };
|
|
292
333
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
continue;
|
|
334
|
+
else {
|
|
335
|
+
yield { filePath: path.join(dayDir, file.name), filenameId: null, supportedName: false };
|
|
296
336
|
}
|
|
297
337
|
}
|
|
298
338
|
}
|
|
299
339
|
}
|
|
300
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
|
+
}
|
|
301
498
|
sessions.sort((a, b) => a.path.localeCompare(b.path));
|
|
302
499
|
return sessions;
|
|
303
500
|
}
|
package/dist/local/commands.js
CHANGED
|
@@ -51,7 +51,7 @@ function maxRecordBytesOverride() {
|
|
|
51
51
|
const value = Number(raw);
|
|
52
52
|
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
53
53
|
}
|
|
54
|
-
function commandSourceScanUnlocked(json) {
|
|
54
|
+
function commandSourceScanUnlocked(json, adapter) {
|
|
55
55
|
const { db, installationId } = open();
|
|
56
56
|
try {
|
|
57
57
|
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
@@ -64,7 +64,7 @@ function commandSourceScanUnlocked(json) {
|
|
|
64
64
|
summary = (0, scan_1.reconcile)(db, {
|
|
65
65
|
installationId,
|
|
66
66
|
leaseOwner: owner,
|
|
67
|
-
adapter
|
|
67
|
+
adapter,
|
|
68
68
|
maxSegmentBytes: maxSegmentBytesOverride(),
|
|
69
69
|
maxRecordBytes: maxRecordBytesOverride(),
|
|
70
70
|
});
|
|
@@ -90,7 +90,7 @@ function commandSourceScanUnlocked(json) {
|
|
|
90
90
|
(0, db_1.closeDb)();
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
async function commandUploadUnlocked(json) {
|
|
93
|
+
async function commandUploadUnlocked(json, adapter) {
|
|
94
94
|
const { db, installationId } = open();
|
|
95
95
|
try {
|
|
96
96
|
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
@@ -103,7 +103,7 @@ async function commandUploadUnlocked(json) {
|
|
|
103
103
|
summary = await (0, upload_1.runUpload)(db, {
|
|
104
104
|
installationId,
|
|
105
105
|
leaseOwner: owner,
|
|
106
|
-
adapter
|
|
106
|
+
adapter,
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
finally {
|
|
@@ -132,13 +132,13 @@ async function commandUploadUnlocked(json) {
|
|
|
132
132
|
}
|
|
133
133
|
function commandSourceApproveRootUnlocked(json, root) {
|
|
134
134
|
if (!root) {
|
|
135
|
-
process.stderr.write("Usage: dreams source approve-
|
|
135
|
+
process.stderr.write("Usage: dreams source approve-dir <path>\n");
|
|
136
136
|
return 1;
|
|
137
137
|
}
|
|
138
138
|
const { db } = open();
|
|
139
139
|
try {
|
|
140
140
|
const roots = (0, config_1.approveRoot)(db, root);
|
|
141
|
-
emit(json, { status: "ok", approved_roots: roots }, () => `Approved
|
|
141
|
+
emit(json, { status: "ok", approved_roots: roots, approved_directories: roots }, () => `Approved directories:\n${roots.map((r) => ` ${r}`).join("\n") || " (none)"}`);
|
|
142
142
|
return 0;
|
|
143
143
|
}
|
|
144
144
|
finally {
|
|
@@ -153,13 +153,20 @@ function commandSourceListUnlocked(json) {
|
|
|
153
153
|
.prepare("SELECT id, source_type, source_key, display_name, status, server_source_id FROM sources ORDER BY id")
|
|
154
154
|
.all();
|
|
155
155
|
const queue = (0, scan_1.listPendingQueue)(db);
|
|
156
|
-
emit(json, {
|
|
156
|
+
emit(json, {
|
|
157
|
+
status: "ok",
|
|
158
|
+
db_path: (0, db_1.dbFilePath)(),
|
|
159
|
+
approved_roots: roots,
|
|
160
|
+
approved_directories: roots,
|
|
161
|
+
sources,
|
|
162
|
+
pending_uploads: queue,
|
|
163
|
+
}, () => [
|
|
157
164
|
`Dream local sources`,
|
|
158
165
|
``,
|
|
159
|
-
` DB:
|
|
160
|
-
` Approved
|
|
161
|
-
` Sources:
|
|
162
|
-
` Pending uploads:
|
|
166
|
+
` DB: ${(0, db_1.dbFilePath)()}`,
|
|
167
|
+
` Approved directories: ${roots.length ? roots.join(", ") : "(none — deny by default)"}`,
|
|
168
|
+
` Sources: ${sources.length}`,
|
|
169
|
+
` Pending uploads: ${queue.length}`,
|
|
163
170
|
].join("\n"));
|
|
164
171
|
return 0;
|
|
165
172
|
}
|
|
@@ -167,12 +174,12 @@ function commandSourceListUnlocked(json) {
|
|
|
167
174
|
(0, db_1.closeDb)();
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
|
-
async function commandSourceDetectUnlocked(json) {
|
|
177
|
+
async function commandSourceDetectUnlocked(json, adapter) {
|
|
171
178
|
const { db, installationId } = open();
|
|
172
179
|
try {
|
|
173
180
|
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
174
181
|
if (owner === null) {
|
|
175
|
-
emit(json, { status: "busy", reason: "another source operation holds the local lease" }, () => "Another
|
|
182
|
+
emit(json, { status: "busy", reason: "another source operation holds the local lease" }, () => "Another Dreams source operation is in progress; skipping.");
|
|
176
183
|
return 0;
|
|
177
184
|
}
|
|
178
185
|
let result;
|
|
@@ -180,7 +187,7 @@ async function commandSourceDetectUnlocked(json) {
|
|
|
180
187
|
result = await (0, detect_1.runSourceDetect)(db, {
|
|
181
188
|
installationId,
|
|
182
189
|
leaseOwner: owner,
|
|
183
|
-
adapter
|
|
190
|
+
adapter,
|
|
184
191
|
});
|
|
185
192
|
}
|
|
186
193
|
finally {
|
|
@@ -207,23 +214,25 @@ function stateBusy(json) {
|
|
|
207
214
|
emit(json, { status: "busy", reason: "another Dreams command holds the local state lock" }, () => "Another Dreams command is using local state; skipping.");
|
|
208
215
|
return 0;
|
|
209
216
|
}
|
|
210
|
-
function commandSourceScan(json) {
|
|
217
|
+
function commandSourceScan(json, sourceType) {
|
|
218
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
211
219
|
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
212
220
|
if (lock === null)
|
|
213
221
|
return stateBusy(json);
|
|
214
222
|
try {
|
|
215
|
-
return commandSourceScanUnlocked(json);
|
|
223
|
+
return commandSourceScanUnlocked(json, adapter);
|
|
216
224
|
}
|
|
217
225
|
finally {
|
|
218
226
|
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
219
227
|
}
|
|
220
228
|
}
|
|
221
|
-
async function commandUpload(json) {
|
|
229
|
+
async function commandUpload(json, sourceType) {
|
|
230
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
222
231
|
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
223
232
|
if (lock === null)
|
|
224
233
|
return stateBusy(json);
|
|
225
234
|
try {
|
|
226
|
-
return await commandUploadUnlocked(json);
|
|
235
|
+
return await commandUploadUnlocked(json, adapter);
|
|
227
236
|
}
|
|
228
237
|
finally {
|
|
229
238
|
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
@@ -251,29 +260,35 @@ function commandSourceList(json) {
|
|
|
251
260
|
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
252
261
|
}
|
|
253
262
|
}
|
|
254
|
-
async function commandSourceDetect(json) {
|
|
263
|
+
async function commandSourceDetect(json, sourceType) {
|
|
264
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
255
265
|
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
256
266
|
if (lock === null)
|
|
257
267
|
return stateBusy(json);
|
|
258
268
|
try {
|
|
259
|
-
return await commandSourceDetectUnlocked(json);
|
|
269
|
+
return await commandSourceDetectUnlocked(json, adapter);
|
|
260
270
|
}
|
|
261
271
|
finally {
|
|
262
272
|
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
263
273
|
}
|
|
264
274
|
}
|
|
265
|
-
function commandSource(subcommand, positional, json) {
|
|
275
|
+
function commandSource(subcommand, positional, json, sourceType) {
|
|
276
|
+
if (sourceType !== undefined && subcommand !== "detect" && subcommand !== "scan") {
|
|
277
|
+
process.stderr.write(`--source is only valid with \`dreams source detect\` or \`dreams source scan\`\n`);
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
266
280
|
switch (subcommand) {
|
|
267
281
|
case "detect":
|
|
268
|
-
return commandSourceDetect(json);
|
|
282
|
+
return commandSourceDetect(json, sourceType);
|
|
269
283
|
case "scan":
|
|
270
|
-
return commandSourceScan(json);
|
|
284
|
+
return commandSourceScan(json, sourceType);
|
|
271
285
|
case "approve-root":
|
|
286
|
+
case "approve-dir":
|
|
272
287
|
return commandSourceApproveRoot(json, positional);
|
|
273
288
|
case "list":
|
|
274
289
|
return commandSourceList(json);
|
|
275
290
|
default:
|
|
276
|
-
process.stderr.write(`Unknown source subcommand: ${subcommand ?? "(none)"} — expected detect, scan, approve-root, or list\n`);
|
|
291
|
+
process.stderr.write(`Unknown source subcommand: ${subcommand ?? "(none)"} — expected detect, scan, approve-root, approve-dir, or list\n`);
|
|
277
292
|
return 1;
|
|
278
293
|
}
|
|
279
294
|
}
|
package/dist/local/db.js
CHANGED
|
@@ -252,6 +252,10 @@ const MIGRATIONS = [
|
|
|
252
252
|
state = COALESCE((SELECT state FROM sync_runtime WHERE id = 1), 'idle')
|
|
253
253
|
WHERE source_id = 'src_claude-code'`,
|
|
254
254
|
],
|
|
255
|
+
[
|
|
256
|
+
// Track which source currently owns the singleton worker ([LET-10317]).
|
|
257
|
+
`ALTER TABLE sync_runtime ADD COLUMN active_source_id TEXT`,
|
|
258
|
+
],
|
|
255
259
|
];
|
|
256
260
|
exports.SCHEMA_VERSION = MIGRATIONS.length;
|
|
257
261
|
function tx(db, fn) {
|