openclaw-zero-agency 0.2.3 → 0.2.5

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 (2) hide show
  1. package/dist/index.js +46 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  import { Type } from "typebox";
9
9
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
10
10
  import { readFile, appendFile, mkdir } from "node:fs/promises";
11
+ import { rename, writeFile } from "node:fs/promises";
11
12
  import { statSync } from "node:fs";
12
13
  import { createHash, randomUUID } from "node:crypto";
13
14
  import { dirname } from "node:path";
@@ -22,6 +23,45 @@ const prune = () => { const t = Date.now(); for (const [k, v] of store())
22
23
  if (v.expires < t)
23
24
  store().delete(k); };
24
25
  const cache = new Map();
26
+ // Receipts are shared through a file, not memory: the tool and the hook run in
27
+ // different execution contexts inside the plugin host, so a Map is not shared.
28
+ let receiptPath = "/tmp/zero-agency-receipts.json";
29
+ async function readReceipts() {
30
+ try {
31
+ return JSON.parse(await readFile(receiptPath, "utf8"));
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ async function writeReceipt(runId, rec) {
38
+ const now = Date.now();
39
+ const all = await readReceipts();
40
+ for (const k of Object.keys(all)) {
41
+ const v = all[k];
42
+ if (!v || v.expires < now)
43
+ delete all[k];
44
+ }
45
+ all.global = rec;
46
+ if (runId)
47
+ all[runId] = rec;
48
+ try {
49
+ await mkdir(dirname(receiptPath), { recursive: true });
50
+ const tmp = `${receiptPath}.tmp`;
51
+ await writeFile(tmp, JSON.stringify(all));
52
+ await rename(tmp, receiptPath);
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ async function findReceipt(runId) {
60
+ const all = await readReceipts();
61
+ const now = Date.now();
62
+ const r = (runId ? all[runId] : undefined) ?? all.global;
63
+ return r && r.expires > now ? r : undefined;
64
+ }
25
65
  const norm = (s) => String(s ?? "").toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim();
26
66
  async function loadRegistry(path) {
27
67
  const st = statSync(path);
@@ -71,6 +111,8 @@ export default definePluginEntry({
71
111
  register(api) {
72
112
  // api.config is the GLOBAL gateway config; plugin-scoped config lives on api.pluginConfig.
73
113
  const cfg = api.pluginConfig ?? {};
114
+ if (cfg.telemetryPath)
115
+ receiptPath = `${dirname(cfg.telemetryPath)}/receipts.json`;
74
116
  const registryPath = cfg.registryPath ?? "";
75
117
  const telemetryPath = cfg.telemetryPath;
76
118
  const gateMode = cfg.gateMode ?? "advisory";
@@ -109,13 +151,9 @@ export default definePluginEntry({
109
151
  if (candidates.length) {
110
152
  const runId = ctx?.runId ?? ctx?.sessionId ?? null;
111
153
  receipt = createHash("sha256").update(`${runId ?? "global"}:${candidates[0].id}:${Date.now()}:${randomUUID()}`).digest("hex").slice(0, 16);
112
- const rec = { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl };
113
- // Tool context does not always carry runId; always publish a global
114
- // receipt so the gate can match when the tool cannot see the run.
115
- if (runId)
116
- store().set(runId, rec);
117
- store().set("global", rec);
118
- prune();
154
+ const wrote = await writeReceipt(runId, { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl });
155
+ if (!wrote)
156
+ await telemetry(telemetryPath, { kind: "receipt-write-failed", path: receiptPath, receipt });
119
157
  }
120
158
  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 });
121
159
  return { content: [{ type: "text", text: JSON.stringify({ candidates, receipt, note }) }], details: { candidates, receipt, note } };
@@ -142,7 +180,7 @@ export default definePluginEntry({
142
180
  return undefined;
143
181
  const runId = event?.runId ?? ctx?.runId ?? "unknown-run";
144
182
  prune();
145
- const r = (runId && store().get(runId)) || store().get("global");
183
+ const r = await findReceipt(runId);
146
184
  if (r) {
147
185
  await telemetry(telemetryPath, { kind: "gate", decision: "receipt-ok", tool: event?.toolName, runId, receipt: r.id });
148
186
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-zero-agency",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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",