openclaw-zero-agency 0.1.0 → 0.2.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/dist/gate.js ADDED
@@ -0,0 +1,51 @@
1
+ // Zero Agency receipt gate. Consequential tool calls require a routing receipt.
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+ import { appendFile, mkdir } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ import { get, prune } from "./receipts.js";
6
+ async function telemetry(path, record) {
7
+ if (!path)
8
+ return;
9
+ try {
10
+ await mkdir(dirname(path), { recursive: true });
11
+ await appendFile(path, JSON.stringify({ ts: new Date().toISOString(), ...record }) + "\n");
12
+ }
13
+ catch { /* never break a turn */ }
14
+ }
15
+ export default definePluginEntry({
16
+ id: "zero-agency/gate",
17
+ name: "Zero Agency Gate",
18
+ description: "Requires a Zero Agency routing receipt before consequential tool calls.",
19
+ register(api) {
20
+ const cfg = api.config ?? api.pluginConfig ?? {};
21
+ const mode = cfg.gateMode ?? "advisory";
22
+ const tools = cfg.consequentialTools ?? ["exec", "apply_patch"];
23
+ const tel = cfg.telemetryPath;
24
+ api.on("before_tool_call", async (event, ctx) => {
25
+ if (mode === "off")
26
+ return undefined;
27
+ const runId = event?.runId ?? ctx?.runId ?? "unknown-run";
28
+ prune();
29
+ const r = get(runId);
30
+ if (r) {
31
+ await telemetry(tel, { kind: "gate", decision: "receipt-ok", tool: event?.toolName, runId, receipt: r.id });
32
+ return undefined;
33
+ }
34
+ await telemetry(tel, { kind: "gate", decision: mode === "enforce" ? "blocked" : "advisory-miss", tool: event?.toolName, runId });
35
+ if (mode !== "enforce")
36
+ return undefined;
37
+ return {
38
+ requireApproval: {
39
+ title: `Zero Agency: no capability receipt (${event?.toolName ?? "tool"})`,
40
+ description: "Consequential call without a capability lookup. Approve for this call only, or deny and run zero_agency_route first.",
41
+ severity: "warning",
42
+ timeoutMs: 120000,
43
+ allowedDecisions: ["allow-once", "deny"],
44
+ onResolution: async (decision) => {
45
+ await telemetry(tel, { kind: "gate", decision, tool: event?.toolName, runId, receipt: null });
46
+ },
47
+ },
48
+ };
49
+ }, { matcher: tools, priority: 50 });
50
+ },
51
+ });
package/dist/index.js CHANGED
@@ -1,50 +1,61 @@
1
- // Zero Agency — capability routing, receipt gate, telemetry.
1
+ // Zero Agency — single-entry plugin: capability routing tools + receipt gate.
2
2
  // Packs inform judgement; they never confer authority.
3
+ //
4
+ // NOTE: kept as one entry on purpose. A hook-bearing plugin cannot pass
5
+ // `openclaw plugins validate` (it requires defineToolPlugin metadata), and a
6
+ // multi-entry package cannot satisfy the manifest-id check. Runtime load is the
7
+ // authoritative test for this package.
3
8
  import { Type } from "typebox";
4
9
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
5
10
  import { readFile, appendFile, mkdir } from "node:fs/promises";
11
+ import { statSync } from "node:fs";
6
12
  import { createHash, randomUUID } from "node:crypto";
7
13
  import { dirname } from "node:path";
8
- const receipts = new Map(); // runId -> receipt
9
- const registryCache = new Map();
10
- const now = () => Date.now();
14
+ const KEY = "openclaw.zero-agency.receipts.v1";
15
+ function store() {
16
+ const g = globalThis;
17
+ if (!g[KEY])
18
+ g[KEY] = new Map();
19
+ return g[KEY];
20
+ }
21
+ const prune = () => { const t = Date.now(); for (const [k, v] of store())
22
+ if (v.expires < t)
23
+ store().delete(k); };
24
+ const cache = new Map();
11
25
  const norm = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim();
12
26
  async function loadRegistry(path) {
13
- const { statSync } = await import("node:fs");
14
27
  const st = statSync(path);
15
- const hit = registryCache.get(path);
28
+ const hit = cache.get(path);
16
29
  if (hit && hit.mtimeMs === st.mtimeMs)
17
30
  return hit.data;
18
31
  const data = JSON.parse(await readFile(path, "utf8"));
19
- registryCache.set(path, { mtimeMs: st.mtimeMs, data });
32
+ cache.set(path, { mtimeMs: st.mtimeMs, data });
20
33
  return data;
21
34
  }
