total-recall-brain 3.43.3 → 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 +16 -9
- 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 +3 -3
- package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
- package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
- package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
- package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
- package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
- package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
- 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 +17 -16
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
- 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 +160 -29
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
- 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 +101 -16
- 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 +54 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
- 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 +64 -15
- 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 +249 -0
- 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 +6 -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/pool.mjs +16 -8
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
- 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 +28 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
- 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 +110 -0
- 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 +597 -70
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
- 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 +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/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/connect.mjs +2 -29
- package/src/cli/context.mjs +1 -0
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/ingest-okf.mjs +4 -4
- 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/release.mjs +4 -1
- 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 +10 -3
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/automatic-brain-updates.mjs +11 -6
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +42 -12
- 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-compactor.mjs +7 -2
- package/src/core/context-compiler.mjs +26 -1
- package/src/core/context-policy.mjs +7 -3
- package/src/core/daemon-control.mjs +22 -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/global-brain-updates.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/mesh-brain-updates.mjs +2 -1
- 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-restart.mjs +128 -7
- 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 +33 -6
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +68 -57
- package/src/core/task-context.mjs +9 -8
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +5 -0
- package/src/core/vault-files.mjs +8 -2
- package/src/core/vault.mjs +1 -1
- package/src/core/vector-field.mjs +134 -192
- package/src/server/index.mjs +3 -1
- 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
|
@@ -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
|
+
}
|
|
@@ -19,6 +19,13 @@ const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'pe
|
|
|
19
19
|
const ignored = new Set(['node_modules', 'vendor', 'dist', 'build', 'coverage', 'venv',
|
|
20
20
|
'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch', 'scaffold', 'memory-derived']);
|
|
21
21
|
const canonical = key => key === '@ssss/cli' ? '@gregiteen/ssss-cli' : key;
|
|
22
|
+
// Plugins also force the engine alias through `overrides`. Those pins move with the dependency,
|
|
23
|
+
// or a consumer ends up asking for two versions of the same package.
|
|
24
|
+
const overrideKeys = pkg => Object.entries(pkg.overrides || {})
|
|
25
|
+
.filter(([key, spec]) => typeof spec === 'string' && BRAIN_PACKAGES.includes(canonical(key)) && canonical(key) !== pkg.name)
|
|
26
|
+
.map(([key]) => key);
|
|
27
|
+
const overridesLag = (pkg, versions) => overrideKeys(pkg)
|
|
28
|
+
.some(key => versions[canonical(key)] && pkg.overrides[key] !== targetSpec(key, versions[canonical(key)]));
|
|
22
29
|
const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
23
30
|
const isLink = file => { try { return fs.lstatSync(file).isSymbolicLink(); } catch { return false; } };
|
|
24
31
|
export const brainUpdateLockPath = owner => path.join(os.tmpdir(),
|
|
@@ -109,7 +116,11 @@ export function discoverBrainConsumers(roots, { onError, installedPlugins = new
|
|
|
109
116
|
.filter(([key]) => BRAIN_PACKAGES.includes(canonical(key)) && canonical(key) !== pkg.name)
|
|
110
117
|
.map(([key, declared]) => ({ section, key, package: canonical(key), declared,
|
|
111
118
|
...installedBrainPackage(real, key) })));
|
|
112
|
-
|
|
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 });
|
|
113
124
|
}
|
|
114
125
|
for (const entry of fs.readdirSync(real, { withFileTypes: true })) {
|
|
115
126
|
if (!entry.isDirectory() || ignored.has(entry.name)) continue;
|
|
@@ -184,13 +195,18 @@ function targetSpec(key, version) {
|
|
|
184
195
|
return key === '@ssss/cli' ? `npm:@gregiteen/ssss-cli@${version}` : version;
|
|
185
196
|
}
|
|
186
197
|
|
|
187
|
-
/** Dry by default. Resolve releases once; install independently; verify actual resolution. */
|
|
188
198
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
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.
|
|
192
205
|
*/
|
|
193
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));
|
|
194
210
|
|
|
195
211
|
/**
|
|
196
212
|
* The daemon runs with NODE_ENV=production, and under that npm and pnpm remove dev dependencies
|
|
@@ -203,6 +219,7 @@ export function installEnv(env = process.env) {
|
|
|
203
219
|
|
|
204
220
|
export async function updateAllBrains(opts = {}) {
|
|
205
221
|
const dryRun = opts.dryRun !== false;
|
|
222
|
+
const installMissing = opts.installMissing === true;
|
|
206
223
|
const exec = opts.exec || spawnSync;
|
|
207
224
|
const roots = listUpdateRoots(opts).map(row => row.root);
|
|
208
225
|
const errors = [];
|
|
@@ -261,14 +278,18 @@ export async function updateAllBrains(opts = {}) {
|
|
|
261
278
|
row.status = 'failed'; row.error = 'Newer local/preview package; refusing an implicit downgrade'; continue;
|
|
262
279
|
}
|
|
263
280
|
const shared = isLink(path.join(owner.owner, 'node_modules')) || isLink(path.join(consumer.root, 'node_modules'));
|
|
264
|
-
const needsChange = shared || consumer.deps.some(dep => {
|
|
281
|
+
const needsChange = shared || (!consumer.runtimeOnly && overridesLag(consumer.pkg, versions)) || consumer.deps.some(dep => {
|
|
265
282
|
const pinLags = !consumer.runtimeOnly && dep.declared !== targetSpec(dep.key, versions[dep.package]);
|
|
266
|
-
if (
|
|
283
|
+
if (keepAsFound(dep, installMissing, consumer.root, owner.owner)) return pinLags;
|
|
267
284
|
return pinLags || dep.version !== versions[dep.package]
|
|
268
285
|
|| !dep.file?.startsWith(path.join(owner.owner, 'node_modules') + path.sep);
|
|
269
286
|
});
|
|
270
|
-
if (!needsChange)
|
|
271
|
-
|
|
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';
|
|
272
293
|
if (!groups.has(owner.owner)) groups.set(owner.owner, { ...owner, consumers: [] });
|
|
273
294
|
groups.get(owner.owner).consumers.push({ consumer, row });
|
|
274
295
|
}
|
|
@@ -283,7 +304,8 @@ export async function updateAllBrains(opts = {}) {
|
|
|
283
304
|
const runtimeOnly = group.consumers.every(({ consumer }) => consumer.runtimeOnly);
|
|
284
305
|
const unpinned = runtimeOnly && group.manager === 'npm';
|
|
285
306
|
const needsInstall = group.consumers.some(({ consumer }) =>
|
|
286
|
-
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)
|
|
287
309
|
&& (dep.version !== versions[dep.package] || !dep.file?.startsWith(path.join(group.owner, 'node_modules') + path.sep))));
|
|
288
310
|
try {
|
|
289
311
|
lockFd = acquireBrainUpdateLock(lock, opts.lockWaitMs === undefined ? {} : { waitMs: opts.lockWaitMs });
|
|
@@ -296,6 +318,9 @@ export async function updateAllBrains(opts = {}) {
|
|
|
296
318
|
saved.set(consumer.file, text);
|
|
297
319
|
const pkg = read(consumer.file);
|
|
298
320
|
for (const dep of consumer.deps) pkg[dep.section][dep.key] = targetSpec(dep.key, versions[dep.package]);
|
|
321
|
+
for (const key of overrideKeys(pkg)) {
|
|
322
|
+
if (versions[canonical(key)]) pkg.overrides[key] = targetSpec(key, versions[canonical(key)]);
|
|
323
|
+
}
|
|
299
324
|
const indent = /\n([ \t]+)"/.exec(text)?.[1] || 2;
|
|
300
325
|
fs.writeFileSync(consumer.file, JSON.stringify(pkg, null, indent) + '\n');
|
|
301
326
|
}
|
|
@@ -312,7 +337,10 @@ export async function updateAllBrains(opts = {}) {
|
|
|
312
337
|
if (runtimeOnly) args.push(group.manager === 'pnpm' ? '--prod' : '--omit=dev');
|
|
313
338
|
if (unpinned) args.push('--no-save', ...new Set(group.consumers.flatMap(({ consumer }) =>
|
|
314
339
|
consumer.deps.map(dep => `${dep.key}@${targetSpec(dep.key, versions[dep.package])}`))));
|
|
315
|
-
|
|
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');
|
|
316
344
|
const install = exec(group.manager, args, { cwd: group.owner, env: installEnv(), encoding: 'utf8',
|
|
317
345
|
timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
|
|
318
346
|
if (install.status !== 0) throw new Error(install.error?.message || install.stderr?.trim().slice(-1500) || `Install exited ${install.status}`);
|
|
@@ -328,7 +356,7 @@ export async function updateAllBrains(opts = {}) {
|
|
|
328
356
|
const placed = (file) => Boolean(file) && allowedRoots.some((root) => file.startsWith(root));
|
|
329
357
|
const bad = consumer.deps.find(dep => {
|
|
330
358
|
const actual = installedBrainPackage(consumer.root, dep.key);
|
|
331
|
-
if (
|
|
359
|
+
if (keepAsFound(dep, installMissing, consumer.root, group.owner) && !actual.file) return false;
|
|
332
360
|
return actual.version !== versions[dep.package] || !placed(actual.file);
|
|
333
361
|
});
|
|
334
362
|
// A link is only a fault when it escapes every legitimate root; a self-contained one is fine.
|
|
@@ -358,5 +386,7 @@ export async function updateAllBrains(opts = {}) {
|
|
|
358
386
|
updated: results.filter(row => row.status === 'updated').length,
|
|
359
387
|
shared_install: results.filter(row => row.status === 'shared_install').length,
|
|
360
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,
|
|
361
391
|
failed: results.filter(row => row.status === 'failed').length, results };
|
|
362
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,
|
|
@@ -62,6 +62,9 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
|
|
|
62
62
|
if (unknown.length) throw new Error(`Cannot expand rules not in this capsule: ${unknown.join(', ')}`);
|
|
63
63
|
const duplicateOf = findDuplicates(ranked.filter(item => !expand.includes(item.slug)));
|
|
64
64
|
const tiers = new Map(ranked.map(item => [item.slug, 'omitted']));
|
|
65
|
+
const isEvidence = item => item.taskEvidence && !item.required;
|
|
66
|
+
const supplement = supplemental.filter(item => item.text && !isEvidence(item)).map(item => item.text.trim());
|
|
67
|
+
const evidence = supplemental.filter(item => item.text && isEvidence(item)).map(item => item.text.trim()).join('\n\n---\n\n');
|
|
65
68
|
|
|
66
69
|
const render = () => {
|
|
67
70
|
const of = tier => ranked.filter(item => tiers.get(item.slug) === tier);
|
|
@@ -71,10 +74,11 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
|
|
|
71
74
|
const blocks = [
|
|
72
75
|
...full.map(item => item.text.trim()),
|
|
73
76
|
...summary.map(item => `[${item.slug}] ${summarizeRule(item.body)}`),
|
|
74
|
-
...
|
|
77
|
+
...supplement,
|
|
75
78
|
];
|
|
76
79
|
if (reference.length) blocks.push(`Referenced: ${reference.map(item => item.slug).join(', ')}`);
|
|
77
80
|
if (duplicate.length) blocks.push(`Duplicates: ${duplicate.map(item => `${item.slug} (of ${duplicateOf.get(item.slug)})`).join(', ')}`);
|
|
81
|
+
if (evidence) blocks.push(evidence);
|
|
78
82
|
const body = blocks.join('\n\n---\n\n');
|
|
79
83
|
// The token count sits inside the text it counts, so iterate to a stable value.
|
|
80
84
|
let tokens = estimateTokens(body);
|
|
@@ -142,5 +146,6 @@ export function compactRules(items, { budget, expand = [], header, complete = fa
|
|
|
142
146
|
for (const slug of duplicateOf.keys()) tryTier(ranked.find(item => item.slug === slug), 'duplicate');
|
|
143
147
|
|
|
144
148
|
const { context, counts, tokens } = render();
|
|
145
|
-
return { context, counts, tokens, tiers: Object.fromEntries(tiers)
|
|
149
|
+
return { context, counts, tokens, tiers: Object.fromEntries(tiers),
|
|
150
|
+
...(evidence && { evidenceStart: context.length - evidence.length }) };
|
|
146
151
|
}
|
|
@@ -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';
|
|
@@ -109,7 +112,10 @@ export async function compileContext({
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
const cliContributions = cliVerbContribution ? [cliVerbContribution] : [];
|
|
112
|
-
const
|
|
115
|
+
const evidence = knowledge.summary.selected
|
|
116
|
+
? knowledge.contributions.map(item => ({ ...item, taskEvidence: true }))
|
|
117
|
+
: knowledge.contributions;
|
|
118
|
+
const supporting = [...evidence, ...cliContributions];
|
|
113
119
|
const result = assembleContext([...required, ...supporting, ...contributions], { total: b.total });
|
|
114
120
|
const evidenceMissing = supporting.some(item => !result.stats.contributions.some(placed => placed.id === item.id));
|
|
115
121
|
// Compaction may reclaim rule explanation space; never claim readiness after losing selected task evidence.
|
|
@@ -139,9 +145,26 @@ export async function compileContext({
|
|
|
139
145
|
result.stats.slots = { invariants: { nodes: rules.filter(n => n.category === 'invariants').length,
|
|
140
146
|
tokens: result.stats.required_tokens } };
|
|
141
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);
|
|
142
150
|
return result;
|
|
143
151
|
}
|
|
144
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
|
+
|
|
145
168
|
/**
|
|
146
169
|
* Second line of every text capsule, so output that is truncated or piped through
|
|
147
170
|
* `head` still carries the rule (the rule once sat at line 544 of the output and
|
|
@@ -163,6 +186,8 @@ export function capsuleResponse(result, { total = 4000, debug = false, format =
|
|
|
163
186
|
retrieval: summarizeRetrieval(result.stats.retrieval),
|
|
164
187
|
};
|
|
165
188
|
const response = { ready: result.ready, context: result.context, stats };
|
|
189
|
+
// This offset refers to the decoded context string, not the escaped JSON bytes.
|
|
190
|
+
if (result.evidenceStart !== undefined) response.evidence_start = result.evidenceStart;
|
|
166
191
|
stats.budget = total;
|
|
167
192
|
// Counts affect serialization themselves. Iterate to a stable representation.
|
|
168
193
|
for (let i = 0; i < 20; i++) {
|
|
@@ -100,14 +100,18 @@ export function assembleContext(contributions, { total = 4000 } = {}) {
|
|
|
100
100
|
if (estimateTokens(render([...selected, item])) <= total) selected.push(item);
|
|
101
101
|
else excluded.push(item.id);
|
|
102
102
|
}
|
|
103
|
-
const
|
|
103
|
+
const isEvidence = item => item.taskEvidence && !item.required;
|
|
104
|
+
const evidence = selected.filter(isEvidence);
|
|
105
|
+
const ordered = [...selected.filter(item => !isEvidence(item)), ...evidence];
|
|
106
|
+
const context = render(ordered);
|
|
107
|
+
const evidenceText = render(evidence);
|
|
104
108
|
const totalTokens = estimateTokens(context);
|
|
105
|
-
return { context, ready: requiredTokens <= total, stats: {
|
|
109
|
+
return { context, ...(evidenceText && { evidenceStart: context.length - evidenceText.length }), ready: requiredTokens <= total, stats: {
|
|
106
110
|
token_measurement: 'estimated_chars_divided_by_four', total_tokens: totalTokens,
|
|
107
111
|
budget_used: totalTokens, budget_remaining: Math.max(0, total - totalTokens),
|
|
108
112
|
required_tokens: requiredTokens, overflow_tokens: Math.max(0, requiredTokens - total),
|
|
109
113
|
required_ids: required.map(c => c.id), excluded_ids: excluded,
|
|
110
|
-
contributions:
|
|
114
|
+
contributions: ordered.map(c => ({ id: c.id, required: Boolean(c.required), chars: c.text.length, estimated_tokens: estimateTokens(c.text) })),
|
|
111
115
|
version: crypto.createHash('sha256').update(JSON.stringify({ total, contributions })).digest('hex'),
|
|
112
116
|
} };
|
|
113
117
|
}
|
|
@@ -37,6 +37,28 @@ export function getDaemonStatus() {
|
|
|
37
37
|
return status === 'not-started' ? 'not_started' : status === 'stopped' ? 'dead' : status;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Remove the lock of a daemon that is gone, for a server told to run none.
|
|
42
|
+
*
|
|
43
|
+
* startDaemon is the only other thing that recovers this lock, and such a server never
|
|
44
|
+
* calls it. A daemon that went down with the machine left a lock nothing would ever
|
|
45
|
+
* clear, so the server reported `dead` and `degraded` for good. Only a lock whose process
|
|
46
|
+
* is verified gone is removed: a live process or one that cannot be identified keeps it.
|
|
47
|
+
* @returns {boolean} True if a lock was removed.
|
|
48
|
+
*/
|
|
49
|
+
export function clearStaleDaemonLock({ observe = getDaemonObservation } = {}) {
|
|
50
|
+
try {
|
|
51
|
+
const before = fs.readFileSync(PID_FILE, 'utf8');
|
|
52
|
+
if (observe().status !== 'stopped') return false;
|
|
53
|
+
if (fs.readFileSync(PID_FILE, 'utf8') !== before) return false;
|
|
54
|
+
fs.unlinkSync(PID_FILE);
|
|
55
|
+
logger.info('daemon-control', `Removed the lock of daemon PID ${before.trim()}, which is gone. This server runs no daemon.`);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
/**
|
|
41
63
|
* Detaches the daemon script in the background and writes its PID to file.
|
|
42
64
|
* @returns {number} The PID of the newly spawned daemon.
|
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. */
|