openclaw-safeclaw-plugin 0.1.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 ADDED
@@ -0,0 +1,77 @@
1
+ # openclaw-safeclaw-plugin
2
+
3
+ Neurosymbolic governance plugin for OpenClaw AI agents. Validates every tool call, message, and action against OWL ontologies and SHACL constraints before execution.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install openclaw-safeclaw-plugin
9
+ ```
10
+
11
+ ## Quick Start (SaaS)
12
+
13
+ Point the plugin at the hosted SafeClaw service:
14
+
15
+ ```bash
16
+ export SAFECLAW_URL="https://api.safeclaw.eu/api/v1"
17
+ export SAFECLAW_ENFORCEMENT="enforce"
18
+ ```
19
+
20
+ No server setup needed — the plugin connects to SafeClaw's hosted service.
21
+
22
+ ## Quick Start (Self-Hosted)
23
+
24
+ ```bash
25
+ # Start the SafeClaw service
26
+ git clone https://github.com/tendlyeu/SafeClaw.git
27
+ cd SafeClaw/safeclaw-service
28
+ python -m venv .venv && source .venv/bin/activate
29
+ pip install -e ".[dev]"
30
+ safeclaw init --user-id yourname
31
+ safeclaw serve
32
+ # Engine ready on http://localhost:8420
33
+ ```
34
+
35
+ The plugin auto-connects to `http://localhost:8420/api/v1` by default.
36
+
37
+ ## What It Does
38
+
39
+ - **Blocks dangerous actions** — force push, deleting root, exposing secrets
40
+ - **Enforces dependencies** — tests must pass before git push
41
+ - **Checks user preferences** — confirmation for irreversible actions
42
+ - **Governs messages** — blocks sensitive data leaks
43
+ - **Full audit trail** — every decision logged with ontological justification
44
+
45
+ ## How It Works
46
+
47
+ The plugin registers hooks on OpenClaw events:
48
+
49
+ 1. **before_tool_call** — validates against SHACL shapes, policies, preferences, dependencies
50
+ 2. **before_agent_start** — injects governance context into the agent's system prompt
51
+ 3. **message_sending** — checks outbound messages for sensitive data
52
+ 4. **after_tool_call** — records action outcomes for dependency tracking
53
+ 5. **llm_input/output** — logs LLM interactions for audit
54
+
55
+ ## Configuration
56
+
57
+ Set via environment variables or `~/.safeclaw/config.json`:
58
+
59
+ | Variable | Default | Description |
60
+ |----------|---------|-------------|
61
+ | `SAFECLAW_URL` | `http://localhost:8420/api/v1` | SafeClaw service URL |
62
+ | `SAFECLAW_API_KEY` | *(empty)* | API key for cloud mode |
63
+ | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in ms |
64
+ | `SAFECLAW_ENABLED` | `true` | Set `false` to disable |
65
+ | `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
66
+ | `SAFECLAW_FAIL_MODE` | `closed` | `open` (allow on failure) or `closed` (block on failure) |
67
+
68
+ ## Enforcement Modes
69
+
70
+ - **`enforce`** — block actions that violate constraints (recommended)
71
+ - **`warn-only`** — log warnings but allow all actions
72
+ - **`audit-only`** — server-side logging only, no client-side action
73
+ - **`disabled`** — plugin is completely inactive
74
+
75
+ ## License
76
+
77
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,59 @@
1
+ # SafeClaw — Neurosymbolic Governance for OpenClaw
2
+
3
+ SafeClaw adds ontology-based constraint checking to your OpenClaw agent. Every tool call, message, and action is validated against OWL ontologies and SHACL shapes before execution.
4
+
5
+ ## What it does
6
+
7
+ - **Blocks dangerous actions** — force push, deleting root, exposing secrets
8
+ - **Enforces dependencies** — tests must pass before git push
9
+ - **Checks user preferences** — confirmation for irreversible actions based on autonomy level
10
+ - **Governs messages** — blocks sensitive data leaks, enforces never-contact lists
11
+ - **Full audit trail** — every decision logged with ontological justification
12
+
13
+ ## Setup
14
+
15
+ ### Option A: Local mode (self-hosted)
16
+
17
+ ```bash
18
+ # 1. Start the SafeClaw service
19
+ pip install safeclaw
20
+ safeclaw init
21
+ uvicorn safeclaw.main:app --port 8420
22
+
23
+ # 2. Install this plugin (done if you're reading this via clawhub)
24
+ # The plugin auto-connects to http://localhost:8420
25
+ ```
26
+
27
+ ### Option B: Cloud mode (safeclaw.eu)
28
+
29
+ ```bash
30
+ # 1. Sign up at safeclaw.eu and get your API key
31
+
32
+ # 2. Set your API key
33
+ export SAFECLAW_URL="https://api.safeclaw.eu/api/v1"
34
+ export SAFECLAW_API_KEY="sc_live_your_key_here"
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ Set via environment variables or `~/.safeclaw/config.json`:
40
+
41
+ | Variable | Default | Description |
42
+ |----------|---------|-------------|
43
+ | `SAFECLAW_URL` | `http://localhost:8420/api/v1` | SafeClaw service URL |
44
+ | `SAFECLAW_API_KEY` | (empty) | API key for remote/cloud mode |
45
+ | `SAFECLAW_TIMEOUT_MS` | `500` | Request timeout in milliseconds |
46
+ | `SAFECLAW_ENABLED` | `true` | Set to `false` to disable |
47
+ | `SAFECLAW_ENFORCEMENT` | `enforce` | `enforce`, `warn-only`, `audit-only`, or `disabled` |
48
+
49
+ ## How it works
50
+
51
+ This plugin registers hooks on every OpenClaw event:
52
+
53
+ 1. **before_tool_call** — validates against SHACL shapes, policies, preferences, dependencies
54
+ 2. **before_agent_start** — injects governance context into the agent's system prompt
55
+ 3. **message_sending** — checks outbound messages for sensitive data and contact rules
56
+ 4. **after_tool_call** — records action outcomes for dependency tracking
57
+ 5. **llm_input/output** — logs LLM interactions for audit
58
+
59
+ If the SafeClaw service is unavailable, the plugin degrades gracefully — no blocks, no crashes.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SafeClaw — Neurosymbolic Governance Plugin for OpenClaw
3
+ *
4
+ * This TypeScript file is the ENTIRE client-side codebase.
5
+ * All governance logic lives in the SafeClaw Python service.
6
+ * This plugin is a thin HTTP bridge that forwards OpenClaw events
7
+ * to the SafeClaw service and acts on the responses.
8
+ */
9
+ interface PluginEvent {
10
+ sessionId?: string;
11
+ userId?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ interface PluginContext {
15
+ sessionId?: string;
16
+ userId?: string;
17
+ [key: string]: unknown;
18
+ }
19
+ interface PluginApi {
20
+ on(event: string, handler: (event: PluginEvent, ctx: PluginContext) => Promise<Record<string, unknown> | void> | void, options?: {
21
+ priority?: number;
22
+ }): void;
23
+ }
24
+ declare const _default: {
25
+ id: string;
26
+ name: string;
27
+ version: string;
28
+ register(api: PluginApi): void;
29
+ };
30
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * SafeClaw — Neurosymbolic Governance Plugin for OpenClaw
3
+ *
4
+ * This TypeScript file is the ENTIRE client-side codebase.
5
+ * All governance logic lives in the SafeClaw Python service.
6
+ * This plugin is a thin HTTP bridge that forwards OpenClaw events
7
+ * to the SafeClaw service and acts on the responses.
8
+ */
9
+ import { readFileSync, existsSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+ function loadConfig() {
13
+ const defaults = {
14
+ serviceUrl: process.env.SAFECLAW_URL ?? 'http://localhost:8420/api/v1',
15
+ apiKey: process.env.SAFECLAW_API_KEY ?? '',
16
+ timeoutMs: parseInt(process.env.SAFECLAW_TIMEOUT_MS ?? '500', 10),
17
+ enabled: process.env.SAFECLAW_ENABLED !== 'false',
18
+ enforcement: process.env.SAFECLAW_ENFORCEMENT ?? 'enforce',
19
+ failMode: process.env.SAFECLAW_FAIL_MODE ?? 'closed',
20
+ agentId: process.env.SAFECLAW_AGENT_ID ?? '',
21
+ agentToken: process.env.SAFECLAW_AGENT_TOKEN ?? '',
22
+ };
23
+ // Try loading from config file
24
+ const configPath = join(homedir(), '.safeclaw', 'config.json');
25
+ if (existsSync(configPath)) {
26
+ try {
27
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
28
+ if (raw.enabled === false)
29
+ defaults.enabled = false;
30
+ if (raw.remote?.serviceUrl)
31
+ defaults.serviceUrl = raw.remote.serviceUrl;
32
+ if (raw.remote?.apiKey)
33
+ defaults.apiKey = raw.remote.apiKey;
34
+ if (raw.remote?.timeoutMs)
35
+ defaults.timeoutMs = raw.remote.timeoutMs;
36
+ if (raw.enforcement?.mode)
37
+ defaults.enforcement = raw.enforcement.mode;
38
+ if (raw.enforcement?.failMode)
39
+ defaults.failMode = raw.enforcement.failMode;
40
+ if (raw.agentId)
41
+ defaults.agentId = raw.agentId;
42
+ if (raw.agentToken)
43
+ defaults.agentToken = raw.agentToken;
44
+ }
45
+ catch {
46
+ // Config file unreadable — use defaults
47
+ }
48
+ }
49
+ defaults.serviceUrl = defaults.serviceUrl.replace(/\/+$/, '');
50
+ const validModes = ['enforce', 'warn-only', 'audit-only', 'disabled'];
51
+ if (!validModes.includes(defaults.enforcement)) {
52
+ console.warn(`[SafeClaw] Invalid enforcement mode "${defaults.enforcement}", defaulting to "enforce"`);
53
+ defaults.enforcement = 'enforce';
54
+ }
55
+ const validFailModes = ['open', 'closed'];
56
+ if (!validFailModes.includes(defaults.failMode)) {
57
+ console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "closed"`);
58
+ defaults.failMode = 'closed';
59
+ }
60
+ return defaults;
61
+ }
62
+ const config = loadConfig();
63
+ // --- HTTP Client ---
64
+ async function post(path, body) {
65
+ if (!config.enabled)
66
+ return null;
67
+ const headers = { 'Content-Type': 'application/json' };
68
+ if (config.apiKey) {
69
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
70
+ }
71
+ const agentFields = config.agentId ? { agentId: config.agentId, agentToken: config.agentToken } : {};
72
+ try {
73
+ const res = await fetch(`${config.serviceUrl}${path}`, {
74
+ method: 'POST',
75
+ headers,
76
+ body: JSON.stringify({ ...body, ...agentFields }),
77
+ signal: AbortSignal.timeout(config.timeoutMs),
78
+ });
79
+ if (!res.ok) {
80
+ console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
81
+ return null; // Caller checks failMode
82
+ }
83
+ return await res.json();
84
+ }
85
+ catch (e) {
86
+ if (e instanceof DOMException && e.name === 'TimeoutError') {
87
+ console.debug(`[SafeClaw] Timeout on ${path}`);
88
+ }
89
+ else {
90
+ console.debug(`[SafeClaw] Service unavailable: ${path}`);
91
+ }
92
+ return null; // Caller checks failMode
93
+ }
94
+ }
95
+ export default {
96
+ id: 'openclaw-safeclaw-plugin',
97
+ name: 'SafeClaw Neurosymbolic Governance',
98
+ version: '0.1.0',
99
+ register(api) {
100
+ if (!config.enabled)
101
+ return;
102
+ // THE GATE — constraint checking on every tool call
103
+ api.on('before_tool_call', async (event, ctx) => {
104
+ const r = await post('/evaluate/tool-call', {
105
+ sessionId: ctx.sessionId ?? event.sessionId,
106
+ userId: ctx.userId ?? event.userId,
107
+ toolName: event.toolName ?? event.tool_name,
108
+ params: event.params ?? {},
109
+ sessionHistory: event.sessionHistory ?? [],
110
+ });
111
+ if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
112
+ return { block: true, blockReason: 'SafeClaw service unavailable (fail-closed)' };
113
+ }
114
+ else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
115
+ console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
116
+ }
117
+ if (r?.block) {
118
+ if (config.enforcement === 'enforce') {
119
+ return { block: true, blockReason: r.reason };
120
+ }
121
+ if (config.enforcement === 'warn-only') {
122
+ console.warn(`[SafeClaw] Warning: ${r.reason}`);
123
+ }
124
+ // audit-only: logged server-side, no action here
125
+ }
126
+ }, { priority: 100 });
127
+ // Context injection — prepend governance context to agent system prompt
128
+ api.on('before_agent_start', async (event, ctx) => {
129
+ const r = await post('/context/build', {
130
+ sessionId: ctx.sessionId ?? event.sessionId,
131
+ userId: ctx.userId ?? event.userId,
132
+ });
133
+ if (r?.prependContext) {
134
+ return { prependContext: r.prependContext };
135
+ }
136
+ }, { priority: 100 });
137
+ // Message governance — check outbound messages
138
+ api.on('message_sending', async (event, ctx) => {
139
+ const r = await post('/evaluate/message', {
140
+ sessionId: ctx.sessionId ?? event.sessionId,
141
+ userId: ctx.userId ?? event.userId,
142
+ to: event.to,
143
+ content: event.content,
144
+ });
145
+ if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
146
+ return { cancel: true };
147
+ }
148
+ else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
149
+ console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
150
+ }
151
+ if (r?.block && config.enforcement === 'enforce') {
152
+ return { cancel: true };
153
+ }
154
+ }, { priority: 100 });
155
+ // Async logging — fire-and-forget, no return value needed
156
+ api.on('llm_input', (event, ctx) => {
157
+ post('/log/llm-input', {
158
+ sessionId: ctx.sessionId ?? event.sessionId,
159
+ content: event.content,
160
+ }).catch(() => { });
161
+ });
162
+ api.on('llm_output', (event, ctx) => {
163
+ post('/log/llm-output', {
164
+ sessionId: ctx.sessionId ?? event.sessionId,
165
+ content: event.content,
166
+ }).catch(() => { });
167
+ });
168
+ api.on('after_tool_call', (event, ctx) => {
169
+ post('/record/tool-result', {
170
+ sessionId: ctx.sessionId ?? event.sessionId,
171
+ toolName: event.toolName ?? event.tool_name,
172
+ params: event.params ?? {},
173
+ result: event.result ?? '',
174
+ success: event.success ?? true,
175
+ }).catch(() => { });
176
+ });
177
+ },
178
+ };
package/index.ts ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * SafeClaw — Neurosymbolic Governance Plugin for OpenClaw
3
+ *
4
+ * This TypeScript file is the ENTIRE client-side codebase.
5
+ * All governance logic lives in the SafeClaw Python service.
6
+ * This plugin is a thin HTTP bridge that forwards OpenClaw events
7
+ * to the SafeClaw service and acts on the responses.
8
+ */
9
+
10
+ import { readFileSync, existsSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+
14
+ // --- Configuration ---
15
+
16
+ interface SafeClawPluginConfig {
17
+ serviceUrl: string;
18
+ apiKey: string;
19
+ timeoutMs: number;
20
+ enabled: boolean;
21
+ enforcement: 'enforce' | 'warn-only' | 'audit-only' | 'disabled';
22
+ failMode: 'open' | 'closed';
23
+ agentId: string;
24
+ agentToken: string;
25
+ }
26
+
27
+ function loadConfig(): SafeClawPluginConfig {
28
+ const defaults: SafeClawPluginConfig = {
29
+ serviceUrl: process.env.SAFECLAW_URL ?? 'http://localhost:8420/api/v1',
30
+ apiKey: process.env.SAFECLAW_API_KEY ?? '',
31
+ timeoutMs: parseInt(process.env.SAFECLAW_TIMEOUT_MS ?? '500', 10),
32
+ enabled: process.env.SAFECLAW_ENABLED !== 'false',
33
+ enforcement: (process.env.SAFECLAW_ENFORCEMENT as SafeClawPluginConfig['enforcement']) ?? 'enforce',
34
+ failMode: (process.env.SAFECLAW_FAIL_MODE as SafeClawPluginConfig['failMode']) ?? 'closed',
35
+ agentId: process.env.SAFECLAW_AGENT_ID ?? '',
36
+ agentToken: process.env.SAFECLAW_AGENT_TOKEN ?? '',
37
+ };
38
+
39
+ // Try loading from config file
40
+ const configPath = join(homedir(), '.safeclaw', 'config.json');
41
+ if (existsSync(configPath)) {
42
+ try {
43
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
44
+ if (raw.enabled === false) defaults.enabled = false;
45
+ if (raw.remote?.serviceUrl) defaults.serviceUrl = raw.remote.serviceUrl;
46
+ if (raw.remote?.apiKey) defaults.apiKey = raw.remote.apiKey;
47
+ if (raw.remote?.timeoutMs) defaults.timeoutMs = raw.remote.timeoutMs;
48
+ if (raw.enforcement?.mode) defaults.enforcement = raw.enforcement.mode;
49
+ if (raw.enforcement?.failMode) defaults.failMode = raw.enforcement.failMode;
50
+ if (raw.agentId) defaults.agentId = raw.agentId;
51
+ if (raw.agentToken) defaults.agentToken = raw.agentToken;
52
+ } catch {
53
+ // Config file unreadable — use defaults
54
+ }
55
+ }
56
+
57
+ defaults.serviceUrl = defaults.serviceUrl.replace(/\/+$/, '');
58
+
59
+ const validModes = ['enforce', 'warn-only', 'audit-only', 'disabled'] as const;
60
+ if (!validModes.includes(defaults.enforcement as any)) {
61
+ console.warn(`[SafeClaw] Invalid enforcement mode "${defaults.enforcement}", defaulting to "enforce"`);
62
+ defaults.enforcement = 'enforce';
63
+ }
64
+
65
+ const validFailModes = ['open', 'closed'] as const;
66
+ if (!validFailModes.includes(defaults.failMode as any)) {
67
+ console.warn(`[SafeClaw] Invalid fail mode "${defaults.failMode}", defaulting to "closed"`);
68
+ defaults.failMode = 'closed';
69
+ }
70
+
71
+ return defaults;
72
+ }
73
+
74
+ const config = loadConfig();
75
+
76
+ // --- HTTP Client ---
77
+
78
+ async function post(path: string, body: Record<string, unknown>): Promise<Record<string, unknown> | null> {
79
+ if (!config.enabled) return null;
80
+
81
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
82
+ if (config.apiKey) {
83
+ headers['Authorization'] = `Bearer ${config.apiKey}`;
84
+ }
85
+
86
+ const agentFields = config.agentId ? { agentId: config.agentId, agentToken: config.agentToken } : {};
87
+
88
+ try {
89
+ const res = await fetch(`${config.serviceUrl}${path}`, {
90
+ method: 'POST',
91
+ headers,
92
+ body: JSON.stringify({ ...body, ...agentFields }),
93
+ signal: AbortSignal.timeout(config.timeoutMs),
94
+ });
95
+ if (!res.ok) {
96
+ console.warn(`[SafeClaw] HTTP ${res.status} from ${path}`);
97
+ return null; // Caller checks failMode
98
+ }
99
+ return await res.json() as Record<string, unknown>;
100
+ } catch (e) {
101
+ if (e instanceof DOMException && e.name === 'TimeoutError') {
102
+ console.debug(`[SafeClaw] Timeout on ${path}`);
103
+ } else {
104
+ console.debug(`[SafeClaw] Service unavailable: ${path}`);
105
+ }
106
+ return null; // Caller checks failMode
107
+ }
108
+ }
109
+
110
+ // --- Plugin Definition ---
111
+
112
+ interface PluginEvent {
113
+ sessionId?: string;
114
+ userId?: string;
115
+ [key: string]: unknown;
116
+ }
117
+
118
+ interface PluginContext {
119
+ sessionId?: string;
120
+ userId?: string;
121
+ [key: string]: unknown;
122
+ }
123
+
124
+ interface PluginApi {
125
+ on(
126
+ event: string,
127
+ handler: (event: PluginEvent, ctx: PluginContext) => Promise<Record<string, unknown> | void> | void,
128
+ options?: { priority?: number },
129
+ ): void;
130
+ }
131
+
132
+ export default {
133
+ id: 'openclaw-safeclaw-plugin',
134
+ name: 'SafeClaw Neurosymbolic Governance',
135
+ version: '0.1.0',
136
+
137
+ register(api: PluginApi) {
138
+ if (!config.enabled) return;
139
+
140
+ // THE GATE — constraint checking on every tool call
141
+ api.on('before_tool_call', async (event: PluginEvent, ctx: PluginContext) => {
142
+ const r = await post('/evaluate/tool-call', {
143
+ sessionId: ctx.sessionId ?? event.sessionId,
144
+ userId: ctx.userId ?? event.userId,
145
+ toolName: event.toolName ?? event.tool_name,
146
+ params: event.params ?? {},
147
+ sessionHistory: event.sessionHistory ?? [],
148
+ });
149
+
150
+ if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
151
+ return { block: true, blockReason: 'SafeClaw service unavailable (fail-closed)' };
152
+ } else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
153
+ console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
154
+ }
155
+ if (r?.block) {
156
+ if (config.enforcement === 'enforce') {
157
+ return { block: true, blockReason: r.reason as string };
158
+ }
159
+ if (config.enforcement === 'warn-only') {
160
+ console.warn(`[SafeClaw] Warning: ${r.reason}`);
161
+ }
162
+ // audit-only: logged server-side, no action here
163
+ }
164
+ }, { priority: 100 });
165
+
166
+ // Context injection — prepend governance context to agent system prompt
167
+ api.on('before_agent_start', async (event: PluginEvent, ctx: PluginContext) => {
168
+ const r = await post('/context/build', {
169
+ sessionId: ctx.sessionId ?? event.sessionId,
170
+ userId: ctx.userId ?? event.userId,
171
+ });
172
+
173
+ if (r?.prependContext) {
174
+ return { prependContext: r.prependContext as string };
175
+ }
176
+ }, { priority: 100 });
177
+
178
+ // Message governance — check outbound messages
179
+ api.on('message_sending', async (event: PluginEvent, ctx: PluginContext) => {
180
+ const r = await post('/evaluate/message', {
181
+ sessionId: ctx.sessionId ?? event.sessionId,
182
+ userId: ctx.userId ?? event.userId,
183
+ to: event.to,
184
+ content: event.content,
185
+ });
186
+
187
+ if (r === null && config.failMode === 'closed' && config.enforcement === 'enforce') {
188
+ return { cancel: true };
189
+ } else if (r === null && config.failMode === 'closed' && config.enforcement === 'warn-only') {
190
+ console.warn('[SafeClaw] Service unavailable (fail-closed mode, warn-only)');
191
+ }
192
+ if (r?.block && config.enforcement === 'enforce') {
193
+ return { cancel: true };
194
+ }
195
+ }, { priority: 100 });
196
+
197
+ // Async logging — fire-and-forget, no return value needed
198
+ api.on('llm_input', (event: PluginEvent, ctx: PluginContext) => {
199
+ post('/log/llm-input', {
200
+ sessionId: ctx.sessionId ?? event.sessionId,
201
+ content: event.content,
202
+ }).catch(() => {});
203
+ });
204
+
205
+ api.on('llm_output', (event: PluginEvent, ctx: PluginContext) => {
206
+ post('/log/llm-output', {
207
+ sessionId: ctx.sessionId ?? event.sessionId,
208
+ content: event.content,
209
+ }).catch(() => {});
210
+ });
211
+
212
+ api.on('after_tool_call', (event: PluginEvent, ctx: PluginContext) => {
213
+ post('/record/tool-result', {
214
+ sessionId: ctx.sessionId ?? event.sessionId,
215
+ toolName: event.toolName ?? event.tool_name,
216
+ params: event.params ?? {},
217
+ result: event.result ?? '',
218
+ success: event.success ?? true,
219
+ }).catch(() => {});
220
+ });
221
+ },
222
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "openclaw-safeclaw-plugin",
3
+ "version": "0.1.0",
4
+ "description": "SafeClaw Neurosymbolic Governance plugin for OpenClaw — validates AI agent actions against OWL ontologies and SHACL constraints",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "typecheck": "tsc --noEmit",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "files": [
14
+ "dist/",
15
+ "index.ts",
16
+ "SKILL.md",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "openclaw",
21
+ "safeclaw",
22
+ "governance",
23
+ "neurosymbolic",
24
+ "ai-safety",
25
+ "owl",
26
+ "shacl",
27
+ "ontology",
28
+ "ai-agent",
29
+ "tool-validation"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/tendlyeu/SafeClaw.git",
35
+ "directory": "openclaw-safeclaw-plugin"
36
+ },
37
+ "homepage": "https://safeclaw.eu",
38
+ "bugs": {
39
+ "url": "https://github.com/tendlyeu/SafeClaw/issues"
40
+ },
41
+ "author": "Tendly EU",
42
+ "devDependencies": {
43
+ "typescript": "^5.4.0",
44
+ "@types/node": "^20.0.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ }
49
+ }