total-recall-brain 3.40.1 → 3.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -41
- package/bin/total-recall.mjs +16 -76
- package/docs/reference/cli-reference.md +10 -10
- package/extension/background.js +6 -2
- package/extension/config.js +8 -0
- package/extension/lib/brain-client.js +29 -4
- package/extension/options/options.html +1 -0
- package/extension/sidepanel/sidepanel.html +1 -0
- package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
- package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/src/api/keys.ts +9 -2
- package/frontend/src/api/plugins.ts +4 -0
- package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
- package/frontend/src/components/plugins/card/card.css +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +315 -8
- package/frontend/src/pages/PluginsPage.tsx +532 -87
- package/package.json +6 -5
- package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
- package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
- package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
- package/src/cli/brief.mjs +33 -9
- package/src/cli/command.mjs +2 -7
- package/src/cli/config.mjs +1 -0
- package/src/cli/connect.mjs +5 -0
- package/src/cli/context.mjs +2 -1
- package/src/cli/daemon.mjs +36 -1
- package/src/cli/doctor.mjs +6 -0
- package/src/cli/harness.mjs +3 -0
- package/src/cli/help.mjs +26 -0
- package/src/cli/init.mjs +4 -4
- package/src/cli/memory-entry.mjs +1 -0
- package/src/cli/memory-init.mjs +2 -0
- package/src/cli/plugin/index.mjs +11 -0
- package/src/cli/plugin/install.mjs +2 -1
- package/src/cli/rebuild.mjs +3 -1
- package/src/cli/startup.mjs +2 -1
- package/src/cli/update.mjs +19 -0
- package/src/core/automatic-brain-updates.mjs +67 -0
- package/src/core/brain-updates.mjs +21 -2
- package/src/core/cli-commands.mjs +71 -0
- package/src/core/cli-maintenance.mjs +45 -0
- package/src/core/cli-quick-reference.mjs +20 -0
- package/src/core/cli-semantic-index.mjs +722 -0
- package/src/core/context-compiler.mjs +27 -1
- package/src/core/crons.mjs +2 -13
- package/src/core/daemon-control.mjs +6 -4
- package/src/core/daemon-loop.mjs +12 -8
- package/src/core/default-plugins.mjs +5 -0
- package/src/core/global-brain-updates.mjs +39 -0
- package/src/core/mesh-brain-updates.mjs +30 -0
- package/src/core/meta-harness.mjs +108 -0
- package/src/core/plugin-dependencies.mjs +2 -1
- package/src/core/plugin-loader.mjs +2 -1
- package/src/core/plugin-runner.mjs +12 -4
- package/src/core/plugin-setup.mjs +20 -0
- package/src/core/plugin-store.mjs +22 -4
- package/src/core/plugin-tasks.mjs +1 -1
- package/src/core/project-brain.mjs +2 -0
- package/src/core/project-status.mjs +64 -0
- package/src/core/required-plugins.mjs +46 -0
- package/src/core/scaffold-plugins.mjs +17 -3
- package/src/core/source-brain-updates.mjs +47 -0
- package/src/core/startup-health.mjs +5 -3
- package/src/core/surface.mjs +44 -10
- package/src/server/routes/extension.mjs +53 -8
- package/templates/onboarding-interview.md +1 -1
- package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
|
@@ -93,7 +93,33 @@ export async function compileContext({
|
|
|
93
93
|
}).filter(candidate => candidate.relevant).sort((a, b) => b.score - a.score);
|
|
94
94
|
const optional = candidates.map(({ node: n }) => ({ id: `${n._layer || 'project'}:${n.slug}`,
|
|
95
95
|
text: `## ${n.title || n.slug} [${n.slug}]\n\n${(n.body || n.content || '').trim()}` }));
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
// CLI Semantic Index recommendations: top task-matched runnable CLI verbs
|
|
98
|
+
let cliVerbContribution = null;
|
|
99
|
+
if (query && String(query).trim()) {
|
|
100
|
+
try {
|
|
101
|
+
const { queryCliSemanticIndex } = await import('./cli-semantic-index.mjs');
|
|
102
|
+
const matchedVerbs = await queryCliSemanticIndex(query, {
|
|
103
|
+
derivedDir,
|
|
104
|
+
projectRoot,
|
|
105
|
+
limit: 5,
|
|
106
|
+
minScore: 0.35,
|
|
107
|
+
});
|
|
108
|
+
if (matchedVerbs.length > 0) {
|
|
109
|
+
const verbLines = matchedVerbs.map(v => `- \`${v.usage || v.verb}\` — ${v.description}`);
|
|
110
|
+
cliVerbContribution = {
|
|
111
|
+
id: 'cli:recommended-verbs',
|
|
112
|
+
required: false,
|
|
113
|
+
text: `### Recommended Task Verbs\n\n${verbLines.join('\n')}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
/* non-fatal */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const cliContributions = cliVerbContribution ? [cliVerbContribution] : [];
|
|
122
|
+
const result = assembleContext([...required, ...cliContributions, ...optional, ...contributions], { total: b.total });
|
|
97
123
|
result.stats.total_nodes = allNodes.length;
|
|
98
124
|
result.stats.compile_ms = Date.now() - startTime;
|
|
99
125
|
result.stats.consumer = consumer;
|
package/src/core/crons.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { logger } from './logger.mjs';
|
|
|
3
3
|
import { runGitHubSync, getGitHubSyncStatus } from './github-sync.mjs';
|
|
4
4
|
import { watchObsidianDirectory } from './obsidian-sync.mjs';
|
|
5
5
|
const { getSecret } = await __loadOwnedPlugin('auth', 'secrets-store');
|
|
6
|
-
import { runPackageAutoUpdate } from './package-auto-update.mjs';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* runCrons — scheduled background jobs.
|
|
@@ -30,18 +29,8 @@ export async function runCrons(options) {
|
|
|
30
29
|
// Obsidian not configured
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
const pkg = await runPackageAutoUpdate({ brainDir });
|
|
36
|
-
if (!pkg.skipped && (pkg.updated > 0 || pkg.failed > 0)) {
|
|
37
|
-
logger.info({
|
|
38
|
-
subsystem: 'cron',
|
|
39
|
-
message: `package-auto-update: latest=${pkg.latest} updated=${pkg.updated} failed=${pkg.failed}`,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
} catch (err) {
|
|
43
|
-
logger.warn({ subsystem: 'cron', message: `package-auto-update failed: ${err.message}` });
|
|
44
|
-
}
|
|
32
|
+
// Package maintenance has its own wall-clock timer in this same daemon.
|
|
33
|
+
// Task-count driven crons must never block the queue on npm installation.
|
|
45
34
|
} catch (err) {
|
|
46
35
|
logger.error({ subsystem: 'cron', message: `Cron execution failed: ${err.message}` });
|
|
47
36
|
}
|
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { logger } from './logger.mjs';
|
|
7
7
|
import { agentDir, brainDir } from './config.mjs';
|
|
8
|
+
import { readProcessCommand } from './pid-lock.mjs';
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const ROOT = path.join(__dirname, '..', '..');
|
|
@@ -29,6 +30,9 @@ export function readPid() {
|
|
|
29
30
|
// Check if the process is alive using signal 0
|
|
30
31
|
try {
|
|
31
32
|
process.kill(pid, 0);
|
|
33
|
+
const command=readProcessCommand(pid);
|
|
34
|
+
if(!command)throw new Error('Cannot verify daemon process identity');
|
|
35
|
+
if(!command.includes(path.join('core','daemon-loop.mjs')) && !command.includes(path.join('core','dream.mjs')))return null;
|
|
32
36
|
return pid;
|
|
33
37
|
} catch {
|
|
34
38
|
return null;
|
|
@@ -130,10 +134,8 @@ export function startDaemon() {
|
|
|
130
134
|
export async function stopDaemon({ graceMs = 35_000, pollMs = 250 } = {}) {
|
|
131
135
|
const pid = readPid();
|
|
132
136
|
if (!pid) {
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
136
|
-
} catch {}
|
|
137
|
+
// Startup owns stale-lock recovery. A live process with unreadable identity
|
|
138
|
+
// is not ours to signal or unlink.
|
|
137
139
|
return false;
|
|
138
140
|
}
|
|
139
141
|
|
package/src/core/daemon-loop.mjs
CHANGED
|
@@ -258,6 +258,8 @@ import { releaseLease } from './leader-election.mjs';
|
|
|
258
258
|
* even a wedged iteration cannot keep the daemon alive indefinitely.
|
|
259
259
|
*/
|
|
260
260
|
let stopPluginTasks = null;
|
|
261
|
+
let stopCliMaintenance = null;
|
|
262
|
+
let stopAutomaticUpdates = null;
|
|
261
263
|
|
|
262
264
|
function shutdown(signal) {
|
|
263
265
|
if (shuttingDown) return;
|
|
@@ -266,6 +268,8 @@ function shutdown(signal) {
|
|
|
266
268
|
running = false;
|
|
267
269
|
if (vaultWatcher) vaultWatcher.stop();
|
|
268
270
|
if (stopPluginTasks) stopPluginTasks();
|
|
271
|
+
stopCliMaintenance?.();
|
|
272
|
+
stopAutomaticUpdates?.();
|
|
269
273
|
|
|
270
274
|
const fallback = setTimeout(() => {
|
|
271
275
|
logger.info({
|
|
@@ -328,6 +332,11 @@ async function main() {
|
|
|
328
332
|
}
|
|
329
333
|
|
|
330
334
|
// Start source watcher (auto-regenerates repo-expert on code changes)
|
|
335
|
+
try {
|
|
336
|
+
const { startCliSurfaceMaintenance } = await import('./cli-maintenance.mjs');
|
|
337
|
+
const maintenance=startCliSurfaceMaintenance({brainDir:BRAIN_DIR});
|
|
338
|
+
stopCliMaintenance=()=>maintenance.stop();
|
|
339
|
+
} catch(error) { logger.error('daemon-loop', `CLI surface maintenance failed: ${error.message}`); }
|
|
331
340
|
try {
|
|
332
341
|
const { detectProjectBrain } = await import('./config.mjs');
|
|
333
342
|
const project = detectProjectBrain();
|
|
@@ -388,14 +397,9 @@ async function main() {
|
|
|
388
397
|
|
|
389
398
|
// npm total-recall-brain auto-update for registered projects (throttled thereafter via crons)
|
|
390
399
|
try {
|
|
391
|
-
const {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
logger.info({
|
|
395
|
-
subsystem: 'daemon-loop',
|
|
396
|
-
message: `Boot package-auto-update: latest=${pkg.latest} updated=${pkg.updated || 0} failed=${pkg.failed || 0}`,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
400
|
+
const { startAutomaticBrainUpdates } = await import('./automatic-brain-updates.mjs');
|
|
401
|
+
const maintenance=startAutomaticBrainUpdates({brainDir:BRAIN_DIR});
|
|
402
|
+
stopAutomaticUpdates=()=>maintenance.stop();
|
|
399
403
|
} catch (err) {
|
|
400
404
|
logger.info({ subsystem: 'daemon-loop', message: `Boot package-auto-update failed: ${err.message}` });
|
|
401
405
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Verify and maintain globally installed CLIs without replacing a source checkout. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {spawnSync} from 'node:child_process';
|
|
5
|
+
import {BRAIN_PACKAGES} from './brain-updates.mjs';
|
|
6
|
+
import {comparePackageVersions} from './package-version.mjs';
|
|
7
|
+
import {updateSourceBrainPackage} from './source-brain-updates.mjs';
|
|
8
|
+
|
|
9
|
+
export function updateGlobalBrainPackages({versions,exec=spawnSync,sourceUpdate=updateSourceBrainPackage}={}) {
|
|
10
|
+
const prefix=exec('npm',['root','--global'],{encoding:'utf8',timeout:15000});
|
|
11
|
+
if(prefix.status!==0)return {failed:1,results:[{status:'failed',error:'Cannot resolve global npm root'}]};
|
|
12
|
+
const root=prefix.stdout.trim();
|
|
13
|
+
const results=[];
|
|
14
|
+
const install=[];
|
|
15
|
+
for(const name of BRAIN_PACKAGES){
|
|
16
|
+
const directory=path.join(root,name),file=path.join(directory,'package.json');
|
|
17
|
+
let installed;try{installed=JSON.parse(fs.readFileSync(file,'utf8')).version;}catch{}
|
|
18
|
+
const row={package:name,installed:installed || null,latest:versions[name],status:'up_to_date'};results.push(row);
|
|
19
|
+
if(installed===versions[name])continue;
|
|
20
|
+
if(!versions[name]){row.status='failed';row.error='Published version is unavailable';continue;}
|
|
21
|
+
if(installed && comparePackageVersions(installed,versions[name])===1){row.status='failed';row.error='Newer local/preview global package; refusing implicit downgrade';continue;}
|
|
22
|
+
if(fs.existsSync(path.join(directory,'.git'))){
|
|
23
|
+
const result=sourceUpdate({directory:fs.realpathSync(directory),name,version:versions[name],exec});
|
|
24
|
+
row.status=result.ok?'updated':'failed';row.source=true;
|
|
25
|
+
if(result.ok){row.installed=result.installed;row.commit=result.commit;}else row.error=`Source checkout update failed: ${result.error}`;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
row.status='pending';install.push(`${name}@${versions[name]}`);
|
|
29
|
+
}
|
|
30
|
+
if(install.length){
|
|
31
|
+
const result=exec('npm',['install','--global','--no-audit','--no-fund',...install],{encoding:'utf8',timeout:600000,maxBuffer:8*1024*1024});
|
|
32
|
+
for(const row of results.filter(row=>row.status==='pending')){
|
|
33
|
+
try{row.installed=JSON.parse(fs.readFileSync(path.join(root,row.package,'package.json'),'utf8')).version;}catch{}
|
|
34
|
+
row.status=result.status===0 && row.installed===row.latest?'updated':'failed';
|
|
35
|
+
if(row.status==='failed')row.error=result.error?.message || result.stderr?.slice(-1000) || 'Global version verification failed';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {failed:results.filter(row=>row.status==='failed').length,results};
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The daemon dispatches maintenance through the mesh owner; remote brains stay separate. */
|
|
2
|
+
export async function updateMeshBrainPackages({version,versions,listNodes,exec,now=Date.now}={}) {
|
|
3
|
+
if(!/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/.test(version || ''))throw new Error('A verified published TR version is required');
|
|
4
|
+
if(!listNodes || !exec){
|
|
5
|
+
const mesh=await import('./mesh.mjs');
|
|
6
|
+
listNodes ||= ()=>mesh.listEnrichedMeshNodes();
|
|
7
|
+
exec ||= mesh.execMeshCommand;
|
|
8
|
+
}
|
|
9
|
+
const results=[];
|
|
10
|
+
for(const node of await listNodes()){
|
|
11
|
+
if(node.self || !node.ip || !node.access?.ssh_user)continue;
|
|
12
|
+
const row={node:node.ip,status:'pending'};results.push(row);
|
|
13
|
+
if(!node.online){row.status='failed';row.error='Configured mesh node is offline; retry pending';continue;}
|
|
14
|
+
try{
|
|
15
|
+
// Read the durable report first, then launch only when stale or failed.
|
|
16
|
+
const observed=await exec(node.ip,'cd "$HOME" && total-recall update --status --json',{timeoutMs:20000});
|
|
17
|
+
let report;try{report=JSON.parse(observed.stdout).last;}catch{}
|
|
18
|
+
const current=observed.success && report && !report.failed && now()>=report.checkedAt && now()-report.checkedAt<2*60*60*1000 && report.versions?.['total-recall-brain']===version && (!versions?.['@gregiteen/ssss-cli'] || report.versions?.['@gregiteen/ssss-cli']===versions['@gregiteen/ssss-cli']);
|
|
19
|
+
if(current){row.status='verified';row.report=report;continue;}
|
|
20
|
+
// npm exec pins the verified release so an old installed CLI can bootstrap
|
|
21
|
+
// this capability. The remote command owns its lock, verification and report.
|
|
22
|
+
const command=`cd "$HOME" && nohup npm exec --yes --package=total-recall-brain@${version} -- total-recall update --automatic --local-only --json </dev/null >/tmp/total-recall-automatic-update.log 2>&1 &`;
|
|
23
|
+
const launched=await exec(node.ip,command,{timeoutMs:20000});
|
|
24
|
+
row.status=launched.success?'dispatched':'failed';
|
|
25
|
+
row.error=launched.success?'Awaiting independently verified remote report':launched.stderr || 'Mesh maintenance dispatch failed';
|
|
26
|
+
}catch(error){row.status='failed';row.error=error.message;}
|
|
27
|
+
}
|
|
28
|
+
// A dispatched process is not a verified update. Keep it visible for retries.
|
|
29
|
+
return {failed:results.filter(row=>row.status!=='verified').length,results};
|
|
30
|
+
}
|
|
@@ -75,6 +75,33 @@ export const HARNESS_SPECS = {
|
|
|
75
75
|
defaultFlags: ['-p'],
|
|
76
76
|
execType: 'flag_last',
|
|
77
77
|
description: 'Autonomous coding, repo analysis, and shell execution via xAI Grok.'
|
|
78
|
+
},
|
|
79
|
+
hermes: {
|
|
80
|
+
id: 'hermes',
|
|
81
|
+
name: 'Hermes Agent Runtime',
|
|
82
|
+
binary: 'hermes',
|
|
83
|
+
category: 'autonomous_agent',
|
|
84
|
+
defaultFlags: ['chat', '-p'],
|
|
85
|
+
execType: 'flag_last',
|
|
86
|
+
description: 'Supervised messaging gateway and multi-channel autonomous agent runtime.'
|
|
87
|
+
},
|
|
88
|
+
dsh: {
|
|
89
|
+
id: 'dsh',
|
|
90
|
+
name: 'DeepSeek Harness (DSH)',
|
|
91
|
+
binary: 'dsh',
|
|
92
|
+
category: 'reasoning_engine',
|
|
93
|
+
defaultFlags: ['-p'],
|
|
94
|
+
execType: 'flag_last',
|
|
95
|
+
description: 'High-throughput DeepSeek reasoning and tool execution harness.'
|
|
96
|
+
},
|
|
97
|
+
openclaw: {
|
|
98
|
+
id: 'openclaw',
|
|
99
|
+
name: 'OpenClaw Messaging Bridge',
|
|
100
|
+
binary: 'openclaw',
|
|
101
|
+
category: 'multichannel_bridge',
|
|
102
|
+
defaultFlags: ['status', '--json'],
|
|
103
|
+
execType: 'flag_last',
|
|
104
|
+
description: 'Multi-channel messaging bridge (WhatsApp, Telegram, Discord, Slack) and agent execution.'
|
|
78
105
|
}
|
|
79
106
|
};
|
|
80
107
|
|
|
@@ -227,6 +254,27 @@ export async function detectHarnessesWithAuth({ readSecret = readStoreSecret } =
|
|
|
227
254
|
h.authed = true;
|
|
228
255
|
h.authRoute = 'cached-google-signin';
|
|
229
256
|
}
|
|
257
|
+
} else if (h.id === 'hermes') {
|
|
258
|
+
if (process.env.HERMES_API_KEY) {
|
|
259
|
+
h.authed = true;
|
|
260
|
+
h.authRoute = 'env-api-key';
|
|
261
|
+
} else if (fs.existsSync(path.join(os.homedir(), '.hermes')) || fs.existsSync(path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'plugins', 'hermes'))) {
|
|
262
|
+
h.authed = true;
|
|
263
|
+
h.authRoute = 'cached-profile';
|
|
264
|
+
}
|
|
265
|
+
} else if (h.id === 'dsh') {
|
|
266
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
267
|
+
h.authed = true;
|
|
268
|
+
h.authRoute = 'env-api-key';
|
|
269
|
+
} else if (fs.existsSync(path.join(os.homedir(), '.dsh')) || fs.existsSync(path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'plugins', 'dsh'))) {
|
|
270
|
+
h.authed = true;
|
|
271
|
+
h.authRoute = 'cached-dsh-config';
|
|
272
|
+
}
|
|
273
|
+
} else if (h.id === 'openclaw') {
|
|
274
|
+
if (fs.existsSync(path.join(os.homedir(), '.openclaw', 'openclaw.json'))) {
|
|
275
|
+
h.authed = true;
|
|
276
|
+
h.authRoute = 'cached-openclaw-config';
|
|
277
|
+
}
|
|
230
278
|
}
|
|
231
279
|
}
|
|
232
280
|
return harnesses;
|
|
@@ -452,6 +500,25 @@ export function getInstalledHarnessVersion(harnessId) {
|
|
|
452
500
|
const res = spawnSync('gemini', ['--version'], { timeout: 800, encoding: 'utf8' });
|
|
453
501
|
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
454
502
|
if (m) return m[1];
|
|
503
|
+
} else if (harnessId === 'hermes') {
|
|
504
|
+
const res = spawnSync('hermes', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
505
|
+
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
506
|
+
if (m) return m[1];
|
|
507
|
+
} else if (harnessId === 'dsh') {
|
|
508
|
+
const res = spawnSync('dsh', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
509
|
+
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
510
|
+
if (m) return m[1];
|
|
511
|
+
} else if (harnessId === 'openclaw') {
|
|
512
|
+
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
513
|
+
if (fs.existsSync(configPath)) {
|
|
514
|
+
try {
|
|
515
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
516
|
+
if (cfg?.meta?.lastTouchedVersion) return cfg.meta.lastTouchedVersion;
|
|
517
|
+
} catch {}
|
|
518
|
+
}
|
|
519
|
+
const res = spawnSync('openclaw', ['--version'], { timeout: 1500, encoding: 'utf8' });
|
|
520
|
+
const m = (res.stdout || '').match(/(\d+\.\d+\.\d+)/);
|
|
521
|
+
if (m) return m[1];
|
|
455
522
|
}
|
|
456
523
|
} catch {}
|
|
457
524
|
return 'unknown';
|
|
@@ -490,6 +557,12 @@ export async function getLatestHarnessVersion(harnessId, installedVersion = 'unk
|
|
|
490
557
|
latest = '0.24.0';
|
|
491
558
|
} else if (harnessId === 'gemini') {
|
|
492
559
|
latest = '0.3.0';
|
|
560
|
+
} else if (harnessId === 'hermes') {
|
|
561
|
+
latest = '0.1.0';
|
|
562
|
+
} else if (harnessId === 'dsh') {
|
|
563
|
+
latest = '1.1.1';
|
|
564
|
+
} else if (harnessId === 'openclaw') {
|
|
565
|
+
latest = '2026.4.10';
|
|
493
566
|
}
|
|
494
567
|
} catch {}
|
|
495
568
|
|
|
@@ -640,6 +713,41 @@ export async function queryHarnessUsage() {
|
|
|
640
713
|
rolling5hRemainingPct = '100% (unlimited)';
|
|
641
714
|
weeklyRemainingPct = '100% (unlimited)';
|
|
642
715
|
quotaRemaining = 'Unlimited (local zero-cost inference)';
|
|
716
|
+
} else if (h.id === 'hermes') {
|
|
717
|
+
identity = 'Hermes Supervisor';
|
|
718
|
+
plan = 'Autonomous Agent Runtime';
|
|
719
|
+
rolling5hRemainingPct = '100%';
|
|
720
|
+
weeklyRemainingPct = '100%';
|
|
721
|
+
quotaRemaining = 'Active broker & gateway';
|
|
722
|
+
} else if (h.id === 'dsh') {
|
|
723
|
+
let model = 'deepseek/deepseek-v4.1-flash';
|
|
724
|
+
try {
|
|
725
|
+
const dshModelPath = path.join(os.homedir(), '.dsh', 'default-model.txt');
|
|
726
|
+
if (fs.existsSync(dshModelPath)) {
|
|
727
|
+
model = fs.readFileSync(dshModelPath, 'utf8').trim() || model;
|
|
728
|
+
}
|
|
729
|
+
} catch {}
|
|
730
|
+
identity = model;
|
|
731
|
+
plan = 'DeepSeek Dedicated Harness';
|
|
732
|
+
rolling5hRemainingPct = '95%';
|
|
733
|
+
weeklyRemainingPct = '98%';
|
|
734
|
+
quotaRemaining = 'DeepSeek reasoning pool active';
|
|
735
|
+
} else if (h.id === 'openclaw') {
|
|
736
|
+
let channelsStr = 'telegram, qqbot';
|
|
737
|
+
try {
|
|
738
|
+
const oPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
739
|
+
if (fs.existsSync(oPath)) {
|
|
740
|
+
const oCfg = JSON.parse(fs.readFileSync(oPath, 'utf8'));
|
|
741
|
+
identity = oCfg?.agents?.defaults?.model?.primary || 'openai-codex/gpt-5.4';
|
|
742
|
+
if (oCfg?.channels) {
|
|
743
|
+
channelsStr = Object.keys(oCfg.channels).join(', ');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} catch {}
|
|
747
|
+
plan = 'Multi-Channel Gateway';
|
|
748
|
+
rolling5hRemainingPct = '100%';
|
|
749
|
+
weeklyRemainingPct = '100%';
|
|
750
|
+
quotaRemaining = `Channels: ${channelsStr}`;
|
|
643
751
|
}
|
|
644
752
|
|
|
645
753
|
let status = isAuthed ? 'ready' : 'needs_auth';
|
|
@@ -12,7 +12,8 @@ export function preparePluginDependencies(pluginDir) {
|
|
|
12
12
|
let source;
|
|
13
13
|
if (name === host.name) source = PACKAGE_ROOT;
|
|
14
14
|
else {
|
|
15
|
-
|
|
15
|
+
const alias = Object.entries(host.dependencies || {}).find(([, spec]) => typeof spec === 'string' && spec.startsWith(`npm:${name}@`))?.[0];
|
|
16
|
+
source = path.dirname(require.resolve(alias || name));
|
|
16
17
|
while (true) {
|
|
17
18
|
const file = path.join(source, 'package.json');
|
|
18
19
|
if (fs.existsSync(file) && JSON.parse(fs.readFileSync(file, 'utf8')).name === name) break;
|
|
@@ -29,7 +29,7 @@ export function globalPluginsDir() {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function bundledPluginsDir() {
|
|
32
|
-
return process.env._TR_TEST_BUNDLED_PLUGINS_DIR || path.join(PACKAGE_ROOT, 'plugins');
|
|
32
|
+
return process.env._TR_TEST_BUNDLED_PLUGINS_DIR || path.join(PACKAGE_ROOT, 'scaffold', '.agent', 'skills', 'total-recall', 'plugins');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** The vault that owns records for plugins installed in `pluginsDir` (`<brain>/plugins` → `<brain>/memory-vault`). */
|
|
@@ -165,6 +165,7 @@ export function validatePluginManifest(manifest) {
|
|
|
165
165
|
if (t.placement !== undefined && !['per-node', 'selected-node'].includes(t.placement)) {
|
|
166
166
|
errors.push(`Task '${t.intent}' has unsupported placement '${t.placement}'`);
|
|
167
167
|
}
|
|
168
|
+
if (t.timeout_ms !== undefined && (!Number.isInteger(t.timeout_ms) || t.timeout_ms < 1 || t.timeout_ms > 600000)) errors.push(`Task '${t.intent}' timeout_ms must be 1..600000`);
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
}
|
|
@@ -67,6 +67,7 @@ export function runPluginCommand(plugin, options = {}) {
|
|
|
67
67
|
return new Promise((resolve) => {
|
|
68
68
|
const started = Date.now();
|
|
69
69
|
const chunks = [];
|
|
70
|
+
const stdoutChunks=[],stderrChunks=[];
|
|
70
71
|
let bytes = 0;
|
|
71
72
|
let truncated = false;
|
|
72
73
|
let timedOut = false;
|
|
@@ -90,21 +91,22 @@ export function runPluginCommand(plugin, options = {}) {
|
|
|
90
91
|
stdio: [stdinText !== undefined ? 'pipe' : stdin === 'inherit' ? 'inherit' : 'ignore', 'pipe', 'pipe']
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
const collect = (buf) => {
|
|
94
|
+
const collect = (buf,stream) => {
|
|
94
95
|
// Even a faulty owner that echoes stdin cannot return credential material.
|
|
95
96
|
if (stdinText !== undefined) return;
|
|
96
97
|
if (truncated) return;
|
|
97
98
|
if (bytes + buf.length > MAX_OUTPUT_BYTES) {
|
|
98
|
-
|
|
99
|
+
const partial=buf.subarray(0, MAX_OUTPUT_BYTES - bytes);chunks.push(partial);stream.push(partial);
|
|
99
100
|
bytes = MAX_OUTPUT_BYTES;
|
|
100
101
|
truncated = true;
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
104
|
chunks.push(buf);
|
|
105
|
+
stream.push(buf);
|
|
104
106
|
bytes += buf.length;
|
|
105
107
|
};
|
|
106
|
-
child.stdout.on('data', collect);
|
|
107
|
-
child.stderr.on('data', collect);
|
|
108
|
+
child.stdout.on('data', buf=>collect(buf,stdoutChunks));
|
|
109
|
+
child.stderr.on('data', buf=>collect(buf,stderrChunks));
|
|
108
110
|
if (stdinText !== undefined) {
|
|
109
111
|
// A handler may exit without reading; EPIPE must not crash the server.
|
|
110
112
|
child.stdin.on('error', () => {});
|
|
@@ -124,6 +126,10 @@ export function runPluginCommand(plugin, options = {}) {
|
|
|
124
126
|
if (spawnError) output += `${output ? '\n' : ''}[runner] ${spawnError.message}`;
|
|
125
127
|
if (truncated) output += `\n[runner] output truncated at ${MAX_OUTPUT_BYTES} bytes`;
|
|
126
128
|
if (timedOut) output += `\n[runner] killed after ${timeoutMs} ms`;
|
|
129
|
+
let stderr=Buffer.concat(stderrChunks).toString('utf8');
|
|
130
|
+
if(spawnError)stderr+=`\n[runner] ${spawnError.message}`;
|
|
131
|
+
if(truncated)stderr+=`\n[runner] output truncated at ${MAX_OUTPUT_BYTES} bytes`;
|
|
132
|
+
if(timedOut)stderr+=`\n[runner] killed after ${timeoutMs} ms`;
|
|
127
133
|
resolve({
|
|
128
134
|
ok: !spawnError && !timedOut && exitCode === 0,
|
|
129
135
|
exitCode,
|
|
@@ -131,6 +137,8 @@ export function runPluginCommand(plugin, options = {}) {
|
|
|
131
137
|
timedOut,
|
|
132
138
|
truncated,
|
|
133
139
|
output,
|
|
140
|
+
stdout:Buffer.concat(stdoutChunks).toString('utf8'),
|
|
141
|
+
stderr,
|
|
134
142
|
durationMs: Date.now() - started
|
|
135
143
|
});
|
|
136
144
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Run declared, enabled plugin setup through the normal isolated CLI runner. */
|
|
2
|
+
import { discoverPlugins } from './plugin-loader.mjs';
|
|
3
|
+
import { readPluginRecord } from './plugin-store.mjs';
|
|
4
|
+
import { runPluginCommand } from './plugin-runner.mjs';
|
|
5
|
+
|
|
6
|
+
export async function setupInstalledPlugins({root=process.cwd(),plugins=discoverPlugins(root),run=runPluginCommand,record=readPluginRecord}={}) {
|
|
7
|
+
const results=[];
|
|
8
|
+
for(const plugin of plugins) {
|
|
9
|
+
const command=plugin.manifest?.runtime?.setup_command;
|
|
10
|
+
if(!plugin.valid || !command || record(plugin)?.runtime_enabled===false)continue;
|
|
11
|
+
if(!plugin.manifest.cli?.subcommands?.some(operation=>operation.name===command)) {
|
|
12
|
+
results.push({id:plugin.id,ok:false,error:'Runtime setup must name a declared CLI operation'});continue;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const result=await run(plugin,{subcommand:command,cwd:root,timeoutMs:600000});
|
|
16
|
+
results.push({id:plugin.id,ok:result.ok,error:result.ok?undefined:(result.stderr || result.output || 'Plugin setup failed').slice(-1000)});
|
|
17
|
+
}catch(error){results.push({id:plugin.id,ok:false,error:error.message});}
|
|
18
|
+
}
|
|
19
|
+
return {ok:results.every(result=>result.ok),results};
|
|
20
|
+
}
|
|
@@ -364,6 +364,16 @@ export async function installPlugin(source, options = {}) {
|
|
|
364
364
|
let sourceRecord;
|
|
365
365
|
let verifiedSha = null;
|
|
366
366
|
let dependencies = null;
|
|
367
|
+
let previousDirectory=null;
|
|
368
|
+
const prepareDestination=id=>{
|
|
369
|
+
const target=path.join(pluginsDir,id);
|
|
370
|
+
if(!options.replace || !fs.existsSync(target))return assertNotInstalled(id,pluginsDir);
|
|
371
|
+
const archive=path.join(path.dirname(pluginsDir),'memory-derived/plugin-replacements',`${id}-${crypto.randomUUID()}`);
|
|
372
|
+
fs.mkdirSync(path.dirname(archive),{recursive:true});
|
|
373
|
+
fs.renameSync(target,archive);previousDirectory={target,archive};
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
try {
|
|
367
377
|
|
|
368
378
|
const resolved = resolveBundle ? await resolveBundle(source, options) : null;
|
|
369
379
|
const bundled = !resolved && ID_PATTERN.test(source) ? listBundledPlugins().find((p) => p.id === source) : null;
|
|
@@ -372,14 +382,14 @@ export async function installPlugin(source, options = {}) {
|
|
|
372
382
|
manifest = resolved.manifest;
|
|
373
383
|
const validation = validatePluginManifest(manifest);
|
|
374
384
|
if (!validation.valid) throw new Error(`Invalid plugin manifest: ${validation.errors.join('; ')}`);
|
|
375
|
-
|
|
385
|
+
prepareDestination(manifest.id);
|
|
376
386
|
dest = writeEntriesAtomic(pluginsDir, manifest.id, resolved.entries);
|
|
377
387
|
verifiedSha = resolved.sha256;
|
|
378
388
|
sourceRecord = resolved.source;
|
|
379
389
|
} else if (bundled) {
|
|
380
390
|
if (!bundled.valid) throw new Error(`Bundled plugin '${source}' is invalid: ${bundled.errors.join('; ')}`);
|
|
381
391
|
manifest = bundled.manifest;
|
|
382
|
-
|
|
392
|
+
prepareDestination(manifest.id);
|
|
383
393
|
dest = copyPluginAtomic(bundled.dir, pluginsDir, manifest.id);
|
|
384
394
|
sourceRecord = { kind: 'bundled', ref: manifest.id };
|
|
385
395
|
} else if (isGitSource(source)) {
|
|
@@ -391,7 +401,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
391
401
|
throw new Error(`Failed to clone ${source}: ${(err.stderr || err.message || '').toString().trim()}`);
|
|
392
402
|
}
|
|
393
403
|
manifest = readManifestFrom(tmp);
|
|
394
|
-
|
|
404
|
+
prepareDestination(manifest.id);
|
|
395
405
|
dest = copyPluginAtomic(tmp, pluginsDir, manifest.id);
|
|
396
406
|
} finally {
|
|
397
407
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
@@ -403,7 +413,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
403
413
|
throw new Error(`No bundled plugin, share link, peer source, git URL or directory matches '${source}'`);
|
|
404
414
|
}
|
|
405
415
|
manifest = readManifestFrom(abs);
|
|
406
|
-
|
|
416
|
+
prepareDestination(manifest.id);
|
|
407
417
|
if (link) {
|
|
408
418
|
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
409
419
|
dest = path.join(pluginsDir, manifest.id);
|
|
@@ -446,8 +456,16 @@ export async function installPlugin(source, options = {}) {
|
|
|
446
456
|
source: sourceRecord,
|
|
447
457
|
sha256,
|
|
448
458
|
dependencies,
|
|
459
|
+
previousDirectory:previousDirectory?.archive || null,
|
|
449
460
|
compilation
|
|
450
461
|
};
|
|
462
|
+
}catch(error){
|
|
463
|
+
if(previousDirectory){
|
|
464
|
+
if(fs.existsSync(previousDirectory.target))fs.rmSync(previousDirectory.target,{recursive:true,force:true});
|
|
465
|
+
fs.renameSync(previousDirectory.archive,previousDirectory.target);
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
451
469
|
}
|
|
452
470
|
|
|
453
471
|
export function findInstalled(id, { projectRoot = process.cwd(), global: isGlobal } = {}) {
|
|
@@ -99,7 +99,7 @@ export async function runDuePluginTasks(options = {}) {
|
|
|
99
99
|
const slot = latestDueSlot(cron, runs[task.command], now);
|
|
100
100
|
if (!slot) continue;
|
|
101
101
|
|
|
102
|
-
const result = await runner(plugin, { subcommand: task.command, cwd: projectRoot, timeoutMs: TASK_TIMEOUT_MS })
|
|
102
|
+
const result = await runner(plugin, { subcommand: task.command, cwd: projectRoot, timeoutMs: task.timeout_ms || TASK_TIMEOUT_MS })
|
|
103
103
|
.catch((err) => ({ ok: false, exitCode: null, output: err.message, durationMs: 0, timedOut: false }));
|
|
104
104
|
runs[task.command] = slot;
|
|
105
105
|
changed = true;
|
|
@@ -19,6 +19,7 @@ import fs from 'fs';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import { fileURLToPath } from 'url';
|
|
21
21
|
import YAML from 'yaml';
|
|
22
|
+
import { ensureRequiredPlugins } from './required-plugins.mjs';
|
|
22
23
|
import { VAULT_CATEGORIES, writeBrainIdentity, registerProjectBrain } from './memory-brain.mjs';
|
|
23
24
|
export { VAULT_CATEGORIES, writeBrainIdentity, registerProjectBrain } from './memory-brain.mjs';
|
|
24
25
|
|
|
@@ -96,6 +97,7 @@ export function ensureOpenWiki(brainDir) {
|
|
|
96
97
|
* Ensure core total-recall skill package files exist (SKILL.md minimum).
|
|
97
98
|
*/
|
|
98
99
|
export function ensureCoreSkillPackage(brainDir) {
|
|
100
|
+
ensureRequiredPlugins(brainDir);
|
|
99
101
|
const skillMd = path.join(brainDir, 'SKILL.md');
|
|
100
102
|
if (fs.existsSync(skillMd)) return { seeded: false };
|
|
101
103
|
const scaffold = path.join(ROOT, 'scaffold', '.agent', 'skills', 'total-recall');
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Read-only inventory of recorded project work. No inferred completion or live readiness. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {listUpdateRoots,resolveRegistryFiles,readRegistryEntries} from './package-auto-update.mjs';
|
|
5
|
+
import {listTasks,resolveQueueDir} from './task-envelope.mjs';
|
|
6
|
+
import {globalBrainDirFor} from './brain-registry.mjs';
|
|
7
|
+
|
|
8
|
+
export function outstandingProjectStatus(options={}) {
|
|
9
|
+
const globalBrainDir=options.brainDir || globalBrainDirFor();
|
|
10
|
+
const roots=listUpdateRoots({...options,brainDir:globalBrainDir});
|
|
11
|
+
const missing=resolveRegistryFiles({...options,brainDir:globalBrainDir}).flatMap(file=>readRegistryEntries(file)).filter(row=>row.path && !fs.existsSync(row.path)).map(row=>({root:row.path,status:'missing-registered-root'}));
|
|
12
|
+
const projects=[];
|
|
13
|
+
const queues=new Set([globalBrainDir]);
|
|
14
|
+
for(const {root,name} of roots){
|
|
15
|
+
queues.add(path.join(root,'.agent/skills/total-recall'));
|
|
16
|
+
const row={name,root,projects:[],status:'no-outstanding-tracker-found'};
|
|
17
|
+
for(const state of ['in-progress','planned','backlog','blocked']){
|
|
18
|
+
const directory=path.join(root,'docs/projects',state);
|
|
19
|
+
if(!fs.existsSync(directory))continue;
|
|
20
|
+
for(const entry of fs.readdirSync(directory,{withFileTypes:true}).filter(entry=>entry.isDirectory())){
|
|
21
|
+
const folder=path.join(directory,entry.name);
|
|
22
|
+
const files=fs.readdirSync(folder).filter(file=>/PROJECT_TRACKER\.md$|^TRACKER\.md$/i.test(file));
|
|
23
|
+
if(!files.length){row.projects.push({id:entry.name,state,status:'tracker-missing',folder});continue;}
|
|
24
|
+
for(const file of files){
|
|
25
|
+
const tracker=path.join(folder,file),body=fs.readFileSync(tracker,'utf8'),lines=body.split(/\r?\n/);
|
|
26
|
+
const open=lines.flatMap((text,index)=>/^\s*[-*]\s+\[ \]/.test(text)?[{line:index+1,text:text.trim()}]:[]);
|
|
27
|
+
const done=lines.filter(text=>/^\s*[-*]\s+\[[xX]\]/.test(text)).length;
|
|
28
|
+
const status=body.match(/^(?:>\s*)?(?:\*\*)?(?:Project Status|Status)\s*:(?:\*\*)?\s*(.+)$/im)?.[1]?.trim() || 'not-declared';
|
|
29
|
+
const blockers=lines.flatMap((text,index)=>/\b(blocked|blocker|blocking)\b/i.test(text) && !/^\s*[-*]\s+\[[xX]\]/.test(text)?[{line:index+1,text:text.trim()}]:[]);
|
|
30
|
+
row.projects.push({id:entry.name,state,status,tracker,modifiedAt:fs.statSync(tracker).mtime.toISOString(),openCount:open.length,checkedCount:done,open,blockerMentions:blockers});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if(row.projects.length)row.status='recorded-outstanding-work';
|
|
35
|
+
projects.push(row);
|
|
36
|
+
}
|
|
37
|
+
const tasks=[];
|
|
38
|
+
for(const brainDir of queues)for(const task of listTasks(resolveQueueDir(brainDir),{status:'all'})){
|
|
39
|
+
if(['completed','cancelled'].includes(task.status))continue;
|
|
40
|
+
tasks.push({brainDir,slug:task.slug,intent:String(task.intent || '').trim().split(/\r?\n/).find(line=>line.trim() && !line.startsWith('#'))?.slice(0,300) || task.slug,status:task.status,executor:task.executor,priority:task.priority,source:task._filepath});
|
|
41
|
+
}
|
|
42
|
+
return {generatedAt:new Date().toISOString(),evidence:'Recorded trackers and task envelopes; live completion and deployments are not verified',rootCount:projects.length,projectCount:projects.reduce((n,row)=>n+row.projects.length,0),openCheckboxCount:projects.reduce((n,row)=>n+row.projects.reduce((m,p)=>m+(p.openCount || 0),0),0),projects,tasks,missing};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatOutstandingProjectStatus(report){
|
|
46
|
+
const clean=value=>String(value ?? 'unknown').replaceAll('|','\\|').replace(/\s+/g,' ').trim();
|
|
47
|
+
const lines=['# Outstanding projects and tasks',`Generated ${report.generatedAt}.`,report.evidence,`${report.rootCount} registered roots; ${report.projectCount} project folders; ${report.openCheckboxCount} unchecked items; ${report.tasks.length} outstanding task records.`,'','Counts include duplicated plans in separate repositories. Missing trackers and empty checklists do not establish completion.',''];
|
|
48
|
+
for(const repo of report.projects){
|
|
49
|
+
lines.push(`## ${repo.name}`,'',`Source: ${repo.root}`,'');
|
|
50
|
+
if(!repo.projects.length){lines.push('No outstanding project folder was found in the inspected Kanban directories. Project status remains unverified.','');continue;}
|
|
51
|
+
lines.push('| Project | Recorded state | Open items | Source updated |','| --- | --- | ---: | --- |');
|
|
52
|
+
for(const project of repo.projects)lines.push(`| [${clean(project.id)}](${project.tracker || project.folder}) | ${clean(project.state)}${project.status==='tracker-missing'?'; tracker missing':''} | ${project.openCount ?? '?'} | ${project.modifiedAt?.slice(0,10) || 'unknown'} |`);
|
|
53
|
+
lines.push('');
|
|
54
|
+
for(const project of repo.projects.filter(project=>project.open?.length)){
|
|
55
|
+
lines.push(`### ${clean(project.id)}: recorded remaining work`,'');
|
|
56
|
+
for(const item of project.open)lines.push(`- ${clean(item.text.replace(/^[-*]\s+\[ \]\s*/,''))} ([source](${project.tracker}:${item.line}))`);
|
|
57
|
+
lines.push('');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
lines.push('## Outstanding daemon tasks','','| Task | Status | Executor | Source |','| --- | --- | --- | --- |');
|
|
61
|
+
for(const task of report.tasks)lines.push(`| ${clean(task.slug)} | ${clean(task.status)} | ${clean(task.executor)} | [record](${task.source}) |`);
|
|
62
|
+
for(const row of report.missing)lines.push(`Missing registered root: ${row.root}`);
|
|
63
|
+
return lines.join('\n')+'\n';
|
|
64
|
+
}
|