@inetafrica/open-claudia 2.6.40 → 2.6.41
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/core/dream.js +41 -16
- package/package.json +1 -1
- package/test-pack-nesting.js +15 -0
package/core/dream.js
CHANGED
|
@@ -41,6 +41,16 @@ const DREAM_MODEL = pickDreamModel();
|
|
|
41
41
|
// reasoning effort by default. Overridable via DREAM_EFFORT (low|medium|high|xhigh|max).
|
|
42
42
|
const DREAM_EFFORT = process.env.DREAM_EFFORT || "max";
|
|
43
43
|
const DREAM_CRON = process.env.DREAM_CRON || "0 4 * * *";
|
|
44
|
+
// Consolidation model timeout. The model weighs the WHOLE corpus in one pass,
|
|
45
|
+
// so a fixed budget that was fine at 30 packs starves at 130 (the timeout that
|
|
46
|
+
// motivated this). Floor 20m, +6s per pack, capped at 40m; DREAM_TIMEOUT_MS
|
|
47
|
+
// (ms) overrides entirely. Even if it still times out, the deterministic phases
|
|
48
|
+
// (prefix nesting, graph tend) run regardless — the model pass is best-effort.
|
|
49
|
+
function dreamTimeoutMs(packCount = 0) {
|
|
50
|
+
const override = Number(process.env.DREAM_TIMEOUT_MS);
|
|
51
|
+
if (override > 0) return override;
|
|
52
|
+
return Math.min(20 * 60 * 1000 + packCount * 6000, 40 * 60 * 1000);
|
|
53
|
+
}
|
|
44
54
|
const MAX_PACK_CHARS = 2500;
|
|
45
55
|
const MAX_ENTITY_CHARS = 900;
|
|
46
56
|
const LIMITS = {
|
|
@@ -277,7 +287,7 @@ function applyDream(decision, backupRoot) {
|
|
|
277
287
|
const lines = [];
|
|
278
288
|
const gone = new Set(); // dirs/slugs removed this run
|
|
279
289
|
|
|
280
|
-
for (const m of decision.merges) {
|
|
290
|
+
for (const m of decision.merges || []) {
|
|
281
291
|
try {
|
|
282
292
|
const into = m?.into && packs.readPack(m.into);
|
|
283
293
|
const from = [].concat(m?.from || []).filter((d) => d && d !== m.into && !gone.has(d) && packs.readPack(d));
|
|
@@ -312,7 +322,7 @@ function applyDream(decision, backupRoot) {
|
|
|
312
322
|
} catch (e) { console.warn(`[dream] merge failed: ${e.message}`); }
|
|
313
323
|
}
|
|
314
324
|
|
|
315
|
-
for (const u of decision.umbrellas) {
|
|
325
|
+
for (const u of decision.umbrellas || []) {
|
|
316
326
|
try {
|
|
317
327
|
const dir = packs.slugify(u?.dir || u?.name);
|
|
318
328
|
if (!dir || gone.has(dir)) continue;
|
|
@@ -345,7 +355,7 @@ function applyDream(decision, backupRoot) {
|
|
|
345
355
|
} catch (e) { console.warn(`[dream] umbrella failed: ${e.message}`); }
|
|
346
356
|
}
|
|
347
357
|
|
|
348
|
-
for (const p of decision.parents) {
|
|
358
|
+
for (const p of decision.parents || []) {
|
|
349
359
|
try {
|
|
350
360
|
const child = p?.pack && !gone.has(p.pack) && packs.readPack(p.pack);
|
|
351
361
|
if (!child || !p.parent || gone.has(p.parent) || !packs.readPack(p.parent)) continue;
|
|
@@ -358,7 +368,7 @@ function applyDream(decision, backupRoot) {
|
|
|
358
368
|
} catch (e) { console.warn(`[dream] parent failed: ${e.message}`); }
|
|
359
369
|
}
|
|
360
370
|
|
|
361
|
-
for (const r of decision.retag) {
|
|
371
|
+
for (const r of decision.retag || []) {
|
|
362
372
|
try {
|
|
363
373
|
if (!r?.pack || gone.has(r.pack) || !packs.readPack(r.pack)) continue;
|
|
364
374
|
const desc = typeof r.description === "string" ? r.description.trim() : "";
|
|
@@ -374,7 +384,7 @@ function applyDream(decision, backupRoot) {
|
|
|
374
384
|
} catch (e) { console.warn(`[dream] retag failed: ${e.message}`); }
|
|
375
385
|
}
|
|
376
386
|
|
|
377
|
-
for (const em of decision.entity_merges) {
|
|
387
|
+
for (const em of decision.entity_merges || []) {
|
|
378
388
|
try {
|
|
379
389
|
const into = em?.into && entities.readEntity(em.into);
|
|
380
390
|
const from = [].concat(em?.from || []).filter((s) => s && s !== em.into && !gone.has(s) && entities.readEntity(s));
|
|
@@ -397,7 +407,7 @@ function applyDream(decision, backupRoot) {
|
|
|
397
407
|
} catch (e) { console.warn(`[dream] entity merge failed: ${e.message}`); }
|
|
398
408
|
}
|
|
399
409
|
|
|
400
|
-
for (const en of decision.entity_notes) {
|
|
410
|
+
for (const en of decision.entity_notes || []) {
|
|
401
411
|
try {
|
|
402
412
|
const ent = en?.entity && !gone.has(en.entity) && entities.readEntity(en.entity);
|
|
403
413
|
if (!ent || typeof en.notes !== "string" || !en.notes.trim()) continue;
|
|
@@ -736,14 +746,26 @@ async function runDream({ trigger = "manual" } = {}) {
|
|
|
736
746
|
|
|
737
747
|
_dreaming = true;
|
|
738
748
|
try {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
749
|
+
// Best-effort consolidation: on a large corpus this call can time out or
|
|
750
|
+
// return unreadable JSON. That must NOT abort the dream — the deterministic
|
|
751
|
+
// phases below (prefix nesting, graph tend) need no model and are the
|
|
752
|
+
// load-bearing cleanup. Catch any model failure, fall back to an empty
|
|
753
|
+
// decision (applyDream no-ops), note it for the report, and carry on.
|
|
754
|
+
let decision = null;
|
|
755
|
+
let modelFailNote = "";
|
|
756
|
+
try {
|
|
757
|
+
const { text } = await spawnSubagent(buildDreamPrompt(), {
|
|
758
|
+
model: DREAM_MODEL,
|
|
759
|
+
effort: DREAM_EFFORT,
|
|
760
|
+
timeoutMs: dreamTimeoutMs(packCount),
|
|
761
|
+
systemPrompt: "You are a background memory consolidation process. Reply with ONLY the requested JSON object. No prose, no markdown, no tool use.",
|
|
762
|
+
});
|
|
763
|
+
decision = parseDream(text);
|
|
764
|
+
if (!decision) modelFailNote = "the AI consolidation step returned unreadable output";
|
|
765
|
+
} catch (e) {
|
|
766
|
+
modelFailNote = `the AI consolidation step didn't finish (${e.message})`;
|
|
767
|
+
}
|
|
768
|
+
if (!decision) decision = { report: "" };
|
|
747
769
|
|
|
748
770
|
const backupRoot = makeBackupRoot();
|
|
749
771
|
const applied = applyDream(decision, backupRoot);
|
|
@@ -754,7 +776,10 @@ async function runDream({ trigger = "manual" } = {}) {
|
|
|
754
776
|
try { nestLines = nestPrefixFamilies(backupRoot).lines; }
|
|
755
777
|
catch (e) { console.warn(`[dream] nesting pass failed: ${e.message}`); }
|
|
756
778
|
const consolidationLines = applied.concat(nestLines);
|
|
757
|
-
|
|
779
|
+
let report = decision.report || (consolidationLines.length > 0 ? "Tidied up my memory overnight." : "");
|
|
780
|
+
if (modelFailNote) {
|
|
781
|
+
report = `Heads up — ${modelFailNote}, so I skipped the AI-led merges this round but still ran the automatic filing + graph upkeep.${report ? " " + report : ""}`;
|
|
782
|
+
}
|
|
758
783
|
|
|
759
784
|
// Phase 2: self-improvement introspection. Reviews the day's seeds, reads
|
|
760
785
|
// its own code/memory READ-ONLY (allowedTools whitelist), promotes lessons
|
|
@@ -860,7 +885,7 @@ function initDream(adapters) {
|
|
|
860
885
|
}
|
|
861
886
|
|
|
862
887
|
module.exports = {
|
|
863
|
-
runDream, initDream, buildDreamPrompt, parseDream, applyDream, manageAbilityTiers, nestPrefixFamilies,
|
|
888
|
+
runDream, initDream, buildDreamPrompt, parseDream, applyDream, manageAbilityTiers, nestPrefixFamilies, dreamTimeoutMs,
|
|
864
889
|
buildIntrospectionPrompt, parseIntrospection, applyIntrospection, writeDreamReport,
|
|
865
890
|
enabled, summaryEnabled, introspectEnabled, selfApplyEnabled,
|
|
866
891
|
DREAM_CRON, DREAM_MODEL, DREAM_EFFORT, PROMOTE_MIN_PROJECTS,
|
package/package.json
CHANGED
package/test-pack-nesting.js
CHANGED
|
@@ -77,6 +77,21 @@ assert.ok(res.lines.some((l) => l.includes("billing")), "nest pass announces the
|
|
|
77
77
|
const again = dream.nestPrefixFamilies(null);
|
|
78
78
|
assert.strictEqual(again.filed, 0, "second pass files nothing new");
|
|
79
79
|
|
|
80
|
+
// ── dream resilience: the deterministic cleanup must not depend on the model ──
|
|
81
|
+
// applyDream tolerates an empty decision (the fallback used when the AI
|
|
82
|
+
// consolidation step times out / returns garbage) instead of throwing.
|
|
83
|
+
assert.deepStrictEqual(dream.applyDream({}, null), [], "empty decision applies nothing, throws nothing");
|
|
84
|
+
assert.deepStrictEqual(dream.applyDream({ report: "" }, null), [], "report-only fallback decision is safe");
|
|
85
|
+
|
|
86
|
+
// timeout scales with corpus: floor 20m, grows per pack, capped at 40m, env override.
|
|
87
|
+
assert.strictEqual(dream.dreamTimeoutMs(0), 20 * 60 * 1000, "floor is 20 minutes");
|
|
88
|
+
assert.ok(dream.dreamTimeoutMs(130) > dream.dreamTimeoutMs(30), "more packs → longer budget");
|
|
89
|
+
assert.strictEqual(dream.dreamTimeoutMs(100000), 40 * 60 * 1000, "capped at 40 minutes");
|
|
90
|
+
const prev = process.env.DREAM_TIMEOUT_MS;
|
|
91
|
+
process.env.DREAM_TIMEOUT_MS = "777000";
|
|
92
|
+
assert.strictEqual(dream.dreamTimeoutMs(50), 777000, "DREAM_TIMEOUT_MS overrides entirely");
|
|
93
|
+
if (prev === undefined) delete process.env.DREAM_TIMEOUT_MS; else process.env.DREAM_TIMEOUT_MS = prev;
|
|
94
|
+
|
|
80
95
|
// ── versioned-only fold (FTS-gated): a versioned proposal folds into its pack,
|
|
81
96
|
// a non-versioned near-duplicate does NOT. Skipped when node:sqlite is absent. ──
|
|
82
97
|
if (packs.reindex()) {
|