@randromeda/arbiter-openclaw 0.1.0 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to `@randromeda/arbiter-openclaw` will be documented in this file.
4
4
 
5
+ ## [0.1.2] - 2026-04-11
6
+
7
+ - Documented the safe canary-path smoke test for OpenClaw using `/tmp/arbiter-deny-test`.
8
+
9
+ ## [0.1.1] - 2026-04-03
10
+
11
+ - Added local runtime auto-discovery from `~/.arbiter/config.json`.
12
+ - Added optional `localConfigPath` override in plugin config.
13
+ - Removed environment-variable fallback wiring from plugin runtime setup.
14
+
5
15
  ## [0.1.0] - 2026-04-02
6
16
 
7
17
  - Initial native OpenClaw plugin implementation.
package/README.md CHANGED
@@ -29,7 +29,9 @@ openclaw plugins install ./integrations/openclaw-plugin
29
29
 
30
30
  ## Config
31
31
 
32
- Add plugin config under `plugins.entries.arbiter-openclaw.config` in your OpenClaw config:
32
+ The plugin auto-discovers local runtime settings from `~/.arbiter/config.json` by default.
33
+
34
+ Minimal local config:
33
35
 
34
36
  ```json
35
37
  {
@@ -38,10 +40,6 @@ Add plugin config under `plugins.entries.arbiter-openclaw.config` in your OpenCl
38
40
  "arbiter-openclaw": {
39
41
  "enabled": true,
40
42
  "config": {
41
- "arbiterUrl": "http://localhost:8080",
42
- "tenantId": "tenant-demo",
43
- "gatewayKey": "gw-key",
44
- "serviceKey": "svc-key",
45
43
  "protectTools": ["exec", "process", "write", "edit", "apply_patch"],
46
44
  "recordState": true,
47
45
  "failClosed": true
@@ -52,10 +50,31 @@ Add plugin config under `plugins.entries.arbiter-openclaw.config` in your OpenCl
52
50
  }
53
51
  ```
54
52
 
