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.
Files changed (3) hide show
  1. package/dist/index.js +49 -7
  2. package/index.ts +48 -7
  3. 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
- console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
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.debug(`[SafeClaw] Timeout on ${path}`);
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.debug(`[SafeClaw] Service unavailable: ${path}`);
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.0',
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: 'SafeClaw service unavailable (fail-closed)' };
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('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
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
- console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
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.debug(`[SafeClaw] Timeout on ${path}`);
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.debug(`[SafeClaw] Service unavailable: ${path}`);
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.0',
170
+ version: '0.1.2',
136
171
 
137
172
  register(api: PluginApi) {
138
- if (!config.enabled) return;
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: 'SafeClaw service unavailable (fail-closed)' };
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('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
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.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",