total-recall-brain 3.41.4 → 3.41.6
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 +10 -6
- package/docs/reference/cli-reference.md +18 -0
- package/frontend/dist/assets/index-4tGQfTa2.js +5605 -0
- package/frontend/dist/assets/index-DtwsDyFM.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +11 -0
- package/frontend/src/App.tsx +47 -122
- package/frontend/src/components/plugins/plugin-frame.ts +1 -0
- package/frontend/src/index.css +152 -86
- package/frontend/src/pages/HelpPage.tsx +13 -10
- package/frontend/src/pages/MemoryPage.tsx +28 -30
- package/frontend/src/pages/NetworkPage.tsx +3 -3
- package/frontend/src/pages/PluginsPage.tsx +6 -5
- package/frontend/src/pages/RulesPage.tsx +16 -12
- package/package.json +4 -3
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +4 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3127 -49
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +136 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +135 -26
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +228 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +24 -30
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +29 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +73 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +182 -365
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/agy.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/claude.mjs +275 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cline.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/codex.mjs +187 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/cursor.mjs +158 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/devin.mjs +157 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/dsh.mjs +183 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/grok.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +87 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/openclaw.mjs +173 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/pi.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate.mjs +12 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/json-reply.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +72 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/model-catalog.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/paths.mjs +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +169 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +136 -346
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/ssss-store.mjs +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/telemetry.mjs +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +117 -35
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +398 -57
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +304 -46
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/mermaid-diagrams/SKILL.md +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/skills/write-connector/SKILL.md +56 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3097 -81
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +4 -1
- package/src/cli/agent.mjs +12 -1
- package/src/cli/compact.mjs +108 -0
- package/src/cli/connect.mjs +56 -1
- package/src/cli/context.mjs +55 -23
- package/src/cli/doctor.mjs +5 -2
- package/src/cli/forget.mjs +2 -0
- package/src/cli/harness.mjs +62 -50
- package/src/cli/memory-entry.mjs +2 -2
- package/src/cli/preflight.mjs +152 -16
- package/src/cli/release.mjs +386 -0
- package/src/cli/remember.mjs +19 -1
- package/src/cli/rules.mjs +54 -12
- package/src/cli/startup.mjs +1 -1
- package/src/cli/status.mjs +12 -2
- package/src/core/claude-hooks.mjs +41 -3
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-semantic-index.mjs +3 -2
- package/src/core/conflict-detector.mjs +23 -12
- package/src/core/context-compactor.mjs +145 -0
- package/src/core/context-policy.mjs +20 -0
- package/src/core/duplicate-guard.mjs +350 -0
- package/src/core/meta-harness.mjs +31 -11
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +29 -1
- package/src/core/scaffold-plugins.mjs +7 -0
- package/src/core/stdio-flush.mjs +23 -0
- package/src/core/surface.mjs +33 -10
- package/src/server/invoke-durability.mjs +39 -0
- package/src/server/invoke-write-child.mjs +25 -0
- package/src/server/routes/invoke.mjs +7 -0
- package/src/server/routes/plugins.mjs +2 -2
- package/frontend/dist/assets/index-BIQmqDKP.css +0 -1
- package/frontend/dist/assets/index-CRzjQLu7.js +0 -5631
- package/frontend/src/components/DaemonLogsTab.tsx +0 -54
- package/frontend/src/components/ResearchAgendaTab.tsx +0 -712
- package/frontend/src/components/TaskQueueTab.tsx +0 -193
- package/frontend/src/components/VoiceInput.tsx +0 -112
- package/frontend/src/pages/ChatPage.tsx +0 -1059
- package/frontend/src/pages/CollabPage.tsx +0 -470
- package/frontend/src/pages/DesignDocsPage.tsx +0 -621
- package/frontend/src/pages/FilesPage.tsx +0 -393
- package/frontend/src/pages/HealthPage.tsx +0 -359
- package/frontend/src/pages/InboxPage.tsx +0 -83
- package/frontend/src/pages/InstructionsPage.tsx +0 -583
- package/frontend/src/pages/IntegrationsPage.tsx +0 -761
- package/frontend/src/pages/MeshPage.css +0 -58
- package/frontend/src/pages/MeshPage.tsx +0 -1219
- package/frontend/src/pages/OkfPage.tsx +0 -535
- package/frontend/src/pages/SandboxPage.tsx +0 -113
- package/frontend/src/pages/SkillsPage.tsx +0 -1994
- package/frontend/src/pages/TasksPage.tsx +0 -427
- package/frontend/src/pages/UsagePage.tsx +0 -702
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +0 -65
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +0 -76
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +0 -19
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -266
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +0 -446
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +0 -152
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +0 -71
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +0 -35
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +0 -175
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +0 -575
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness (DSH) PreToolUse hook adapter.
|
|
3
|
+
* Migrates legacy tr-first.sh / tr-wrapper.sh into managed preflight gate.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
|
|
9
|
+
const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
|
|
10
|
+
|
|
11
|
+
export const dshAdapter = {
|
|
12
|
+
id: 'dsh',
|
|
13
|
+
level: 'enforced',
|
|
14
|
+
source: {
|
|
15
|
+
note: 'DeepSeek Harness hook configuration in ~/.dsh/hooks/hooks.json',
|
|
16
|
+
retrieved: '2026-11-04'
|
|
17
|
+
},
|
|
18
|
+
limits: [
|
|
19
|
+
'Claude-compatible PreToolUse contract: exit code 2 blocks execution',
|
|
20
|
+
'Repository sweep checks enforce Total Recall consult requirement'
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
parse(input = {}) {
|
|
24
|
+
const event = input.hook_event_name || input.event || 'PreToolUse';
|
|
25
|
+
const toolName = input.tool_name || input.toolName || '';
|
|
26
|
+
const toolInput = input.tool_input || input.toolInput || {};
|
|
27
|
+
const cwd = input.cwd || process.cwd();
|
|
28
|
+
|
|
29
|
+
let tool = 'other';
|
|
30
|
+
let command;
|
|
31
|
+
let filePath;
|
|
32
|
+
|
|
33
|
+
if (toolName === 'Bash' || toolName === 'shell') {
|
|
34
|
+
tool = 'shell';
|
|
35
|
+
command = toolInput.command || toolInput.cmd || '';
|
|
36
|
+
} else if (toolName === 'Edit' || toolName === 'Write') {
|
|
37
|
+
tool = 'write';
|
|
38
|
+
filePath = toolInput.file_path || toolInput.path || '';
|
|
39
|
+
} else if (toolName === 'Read' || /^(Glob|Grep)$/.test(toolName)) {
|
|
40
|
+
tool = 'read';
|
|
41
|
+
filePath = toolInput.file_path || toolInput.path || '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
event,
|
|
46
|
+
tool,
|
|
47
|
+
command,
|
|
48
|
+
path: filePath,
|
|
49
|
+
cwd,
|
|
50
|
+
raw: input
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
emit(decision, event, cwd = process.cwd()) {
|
|
55
|
+
if (decision?.decision === 'allow') {
|
|
56
|
+
return { stdout: '', exitCode: 0 };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
|
|
60
|
+
const payload = {
|
|
61
|
+
decision: 'deny',
|
|
62
|
+
permissionDecision: 'deny',
|
|
63
|
+
reason,
|
|
64
|
+
fix: decision?.fix || ACK_COMMAND
|
|
65
|
+
};
|
|
66
|
+
return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async run({ stdin, decideWith }) {
|
|
70
|
+
const parsed = this.parse(stdin);
|
|
71
|
+
const decision = decideWith({
|
|
72
|
+
tool: parsed.tool,
|
|
73
|
+
command: parsed.command,
|
|
74
|
+
path: parsed.path,
|
|
75
|
+
cwd: parsed.cwd
|
|
76
|
+
});
|
|
77
|
+
return this.emit(decision, parsed.event, parsed.cwd);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
install({ home = os.homedir(), dryRun = false, remove = false, off = false } = {}) {
|
|
81
|
+
const isRemove = remove || off;
|
|
82
|
+
const dshDir = path.join(home, '.dsh', 'hooks');
|
|
83
|
+
const hooksPath = path.join(dshDir, 'hooks.json');
|
|
84
|
+
const legacyTrFirst = path.join(dshDir, 'tr-first.sh');
|
|
85
|
+
const added = [];
|
|
86
|
+
const removed = [];
|
|
87
|
+
|
|
88
|
+
let hooksConfig = { hooks: {} };
|
|
89
|
+
if (fs.existsSync(hooksPath)) {
|
|
90
|
+
try {
|
|
91
|
+
hooksConfig = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw new Error(`${hooksPath} is not valid JSON; fix it before installing hooks: ${err.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
hooksConfig.hooks = hooksConfig.hooks || {};
|
|
97
|
+
hooksConfig.hooks.PreToolUse = hooksConfig.hooks.PreToolUse || [];
|
|
98
|
+
|
|
99
|
+
if (isRemove) {
|
|
100
|
+
const beforeLen = hooksConfig.hooks.PreToolUse.length;
|
|
101
|
+
hooksConfig.hooks.PreToolUse = hooksConfig.hooks.PreToolUse.filter(e =>
|
|
102
|
+
!String(e.command || '').includes('preflight') && !String(e.command || '').includes('tr-first')
|
|
103
|
+
);
|
|
104
|
+
if (hooksConfig.hooks.PreToolUse.length < beforeLen) {
|
|
105
|
+
removed.push('managed PreToolUse gate');
|
|
106
|
+
}
|
|
107
|
+
if (!dryRun && fs.existsSync(hooksPath)) {
|
|
108
|
+
fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
|
|
109
|
+
}
|
|
110
|
+
return { settings: hooksPath, added: [], removed };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Remove legacy unmanaged tr-first.sh scripts if present (F-011)
|
|
114
|
+
const beforeLen = hooksConfig.hooks.PreToolUse.length;
|
|
115
|
+
hooksConfig.hooks.PreToolUse = hooksConfig.hooks.PreToolUse.filter(e => {
|
|
116
|
+
const cmd = String(e.command || '');
|
|
117
|
+
return !cmd.includes('tr-first.sh') && !cmd.includes('tr-wrapper.sh');
|
|
118
|
+
});
|
|
119
|
+
if (hooksConfig.hooks.PreToolUse.length < beforeLen) {
|
|
120
|
+
removed.push('legacy tr-first.sh hook entry');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const gateCommand = 'total-recall preflight gate --harness dsh';
|
|
124
|
+
const hasPreTool = hooksConfig.hooks.PreToolUse.some(e =>
|
|
125
|
+
String(e.command || '').includes('preflight gate --harness dsh')
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (!hasPreTool) {
|
|
129
|
+
hooksConfig.hooks.PreToolUse.push({
|
|
130
|
+
matcher: '*',
|
|
131
|
+
command: gateCommand,
|
|
132
|
+
timeout: 10
|
|
133
|
+
});
|
|
134
|
+
added.push('managed PreToolUse gate');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!dryRun && (added.length || removed.length)) {
|
|
138
|
+
fs.mkdirSync(dshDir, { recursive: true });
|
|
139
|
+
if (fs.existsSync(hooksPath)) {
|
|
140
|
+
fs.copyFileSync(hooksPath, `${hooksPath}.bak-total-recall`);
|
|
141
|
+
}
|
|
142
|
+
fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { settings: hooksPath, added, removed };
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
verify({ home = os.homedir() } = {}) {
|
|
149
|
+
const hooksPath = path.join(home, '.dsh', 'hooks', 'hooks.json');
|
|
150
|
+
let installed = false;
|
|
151
|
+
if (fs.existsSync(hooksPath)) {
|
|
152
|
+
try {
|
|
153
|
+
const config = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
|
|
154
|
+
const preHooks = config.hooks?.PreToolUse || [];
|
|
155
|
+
installed = preHooks.some(e => String(e.command || '').includes('preflight gate --harness dsh'));
|
|
156
|
+
} catch { /* unparseable */ }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const syntheticDeniedCall = {
|
|
160
|
+
hook_event_name: 'PreToolUse',
|
|
161
|
+
tool_name: 'Bash',
|
|
162
|
+
tool_input: { command: 'rm -rf /tmp/foo' },
|
|
163
|
+
cwd: '/nonexistent-directory-without-receipt'
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const out = this.emit(
|
|
167
|
+
{ decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
|
|
168
|
+
'PreToolUse',
|
|
169
|
+
syntheticDeniedCall.cwd
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const exercised = out.exitCode === 2 && out.stdout.includes('"decision":"deny"');
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
installed,
|
|
176
|
+
exercised,
|
|
177
|
+
level: 'enforced',
|
|
178
|
+
native: out.stdout.trim()
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export default dshAdapter;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xAI Grok Build CLI hook adapter.
|
|
3
|
+
* Official doc: https://docs.x.ai/build/features/hooks
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
|
|
9
|
+
const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
|
|
10
|
+
|
|
11
|
+
export const grokAdapter = {
|
|
12
|
+
id: 'grok',
|
|
13
|
+
level: 'enforced (unverified live)',
|
|
14
|
+
source: {
|
|
15
|
+
url: 'https://docs.x.ai/build/features/hooks',
|
|
16
|
+
retrieved: '2026-11-04',
|
|
17
|
+
note: 'Grok Build hooks in ~/.grok/hooks/ or .grok/hooks/'
|
|
18
|
+
},
|
|
19
|
+
limits: [
|
|
20
|
+
'Timeouts, crashes and malformed outputs fail open in Grok Build',
|
|
21
|
+
'Project-level hooks require trust in ~/.grok/trusted_folders.toml'
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
parse(input = {}) {
|
|
25
|
+
const event = input.hookEventName || input.event || 'PreToolUse';
|
|
26
|
+
const toolName = input.toolName || input.tool_name || input.tool || '';
|
|
27
|
+
const toolInput = input.toolInput || input.tool_input || input.input || {};
|
|
28
|
+
const cwd = input.cwd || input.workspaceRoot || process.cwd();
|
|
29
|
+
|
|
30
|
+
let tool = 'other';
|
|
31
|
+
let command;
|
|
32
|
+
let filePath;
|
|
33
|
+
|
|
34
|
+
if (toolName === 'Bash' || toolName === 'exec' || toolName === 'shell') {
|
|
35
|
+
tool = 'shell';
|
|
36
|
+
command = toolInput.command || toolInput.cmd || '';
|
|
37
|
+
} else if (/^(Edit|Write)$/.test(toolName)) {
|
|
38
|
+
tool = 'write';
|
|
39
|
+
filePath = toolInput.file_path || toolInput.path || '';
|
|
40
|
+
} else if (/^(Read|Glob|Grep)$/.test(toolName)) {
|
|
41
|
+
tool = 'read';
|
|
42
|
+
filePath = toolInput.file_path || toolInput.path || '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
event,
|
|
47
|
+
tool,
|
|
48
|
+
command,
|
|
49
|
+
path: filePath,
|
|
50
|
+
cwd,
|
|
51
|
+
raw: input
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
emit(decision, event, cwd = process.cwd()) {
|
|
56
|
+
if (decision?.decision === 'allow') {
|
|
57
|
+
const payload = { decision: 'allow' };
|
|
58
|
+
return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
|
|
62
|
+
const payload = {
|
|
63
|
+
decision: 'deny',
|
|
64
|
+
reason,
|
|
65
|
+
fix: decision?.fix || ACK_COMMAND
|
|
66
|
+
};
|
|
67
|
+
return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async run({ stdin, decideWith }) {
|
|
71
|
+
const parsed = this.parse(stdin);
|
|
72
|
+
const decision = decideWith({
|
|
73
|
+
tool: parsed.tool,
|
|
74
|
+
command: parsed.command,
|
|
75
|
+
path: parsed.path,
|
|
76
|
+
cwd: parsed.cwd
|
|
77
|
+
});
|
|
78
|
+
return this.emit(decision, parsed.event, parsed.cwd);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
install({ home = os.homedir(), dryRun = false } = {}) {
|
|
82
|
+
const grokDir = path.join(home, '.grok', 'hooks');
|
|
83
|
+
const hooksPath = path.join(grokDir, 'total-recall-preflight.json');
|
|
84
|
+
const added = [];
|
|
85
|
+
|
|
86
|
+
const hookDef = {
|
|
87
|
+
name: 'total-recall-preflight',
|
|
88
|
+
description: 'Fail-closed preflight gate for Total Recall',
|
|
89
|
+
events: ['PreToolUse'],
|
|
90
|
+
matcher: '*',
|
|
91
|
+
command: 'total-recall preflight gate --harness grok',
|
|
92
|
+
timeout: 5
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (!dryRun) {
|
|
96
|
+
fs.mkdirSync(grokDir, { recursive: true });
|
|
97
|
+
fs.writeFileSync(hooksPath, `${JSON.stringify(hookDef, null, 2)}\n`);
|
|
98
|
+
added.push('managed Grok PreToolUse hook');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { settings: hooksPath, added };
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
verify({ home = os.homedir() } = {}) {
|
|
105
|
+
const hooksPath = path.join(home, '.grok', 'hooks', 'total-recall-preflight.json');
|
|
106
|
+
const installed = fs.existsSync(hooksPath);
|
|
107
|
+
|
|
108
|
+
const syntheticDeniedCall = {
|
|
109
|
+
hookEventName: 'PreToolUse',
|
|
110
|
+
toolName: 'Bash',
|
|
111
|
+
toolInput: { command: 'rm -rf /tmp/foo' },
|
|
112
|
+
cwd: '/nonexistent-directory-without-receipt'
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const out = this.emit(
|
|
116
|
+
{ decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
|
|
117
|
+
'PreToolUse',
|
|
118
|
+
syntheticDeniedCall.cwd
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const exercised = out.exitCode === 2 && out.stdout.includes('"decision":"deny"');
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
installed,
|
|
125
|
+
exercised,
|
|
126
|
+
level: 'enforced (unverified live)',
|
|
127
|
+
native: out.stdout.trim()
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export default grokAdapter;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes Agent Runtime hook adapter.
|
|
3
|
+
* Official doc: https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
|
|
9
|
+
const ACK_COMMAND = 'total-recall tr-preflight --ack --task "<task>" --action read,edit,test';
|
|
10
|
+
|
|
11
|
+
export const hermesAdapter = {
|
|
12
|
+
id: 'hermes',
|
|
13
|
+
level: 'enforced',
|
|
14
|
+
source: {
|
|
15
|
+
url: 'https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks',
|
|
16
|
+
retrieved: '2026-11-04',
|
|
17
|
+
note: 'Hermes shell & plugin hooks in ~/.hermes/config.yaml'
|
|
18
|
+
},
|
|
19
|
+
limits: [
|
|
20
|
+
'Hermes plugin callbacks fail closed on timeout',
|
|
21
|
+
'Pre-tool hook returns {"action":"block","message":...} with exit 2'
|
|
22
|
+
],
|
|
23
|
+
|
|
24
|
+
parse(input = {}) {
|
|
25
|
+
const event = input.event || input.hook_event_name || 'pre_tool_call';
|
|
26
|
+
const toolName = input.tool_name || input.tool || '';
|
|
27
|
+
const toolInput = input.tool_input || input.params || input.args || {};
|
|
28
|
+
const cwd = input.cwd || process.cwd();
|
|
29
|
+
|
|
30
|
+
let tool = 'other';
|
|
31
|
+
let command;
|
|
32
|
+
let filePath;
|
|
33
|
+
|
|
34
|
+
if (toolName === 'terminal' || toolName === 'Bash' || toolName === 'shell' || toolName === 'exec') {
|
|
35
|
+
tool = 'shell';
|
|
36
|
+
command = toolInput.command || toolInput.cmd || '';
|
|
37
|
+
} else if (toolName === 'write_file' || toolName === 'Edit' || toolName === 'Write') {
|
|
38
|
+
tool = 'write';
|
|
39
|
+
filePath = toolInput.path || toolInput.file_path || '';
|
|
40
|
+
} else if (toolName === 'read_file' || toolName === 'Read') {
|
|
41
|
+
tool = 'read';
|
|
42
|
+
filePath = toolInput.path || toolInput.file_path || '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
event,
|
|
47
|
+
tool,
|
|
48
|
+
command,
|
|
49
|
+
path: filePath,
|
|
50
|
+
cwd,
|
|
51
|
+
raw: input
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
emit(decision, event, cwd = process.cwd()) {
|
|
56
|
+
if (decision?.decision === 'allow') {
|
|
57
|
+
const payload = { action: 'allow' };
|
|
58
|
+
return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 0 };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const reason = decision?.reason || `TR-PREFLIGHT gate: action denied. Run: ${decision?.fix || ACK_COMMAND}`;
|
|
62
|
+
const payload = {
|
|
63
|
+
action: 'block',
|
|
64
|
+
message: reason,
|
|
65
|
+
fix: decision?.fix || ACK_COMMAND
|
|
66
|
+
};
|
|
67
|
+
return { stdout: `${JSON.stringify(payload)}\n`, exitCode: 2 };
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async run({ stdin, decideWith }) {
|
|
71
|
+
const parsed = this.parse(stdin);
|
|
72
|
+
const decision = decideWith({
|
|
73
|
+
tool: parsed.tool,
|
|
74
|
+
command: parsed.command,
|
|
75
|
+
path: parsed.path,
|
|
76
|
+
cwd: parsed.cwd
|
|
77
|
+
});
|
|
78
|
+
return this.emit(decision, parsed.event, parsed.cwd);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
install({ home = os.homedir(), dryRun = false, remove = false, off = false } = {}) {
|
|
82
|
+
const isRemove = remove || off;
|
|
83
|
+
const hermesDir = path.join(home, '.hermes');
|
|
84
|
+
const hooksPath = path.join(hermesDir, 'hooks.json');
|
|
85
|
+
const added = [];
|
|
86
|
+
const removed = [];
|
|
87
|
+
|
|
88
|
+
let hooksConfig = { hooks: {} };
|
|
89
|
+
if (fs.existsSync(hooksPath)) {
|
|
90
|
+
try {
|
|
91
|
+
hooksConfig = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw new Error(`${hooksPath} is not valid JSON; fix it before installing hooks: ${err.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
hooksConfig.hooks = hooksConfig.hooks || {};
|
|
97
|
+
hooksConfig.hooks.pre_tool_call = hooksConfig.hooks.pre_tool_call || [];
|
|
98
|
+
|
|
99
|
+
if (isRemove) {
|
|
100
|
+
const beforeLen = hooksConfig.hooks.pre_tool_call.length;
|
|
101
|
+
hooksConfig.hooks.pre_tool_call = hooksConfig.hooks.pre_tool_call.filter(e =>
|
|
102
|
+
!String(e.command || '').includes('preflight')
|
|
103
|
+
);
|
|
104
|
+
if (hooksConfig.hooks.pre_tool_call.length < beforeLen) {
|
|
105
|
+
removed.push('managed pre_tool_call gate');
|
|
106
|
+
}
|
|
107
|
+
if (!dryRun && fs.existsSync(hooksPath)) {
|
|
108
|
+
fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
|
|
109
|
+
}
|
|
110
|
+
return { settings: hooksPath, added: [], removed };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const gateCommand = 'total-recall preflight gate --harness hermes';
|
|
114
|
+
const hasPreTool = hooksConfig.hooks.pre_tool_call.some(e =>
|
|
115
|
+
String(e.command || '').includes('preflight gate --harness hermes')
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!hasPreTool) {
|
|
119
|
+
hooksConfig.hooks.pre_tool_call.push({
|
|
120
|
+
matcher: '*',
|
|
121
|
+
command: gateCommand,
|
|
122
|
+
timeout: 10
|
|
123
|
+
});
|
|
124
|
+
added.push('managed pre_tool_call gate');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!dryRun && added.length) {
|
|
128
|
+
fs.mkdirSync(hermesDir, { recursive: true });
|
|
129
|
+
if (fs.existsSync(hooksPath)) {
|
|
130
|
+
fs.copyFileSync(hooksPath, `${hooksPath}.bak-total-recall`);
|
|
131
|
+
}
|
|
132
|
+
fs.writeFileSync(hooksPath, `${JSON.stringify(hooksConfig, null, 2)}\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { settings: hooksPath, added };
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
verify({ home = os.homedir() } = {}) {
|
|
139
|
+
const hooksPath = path.join(home, '.hermes', 'hooks.json');
|
|
140
|
+
let installed = false;
|
|
141
|
+
if (fs.existsSync(hooksPath)) {
|
|
142
|
+
try {
|
|
143
|
+
const config = JSON.parse(fs.readFileSync(hooksPath, 'utf8') || '{}');
|
|
144
|
+
const preHooks = config.hooks?.pre_tool_call || [];
|
|
145
|
+
installed = preHooks.some(e => String(e.command || '').includes('preflight gate --harness hermes'));
|
|
146
|
+
} catch { /* unparseable */ }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const syntheticDeniedCall = {
|
|
150
|
+
event: 'pre_tool_call',
|
|
151
|
+
tool_name: 'terminal',
|
|
152
|
+
tool_input: { command: 'rm -rf /tmp/foo' },
|
|
153
|
+
cwd: '/nonexistent-directory-without-receipt'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const out = this.emit(
|
|
157
|
+
{ decision: 'deny', reason: 'receipt missing', fix: ACK_COMMAND },
|
|
158
|
+
'pre_tool_call',
|
|
159
|
+
syntheticDeniedCall.cwd
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const exercised = out.exitCode === 2 && out.stdout.includes('"action":"block"');
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
installed,
|
|
166
|
+
exercised,
|
|
167
|
+
level: 'enforced',
|
|
168
|
+
native: out.stdout.trim()
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export default hermesAdapter;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate Adapters registry for Meta Harness (tr-plugin-cli-agents).
|
|
3
|
+
* Provides harness-specific parsing, emission, installation and verification.
|
|
4
|
+
*/
|
|
5
|
+
import { claudeAdapter } from './claude.mjs';
|
|
6
|
+
import { agyAdapter } from './agy.mjs';
|
|
7
|
+
import { codexAdapter } from './codex.mjs';
|
|
8
|
+
import { dshAdapter } from './dsh.mjs';
|
|
9
|
+
import { hermesAdapter } from './hermes.mjs';
|
|
10
|
+
import { openclawAdapter } from './openclaw.mjs';
|
|
11
|
+
import { cursorAdapter } from './cursor.mjs';
|
|
12
|
+
import { devinAdapter } from './devin.mjs';
|
|
13
|
+
import { grokAdapter } from './grok.mjs';
|
|
14
|
+
import { clineAdapter } from './cline.mjs';
|
|
15
|
+
import { piAdapter } from './pi.mjs';
|
|
16
|
+
|
|
17
|
+
const ADAPTERS = new Map([
|
|
18
|
+
['claude', claudeAdapter],
|
|
19
|
+
['claude-code', claudeAdapter],
|
|
20
|
+
['agy', agyAdapter],
|
|
21
|
+
['antigravity', agyAdapter],
|
|
22
|
+
['codex', codexAdapter],
|
|
23
|
+
['dsh', dshAdapter],
|
|
24
|
+
['hermes', hermesAdapter],
|
|
25
|
+
['openclaw', openclawAdapter],
|
|
26
|
+
['cursor', cursorAdapter],
|
|
27
|
+
['devin', devinAdapter],
|
|
28
|
+
['windsurf', devinAdapter],
|
|
29
|
+
['grok', grokAdapter],
|
|
30
|
+
['cline', clineAdapter],
|
|
31
|
+
['pi', piAdapter]
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
export function get(harnessId) {
|
|
35
|
+
if (!harnessId) return null;
|
|
36
|
+
return ADAPTERS.get(String(harnessId).toLowerCase()) || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function list() {
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const result = [];
|
|
42
|
+
for (const adapter of ADAPTERS.values()) {
|
|
43
|
+
if (!seen.has(adapter.id)) {
|
|
44
|
+
seen.add(adapter.id);
|
|
45
|
+
result.push(adapter);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function install(harnessId, opts = {}) {
|
|
52
|
+
const adapter = get(harnessId);
|
|
53
|
+
if (!adapter) throw new Error(`Unknown harness adapter: ${harnessId}`);
|
|
54
|
+
if (typeof adapter.install !== 'function') {
|
|
55
|
+
throw new Error(`Harness adapter ${harnessId} does not support installation`);
|
|
56
|
+
}
|
|
57
|
+
return adapter.install(opts);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function verify(harnessId, opts = {}) {
|
|
61
|
+
const adapter = get(harnessId);
|
|
62
|
+
if (!adapter) throw new Error(`Unknown harness adapter: ${harnessId}`);
|
|
63
|
+
if (typeof adapter.verify !== 'function') {
|
|
64
|
+
throw new Error(`Harness adapter ${harnessId} does not support verification`);
|
|
65
|
+
}
|
|
66
|
+
return adapter.verify(opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
get,
|
|
71
|
+
list,
|
|
72
|
+
install,
|
|
73
|
+
verify,
|
|
74
|
+
adapters: {
|
|
75
|
+
claude: claudeAdapter,
|
|
76
|
+
agy: agyAdapter,
|
|
77
|
+
codex: codexAdapter,
|
|
78
|
+
dsh: dshAdapter,
|
|
79
|
+
hermes: hermesAdapter,
|
|
80
|
+
openclaw: openclawAdapter,
|
|
81
|
+
cursor: cursorAdapter,
|
|
82
|
+
devin: devinAdapter,
|
|
83
|
+
grok: grokAdapter,
|
|
84
|
+
cline: clineAdapter,
|
|
85
|
+
pi: piAdapter
|
|
86
|
+
}
|
|
87
|
+
};
|