openclaw-safeclaw-plugin 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +49 -7
- package/index.ts +48 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -77,28 +77,70 @@ async function post(path, body) {
|
|
|
77
77
|
signal: AbortSignal.timeout(config.timeoutMs),
|
|
78
78
|
});
|
|
79
79
|
if (!res.ok) {
|
|
80
|
-
|
|
80
|
+
// Try to parse structured error body from service
|
|
81
|
+
try {
|
|
82
|
+
const errBody = await res.json();
|
|
83
|
+
const detail = errBody.detail ?? `HTTP ${res.status}`;
|
|
84
|
+
const hint = errBody.hint ? ` (${errBody.hint})` : '';
|
|
85
|
+
console.warn(`[SafeClaw] ${path}: ${detail}${hint}`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
|
|
89
|
+
}
|
|
81
90
|
return null; // Caller checks failMode
|
|
82
91
|
}
|
|
83
92
|
return await res.json();
|
|
84
93
|
}
|
|
85
94
|
catch (e) {
|
|
86
95
|
if (e instanceof DOMException && e.name === 'TimeoutError') {
|
|
87
|
-
console.
|
|
96
|
+
console.warn(`[SafeClaw] Timeout after ${config.timeoutMs}ms on ${path} (${config.serviceUrl})`);
|
|
97
|
+
}
|
|
98
|
+
else if (e instanceof TypeError && (e.message.includes('fetch') || e.message.includes('ECONNREFUSED'))) {
|
|
99
|
+
console.warn(`[SafeClaw] Connection refused: ${config.serviceUrl}${path} — is the service running?`);
|
|
88
100
|
}
|
|
89
101
|
else {
|
|
90
|
-
console.
|
|
102
|
+
console.warn(`[SafeClaw] Service unavailable: ${config.serviceUrl}${path}`);
|
|
91
103
|
}
|
|
92
104
|
return null; // Caller checks failMode
|
|
93
105
|
}
|
|
94
106
|
}
|
|
107
|
+
async function checkConnection() {
|
|
108
|
+
const label = `[SafeClaw]`;
|
|
109
|
+
console.log(`${label} Connecting to ${config.serviceUrl} ...`);
|
|
110
|
+
console.log(`${label} Mode: enforcement=${config.enforcement}, failMode=${config.failMode}`);
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch(`${config.serviceUrl}/health`, {
|
|
113
|
+
signal: AbortSignal.timeout(config.timeoutMs * 2),
|
|
114
|
+
});
|
|
115
|
+
if (res.ok) {
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
console.log(`${label} ✓ Connected — service ${data.status ?? 'ok'}`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.warn(`${label} ✗ Service responded with HTTP ${res.status}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
console.warn(`${label} ✗ Cannot reach service at ${config.serviceUrl}`);
|
|
125
|
+
if (config.failMode === 'closed') {
|
|
126
|
+
console.warn(`${label} fail-mode=closed → tool calls will be BLOCKED until service is reachable`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.warn(`${label} fail-mode=open → tool calls will be ALLOWED despite no connection`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
95
133
|
export default {
|
|
96
134
|
id: 'openclaw-safeclaw-plugin',
|
|
97
135
|
name: 'SafeClaw Neurosymbolic Governance',
|
|
98
|
-
version: '0.1.
|
|
136
|
+
version: '0.1.2',
|
|
99
137
|
register(api) {
|
|
100
|
-
if (!config.enabled)
|
|
138
|
+
if (!config.enabled) {
|
|
139
|
+
console.log('[SafeClaw] Plugin disabled');
|
|
101
140
|
return;
|
|
141
|
+
}
|
|
142
|
+
// Fire-and-forget startup health check
|
|
143
|
+
checkConnection().catch(() => { });
|
|
102
144
|
// THE GATE — constraint checking on every tool call
|
|
103
145
|
api.on('before_tool_call', async (event, ctx) => {
|
|
104
146
|
const r = await post('/evaluate/tool-call', {
|
|
@@ -109,10 +151,10 @@ export default {
|
|
|
109
151
|
sessionHistory: event.sessionHistory ?? [],
|
|
110
152
|
});
|
|
111
153
|
if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
|
|
112
|
-
return { block: true, blockReason:
|
|
154
|
+
return { block: true, blockReason: `SafeClaw service unavailable at ${config.serviceUrl} (fail-closed)` };
|
|
113
155
|
}
|
|
114
156
|
else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
|
|
115
|
-
console.warn(
|
|
157
|
+
console.warn(`[SafeClaw] Service unavailable at ${config.serviceUrl} (fail-closed mode, warn-only)`);
|
|
116
158
|
}
|
|
117
159
|
if (r?.block) {
|
|
118
160
|
if (config.enforcement === 'enforce') {
|
package/index.ts
CHANGED
|
@@ -93,15 +93,25 @@ async function post(path: string, body: Record<string, unknown>): Promise<Record
|
|
|
93
93
|
signal: AbortSignal.timeout(config.timeoutMs),
|
|
94
94
|
});
|
|
95
95
|
if (!res.ok) {
|
|
96
|
-
|
|
96
|
+
// Try to parse structured error body from service
|
|
97
|
+
try {
|
|
98
|
+
const errBody = await res.json() as Record<string, unknown>;
|
|
99
|
+
const detail = errBody.detail ?? `HTTP ${res.status}`;
|
|
100
|
+
const hint = errBody.hint ? ` (${errBody.hint})` : '';
|
|
101
|
+
console.warn(`[SafeClaw] ${path}: ${detail}${hint}`);
|
|
102
|
+
} catch {
|
|
103
|
+
console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
|
|
104
|
+
}
|
|
97
105
|
return null; // Caller checks failMode
|
|
98
106
|
}
|
|
99
107
|
return await res.json() as Record<string, unknown>;
|
|
100
108
|
} catch (e) {
|
|
101
109
|
if (e instanceof DOMException && e.name === 'TimeoutError') {
|
|
102
|
-
console.
|
|
110
|
+
console.warn(`[SafeClaw] Timeout after ${config.timeoutMs}ms on ${path} (${config.serviceUrl})`);
|
|
111
|
+
} else if (e instanceof TypeError && (e.message.includes('fetch') || e.message.includes('ECONNREFUSED'))) {
|
|
112
|
+
console.warn(`[SafeClaw] Connection refused: ${config.serviceUrl}${path} — is the service running?`);
|
|
103
113
|
} else {
|
|
104
|
-
console.
|
|
114
|
+
console.warn(`[SafeClaw] Service unavailable: ${config.serviceUrl}${path}`);
|
|
105
115
|
}
|
|
106
116
|
return null; // Caller checks failMode
|
|
107
117
|
}
|
|
@@ -129,13 +139,44 @@ interface PluginApi {
|
|
|
129
139
|
): void;
|
|
130
140
|
}
|
|
131
141
|
|
|
142
|
+
async function checkConnection(): Promise<void> {
|
|
143
|
+
const label = `[SafeClaw]`;
|
|
144
|
+
console.log(`${label} Connecting to ${config.serviceUrl} ...`);
|
|
145
|
+
console.log(`${label} Mode: enforcement=${config.enforcement}, failMode=${config.failMode}`);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${config.serviceUrl}/health`, {
|
|
149
|
+
signal: AbortSignal.timeout(config.timeoutMs * 2),
|
|
150
|
+
});
|
|
151
|
+
if (res.ok) {
|
|
152
|
+
const data = await res.json() as Record<string, unknown>;
|
|
153
|
+
console.log(`${label} ✓ Connected — service ${data.status ?? 'ok'}`);
|
|
154
|
+
} else {
|
|
155
|
+
console.warn(`${label} ✗ Service responded with HTTP ${res.status}`);
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
console.warn(`${label} ✗ Cannot reach service at ${config.serviceUrl}`);
|
|
159
|
+
if (config.failMode === 'closed') {
|
|
160
|
+
console.warn(`${label} fail-mode=closed → tool calls will be BLOCKED until service is reachable`);
|
|
161
|
+
} else {
|
|
162
|
+
console.warn(`${label} fail-mode=open → tool calls will be ALLOWED despite no connection`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
132
167
|
export default {
|
|
133
168
|
id: 'openclaw-safeclaw-plugin',
|
|
134
169
|
name: 'SafeClaw Neurosymbolic Governance',
|
|
135
|
-
version: '0.1.
|
|
170
|
+
version: '0.1.2',
|
|
136
171
|
|
|
137
172
|
register(api: PluginApi) {
|
|
138
|
-
if (!config.enabled)
|
|
173
|
+
if (!config.enabled) {
|
|
174
|
+
console.log('[SafeClaw] Plugin disabled');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Fire-and-forget startup health check
|
|
179
|
+
checkConnection().catch(() => {});
|
|
139
180
|
|
|
140
181
|
// THE GATE — constraint checking on every tool call
|
|
141
182
|
api.on('before_tool_call', async (event: PluginEvent, ctx: PluginContext) => {
|
|
@@ -148,9 +189,9 @@ export default {
|
|
|
148
189
|
});
|
|
149
190
|
|
|
150
191
|
if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
|
|
151
|
-
return { block: true, blockReason:
|
|
192
|
+
return { block: true, blockReason: `SafeClaw service unavailable at ${config.serviceUrl} (fail-closed)` };
|
|
152
193
|
} else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
|
|
153
|
-
console.warn(
|
|
194
|
+
console.warn(`[SafeClaw] Service unavailable at ${config.serviceUrl} (fail-closed mode, warn-only)`);
|
|
154
195
|
}
|
|
155
196
|
if (r?.block) {
|
|
156
197
|
if (config.enforcement === 'enforce') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-safeclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|