22
- function scoreCapability(cap, q) {
23
- let score = 0;
35
+ function score(cap, q) {
36
+ let s = 0;
24
37
  const hits = [];
25
38
  for (const t of cap.triggers ?? []) {
26
39
  const nt = norm(t);
27
40
  if (nt && q.includes(nt)) {
28
- score += 3;
41
+ s += 3;
29
42
  hits.push(t);
43
+ continue;
30
44
  }
31
- else if (nt) {
32
- const words = nt.split(" ").filter((w) => w.length > 3);
33
- const overlap = words.filter((w) => q.includes(w)).length;
34
- if (words.length && overlap === words.length) {
35
- score += 2;
36
- hits.push(t);
37
- }
38
- else if (overlap > 0) {
39
- score += overlap / words.length;
40
- }
45
+ const words = nt.split(" ").filter((w) => w.length > 3);
46
+ const overlap = words.filter((w) => q.includes(w)).length;
47
+ if (words.length && overlap === words.length) {
48
+ s += 2;
49
+ hits.push(t);
41
50
  }
51
+ else if (overlap)
52
+ s += overlap / words.length;
42
53
  }
43
- const descWords = norm(cap.description).split(" ").filter((w) => w.length > 4);
44
- score += 0.5 * descWords.filter((w) => q.includes(w)).length;
45
- return { score, hits };
54
+ const dw = norm(cap.description).split(" ").filter((w) => w.length > 4);
55
+ s += 0.5 * dw.filter((w) => q.includes(w)).length;
56
+ return { score: s, hits };
46
57
  }
47
- async function writeTelemetry(path, record) {
58
+ async function telemetry(path, record) {
48
59
  if (!path)
49
60
  return;
50
61
  try {
@@ -53,12 +64,6 @@ async function writeTelemetry(path, record) {
53
64
  }
54
65
  catch { /* telemetry must never break a turn */ }
55
66
  }
56
- function prune(ttl) {
57
- const t = now();
58
- for (const [k, v] of receipts)
59
- if (v.expires < t)
60
- receipts.delete(k);
61
- }
62
67
  export default definePluginEntry({
63
68
  id: "zero-agency",
64
69
  name: "Zero Agency",
@@ -66,121 +71,87 @@ export default definePluginEntry({
66
71
  register(api) {
67
72
  const cfg = api.config ?? api.pluginConfig ?? {};
68
73
  const registryPath = cfg.registryPath ?? "";
69
- const telemetryPath = cfg.telemetryPath ?? "";
74
+ const telemetryPath = cfg.telemetryPath;
70
75
  const gateMode = cfg.gateMode ?? "advisory";
71
- const consequentialTools = cfg.consequentialTools ?? ["exec", "apply_patch"];
72
- const ttl = cfg.receiptTtlMs ?? 15 * 60 * 1000;
76
+ const consequential = cfg.consequentialTools ?? ["exec", "apply_patch"];
77
+ const ttl = cfg.receiptTtlMs ?? 900000;
73
78
  api.registerTool({
74
79
  name: "zero_agency_route",
75
- description: "Find the Zero Agency capability pack that governs a domain task. Call before acting on security review, release verification, or incident triage. Returns a receipt required by the gate.",
80
+ description: "Find the capability pack governing a domain task. Call before acting on security review, release verification or incident triage. Issues a receipt the gate accepts.",
76
81
  parameters: Type.Object({
77
82
  task: Type.String({ description: "The task or question, in natural language." }),
78
83
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5 })),
79
84
  }),
80
85
  outputSchema: Type.Object({
81
- candidates: Type.Array(Type.Object({
82
- id: Type.String(), pack: Type.String(), score: Type.Number(), triggers: Type.Array(Type.String()),
83
- })),
86
+ candidates: Type.Array(Type.Object({ id: Type.String(), pack: Type.String(), score: Type.Number(), triggers: Type.Array(Type.String()) })),
84
87
  receipt: Type.Union([Type.String(), Type.Null()]),
85
88
  note: Type.String(),
86
89
  }, { additionalProperties: false }),
87
90
  async execute(_id, params, ctx) {
88
91
  const q = norm(params.task);
89
- const limit = params.limit ?? 3;
90
92
  let candidates = [];
91
93
  let note = "";
92
- if (!registryPath) {
93
- note = "zero-agency is not configured: registryPath is unset";
94
+ try {
95
+ const reg = await loadRegistry(registryPath);
96
+ candidates = (reg.capabilities ?? [])
97
+ .map((c) => ({ c, ...score(c, q) }))
98
+ .filter((x) => x.score > 0)
99
+ .sort((a, b) => b.score - a.score)
100
+ .slice(0, params.limit ?? 3)
101
+ .map((x) => ({ id: x.c.id, pack: x.c.pack, score: Number(x.score.toFixed(2)), triggers: x.hits }));
102
+ note = candidates.length ? `load ${candidates[0].pack} before acting` : "no capability matched; if this action is consequential, stop and ask";
94
103
  }
95
- else {
96
- try {
97
- const reg = await loadRegistry(registryPath);
98
- candidates = (reg.capabilities ?? [])
99
- .map((c) => ({ c, ...scoreCapability(c, q) }))
100
- .filter((x) => x.score > 0)
101
- .sort((a, b) => b.score - a.score)
102
- .slice(0, limit)
103
- .map((x) => ({ id: x.c.id, pack: x.c.pack, score: Number(x.score.toFixed(2)), triggers: x.hits }));
104
- if (!candidates.length)
105
- note = "no capability matched; if this action is consequential, stop and ask";
106
- else
107
- note = `load ${candidates[0].pack} before acting`;
108
- }
109
- catch (e) {
110
- note = `registry unavailable: ${e.message}`;
111
- }
104
+ catch (e) {
105
+ note = `registry unavailable: ${e?.message ?? "unknown"}`;
112
106
  }
113
107
  let receipt = null;
114
108
  if (candidates.length) {
115
- const runId = ctx?.runId ?? "unknown-run";
116
- receipt = createHash("sha256").update(`${runId}:${candidates[0].id}:${now()}:${randomUUID()}`).digest("hex").slice(0, 16);
117
- receipts.set(runId, { id: receipt, pack: candidates[0].pack, at: now(), expires: now() + ttl });
118
- prune(ttl);
109
+ const runId = ctx?.runId ?? ctx?.sessionId ?? "unknown-run";
110
+ receipt = createHash("sha256").update(`${runId}:${candidates[0].id}:${Date.now()}:${randomUUID()}`).digest("hex").slice(0, 16);
111
+ store().set(runId, { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl });
112
+ prune();
119
113
  }
120
- await writeTelemetry(telemetryPath, {
121
- kind: "route", runId: ctx?.runId ?? null, agent: ctx?.agentId ?? null,
122
- session: ctx?.sessionKey ?? null, query: params.task.slice(0, 200),
123
- selected: candidates[0]?.id ?? null, candidates: candidates.length, receipt,
124
- });
125
- return {
126
- content: [{ type: "text", text: JSON.stringify({ candidates, receipt, note }) }],
127
- details: { candidates, receipt, note },
128
- };
114
+ await telemetry(telemetryPath, { kind: "route", agent: ctx?.agentId ?? null, session: ctx?.sessionKey ?? null, query: params.task.slice(0, 200), selected: candidates[0]?.id ?? null, candidates: candidates.length, receipt });
115
+ return { content: [{ type: "text", text: JSON.stringify({ candidates, receipt, note }) }], details: { candidates, receipt, note } };
129
116
  },
130
117
  });
131
118
  api.registerTool({
132
119
  name: "zero_agency_status",
133
- description: "Report Zero Agency registry version, capability count and gate mode.",
120
+ description: "Report Zero Agency registry state: capability count, token total and live receipts.",
134
121
  parameters: Type.Object({}),
135
- outputSchema: Type.Object({
136
- gateMode: Type.String(), registry: Type.String(), capabilities: Type.Integer(),
137
- receipts: Type.Integer(), registryError: Type.Union([Type.String(), Type.Null()]),
138
- }, { additionalProperties: false }),
122
+ outputSchema: Type.Object({ registry: Type.String(), capabilities: Type.Integer(), tokens: Type.Integer(), receipts: Type.Integer(), registryError: Type.Union([Type.String(), Type.Null()]) }, { additionalProperties: false }),
139
123
  async execute() {
140
- let capabilities = 0;
141
- let registryError = null;
142
124
  try {
143
125
  const reg = await loadRegistry(registryPath);
144
- capabilities = (reg.capabilities ?? []).length;
126
+ const caps = reg.capabilities ?? [];
127
+ return { content: [{ type: "text", text: `zero-agency: ${caps.length} capabilities` }], details: { registry: registryPath, capabilities: caps.length, tokens: caps.reduce((s, c) => s + (c.tokens ?? 0), 0), receipts: store().size, registryError: null } };
145
128
  }
146
129
  catch (e) {
147
- registryError = e.message;
130
+ return { content: [{ type: "text", text: `zero-agency: registry error` }], details: { registry: registryPath, capabilities: 0, tokens: 0, receipts: store().size, registryError: e?.message ?? "unknown" } };
148
131
  }
149
- return {
150
- content: [{ type: "text", text: `zero-agency: ${capabilities} capabilities, gate=${gateMode}` }],
151
- details: { gateMode, registry: registryPath, capabilities, receipts: receipts.size, registryError },
152
- };
153
132
  },
154
133
  });
155
- // Receipt gate. Fail-closed in enforce mode: no receipt, no consequential call.
156
134
  api.on("before_tool_call", async (event, ctx) => {
157
135
  if (gateMode === "off")
158
136
  return undefined;
159
137
  const runId = event?.runId ?? ctx?.runId ?? "unknown-run";
160
- prune(ttl);
161
- const r = receipts.get(runId);
138
+ prune();
139
+ const r = store().get(runId);
162
140
  if (r) {
163
- await writeTelemetry(telemetryPath, { kind: "gate", decision: "receipt-ok", tool: event?.toolName, runId, receipt: r.id });
141
+ await telemetry(telemetryPath, { kind: "gate", decision: "receipt-ok", tool: event?.toolName, runId, receipt: r.id });
164
142
  return undefined;
165
143
  }
166
- await writeTelemetry(telemetryPath, {
167
- kind: "gate", decision: gateMode === "enforce" ? "blocked" : "advisory-miss",
168
- tool: event?.toolName, runId,
169
- });
144
+ await telemetry(telemetryPath, { kind: "gate", decision: gateMode === "enforce" ? "blocked" : "advisory-miss", tool: event?.toolName, runId });
170
145
  if (gateMode !== "enforce")
171
146
  return undefined;
172
147
  return {
173
148
  requireApproval: {
174
149
  title: `Zero Agency: no capability receipt (${event?.toolName ?? "tool"})`,
175
- description: "This consequential call was made without a capability pack lookup. Approve to proceed for this call only, or deny and run zero_agency_route first.",
176
- severity: "warning",
177
- timeoutMs: 120000,
178
- allowedDecisions: ["allow-once", "deny"],
179
- onResolution: async (decision) => {
180
- await writeTelemetry(telemetryPath, { kind: "gate", decision, tool: event?.toolName, runId, receipt: null });
181
- },
150
+ description: "Consequential call without a capability lookup. Approve for this call only, or deny and run zero_agency_route first.",
151
+ severity: "warning", timeoutMs: 120000, allowedDecisions: ["allow-once", "deny"],
152
+ onResolution: async (decision) => { await telemetry(telemetryPath, { kind: "gate", decision, tool: event?.toolName, runId, receipt: null }); },
182
153
  },
183
154
  };
184
- }, { matcher: consequentialTools, priority: 50 });
155
+ }, { matcher: consequential, priority: 50 });
185
156
  },
186
157
  });
@@ -0,0 +1,22 @@
1
+ const KEY = Symbol.for("openclaw.zero-agency.receipts.v1");
2
+ function store() {
3
+ const g = globalThis;
4
+ if (!g[KEY])
5
+ g[KEY] = new Map();
6
+ return g[KEY];
7
+ }
8
+ export function put(runId, receipt) {
9
+ store().set(runId, receipt);
10
+ }
11
+ export function get(runId) {
12
+ return store().get(runId);
13
+ }
14
+ export function prune() {
15
+ const t = Date.now();
16
+ for (const [k, v] of store())
17
+ if (v.expires < t)
18
+ store().delete(k);
19
+ }
20
+ export function size() {
21
+ return store().size;
22
+ }
package/dist/tools.js ADDED
@@ -0,0 +1,143 @@
1
+ // Zero Agency — capability routing tools. Packs inform judgement; they never confer authority.
2
+ import { Type } from "typebox";
3
+ import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
4
+ import { readFile, appendFile, mkdir } from "node:fs/promises";
5
+ import { statSync } from "node:fs";
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { dirname } from "node:path";
8
+ import { put, prune, size } from "./receipts.js";
9
+ const cache = new Map();
10
+ const norm = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim();
11
+ async function loadRegistry(path) {
12
+ const st = statSync(path);
13
+ const hit = cache.get(path);
14
+ if (hit && hit.mtimeMs === st.mtimeMs)
15
+ return hit.data;
16
+ const data = JSON.parse(await readFile(path, "utf8"));
17
+ cache.set(path, { mtimeMs: st.mtimeMs, data });
18
+ return data;
19
+ }
20
+ function score(cap, q) {
21
+ let s = 0;
22
+ const hits = [];
23
+ for (const t of cap.triggers ?? []) {
24
+ const nt = norm(t);
25
+ if (nt && q.includes(nt)) {
26
+ s += 3;
27
+ hits.push(t);
28
+ continue;
29
+ }
30
+ const words = nt.split(" ").filter((w) => w.length > 3);
31
+ const overlap = words.filter((w) => q.includes(w)).length;
32
+ if (words.length && overlap === words.length) {
33
+ s += 2;
34
+ hits.push(t);
35
+ }
36
+ else if (overlap)
37
+ s += overlap / words.length;
38
+ }
39
+ const dw = norm(cap.description).split(" ").filter((w) => w.length > 4);
40
+ s += 0.5 * dw.filter((w) => q.includes(w)).length;
41
+ return { score: s, hits };
42
+ }
43
+ async function telemetry(path, record) {
44
+ if (!path)
45
+ return;
46
+ try {
47
+ await mkdir(dirname(path), { recursive: true });
48
+ await appendFile(path, JSON.stringify({ ts: new Date().toISOString(), ...record }) + "\n");
49
+ }
50
+ catch { /* telemetry must never break a turn */ }
51
+ }
52
+ export default defineToolPlugin({
53
+ id: "zero-agency/tools",
54
+ name: "Zero Agency",
55
+ description: "Capability routing for the fleet. Packs inform judgement; they never confer authority.",
56
+ configSchema: Type.Object({
57
+ registryPath: Type.String({ description: "Absolute path to capabilities.json (read-only)." }),
58
+ telemetryPath: Type.Optional(Type.String({ description: "JSONL telemetry path." })),
59
+ receiptTtlMs: Type.Optional(Type.Integer({ minimum: 1000 })),
60
+ gateMode: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("advisory"), Type.Literal("enforce")], { description: "Receipt gate mode (default advisory)." })),
61
+ consequentialTools: Type.Optional(Type.Array(Type.String(), { description: "Tool ids gated by receipt." })),
62
+ }),
63
+ tools: (tool) => [
64
+ tool({
65
+ name: "zero_agency_route",
66
+ label: "Zero Agency Route",
67
+ description: "Find the capability pack governing a domain task. Call before acting on security review, release verification or incident triage. Issues a receipt the gate accepts.",
68
+ parameters: Type.Object({
69
+ task: Type.String({ description: "The task or question, in natural language." }),
70
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5 })),
71
+ }),
72
+ outputSchema: Type.Object({
73
+ candidates: Type.Array(Type.Object({
74
+ id: Type.String(), pack: Type.String(), score: Type.Number(), triggers: Type.Array(Type.String()),
75
+ })),
76
+ receipt: Type.Union([Type.String(), Type.Null()]),
77
+ note: Type.String(),
78
+ }, { additionalProperties: false }),
79
+ async execute(params, config, context) {
80
+ context?.signal?.throwIfAborted?.();
81
+ const q = norm(params.task);
82
+ const limit = params.limit ?? 3;
83
+ const ttl = config.receiptTtlMs ?? 900000;
84
+ let candidates = [];
85
+ let note = "";
86
+ try {
87
+ const reg = await loadRegistry(config.registryPath);
88
+ candidates = (reg.capabilities ?? [])
89
+ .map((c) => ({ c, ...score(c, q) }))
90
+ .filter((x) => x.score > 0)
91
+ .sort((a, b) => b.score - a.score)
92
+ .slice(0, limit)
93
+ .map((x) => ({ id: x.c.id, pack: x.c.pack, score: Number(x.score.toFixed(2)), triggers: x.hits }));
94
+ note = candidates.length
95
+ ? `load ${candidates[0].pack} before acting`
96
+ : "no capability matched; if this action is consequential, stop and ask";
97
+ }
98
+ catch (e) {
99
+ note = `registry unavailable: ${e?.message ?? "unknown"}`;
100
+ }
101
+ let receipt = null;
102
+ if (candidates.length) {
103
+ const runId = context?.runId ?? context?.sessionId ?? "unknown-run";
104
+ receipt = createHash("sha256").update(`${runId}:${candidates[0].id}:${Date.now()}:${randomUUID()}`).digest("hex").slice(0, 16);
105
+ put(runId, { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl });
106
+ prune();
107
+ }
108
+ await telemetry(config.telemetryPath, {
109
+ kind: "route", agent: context?.agentId ?? null,
110
+ session: context?.sessionId ?? null, query: params.task.slice(0, 200),
111
+ selected: candidates[0]?.id ?? null, candidates: candidates.length, receipt,
112
+ });
113
+ return { candidates, receipt, note };
114
+ },
115
+ }),
116
+ tool({
117
+ name: "zero_agency_status",
118
+ label: "Zero Agency Status",
119
+ description: "Report the Zero Agency registry state: capability count, token total and live receipts.",
120
+ parameters: Type.Object({}),
121
+ outputSchema: Type.Object({
122
+ registry: Type.String(), capabilities: Type.Integer(), tokens: Type.Integer(),
123
+ receipts: Type.Integer(), registryError: Type.Union([Type.String(), Type.Null()]),
124
+ }, { additionalProperties: false }),
125
+ async execute(_params, config) {
126
+ try {
127
+ const reg = await loadRegistry(config.registryPath);
128
+ const caps = reg.capabilities ?? [];
129
+ return {
130
+ registry: config.registryPath,
131
+ capabilities: caps.length,
132
+ tokens: caps.reduce((s, c) => s + (c.tokens ?? 0), 0),
133
+ receipts: size(),
134
+ registryError: null,
135
+ };
136
+ }
137
+ catch (e) {
138
+ return { registry: config.registryPath, capabilities: 0, tokens: 0, receipts: size(), registryError: e?.message ?? "unknown" };
139
+ }
140
+ },
141
+ }),
142
+ ],
143
+ });
@@ -7,12 +7,13 @@
7
7
  "configSchema": {
8
8
  "type": "object",
9
9
  "additionalProperties": false,
10
+ "required": ["registryPath"],
10
11
  "properties": {
11
- "registryPath": { "type": "string", "description": "Absolute path to capabilities.json (read-only mount)." },
12
- "telemetryPath": { "type": "string", "description": "Absolute path to the JSONL telemetry file." },
12
+ "registryPath": { "type": "string", "description": "Absolute path to capabilities.json (read-only)." },
13
+ "telemetryPath": { "type": "string", "description": "JSONL telemetry path." },
14
+ "receiptTtlMs": { "type": "integer", "minimum": 1000, "default": 900000 },
13
15
  "gateMode": { "type": "string", "enum": ["off", "advisory", "enforce"], "default": "advisory" },
14
- "consequentialTools": { "type": "array", "items": { "type": "string" }, "default": ["exec", "apply_patch"] },
15
- "receiptTtlMs": { "type": "integer", "minimum": 1000, "default": 900000 }
16
+ "consequentialTools": { "type": "array", "items": { "type": "string" }, "default": ["exec", "apply_patch"] }
16
17
  }
17
18
  }
