@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/sync.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Hook-safe `dreams sync` ([LET-10237] / [LET-10240]).
|
|
3
|
+
* Hook-safe `dreams sync` / `dreams wake` ([LET-10237] / [LET-10240]).
|
|
4
|
+
*
|
|
5
|
+
* - `dreams sync` (Stop / live turn): records a durable trigger (optional
|
|
6
|
+
* session_id from stdin) and may spawn a detached worker.
|
|
7
|
+
* - `dreams wake` (SessionStart): quiet housekeeping (managed CLI + skill
|
|
8
|
+
* repair) then force-kicks the worker for pending/backfill drain — no stdin
|
|
9
|
+
* session hint.
|
|
4
10
|
*
|
|
5
|
-
* Default invocation records a durable trigger and may spawn a detached worker.
|
|
6
11
|
* The hook parent always exits 0. The worker performs live scan + live-first
|
|
7
12
|
* upload, then — only when an active backfill policy already exists and upload
|
|
8
13
|
* budget remains — one backfill continuation slice + remaining-budget upload.
|
|
@@ -51,6 +56,7 @@ exports.commandSyncPause = commandSyncPause;
|
|
|
51
56
|
exports.commandSyncResume = commandSyncResume;
|
|
52
57
|
exports.commandSyncStatus = commandSyncStatus;
|
|
53
58
|
exports.commandSyncWorker = commandSyncWorker;
|
|
59
|
+
exports.commandWake = commandWake;
|
|
54
60
|
exports.commandSync = commandSync;
|
|
55
61
|
const node_child_process_1 = require("node:child_process");
|
|
56
62
|
const path = __importStar(require("node:path"));
|
|
@@ -61,17 +67,15 @@ const leases_1 = require("./local/leases");
|
|
|
61
67
|
const scan_1 = require("./local/scan");
|
|
62
68
|
const state_lock_1 = require("./local/state-lock");
|
|
63
69
|
const sync_control_1 = require("./local/sync-control");
|
|
70
|
+
const codex_1 = require("./local/codex");
|
|
64
71
|
const source_adapters_1 = require("./local/source-adapters");
|
|
72
|
+
const managed_install_1 = require("./managed-install");
|
|
73
|
+
const skill_1 = require("./skill");
|
|
65
74
|
const store_1 = require("./store");
|
|
66
75
|
const STDIN_LIMIT_BYTES = 64 * 1024;
|
|
67
76
|
/** Resolve `--source <type>` at the sync command boundary (stdin never selects source). */
|
|
68
77
|
function resolveSyncAdapter(sourceType) {
|
|
69
|
-
|
|
70
|
-
return (0, source_adapters_1.defaultSourceAdapter)();
|
|
71
|
-
const trimmed = sourceType.trim();
|
|
72
|
-
if (!trimmed)
|
|
73
|
-
throw new Error("unknown source type: (empty)");
|
|
74
|
-
return (0, source_adapters_1.getSourceAdapterByType)(trimmed);
|
|
78
|
+
return (0, source_adapters_1.resolveSourceAdapter)(sourceType);
|
|
75
79
|
}
|
|
76
80
|
function defaultWorkerEntry() {
|
|
77
81
|
const candidate = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
|
@@ -215,7 +219,7 @@ async function commandSyncSchedule(options) {
|
|
|
215
219
|
const adapter = options.adapter;
|
|
216
220
|
const sourceId = adapter.sourceId;
|
|
217
221
|
try {
|
|
218
|
-
const sessionHint = await readSessionHintFromStdin(adapter);
|
|
222
|
+
const sessionHint = options.skipStdin ? null : await readSessionHintFromStdin(adapter);
|
|
219
223
|
const { db } = openInstallationDb();
|
|
220
224
|
try {
|
|
221
225
|
const leaseRow = db.prepare("SELECT owner, expires_at FROM leases WHERE name = ?").get(leases_1.SYNC_WORKER_LEASE);
|
|
@@ -333,28 +337,78 @@ async function commandSyncResume(json) {
|
|
|
333
337
|
(0, db_1.closeDb)();
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
|
-
function commandSyncStatus(json) {
|
|
340
|
+
function commandSyncStatus(json, sourceType) {
|
|
341
|
+
let adapter;
|
|
342
|
+
try {
|
|
343
|
+
adapter = resolveSyncAdapter(sourceType);
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
347
|
+
if (json) {
|
|
348
|
+
console.log(JSON.stringify({ status: "error", schema_version: 1, code: "invalid_argument", message }));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
process.stderr.write(`${message}\n`);
|
|
352
|
+
}
|
|
353
|
+
return 1;
|
|
354
|
+
}
|
|
355
|
+
const harnessPresent = adapter.detect().present;
|
|
337
356
|
const { db } = openInstallationDb();
|
|
338
357
|
try {
|
|
358
|
+
const configured = (0, sync_control_1.sourceExists)(db, adapter.sourceId);
|
|
339
359
|
const leaseRow = db.prepare("SELECT expires_at FROM leases WHERE name = ?").get(leases_1.SYNC_WORKER_LEASE);
|
|
340
|
-
const
|
|
341
|
-
(0, sync_control_1.normalizeStaleRunning)(db,
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
360
|
+
const workerLeaseHeld = Boolean(leaseRow && leaseRow.expires_at > Date.now());
|
|
361
|
+
(0, sync_control_1.normalizeStaleRunning)(db, workerLeaseHeld);
|
|
362
|
+
// Peek only — status must not mint source/trigger rows for unconfigured adapters.
|
|
363
|
+
const runtime = (0, sync_control_1.peekRuntime)(db, adapter.sourceId);
|
|
364
|
+
const trigger = (0, sync_control_1.observeTrigger)(db, adapter.sourceId);
|
|
365
|
+
const pending = configured && (trigger.requested_gen > trigger.processed_gen || trigger.forced);
|
|
366
|
+
const workerActiveForSource = workerLeaseHeld && configured && (0, sync_control_1.readActiveSourceId)(db) === adapter.sourceId;
|
|
367
|
+
const effective = configured
|
|
368
|
+
? (0, sync_control_1.effectiveSyncState)({
|
|
369
|
+
runtime,
|
|
370
|
+
pending,
|
|
371
|
+
forced: trigger.forced,
|
|
372
|
+
workerActive: workerActiveForSource,
|
|
373
|
+
})
|
|
374
|
+
: runtime.paused
|
|
375
|
+
? "paused"
|
|
376
|
+
: "idle";
|
|
377
|
+
const discovery = adapter.sourceType === codex_1.CODEX_SOURCE_TYPE
|
|
378
|
+
? (() => {
|
|
379
|
+
const report = (0, codex_1.inspectCodexDiscovery)();
|
|
380
|
+
return {
|
|
381
|
+
status: report.status === "warning" ? "warning" : report.status === "ok" ? "ok" : report.status,
|
|
382
|
+
harness_present: report.harness_present,
|
|
383
|
+
sessions_dir_present: report.sessions_dir_present,
|
|
384
|
+
candidates: report.candidates,
|
|
385
|
+
supported: report.supported,
|
|
386
|
+
unsupported: report.unsupported,
|
|
387
|
+
excluded_subagent: report.excluded_subagent,
|
|
388
|
+
by_reason: report.by_reason,
|
|
389
|
+
examples: report.examples,
|
|
390
|
+
message: report.message,
|
|
391
|
+
};
|
|
392
|
+
})()
|
|
393
|
+
: {
|
|
394
|
+
status: harnessPresent ? "ok" : "absent",
|
|
395
|
+
harness_present: harnessPresent,
|
|
396
|
+
message: harnessPresent ? null : `${adapter.displayName} was not detected`,
|
|
397
|
+
};
|
|
351
398
|
const payload = {
|
|
352
399
|
schema_version: 1,
|
|
353
400
|
status: "ok",
|
|
401
|
+
source: adapter.sourceType,
|
|
402
|
+
source_id: adapter.sourceId,
|
|
403
|
+
source_configured: configured,
|
|
404
|
+
source_present: harnessPresent,
|
|
354
405
|
sync: {
|
|
355
406
|
...runtime,
|
|
356
407
|
effective_state: effective,
|
|
357
|
-
worker_active:
|
|
408
|
+
worker_active: workerActiveForSource,
|
|
409
|
+
worker_lease_held: workerLeaseHeld,
|
|
410
|
+
// Stale ownership must not outlive the lease (belt-and-suspenders with completeSyncPass).
|
|
411
|
+
active_source_id: workerLeaseHeld ? (0, sync_control_1.readActiveSourceId)(db) : null,
|
|
358
412
|
pending,
|
|
359
413
|
requested_gen: trigger.requested_gen,
|
|
360
414
|
processed_gen: trigger.processed_gen,
|
|
@@ -362,19 +416,24 @@ function commandSyncStatus(json) {
|
|
|
362
416
|
session_hints: trigger.session_hints,
|
|
363
417
|
cadence_ms: sync_control_1.CADENCE_MS,
|
|
364
418
|
},
|
|
419
|
+
discovery,
|
|
365
420
|
};
|
|
366
421
|
emit(json, payload, () => [
|
|
367
422
|
"Dreams sync status",
|
|
368
423
|
"",
|
|
424
|
+
` Source: ${adapter.sourceType} (${adapter.sourceId})${configured ? "" : " — not configured"}`,
|
|
369
425
|
` Effective: ${effective}`,
|
|
370
426
|
` Runtime: ${runtime.state}`,
|
|
371
427
|
` Paused: ${runtime.paused ? "yes" : "no"}`,
|
|
372
428
|
` Pending: ${pending ? "yes" : "no"}`,
|
|
373
|
-
` Worker: ${
|
|
429
|
+
` Worker: ${workerActiveForSource ? "active for this source" : workerLeaseHeld ? "active for another source" : "idle"}`,
|
|
374
430
|
` Next due: ${runtime.next_due_at ?? "(immediately)"}`,
|
|
375
431
|
` Last success:${runtime.last_success_at ? ` ${runtime.last_success_at}` : " (none)"}`,
|
|
376
432
|
` Last error: ${runtime.last_error_code ? `${runtime.last_error_code}: ${runtime.last_error_message}` : " (none)"}`,
|
|
377
|
-
|
|
433
|
+
discovery.message ? ` Discovery: ${discovery.message}` : "",
|
|
434
|
+
]
|
|
435
|
+
.filter((line) => line !== "")
|
|
436
|
+
.join("\n"));
|
|
378
437
|
return 0;
|
|
379
438
|
}
|
|
380
439
|
finally {
|
|
@@ -594,7 +653,7 @@ async function commandSyncWorker(options) {
|
|
|
594
653
|
console.log(JSON.stringify({ status: "paused", schema_version: 1, source: adapter.sourceType }));
|
|
595
654
|
return 0;
|
|
596
655
|
}
|
|
597
|
-
(0, sync_control_1.markRuntimeRunning)(db);
|
|
656
|
+
(0, sync_control_1.markRuntimeRunning)(db, sourceId);
|
|
598
657
|
while (passes < sync_control_1.MAX_TRAILING_PASSES) {
|
|
599
658
|
if ((0, sync_control_1.isPaused)(db))
|
|
600
659
|
break;
|
|
@@ -704,8 +763,16 @@ async function commandSyncWorker(options) {
|
|
|
704
763
|
catch {
|
|
705
764
|
// test hooks must not break cleanup
|
|
706
765
|
}
|
|
707
|
-
if (workerOwner)
|
|
766
|
+
if (workerOwner) {
|
|
708
767
|
(0, leases_1.releaseLease)(db, leases_1.SYNC_WORKER_LEASE, workerOwner);
|
|
768
|
+
try {
|
|
769
|
+
// Ownership spans trailing passes; clear only when this worker exits.
|
|
770
|
+
(0, sync_control_1.clearActiveSourceId)(db);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
// best effort
|
|
774
|
+
}
|
|
775
|
+
}
|
|
709
776
|
(0, db_1.closeDb)();
|
|
710
777
|
// Release the local-state lock before the final pending check so a hook that
|
|
711
778
|
// spawned during shutdown cannot observe busy-on-lock and exit while we also
|
|
@@ -734,6 +801,103 @@ async function commandSyncWorker(options) {
|
|
|
734
801
|
spawnDetachedWorker(true, successorType);
|
|
735
802
|
}
|
|
736
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* SessionStart entrypoint: repair local CLI/skill quietly, then force-schedule
|
|
806
|
+
* the sync worker for pending live/backfill drain (no session stdin hint).
|
|
807
|
+
* Always exits 0 so harness hooks stay non-blocking.
|
|
808
|
+
*/
|
|
809
|
+
async function commandWake(options) {
|
|
810
|
+
let adapter;
|
|
811
|
+
try {
|
|
812
|
+
adapter = resolveSyncAdapter(options.sourceType);
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
// Still exit 0 for hook safety; surface the error in JSON/human when possible.
|
|
816
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
817
|
+
try {
|
|
818
|
+
emit(options.json, { status: "coalesced", schema_version: 1, command: "wake", worker: "schedule_error", message }, () => `Wake could not resolve source: ${message}`);
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
// ignore
|
|
822
|
+
}
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
const housekeeping = { managed_install: null, skill: null, error: null };
|
|
826
|
+
try {
|
|
827
|
+
const managed = (0, managed_install_1.ensureManagedInstall)();
|
|
828
|
+
housekeeping.managed_install = managed.status;
|
|
829
|
+
housekeeping.skill = (0, skill_1.installSkill)();
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
housekeeping.error = error instanceof Error ? error.message : String(error);
|
|
833
|
+
}
|
|
834
|
+
// Reuse the sync scheduler with force + no stdin. Emit a wake-shaped envelope
|
|
835
|
+
// by wrapping schedule output when JSON is requested.
|
|
836
|
+
if (!options.json) {
|
|
837
|
+
return commandSyncSchedule({ json: false, force: true, adapter, skipStdin: true });
|
|
838
|
+
}
|
|
839
|
+
// Capture schedule JSON by temporarily patching console.log is brittle; call
|
|
840
|
+
// the schedule path with json:true — it already prints. Prepend housekeeping
|
|
841
|
+
// by running schedule logic inline via a thin re-emit is hard. Instead: run
|
|
842
|
+
// schedule with json false internals... Simplest: run schedule with json true
|
|
843
|
+
// after printing nothing, and document housekeeping in a second object — agents
|
|
844
|
+
// prefer one object. So duplicate the force-schedule emit with housekeeping.
|
|
845
|
+
try {
|
|
846
|
+
const { db } = openInstallationDb();
|
|
847
|
+
try {
|
|
848
|
+
const leaseRow = db.prepare("SELECT owner, expires_at FROM leases WHERE name = ?").get(leases_1.SYNC_WORKER_LEASE);
|
|
849
|
+
const workerActive = Boolean(leaseRow && leaseRow.expires_at > Date.now());
|
|
850
|
+
(0, sync_control_1.normalizeStaleRunning)(db, workerActive);
|
|
851
|
+
const recorded = (0, sync_control_1.recordSyncRequest)(db, { forced: true, sessionHint: null, sourceId: adapter.sourceId });
|
|
852
|
+
if (recorded.paused) {
|
|
853
|
+
emit(true, { status: "paused", schema_version: 1, command: "wake", source: adapter.sourceType, housekeeping }, () => "Wake: sync paused.");
|
|
854
|
+
return 0;
|
|
855
|
+
}
|
|
856
|
+
if (workerActive) {
|
|
857
|
+
emit(true, {
|
|
858
|
+
status: "coalesced",
|
|
859
|
+
schema_version: 1,
|
|
860
|
+
command: "wake",
|
|
861
|
+
source: adapter.sourceType,
|
|
862
|
+
trigger_generation: recorded.generation,
|
|
863
|
+
forced: true,
|
|
864
|
+
housekeeping,
|
|
865
|
+
}, () => "Wake coalesced into running worker.");
|
|
866
|
+
return 0;
|
|
867
|
+
}
|
|
868
|
+
const spawned = spawnDetachedWorker(true, adapter.sourceType);
|
|
869
|
+
if (!spawned) {
|
|
870
|
+
(0, sync_control_1.markRuntimeDegraded)(db, "spawn_failed", "worker could not be detached; trigger left pending for next wake", null, adapter.sourceId);
|
|
871
|
+
}
|
|
872
|
+
emit(true, {
|
|
873
|
+
status: spawned ? "scheduled" : "coalesced",
|
|
874
|
+
schema_version: 1,
|
|
875
|
+
command: "wake",
|
|
876
|
+
source: adapter.sourceType,
|
|
877
|
+
trigger_generation: recorded.generation,
|
|
878
|
+
forced: true,
|
|
879
|
+
worker: spawned ? "spawned" : "spawn_failed",
|
|
880
|
+
housekeeping,
|
|
881
|
+
}, () => (spawned ? "Wake scheduled sync worker." : "Wake recorded; worker spawn failed."));
|
|
882
|
+
return 0;
|
|
883
|
+
}
|
|
884
|
+
finally {
|
|
885
|
+
(0, db_1.closeDb)();
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
emit(true, {
|
|
890
|
+
status: "coalesced",
|
|
891
|
+
schema_version: 1,
|
|
892
|
+
command: "wake",
|
|
893
|
+
source: adapter.sourceType,
|
|
894
|
+
worker: "schedule_error",
|
|
895
|
+
message: error instanceof Error ? error.message : String(error),
|
|
896
|
+
housekeeping,
|
|
897
|
+
}, () => "Wake could not fully schedule; will retry later.");
|
|
898
|
+
return 0;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
737
901
|
async function commandSync(subcommand, options) {
|
|
738
902
|
let adapter;
|
|
739
903
|
try {
|
|
@@ -778,11 +942,7 @@ async function commandSync(subcommand, options) {
|
|
|
778
942
|
process.stderr.write(`--force is not valid with sync status\n`);
|
|
779
943
|
return 1;
|
|
780
944
|
}
|
|
781
|
-
|
|
782
|
-
process.stderr.write(`--source is not valid with sync status\n`);
|
|
783
|
-
return 1;
|
|
784
|
-
}
|
|
785
|
-
return commandSyncStatus(options.json);
|
|
945
|
+
return commandSyncStatus(options.json, options.sourceType);
|
|
786
946
|
default:
|
|
787
947
|
process.stderr.write(`Unknown sync subcommand: ${subcommand} — expected pause, resume, or status\n`);
|
|
788
948
|
return 1;
|