total-recall-brain 3.44.0 → 3.45.0
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/bin/total-recall.mjs +6 -4
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +13 -8
- package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +2 -2
- package/frontend/src/pages/PluginsPage.tsx +1 -1
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/command.mjs +41 -2
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +6 -7
- package/src/cli/plugin/index.mjs +11 -7
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/startup.mjs +1 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +6 -2
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +31 -11
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compiler.mjs +20 -0
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +148 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-contracts/card.mjs +3 -0
- package/src/core/plugin-loader.mjs +12 -3
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-store.mjs +35 -23
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +26 -4
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +1 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +32 -4
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +65 -54
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +1 -0
- package/src/core/vector-field.mjs +134 -192
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/route-manifest.json +0 -56
- package/src/server/routes/brains.mjs +0 -18
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory access ledger (DECAY-01).
|
|
3
|
+
*
|
|
4
|
+
* Records when a memory is actually used: returned by recall or included in a context
|
|
5
|
+
* capsule. Decay reads it to tell a memory nobody uses from one read every day. It lives in
|
|
6
|
+
* the brain's derived state, so a read never rewrites a vault file.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { atomicWrite } from './vault.mjs';
|
|
11
|
+
|
|
12
|
+
const LEDGER = 'access-ledger.json';
|
|
13
|
+
|
|
14
|
+
function ledgerFile(derivedDir) {
|
|
15
|
+
return path.join(derivedDir, LEDGER);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The ledger as `{ slug: { last_accessed, count } }`. A missing or damaged file is an empty ledger. */
|
|
19
|
+
export function readAccess(derivedDir) {
|
|
20
|
+
if (!derivedDir) return {};
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(fs.readFileSync(ledgerFile(derivedDir), 'utf8'));
|
|
23
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
24
|
+
} catch {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Reserved ledger key: the days on which this brain was used at all (YYYY-MM-DD, sorted). */
|
|
30
|
+
export const ACTIVE_DAYS = '_active_days';
|
|
31
|
+
|
|
32
|
+
/** The days this brain was used, oldest first. */
|
|
33
|
+
export function activeDays(ledger = {}) {
|
|
34
|
+
return Array.isArray(ledger[ACTIVE_DAYS]) ? ledger[ACTIVE_DAYS] : [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* How many of the brain's active days came after `sinceMs`. Decay counts these, never calendar
|
|
39
|
+
* days, so a brain nobody works in never decays (Greg, 2026-11-09).
|
|
40
|
+
*/
|
|
41
|
+
export function activeDaysSince(ledger, sinceMs) {
|
|
42
|
+
const since = new Date(sinceMs).toISOString().slice(0, 10);
|
|
43
|
+
return activeDays(ledger).filter(day => day > since).length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Record one use of each slug, and today as an active day. Never throws: a failed write must not break recall or preflight. */
|
|
47
|
+
export function recordAccess(derivedDir, slugs, now = new Date()) {
|
|
48
|
+
const unique = [...new Set((slugs || []).filter(slug => typeof slug === 'string' && slug && slug !== ACTIVE_DAYS))];
|
|
49
|
+
if (!derivedDir || unique.length === 0) return false;
|
|
50
|
+
try {
|
|
51
|
+
const ledger = readAccess(derivedDir);
|
|
52
|
+
const stamp = now.toISOString();
|
|
53
|
+
const today = stamp.slice(0, 10);
|
|
54
|
+
const days = activeDays(ledger);
|
|
55
|
+
if (!days.includes(today)) ledger[ACTIVE_DAYS] = [...days, today].sort();
|
|
56
|
+
for (const slug of unique) {
|
|
57
|
+
const entry = ledger[slug] || { count: 0 };
|
|
58
|
+
// Three active days are enough for the field's first and second derivatives.
|
|
59
|
+
const daily = { ...entry.daily_counts, [today]: (Number(entry.daily_counts?.[today]) || 0) + 1 };
|
|
60
|
+
const recentDays = activeDays(ledger).slice(-3);
|
|
61
|
+
ledger[slug] = { last_accessed: stamp, count: (Number(entry.count) || 0) + 1,
|
|
62
|
+
daily_counts: Object.fromEntries(Object.entries(daily).filter(([day]) => recentDays.includes(day))) };
|
|
63
|
+
}
|
|
64
|
+
fs.mkdirSync(derivedDir, { recursive: true });
|
|
65
|
+
atomicWrite(ledgerFile(derivedDir), JSON.stringify(ledger));
|
|
66
|
+
return true;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The latest known use of a memory: the ledger, its own `last_accessed`, or its last update. */
|
|
73
|
+
export function lastUsedMs(node, ledger = {}) {
|
|
74
|
+
const times = [ledger[node.slug]?.last_accessed, node.last_accessed, node.updated, node.created]
|
|
75
|
+
.map(value => new Date(value || 0).getTime())
|
|
76
|
+
.filter(Number.isFinite);
|
|
77
|
+
return times.length ? Math.max(...times) : 0;
|
|
78
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to
|
|
1
|
+
/** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to Metaharness. */
|
|
2
2
|
import { loadPluginModule } from './plugin-runtime.mjs';
|
|
3
3
|
|
|
4
4
|
let owner, ownerError;
|
|
5
5
|
try { owner = await loadPluginModule('meta-harness', 'process-manager'); }
|
|
6
6
|
catch (error) { ownerError = error; }
|
|
7
7
|
function implementation() {
|
|
8
|
-
if (!owner) throw new Error(`
|
|
8
|
+
if (!owner) throw new Error(`Metaharness processes are unavailable: ${ownerError?.message || 'install the plugin'}`);
|
|
9
9
|
return owner;
|
|
10
10
|
}
|
|
11
11
|
export const loadAgentState = (...args) => implementation().loadAgentState(...args);
|
|
@@ -47,6 +47,11 @@ export function detectProjectBrain(startDir = process.cwd()) {
|
|
|
47
47
|
// sibling in src/cli/agent-dir.mjs checked both, so the two could disagree
|
|
48
48
|
// about whether a project brain exists at all. It is now the single
|
|
49
49
|
// implementation and the sibling delegates here.
|
|
50
|
+
// The Total Recall package's own `scaffold/` is the template every install copies. It carries
|
|
51
|
+
// a SKILL.md, so the walk took it for a project: a context call or `project-id` from inside
|
|
52
|
+
// it wrote brain.json and derived ledgers into the template, which then ships (2026-11-09).
|
|
53
|
+
// Skip it: the project that contains the package (its checkout or the consumer) is the brain.
|
|
54
|
+
if (isPackageScaffold(dir)) { dir = path.dirname(dir); continue; }
|
|
50
55
|
for (const name of ['.agent', '.agents']) {
|
|
51
56
|
const candidate = path.join(dir, name, 'skills', 'total-recall');
|
|
52
57
|
if (fs.existsSync(path.join(candidate, 'SKILL.md')) || fs.existsSync(path.join(candidate, 'memory-vault'))) {
|
|
@@ -131,3 +136,13 @@ function pluginTargets(pluginsRoot) {
|
|
|
131
136
|
}
|
|
132
137
|
return targets;
|
|
133
138
|
}
|
|
139
|
+
|
|
140
|
+
/** True for the Total Recall package's install template: `<package>/scaffold`, in a checkout or in node_modules. */
|
|
141
|
+
export function isPackageScaffold(dir) {
|
|
142
|
+
if (path.basename(dir) !== 'scaffold') return false;
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(fs.readFileSync(path.join(path.dirname(dir), 'package.json'), 'utf8')).name === 'total-recall-brain';
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -116,7 +116,11 @@ export function discoverBrainConsumers(roots, { onError, installedPlugins = new
|
|
|
116
116
|
.filter(([key]) => BRAIN_PACKAGES.includes(canonical(key)) && canonical(key) !== pkg.name)
|
|
117
117
|
.map(([key, declared]) => ({ section, key, package: canonical(key), declared,
|
|
118
118
|
...installedBrainPackage(real, key) })));
|
|
119
|
-
|
|
119
|
+
// A source checkout of a brain package is not a consumer: its pins move only
|
|
120
|
+
// with its own releases (updateSourceBrainPackage). Rewriting them left the
|
|
121
|
+
// cloud server's checkout dirty, and the release runner refuses a dirty one.
|
|
122
|
+
const brainSource = BRAIN_PACKAGES.includes(pkg.name);
|
|
123
|
+
if (deps.length && !brainSource) consumers.push({ root: real, file, pkg, deps, runtimeOnly });
|
|
120
124
|
}
|
|
121
125
|
for (const entry of fs.readdirSync(real, { withFileTypes: true })) {
|
|
122
126
|
if (!entry.isDirectory() || ignored.has(entry.name)) continue;
|
|
@@ -191,13 +195,18 @@ function targetSpec(key, version) {
|
|
|
191
195
|
return key === '@ssss/cli' ? `npm:@gregiteen/ssss-cli@${version}` : version;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
/** Dry by default. Resolve releases once; install independently; verify actual resolution. */
|
|
195
198
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
+
* Automatic maintenance keeps each checkout's tree as found. A dependency with no installed
|
|
200
|
+
* copy, in a checkout (and owner) with no node_modules at all, gets its pin updated and nothing
|
|
201
|
+
* more: the tree was reclaimed for disk space or never installed, and restoring it is the
|
|
202
|
+
* owner's call. A dev dependency absent from a shared install is the same case. Without this,
|
|
203
|
+
* every hourly run reinstalled full trees into idle checkouts (~10 GB on 2026-11-07, again on
|
|
204
|
+
* 2026-11-09 until the disk filled). `installMissing: true` (CLI --install-missing) opts in.
|
|
199
205
|
*/
|
|
200
206
|
const absentDevDep = (dep) => dep.section === 'devDependencies' && !dep.file;
|
|
207
|
+
const noInstallTree = (...roots) => roots.every(root => !fs.existsSync(path.join(root, 'node_modules')));
|
|
208
|
+
const keepAsFound = (dep, installMissing, ...roots) =>
|
|
209
|
+
!installMissing && !dep.file && (absentDevDep(dep) || noInstallTree(...roots));
|
|
201
210
|
|
|
202
211
|
/**
|
|
203
212
|
* The daemon runs with NODE_ENV=production, and under that npm and pnpm remove dev dependencies
|
|
@@ -210,6 +219,7 @@ export function installEnv(env = process.env) {
|
|
|
210
219
|
|
|
211
220
|
export async function updateAllBrains(opts = {}) {
|
|
212
221
|
const dryRun = opts.dryRun !== false;
|
|
222
|
+
const installMissing = opts.installMissing === true;
|
|
213
223
|
const exec = opts.exec || spawnSync;
|
|
214
224
|
const roots = listUpdateRoots(opts).map(row => row.root);
|
|
215
225
|
const errors = [];
|
|
@@ -270,12 +280,16 @@ export async function updateAllBrains(opts = {}) {
|
|
|
270
280
|
const shared = isLink(path.join(owner.owner, 'node_modules')) || isLink(path.join(consumer.root, 'node_modules'));
|
|
271
281
|
const needsChange = shared || (!consumer.runtimeOnly && overridesLag(consumer.pkg, versions)) || consumer.deps.some(dep => {
|
|
272
282
|
const pinLags = !consumer.runtimeOnly && dep.declared !== targetSpec(dep.key, versions[dep.package]);
|
|
273
|
-
if (
|
|
283
|
+
if (keepAsFound(dep, installMissing, consumer.root, owner.owner)) return pinLags;
|
|
274
284
|
return pinLags || dep.version !== versions[dep.package]
|
|
275
285
|
|| !dep.file?.startsWith(path.join(owner.owner, 'node_modules') + path.sep);
|
|
276
286
|
});
|
|
277
|
-
if (!needsChange)
|
|
278
|
-
|
|
287
|
+
if (!needsChange) {
|
|
288
|
+
// Kept as found: report the missing tree, install nothing.
|
|
289
|
+
if (consumer.deps.some(dep => !dep.version)) row.status = 'not_installed';
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
row.status = dryRun ? (consumer.deps.some(dep => !dep.version) ? 'not_installed' : 'would_update') : 'pending';
|
|
279
293
|
if (!groups.has(owner.owner)) groups.set(owner.owner, { ...owner, consumers: [] });
|
|
280
294
|
groups.get(owner.owner).consumers.push({ consumer, row });
|
|
281
295
|
}
|
|
@@ -290,7 +304,8 @@ export async function updateAllBrains(opts = {}) {
|
|
|
290
304
|
const runtimeOnly = group.consumers.every(({ consumer }) => consumer.runtimeOnly);
|
|
291
305
|
const unpinned = runtimeOnly && group.manager === 'npm';
|
|
292
306
|
const needsInstall = group.consumers.some(({ consumer }) =>
|
|
293
|
-
isLink(path.join(consumer.root, 'node_modules')) || consumer.deps.some(dep =>
|
|
307
|
+
isLink(path.join(consumer.root, 'node_modules')) || consumer.deps.some(dep =>
|
|
308
|
+
!keepAsFound(dep, installMissing, consumer.root, group.owner)
|
|
294
309
|
&& (dep.version !== versions[dep.package] || !dep.file?.startsWith(path.join(group.owner, 'node_modules') + path.sep))));
|
|
295
310
|
try {
|
|
296
311
|
lockFd = acquireBrainUpdateLock(lock, opts.lockWaitMs === undefined ? {} : { waitMs: opts.lockWaitMs });
|
|
@@ -322,7 +337,10 @@ export async function updateAllBrains(opts = {}) {
|
|
|
322
337
|
if (runtimeOnly) args.push(group.manager === 'pnpm' ? '--prod' : '--omit=dev');
|
|
323
338
|
if (unpinned) args.push('--no-save', ...new Set(group.consumers.flatMap(({ consumer }) =>
|
|
324
339
|
consumer.deps.map(dep => `${dep.key}@${targetSpec(dep.key, versions[dep.package])}`))));
|
|
325
|
-
|
|
340
|
+
// A lockfile-only update builds no tree, so it runs no lifecycle scripts: npm still
|
|
341
|
+
// ran a checkout's own postinstall here, which fails in a repository that was never
|
|
342
|
+
// installed (a postinstall calling a tool that is not yet installed, 2026-11-09).
|
|
343
|
+
else if (!needsInstall) args.push(group.manager === 'pnpm' ? '--lockfile-only' : '--package-lock-only', '--ignore-scripts');
|
|
326
344
|
const install = exec(group.manager, args, { cwd: group.owner, env: installEnv(), encoding: 'utf8',
|
|
327
345
|
timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
|
|
328
346
|
if (install.status !== 0) throw new Error(install.error?.message || install.stderr?.trim().slice(-1500) || `Install exited ${install.status}`);
|
|
@@ -338,7 +356,7 @@ export async function updateAllBrains(opts = {}) {
|
|
|
338
356
|
const placed = (file) => Boolean(file) && allowedRoots.some((root) => file.startsWith(root));
|
|
339
357
|
const bad = consumer.deps.find(dep => {
|
|
340
358
|
const actual = installedBrainPackage(consumer.root, dep.key);
|
|
341
|
-
if (
|
|
359
|
+
if (keepAsFound(dep, installMissing, consumer.root, group.owner) && !actual.file) return false;
|
|
342
360
|
return actual.version !== versions[dep.package] || !placed(actual.file);
|
|
343
361
|
});
|
|
344
362
|
// A link is only a fault when it escapes every legitimate root; a self-contained one is fine.
|
|
@@ -368,5 +386,7 @@ export async function updateAllBrains(opts = {}) {
|
|
|
368
386
|
updated: results.filter(row => row.status === 'updated').length,
|
|
369
387
|
shared_install: results.filter(row => row.status === 'shared_install').length,
|
|
370
388
|
would_update: results.filter(row => row.status === 'would_update').length,
|
|
389
|
+
not_installed: results.filter(row => row.status === 'not_installed').length,
|
|
390
|
+
up_to_date: results.filter(row => row.status === 'up_to_date').length,
|
|
371
391
|
failed: results.filter(row => row.status === 'failed').length, results };
|
|
372
392
|
}
|
|
@@ -12,6 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { installLessonHooks } from './lesson-hooks.mjs';
|
|
15
16
|
|
|
16
17
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
17
18
|
export const GATE_TEMPLATE = path.join(PACKAGE_ROOT, 'templates', 'claude-hooks', 'total-recall-gate.mjs');
|
|
@@ -104,5 +105,6 @@ export function installClaudeCodeHooks(opts = {}) {
|
|
|
104
105
|
if (fs.existsSync(settingsPath)) fs.copyFileSync(settingsPath, `${settingsPath}.bak-total-recall`);
|
|
105
106
|
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
const lessons = installLessonHooks({ harness: 'claude', settings: settingsPath, status: 'installed' }, { dryRun: opts.dryRun });
|
|
109
|
+
return { gate: hookPath, settings: settingsPath, added, removed, lessons };
|
|
108
110
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { generateMemoryCleanupProposals, dedupeProposals, evaluateProposalGate } from './optimizer.mjs';
|
|
2
|
+
import { writeNode } from './vault.mjs';
|
|
3
|
+
|
|
4
|
+
/** Compaction uses the same gate, identity and review path as dream cleanup. */
|
|
5
|
+
export async function fileCleanupProposals(vaultDir, { generate = generateMemoryCleanupProposals,
|
|
6
|
+
dedupe = dedupeProposals, gate = evaluateProposalGate, write = writeNode } = {}) {
|
|
7
|
+
const { kept, suppressed } = dedupe(await generate(vaultDir), vaultDir);
|
|
8
|
+
let persisted = 0;
|
|
9
|
+
for (const proposal of kept) {
|
|
10
|
+
await gate(proposal, null, vaultDir);
|
|
11
|
+
await write(proposal, vaultDir);
|
|
12
|
+
persisted++;
|
|
13
|
+
}
|
|
14
|
+
return { success: true, persisted, suppressed, output: `Compaction filed ${persisted} cleanup proposals`, executor: 'compaction' };
|
|
15
|
+
}
|
|
@@ -5,10 +5,12 @@ export const CLI_COMMANDS = {
|
|
|
5
5
|
setup: 'setup.mjs',
|
|
6
6
|
start: 'start.mjs',
|
|
7
7
|
startup: 'startup.mjs',
|
|
8
|
+
server: 'server.mjs',
|
|
8
9
|
deploy: 'deploy.mjs',
|
|
9
10
|
doctor: 'doctor.mjs',
|
|
10
11
|
backup: 'backup.mjs',
|
|
11
12
|
dream: 'dream.mjs',
|
|
13
|
+
lessons: 'lessons.mjs',
|
|
12
14
|
lint: 'lint.mjs',
|
|
13
15
|
daemon: 'daemon.mjs',
|
|
14
16
|
restore: 'restore.mjs',
|
|
@@ -21,7 +21,10 @@ export function cliInputFingerprint(root) {
|
|
|
21
21
|
|
|
22
22
|
export async function maintainCliSurfaces({brainDir,brains,force=false,run=runBounded,fingerprint=cliInputFingerprint}={}) {
|
|
23
23
|
const results=[];
|
|
24
|
-
|
|
24
|
+
// By default only the global brain: entry files are static and each repository reads its
|
|
25
|
+
// rules and commands on demand, so the daemon never fans a refresh out across registered
|
|
26
|
+
// repositories (ON-DEMAND-CONTEXT-01). An explicit brain list is still honoured.
|
|
27
|
+
for(const brain of brains || listLocalBrains({globalBrainDir:brainDir}).filter(item=>item.kind==='global')) {
|
|
25
28
|
if(!brain.exists || brain.enabled===false)continue;
|
|
26
29
|
const root=brain.root || path.dirname(path.dirname(brain.brainDir));
|
|
27
30
|
const file=path.join(brain.brainDir,'memory-derived/cli-maintenance.json');
|
|
@@ -22,6 +22,21 @@ import { logger } from './logger.mjs';
|
|
|
22
22
|
import { atomicWrite } from './vault.mjs';
|
|
23
23
|
import { globalAgentDir } from './local-config.mjs';
|
|
24
24
|
|
|
25
|
+
/** Usage text for declared plugin arguments: positional as <label>, flags as [--flag <label>] or [--flag]. */
|
|
26
|
+
export function formatSubcommandArgs(args) {
|
|
27
|
+
if (typeof args === 'string') return args ? ` ${args}` : '';
|
|
28
|
+
if (!Array.isArray(args)) return '';
|
|
29
|
+
const parts = [];
|
|
30
|
+
for (const arg of args) {
|
|
31
|
+
if (!arg || typeof arg !== 'object') continue;
|
|
32
|
+
const label = String(arg.name || arg.label || 'value').replace(/\s+/g, '-');
|
|
33
|
+
if (arg.positional || !arg.flag) parts.push(arg.required ? `<${label}>` : `[${label}]`);
|
|
34
|
+
else if (arg.type === 'boolean') parts.push(arg.required ? arg.flag : `[${arg.flag}]`);
|
|
35
|
+
else parts.push(arg.required ? `${arg.flag} <${label}>` : `[${arg.flag} <${label}>]`);
|
|
36
|
+
}
|
|
37
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
/** Canonical descriptions and usage signatures for built-in core commands. */
|
|
26
41
|
const CORE_METADATA = {
|
|
27
42
|
app: {
|
|
@@ -49,6 +64,11 @@ const CORE_METADATA = {
|
|
|
49
64
|
usage: 'total-recall startup [check|ensure]',
|
|
50
65
|
effect: 'write'
|
|
51
66
|
},
|
|
67
|
+
server: {
|
|
68
|
+
description: 'Repair and verify the macOS server LaunchAgent for this package.',
|
|
69
|
+
usage: 'total-recall server ensure [--dry-run]',
|
|
70
|
+
effect: 'write'
|
|
71
|
+
},
|
|
52
72
|
deploy: {
|
|
53
73
|
description: 'Deploy host runtime layers, daemon services, or launch browser setup.',
|
|
54
74
|
usage: 'total-recall deploy [--ui]',
|
|
@@ -66,7 +86,7 @@ const CORE_METADATA = {
|
|
|
66
86
|
},
|
|
67
87
|
dream: {
|
|
68
88
|
description: 'Trigger memory consolidation sleep cycle.',
|
|
69
|
-
usage: 'total-recall dream',
|
|
89
|
+
usage: 'total-recall dream [--all-brains] [--dry-run] [--json]',
|
|
70
90
|
effect: 'write'
|
|
71
91
|
},
|
|
72
92
|
lint: {
|
|
@@ -320,7 +340,7 @@ const CORE_METADATA = {
|
|
|
320
340
|
effect: 'read'
|
|
321
341
|
},
|
|
322
342
|
harness: {
|
|
323
|
-
description: '
|
|
343
|
+
description: 'Metaharness orchestration, task dispatch, and council consensus.',
|
|
324
344
|
usage: 'total-recall harness [dispatch|council|usage]',
|
|
325
345
|
effect: 'write'
|
|
326
346
|
},
|
|
@@ -431,7 +451,7 @@ export function harvestAllCliVerbs({ projectRoot = process.cwd(), skillsDir = nu
|
|
|
431
451
|
if (seenIds.has(subId)) continue;
|
|
432
452
|
seenIds.add(subId);
|
|
433
453
|
|
|
434
|
-
const argsStr = sub.args
|
|
454
|
+
const argsStr = formatSubcommandArgs(sub.args);
|
|
435
455
|
verbs.push({
|
|
436
456
|
id: subId,
|
|
437
457
|
name: sub.name,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Local task capsules: explicit required rules, ranked supporting knowledge, complete-render accounting. */
|
|
2
2
|
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { getNodes } from './vault-cache.mjs';
|
|
5
|
+
import { recordAccess } from './access-ledger.mjs';
|
|
6
|
+
import { brainDir as globalBrainDir } from './local-config.mjs';
|
|
4
7
|
import crypto from 'node:crypto';
|
|
5
8
|
import { selectRules, assembleContext, curatedRules, ruleFingerprint, RULE_CATEGORIES } from './context-policy.mjs';
|
|
6
9
|
import { retrieveTaskKnowledge, summarizeRetrieval } from './task-context.mjs';
|
|
@@ -142,9 +145,26 @@ export async function compileContext({
|
|
|
142
145
|
result.stats.slots = { invariants: { nodes: rules.filter(n => n.category === 'invariants').length,
|
|
143
146
|
tokens: result.stats.required_tokens } };
|
|
144
147
|
result.stats.slots_filled = result.stats.contributions.length;
|
|
148
|
+
// DECAY-01: a capsule is a real use of the rules and memories it carries.
|
|
149
|
+
recordCapsuleAccess(derivedDir, rules, knowledge.contributions);
|
|
145
150
|
return result;
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Record each rule and memory a capsule carries in the ledger of the brain that owns it, so
|
|
155
|
+
* decay in the global brain sees global rules used by every project.
|
|
156
|
+
*/
|
|
157
|
+
export function recordCapsuleAccess(derivedDir, rules = [], contributions = [], globalDerivedDir = path.join(globalBrainDir, 'memory-derived')) {
|
|
158
|
+
const owned = { project: [], global: [] };
|
|
159
|
+
for (const node of rules) (node._layer === 'global' ? owned.global : owned.project).push(node.slug);
|
|
160
|
+
for (const { id } of contributions || []) {
|
|
161
|
+
const match = /^vault:(global|project):(.+)$/.exec(String(id || ''));
|
|
162
|
+
if (match) owned[match[1]].push(match[2]);
|
|
163
|
+
}
|
|
164
|
+
if (derivedDir) recordAccess(derivedDir, owned.project);
|
|
165
|
+
recordAccess(derivedDir && path.resolve(derivedDir) === path.resolve(globalDerivedDir) ? derivedDir : globalDerivedDir, owned.global);
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
/**
|
|
149
169
|
* Second line of every text capsule, so output that is truncated or piped through
|
|
150
170
|
* `head` still carries the rule (the rule once sat at line 544 of the output and
|
package/src/core/daemon-loop.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { loadPluginModule as __loadOwnedPlugin } from './plugin-runtime.mjs';
|
|
|
4
4
|
*
|
|
5
5
|
* Durable worker over the open task envelope queue.
|
|
6
6
|
* - Dispatches via executor registry (fail-loud on unknown)
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - Automations owns scheduled maintenance; empty queues stay empty
|
|
8
|
+
* - Executes dream tasks scheduled by the automations plugin
|
|
9
9
|
* - Ingests sessions on boot; recompiles surfaces every N ticks
|
|
10
10
|
* - Auto-syncs all .md files from registered repos into Total Recall
|
|
11
11
|
*
|
|
@@ -17,7 +17,7 @@ import { DAEMON_PID_FILE } from './daemon-control.mjs';
|
|
|
17
17
|
import fs from 'fs';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { readProcessCommand, entryPathHint, shouldHonorPidLock } from './pid-lock.mjs';
|
|
20
|
-
import { createScheduler, updateTaskStatus
|
|
20
|
+
import { createScheduler, updateTaskStatus } from './scheduler.mjs';
|
|
21
21
|
import { scanAndIngest } from './session-watcher.mjs';
|
|
22
22
|
import { syncAllRepos } from './repo-sync.mjs';
|
|
23
23
|
import { refreshProviderUsage } from './usage-fetcher.mjs';
|
|
@@ -26,7 +26,7 @@ import { updateQueueItem } from './research-queue.mjs';
|
|
|
26
26
|
import { agentDir, brainDir, detectProjectBrain } from './config.mjs';
|
|
27
27
|
import { runCrons } from './crons.mjs';
|
|
28
28
|
import { dispatchTask as registryDispatch } from './task-executors.mjs';
|
|
29
|
-
import {
|
|
29
|
+
import { publishTaskSchedulerProtocol } from './task-envelope.mjs';
|
|
30
30
|
|
|
31
31
|
const AGENT_DIR = agentDir;
|
|
32
32
|
const BRAIN_DIR = brainDir;
|
|
@@ -61,8 +61,16 @@ const RECOMPILE_EVERY_N_TASKS = 20;
|
|
|
61
61
|
const TASK_SLEEP_MS = 10000;
|
|
62
62
|
const EMPTY_SLEEP_MS = 30000;
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
/** An empty queue only sleeps. Plugin services own recurring task creation. */
|
|
65
|
+
export async function nextDaemonTask(scheduler, { localOnly = false, sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
|
|
66
|
+
const next = scheduler.next({ localOnly });
|
|
67
|
+
if (!next.task) {
|
|
68
|
+
const sleepMs = localOnly ? TASK_SLEEP_MS : EMPTY_SLEEP_MS;
|
|
69
|
+
logger.info({ subsystem: 'daemon-loop', message: `Queue empty — sleeping ${sleepMs}ms` });
|
|
70
|
+
await sleep(sleepMs);
|
|
71
|
+
}
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
66
74
|
|
|
67
75
|
function executorContext(runtimeConfig) {
|
|
68
76
|
return {
|
|
@@ -242,6 +250,7 @@ let running = true;
|
|
|
242
250
|
let vaultWatcher = null;
|
|
243
251
|
let shuttingDown = false;
|
|
244
252
|
let schedulerProtocolActive = false;
|
|
253
|
+
let lastLessonScan = 0;
|
|
245
254
|
|
|
246
255
|
// Upper bound on a graceful stop. A loop iteration can be mid-task, so the
|
|
247
256
|
// signal cannot exit instantly — but it must exit *eventually*.
|
|
@@ -259,7 +268,7 @@ import { releaseLease } from './leader-election.mjs';
|
|
|
259
268
|
* daemon.log across three separate SIGTERMs. The fallback timer bounds that, so
|
|
260
269
|
* even a wedged iteration cannot keep the daemon alive indefinitely.
|
|
261
270
|
*/
|
|
262
|
-
let
|
|
271
|
+
let stopPluginServices = null;
|
|
263
272
|
let stopCliMaintenance = null;
|
|
264
273
|
let stopAutomaticUpdates = null;
|
|
265
274
|
|
|
@@ -269,7 +278,7 @@ function shutdown(signal) {
|
|
|
269
278
|
logger.info({ subsystem: 'daemon-loop', message: `${signal} received — shutting down gracefully` });
|
|
270
279
|
running = false;
|
|
271
280
|
if (vaultWatcher) vaultWatcher.stop();
|
|
272
|
-
if (
|
|
281
|
+
if (stopPluginServices) stopPluginServices();
|
|
273
282
|
stopCliMaintenance?.();
|
|
274
283
|
stopAutomaticUpdates?.();
|
|
275
284
|
|
|
@@ -451,15 +460,16 @@ async function main() {
|
|
|
451
460
|
}
|
|
452
461
|
}
|
|
453
462
|
|
|
454
|
-
let emptyTicks = 0;
|
|
455
463
|
|
|
456
|
-
//
|
|
457
|
-
//
|
|
464
|
+
// Per-node services owned by plugins (scheduled plugin tasks belong to
|
|
465
|
+
// automations) start here; core runs whatever active plugins declare.
|
|
458
466
|
try {
|
|
459
|
-
const {
|
|
460
|
-
|
|
467
|
+
const { startPluginDaemonServices } = await import('./plugin-runtime.mjs');
|
|
468
|
+
const services = await startPluginDaemonServices();
|
|
469
|
+
stopPluginServices = services.stop;
|
|
470
|
+
logger.info('daemon-loop', `Plugin daemon services started: ${services.started.join(', ') || 'none'}${services.failed.length ? `; failed: ${services.failed.join(', ')}` : ''}`);
|
|
461
471
|
} catch (err) {
|
|
462
|
-
logger.warn('daemon-loop', `Plugin
|
|
472
|
+
logger.warn('daemon-loop', `Plugin daemon services failed to start: ${err.message}`);
|
|
463
473
|
}
|
|
464
474
|
|
|
465
475
|
while (running) {
|
|
@@ -560,14 +570,20 @@ async function main() {
|
|
|
560
570
|
}
|
|
561
571
|
}
|
|
562
572
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
573
|
+
const localOnly = !await isLeader();
|
|
574
|
+
|
|
575
|
+
// Local transcripts stay on their node. Followers also queue and run reviews.
|
|
576
|
+
if (Date.now() - lastLessonScan >= 60_000) {
|
|
577
|
+
lastLessonScan = Date.now();
|
|
578
|
+
try {
|
|
579
|
+
scanAndIngest(SESSIONS_DIR);
|
|
580
|
+
const { enqueueSessionReviews } = await import('./lessons.mjs');
|
|
581
|
+
enqueueSessionReviews({ brainDir: BRAIN_DIR });
|
|
582
|
+
} catch (error) { logger.warn('daemon-loop', `Local lesson ingest failed: ${error.message}`); }
|
|
567
583
|
}
|
|
568
584
|
|
|
569
585
|
// Periodically recompile surfaces (LEADER ONLY)
|
|
570
|
-
if (taskCount > 0 && taskCount % RECOMPILE_EVERY_N_TASKS === 0) {
|
|
586
|
+
if (!localOnly && taskCount > 0 && taskCount % RECOMPILE_EVERY_N_TASKS === 0) {
|
|
571
587
|
logger.info({ subsystem: 'daemon-loop', message: 'Running scheduled surface recompile...' });
|
|
572
588
|
try {
|
|
573
589
|
const { compileSurface } = await import('./surface.mjs');
|
|
@@ -581,16 +597,6 @@ async function main() {
|
|
|
581
597
|
logger.info({ subsystem: 'daemon-loop', message: `Surface recompile error: ${err.message}` });
|
|
582
598
|
}
|
|
583
599
|
|
|
584
|
-
try {
|
|
585
|
-
const { compactAppendLogs } = await import('./append-log.mjs');
|
|
586
|
-
const compactResult = compactAppendLogs();
|
|
587
|
-
if (compactResult.logs_compacted > 0) {
|
|
588
|
-
logger.info({ subsystem: 'daemon-loop', message: `Compacted ${compactResult.logs_compacted} append logs (${compactResult.total_entries} entries)` });
|
|
589
|
-
}
|
|
590
|
-
} catch (err) {
|
|
591
|
-
logger.info({ subsystem: 'daemon-loop', message: `Append log compaction error: ${err.message}` });
|
|
592
|
-
}
|
|
593
|
-
|
|
594
600
|
try {
|
|
595
601
|
await runCrons({ vaultDir: VAULT_DIR, skillsDir: SKILLS_DIR, brainDir: BRAIN_DIR });
|
|
596
602
|
} catch (err) {
|
|
@@ -627,42 +633,13 @@ async function main() {
|
|
|
627
633
|
queueDir: QUEUE_DIR,
|
|
628
634
|
vaultDir: VAULT_DIR,
|
|
629
635
|
sessionsDir: SESSIONS_DIR,
|
|
636
|
+
localOnly,
|
|
630
637
|
});
|
|
631
638
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
// Empty queue: optional system dream, then sleep (no busy-work invention)
|
|
639
|
+
const { task, source } = await nextDaemonTask(scheduler, { localOnly });
|
|
635
640
|
if (!task) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const dreamEnv = buildTaskEnvelope({
|
|
639
|
-
intent: 'Scheduled system dream cycle',
|
|
640
|
-
slug: `dream-system-${Date.now().toString(36)}`,
|
|
641
|
-
kind: 'system',
|
|
642
|
-
executor: 'dream',
|
|
643
|
-
category: 'memory-maintenance',
|
|
644
|
-
priority: 70,
|
|
645
|
-
system: true,
|
|
646
|
-
origin: { agent: 'daemon', created_by: 'daemon-system' },
|
|
647
|
-
capabilities: ['vault:read', 'vault:write'],
|
|
648
|
-
});
|
|
649
|
-
persistTaskToDisk(dreamEnv, QUEUE_DIR);
|
|
650
|
-
task = dreamEnv;
|
|
651
|
-
source = 'system';
|
|
652
|
-
logger.info({
|
|
653
|
-
subsystem: 'daemon-loop',
|
|
654
|
-
message: `Empty queue → enqueue system dream (${emptyTicks} empty ticks)`,
|
|
655
|
-
});
|
|
656
|
-
} else {
|
|
657
|
-
logger.info({
|
|
658
|
-
subsystem: 'daemon-loop',
|
|
659
|
-
message: `Queue empty (tick ${emptyTicks}) — sleeping ${EMPTY_SLEEP_MS}ms`,
|
|
660
|
-
});
|
|
661
|
-
await new Promise((r) => setTimeout(r, EMPTY_SLEEP_MS));
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
} else {
|
|
665
|
-
emptyTicks = 0;
|
|
641
|
+
if (localOnly) taskCount++;
|
|
642
|
+
continue;
|
|
666
643
|
}
|
|
667
644
|
|
|
668
645
|
taskCount++;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Default runtime foundation and routine agent-development tools. */
|
|
2
2
|
export const DEFAULT_PLUGIN_IDS = Object.freeze([
|
|
3
3
|
'auth', 'ssss', 'mesh', 'openwiki', 'composable-cli', 'meta-harness',
|
|
4
|
-
'code-quality', 'security', 'embeddings', 'git
|
|
4
|
+
'code-quality', 'security', 'embeddings', 'git', 'automations',
|
|
5
5
|
]);
|
|
6
6
|
|
|
7
7
|
/** Required defaults that own additional installation payloads. */
|