@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.
@@ -3,7 +3,8 @@
3
3
  * `dreams source detect` — register a harness as a detected Cloud source
4
4
  * without reading transcripts, approving roots, scanning, or uploading.
5
5
  *
6
- * Today CLI defaults to the Claude adapter ([LET-10313]).
6
+ * CLI defaults to the Claude adapter; pass an explicit adapter for Codex
7
+ * (`dreams source detect --source codex`) ([LET-10313] / [LET-10315]).
7
8
  */
8
9
  Object.defineProperty(exports, "__esModule", { value: true });
9
10
  exports.detectClaudeCodeHarness = detectClaudeCodeHarness;
@@ -16,6 +17,7 @@ const version_1 = require("../version");
16
17
  const skill_1 = require("../skill");
17
18
  const bindings_1 = require("./bindings");
18
19
  const claude_1 = require("./claude");
20
+ const codex_1 = require("./codex");
19
21
  const db_1 = require("./db");
20
22
  const leases_1 = require("./leases");
21
23
  /**
@@ -59,7 +61,9 @@ async function runSourceDetect(db, options) {
59
61
  registration: "skipped",
60
62
  message: adapter.sourceType === claude_1.claudeSourceAdapter.sourceType
61
63
  ? "Claude Code was not detected under ~/.claude"
62
- : `${adapter.displayName} was not detected`,
64
+ : adapter.sourceType === codex_1.CODEX_SOURCE_TYPE
65
+ ? "Codex was not detected under ~/.codex"
66
+ : `${adapter.displayName} was not detected`,
63
67
  };
64
68
  }
65
69
  const localId = ensureLocalDetectedSource(db, options.leaseOwner, adapter);
@@ -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
+ }
@@ -11,6 +11,7 @@ exports.registerSourceAdapter = registerSourceAdapter;
11
11
  exports.getSourceAdapterById = getSourceAdapterById;
12
12
  exports.getSourceAdapterByType = getSourceAdapterByType;
13
13
  exports.defaultSourceAdapter = defaultSourceAdapter;
14
+ exports.resolveSourceAdapter = resolveSourceAdapter;
14
15
  exports.listSourceAdapters = listSourceAdapters;
15
16
  const claude_1 = require("./claude");
16
17
  const codex_1 = require("./codex");
@@ -51,6 +52,18 @@ function defaultSourceAdapter() {
51
52
  throw new Error("no default source adapter registered");
52
53
  return getSourceAdapterById(defaultSourceId);
53
54
  }
55
+ /**
56
+ * Resolve `--source <type>` at command boundaries (stdin never selects source).
57
+ * Omitted / undefined preserves the Claude default; empty/unknown fail closed.
58
+ */
59
+ function resolveSourceAdapter(sourceType) {
60
+ if (sourceType === undefined)
61
+ return defaultSourceAdapter();
62
+ const trimmed = sourceType.trim();
63
+ if (!trimmed)
64
+ throw new Error("unknown source type: (empty)");
65
+ return getSourceAdapterByType(trimmed);
66
+ }
54
67
  function listSourceAdapters() {
55
68
  return [...bySourceId.values()];
56
69
  }
@@ -10,7 +10,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.DEFAULT_UPLOAD_ROW_BUDGET = exports.MAX_TRAILING_PASSES = exports.RETRY_BACKOFF_MS = exports.CADENCE_MS = exports.DEFAULT_SOURCE_ID = void 0;
11
11
  exports.uploadRowBudget = uploadRowBudget;
12
12
  exports.readTrigger = readTrigger;
13
+ exports.readActiveSourceId = readActiveSourceId;
13
14
  exports.readRuntime = readRuntime;
15
+ exports.peekRuntime = peekRuntime;
16
+ exports.sourceExists = sourceExists;
14
17
  exports.recordSyncRequest = recordSyncRequest;
15
18
  exports.setPaused = setPaused;
16
19
  exports.isPaused = isPaused;
@@ -20,6 +23,7 @@ exports.completeSyncPass = completeSyncPass;
20
23
  exports.markRuntimeDegraded = markRuntimeDegraded;
21
24
  exports.earliestUploadRetryAt = earliestUploadRetryAt;
22
25
  exports.markRuntimeRunning = markRuntimeRunning;
26
+ exports.clearActiveSourceId = clearActiveSourceId;
23
27
  exports.normalizeStaleRunning = normalizeStaleRunning;
24
28
  exports.triggerPending = triggerPending;
25
29
  exports.sourceIsRunnable = sourceIsRunnable;
