total-recall-brain 3.26.0 → 3.28.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 +31 -1
- package/docs/ARCHITECTURE.md +76 -9
- package/frontend/dist/assets/index-U7o-qU99.js +5742 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +39 -1
- package/frontend/src/api/index.ts +1 -0
- package/frontend/src/api/plugins.ts +167 -0
- package/frontend/src/pages/ChatPage.tsx +44 -17
- package/frontend/src/pages/IntegrationsPage.tsx +7 -0
- package/frontend/src/pages/PluginsPage.spec.tsx +17 -0
- package/frontend/src/pages/PluginsPage.tsx +1331 -0
- package/package.json +1 -1
- package/scaffold/.agent/plugins/.gitkeep +0 -0
- package/scaffold/.agent/plugins/code-quality/README.md +16 -0
- package/scaffold/.agent/plugins/code-quality/cli.mjs +15 -0
- package/scaffold/.agent/plugins/code-quality/plugin.json +34 -0
- package/scaffold/.agent/plugins/git-sentinel/README.md +16 -0
- package/scaffold/.agent/plugins/git-sentinel/cli.mjs +86 -0
- package/scaffold/.agent/plugins/git-sentinel/plugin.json +34 -0
- package/scaffold/.agent/plugins/meta-harness/README.md +16 -0
- package/scaffold/.agent/plugins/meta-harness/cli.mjs +34 -0
- package/scaffold/.agent/plugins/meta-harness/plugin.json +34 -0
- package/scaffold/.agent/plugins/system-monitor/README.md +16 -0
- package/scaffold/.agent/plugins/system-monitor/cli.mjs +79 -0
- package/scaffold/.agent/plugins/system-monitor/generator.mjs +20 -0
- package/scaffold/.agent/plugins/system-monitor/plugin.json +38 -0
- package/scaffold/.agent/skills/meta-harness/SKILL.md +113 -0
- package/scaffold/.agent/skills/total-recall/references/cli-reference.md +23 -0
- package/src/cli/agent.mjs +173 -0
- package/src/cli/connect.mjs +89 -4
- package/src/cli/deploy-ui.mjs +6 -0
- package/src/cli/harness.mjs +135 -0
- package/src/cli/ingest-openwiki.mjs +71 -2
- package/src/cli/init.mjs +1 -0
- package/src/cli/lint.mjs +47 -3
- package/src/cli/mesh.mjs +286 -0
- package/src/cli/plugin/create.mjs +186 -0
- package/src/cli/plugin/index.mjs +139 -0
- package/src/cli/plugin/install.mjs +141 -0
- package/src/cli/plugin/list.mjs +83 -0
- package/src/cli/plugin/remove.mjs +51 -0
- package/src/cli/plugin/search.mjs +37 -0
- package/src/cli/skill-projection.mjs +14 -0
- package/src/cli/wizard.html +9 -0
- package/src/core/agent-manager.mjs +290 -0
- package/src/core/cache-prune.mjs +341 -0
- package/src/core/context-cache.mjs +130 -0
- package/src/core/daemon-loop.mjs +47 -1
- package/src/core/evolving-context.mjs +171 -0
- package/src/core/mesh-activity.mjs +140 -0
- package/src/core/mesh.mjs +174 -1
- package/src/core/meta-harness.mjs +251 -0
- package/src/core/plugin-context.mjs +206 -0
- package/src/core/plugin-loader.mjs +210 -0
- package/src/core/runtime.mjs +65 -13
- package/src/core/surface.mjs +32 -10
- package/src/server/api.mjs +18 -2
- package/src/server/index.mjs +8 -2
- package/src/server/rest.mjs +4 -0
- package/src/server/routes/integrations.mjs +5 -1
- package/src/server/routes/mesh-presence.mjs +143 -0
- package/src/server/routes/models.mjs +42 -30
- package/src/server/routes/plugins.mjs +517 -0
- package/src/server/tools.mjs +266 -0
- package/frontend/dist/assets/index-Bl3I11B0.js +0 -5742
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { listAgents, spawnAgent, killAgent, getAgentLogs } from '../core/agent-manager.mjs';
|
|
4
|
+
import { detectHarnesses } from '../core/meta-harness.mjs';
|
|
5
|
+
|
|
6
|
+
function printHelp() {
|
|
7
|
+
console.log(`
|
|
8
|
+
🤖 Total Recall — CLI Agent Management & Process Controller
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
npx total-recall agent <command> [options]
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
list List all running and recent agent processes
|
|
15
|
+
spawn <harness> "<task>" Spawn a background or foreground agent task
|
|
16
|
+
status [id] Show detailed status of an agent
|
|
17
|
+
logs <id> [--tail 50] Inspect streaming logs of a spawned agent
|
|
18
|
+
kill <id|pid> Terminate an active agent process
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--node <name> Target a remote mesh node (macmini, cloud, etc.)
|
|
22
|
+
--json Output machine-readable JSON for Unix pipelines
|
|
23
|
+
--detach Spawn process detached in background (default: true)
|
|
24
|
+
--name <label> Assign a human-readable name to the agent session
|
|
25
|
+
--tail <lines> Number of log lines to inspect (default: 50)
|
|
26
|
+
|
|
27
|
+
Available Harnesses:
|
|
28
|
+
agy, claude, codex, gemini
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
npx total-recall agent list
|
|
32
|
+
npx total-recall agent spawn claude "Refactor src/cli/harness.mjs error handling"
|
|
33
|
+
npx total-recall agent spawn agy "Crawl recent preprints on quantum shuttling" --name "Quantum Scout"
|
|
34
|
+
npx total-recall agent logs agent-claude-xyz --tail 100
|
|
35
|
+
npx total-recall agent kill agent-claude-xyz
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function run(argv) {
|
|
40
|
+
let args = argv;
|
|
41
|
+
if (Array.isArray(args) && args[0]?.endsWith('node')) {
|
|
42
|
+
args = args.slice(2);
|
|
43
|
+
}
|
|
44
|
+
if (args[0] === 'agent' || args[0] === 'agents') {
|
|
45
|
+
args = args.slice(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const command = args[0];
|
|
49
|
+
const rest = args.slice(1);
|
|
50
|
+
|
|
51
|
+
if (!command || command === 'list') {
|
|
52
|
+
const isJson = rest.includes('--json');
|
|
53
|
+
const agents = listAgents();
|
|
54
|
+
|
|
55
|
+
if (isJson) {
|
|
56
|
+
console.log(JSON.stringify(agents, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`\n🤖 Total Recall — Active Agent Process Registry\n`);
|
|
61
|
+
if (agents.length === 0) {
|
|
62
|
+
console.log('No agents currently active or tracked.');
|
|
63
|
+
console.log('Spawn an agent with: npx total-recall agent spawn <harness> "<task>"\n');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pad = (s, l) => String(s || '').padEnd(l).slice(0, l);
|
|
68
|
+
console.log('┌────────────────────────┬──────────────┬────────┬────────────┬────────────────────────────┬─────────────────────────────┐');
|
|
69
|
+
console.log(`│ ${pad('Agent ID', 22)} │ ${pad('Harness', 12)} │ ${pad('PID', 6)} │ ${pad('Status', 10)} │ ${pad('Started At', 26)} │ ${pad('Task Intent', 27)} │`);
|
|
70
|
+
console.log('├────────────────────────┼──────────────┼────────┼────────────┼────────────────────────────┼─────────────────────────────┤');
|
|
71
|
+
|
|
72
|
+
for (const a of agents) {
|
|
73
|
+
const statusBadge = a.status === 'running' ? '\x1b[32mrunning\x1b[0m ' : (a.status === 'completed' ? '\x1b[36mcompleted\x1b[0m ' : '\x1b[90mstopped\x1b[0m ');
|
|
74
|
+
const time = (a.startedAt || '').replace('T', ' ').slice(0, 19);
|
|
75
|
+
console.log(`│ \x1b[1m${pad(a.id, 22)}\x1b[0m │ ${pad(a.harness, 12)} │ ${pad(a.pid, 6)} │ ${statusBadge} │ ${pad(time, 26)} │ ${pad(a.task, 27)} │`);
|
|
76
|
+
}
|
|
77
|
+
console.log('└────────────────────────┴──────────────┴────────┴────────────┴────────────────────────────┴─────────────────────────────┘\n');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (command === 'spawn') {
|
|
82
|
+
let targetNode = null;
|
|
83
|
+
const filteredRest = [];
|
|
84
|
+
for (let i = 0; i < rest.length; i++) {
|
|
85
|
+
if (rest[i] === '--node' || rest[i] === '-n') {
|
|
86
|
+
targetNode = rest[i + 1];
|
|
87
|
+
i++;
|
|
88
|
+
} else {
|
|
89
|
+
filteredRest.push(rest[i]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const harness = filteredRest[0];
|
|
94
|
+
const task = filteredRest.filter(r => !r.startsWith('--')).slice(1).join(' ').trim();
|
|
95
|
+
const detach = !filteredRest.includes('--no-detach');
|
|
96
|
+
const isJson = filteredRest.includes('--json');
|
|
97
|
+
const nameIdx = filteredRest.indexOf('--name');
|
|
98
|
+
const name = nameIdx !== -1 ? filteredRest[nameIdx + 1] : null;
|
|
99
|
+
|
|
100
|
+
if (!harness || !task) {
|
|
101
|
+
console.error('Error: Please specify harness ID and task prompt.');
|
|
102
|
+
console.error('Usage: npx total-recall agent spawn <harness> "<task>" [--name "..."] [--node <node>]');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const nodeStr = targetNode ? ` on mesh node \x1b[1;33m${targetNode}\x1b[0m` : '';
|
|
107
|
+
if (!isJson) console.log(`\n🚀 Spawning agent on harness: \x1b[1;36m${harness}\x1b[0m${nodeStr}...`);
|
|
108
|
+
try {
|
|
109
|
+
const record = await spawnAgent(harness, task, { detach, name, node: targetNode });
|
|
110
|
+
if (isJson) {
|
|
111
|
+
console.log(JSON.stringify(record, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
console.log(`✅ Agent session spawned successfully!`);
|
|
115
|
+
console.log(` Session ID: \x1b[1m${record.id}\x1b[0m (PID: ${record.pid || 'remote'})`);
|
|
116
|
+
console.log(` Status: \x1b[32m${record.status}\x1b[0m`);
|
|
117
|
+
if (record.node) console.log(` Mesh Node: ${record.node}`);
|
|
118
|
+
if (record.logFile) console.log(` Log File: ${record.logFile}`);
|
|
119
|
+
console.log(`\nMonitor logs with: \x1b[36mnpx total-recall agent logs ${record.id}\x1b[0m\n`);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error(`\n❌ Failed to spawn agent: ${err.message}\n`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (command === 'logs') {
|
|
128
|
+
const id = rest.find(r => !r.startsWith('--'));
|
|
129
|
+
if (!id) {
|
|
130
|
+
console.error('Error: Please specify agent ID or PID.');
|
|
131
|
+
console.error('Usage: npx total-recall agent logs <id> [--tail 50]');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const tailIdx = rest.indexOf('--tail');
|
|
136
|
+
const tailLines = tailIdx !== -1 ? parseInt(rest[tailIdx + 1], 10) : 50;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const logs = await getAgentLogs(id, tailLines);
|
|
140
|
+
console.log(`\n📜 Logs for Agent [${id}]:\n`);
|
|
141
|
+
console.log(logs || '(Log is empty)');
|
|
142
|
+
console.log('');
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (command === 'kill') {
|
|
151
|
+
const id = rest.find(r => !r.startsWith('--'));
|
|
152
|
+
if (!id) {
|
|
153
|
+
console.error('Error: Please specify agent ID or PID to terminate.');
|
|
154
|
+
console.error('Usage: npx total-recall agent kill <id|pid>');
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const res = await killAgent(id);
|
|
160
|
+
console.log(`\n✅ ${res.message}\n`);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
printHelp();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default async function (args) {
|
|
172
|
+
await run(args);
|
|
173
|
+
}
|
package/src/cli/connect.mjs
CHANGED
|
@@ -28,6 +28,30 @@ const CLIENTS = {
|
|
|
28
28
|
mode: 'hermes',
|
|
29
29
|
memoryPath: path.join(os.homedir(), '.hermes', 'memories', 'MEMORY.md'),
|
|
30
30
|
userPath: path.join(os.homedir(), '.hermes', 'memories', 'USER.md'),
|
|
31
|
+
skillsProjection: { scope: 'project', dir: path.join('.hermes', 'skills') }
|
|
32
|
+
},
|
|
33
|
+
'hermes-agent': {
|
|
34
|
+
label: 'Hermes Agent',
|
|
35
|
+
mode: 'hermes',
|
|
36
|
+
memoryPath: path.join(os.homedir(), '.hermes', 'memories', 'MEMORY.md'),
|
|
37
|
+
userPath: path.join(os.homedir(), '.hermes', 'memories', 'USER.md'),
|
|
38
|
+
skillsProjection: { scope: 'project', dir: path.join('.hermes', 'skills') }
|
|
39
|
+
},
|
|
40
|
+
dsh: {
|
|
41
|
+
label: 'DeepSeek Harness (dsh)',
|
|
42
|
+
mode: 'dsh',
|
|
43
|
+
globalMemoryPath: path.join(os.homedir(), '.dsh', 'memory', 'MEMORY.md'),
|
|
44
|
+
globalUserPath: path.join(os.homedir(), '.dsh', 'memory', 'user.md'),
|
|
45
|
+
localTarget: 'AGENTS.md',
|
|
46
|
+
skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
|
|
47
|
+
},
|
|
48
|
+
'deepseek-harness': {
|
|
49
|
+
label: 'DeepSeek Harness',
|
|
50
|
+
mode: 'dsh',
|
|
51
|
+
globalMemoryPath: path.join(os.homedir(), '.dsh', 'memory', 'MEMORY.md'),
|
|
52
|
+
globalUserPath: path.join(os.homedir(), '.dsh', 'memory', 'user.md'),
|
|
53
|
+
localTarget: 'AGENTS.md',
|
|
54
|
+
skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
|
|
31
55
|
},
|
|
32
56
|
openclaw: {
|
|
33
57
|
label: 'OpenClaw',
|
|
@@ -163,6 +187,10 @@ function printHelp() {
|
|
|
163
187
|
antigravity
|
|
164
188
|
gemini
|
|
165
189
|
aider
|
|
190
|
+
pi
|
|
191
|
+
hermes
|
|
192
|
+
dsh
|
|
193
|
+
openclaw
|
|
166
194
|
http-api
|
|
167
195
|
obsidian
|
|
168
196
|
generic
|
|
@@ -797,14 +825,71 @@ export default async function connect(args) {
|
|
|
797
825
|
result.notes.push(` ~/.pi/agent/AGENTS.md → ${instructionsPath}`);
|
|
798
826
|
} else if (preset.mode === 'hermes') {
|
|
799
827
|
// Hermes: write compiled MEMORY.md to ~/.hermes/memories/MEMORY.md (max 2200 chars)
|
|
828
|
+
// and project-local .hermes/memories/MEMORY.md if inside a project workspace
|
|
800
829
|
const instructions = readInstructions(cwd, agentDir);
|
|
801
830
|
if (!instructions) throw new Error('No INSTRUCTIONS.md found. Run npx total-recall init or compile first.');
|
|
802
|
-
|
|
831
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
832
|
+
const globalMemPath = path.join(homeDir, '.hermes', 'memories', 'MEMORY.md');
|
|
803
833
|
const capped = instructions.slice(0, 2200);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
834
|
+
|
|
835
|
+
let wroteGlobal = false;
|
|
836
|
+
try {
|
|
837
|
+
fs.mkdirSync(path.dirname(globalMemPath), { recursive: true });
|
|
838
|
+
fs.writeFileSync(globalMemPath, capped, 'utf8');
|
|
839
|
+
wroteGlobal = true;
|
|
840
|
+
} catch {
|
|
841
|
+
// Gracefully continue if global directory is unwritable in sandbox
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
let localMemPath = null;
|
|
845
|
+
if (!isHome) {
|
|
846
|
+
localMemPath = path.join(cwd, '.hermes', 'memories', 'MEMORY.md');
|
|
847
|
+
fs.mkdirSync(path.dirname(localMemPath), { recursive: true });
|
|
848
|
+
fs.writeFileSync(localMemPath, capped, 'utf8');
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
result.projection = { targetPath: localMemPath || globalMemPath, action: 'written' };
|
|
852
|
+
if (wroteGlobal) {
|
|
853
|
+
result.notes.push(` Hermes MEMORY.md (${capped.length} chars) → ${globalMemPath}`);
|
|
854
|
+
}
|
|
855
|
+
if (localMemPath) {
|
|
856
|
+
result.notes.push(` Hermes local MEMORY.md → ${localMemPath}`);
|
|
857
|
+
}
|
|
807
858
|
result.notes.push(' Note: Hermes has a 2,200-char limit on MEMORY.md. Total Recall auto-caps this.');
|
|
859
|
+
} else if (preset.mode === 'dsh') {
|
|
860
|
+
// DeepSeek Harness: write global ~/.dsh/memory/MEMORY.md and symlink project AGENTS.md
|
|
861
|
+
const instructions = readInstructions(cwd, agentDir);
|
|
862
|
+
if (!instructions) throw new Error('No INSTRUCTIONS.md found. Run npx total-recall init or compile first.');
|
|
863
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
864
|
+
const globalMemPath = path.join(homeDir, '.dsh', 'memory', 'MEMORY.md');
|
|
865
|
+
|
|
866
|
+
let wroteGlobal = false;
|
|
867
|
+
try {
|
|
868
|
+
fs.mkdirSync(path.dirname(globalMemPath), { recursive: true });
|
|
869
|
+
fs.writeFileSync(globalMemPath, instructions, 'utf8');
|
|
870
|
+
wroteGlobal = true;
|
|
871
|
+
} catch {
|
|
872
|
+
// Gracefully continue if global directory is unwritable in sandbox
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const agentsPath = path.join(cwd, preset.localTarget);
|
|
876
|
+
if (pathExists(agentsPath)) {
|
|
877
|
+
if (opts.force) {
|
|
878
|
+
try {
|
|
879
|
+
fs.unlinkSync(agentsPath);
|
|
880
|
+
} catch {
|
|
881
|
+
fs.rmSync(agentsPath, { force: true, recursive: true });
|
|
882
|
+
}
|
|
883
|
+
fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
|
|
884
|
+
}
|
|
885
|
+
} else {
|
|
886
|
+
fs.symlinkSync('INSTRUCTIONS.md', agentsPath);
|
|
887
|
+
}
|
|
888
|
+
result.projection = { targetPath: agentsPath, action: 'symlinked' };
|
|
889
|
+
if (wroteGlobal) {
|
|
890
|
+
result.notes.push(` DeepSeek Harness MEMORY.md → ${globalMemPath}`);
|
|
891
|
+
}
|
|
892
|
+
result.notes.push(` DeepSeek Harness AGENTS.md → INSTRUCTIONS.md`);
|
|
808
893
|
} else if (preset.mode === 'openclaw') {
|
|
809
894
|
// OpenClaw: write MEMORY.md + symlink AGENTS.md in cwd
|
|
810
895
|
const instructions = readInstructions(cwd, agentDir);
|
package/src/cli/deploy-ui.mjs
CHANGED
|
@@ -692,6 +692,9 @@ export function startDeployUI(port = 3001) {
|
|
|
692
692
|
'gemini': [HOME + '/.gemini'],
|
|
693
693
|
'pi': [HOME + '/.pi/agent'],
|
|
694
694
|
'hermes': [HOME + '/.hermes'],
|
|
695
|
+
'hermes-agent':[HOME + '/.hermes'],
|
|
696
|
+
'dsh': [HOME + '/.dsh'],
|
|
697
|
+
'deepseek-harness': [HOME + '/.dsh'],
|
|
695
698
|
'openclaw': [HOME + '/.openclaw'],
|
|
696
699
|
'obsidian': [HOME + '/Library/Application Support/obsidian', HOME + '/.config/obsidian'],
|
|
697
700
|
};
|
|
@@ -725,6 +728,9 @@ export function startDeployUI(port = 3001) {
|
|
|
725
728
|
'gemini': { client: 'gemini', label: 'Gemini CLI' },
|
|
726
729
|
'pi': { client: 'pi', label: 'Pi Coding Agent' },
|
|
727
730
|
'hermes': { client: 'hermes', label: 'Hermes Agent' },
|
|
731
|
+
'hermes-agent':{ client: 'hermes-agent', label: 'Hermes Agent' },
|
|
732
|
+
'dsh': { client: 'dsh', label: 'DeepSeek Harness (dsh)' },
|
|
733
|
+
'deepseek-harness': { client: 'deepseek-harness', label: 'DeepSeek Harness' },
|
|
728
734
|
'openclaw': { client: 'openclaw', label: 'OpenClaw' },
|
|
729
735
|
};
|
|
730
736
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { detectHarnesses, dispatchTask, runCouncil, HARNESS_SPECS } from '../core/meta-harness.mjs';
|
|
4
|
+
|
|
5
|
+
function printHelp() {
|
|
6
|
+
console.log(`
|
|
7
|
+
🎮 Total Recall — Meta Harness & Multi-Agent Management Layer
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
npx total-recall harness <command> [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
list Inspect all external IDE and CLI developer harnesses
|
|
14
|
+
dispatch <id> "<task>" Headlessly invoke a specific agent harness
|
|
15
|
+
Options: --node <name> to execute remotely on a mesh node
|
|
16
|
+
council "<task>" Run concurrent multi-harness consensus deliberation
|
|
17
|
+
|
|
18
|
+
Available Harness IDs:
|
|
19
|
+
agy Google Antigravity CLI (Frontier reasoning, AI Ultra)
|
|
20
|
+
claude Claude Code CLI (Deep codebase refactoring & Unix execution)
|
|
21
|
+
codex OpenAI Codex CLI (Program synthesis & sandbox execution)
|
|
22
|
+
gemini Google Gemini CLI (Fast utilities & completion chaining)
|
|
23
|
+
ollama Ollama Local LLM (Local neural reasoning, zero API cost)
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
npx total-recall harness list
|
|
27
|
+
npx total-recall harness dispatch claude "Review and run code quality checks on src/core/"
|
|
28
|
+
npx total-recall harness dispatch ollama --node macmini "What is test-time compute scaling?"
|
|
29
|
+
npx total-recall harness council "Propose architecture for decentralized research mesh"
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function run(argv) {
|
|
34
|
+
let args = argv;
|
|
35
|
+
if (Array.isArray(args) && args[0]?.endsWith('node')) {
|
|
36
|
+
args = args.slice(2);
|
|
37
|
+
}
|
|
38
|
+
if (args[0] === 'harness') {
|
|
39
|
+
args = args.slice(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const command = args[0];
|
|
43
|
+
const rest = args.slice(1);
|
|
44
|
+
|
|
45
|
+
if (!command || command === 'list' || command === 'status') {
|
|
46
|
+
const harnesses = detectHarnesses();
|
|
47
|
+
console.log(`\n🎮 Total Recall — Connected Agent Harnesses & Runtimes\n`);
|
|
48
|
+
|
|
49
|
+
const pad = (s, l) => String(s || '').padEnd(l).slice(0, l);
|
|
50
|
+
console.log('┌──────────────┬──────────────────────────────┬──────────────┬────────────────────────────────────────────────────────┐');
|
|
51
|
+
console.log(`│ ${pad('Harness ID', 12)} │ ${pad('Name', 28)} │ ${pad('Status', 12)} │ ${pad('Binary Path', 54)} │`);
|
|
52
|
+
console.log('├──────────────┼──────────────────────────────┼──────────────┼────────────────────────────────────────────────────────┤');
|
|
53
|
+
|
|
54
|
+
for (const h of harnesses) {
|
|
55
|
+
const status = h.available ? '\x1b[32mActive ✅\x1b[0m ' : '\x1b[31mNot Found ❌\x1b[0m';
|
|
56
|
+
const p = h.binaryPath || '—';
|
|
57
|
+
console.log(`│ \x1b[1m${pad(h.id, 12)}\x1b[0m │ ${pad(h.name, 28)} │ ${status} │ ${pad(p, 54)} │`);
|
|
58
|
+
}
|
|
59
|
+
console.log('└──────────────┴──────────────────────────────┴──────────────┴────────────────────────────────────────────────────────┘\n');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (command === 'dispatch') {
|
|
64
|
+
let targetNode = null;
|
|
65
|
+
const filteredRest = [];
|
|
66
|
+
for (let i = 0; i < rest.length; i++) {
|
|
67
|
+
if (rest[i] === '--node' || rest[i] === '-n') {
|
|
68
|
+
targetNode = rest[i + 1];
|
|
69
|
+
i++;
|
|
70
|
+
} else {
|
|
71
|
+
filteredRest.push(rest[i]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const harnessId = filteredRest[0];
|
|
76
|
+
const task = filteredRest.slice(1).join(' ').trim();
|
|
77
|
+
if (!harnessId || !task) {
|
|
78
|
+
console.error('Error: Please specify harness ID and task prompt.');
|
|
79
|
+
console.error('Usage: npx total-recall harness dispatch <id> [--node <node>] "<task>"');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const nodeStr = targetNode ? ` on mesh node \x1b[1;33m${targetNode}\x1b[0m` : '';
|
|
84
|
+
console.log(`\n🚀 Dispatching task to \x1b[1;36m${harnessId}\x1b[0m${nodeStr}...`);
|
|
85
|
+
try {
|
|
86
|
+
const res = await dispatchTask(harnessId, task, { node: targetNode });
|
|
87
|
+
console.log(`\n--- [Response from ${res.harnessName}${targetNode ? ' (' + targetNode + ')' : ''}] ---\n`);
|
|
88
|
+
console.log(res.response);
|
|
89
|
+
console.log(`\n✅ Exit code: ${res.exitCode}`);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(`\n❌ Dispatch failed: ${err.message}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (command === 'council') {
|
|
98
|
+
const task = rest.join(' ').trim();
|
|
99
|
+
if (!task) {
|
|
100
|
+
console.error('Error: Please specify task prompt for council deliberation.');
|
|
101
|
+
console.error('Usage: npx total-recall harness council "<task>"');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`\n🏛️ Convening Multi-Agent Council across all available harnesses...`);
|
|
106
|
+
console.log(` Task: "${task}"\n`);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const councilRes = await runCouncil(task);
|
|
110
|
+
console.log(`Participating Harnesses: ${councilRes.participants.join(', ')}\n`);
|
|
111
|
+
|
|
112
|
+
for (const r of councilRes.results) {
|
|
113
|
+
console.log(`================================================================`);
|
|
114
|
+
console.log(`● Harness: \x1b[1;36m${r.harnessName}\x1b[0m (${r.harnessId})`);
|
|
115
|
+
console.log(`================================================================`);
|
|
116
|
+
if (r.success) {
|
|
117
|
+
console.log(r.response);
|
|
118
|
+
} else {
|
|
119
|
+
console.log(`\x1b[31mError: ${r.error || r.stderr || 'Execution failed'}\x1b[0m`);
|
|
120
|
+
}
|
|
121
|
+
console.log('');
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(`\n❌ Council deliberation failed: ${err.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
printHelp();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default async function (args) {
|
|
134
|
+
await run(args);
|
|
135
|
+
}
|
|
@@ -11,6 +11,7 @@ function printHelp() {
|
|
|
11
11
|
--dry-run Preview import without writing
|
|
12
12
|
--global Target the global brain
|
|
13
13
|
--project Target the project brain (default)
|
|
14
|
+
--plugins Discover and ingest OpenWiki hubs from installed plugins
|
|
14
15
|
--help, -h Show this help
|
|
15
16
|
`);
|
|
16
17
|
}
|
|
@@ -18,6 +19,7 @@ function printHelp() {
|
|
|
18
19
|
export async function runOpenWikiIngest(args) {
|
|
19
20
|
let openwikiPath = null;
|
|
20
21
|
let dryRun = false;
|
|
22
|
+
let includePlugins = false;
|
|
21
23
|
let layerFlag = '--project';
|
|
22
24
|
let help = false;
|
|
23
25
|
|
|
@@ -27,6 +29,7 @@ export async function runOpenWikiIngest(args) {
|
|
|
27
29
|
case '--dry-run': dryRun = true; break;
|
|
28
30
|
case '--global': layerFlag = '--global'; break;
|
|
29
31
|
case '--project': layerFlag = '--project'; break;
|
|
32
|
+
case '--plugins': includePlugins = true; break;
|
|
30
33
|
case '--help': case '-h': help = true; break;
|
|
31
34
|
default:
|
|
32
35
|
if (!arg.startsWith('-') && !openwikiPath) {
|
|
@@ -35,12 +38,12 @@ export async function runOpenWikiIngest(args) {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
if (help || !openwikiPath) {
|
|
41
|
+
if (help || (!openwikiPath && !includePlugins)) {
|
|
39
42
|
printHelp();
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
if (!fs.existsSync(openwikiPath)) {
|
|
46
|
+
if (openwikiPath && !fs.existsSync(openwikiPath)) {
|
|
44
47
|
console.error(`❌ Error: OpenWiki directory not found at ${openwikiPath}`);
|
|
45
48
|
process.exit(1);
|
|
46
49
|
}
|
|
@@ -116,6 +119,20 @@ export async function runOpenWikiIngest(args) {
|
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
if (includePlugins) {
|
|
123
|
+
console.log(`\n 🔌 Ingesting OpenWiki hubs from installed plugins...`);
|
|
124
|
+
const pluginResults = await syncPluginOpenWikiHubs({ dryRun, layerFlag });
|
|
125
|
+
for (const r of pluginResults) {
|
|
126
|
+
if (r.status === 'imported' || r.status === 'dry_run') {
|
|
127
|
+
console.log(` 🟢 [Plugin Hub] ${r.slug} ("${r.title}")`);
|
|
128
|
+
imported++;
|
|
129
|
+
} else {
|
|
130
|
+
console.error(` 🔴 [Plugin Hub] Error importing ${r.slug}: ${r.error}`);
|
|
131
|
+
errors++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
119
136
|
console.log(`\n Import Summary:`);
|
|
120
137
|
console.log(` 🟢 Imported: ${imported} files`);
|
|
121
138
|
console.log(` 🔴 Errors: ${errors} files`);
|
|
@@ -124,3 +141,55 @@ export async function runOpenWikiIngest(args) {
|
|
|
124
141
|
console.log(`\n ✅ OpenWiki successfully integrated into Total Recall.`);
|
|
125
142
|
}
|
|
126
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Discover and ingest OpenWiki hubs declared by installed plugins.
|
|
147
|
+
*/
|
|
148
|
+
export async function syncPluginOpenWikiHubs(options = {}) {
|
|
149
|
+
const { projectRoot = process.cwd(), dryRun = false, layerFlag = '--project' } = options;
|
|
150
|
+
const { discoverPlugins } = await import('../core/plugin-loader.mjs');
|
|
151
|
+
const plugins = discoverPlugins(projectRoot);
|
|
152
|
+
const results = [];
|
|
153
|
+
|
|
154
|
+
for (const plugin of plugins) {
|
|
155
|
+
const hubs = plugin.manifest?.openwiki_hubs || [];
|
|
156
|
+
for (const hub of hubs) {
|
|
157
|
+
if (!hub.path) continue;
|
|
158
|
+
const hubFilePath = path.isAbsolute(hub.path) ? hub.path : path.join(plugin.dir, hub.path);
|
|
159
|
+
if (!fs.existsSync(hubFilePath)) continue;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const content = fs.readFileSync(hubFilePath, 'utf8');
|
|
163
|
+
const title = hub.title || path.basename(hub.path, '.md').replace(/[-_]/g, ' ');
|
|
164
|
+
const slug = `openwiki-plugin-${plugin.id}-${path.basename(hub.path, '.md')}`.toLowerCase();
|
|
165
|
+
|
|
166
|
+
if (dryRun) {
|
|
167
|
+
results.push({ slug, title, status: 'dry_run' });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const cliArgs = [
|
|
172
|
+
process.argv[1],
|
|
173
|
+
'remember', 'fact',
|
|
174
|
+
content,
|
|
175
|
+
'--title', title,
|
|
176
|
+
'--slug', slug,
|
|
177
|
+
'--tags', `openwiki,plugin,plugin-${plugin.id}`,
|
|
178
|
+
layerFlag
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const res = spawnSync(process.argv[0], cliArgs, { encoding: 'utf8' });
|
|
182
|
+
results.push({
|
|
183
|
+
slug,
|
|
184
|
+
title,
|
|
185
|
+
status: res.status === 0 ? 'imported' : 'error',
|
|
186
|
+
error: res.status === 0 ? null : res.stderr
|
|
187
|
+
});
|
|
188
|
+
} catch (err) {
|
|
189
|
+
results.push({ slug: hub.path, title: hub.title, status: 'error', error: err.message });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return results;
|
|
195
|
+
}
|
package/src/cli/init.mjs
CHANGED
package/src/cli/lint.mjs
CHANGED
|
@@ -22,13 +22,14 @@ import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
|
22
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
|
|
24
24
|
function parseArgs(args) {
|
|
25
|
-
const opts = { vault: null, strict: false, json: false, help: false, okf: false };
|
|
25
|
+
const opts = { vault: null, strict: false, json: false, help: false, okf: false, plugins: false };
|
|
26
26
|
for (let i = 0; i < args.length; i++) {
|
|
27
27
|
switch (args[i]) {
|
|
28
28
|
case '--vault': opts.vault = args[++i]; break;
|
|
29
29
|
case '--strict': opts.strict = true; break;
|
|
30
30
|
case '--json': opts.json = true; break;
|
|
31
31
|
case '--okf': opts.okf = true; break;
|
|
32
|
+
case '--plugins': opts.plugins = true; break;
|
|
32
33
|
case '--help': case '-h': opts.help = true; break;
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -37,10 +38,11 @@ function parseArgs(args) {
|
|
|
37
38
|
|
|
38
39
|
function printHelp() {
|
|
39
40
|
console.log(`
|
|
40
|
-
total-recall lint — Validate vault nodes against SSSS schema v2
|
|
41
|
+
total-recall lint — Validate vault nodes against SSSS schema v2, OKF spec, or plugins
|
|
41
42
|
|
|
42
43
|
Scans all .md files in the memory vault and validates their YAML
|
|
43
|
-
frontmatter against the Zod schemas
|
|
44
|
+
frontmatter against the Zod schemas, checks OKF compliance, or validates
|
|
45
|
+
installed plugin.json manifests.
|
|
44
46
|
|
|
45
47
|
Usage: total-recall lint [options]
|
|
46
48
|
|
|
@@ -50,6 +52,7 @@ function printHelp() {
|
|
|
50
52
|
--project Lint the project vault for the current repo
|
|
51
53
|
--strict Treat warnings as errors (exit 1 on any issue)
|
|
52
54
|
--okf Verify OKF v0.1 draft metadata compliance
|
|
55
|
+
--plugins Validate all installed plugin manifests (plugin.json)
|
|
53
56
|
--json Output results as JSONL
|
|
54
57
|
--help, -h Show this help
|
|
55
58
|
|
|
@@ -102,6 +105,47 @@ export default async function lint(args) {
|
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
104
107
|
|
|
108
|
+
if (opts.plugins) {
|
|
109
|
+
const { discoverPlugins } = await import('../core/plugin-loader.mjs');
|
|
110
|
+
const plugins = discoverPlugins(process.cwd());
|
|
111
|
+
let pluginErrors = 0;
|
|
112
|
+
|
|
113
|
+
if (!opts.json) {
|
|
114
|
+
console.error(`\n 🔌 Linting installed plugins:\n`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const p of plugins) {
|
|
118
|
+
if (!p.valid) {
|
|
119
|
+
pluginErrors += p.errors.length;
|
|
120
|
+
if (opts.json) {
|
|
121
|
+
console.log(JSON.stringify({ plugin: p.id, manifestPath: p.manifestPath, level: 'error', errors: p.errors }));
|
|
122
|
+
} else {
|
|
123
|
+
console.error(` ❌ Plugin '${p.id}' (${p.manifestPath}):`);
|
|
124
|
+
for (const err of p.errors) {
|
|
125
|
+
console.error(` ${err}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (opts.json) {
|
|
130
|
+
console.log(JSON.stringify({ plugin: p.id, manifestPath: p.manifestPath, level: 'info', valid: true }));
|
|
131
|
+
} else {
|
|
132
|
+
console.error(` ✅ Plugin '${p.id}' (v${p.manifest.version || '0.0.0'}): valid`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!opts.json) {
|
|
138
|
+
console.error(`\n ── Plugin Results ──`);
|
|
139
|
+
console.error(` Total plugins scanned: ${plugins.length}`);
|
|
140
|
+
console.error(` Errors: ${pluginErrors}\n`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (pluginErrors > 0) {
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
105
149
|
if (!opts.json) {
|
|
106
150
|
console.error(`\n 🔍 Linting vault: ${vaultDir}\n`);
|
|
107
151
|
}
|