18
19
  }
package/package.json CHANGED
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "openclaw-zero-agency",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Zero Agency: capability packs, programmatic routing and a receipt gate for OpenClaw. Packs inform judgement; they never confer authority.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "license": "MIT",
8
- "keywords": ["openclaw", "openclaw-plugin", "agents", "capability-routing", "governance"],
9
- "files": ["dist", "openclaw.plugin.json", "README.md", "LICENSE"],
8
+ "keywords": [
9
+ "openclaw",
10
+ "openclaw-plugin",
11
+ "agents",
12
+ "capability-routing",
13
+ "governance"
14
+ ],
15
+ "files": [
16
+ "dist",
17
+ "openclaw.plugin.json",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
10
21
  "scripts": {
11
22
  "build": "tsc -p tsconfig.build.json",
12
23
  "typecheck": "tsc --noEmit -p tsconfig.json",
@@ -23,7 +34,9 @@
23
34
  "typescript": "^7.0.2"
24
35
  },
25
36
  "openclaw": {
26
- "extensions": ["./dist/index.js"],
37
+ "extensions": [
38
+ "./dist/index.js"
39
+ ],
27
40
  "compat": {
28
41
  "pluginApi": ">=2026.10.1",
29
42
  "minGatewayVersion": "2026.10.1"