@@ -102,31 +106,60 @@ function globalWorkerState(db) {
102
106
  * Per-source schedule view + global pause. Cadence/retry fields come from
103
107
  * `sync_triggers`; `paused` and global `running` come from `sync_runtime`.
104
108
  */
105
- function readRuntime(db, sourceId = exports.DEFAULT_SOURCE_ID) {
109
+ function runtimeFromTriggerRow(db, sourceId, row) {
106
110
  ensureRuntime(db);
107
- ensureTrigger(db, sourceId);
108
111
  const paused = globalPaused(db);
109
- const globalState = globalWorkerState(db);
110
- const row = db
111
- .prepare(`SELECT state, last_attempt_at, last_success_at, last_error_at, last_error_code, last_error_message, next_due_at
112
- FROM sync_triggers WHERE source_id = ?`)
113
- .get(sourceId);
114
- let state = row.state || "idle";
112
+ let state = row?.state || "idle";
115
113
  if (paused)
116
114
  state = "paused";
117
- else if (globalState === "running")
115
+ else if (readActiveSourceId(db) === sourceId && globalWorkerState(db) === "running") {
116
+ // Only the source that owns the singleton worker is "running".
118
117
  state = "running";
118
+ }
119
119
  return {
120
120
  paused,
121
121
  state,
122
- last_attempt_at: row.last_attempt_at,
123
- last_success_at: row.last_success_at,
124
- last_error_at: row.last_error_at,
125
- last_error_code: row.last_error_code ?? null,
126
- last_error_message: row.last_error_message,
127
- next_due_at: row.next_due_at,
122
+ last_attempt_at: row?.last_attempt_at ?? null,
123
+ last_success_at: row?.last_success_at ?? null,
124
+ last_error_at: row?.last_error_at ?? null,
125
+ last_error_code: row?.last_error_code ?? null,
126
+ last_error_message: row?.last_error_message ?? null,
127
+ next_due_at: row?.next_due_at ?? null,
128
128
  };
129
129
  }
130
+ function readActiveSourceId(db) {
131
+ ensureRuntime(db);
132
+ try {
133
+ const row = db.prepare("SELECT active_source_id FROM sync_runtime WHERE id = 1").get();
134
+ return row?.active_source_id ?? null;
135
+ }
136
+ catch {
137
+ // Pre-migration databases in the middle of openDb won't hit this path.
138
+ return null;
139
+ }
140
+ }
141
+ function readRuntime(db, sourceId = exports.DEFAULT_SOURCE_ID) {
142
+ ensureTrigger(db, sourceId);
143
+ const row = db
144
+ .prepare(`SELECT state, last_attempt_at, last_success_at, last_error_at, last_error_code, last_error_message, next_due_at
145
+ FROM sync_triggers WHERE source_id = ?`)
146
+ .get(sourceId);
147
+ return runtimeFromTriggerRow(db, sourceId, row);
148
+ }
149
+ /**
150
+ * Status-safe runtime peek: never creates source/trigger rows. Missing sources
151
+ * report as idle/not configured ([LET-10317]).
152
+ */
153
+ function peekRuntime(db, sourceId) {
154
+ const row = db
155
+ .prepare(`SELECT state, last_attempt_at, last_success_at, last_error_at, last_error_code, last_error_message, next_due_at
156
+ FROM sync_triggers WHERE source_id = ?`)
157
+ .get(sourceId);
158
+ return runtimeFromTriggerRow(db, sourceId, row);
159
+ }
160
+ function sourceExists(db, sourceId) {
161
+ return Boolean(db.prepare("SELECT id FROM sources WHERE id = ?").get(sourceId));
162
+ }
130
163
  /** Atomic pause check + trigger record. Returns null when paused. */
131
164
  function recordSyncRequest(db, options = {}) {
132
165
  const sourceId = options.sourceId ?? exports.DEFAULT_SOURCE_ID;
@@ -272,10 +305,22 @@ function earliestUploadRetryAt(db, sourceId = exports.DEFAULT_SOURCE_ID) {
272
305
  .get(sourceId);
273
306
  return row?.next_at ?? null;
274
307
  }
275
- function markRuntimeRunning(db) {
308
+ function markRuntimeRunning(db, sourceId = exports.DEFAULT_SOURCE_ID) {
276
309
  (0, db_1.tx)(db, () => {
277
310
  ensureRuntime(db);
278
- db.prepare(`UPDATE sync_runtime SET state = CASE WHEN paused = 1 THEN 'paused' ELSE 'running' END, last_attempt_at = ?, updated_at = ? WHERE id = 1`).run((0, db_1.nowIso)(), (0, db_1.nowIso)());
311
+ db.prepare(`UPDATE sync_runtime
312
+ SET state = CASE WHEN paused = 1 THEN 'paused' ELSE 'running' END,
313
+ active_source_id = ?,
314
+ last_attempt_at = ?,
315
+ updated_at = ?
316
+ WHERE id = 1`).run(sourceId, (0, db_1.nowIso)(), (0, db_1.nowIso)());
317
+ });
318
+ }
319
+ /** Clear singleton-worker source ownership (call on worker shutdown after lease release). */
320
+ function clearActiveSourceId(db) {
321
+ (0, db_1.tx)(db, () => {
322
+ ensureRuntime(db);
323
+ db.prepare(`UPDATE sync_runtime SET active_source_id = NULL, updated_at = ? WHERE id = 1`).run((0, db_1.nowIso)());
279
324
  });
280
325
  }
281
326
  /** Normalize stale running state when no worker lease is held. */
@@ -285,11 +330,17 @@ function normalizeStaleRunning(db, workerLeaseHeld) {
285
330
  (0, db_1.tx)(db, () => {
286
331
  ensureRuntime(db);
287
332
  const row = db.prepare("SELECT state, paused FROM sync_runtime WHERE id = 1").get();
288
- if (row.state !== "running")
289
- return;
290
- db.prepare(`UPDATE sync_runtime
291
- SET state = CASE WHEN paused = 1 THEN 'paused' ELSE 'idle' END, updated_at = ?
292
- WHERE id = 1`).run((0, db_1.nowIso)());
333
+ if (row.state === "running") {
334
+ db.prepare(`UPDATE sync_runtime
335
+ SET state = CASE WHEN paused = 1 THEN 'paused' ELSE 'idle' END,
336
+ active_source_id = NULL,
337
+ updated_at = ?
338
+ WHERE id = 1`).run((0, db_1.nowIso)());
339
+ }
340
+ else {
341
+ // Pass completion may already have set idle while ownership lingered.
342
+ db.prepare(`UPDATE sync_runtime SET active_source_id = NULL, updated_at = ? WHERE id = 1`).run((0, db_1.nowIso)());
343
+ }
293
344
  });
294
345
  }
295
346
  function triggerPending(db, sourceId = exports.DEFAULT_SOURCE_ID) {