@moltnet/guard 1.0.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/README.md +122 -0
- package/clawdbot.plugin.json +65 -0
- package/index.ts +338 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# @moltnet/guard
|
|
2
|
+
|
|
3
|
+
**MoltGuard plugin for Clawdbot** — Security & observability for AI agents.
|
|
4
|
+
|
|
5
|
+
[](https://guard.moltnet.ai)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔍 **Action Logging** — Every tool call is logged with risk classification
|
|
10
|
+
- 🧠 **Mind Graph** — Visualize your agent's thought process in real-time
|
|
11
|
+
- ✋ **Intent Gating** — Require approval for high-risk actions
|
|
12
|
+
- 🎮 **Remote Control** — Pause, resume, or stop your agent remotely
|
|
13
|
+
- 📊 **Audit Dashboard** — Complete history with search, filters, and export
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
clawdbot plugins install @moltnet/guard
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
1. **Get your token** at [guard.moltnet.ai](https://guard.moltnet.ai)
|
|
24
|
+
|
|
25
|
+
2. **Add to your Clawdbot config** (`~/.clawdbot/config.json`):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"plugins": {
|
|
30
|
+
"entries": {
|
|
31
|
+
"moltguard": {
|
|
32
|
+
"enabled": true,
|
|
33
|
+
"config": {
|
|
34
|
+
"token": "mg_your_token_here",
|
|
35
|
+
"agentName": "my-assistant"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. **Restart Clawdbot**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
clawdbot gateway restart
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration Options
|
|
50
|
+
|
|
51
|
+
| Option | Type | Default | Description |
|
|
52
|
+
|--------|------|---------|-------------|
|
|
53
|
+
| `token` | string | **required** | Your MoltGuard API token |
|
|
54
|
+
| `url` | string | `https://guard.moltnet.ai` | MoltGuard server URL |
|
|
55
|
+
| `agentName` | string | `clawdbot-<hostname>` | Display name in dashboard |
|
|
56
|
+
| `logThoughts` | boolean | `true` | Send reasoning traces to Mind Graph |
|
|
57
|
+
| `gateHighRisk` | boolean | `true` | Require approval for high-risk actions |
|
|
58
|
+
| `gateTools` | string[] | `["exec", "message", "write"]` | Tools that require approval |
|
|
59
|
+
| `pollCommands` | boolean | `true` | Enable remote control (pause/resume/stop) |
|
|
60
|
+
| `pollIntervalMs` | number | `5000` | Command poll interval in ms |
|
|
61
|
+
|
|
62
|
+
## Risk Classification
|
|
63
|
+
|
|
64
|
+
The plugin automatically classifies tool calls by risk:
|
|
65
|
+
|
|
66
|
+
| Risk | Tools |
|
|
67
|
+
|------|-------|
|
|
68
|
+
| 🔴 **Critical** | `gateway`, `exec` (with `rm`, `sudo`) |
|
|
69
|
+
| 🟠 **High** | `exec`, `message`, `nodes` |
|
|
70
|
+
| 🟡 **Medium** | `write`, `edit`, `browser`, `cron`, `process` |
|
|
71
|
+
| 🟢 **Low** | `read`, `web_search`, `web_fetch`, `memory_*` |
|
|
72
|
+
|
|
73
|
+
## How It Works
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
77
|
+
│ Clawdbot │────▶│ MoltGuard │◀────│ Human │
|
|
78
|
+
│ (Agent) │ │ Plugin │ │ (Dashboard) │
|
|
79
|
+
└─────────────────┘ └────────┬────────┘ └─────────────────┘
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
┌─────────────────┐
|
|
83
|
+
│ guard.moltnet │
|
|
84
|
+
│ .ai │
|
|
85
|
+
└─────────────────┘
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
1. Plugin intercepts all tool calls via `tool_result_persist` hook
|
|
89
|
+
2. Actions are logged to MoltGuard with risk classification
|
|
90
|
+
3. High-risk actions wait for approval (if `gateHighRisk` enabled)
|
|
91
|
+
4. Human approves/rejects from dashboard or Telegram
|
|
92
|
+
5. Plugin polls for remote control commands
|
|
93
|
+
|
|
94
|
+
## Self-Hosting
|
|
95
|
+
|
|
96
|
+
If you're running your own MoltGuard instance:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"plugins": {
|
|
101
|
+
"entries": {
|
|
102
|
+
"moltguard": {
|
|
103
|
+
"enabled": true,
|
|
104
|
+
"config": {
|
|
105
|
+
"url": "https://your-moltguard.com",
|
|
106
|
+
"token": "your_token"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Links
|
|
115
|
+
|
|
116
|
+
- **Dashboard:** [guard.moltnet.ai](https://guard.moltnet.ai)
|
|
117
|
+
- **Docs:** [guard.moltnet.ai/docs](https://guard.moltnet.ai/docs)
|
|
118
|
+
- **GitHub:** [github.com/moltnet/guard](https://github.com/moltnet/guard)
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "moltguard",
|
|
3
|
+
"name": "MoltGuard",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Security & observability for AI agents. See what your agent is thinking, approve risky actions, control it remotely.",
|
|
6
|
+
"homepage": "https://guard.moltnet.ai",
|
|
7
|
+
"author": "MoltNet <hello@moltnet.ai>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"url": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"default": "https://guard.moltnet.ai",
|
|
16
|
+
"description": "MoltGuard server URL"
|
|
17
|
+
},
|
|
18
|
+
"token": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Your MoltGuard API token (get one at guard.moltnet.ai)"
|
|
21
|
+
},
|
|
22
|
+
"agentName": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Display name for this agent in MoltGuard dashboard"
|
|
25
|
+
},
|
|
26
|
+
"logThoughts": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"default": true,
|
|
29
|
+
"description": "Send reasoning traces to Mind Graph"
|
|
30
|
+
},
|
|
31
|
+
"gateHighRisk": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": true,
|
|
34
|
+
"description": "Require approval for high-risk actions"
|
|
35
|
+
},
|
|
36
|
+
"gateTools": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": { "type": "string" },
|
|
39
|
+
"default": ["exec", "message", "write"],
|
|
40
|
+
"description": "Tool names that require approval when gateHighRisk is enabled"
|
|
41
|
+
},
|
|
42
|
+
"pollCommands": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"default": true,
|
|
45
|
+
"description": "Poll for remote control commands (pause/resume/stop)"
|
|
46
|
+
},
|
|
47
|
+
"pollIntervalMs": {
|
|
48
|
+
"type": "number",
|
|
49
|
+
"default": 5000,
|
|
50
|
+
"description": "How often to poll for commands (ms)"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"required": ["token"]
|
|
54
|
+
},
|
|
55
|
+
"uiHints": {
|
|
56
|
+
"url": { "label": "MoltGuard URL", "placeholder": "https://guard.moltnet.ai" },
|
|
57
|
+
"token": { "label": "API Token", "sensitive": true, "placeholder": "mg_xxxxxxxxxxxx" },
|
|
58
|
+
"agentName": { "label": "Agent Name", "placeholder": "my-assistant" },
|
|
59
|
+
"logThoughts": { "label": "Log Thoughts to Mind Graph" },
|
|
60
|
+
"gateHighRisk": { "label": "Gate High-Risk Actions" },
|
|
61
|
+
"gateTools": { "label": "Tools Requiring Approval" },
|
|
62
|
+
"pollCommands": { "label": "Enable Remote Control" },
|
|
63
|
+
"pollIntervalMs": { "label": "Poll Interval (ms)" }
|
|
64
|
+
}
|
|
65
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoltGuard Plugin for Clawdbot
|
|
3
|
+
*
|
|
4
|
+
* Security & observability for AI agents.
|
|
5
|
+
* - Logs all tool calls to MoltGuard
|
|
6
|
+
* - Sends reasoning traces to Mind Graph
|
|
7
|
+
* - Gates high-risk actions (requires approval)
|
|
8
|
+
* - Polls for remote control commands
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PluginAPI, PluginRegisterFn } from 'clawdbot';
|
|
12
|
+
|
|
13
|
+
interface MoltGuardConfig {
|
|
14
|
+
url: string;
|
|
15
|
+
token: string;
|
|
16
|
+
agentName?: string;
|
|
17
|
+
logThoughts?: boolean;
|
|
18
|
+
gateHighRisk?: boolean;
|
|
19
|
+
gateTools?: string[];
|
|
20
|
+
pollCommands?: boolean;
|
|
21
|
+
pollIntervalMs?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface MoltGuardState {
|
|
25
|
+
agentId: string | null;
|
|
26
|
+
sessionId: string | null;
|
|
27
|
+
paused: boolean;
|
|
28
|
+
pollInterval: NodeJS.Timeout | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const state: MoltGuardState = {
|
|
32
|
+
agentId: null,
|
|
33
|
+
sessionId: null,
|
|
34
|
+
paused: false,
|
|
35
|
+
pollInterval: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Risk classification for common tools
|
|
39
|
+
const TOOL_RISK: Record<string, 'low' | 'medium' | 'high' | 'critical'> = {
|
|
40
|
+
// High risk - external effects
|
|
41
|
+
'exec': 'high',
|
|
42
|
+
'message': 'high',
|
|
43
|
+
'write': 'medium',
|
|
44
|
+
'edit': 'medium',
|
|
45
|
+
'browser': 'medium',
|
|
46
|
+
'web_fetch': 'low',
|
|
47
|
+
'web_search': 'low',
|
|
48
|
+
'read': 'low',
|
|
49
|
+
'memory_search': 'low',
|
|
50
|
+
'memory_get': 'low',
|
|
51
|
+
'tts': 'low',
|
|
52
|
+
'image': 'low',
|
|
53
|
+
// Critical - system changes
|
|
54
|
+
'gateway': 'critical',
|
|
55
|
+
'cron': 'medium',
|
|
56
|
+
'nodes': 'high',
|
|
57
|
+
'canvas': 'low',
|
|
58
|
+
'process': 'medium',
|
|
59
|
+
'sessions_spawn': 'medium',
|
|
60
|
+
'sessions_send': 'medium',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
async function apiCall(
|
|
64
|
+
config: MoltGuardConfig,
|
|
65
|
+
endpoint: string,
|
|
66
|
+
method: 'GET' | 'POST' | 'PATCH' = 'POST',
|
|
67
|
+
body?: Record<string, unknown>
|
|
68
|
+
): Promise<unknown> {
|
|
69
|
+
const url = `${config.url}/api${endpoint}`;
|
|
70
|
+
const headers: Record<string, string> = {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
'X-API-Key': config.token,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const res = await fetch(url, {
|
|
76
|
+
method,
|
|
77
|
+
headers,
|
|
78
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const text = await res.text();
|
|
83
|
+
throw new Error(`MoltGuard API error: ${res.status} ${text}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return res.json();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function registerAgent(config: MoltGuardConfig): Promise<string> {
|
|
90
|
+
const hostname = process.env.HOSTNAME || 'unknown';
|
|
91
|
+
const result = await apiCall(config, '/agents/register', 'POST', {
|
|
92
|
+
name: config.agentName || `clawdbot-${hostname}`,
|
|
93
|
+
description: `Clawdbot agent on ${hostname}`,
|
|
94
|
+
capabilities: ['tool_execution', 'reasoning', 'memory'],
|
|
95
|
+
}) as { id: string };
|
|
96
|
+
return result.id;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function startSession(config: MoltGuardConfig, agentName: string): Promise<string> {
|
|
100
|
+
const result = await apiCall(config, '/sessions', 'POST', {
|
|
101
|
+
agent: agentName,
|
|
102
|
+
metadata: JSON.stringify({ startedAt: new Date().toISOString() }),
|
|
103
|
+
}) as { id: string };
|
|
104
|
+
return result.id;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function logTrace(
|
|
108
|
+
config: MoltGuardConfig,
|
|
109
|
+
agentName: string,
|
|
110
|
+
sessionId: string | null,
|
|
111
|
+
type: string,
|
|
112
|
+
title: string,
|
|
113
|
+
content: string
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
await apiCall(config, '/traces', 'POST', {
|
|
116
|
+
agent: agentName,
|
|
117
|
+
sessionId,
|
|
118
|
+
type,
|
|
119
|
+
title,
|
|
120
|
+
content,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function logAction(
|
|
125
|
+
config: MoltGuardConfig,
|
|
126
|
+
agentName: string,
|
|
127
|
+
sessionId: string | null,
|
|
128
|
+
toolName: string,
|
|
129
|
+
params: Record<string, unknown>,
|
|
130
|
+
risk: string,
|
|
131
|
+
status: 'pending' | 'executed' = 'executed'
|
|
132
|
+
): Promise<{ id: string; status: string }> {
|
|
133
|
+
const result = await apiCall(config, '/actions', 'POST', {
|
|
134
|
+
agent: agentName,
|
|
135
|
+
sessionId,
|
|
136
|
+
type: `tool.${toolName}`,
|
|
137
|
+
description: summarizeToolCall(toolName, params),
|
|
138
|
+
details: JSON.stringify(params),
|
|
139
|
+
risk,
|
|
140
|
+
status,
|
|
141
|
+
reversible: toolName === 'write' || toolName === 'edit' ? 1 : 0,
|
|
142
|
+
}) as { id: string; status: string };
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function pollForDecision(
|
|
147
|
+
config: MoltGuardConfig,
|
|
148
|
+
actionId: string,
|
|
149
|
+
timeoutMs: number = 300000
|
|
150
|
+
): Promise<'approved' | 'rejected' | 'timeout'> {
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
const pollInterval = 2000;
|
|
153
|
+
|
|
154
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
155
|
+
const result = await apiCall(config, `/actions/${actionId}/decision`, 'GET') as { status: string };
|
|
156
|
+
|
|
157
|
+
if (result.status === 'approved') return 'approved';
|
|
158
|
+
if (result.status === 'rejected' || result.status === 'timeout') return result.status as 'rejected' | 'timeout';
|
|
159
|
+
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return 'timeout';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function pollCommands(config: MoltGuardConfig, agentName: string): Promise<void> {
|
|
167
|
+
try {
|
|
168
|
+
const commands = await apiCall(config, `/control/${agentName}/pending`, 'GET') as Array<{
|
|
169
|
+
id: string;
|
|
170
|
+
command: string;
|
|
171
|
+
params?: string;
|
|
172
|
+
}>;
|
|
173
|
+
|
|
174
|
+
for (const cmd of commands) {
|
|
175
|
+
console.log(`[moltguard] Received command: ${cmd.command}`);
|
|
176
|
+
|
|
177
|
+
switch (cmd.command) {
|
|
178
|
+
case 'pause':
|
|
179
|
+
state.paused = true;
|
|
180
|
+
console.log('[moltguard] Agent PAUSED by remote control');
|
|
181
|
+
break;
|
|
182
|
+
case 'resume':
|
|
183
|
+
state.paused = false;
|
|
184
|
+
console.log('[moltguard] Agent RESUMED by remote control');
|
|
185
|
+
break;
|
|
186
|
+
case 'stop':
|
|
187
|
+
console.log('[moltguard] Agent STOPPED by remote control');
|
|
188
|
+
process.exit(0);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ACK the command
|
|
193
|
+
await apiCall(config, `/control/${agentName}/ack/${cmd.id}`, 'POST');
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
// Silent fail for polling
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function summarizeToolCall(toolName: string, params: Record<string, unknown>): string {
|
|
201
|
+
switch (toolName) {
|
|
202
|
+
case 'exec':
|
|
203
|
+
return `Execute: ${String(params.command || '').slice(0, 100)}`;
|
|
204
|
+
case 'write':
|
|
205
|
+
return `Write file: ${params.path || params.file_path}`;
|
|
206
|
+
case 'edit':
|
|
207
|
+
return `Edit file: ${params.path || params.file_path}`;
|
|
208
|
+
case 'read':
|
|
209
|
+
return `Read file: ${params.path || params.file_path}`;
|
|
210
|
+
case 'message':
|
|
211
|
+
return `Send message: ${String(params.message || '').slice(0, 50)}...`;
|
|
212
|
+
case 'browser':
|
|
213
|
+
return `Browser: ${params.action} ${params.targetUrl || ''}`;
|
|
214
|
+
case 'web_fetch':
|
|
215
|
+
return `Fetch URL: ${params.url}`;
|
|
216
|
+
case 'web_search':
|
|
217
|
+
return `Search: ${params.query}`;
|
|
218
|
+
case 'gateway':
|
|
219
|
+
return `Gateway: ${params.action}`;
|
|
220
|
+
case 'cron':
|
|
221
|
+
return `Cron: ${params.action}`;
|
|
222
|
+
default:
|
|
223
|
+
return `${toolName}: ${JSON.stringify(params).slice(0, 80)}`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getToolRisk(toolName: string, params: Record<string, unknown>): 'low' | 'medium' | 'high' | 'critical' {
|
|
228
|
+
const baseRisk = TOOL_RISK[toolName] || 'medium';
|
|
229
|
+
|
|
230
|
+
// Elevate risk for certain patterns
|
|
231
|
+
if (toolName === 'exec') {
|
|
232
|
+
const cmd = String(params.command || '').toLowerCase();
|
|
233
|
+
if (cmd.includes('rm ') || cmd.includes('sudo') || cmd.includes('chmod')) {
|
|
234
|
+
return 'critical';
|
|
235
|
+
}
|
|
236
|
+
if (cmd.includes('curl') || cmd.includes('wget') || cmd.includes('ssh')) {
|
|
237
|
+
return 'high';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (toolName === 'write' || toolName === 'edit') {
|
|
242
|
+
const path = String(params.path || params.file_path || '');
|
|
243
|
+
if (path.includes('.ssh') || path.includes('.env') || path.includes('config')) {
|
|
244
|
+
return 'high';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return baseRisk;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const register: PluginRegisterFn = (api: PluginAPI) => {
|
|
252
|
+
const config = api.config.plugins?.entries?.moltguard?.config as MoltGuardConfig | undefined;
|
|
253
|
+
|
|
254
|
+
if (!config?.token) {
|
|
255
|
+
console.log('[moltguard] No token configured. Get one at https://guard.moltnet.ai');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const cfg: MoltGuardConfig = {
|
|
260
|
+
url: config.url || 'https://guard.moltnet.ai',
|
|
261
|
+
token: config.token,
|
|
262
|
+
agentName: config.agentName,
|
|
263
|
+
logThoughts: config.logThoughts ?? true,
|
|
264
|
+
gateHighRisk: config.gateHighRisk ?? true,
|
|
265
|
+
gateTools: config.gateTools ?? ['exec', 'message', 'write'],
|
|
266
|
+
pollCommands: config.pollCommands ?? true,
|
|
267
|
+
pollIntervalMs: config.pollIntervalMs ?? 5000,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const agentName = cfg.agentName || `clawdbot-${process.env.HOSTNAME || 'agent'}`;
|
|
271
|
+
|
|
272
|
+
// Register agent on startup
|
|
273
|
+
(async () => {
|
|
274
|
+
try {
|
|
275
|
+
state.agentId = await registerAgent(cfg);
|
|
276
|
+
state.sessionId = await startSession(cfg, agentName);
|
|
277
|
+
console.log(`[moltguard] Connected to ${cfg.url}`);
|
|
278
|
+
console.log(`[moltguard] Agent: ${agentName} | Session: ${state.sessionId}`);
|
|
279
|
+
|
|
280
|
+
// Start command polling
|
|
281
|
+
if (cfg.pollCommands) {
|
|
282
|
+
state.pollInterval = setInterval(() => {
|
|
283
|
+
pollCommands(cfg, agentName);
|
|
284
|
+
}, cfg.pollIntervalMs);
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error('[moltguard] Failed to connect:', err instanceof Error ? err.message : String(err));
|
|
288
|
+
}
|
|
289
|
+
})();
|
|
290
|
+
|
|
291
|
+
// Hook: Intercept tool results before persistence
|
|
292
|
+
api.hooks?.register('tool_result_persist', (toolResult) => {
|
|
293
|
+
if (!toolResult || typeof toolResult !== 'object') return toolResult;
|
|
294
|
+
|
|
295
|
+
const { name, input } = toolResult as { name?: string; input?: Record<string, unknown> };
|
|
296
|
+
if (!name) return toolResult;
|
|
297
|
+
|
|
298
|
+
const risk = getToolRisk(name, input || {});
|
|
299
|
+
const shouldGate = cfg.gateHighRisk &&
|
|
300
|
+
(risk === 'high' || risk === 'critical') &&
|
|
301
|
+
cfg.gateTools?.includes(name);
|
|
302
|
+
|
|
303
|
+
// Log the action (async, don't block)
|
|
304
|
+
logAction(cfg, agentName, state.sessionId, name, input || {}, risk, shouldGate ? 'pending' : 'executed')
|
|
305
|
+
.catch(err => console.error('[moltguard] Failed to log action:', err));
|
|
306
|
+
|
|
307
|
+
return toolResult;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Hook: Log agent bootstrap (session start)
|
|
311
|
+
api.hooks?.register('agent:bootstrap', (event) => {
|
|
312
|
+
if (cfg.logThoughts) {
|
|
313
|
+
logTrace(cfg, agentName, state.sessionId, 'session', 'Session Started', 'Agent bootstrap initiated')
|
|
314
|
+
.catch(() => {});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Hook: Log commands
|
|
319
|
+
api.hooks?.register('command', (event) => {
|
|
320
|
+
if (cfg.logThoughts && event.action) {
|
|
321
|
+
logTrace(cfg, agentName, state.sessionId, 'command', `Command: /${event.action}`, `User issued /${event.action}`)
|
|
322
|
+
.catch(() => {});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Cleanup on exit
|
|
327
|
+
process.on('beforeExit', () => {
|
|
328
|
+
if (state.pollInterval) {
|
|
329
|
+
clearInterval(state.pollInterval);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
console.log('[moltguard] Plugin loaded');
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
export default register;
|
|
337
|
+
export const id = 'moltguard';
|
|
338
|
+
export const name = 'MoltGuard';
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moltnet/guard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MoltGuard plugin for Clawdbot - Security & observability for AI agents",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"author": "MoltNet <hello@moltnet.ai>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://guard.moltnet.ai",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/moltnet/guard"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"clawdbot",
|
|
16
|
+
"clawdbot-plugin",
|
|
17
|
+
"ai-agent",
|
|
18
|
+
"security",
|
|
19
|
+
"observability",
|
|
20
|
+
"audit",
|
|
21
|
+
"moltguard"
|
|
22
|
+
],
|
|
23
|
+
"clawdbot": {
|
|
24
|
+
"extensions": ["./index.ts"]
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"clawdbot": ">=0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"index.ts",
|
|
31
|
+
"clawdbot.plugin.json",
|
|
32
|
+
"README.md"
|
|
33
|
+
]
|
|
34
|
+
}
|