@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.
- package/README.md +111 -35
- package/bin/dreams-codex-hook.cjs +91 -0
- package/bin/dreams.cjs +21 -0
- package/dist/auth/commands.js +67 -5
- package/dist/auth/credentials.js +101 -22
- package/dist/auth/index.js +106 -13
- package/dist/cli.js +239 -13
- package/dist/cloud/client.js +89 -5
- package/dist/cloud/upload.js +150 -70
- package/dist/development-reset.js +359 -0
- package/dist/launcher.js +311 -0
- package/dist/local/backfill.js +625 -0
- package/dist/local/bindings.js +30 -0
- package/dist/local/claude-hooks.js +169 -0
- package/dist/local/claude.js +130 -176
- package/dist/local/codex-hooks.js +235 -0
- package/dist/local/codex.js +510 -0
- package/dist/local/commands.js +153 -19
- package/dist/local/db.js +68 -0
- package/dist/local/detect.js +148 -0
- package/dist/local/discovered.js +6 -0
- package/dist/local/hooks-install.js +102 -0
- package/dist/local/leases.js +3 -1
- package/dist/local/scan.js +99 -17
- package/dist/local/source-adapter.js +8 -0
- package/dist/local/source-adapters.js +71 -0
- package/dist/local/state-lock.js +88 -0
- package/dist/local/sync-control.js +432 -0
- package/dist/local/transcript-bytes.js +204 -0
- package/dist/managed-install.js +74 -86
- package/dist/onboard.js +26 -72
- package/dist/skill.js +373 -16
- package/dist/snapshots.js +210 -0
- package/dist/suppress-sqlite-warning.js +34 -0
- package/dist/sync.js +950 -0
- package/package.json +1 -1
package/dist/local/commands.js
CHANGED
|
@@ -10,13 +10,17 @@ exports.commandSourceScan = commandSourceScan;
|
|
|
10
10
|
exports.commandUpload = commandUpload;
|
|
11
11
|
exports.commandSourceApproveRoot = commandSourceApproveRoot;
|
|
12
12
|
exports.commandSourceList = commandSourceList;
|
|
13
|
+
exports.commandSourceDetect = commandSourceDetect;
|
|
13
14
|
exports.commandSource = commandSource;
|
|
14
15
|
const store_1 = require("../store");
|
|
16
|
+
const upload_1 = require("../cloud/upload");
|
|
15
17
|
const config_1 = require("./config");
|
|
16
18
|
const db_1 = require("./db");
|
|
19
|
+
const detect_1 = require("./detect");
|
|
17
20
|
const leases_1 = require("./leases");
|
|
18
21
|
const scan_1 = require("./scan");
|
|
19
|
-
const
|
|
22
|
+
const source_adapters_1 = require("./source-adapters");
|
|
23
|
+
const state_lock_1 = require("./state-lock");
|
|
20
24
|
function open() {
|
|
21
25
|
const installation = (0, store_1.loadOrCreateInstallation)();
|
|
22
26
|
const db = (0, db_1.openDb)({
|
|
@@ -31,7 +35,7 @@ function emit(json, payload, human) {
|
|
|
31
35
|
else
|
|
32
36
|
console.log(human());
|
|
33
37
|
}
|
|
34
|
-
/** Optional override for the
|
|
38
|
+
/** Optional override for the soft segment packing target (advanced/testing). */
|
|
35
39
|
function maxSegmentBytesOverride() {
|
|
36
40
|
const raw = process.env.DREAMS_MAX_SEGMENT_BYTES;
|
|
37
41
|
if (!raw)
|
|
@@ -39,7 +43,15 @@ function maxSegmentBytesOverride() {
|
|
|
39
43
|
const value = Number(raw);
|
|
40
44
|
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
41
45
|
}
|
|
42
|
-
|
|
46
|
+
/** Optional override for the hard per-record ceiling (advanced/testing). */
|
|
47
|
+
function maxRecordBytesOverride() {
|
|
48
|
+
const raw = process.env.DREAMS_MAX_RECORD_BYTES;
|
|
49
|
+
if (!raw)
|
|
50
|
+
return undefined;
|
|
51
|
+
const value = Number(raw);
|
|
52
|
+
return Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
53
|
+
}
|
|
54
|
+
function commandSourceScanUnlocked(json, adapter) {
|
|
43
55
|
const { db, installationId } = open();
|
|
44
56
|
try {
|
|
45
57
|
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
@@ -49,7 +61,13 @@ function commandSourceScan(json) {
|
|
|
49
61
|
}
|
|
50
62
|
let summary;
|
|
51
63
|
try {
|
|
52
|
-
summary = (0, scan_1.reconcile)(db, {
|
|
64
|
+
summary = (0, scan_1.reconcile)(db, {
|
|
65
|
+
installationId,
|
|
66
|
+
leaseOwner: owner,
|
|
67
|
+
adapter,
|
|
68
|
+
maxSegmentBytes: maxSegmentBytesOverride(),
|
|
69
|
+
maxRecordBytes: maxRecordBytesOverride(),
|
|
70
|
+
});
|
|
53
71
|
}
|
|
54
72
|
finally {
|
|
55
73
|
(0, leases_1.releaseLease)(db, leases_1.SOURCE_LEASE, owner);
|
|
@@ -72,7 +90,7 @@ function commandSourceScan(json) {
|
|
|
72
90
|
(0, db_1.closeDb)();
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
|
-
async function
|
|
93
|
+
async function commandUploadUnlocked(json, adapter) {
|
|
76
94
|
const { db, installationId } = open();
|
|
77
95
|
try {
|
|
78
96
|
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
@@ -82,7 +100,11 @@ async function commandUpload(json) {
|
|
|
82
100
|
}
|
|
83
101
|
let summary;
|
|
84
102
|
try {
|
|
85
|
-
summary = await (0, upload_1.runUpload)(db, {
|
|
103
|
+
summary = await (0, upload_1.runUpload)(db, {
|
|
104
|
+
installationId,
|
|
105
|
+
leaseOwner: owner,
|
|
106
|
+
adapter,
|
|
107
|
+
});
|
|
86
108
|
}
|
|
87
109
|
finally {
|
|
88
110
|
(0, leases_1.releaseLease)(db, leases_1.SOURCE_LEASE, owner);
|
|
@@ -108,36 +130,43 @@ async function commandUpload(json) {
|
|
|
108
130
|
(0, db_1.closeDb)();
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
|
-
function
|
|
133
|
+
function commandSourceApproveRootUnlocked(json, root) {
|
|
112
134
|
if (!root) {
|
|
113
|
-
process.stderr.write("Usage: dreams source approve-
|
|
135
|
+
process.stderr.write("Usage: dreams source approve-dir <path>\n");
|
|
114
136
|
return 1;
|
|
115
137
|
}
|
|
116
138
|
const { db } = open();
|
|
117
139
|
try {
|
|
118
140
|
const roots = (0, config_1.approveRoot)(db, root);
|
|
119
|
-
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)"}`);
|
|
120
142
|
return 0;
|
|
121
143
|
}
|
|
122
144
|
finally {
|
|
123
145
|
(0, db_1.closeDb)();
|
|
124
146
|
}
|
|
125
147
|
}
|
|
126
|
-
function
|
|
148
|
+
function commandSourceListUnlocked(json) {
|
|
127
149
|
const { db } = open();
|
|
128
150
|
try {
|
|
129
151
|
const roots = (0, config_1.getApprovedRoots)(db);
|
|
130
152
|
const sources = db
|
|
131
|
-
.prepare("SELECT id, source_type, source_key, display_name, status FROM sources ORDER BY id")
|
|
153
|
+
.prepare("SELECT id, source_type, source_key, display_name, status, server_source_id FROM sources ORDER BY id")
|
|
132
154
|
.all();
|
|
133
155
|
const queue = (0, scan_1.listPendingQueue)(db);
|
|
134
|
-
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
|
+
}, () => [
|
|
135
164
|
`Dream local sources`,
|
|
136
165
|
``,
|
|
137
|
-
` DB:
|
|
138
|
-
` Approved
|
|
139
|
-
` Sources:
|
|
140
|
-
` 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}`,
|
|
141
170
|
].join("\n"));
|
|
142
171
|
return 0;
|
|
143
172
|
}
|
|
@@ -145,16 +174,121 @@ function commandSourceList(json) {
|
|
|
145
174
|
(0, db_1.closeDb)();
|
|
146
175
|
}
|
|
147
176
|
}
|
|
148
|
-
function
|
|
177
|
+
async function commandSourceDetectUnlocked(json, adapter) {
|
|
178
|
+
const { db, installationId } = open();
|
|
179
|
+
try {
|
|
180
|
+
const owner = (0, leases_1.acquireLease)(db, leases_1.SOURCE_LEASE);
|
|
181
|
+
if (owner === null) {
|
|
182
|
+
emit(json, { status: "busy", reason: "another source operation holds the local lease" }, () => "Another Dreams source operation is in progress; skipping.");
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
let result;
|
|
186
|
+
try {
|
|
187
|
+
result = await (0, detect_1.runSourceDetect)(db, {
|
|
188
|
+
installationId,
|
|
189
|
+
leaseOwner: owner,
|
|
190
|
+
adapter,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
(0, leases_1.releaseLease)(db, leases_1.SOURCE_LEASE, owner);
|
|
195
|
+
}
|
|
196
|
+
emit(json, result, () => [
|
|
197
|
+
result.status === "ok" ? "Source detection complete" : "Source detection failed",
|
|
198
|
+
``,
|
|
199
|
+
` Harness: ${result.harness}${result.harness_present ? "" : " (not found)"}`,
|
|
200
|
+
` Local source: ${result.local_source_id ?? "(none)"}`,
|
|
201
|
+
` Server source: ${result.server_source_id ?? "(none)"}`,
|
|
202
|
+
` Workspace: ${result.workspace_id ?? "(none)"}`,
|
|
203
|
+
` Source status: ${result.source_status ?? "(none)"}`,
|
|
204
|
+
` Registration: ${result.registration}`,
|
|
205
|
+
result.message ? ` Message: ${result.message}` : ``,
|
|
206
|
+
].filter((line) => line !== ``).join("\n"));
|
|
207
|
+
return result.status === "ok" ? 0 : 1;
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
(0, db_1.closeDb)();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function stateBusy(json) {
|
|
214
|
+
emit(json, { status: "busy", reason: "another Dreams command holds the local state lock" }, () => "Another Dreams command is using local state; skipping.");
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
function commandSourceScan(json, sourceType) {
|
|
218
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
219
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
220
|
+
if (lock === null)
|
|
221
|
+
return stateBusy(json);
|
|
222
|
+
try {
|
|
223
|
+
return commandSourceScanUnlocked(json, adapter);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function commandUpload(json, sourceType) {
|
|
230
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
231
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
232
|
+
if (lock === null)
|
|
233
|
+
return stateBusy(json);
|
|
234
|
+
try {
|
|
235
|
+
return await commandUploadUnlocked(json, adapter);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function commandSourceApproveRoot(json, root) {
|
|
242
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
243
|
+
if (lock === null)
|
|
244
|
+
return stateBusy(json);
|
|
245
|
+
try {
|
|
246
|
+
return commandSourceApproveRootUnlocked(json, root);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function commandSourceList(json) {
|
|
253
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
254
|
+
if (lock === null)
|
|
255
|
+
return stateBusy(json);
|
|
256
|
+
try {
|
|
257
|
+
return commandSourceListUnlocked(json);
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function commandSourceDetect(json, sourceType) {
|
|
264
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
265
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
266
|
+
if (lock === null)
|
|
267
|
+
return stateBusy(json);
|
|
268
|
+
try {
|
|
269
|
+
return await commandSourceDetectUnlocked(json, adapter);
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
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
|
+
}
|
|
149
280
|
switch (subcommand) {
|
|
281
|
+
case "detect":
|
|
282
|
+
return commandSourceDetect(json, sourceType);
|
|
150
283
|
case "scan":
|
|
151
|
-
return commandSourceScan(json);
|
|
284
|
+
return commandSourceScan(json, sourceType);
|
|
152
285
|
case "approve-root":
|
|
286
|
+
case "approve-dir":
|
|
153
287
|
return commandSourceApproveRoot(json, positional);
|
|
154
288
|
case "list":
|
|
155
289
|
return commandSourceList(json);
|
|
156
290
|
default:
|
|
157
|
-
process.stderr.write(`Unknown source subcommand: ${subcommand ?? "(none)"} — expected 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`);
|
|
158
292
|
return 1;
|
|
159
293
|
}
|
|
160
294
|
}
|
package/dist/local/db.js
CHANGED
|
@@ -188,6 +188,74 @@ const MIGRATIONS = [
|
|
|
188
188
|
// Due-row selection: pending rows for a source, ordered by priority then time.
|
|
189
189
|
`CREATE INDEX idx_upload_queue_due ON upload_queue (source_id, state, priority, next_attempt_at)`,
|
|
190
190
|
],
|
|
191
|
+
[
|
|
192
|
+
// Hook-safe sync worker ([LET-10237]): durable trigger coalescing + runtime status.
|
|
193
|
+
`CREATE TABLE sync_triggers (
|
|
194
|
+
source_id TEXT PRIMARY KEY REFERENCES sources(id) ON DELETE CASCADE,
|
|
195
|
+
requested_gen INTEGER NOT NULL DEFAULT 0,
|
|
196
|
+
processed_gen INTEGER NOT NULL DEFAULT 0,
|
|
197
|
+
forced INTEGER NOT NULL DEFAULT 0,
|
|
198
|
+
session_hints TEXT,
|
|
199
|
+
updated_at TEXT NOT NULL
|
|
200
|
+
)`,
|
|
201
|
+
`CREATE TABLE sync_runtime (
|
|
202
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
203
|
+
paused INTEGER NOT NULL DEFAULT 0,
|
|
204
|
+
state TEXT NOT NULL DEFAULT 'idle',
|
|
205
|
+
last_attempt_at TEXT,
|
|
206
|
+
last_success_at TEXT,
|
|
207
|
+
last_error_at TEXT,
|
|
208
|
+
last_error_code TEXT,
|
|
209
|
+
last_error_message TEXT,
|
|
210
|
+
next_due_at TEXT,
|
|
211
|
+
updated_at TEXT NOT NULL
|
|
212
|
+
)`,
|
|
213
|
+
`INSERT INTO sync_runtime (id, paused, state, updated_at) VALUES (1, 0, 'idle', datetime('now'))`,
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
// Backfill session frontier ([LET-10239]): per-generation completion for an
|
|
217
|
+
// approved [since_at, until_at] window. Mtime orders unfinished sessions only.
|
|
218
|
+
// Rows are scoped by policy_id so a later window cannot inherit stale completes.
|
|
219
|
+
`CREATE TABLE backfill_frontier (
|
|
220
|
+
policy_id TEXT NOT NULL,
|
|
221
|
+
source_id TEXT NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
|
222
|
+
session_id TEXT NOT NULL,
|
|
223
|
+
generation INTEGER NOT NULL,
|
|
224
|
+
source_file_id INTEGER REFERENCES source_files(id) ON DELETE SET NULL,
|
|
225
|
+
mtime_ms INTEGER,
|
|
226
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
227
|
+
completed_at TEXT,
|
|
228
|
+
updated_at TEXT NOT NULL,
|
|
229
|
+
PRIMARY KEY (policy_id, session_id, generation)
|
|
230
|
+
)`,
|
|
231
|
+
`CREATE INDEX idx_backfill_frontier_unfinished
|
|
232
|
+
ON backfill_frontier (policy_id, status, mtime_ms)`,
|
|
233
|
+
],
|
|
234
|
+
[
|
|
235
|
+
// Per-source sync schedule ([LET-10314]): cadence/retry isolation across adapters.
|
|
236
|
+
// Global pause/running remain on sync_runtime; due state lives on sync_triggers.
|
|
237
|
+
`ALTER TABLE sync_triggers ADD COLUMN state TEXT NOT NULL DEFAULT 'idle'`,
|
|
238
|
+
`ALTER TABLE sync_triggers ADD COLUMN last_attempt_at TEXT`,
|
|
239
|
+
`ALTER TABLE sync_triggers ADD COLUMN last_success_at TEXT`,
|
|
240
|
+
`ALTER TABLE sync_triggers ADD COLUMN last_error_at TEXT`,
|
|
241
|
+
`ALTER TABLE sync_triggers ADD COLUMN last_error_code TEXT`,
|
|
242
|
+
`ALTER TABLE sync_triggers ADD COLUMN last_error_message TEXT`,
|
|
243
|
+
`ALTER TABLE sync_triggers ADD COLUMN next_due_at TEXT`,
|
|
244
|
+
// Preserve any pre-migration global cadence onto the default Claude trigger row.
|
|
245
|
+
`UPDATE sync_triggers
|
|
246
|
+
SET next_due_at = (SELECT next_due_at FROM sync_runtime WHERE id = 1),
|
|
247
|
+
last_attempt_at = (SELECT last_attempt_at FROM sync_runtime WHERE id = 1),
|
|
248
|
+
last_success_at = (SELECT last_success_at FROM sync_runtime WHERE id = 1),
|
|
249
|
+
last_error_at = (SELECT last_error_at FROM sync_runtime WHERE id = 1),
|
|
250
|
+
last_error_code = (SELECT last_error_code FROM sync_runtime WHERE id = 1),
|
|
251
|
+
last_error_message = (SELECT last_error_message FROM sync_runtime WHERE id = 1),
|
|
252
|
+
state = COALESCE((SELECT state FROM sync_runtime WHERE id = 1), 'idle')
|
|
253
|
+
WHERE source_id = 'src_claude-code'`,
|
|
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
|
+
],
|
|
191
259
|
];
|
|
192
260
|
exports.SCHEMA_VERSION = MIGRATIONS.length;
|
|
193
261
|
function tx(db, fn) {
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `dreams source detect` — register a harness as a detected Cloud source
|
|
4
|
+
* without reading transcripts, approving roots, scanning, or uploading.
|
|
5
|
+
*
|
|
6
|
+
* CLI defaults to the Claude adapter; pass an explicit adapter for Codex
|
|
7
|
+
* (`dreams source detect --source codex`) ([LET-10313] / [LET-10315]).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.detectClaudeCodeHarness = detectClaudeCodeHarness;
|
|
11
|
+
exports.localClaudeSourceId = localClaudeSourceId;
|
|
12
|
+
exports.ensureLocalDetectedSource = ensureLocalDetectedSource;
|
|
13
|
+
exports.runSourceDetect = runSourceDetect;
|
|
14
|
+
const index_1 = require("../auth/index");
|
|
15
|
+
const client_1 = require("../cloud/client");
|
|
16
|
+
const version_1 = require("../version");
|
|
17
|
+
const skill_1 = require("../skill");
|
|
18
|
+
const bindings_1 = require("./bindings");
|
|
19
|
+
const claude_1 = require("./claude");
|
|
20
|
+
const codex_1 = require("./codex");
|
|
21
|
+
const db_1 = require("./db");
|
|
22
|
+
const leases_1 = require("./leases");
|
|
23
|
+
/**
|
|
24
|
+
* Deterministic harness-owned filesystem facts only. Never opens transcript
|
|
25
|
+
* files or walks session contents.
|
|
26
|
+
*/
|
|
27
|
+
function detectClaudeCodeHarness(home) {
|
|
28
|
+
const result = (0, claude_1.detectClaudeCodeHarness)(home);
|
|
29
|
+
return { present: result.present, harness: claude_1.claudeSourceAdapter.sourceType, evidence: result.evidence };
|
|
30
|
+
}
|
|
31
|
+
function localClaudeSourceId() {
|
|
32
|
+
return claude_1.claudeSourceAdapter.sourceId;
|
|
33
|
+
}
|
|
34
|
+
/** Ensure the local sources row exists without touching queues or files. */
|
|
35
|
+
function ensureLocalDetectedSource(db, owner, adapter) {
|
|
36
|
+
const id = adapter.sourceId;
|
|
37
|
+
return (0, db_1.tx)(db, () => {
|
|
38
|
+
(0, leases_1.assertLeaseHeld)(db, leases_1.SOURCE_LEASE, owner);
|
|
39
|
+
const existing = db.prepare("SELECT id FROM sources WHERE id = ?").get(id);
|
|
40
|
+
if (!existing) {
|
|
41
|
+
const now = (0, db_1.nowIso)();
|
|
42
|
+
db.prepare(`INSERT INTO sources (id, source_type, source_key, display_name, adapter_version, status, created_at, updated_at)
|
|
43
|
+
VALUES (?, ?, ?, ?, ?, 'detected', ?, ?)`).run(id, adapter.sourceType, adapter.sourceKey, adapter.displayName, adapter.adapterVersion, now, now);
|
|
44
|
+
}
|
|
45
|
+
return id;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function runSourceDetect(db, options) {
|
|
49
|
+
const adapter = options.adapter;
|
|
50
|
+
const detection = adapter.detect();
|
|
51
|
+
if (!detection.present) {
|
|
52
|
+
return {
|
|
53
|
+
status: "error",
|
|
54
|
+
harness: adapter.sourceType,
|
|
55
|
+
harness_present: false,
|
|
56
|
+
evidence: [],
|
|
57
|
+
local_source_id: null,
|
|
58
|
+
server_source_id: null,
|
|
59
|
+
workspace_id: null,
|
|
60
|
+
source_status: null,
|
|
61
|
+
registration: "skipped",
|
|
62
|
+
message: adapter.sourceType === claude_1.claudeSourceAdapter.sourceType
|
|
63
|
+
? "Claude Code was not detected under ~/.claude"
|
|
64
|
+
: adapter.sourceType === codex_1.CODEX_SOURCE_TYPE
|
|
65
|
+
? "Codex was not detected under ~/.codex"
|
|
66
|
+
: `${adapter.displayName} was not detected`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const localId = ensureLocalDetectedSource(db, options.leaseOwner, adapter);
|
|
70
|
+
const before = db.prepare("SELECT server_source_id, status FROM sources WHERE id = ?").get(localId);
|
|
71
|
+
let registration;
|
|
72
|
+
try {
|
|
73
|
+
registration = await (0, client_1.registerSource)({
|
|
74
|
+
sourceType: adapter.sourceType,
|
|
75
|
+
sourceKey: adapter.sourceKey,
|
|
76
|
+
displayName: adapter.displayName,
|
|
77
|
+
installationId: options.installationId,
|
|
78
|
+
adapterVersion: adapter.adapterVersion,
|
|
79
|
+
// Required for detection-only create: Cloud defaults omitted status to active.
|
|
80
|
+
status: "detected",
|
|
81
|
+
cliVersion: (0, version_1.packageVersion)(),
|
|
82
|
+
skillVersion: (0, skill_1.installedSkillVersion)(),
|
|
83
|
+
platform: process.platform,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (error instanceof index_1.NotAuthenticatedError) {
|
|
88
|
+
return {
|
|
89
|
+
status: "error",
|
|
90
|
+
harness: adapter.sourceType,
|
|
91
|
+
harness_present: true,
|
|
92
|
+
evidence: detection.evidence,
|
|
93
|
+
local_source_id: localId,
|
|
94
|
+
server_source_id: before.server_source_id,
|
|
95
|
+
workspace_id: null,
|
|
96
|
+
source_status: before.status,
|
|
97
|
+
registration: "skipped",
|
|
98
|
+
message: "not authenticated: run `dreams auth login`",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (error instanceof client_1.CloudAuthError) {
|
|
102
|
+
return {
|
|
103
|
+
status: "error",
|
|
104
|
+
harness: adapter.sourceType,
|
|
105
|
+
harness_present: true,
|
|
106
|
+
evidence: detection.evidence,
|
|
107
|
+
local_source_id: localId,
|
|
108
|
+
server_source_id: before.server_source_id,
|
|
109
|
+
workspace_id: null,
|
|
110
|
+
source_status: before.status,
|
|
111
|
+
registration: "skipped",
|
|
112
|
+
message: `authentication failed: ${error.message}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (error instanceof client_1.CloudError) {
|
|
116
|
+
return {
|
|
117
|
+
status: "error",
|
|
118
|
+
harness: adapter.sourceType,
|
|
119
|
+
harness_present: true,
|
|
120
|
+
evidence: detection.evidence,
|
|
121
|
+
local_source_id: localId,
|
|
122
|
+
server_source_id: before.server_source_id,
|
|
123
|
+
workspace_id: null,
|
|
124
|
+
source_status: before.status,
|
|
125
|
+
registration: "skipped",
|
|
126
|
+
message: `could not register source: ${error.message}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
const persisted = (0, bindings_1.persistSourceBindings)(db, localId, {
|
|
132
|
+
serverSourceId: registration.serverSourceId,
|
|
133
|
+
status: registration.status,
|
|
134
|
+
workspaceId: registration.workspaceId,
|
|
135
|
+
}, options.leaseOwner);
|
|
136
|
+
return {
|
|
137
|
+
status: "ok",
|
|
138
|
+
harness: adapter.sourceType,
|
|
139
|
+
harness_present: true,
|
|
140
|
+
evidence: detection.evidence,
|
|
141
|
+
local_source_id: localId,
|
|
142
|
+
server_source_id: persisted.serverSourceId,
|
|
143
|
+
workspace_id: persisted.workspaceId,
|
|
144
|
+
source_status: persisted.status,
|
|
145
|
+
registration: before.server_source_id ? "refreshed" : "created",
|
|
146
|
+
message: null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `dreams hooks install [--dry-run] [--source <type>] [--json]`
|
|
4
|
+
*
|
|
5
|
+
* CLI owns the mutation; the setup skill owns consent and showing the dry-run.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.commandHooksInstall = commandHooksInstall;
|
|
9
|
+
const claude_1 = require("./claude");
|
|
10
|
+
const claude_hooks_1 = require("./claude-hooks");
|
|
11
|
+
const codex_1 = require("./codex");
|
|
12
|
+
const codex_hooks_1 = require("./codex-hooks");
|
|
13
|
+
const source_adapters_1 = require("./source-adapters");
|
|
14
|
+
const skill_1 = require("../skill");
|
|
15
|
+
const managed_install_1 = require("../managed-install");
|
|
16
|
+
function emit(json, payload, human) {
|
|
17
|
+
if (json)
|
|
18
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
19
|
+
else
|
|
20
|
+
console.log(human());
|
|
21
|
+
}
|
|
22
|
+
function commandHooksInstall(options) {
|
|
23
|
+
let sources;
|
|
24
|
+
try {
|
|
25
|
+
if (options.sourceType) {
|
|
26
|
+
const adapter = (0, source_adapters_1.resolveSourceAdapter)(options.sourceType);
|
|
27
|
+
if (adapter.sourceType !== "claude-code" && adapter.sourceType !== "codex") {
|
|
28
|
+
process.stderr.write(`hooks install does not support source type ${adapter.sourceType}\n`);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
sources = [adapter.sourceType];
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
sources = ["claude-code", "codex"];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const cliPath = (0, managed_install_1.stableCliPath)();
|
|
42
|
+
const results = [];
|
|
43
|
+
const humanLines = [
|
|
44
|
+
options.dryRun ? "Dreams hooks install (dry-run — no files written)" : "Dreams hooks install",
|
|
45
|
+
"",
|
|
46
|
+
` CLI path: ${cliPath}`,
|
|
47
|
+
];
|
|
48
|
+
for (const source of sources) {
|
|
49
|
+
if (source === "claude-code") {
|
|
50
|
+
const present = (0, claude_1.detectClaudeCodeHarness)().present;
|
|
51
|
+
if (!present && options.sourceType === undefined) {
|
|
52
|
+
humanLines.push("", "Claude Code: harness not detected — skipped");
|
|
53
|
+
results.push({ source: "claude-code", status: "skipped", reason: "harness_not_detected" });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const plan = (0, claude_hooks_1.planClaudeHooksInstall)({ cliPath });
|
|
57
|
+
if (!options.dryRun)
|
|
58
|
+
(0, claude_hooks_1.installClaudeHooks)({ cliPath });
|
|
59
|
+
results.push({
|
|
60
|
+
source: "claude-code",
|
|
61
|
+
status: options.dryRun ? "dry_run" : plan.changed ? "updated" : "unchanged",
|
|
62
|
+
settings_path: plan.settings_path,
|
|
63
|
+
changed: plan.changed,
|
|
64
|
+
session_start_command: plan.session_start_command,
|
|
65
|
+
stop_command: plan.stop_command,
|
|
66
|
+
next_contents: options.dryRun ? plan.next_contents : undefined,
|
|
67
|
+
});
|
|
68
|
+
humanLines.push("", `Claude Code (${options.dryRun ? "dry-run" : plan.changed ? "updated" : "unchanged"}):`, ` Settings: ${plan.settings_path}`, ` SessionStart → wake: ${plan.session_start_command}`, ` Stop → sync: ${plan.stop_command}`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const present = (0, codex_1.detectCodexHarness)().present;
|
|
72
|
+
if (!present && options.sourceType === undefined) {
|
|
73
|
+
humanLines.push("", "Codex: harness not detected — skipped");
|
|
74
|
+
results.push({ source: "codex", status: "skipped", reason: "harness_not_detected" });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const hookScriptPath = (0, skill_1.codexSyncHookScriptPath)();
|
|
78
|
+
const plan = (0, codex_hooks_1.planCodexHooksInstall)({ cliPath, hookScriptPath });
|
|
79
|
+
if (!options.dryRun)
|
|
80
|
+
(0, codex_hooks_1.installCodexHooks)({ cliPath, hookScriptPath });
|
|
81
|
+
results.push({
|
|
82
|
+
source: "codex",
|
|
83
|
+
status: options.dryRun ? "dry_run" : plan.hooks_changed || plan.config_changed ? "updated" : "unchanged",
|
|
84
|
+
hooks_path: plan.hooks_path,
|
|
85
|
+
config_path: plan.config_path,
|
|
86
|
+
hooks_changed: plan.hooks_changed,
|
|
87
|
+
config_changed: plan.config_changed,
|
|
88
|
+
session_start_command: plan.session_start_command,
|
|
89
|
+
stop_command: plan.stop_command,
|
|
90
|
+
next_hooks_contents: options.dryRun ? plan.next_hooks_contents : undefined,
|
|
91
|
+
next_config_contents: options.dryRun ? plan.next_config_contents : undefined,
|
|
92
|
+
});
|
|
93
|
+
humanLines.push("", `Codex (${options.dryRun ? "dry-run" : plan.hooks_changed || plan.config_changed ? "updated" : "unchanged"}):`, ` Hooks: ${plan.hooks_path}`, ` Config: ${plan.config_path}`, ` SessionStart → wake: ${plan.session_start_command}`, ` Stop → sync: ${plan.stop_command}`, options.dryRun ? " (Also ensures [features].hooks = true in config.toml)" : "");
|
|
94
|
+
}
|
|
95
|
+
emit(options.json, {
|
|
96
|
+
status: "ok",
|
|
97
|
+
dry_run: options.dryRun,
|
|
98
|
+
cli_path: cliPath,
|
|
99
|
+
results,
|
|
100
|
+
}, () => humanLines.filter((line) => line !== "").join("\n"));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
package/dist/local/leases.js
CHANGED
|
@@ -42,7 +42,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
};
|
|
43
43
|
})();
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.LeaseLostError = exports.SOURCE_LEASE = void 0;
|
|
45
|
+
exports.LeaseLostError = exports.SYNC_WORKER_LEASE = exports.SOURCE_LEASE = void 0;
|
|
46
46
|
exports.assertLeaseHeld = assertLeaseHeld;
|
|
47
47
|
exports.renewLease = renewLease;
|
|
48
48
|
exports.acquireLease = acquireLease;
|
|
@@ -57,6 +57,8 @@ const DEFAULT_TTL_MS = 30_000;
|
|
|
57
57
|
* against this one lease so they never interleave writes.
|
|
58
58
|
*/
|
|
59
59
|
exports.SOURCE_LEASE = "source";
|
|
60
|
+
/** Election lease for the detached sync worker so only one worker runs at a time. */
|
|
61
|
+
exports.SYNC_WORKER_LEASE = "sync-worker";
|
|
60
62
|
/** Thrown when a write is attempted after the lease was lost/reclaimed. */
|
|
61
63
|
class LeaseLostError extends Error {
|
|
62
64
|
constructor(name) {
|