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
|
@@ -2,18 +2,22 @@ import { installPlugin as install } from '../../core/plugin-distribution.mjs';
|
|
|
2
2
|
import { syncPluginCommands } from '../command.mjs';
|
|
3
3
|
|
|
4
4
|
export async function installPlugin(args = []) {
|
|
5
|
-
|
|
5
|
+
// Every plugin installs into the shared folder; `--global` is accepted and changes nothing.
|
|
6
6
|
const isLink = args.includes('--link') || args.includes('-l');
|
|
7
7
|
const source = args.find(a => !a.startsWith('-'));
|
|
8
8
|
|
|
9
|
+
if (args.includes('--project')) {
|
|
10
|
+
console.error("❌ Plugins are installed only in the shared folder. In a repository, switch one with 'total-recall plugin on|off <id>'.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
9
13
|
if (!source) {
|
|
10
14
|
console.error('❌ Error: Missing plugin source.');
|
|
11
|
-
console.error(' Usage: total-recall plugin install <bundled-id | share-link | peer:<host>/<id> | git-url | path> [--link]
|
|
15
|
+
console.error(' Usage: total-recall plugin install <bundled-id | share-link | peer:<host>/<id> | git-url | path> [--link]\n');
|
|
12
16
|
process.exit(1);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
try {
|
|
16
|
-
const result = await install(source, { projectRoot: process.cwd(),
|
|
20
|
+
const result = await install(source, { projectRoot: process.cwd(), link: isLink, replace:args.includes('--replace') });
|
|
17
21
|
const from = result.source.kind === 'peer' ? `peer ${result.source.peer_hostname}` : result.source.kind;
|
|
18
22
|
console.log(`\n✅ Installed '${result.plugin.name}' (id: ${result.plugin.id}, v${result.plugin.version}) from ${from}`);
|
|
19
23
|
console.log(` Location: ${result.plugin.dir}`);
|
|
@@ -24,7 +28,7 @@ export async function installPlugin(args = []) {
|
|
|
24
28
|
} else if (result.dependencies?.reason && !['no package.json', 'no dependencies declared'].includes(result.dependencies.reason)) {
|
|
25
29
|
console.log(` Dependencies: NOT installed (${result.dependencies.reason})`);
|
|
26
30
|
}
|
|
27
|
-
const cmds = syncPluginCommands(result.plugin.dir, { global:
|
|
31
|
+
const cmds = syncPluginCommands(result.plugin.dir, { global: true });
|
|
28
32
|
if (cmds.created.length) console.log(` Commands: ${cmds.created.map((c) => `total-recall ${c}`).join(', ')}`);
|
|
29
33
|
for (const c of cmds.skipped) console.log(` Skipped command '${c.name}': ${c.reason}`);
|
|
30
34
|
console.log('');
|
|
@@ -34,17 +38,8 @@ export async function installPlugin(args = []) {
|
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
36
40
|
const { autoCompileSurfaces } = await import('../../core/auto-compile.mjs');
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
// resolves to the global plugins dir, so a plugin installed into the global
|
|
40
|
-
// brain still reports scope 'project'. Ask the resolver whether a project
|
|
41
|
-
// brain exists at all, and otherwise fall back to the global layer — the one
|
|
42
|
-
// that was really written. Assuming 'project' compiled nothing and printed
|
|
43
|
-
// "Auto-compile failed: No project brain found" after a successful install.
|
|
44
|
-
const { resolveBrainDir } = await import('../agent-dir.mjs');
|
|
45
|
-
let targetLayer = 'global';
|
|
46
|
-
try { resolveBrainDir('project'); targetLayer = 'project'; } catch { targetLayer = 'global'; }
|
|
47
|
-
if (!await autoCompileSurfaces({ layer: targetLayer, semantic: false, force: true })) process.exitCode = 1;
|
|
41
|
+
// The plugin landed in the shared folder, so the global layer is the one to compile.
|
|
42
|
+
if (!await autoCompileSurfaces({ layer: 'global', semantic: false, force: true })) process.exitCode = 1;
|
|
48
43
|
} catch (err) {
|
|
49
44
|
console.error(`❌ ${err.message}`);
|
|
50
45
|
process.exit(1);
|
|
@@ -8,7 +8,7 @@ export async function removePlugin(args = []) {
|
|
|
8
8
|
|
|
9
9
|
if (!id) {
|
|
10
10
|
console.error('❌ Error: Missing plugin ID to remove.');
|
|
11
|
-
console.error(' Usage: total-recall plugin remove <id
|
|
11
|
+
console.error(' Usage: total-recall plugin remove <id>\n');
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ function parseArgs(args) {
|
|
|
22
22
|
* `includes` makes a short query match inside unrelated words: searching "git"
|
|
23
23
|
* returned the signing plugin, whose description begins "Digital agreement…".
|
|
24
24
|
* A hit inside a word is only accepted at a word start so that real substrings
|
|
25
|
-
* still work ("
|
|
25
|
+
* still work ("qual" finds "code-quality") without matching "digital".
|
|
26
26
|
*/
|
|
27
27
|
function matches(p, query, useCase) {
|
|
28
28
|
if (useCase && !(p.use_cases || []).includes(useCase)) return false;
|
|
@@ -32,7 +32,7 @@ function matches(p, query, useCase) {
|
|
|
32
32
|
const words = query.split(/\s+/).filter(Boolean);
|
|
33
33
|
return words.every((word) => {
|
|
34
34
|
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
35
|
-
// Anchor at a word start so "
|
|
35
|
+
// Anchor at a word start so "qual" matches "code-quality" but "git" does
|
|
36
36
|
// not match "digital".
|
|
37
37
|
const bounded = new RegExp(`(^|[^a-z0-9])${escaped}`, 'i');
|
|
38
38
|
return haystacks.some((h) => bounded.test(h) || h.startsWith(word));
|
package/src/cli/preflight.mjs
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { ensureDependencies } from '../core/ensure-dependencies.mjs';
|
|
4
5
|
import { detectProjectBrain } from '../core/brain-locate.mjs';
|
|
5
6
|
import { globalBrainDirFor, isBrainEnabled } from '../core/brain-registry.mjs';
|
|
6
7
|
import { GATE_MODES, gateConfigPath, projectBrainDir, readGateLayers, resolveGateMode, writeGateMode } from '../core/gate-mode.mjs';
|
|
7
8
|
import { installHarnessHooks, loadGateAdapters } from '../core/harness-hooks.mjs';
|
|
8
|
-
import { ackDirectory, capsuleVerb, decide, isAckCommand, isCheckCommand, isRouteCommand, isSubagentPayload, nativeToolOf, sessionOf, SWEEP_CONSULT_MS, whenGateFails } from '../core/preflight-gate.mjs';
|
|
9
|
+
import { ackDirectory, capsuleVerb, decide, gatePackageBroken, isAckCommand, isCheckCommand, isRouteCommand, isSubagentPayload, nativeToolOf, sessionOf, SWEEP_CONSULT_MS, whenGateFails } from '../core/preflight-gate.mjs';
|
|
9
10
|
import { ACK_COMMAND, acknowledge, checkAgentReceipt, checkAnyReceipt, findRoot, forgetCapsuleDeliveries, lastArrivalMs, lastCapsuleDelivery, lastConsultMs, lastRoutedMs, lastRouteWarnedMs, lastSessionAck, markArrival, markDelegated, markRouted, markRouteWarned, markSessionAck, noteCaller, recordDecision, sessionHasStanding, sessionKey, readDecisions, takeCaller } from '../core/preflight-receipt.mjs';
|
|
10
11
|
|
|
11
12
|
const USAGE = `Usage: total-recall preflight <command>
|
|
12
13
|
ack --task "<task>" [--action read,edit,test] [--json] Record a receipt after context ready:true and rules verify, and print the capsule.
|
|
13
14
|
Prints one line instead when this session received the same rules and skills within 2 hours
|
|
15
|
+
Restores missing or stale locked dependencies for edit/test actions
|
|
14
16
|
check [--json] Exit 1 unless a fresh receipt covers this agent, wherever it acknowledged.
|
|
15
17
|
Run by hand, it reports this directory's receipt
|
|
16
18
|
gate [--harness <id>] Hook entry: read a call on stdin, answer allow or deny (exit 2 on deny)
|
|
@@ -81,12 +83,23 @@ async function ack(args) {
|
|
|
81
83
|
const task = flag(args, '--task') || '';
|
|
82
84
|
const actions = flag(args, '--action') || 'read,edit,test';
|
|
83
85
|
const result = acknowledge({ root, task, actions, maxAgeMs: await configuredMaxAgeMs(), session: takeCaller(root, 'ack', { task }) });
|
|
86
|
+
const needsDependencies = actions.split(',').some(action => ['edit', 'test'].includes(action.trim()));
|
|
87
|
+
const dependencies = result.ok && needsDependencies ? await ensureDependencies(root, { reason: `preflight:${actions}` }) : null;
|
|
88
|
+
const ok = result.ok && dependencies?.ok !== false;
|
|
84
89
|
if (args.includes('--json')) {
|
|
85
|
-
console.log(JSON.stringify({ ok: result.
|
|
86
|
-
if (!
|
|
90
|
+
console.log(JSON.stringify({ ok, problems: [...result.problems, ...(dependencies?.ok === false ? [`dependencies: ${dependencies.error}`] : [])], receipt: result.file || null, capsule: result.capsule ?? null, unchanged: Boolean(result.unchanged), dependencies }));
|
|
91
|
+
if (!ok) process.exitCode = 1;
|
|
87
92
|
return;
|
|
88
93
|
}
|
|
89
94
|
if (!result.ok) return printFailure(result.problems);
|
|
95
|
+
if (dependencies) {
|
|
96
|
+
const detail = dependencies.ok ? (dependencies.command?.join(' ') || dependencies.trigger) : dependencies.error;
|
|
97
|
+
console.log(` dependencies: ${dependencies.status} — ${detail}`);
|
|
98
|
+
if (!dependencies.ok) {
|
|
99
|
+
console.error('✖ Dependency install failed. Repair dependencies before edit or test work. The instruction receipt is recorded.');
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
90
103
|
if (result.unchanged) {
|
|
91
104
|
const age = result.unchanged.ageMs < 60000 ? 'less than a minute ago' : `${Math.round(result.unchanged.ageMs / 60000)} minutes ago`;
|
|
92
105
|
console.log(`✔ tr-preflight recorded: rules and skills are unchanged since this session received the capsule ${age}, so there is nothing new to read. If your task is new, or you no longer have the capsule, run: total-recall context "<task>" --action ${actions}`);
|
|
@@ -398,6 +411,8 @@ function evaluate(call, options = {}) {
|
|
|
398
411
|
}
|
|
399
412
|
|
|
400
413
|
function judge(call, { harness, readablePaths, session = null, subagent = false, nativeTool = null } = {}) {
|
|
414
|
+
const broken = gatePackageBroken();
|
|
415
|
+
if (broken) return whenGateFails(call, new Error(broken));
|
|
401
416
|
const root = findRoot(call.cwd || process.cwd());
|
|
402
417
|
const limit = envMaxAgeMs();
|
|
403
418
|
const declared = readGateDeclarations();
|
|
@@ -584,12 +599,10 @@ async function mode(args) {
|
|
|
584
599
|
|
|
585
600
|
export default async function preflight(args = []) {
|
|
586
601
|
const [first = '--help'] = args;
|
|
587
|
-
if (first === '--help' || first === '-h' || first === 'help') return console.log(USAGE);
|
|
602
|
+
if (first === '--help' || first === '-h' || first === 'help' || args.includes('--help') || args.includes('-h')) return console.log(USAGE);
|
|
588
603
|
// Original `tr-preflight` forms.
|
|
589
604
|
if (first === '--ack' || first === 'ack') return ack(args);
|
|
590
605
|
if (first === '--check' || first === 'check') return check(args);
|
|
591
|
-
// Help never performs the subcommand: `install --help` once rewrote the harness settings (C-019).
|
|
592
|
-
if (args.includes('--help') || args.includes('-h')) return console.log(USAGE);
|
|
593
606
|
if (first === 'gate') return gate(args.slice(1));
|
|
594
607
|
if (first === 'remind') return remind(args);
|
|
595
608
|
if (first === 'session-start') return sessionStart();
|
package/src/cli/proposals.mjs
CHANGED
|
@@ -208,7 +208,7 @@ export default async function proposals(args) {
|
|
|
208
208
|
console.log(` ${String(days).padStart(4)}d [i${n.importance}] ${n.slug}`);
|
|
209
209
|
}
|
|
210
210
|
if (stale.length > 40) console.log(`\n … and ${stale.length - 40} more.`);
|
|
211
|
-
console.log('\n
|
|
211
|
+
console.log('\n This query is read-only. Request research explicitly for a node that needs verification.\n');
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
|
package/src/cli/recall.mjs
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { resolveAgentDir, resolveBrainDir, parseLayerFlag, getBothBrains } from './agent-dir.mjs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
/** One brain search shared by recall and paired evaluation. Does not record access. */
|
|
6
|
+
export async function searchRecallBrain(query, { semantic = false, forceFast = false, field = false, ...options } = {}) {
|
|
7
|
+
let rankResults = null;
|
|
8
|
+
if (field) {
|
|
9
|
+
const { loadField, rankWithField } = await import('../core/vector-field.mjs');
|
|
10
|
+
const compiled = loadField(options.derivedDir);
|
|
11
|
+
if (!compiled) throw new Error('Vector field missing or corrupt. Run total-recall recall --compile-field first.');
|
|
12
|
+
rankResults = results => rankWithField(results, { field: compiled, derivedDir: options.derivedDir });
|
|
13
|
+
}
|
|
14
|
+
const { fastSearch } = await import('../core/fast-recall.mjs');
|
|
15
|
+
const fastResults = fastSearch(query, { ...options, fullText: !forceFast, rankResults });
|
|
16
|
+
if (!semantic || fastResults.some(result => (result.base_score ?? result.score) === 1) || forceFast) return fastResults;
|
|
17
|
+
const { semanticSearch } = await import('../core/search.mjs');
|
|
18
|
+
return semanticSearch(query, { ...options, rankResults });
|
|
19
|
+
}
|
|
3
20
|
|
|
4
21
|
function printHelp() {
|
|
5
22
|
console.log(`
|
|
@@ -20,8 +37,18 @@ function printHelp() {
|
|
|
20
37
|
--fast Force frontmatter-only search without semantic fallback
|
|
21
38
|
--local Search indexed body text without a provider (default)
|
|
22
39
|
--semantic Opt into semantic enrichment
|
|
40
|
+
--field Re-rank candidates with the compiled field (opt-in, local)
|
|
41
|
+
--compile-field Compile existing embeddings into the selected brain's field
|
|
42
|
+
--evaluate-field <file> Compare local recall with/without field on fixed JSON queries
|
|
23
43
|
--timings Print local stage timings on stderr
|
|
24
44
|
|
|
45
|
+
Field operations (support --project/--global, --top-k, --format):
|
|
46
|
+
total-recall recall --compile-field --project
|
|
47
|
+
total-recall recall --evaluate-field queries.json --project --format json
|
|
48
|
+
Query file: [{ "query": "search terms", "relevant": ["expected-slug"] }]
|
|
49
|
+
Overlap@k = shared slugs / k. Hit rate@k = queries with a relevant slug / queries.
|
|
50
|
+
Evaluation does not record access. It uses local recall and requires a compiled field.
|
|
51
|
+
|
|
25
52
|
Examples:
|
|
26
53
|
npx total-recall recall "Never run tsc directly"
|
|
27
54
|
npx total-recall recall "Express server port" --top-k 3
|
|
@@ -55,6 +82,7 @@ export default async function recall(args) {
|
|
|
55
82
|
let forceFast = false;
|
|
56
83
|
let semantic = false;
|
|
57
84
|
let timings = false;
|
|
85
|
+
let field = false;
|
|
58
86
|
|
|
59
87
|
for (let i = 1; i < remainingArgs.length; i++) {
|
|
60
88
|
const arg = remainingArgs[i];
|
|
@@ -111,8 +139,11 @@ export default async function recall(args) {
|
|
|
111
139
|
semantic = false;
|
|
112
140
|
} else if (arg === '--timings') {
|
|
113
141
|
timings = true;
|
|
142
|
+
} else if (arg === '--field') {
|
|
143
|
+
field = true;
|
|
114
144
|
}
|
|
115
145
|
}
|
|
146
|
+
top_k = Math.max(1, Math.min(20, top_k));
|
|
116
147
|
|
|
117
148
|
// Determine which brains to search
|
|
118
149
|
const brains = getBothBrains();
|
|
@@ -152,6 +183,26 @@ export default async function recall(args) {
|
|
|
152
183
|
}
|
|
153
184
|
|
|
154
185
|
try {
|
|
186
|
+
if (query === '--compile-field' || query === '--evaluate-field') {
|
|
187
|
+
const reports = [];
|
|
188
|
+
const cases = query === '--evaluate-field'
|
|
189
|
+
? JSON.parse(fs.readFileSync(remainingArgs[1], 'utf8')) : null;
|
|
190
|
+
for (const target of searchTargets) {
|
|
191
|
+
const options = { vaultDir: path.join(target.brainDir, 'memory-vault'),
|
|
192
|
+
derivedDir: path.join(target.brainDir, 'memory-derived'), top_k, includeSessions: false };
|
|
193
|
+
if (query === '--evaluate-field') {
|
|
194
|
+
const { evaluateField } = await import('../core/field-evaluation.mjs');
|
|
195
|
+
reports.push({ brain: target.label, ...await evaluateField(cases, {
|
|
196
|
+
topK: top_k, search: (q, enabled) => searchRecallBrain(q, { ...options, field: enabled }),
|
|
197
|
+
}) });
|
|
198
|
+
} else {
|
|
199
|
+
const { compileField } = await import('../core/vector-field.mjs');
|
|
200
|
+
reports.push({ brain: target.label, ...(await compileField(options)).meta });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
155
206
|
// Search all target brains and merge results
|
|
156
207
|
let allResults = [];
|
|
157
208
|
let isDegraded = false;
|
|
@@ -164,8 +215,7 @@ export default async function recall(args) {
|
|
|
164
215
|
|
|
165
216
|
try {
|
|
166
217
|
let results = [];
|
|
167
|
-
|
|
168
|
-
const fastResults = fastSearch(query, {
|
|
218
|
+
results = await searchRecallBrain(query, {
|
|
169
219
|
derivedDir,
|
|
170
220
|
vaultDir,
|
|
171
221
|
top_k,
|
|
@@ -174,28 +224,12 @@ export default async function recall(args) {
|
|
|
174
224
|
modality,
|
|
175
225
|
importance,
|
|
176
226
|
priority,
|
|
177
|
-
|
|
227
|
+
forceFast, semantic, field,
|
|
228
|
+
includeSessions: includeSessions && target.label !== 'global',
|
|
178
229
|
});
|
|
179
|
-
if (timings) console.error(JSON.stringify({ brain: target.label, ...
|
|
180
|
-
if (['missing', 'corrupt', 'metadata-only'].includes(
|
|
181
|
-
console.error(`Local full-text index for ${target.label}: ${
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!semantic || fastResults.some(result => result.score === 1) || forceFast) {
|
|
185
|
-
results = fastResults;
|
|
186
|
-
} else {
|
|
187
|
-
const { semanticSearch } = await import('../core/search.mjs');
|
|
188
|
-
results = await semanticSearch(query, {
|
|
189
|
-
vaultDir,
|
|
190
|
-
derivedDir,
|
|
191
|
-
top_k,
|
|
192
|
-
includeSessions: includeSessions && target.label !== 'global', // sessions only in project/local
|
|
193
|
-
category,
|
|
194
|
-
tags,
|
|
195
|
-
modality,
|
|
196
|
-
importance,
|
|
197
|
-
priority
|
|
198
|
-
});
|
|
230
|
+
if (timings) console.error(JSON.stringify({ brain: target.label, ...results.stats }));
|
|
231
|
+
if (['missing', 'corrupt', 'metadata-only'].includes(results.stats?.index) && !forceFast) {
|
|
232
|
+
console.error(`Local full-text index for ${target.label}: ${results.stats.index}. Run total-recall compile to rebuild; available metadata results may be incomplete.`);
|
|
199
233
|
}
|
|
200
234
|
if (results.degradedTextSearch) {
|
|
201
235
|
isDegraded = true;
|
|
@@ -226,6 +260,15 @@ export default async function recall(args) {
|
|
|
226
260
|
allResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
227
261
|
allResults = allResults.slice(0, top_k);
|
|
228
262
|
|
|
263
|
+
// DECAY-01: a recalled memory is a used memory; record it in the brain it came from.
|
|
264
|
+
try {
|
|
265
|
+
const { recordAccess } = await import('../core/access-ledger.mjs');
|
|
266
|
+
for (const target of searchTargets) {
|
|
267
|
+
const slugs = allResults.filter(r => r._layer === target.label).map(r => r.slug || r.id).filter(Boolean);
|
|
268
|
+
recordAccess(path.join(target.brainDir, 'memory-derived'), slugs);
|
|
269
|
+
}
|
|
270
|
+
} catch { /* access accounting never breaks recall */ }
|
|
271
|
+
|
|
229
272
|
if (format === 'json') {
|
|
230
273
|
for (const error of searchErrors) console.error(`Search failed for ${error.label}: ${error.message.split('\n')[0]}`);
|
|
231
274
|
if (searchErrors.length && !allResults.length) process.exitCode = 1;
|
package/src/cli/release.mjs
CHANGED
|
@@ -141,7 +141,10 @@ export async function tarballServed(pkg, version, { exec = spawnSync, fetchImpl
|
|
|
141
141
|
* Wait until a just-published version can be installed: listed as latest, and its tarball served.
|
|
142
142
|
* 3.42.0 took about six minutes and 3.42.1 about eight, its listing four minutes ahead of its tarball.
|
|
143
143
|
*/
|
|
144
|
-
|
|
144
|
+
/** npm has taken 8 and then 23 minutes to serve an accepted publish, so a shorter wait reports failure on a good release. */
|
|
145
|
+
export const REGISTRY_WAIT = Object.freeze({ attempts: 120, delayMs: 20000 });
|
|
146
|
+
|
|
147
|
+
export async function awaitPublished(pkg, version, { attempts = REGISTRY_WAIT.attempts, delayMs = REGISTRY_WAIT.delayMs, exec = spawnSync, sleep, tarball = tarballServed } = {}) {
|
|
145
148
|
const pause = sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
146
149
|
let last = null;
|
|
147
150
|
for (let i = 0; i < attempts; i++) {
|
package/src/cli/remember.mjs
CHANGED
|
@@ -37,6 +37,7 @@ function printHelp() {
|
|
|
37
37
|
--object <string> SPO Object (default: brain)
|
|
38
38
|
--related <list> Comma-separated list of related slugs
|
|
39
39
|
--expires <duration> TTL for temporary rules (e.g. "7d", "2w", "30d", "6h", "3m")
|
|
40
|
+
--allow-long Permit a rule above 800 characters (context:policy also permits it)
|
|
40
41
|
--confined-fact Create a descriptive fact with generated identity; refresh local search without executable generators
|
|
41
42
|
|
|
42
43
|
Examples:
|
|
@@ -74,9 +75,6 @@ function parseDuration(str) {
|
|
|
74
75
|
return date;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
const RULE_SOFT_LIMIT = 300;
|
|
78
|
-
const RULE_HARD_LIMIT = 800;
|
|
79
|
-
|
|
80
78
|
export default async function remember(args) {
|
|
81
79
|
// Parse layer flag first
|
|
82
80
|
const { layer: explicitLayer, remainingArgs: layerArgs } = parseLayerFlag(args);
|
|
@@ -244,16 +242,6 @@ export default async function remember(args) {
|
|
|
244
242
|
}
|
|
245
243
|
}
|
|
246
244
|
|
|
247
|
-
// Rules load into every task capsule, so they must be brief.
|
|
248
|
-
if (['invariants', 'preferences', 'anti-patterns'].includes(category) && !tags.includes('context:policy')) {
|
|
249
|
-
const n = bodyContent.trim().length;
|
|
250
|
-
if (n > RULE_HARD_LIMIT && !allowLong) {
|
|
251
|
-
console.error(`❌ Rule is ${n} characters (limit ${RULE_HARD_LIMIT}). Rules must be brief: one or two sentences, no history, dates, quotes or incident detail (save those as a fact). Shorten it, or pass --allow-long.`);
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
if (n > RULE_SOFT_LIMIT) console.error(`⚠️ Rule is ${n} characters. Keep rules brief (under ${RULE_SOFT_LIMIT}): state the constraint only, move history and examples to a fact. Shorten with: total-recall edit <slug> "<shorter>"`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
245
|
const resolvedAgentDir = resolveAgentDir(layer);
|
|
258
246
|
const resolvedBrainDir = resolveBrainDir(layer);
|
|
259
247
|
const skillsDir = path.join(resolvedAgentDir, 'skills');
|
|
@@ -344,7 +332,7 @@ export default async function remember(args) {
|
|
|
344
332
|
|
|
345
333
|
// Saving memory never optimizes or archives other canonical documents.
|
|
346
334
|
|
|
347
|
-
const vaultResult = await writeNodeValidatedAsync(node, vaultDir);
|
|
335
|
+
const vaultResult = await writeNodeValidatedAsync(node, vaultDir, { allowLong });
|
|
348
336
|
if (!vaultResult.success) {
|
|
349
337
|
console.error(` ❌ Validation failed: ${vaultResult.validation.errors.join('; ')}`);
|
|
350
338
|
if (vaultResult.repair) {
|
|
@@ -355,6 +343,7 @@ export default async function remember(args) {
|
|
|
355
343
|
}
|
|
356
344
|
process.exit(1);
|
|
357
345
|
}
|
|
346
|
+
for (const warning of vaultResult.validation?.warnings || []) console.error(` ⚠️ ${warning}`);
|
|
358
347
|
console.log(` ✅ Permanent SSSS memory node created ${layerLabel} in vault: memory-vault/${category}/${finalSlug}.md`);
|
|
359
348
|
|
|
360
349
|
// Automatically recompile surfaces and local indexes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ensureServerService } from '../core/server-service.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function server(args = []) {
|
|
4
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
5
|
+
console.log('Usage: total-recall server ensure [--dry-run]\nRepair this package\'s macOS LaunchAgent and verify its process. --dry-run prints the plist diff without changes. On systemd hosts, server-release owns the server.');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (args[0] !== 'ensure' || args.slice(1).some(arg => arg !== '--dry-run')) {
|
|
9
|
+
console.error('Usage: total-recall server ensure [--dry-run]');
|
|
10
|
+
process.exitCode = 2;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const result = await ensureServerService({ dryRun: args.includes('--dry-run') });
|
|
15
|
+
console.log(result.diff || result.reason || `Server verified: pid ${result.pid}, ${result.entry}`);
|
|
16
|
+
return result;
|
|
17
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
18
|
+
}
|
package/src/cli/startup.mjs
CHANGED
|
@@ -36,6 +36,7 @@ export default async function startup(args = []) {
|
|
|
36
36
|
if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
|
|
37
37
|
else {
|
|
38
38
|
console.log(`Startup: required plugins ${report.required_plugins?.status || 'unknown'}; server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
|
|
39
|
+
if (report.server?.reason) console.log(`Server: ${report.server.reason}`);
|
|
39
40
|
if (report.server?.stale) console.log(`Server: running ${report.server.stale.running} with ${report.server.stale.on_disk} on disk; it has not been replaced.`);
|
|
40
41
|
for (const error of report.required_plugins?.errors || []) console.log(`Required plugin: ${error}`);
|
|
41
42
|
const harnesses = await queryHarnessUsage().catch(() => []);
|
package/src/cli/task.mjs
CHANGED
|
@@ -34,10 +34,11 @@ function printHelp() {
|
|
|
34
34
|
show <slug> Show one task
|
|
35
35
|
cancel <slug> Cancel a task, optionally with --expected-status pending
|
|
36
36
|
executors List registered executor ids
|
|
37
|
+
idle Enqueue leader maintenance from an automations manifest task
|
|
37
38
|
|
|
38
39
|
add options:
|
|
39
40
|
--kind <k> memory | research | maintenance | system | custom (default: custom)
|
|
40
|
-
--executor <id> dream | custom | research |
|
|
41
|
+
--executor <id> dream | custom | research | legacy
|
|
41
42
|
--priority <p> high | normal | low | absolute | 0-100 (default: normal)
|
|
42
43
|
--cap <list> Comma-separated capabilities (default: vault:read)
|
|
43
44
|
Use vault:write to draft results into memory-inbox
|
|
@@ -58,7 +59,7 @@ function printHelp() {
|
|
|
58
59
|
|
|
59
60
|
Policy:
|
|
60
61
|
shell / net:post capabilities are denied by default.
|
|
61
|
-
|
|
62
|
+
Empty queues stay empty. Automations owns the idle schedule.
|
|
62
63
|
`);
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -168,6 +169,12 @@ export default async function taskCli(argv) {
|
|
|
168
169
|
const brainDir = resolveBrainDir(layer);
|
|
169
170
|
const queueDir = resolveQueueDir(brainDir);
|
|
170
171
|
|
|
172
|
+
if (opts.command === 'idle') {
|
|
173
|
+
const { enqueueScheduledIdleTasks } = await import('../core/scheduler.mjs');
|
|
174
|
+
console.log(JSON.stringify(await enqueueScheduledIdleTasks({ queueDir, vaultDir: path.join(brainDir, 'memory-vault') })));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
171
178
|
if (opts.command === 'executors') {
|
|
172
179
|
console.log('\n Registered executors:\n');
|
|
173
180
|
for (const id of listExecutorIds()) {
|
package/src/cli/update.mjs
CHANGED
|
@@ -50,6 +50,7 @@ export function parseArgs(args) {
|
|
|
50
50
|
else if (a === '--port' && args[i + 1]) opts.port = Number(args[++i]);
|
|
51
51
|
else if (a === '--no-build') opts.noBuild = true;
|
|
52
52
|
else if (a === '--all-brains') opts.allBrains = true;
|
|
53
|
+
else if (a === '--install-missing') opts.installMissing = true;
|
|
53
54
|
else if (a === '--json') opts.json = true;
|
|
54
55
|
else if (a === '--automatic') opts.automatic = true;
|
|
55
56
|
else if (a === '--status') opts.status = true;
|
|
@@ -70,16 +71,20 @@ function printHelp() {
|
|
|
70
71
|
total-recall update --apply --force Ignore throttle / disabled flag
|
|
71
72
|
total-recall update --repo <path> Include extra root (repeatable)
|
|
72
73
|
total-recall update --all-brains Plan SSSS + Total Recall updates, including plugins/nested packages
|
|
73
|
-
total-recall update --all-brains --apply [--json]
|
|
74
|
+
total-recall update --all-brains --apply [--install-missing] [--json]
|
|
74
75
|
Pin the same published versions, use npm/pnpm, replace shared dependency
|
|
75
76
|
links with independent local installs, and verify every resolved version.
|
|
77
|
+
A checkout with no node_modules keeps none: only its pin is updated.
|
|
78
|
+
--install-missing installs those trees too (never done automatically).
|
|
76
79
|
A plugin checkout linked into a brain but not registered as a project is
|
|
77
80
|
only installed here: its dev dependencies are skipped (it runs under the
|
|
78
81
|
shared install) and its runtime ones update with --omit=dev, without
|
|
79
82
|
writing package.json or the lockfile. Register it to develop it here.
|
|
80
83
|
total-recall update --automatic [--json]
|
|
81
84
|
Daemon maintenance: update both packages, ensure defaults, update every
|
|
82
|
-
installed plugin from its recorded source and refresh surfaces.
|
|
85
|
+
installed plugin from its recorded source and refresh surfaces. Where a
|
|
86
|
+
per-user service (LaunchAgent, systemd user unit) runs the server from
|
|
87
|
+
the global install, also restart a stale server and verify /health.
|
|
83
88
|
total-recall update --status [--json]
|
|
84
89
|
Read the actual last automatic maintenance report without starting work.
|
|
85
90
|
Also reports what the server on this host runs against the release.
|
|
@@ -96,6 +101,7 @@ function printHelp() {
|
|
|
96
101
|
|
|
97
102
|
Env:
|
|
98
103
|
TR_AUTO_UPDATE_PACKAGE=0 Disable daemon cron auto-update (default: on)
|
|
104
|
+
TR_SERVER_AUTO_RESTART=0 Report a stale server instead of restarting it (default: on)
|
|
99
105
|
TR_SYNC_REPOS=/a:/b Extra project roots
|
|
100
106
|
|
|
101
107
|
Source monorepos named ${PACKAGE_NAME} are skipped (use git, not npm into self).
|
|
@@ -124,7 +130,7 @@ export default async function update(args = []) {
|
|
|
124
130
|
|
|
125
131
|
if (opts.allBrains) {
|
|
126
132
|
const summary = await updateAllBrains({ brainDir, roots: opts.repos,
|
|
127
|
-
dryRun: !opts.apply || opts.dryRun, ensureDefaults: true });
|
|
133
|
+
dryRun: !opts.apply || opts.dryRun, ensureDefaults: true, installMissing: opts.installMissing === true });
|
|
128
134
|
if (summary.failed) process.exitCode = 1;
|
|
129
135
|
if (opts.apply && !opts.dryRun) {
|
|
130
136
|
const hooks = await installHarnessHooks({ log: (line) => { if (!opts.json) console.log(line); } });
|
|
@@ -134,6 +140,7 @@ export default async function update(args = []) {
|
|
|
134
140
|
if (opts.json) console.log(JSON.stringify(summary, null, 2));
|
|
135
141
|
else {
|
|
136
142
|
console.log(`\n Uniform brain updates: ${summary.consumer_count || 0} consumers`);
|
|
143
|
+
console.log(` Missing: ${summary.not_installed || 0}; current: ${summary.up_to_date || 0}; would update: ${summary.would_update || 0}; updated: ${summary.updated || 0}; failed: ${summary.failed || 0}`);
|
|
137
144
|
if (summary.reason) console.log(` ${summary.reason}`);
|
|
138
145
|
for (const row of summary.results) {
|
|
139
146
|
console.log(` ${row.root}: ${row.status}`);
|
|
@@ -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);
|
|
@@ -13,6 +13,7 @@ import { updateMeshBrainPackages } from './mesh-brain-updates.mjs';
|
|
|
13
13
|
import { updateSourceBrainPackages } from './source-brain-updates.mjs';
|
|
14
14
|
import { updateInstalledPlugins } from './plugin-updates.mjs';
|
|
15
15
|
import { runningServerStatus } from './running-server.mjs';
|
|
16
|
+
import { replaceStaleServer } from './server-restart.mjs';
|
|
16
17
|
|
|
17
18
|
export function automaticUpdateStatus(brainDir) {
|
|
18
19
|
let last;
|
|
@@ -37,7 +38,7 @@ export function startAutomaticBrainUpdates({brainDir,intervalMs=60*60*1000,run=r
|
|
|
37
38
|
return {tick,stop(){stopped=true;clearInterval(timer);}};
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,serverCheck=runningServerStatus,now=Date.now}={}) {
|
|
41
|
+
export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,pluginUpdate=updateInstalledPlugins,skillMaintenance=maintainPortableSkills,serverCheck=runningServerStatus,replaceServer=replaceStaleServer,now=Date.now}={}) {
|
|
41
42
|
if(!force && !isPackageAutoUpdateEnabled())return {skipped:true,reason:'disabled',results:[]};
|
|
42
43
|
const file=path.join(brainDir,'memory-derived/automatic-brain-updates.json');
|
|
43
44
|
let saved;try{saved=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
|
|
@@ -65,16 +66,20 @@ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false
|
|
|
65
66
|
const plugins=await pluginUpdate({roots:[path.resolve(brainDir,'../../..')]});
|
|
66
67
|
const mesh=fleet && summary.versions?await meshUpdate({versions:summary.versions,version:summary.versions['total-recall-brain']}):{failed:0,results:[],scope:'local-only'};
|
|
67
68
|
const changed=summary.updated>0 || plugins.updated>0 || global.results.some(row=>row.status==='updated') || sources.results.some(row=>row.status==='updated') || JSON.stringify(saved?.versions)!==JSON.stringify(summary.versions);
|
|
68
|
-
// A lagging server is reported and not counted as a failure: maintenance cannot fix it, and
|
|
69
|
-
// a failure would retry every five minutes. Replacing one in production is a deploy decision.
|
|
70
|
-
const server=await serverCheck({brainDir,latest:summary.versions?.['total-recall-brain']});
|
|
71
|
-
if(server.status==='stale' || server.status==='down')logger.error('automatic-brain-updates',`Running server: ${server.reason}`);
|
|
72
69
|
const skills=await skillMaintenance({brainDir});
|
|
73
70
|
const surfaces=await compile({brainDir,force:changed || skills.updated > 0});
|
|
71
|
+
// Last, so a replaced server starts on everything this run moved. Where maintenance cannot
|
|
72
|
+
// replace it, a lagging server is reported and not counted as a failure, which would retry
|
|
73
|
+
// every five minutes for nothing: in production that is a deploy decision. Where it can, a
|
|
74
|
+
// hold or a failed attempt is this machine's own failure, which is what makes a peer dispatch it again.
|
|
75
|
+
let server=await serverCheck({brainDir,latest:summary.versions?.['total-recall-brain']});
|
|
76
|
+
if(server.status==='stale')server=await replaceServer({brainDir,server,globalRoot:global.root,previous:saved?.server});
|
|
77
|
+
if(server.status==='stale' || server.status==='down')logger.error('automatic-brain-updates',`Running server: ${server.replacement?.reason || server.reason}`);
|
|
78
|
+
const serverFailed=['held','failed'].includes(server.replacement?.state)?1:0;
|
|
74
79
|
// local_failed is this node's own maintenance. Peers judge a node by it, not by
|
|
75
80
|
// `failed`, which also counts the node's view of its peers: judging by that made
|
|
76
81
|
// every fleet pass mark every peer failed forever, each because the others had.
|
|
77
|
-
const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length);
|
|
82
|
+
const local_failed=(summary.failed || 0)+global.failed+sources.failed+plugins.failed+(skills.failed || 0)+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length)+serverFailed;
|
|
78
83
|
report={...summary,global,sources,plugins,mesh,server,skills,surfaces,local_failed,failed:local_failed+mesh.failed,checkedAt:now()};
|
|
79
84
|
}catch(error){report={failed:1,error:error.message,checkedAt:now(),results:[]};}
|
|
80
85
|
const temporary=`${file}.${process.pid}.tmp`;fs.writeFileSync(temporary,JSON.stringify(report,null,2));fs.renameSync(temporary,file);
|