openclaw-safeclaw-plugin 0.8.1 → 0.9.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/cli.tsx +6 -1
- package/dist/cli.js +7 -1
- package/dist/index.js +32 -3
- package/index.ts +38 -3
- package/package.json +1 -1
package/cli.tsx
CHANGED
|
@@ -249,7 +249,12 @@ if (command === 'connect') {
|
|
|
249
249
|
} else if (res.status === 401 || res.status === 403) {
|
|
250
250
|
console.log('[ok] Service evaluate: responding (auth required)');
|
|
251
251
|
} else {
|
|
252
|
-
|
|
252
|
+
let detail = '';
|
|
253
|
+
try {
|
|
254
|
+
const body = await res.json() as Record<string, unknown>;
|
|
255
|
+
detail = ` — ${body.detail ?? body.error ?? JSON.stringify(body)}`;
|
|
256
|
+
} catch { /* ignore */ }
|
|
257
|
+
console.log(`[!!] Service evaluate: HTTP ${res.status}${detail}`);
|
|
253
258
|
allOk = false;
|
|
254
259
|
}
|
|
255
260
|
} catch (e) {
|
package/dist/cli.js
CHANGED
|
@@ -242,7 +242,13 @@ else if (command === 'status') {
|
|
|
242
242
|
console.log('[ok] Service evaluate: responding (auth required)');
|
|
243
243
|
}
|
|
244
244
|
else {
|
|
245
|
-
|
|
245
|
+
let detail = '';
|
|
246
|
+
try {
|
|
247
|
+
const body = await res.json();
|
|
248
|
+
detail = ` — ${body.detail ?? body.error ?? JSON.stringify(body)}`;
|
|
249
|
+
}
|
|
250
|
+
catch { /* ignore */ }
|
|
251
|
+
console.log(`[!!] Service evaluate: HTTP ${res.status}${detail}`);
|
|
246
252
|
allOk = false;
|
|
247
253
|
}
|
|
248
254
|
}
|
package/dist/index.js
CHANGED
|
@@ -53,6 +53,24 @@ async function post(path, body) {
|
|
|
53
53
|
return null; // Caller checks failMode
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
let handshakeCompleted = false;
|
|
57
|
+
async function performHandshake() {
|
|
58
|
+
if (!config.apiKey) {
|
|
59
|
+
console.warn('[SafeClaw] No API key configured — skipping handshake');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const r = await post('/handshake', {
|
|
63
|
+
pluginVersion: '0.1.3',
|
|
64
|
+
configHash: configHash(config),
|
|
65
|
+
});
|
|
66
|
+
if (r === null) {
|
|
67
|
+
console.warn('[SafeClaw] ✗ Handshake failed — API key may be invalid or service unreachable');
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
console.log(`[SafeClaw] ✓ Handshake OK — org=${r.orgId}, scope=${r.scope}, engine=${r.engineReady ? 'ready' : 'not ready'}`);
|
|
71
|
+
handshakeCompleted = true;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
56
74
|
async function checkConnection() {
|
|
57
75
|
const label = `[SafeClaw]`;
|
|
58
76
|
console.log(`${label} Connecting to ${config.serviceUrl} ...`);
|
|
@@ -82,7 +100,7 @@ async function checkConnection() {
|
|
|
82
100
|
export default {
|
|
83
101
|
id: 'safeclaw',
|
|
84
102
|
name: 'SafeClaw Neurosymbolic Governance',
|
|
85
|
-
version: '0.1.
|
|
103
|
+
version: '0.1.3',
|
|
86
104
|
register(api) {
|
|
87
105
|
if (!config.enabled) {
|
|
88
106
|
console.log('[SafeClaw] Plugin disabled');
|
|
@@ -106,8 +124,16 @@ export default {
|
|
|
106
124
|
// Heartbeat failure is non-fatal
|
|
107
125
|
}
|
|
108
126
|
};
|
|
109
|
-
// Start heartbeat after connection check
|
|
110
|
-
checkConnection()
|
|
127
|
+
// Start heartbeat after connection check + handshake
|
|
128
|
+
checkConnection()
|
|
129
|
+
.then(() => performHandshake())
|
|
130
|
+
.then((ok) => {
|
|
131
|
+
if (!ok && config.failMode === 'closed') {
|
|
132
|
+
console.warn('[SafeClaw] ⚠ Handshake failed with fail-mode=closed — tool calls will be BLOCKED');
|
|
133
|
+
}
|
|
134
|
+
return sendHeartbeat();
|
|
135
|
+
})
|
|
136
|
+
.catch(() => { });
|
|
111
137
|
const heartbeatInterval = setInterval(sendHeartbeat, 30000);
|
|
112
138
|
// Clean shutdown: send shutdown heartbeat and clear interval
|
|
113
139
|
const shutdown = () => {
|
|
@@ -127,6 +153,9 @@ export default {
|
|
|
127
153
|
process.on('SIGTERM', () => { shutdown(); process.exit(0); });
|
|
128
154
|
// THE GATE — constraint checking on every tool call
|
|
129
155
|
api.on('before_tool_call', async (event, ctx) => {
|
|
156
|
+
if (!handshakeCompleted && config.failMode === 'closed' && config.enforcement === 'enforce') {
|
|
157
|
+
return { block: true, blockReason: 'SafeClaw handshake not completed (fail-closed)' };
|
|
158
|
+
}
|
|
130
159
|
const r = await post('/evaluate/tool-call', {
|
|
131
160
|
sessionId: ctx.sessionId ?? event.sessionId,
|
|
132
161
|
userId: ctx.userId ?? event.userId,
|
package/index.ts
CHANGED
|
@@ -79,6 +79,29 @@ interface PluginApi {
|
|
|
79
79
|
): void;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
let handshakeCompleted = false;
|
|
83
|
+
|
|
84
|
+
async function performHandshake(): Promise<boolean> {
|
|
85
|
+
if (!config.apiKey) {
|
|
86
|
+
console.warn('[SafeClaw] No API key configured — skipping handshake');
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const r = await post('/handshake', {
|
|
91
|
+
pluginVersion: '0.1.3',
|
|
92
|
+
configHash: configHash(config),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (r === null) {
|
|
96
|
+
console.warn('[SafeClaw] ✗ Handshake failed — API key may be invalid or service unreachable');
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(`[SafeClaw] ✓ Handshake OK — org=${r.orgId}, scope=${r.scope}, engine=${r.engineReady ? 'ready' : 'not ready'}`);
|
|
101
|
+
handshakeCompleted = true;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
async function checkConnection(): Promise<void> {
|
|
83
106
|
const label = `[SafeClaw]`;
|
|
84
107
|
console.log(`${label} Connecting to ${config.serviceUrl} ...`);
|
|
@@ -107,7 +130,7 @@ async function checkConnection(): Promise<void> {
|
|
|
107
130
|
export default {
|
|
108
131
|
id: 'safeclaw',
|
|
109
132
|
name: 'SafeClaw Neurosymbolic Governance',
|
|
110
|
-
version: '0.1.
|
|
133
|
+
version: '0.1.3',
|
|
111
134
|
|
|
112
135
|
register(api: PluginApi) {
|
|
113
136
|
if (!config.enabled) {
|
|
@@ -133,8 +156,16 @@ export default {
|
|
|
133
156
|
}
|
|
134
157
|
};
|
|
135
158
|
|
|
136
|
-
// Start heartbeat after connection check
|
|
137
|
-
checkConnection()
|
|
159
|
+
// Start heartbeat after connection check + handshake
|
|
160
|
+
checkConnection()
|
|
161
|
+
.then(() => performHandshake())
|
|
162
|
+
.then((ok) => {
|
|
163
|
+
if (!ok && config.failMode === 'closed') {
|
|
164
|
+
console.warn('[SafeClaw] ⚠ Handshake failed with fail-mode=closed — tool calls will be BLOCKED');
|
|
165
|
+
}
|
|
166
|
+
return sendHeartbeat();
|
|
167
|
+
})
|
|
168
|
+
.catch(() => {});
|
|
138
169
|
const heartbeatInterval = setInterval(sendHeartbeat, 30000);
|
|
139
170
|
|
|
140
171
|
// Clean shutdown: send shutdown heartbeat and clear interval
|
|
@@ -156,6 +187,10 @@ export default {
|
|
|
156
187
|
|
|
157
188
|
// THE GATE — constraint checking on every tool call
|
|
158
189
|
api.on('before_tool_call', async (event: PluginEvent, ctx: PluginContext) => {
|
|
190
|
+
if (!handshakeCompleted && config.failMode === 'closed' && config.enforcement === 'enforce') {
|
|
191
|
+
return { block: true, blockReason: 'SafeClaw handshake not completed (fail-closed)' };
|
|
192
|
+
}
|
|
193
|
+
|
|
159
194
|
const r = await post('/evaluate/tool-call', {
|
|
160
195
|
sessionId: ctx.sessionId ?? event.sessionId,
|
|
161
196
|
userId: ctx.userId ?? event.userId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "SafeClaw Neurosymbolic Governance plugin for OpenClaw — validates AI agent actions against OWL ontologies and SHACL constraints",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|