openclaw-safeclaw-plugin 0.8.0 → 0.8.1

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 CHANGED
@@ -56,7 +56,7 @@ Set via environment variables or `~/.safeclaw/config.json`:
56
56
  |----------|---------|-------------|
57
57
  | `SAFECLAW_URL` | `https://api.safeclaw.eu/api/v1` | SafeClaw service URL |
58
58
  | `SAFECLAW_API_KEY` | *(empty)* | API key (set automatically by `safeclaw connect`) |
59
- | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in ms |
59
+ | `SAFECLAW_TIMEOUT_MS` | `5000` | Request timeout in ms |
60
60
  | `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
61
61
  | `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
62
62
  | `SAFECLAW_FAIL_MODE` | `open` | `open` (allow on failure) or `closed` (block on failure) |
package/cli.tsx CHANGED
@@ -7,6 +7,7 @@ import { join, dirname } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
9
  import App from './tui/App.js';
10
+ import { loadConfig } from './tui/config.js';
10
11
 
11
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
13
 
@@ -176,9 +177,15 @@ if (command === 'connect') {
176
177
  console.log('Try: openclaw plugins install openclaw-safeclaw-plugin');
177
178
  }
178
179
  } else if (command === 'status') {
180
+ const cfg = loadConfig();
179
181
  const configPath = join(homedir(), '.safeclaw', 'config.json');
180
182
  let allOk = true;
181
183
 
184
+ // 0. Active config summary
185
+ console.log(`Config: enforcement=${cfg.enforcement}, failMode=${cfg.failMode}, timeout=${cfg.timeoutMs}ms`);
186
+ console.log(`Service: ${cfg.serviceUrl}`);
187
+ console.log('');
188
+
182
189
  // 1. Config file
183
190
  if (existsSync(configPath)) {
184
191
  console.log('[ok] Config file: ' + configPath);
@@ -188,20 +195,9 @@ if (command === 'connect') {
188
195
  }
189
196
 
190
197
  // 2. API key
191
- let apiKey = '';
192
- let serviceUrl = 'https://api.safeclaw.eu/api/v1';
193
- if (existsSync(configPath)) {
194
- try {
195
- const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
196
- const remote = cfg.remote as Record<string, string> | undefined;
197
- apiKey = remote?.apiKey ?? '';
198
- serviceUrl = remote?.serviceUrl ?? serviceUrl;
199
- } catch { /* ignore */ }
200
- }
201
-
202
- if (apiKey && apiKey.startsWith('sc_')) {
198
+ if (cfg.apiKey && cfg.apiKey.startsWith('sc_')) {
203
199
  console.log('[ok] API key: configured (sc_...)');
204
- } else if (apiKey) {
200
+ } else if (cfg.apiKey) {
205
201
  console.log('[!!] API key: invalid (must start with sc_)');
206
202
  allOk = false;
207
203
  } else {
@@ -209,25 +205,64 @@ if (command === 'connect') {
209
205
  allOk = false;
210
206
  }
211
207
 
212
- // 3. SafeClaw service reachable
208
+ // 3. SafeClaw service — health check (uses same timeout as plugin)
209
+ let serviceHealthy = false;
213
210
  try {
214
- const res = await fetch(`${serviceUrl}/health`, {
215
- signal: AbortSignal.timeout(5000),
216
- headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
211
+ const res = await fetch(`${cfg.serviceUrl}/health`, {
212
+ signal: AbortSignal.timeout(cfg.timeoutMs),
213
+ headers: cfg.apiKey ? { 'Authorization': `Bearer ${cfg.apiKey}` } : {},
217
214
  });
218
215
  if (res.ok) {
219
216
  const data = await res.json() as Record<string, unknown>;
220
- console.log(`[ok] SafeClaw service: ${data.status ?? 'ok'} (${serviceUrl})`);
217
+ console.log(`[ok] Service health: ${data.status ?? 'ok'}`);
218
+ serviceHealthy = true;
221
219
  } else {
222
- console.log(`[!!] SafeClaw service: HTTP ${res.status} (${serviceUrl})`);
220
+ console.log(`[!!] Service health: HTTP ${res.status}`);
223
221
  allOk = false;
224
222
  }
225
- } catch {
226
- console.log(`[!!] SafeClaw service: unreachable (${serviceUrl})`);
223
+ } catch (e) {
224
+ const isTimeout = e instanceof DOMException && e.name === 'TimeoutError';
225
+ console.log(`[!!] Service health: ${isTimeout ? `timeout after ${cfg.timeoutMs}ms` : 'unreachable'}`);
227
226
  allOk = false;
228
227
  }
229
228
 
230
- // 4. OpenClaw installed
229
+ // 4. SafeClaw service — evaluate endpoint (the actual gate)
230
+ if (serviceHealthy) {
231
+ try {
232
+ const res = await fetch(`${cfg.serviceUrl}/evaluate/tool-call`, {
233
+ method: 'POST',
234
+ headers: {
235
+ 'Content-Type': 'application/json',
236
+ ...(cfg.apiKey ? { 'Authorization': `Bearer ${cfg.apiKey}` } : {}),
237
+ },
238
+ body: JSON.stringify({
239
+ sessionId: 'status-check',
240
+ userId: 'status-check',
241
+ toolName: 'echo',
242
+ params: { message: 'status-check' },
243
+ }),
244
+ signal: AbortSignal.timeout(cfg.timeoutMs),
245
+ });
246
+ if (res.ok || res.status === 422) {
247
+ // 422 = validation error is fine — means the service is processing requests
248
+ console.log('[ok] Service evaluate: responding');
249
+ } else if (res.status === 401 || res.status === 403) {
250
+ console.log('[ok] Service evaluate: responding (auth required)');
251
+ } else {
252
+ console.log(`[!!] Service evaluate: HTTP ${res.status}`);
253
+ allOk = false;
254
+ }
255
+ } catch (e) {
256
+ const isTimeout = e instanceof DOMException && e.name === 'TimeoutError';
257
+ console.log(`[!!] Service evaluate: ${isTimeout ? `timeout after ${cfg.timeoutMs}ms` : 'unreachable'}`);
258
+ if (cfg.failMode === 'closed') {
259
+ console.log(' ↳ failMode=closed means ALL tool calls will be blocked!');
260
+ }
261
+ allOk = false;
262
+ }
263
+ }
264
+
265
+ // 5. OpenClaw installed
231
266
  try {
232
267
  execSync('which openclaw', { encoding: 'utf-8', stdio: 'pipe' });
233
268
  console.log('[ok] OpenClaw: installed');
@@ -236,7 +271,7 @@ if (command === 'connect') {
236
271
  allOk = false;
237
272
  }
238
273
 
239
- // 5. Plugin extension files exist
274
+ // 6. Plugin extension files exist
240
275
  const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
241
276
  const hasManifest = existsSync(join(extensionDir, 'openclaw.plugin.json'));
242
277
  const hasEntry = existsSync(join(extensionDir, 'index.js'));
@@ -255,7 +290,7 @@ if (command === 'connect') {
255
290
  allOk = false;
256
291
  }
257
292
 
258
- // 6. Plugin enabled in OpenClaw config
293
+ // 7. Plugin enabled in OpenClaw config
259
294
  const ocConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
260
295
  if (existsSync(ocConfigPath)) {
261
296
  const ocConfig = readJson(ocConfigPath);
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { join, dirname } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { fileURLToPath } from 'url';
9
9
  import App from './tui/App.js';
10
+ import { loadConfig } from './tui/config.js';
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
12
  function readJson(path) {
12
13
  try {
@@ -167,8 +168,13 @@ else if (command === 'setup') {
167
168
  }
168
169
  }
169
170
  else if (command === 'status') {
171
+ const cfg = loadConfig();
170
172
  const configPath = join(homedir(), '.safeclaw', 'config.json');
171
173
  let allOk = true;
174
+ // 0. Active config summary
175
+ console.log(`Config: enforcement=${cfg.enforcement}, failMode=${cfg.failMode}, timeout=${cfg.timeoutMs}ms`);
176
+ console.log(`Service: ${cfg.serviceUrl}`);
177
+ console.log('');
172
178
  // 1. Config file
173
179
  if (existsSync(configPath)) {
174
180
  console.log('[ok] Config file: ' + configPath);
@@ -178,21 +184,10 @@ else if (command === 'status') {
178
184
  allOk = false;
179
185
  }
180
186
  // 2. API key
181
- let apiKey = '';
182
- let serviceUrl = 'https://api.safeclaw.eu/api/v1';
183
- if (existsSync(configPath)) {
184
- try {
185
- const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
186
- const remote = cfg.remote;
187
- apiKey = remote?.apiKey ?? '';
188
- serviceUrl = remote?.serviceUrl ?? serviceUrl;
189
- }
190
- catch { /* ignore */ }
191
- }
192
- if (apiKey && apiKey.startsWith('sc_')) {
187
+ if (cfg.apiKey && cfg.apiKey.startsWith('sc_')) {
193
188
  console.log('[ok] API key: configured (sc_...)');
194
189
  }
195
- else if (apiKey) {
190
+ else if (cfg.apiKey) {
196
191
  console.log('[!!] API key: invalid (must start with sc_)');
197
192
  allOk = false;
198
193
  }
@@ -200,26 +195,67 @@ else if (command === 'status') {
200
195
  console.log('[!!] API key: not set. Run: safeclaw connect <api-key>');
201
196
  allOk = false;
202
197
  }
203
- // 3. SafeClaw service reachable
198
+ // 3. SafeClaw service — health check (uses same timeout as plugin)
199
+ let serviceHealthy = false;
204
200
  try {
205
- const res = await fetch(`${serviceUrl}/health`, {
206
- signal: AbortSignal.timeout(5000),
207
- headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
201
+ const res = await fetch(`${cfg.serviceUrl}/health`, {
202
+ signal: AbortSignal.timeout(cfg.timeoutMs),
203
+ headers: cfg.apiKey ? { 'Authorization': `Bearer ${cfg.apiKey}` } : {},
208
204
  });
209
205
  if (res.ok) {
210
206
  const data = await res.json();
211
- console.log(`[ok] SafeClaw service: ${data.status ?? 'ok'} (${serviceUrl})`);
207
+ console.log(`[ok] Service health: ${data.status ?? 'ok'}`);
208
+ serviceHealthy = true;
212
209
  }
213
210
  else {
214
- console.log(`[!!] SafeClaw service: HTTP ${res.status} (${serviceUrl})`);
211
+ console.log(`[!!] Service health: HTTP ${res.status}`);
215
212
  allOk = false;
216
213
  }
217
214
  }
218
- catch {
219
- console.log(`[!!] SafeClaw service: unreachable (${serviceUrl})`);
215
+ catch (e) {
216
+ const isTimeout = e instanceof DOMException && e.name === 'TimeoutError';
217
+ console.log(`[!!] Service health: ${isTimeout ? `timeout after ${cfg.timeoutMs}ms` : 'unreachable'}`);
220
218
  allOk = false;
221
219
  }
222
- // 4. OpenClaw installed
220
+ // 4. SafeClaw service — evaluate endpoint (the actual gate)
221
+ if (serviceHealthy) {
222
+ try {
223
+ const res = await fetch(`${cfg.serviceUrl}/evaluate/tool-call`, {
224
+ method: 'POST',
225
+ headers: {
226
+ 'Content-Type': 'application/json',
227
+ ...(cfg.apiKey ? { 'Authorization': `Bearer ${cfg.apiKey}` } : {}),
228
+ },
229
+ body: JSON.stringify({
230
+ sessionId: 'status-check',
231
+ userId: 'status-check',
232
+ toolName: 'echo',
233
+ params: { message: 'status-check' },
234
+ }),
235
+ signal: AbortSignal.timeout(cfg.timeoutMs),
236
+ });
237
+ if (res.ok || res.status === 422) {
238
+ // 422 = validation error is fine — means the service is processing requests
239
+ console.log('[ok] Service evaluate: responding');
240
+ }
241
+ else if (res.status === 401 || res.status === 403) {
242
+ console.log('[ok] Service evaluate: responding (auth required)');
243
+ }
244
+ else {
245
+ console.log(`[!!] Service evaluate: HTTP ${res.status}`);
246
+ allOk = false;
247
+ }
248
+ }
249
+ catch (e) {
250
+ const isTimeout = e instanceof DOMException && e.name === 'TimeoutError';
251
+ console.log(`[!!] Service evaluate: ${isTimeout ? `timeout after ${cfg.timeoutMs}ms` : 'unreachable'}`);
252
+ if (cfg.failMode === 'closed') {
253
+ console.log(' ↳ failMode=closed means ALL tool calls will be blocked!');
254
+ }
255
+ allOk = false;
256
+ }
257
+ }
258
+ // 5. OpenClaw installed
223
259
  try {
224
260
  execSync('which openclaw', { encoding: 'utf-8', stdio: 'pipe' });
225
261
  console.log('[ok] OpenClaw: installed');
@@ -228,7 +264,7 @@ else if (command === 'status') {
228
264
  console.log('[!!] OpenClaw: not found in PATH');
229
265
  allOk = false;
230
266
  }
231
- // 5. Plugin extension files exist
267
+ // 6. Plugin extension files exist
232
268
  const extensionDir = join(homedir(), '.openclaw', 'extensions', 'safeclaw');
233
269
  const hasManifest = existsSync(join(extensionDir, 'openclaw.plugin.json'));
234
270
  const hasEntry = existsSync(join(extensionDir, 'index.js'));
@@ -249,7 +285,7 @@ else if (command === 'status') {
249
285
  console.log('[!!] Plugin: not installed. Run: safeclaw setup');
250
286
  allOk = false;
251
287
  }
252
- // 6. Plugin enabled in OpenClaw config
288
+ // 7. Plugin enabled in OpenClaw config
253
289
  const ocConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
254
290
  if (existsSync(ocConfigPath)) {
255
291
  const ocConfig = readJson(ocConfigPath);
@@ -16,10 +16,10 @@ export function loadConfig() {
16
16
  const defaults = {
17
17
  serviceUrl: 'https://api.safeclaw.eu/api/v1',
18
18
  apiKey: '',
19
- timeoutMs: 500,
19
+ timeoutMs: 5000,
20
20
  enabled: true,
21
21
  enforcement: 'enforce',
22
- failMode: 'closed',
22
+ failMode: 'open',
23
23
  agentId: '',
24
24
  agentToken: '',
25
25
  };
@@ -73,8 +73,8 @@ export function loadConfig() {
73
73
  }
74
74
  const validFailModes = ['open', 'closed'];
75
75
  if (!validFailModes.includes(defaults.failMode)) {
76
- console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "closed"`);
77
- defaults.failMode = 'closed';
76
+ console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "open"`);
77
+ defaults.failMode = 'open';
78
78
  }
79
79
  return defaults;
80
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-safeclaw-plugin",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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",
package/tui/config.ts CHANGED
@@ -34,10 +34,10 @@ export function loadConfig(): SafeClawConfig {
34
34
  const defaults: SafeClawConfig = {
35
35
  serviceUrl: 'https://api.safeclaw.eu/api/v1',
36
36
  apiKey: '',
37
- timeoutMs: 500,
37
+ timeoutMs: 5000,
38
38
  enabled: true,
39
39
  enforcement: 'enforce',
40
- failMode: 'closed',
40
+ failMode: 'open',
41
41
  agentId: '',
42
42
  agentToken: '',
43
43
  };
@@ -79,8 +79,8 @@ export function loadConfig(): SafeClawConfig {
79
79
 
80
80
  const validFailModes = ['open', 'closed'] as const;
81
81
  if (!validFailModes.includes(defaults.failMode as any)) {
82
- console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "closed"`);
83
- defaults.failMode = 'closed';
82
+ console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "open"`);
83
+ defaults.failMode = 'open';
84
84
  }
85
85
 
86
86
  return defaults;