53
+ Optional explicit config (for non-local or customized setups):
54
+
55
+ ```json
56
+ {
57
+ "plugins": {
58
+ "entries": {
59
+ "arbiter-openclaw": {
60
+ "enabled": true,
61
+ "config": {
62
+ "arbiterUrl": "http://localhost:8080",
63
+ "tenantId": "tenant-demo",
64
+ "gatewayKey": "gw-key",
65
+ "serviceKey": "svc-key"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
55
73
  Config options:
56
74
 
57
- - `arbiterUrl`: Arbiter base URL.
58
- - `tenantId`: Tenant ID for canonical requests and state records.
75
+ - `arbiterUrl`: Arbiter base URL. Defaults from local runtime config when available.
76
+ - `localConfigPath`: Optional override path for local runtime config. Defaults to `~/.arbiter/config.json`.
77
+ - `tenantId`: Tenant ID for canonical requests and state records. Defaults from local runtime config when available.
59
78
  - `gatewayKey`: Optional key for intercept routes.
60
79
  - `serviceKey`: Optional key for verify/state routes.
61
80
  - `actorIdMode`: `agent-id` (default) or `config`.
@@ -65,6 +84,13 @@ Config options:
65
84
  - `failClosed`: Block on Arbiter errors (default `true`).
66
85
  - `timeoutMs`: Per-request timeout (default `5000`).
67
86
 
87
+ Local runtime quick setup:
88
+
89
+ ```bash
90
+ go run ./cmd/arbiter local init
91
+ go run ./cmd/arbiter local start
92
+ ```
93
+
68
94
  ## Stock OpenClaw Tool Mapping
69
95
 
70
96
  The default protected tools are:
@@ -77,6 +103,8 @@ The default protected tools are:
77
103
 
78
104
  Use Arbiter policy to allow or deny these tools. The included Arbiter filesystem policy denies destructive delete commands and `apply_patch` file-deletion directives.
79
105
 
106
+ For a safer OpenClaw smoke test, the default policy also denies writes and shell commands that target the canary prefix `/tmp/arbiter-deny-test`. A normal chat prompt such as `Use the exec tool exactly once to run: mkdir -p /tmp/arbiter-deny-test/nested` should trigger an Arbiter block without relying on `rm -rf`.
107
+
80
108
  ## Development
81
109
 
82
110
  Run plugin tests:
package/index.js CHANGED
@@ -9,8 +9,7 @@ export default definePluginEntry({
9
9
  const guardrail = createArbiterGuardrail({
10
10
  pluginConfig: api.pluginConfig,
11
11
  logger: api.logger,
12
- fetchImpl: globalThis.fetch,
13
- env: process.env
12
+ fetchImpl: globalThis.fetch
14
13
  });
15
14
 
16
15
  api.on("before_tool_call", guardrail.beforeToolCall);
@@ -11,6 +11,9 @@
11
11
  "format": "uri",
12
12
  "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://"
13
13
  },
14
+ "localConfigPath": {
15
+ "type": "string"
16
+ },
14
17
  "tenantId": {
15
18
  "type": "string",
16
19
  "minLength": 1
@@ -67,6 +70,11 @@
67
70
  "label": "Arbiter Base URL",
68
71
  "help": "Base URL for the Arbiter interceptor, for example http://localhost:8080."
69
72
  },
73
+ "localConfigPath": {
74
+ "label": "Local Runtime Config Path",
75
+ "help": "Optional path to local Arbiter config (defaults to ~/.arbiter/config.json).",
76
+ "advanced": true
77
+ },
70
78
  "tenantId": {
71
79
  "label": "Tenant ID",
72
80
  "help": "Tenant identifier included in canonical requests and state records."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@randromeda/arbiter-openclaw",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OpenClaw native plugin that enforces Arbiter guardrails before tool execution.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/config.js CHANGED
@@ -1,3 +1,7 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
1
5
  export const DEFAULT_PROTECT_TOOLS = ["exec", "process", "write", "edit", "apply_patch"];
2
6
  export const DEFAULT_TIMEOUT_MS = 5000;
3
7
 
@@ -36,15 +40,38 @@ function normalizeToolList(value) {
36
40
  return tools.length > 0 ? Array.from(new Set(tools)) : DEFAULT_PROTECT_TOOLS.slice();
37
41
  }
38
42
 
39
- export function resolvePluginConfig(pluginConfig, env = process.env) {
43
+ function readLocalRuntimeConfig(customPath) {
44
+ const location = customPath || path.join(os.homedir(), ".arbiter", "config.json");
45
+ try {
46
+ const raw = fs.readFileSync(location, "utf8");
47
+ const parsed = JSON.parse(raw);
48
+ return asRecord(parsed);
49
+ } catch {
50
+ return {};
51
+ }
52
+ }
53
+
54
+ function baseURLFromAddress(address) {
55
+ if (!address) {
56
+ return "";
57
+ }
58
+ if (address.startsWith("http://") || address.startsWith("https://")) {
59
+ return address;
60
+ }
61
+ return `http://${address}`;
62
+ }
63
+
64
+ export function resolvePluginConfig(pluginConfig) {
40
65
  const cfg = asRecord(pluginConfig);
41
- const runtimeEnv = asRecord(env);
66
+ const localConfigPath = asString(cfg.localConfigPath);
67
+ const localConfig = readLocalRuntimeConfig(localConfigPath);
68
+ const localURL = asString(localConfig.base_url) || baseURLFromAddress(asString(localConfig.address));
42
69
 
43
- const arbiterUrl = asString(cfg.arbiterUrl) || asString(runtimeEnv.ARBITER_URL);
44
- const tenantId = asString(cfg.tenantId) || asString(runtimeEnv.ARBITER_TENANT_ID);
45
- const gatewayKey = asString(cfg.gatewayKey) || asString(runtimeEnv.ARBITER_GATEWAY_SHARED_KEY);
46
- const serviceKey = asString(cfg.serviceKey) || asString(runtimeEnv.ARBITER_SERVICE_SHARED_KEY);
47
- const actorId = asString(cfg.actorId) || asString(runtimeEnv.ARBITER_ACTOR_ID);
70
+ const arbiterUrl = asString(cfg.arbiterUrl) || localURL;
71
+ const tenantId = asString(cfg.tenantId) || asString(localConfig.tenant_id);
72
+ const gatewayKey = asString(cfg.gatewayKey);
73
+ const serviceKey = asString(cfg.serviceKey);
74
+ const actorId = asString(cfg.actorId);
48
75
 
49
76
  const actorIdMode = asString(cfg.actorIdMode) === "config" ? "config" : "agent-id";
50
77
  const protectTools = normalizeToolList(cfg.protectTools);
@@ -74,6 +101,7 @@ export function resolvePluginConfig(pluginConfig, env = process.env) {
74
101
  recordState,
75
102
  failClosed,
76
103
  timeoutMs,
104
+ localConfigPath,
77
105
  missing
78
106
  };
79
107
  }
package/src/guardrail.js CHANGED
@@ -31,7 +31,11 @@ function configError(config) {
31
31
  if (!config.missing.length) {
32
32
  return "";
33
33
  }
34
- return `arbiter plugin misconfigured: missing ${config.missing.join(", ")}`;
34
+ let hint = "";
35
+ if (config.missing.includes("arbiterUrl") || config.missing.includes("tenantId")) {
36
+ hint = " (run `go run ./cmd/arbiter local init` and `go run ./cmd/arbiter local start`, or set arbiterUrl/tenantId explicitly)";
37
+ }
38
+ return `arbiter plugin misconfigured: missing ${config.missing.join(", ")}${hint}`;
35
39
  }
36
40
 
37
41
  function serviceHeaders(config) {
@@ -45,10 +49,9 @@ function gatewayHeaders(config) {
45
49
  export function createArbiterGuardrail({
46
50
  pluginConfig,
47
51
  logger,
48
- fetchImpl = globalThis.fetch,
49
- env = process.env
52
+ fetchImpl = globalThis.fetch
50
53
  }) {
51
- const config = resolvePluginConfig(pluginConfig, env);
54
+ const config = resolvePluginConfig(pluginConfig);
52
55
  const protectedTools = new Set(config.protectTools);
53
56
 
54
57
  async function beforeToolCall(event, ctx) {