@fidacy/openclaw-plugin 0.4.3 → 0.5.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/index.js +452 -51
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1310,8 +1310,8 @@ function CreateObject(types, value) {
|
|
|
1310
1310
|
}
|
|
1311
1311
|
function FromUnionKey(types, value) {
|
|
1312
1312
|
const flattened = Flatten(types);
|
|
1313
|
-
const
|
|
1314
|
-
return IsSchema(
|
|
1313
|
+
const record3 = TryBuildRecord(flattened, value);
|
|
1314
|
+
return IsSchema(record3) ? record3 : CreateObject(flattened, value);
|
|
1315
1315
|
}
|
|
1316
1316
|
|
|
1317
1317
|
// ../../node_modules/.pnpm/typebox@1.1.39/node_modules/typebox/build/type/engine/record/from_key.mjs
|
|
@@ -4224,6 +4224,331 @@ __export(typebox_exports, {
|
|
|
4224
4224
|
// src/index.ts
|
|
4225
4225
|
import { defineToolPlugin } from "openclaw/plugin-sdk/tool-plugin";
|
|
4226
4226
|
|
|
4227
|
+
// src/observer.ts
|
|
4228
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4229
|
+
|
|
4230
|
+
// src/evidence.ts
|
|
4231
|
+
import { createHash } from "node:crypto";
|
|
4232
|
+
import { appendFileSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4233
|
+
import { homedir } from "node:os";
|
|
4234
|
+
import { join } from "node:path";
|
|
4235
|
+
var GENESIS = "fidacy.observer.v1";
|
|
4236
|
+
var sha256 = (s) => createHash("sha256").update(s).digest("hex");
|
|
4237
|
+
function canonical(obj) {
|
|
4238
|
+
const keys = Object.keys(obj).sort();
|
|
4239
|
+
return "{" + keys.map((k) => `${JSON.stringify(k)}:${JSON.stringify(obj[k])}`).join(",") + "}";
|
|
4240
|
+
}
|
|
4241
|
+
function chainStep(prevHead, a) {
|
|
4242
|
+
return sha256(`${prevHead}|${sha256(canonical({ category: a.category, i: a.i, tool: a.tool, ts: a.ts }))}`);
|
|
4243
|
+
}
|
|
4244
|
+
function genesisHead() {
|
|
4245
|
+
return sha256(GENESIS);
|
|
4246
|
+
}
|
|
4247
|
+
function fidacyDir() {
|
|
4248
|
+
return join(process.env.FIDACY_HOME ?? join(homedir(), ".fidacy"), "sessions");
|
|
4249
|
+
}
|
|
4250
|
+
function writeSessionLog(sessionId, actions, head, meta) {
|
|
4251
|
+
try {
|
|
4252
|
+
const dir = fidacyDir();
|
|
4253
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4254
|
+
const safe = sessionId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80) || "session";
|
|
4255
|
+
const path = join(dir, `${safe}.json`);
|
|
4256
|
+
const body = {
|
|
4257
|
+
v: "fidacy.observer.v1",
|
|
4258
|
+
recipe: 'h_0 = sha256("fidacy.observer.v1"); h_i = sha256(h_prev + "|" + sha256(canonical(action)))',
|
|
4259
|
+
sessionId,
|
|
4260
|
+
head,
|
|
4261
|
+
...meta,
|
|
4262
|
+
actions
|
|
4263
|
+
};
|
|
4264
|
+
writeFileSync(path, JSON.stringify(body, null, 2), { mode: 384 });
|
|
4265
|
+
return path;
|
|
4266
|
+
} catch {
|
|
4267
|
+
return null;
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
function queuePending(head, sessionId, at, total = 0, byCategory = {}) {
|
|
4271
|
+
try {
|
|
4272
|
+
const dir = fidacyDir();
|
|
4273
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4274
|
+
appendFileSync(join(dir, "pending-anchors.jsonl"), JSON.stringify({ head, sessionId, at, total, byCategory }) + "\n", {
|
|
4275
|
+
mode: 384
|
|
4276
|
+
});
|
|
4277
|
+
} catch {
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
function takePending(limit = 50) {
|
|
4281
|
+
const dir = fidacyDir();
|
|
4282
|
+
const path = join(dir, "pending-anchors.jsonl");
|
|
4283
|
+
const taken = join(dir, "pending-anchors.taking");
|
|
4284
|
+
try {
|
|
4285
|
+
renameSync(path, taken);
|
|
4286
|
+
} catch {
|
|
4287
|
+
return [];
|
|
4288
|
+
}
|
|
4289
|
+
try {
|
|
4290
|
+
const lines = readFileSync(taken, "utf8").split("\n").filter(Boolean);
|
|
4291
|
+
const out = [];
|
|
4292
|
+
for (const line of lines.slice(-limit)) {
|
|
4293
|
+
try {
|
|
4294
|
+
const p = JSON.parse(line);
|
|
4295
|
+
if (typeof p.head === "string" && /^[0-9a-f]{64}$/.test(p.head)) out.push(p);
|
|
4296
|
+
} catch {
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
return out;
|
|
4300
|
+
} catch {
|
|
4301
|
+
return [];
|
|
4302
|
+
} finally {
|
|
4303
|
+
try {
|
|
4304
|
+
writeFileSync(taken, "");
|
|
4305
|
+
} catch {
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function verifyUrl(head) {
|
|
4310
|
+
return `https://fidacy.com/verify?sha256=${head}`;
|
|
4311
|
+
}
|
|
4312
|
+
var CODE = {
|
|
4313
|
+
money: "m",
|
|
4314
|
+
shell: "s",
|
|
4315
|
+
file: "f",
|
|
4316
|
+
network: "net",
|
|
4317
|
+
message: "msg",
|
|
4318
|
+
other: "o"
|
|
4319
|
+
};
|
|
4320
|
+
function sessionLabel(sessionId, total, byCategory) {
|
|
4321
|
+
const id = sessionId.replace(/[^A-Za-z0-9-]/g, "").slice(0, 8) || "unknown";
|
|
4322
|
+
const parts = [`session:${id}`, `n=${total}`];
|
|
4323
|
+
for (const [cat, code] of Object.entries(CODE)) {
|
|
4324
|
+
const n = byCategory[cat] ?? 0;
|
|
4325
|
+
if (n > 0) parts.push(`${code}=${n}`);
|
|
4326
|
+
}
|
|
4327
|
+
return parts.join(" ").slice(0, 120);
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
// src/observer.ts
|
|
4331
|
+
var EXACT = {
|
|
4332
|
+
// Fidacy's own money tools, so the report counts what we ourselves gated.
|
|
4333
|
+
request_payment: "money",
|
|
4334
|
+
assess_action: "money",
|
|
4335
|
+
// The common OpenClaw / agent-host tool vocabulary.
|
|
4336
|
+
bash: "shell",
|
|
4337
|
+
shell: "shell",
|
|
4338
|
+
exec: "shell",
|
|
4339
|
+
run_command: "shell",
|
|
4340
|
+
code_mode_exec: "shell",
|
|
4341
|
+
read: "file",
|
|
4342
|
+
read_file: "file",
|
|
4343
|
+
write: "file",
|
|
4344
|
+
write_file: "file",
|
|
4345
|
+
edit: "file",
|
|
4346
|
+
edit_file: "file",
|
|
4347
|
+
apply_patch: "file",
|
|
4348
|
+
glob: "file",
|
|
4349
|
+
grep: "file",
|
|
4350
|
+
ls: "file",
|
|
4351
|
+
fetch: "network",
|
|
4352
|
+
web_fetch: "network",
|
|
4353
|
+
web_search: "network",
|
|
4354
|
+
http_request: "network",
|
|
4355
|
+
browser: "network",
|
|
4356
|
+
send_message: "message",
|
|
4357
|
+
reply: "message",
|
|
4358
|
+
send_email: "message"
|
|
4359
|
+
};
|
|
4360
|
+
var FUZZY = [
|
|
4361
|
+
["payment", "money"],
|
|
4362
|
+
["invoice", "money"],
|
|
4363
|
+
["transfer", "money"],
|
|
4364
|
+
["charge", "money"],
|
|
4365
|
+
["file", "file"],
|
|
4366
|
+
["patch", "file"],
|
|
4367
|
+
["write", "file"],
|
|
4368
|
+
["read", "file"],
|
|
4369
|
+
["fetch", "network"],
|
|
4370
|
+
["http", "network"],
|
|
4371
|
+
["browser", "network"],
|
|
4372
|
+
["search", "network"],
|
|
4373
|
+
["exec", "shell"],
|
|
4374
|
+
["command", "shell"],
|
|
4375
|
+
["shell", "shell"],
|
|
4376
|
+
["mail", "message"],
|
|
4377
|
+
["message", "message"],
|
|
4378
|
+
["send", "message"]
|
|
4379
|
+
];
|
|
4380
|
+
function classify(toolName, derivedPaths) {
|
|
4381
|
+
const name = toolName.toLowerCase();
|
|
4382
|
+
const exact = EXACT[name];
|
|
4383
|
+
if (exact) return exact;
|
|
4384
|
+
if (derivedPaths && derivedPaths.length > 0) return "file";
|
|
4385
|
+
for (const [needle, category] of FUZZY) if (name.includes(needle)) return category;
|
|
4386
|
+
return "other";
|
|
4387
|
+
}
|
|
4388
|
+
function fingerprint(toolName, derivedPaths) {
|
|
4389
|
+
const target = derivedPaths && derivedPaths.length > 0 ? [...derivedPaths].sort().join("\0") : toolName;
|
|
4390
|
+
return createHash2("sha256").update(`${toolName}\0${target}`).digest("hex").slice(0, 16);
|
|
4391
|
+
}
|
|
4392
|
+
var ACTION_LOG_CAP = 5e3;
|
|
4393
|
+
function newLedger(sessionId, now) {
|
|
4394
|
+
return {
|
|
4395
|
+
sessionId,
|
|
4396
|
+
startedAt: now,
|
|
4397
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4398
|
+
total: 0,
|
|
4399
|
+
toolsSeen: /* @__PURE__ */ new Set(),
|
|
4400
|
+
head: genesisHead(),
|
|
4401
|
+
actions: [],
|
|
4402
|
+
truncated: false
|
|
4403
|
+
};
|
|
4404
|
+
}
|
|
4405
|
+
function record(ledger, toolName, derivedPaths, failed = false, at = Date.now()) {
|
|
4406
|
+
const category = classify(toolName, derivedPaths);
|
|
4407
|
+
let tally = ledger.byCategory.get(category);
|
|
4408
|
+
if (!tally) {
|
|
4409
|
+
tally = { calls: 0, failures: 0, targets: /* @__PURE__ */ new Set() };
|
|
4410
|
+
ledger.byCategory.set(category, tally);
|
|
4411
|
+
}
|
|
4412
|
+
tally.calls += 1;
|
|
4413
|
+
if (failed) tally.failures += 1;
|
|
4414
|
+
if (derivedPaths && derivedPaths.length > 0 && tally.targets.size < TARGET_CAP) {
|
|
4415
|
+
tally.targets.add(fingerprint(toolName, derivedPaths));
|
|
4416
|
+
}
|
|
4417
|
+
ledger.total += 1;
|
|
4418
|
+
if (ledger.toolsSeen.size < TOOLS_CAP) ledger.toolsSeen.add(toolName);
|
|
4419
|
+
const action = {
|
|
4420
|
+
i: ledger.total,
|
|
4421
|
+
category,
|
|
4422
|
+
tool: toolName,
|
|
4423
|
+
ts: new Date(at).toISOString()
|
|
4424
|
+
};
|
|
4425
|
+
ledger.head = chainStep(ledger.head, action);
|
|
4426
|
+
if (ledger.actions.length < ACTION_LOG_CAP) ledger.actions.push(action);
|
|
4427
|
+
else ledger.truncated = true;
|
|
4428
|
+
}
|
|
4429
|
+
var TARGET_CAP = 500;
|
|
4430
|
+
var TOOLS_CAP = 200;
|
|
4431
|
+
var ORDER = ["money", "shell", "file", "network", "message", "other"];
|
|
4432
|
+
var LABEL = {
|
|
4433
|
+
money: "moved money",
|
|
4434
|
+
shell: "ran commands",
|
|
4435
|
+
file: "touched files",
|
|
4436
|
+
network: "reached the network",
|
|
4437
|
+
message: "sent messages",
|
|
4438
|
+
other: "other"
|
|
4439
|
+
};
|
|
4440
|
+
function renderReport(ledger, now, activated, logPath) {
|
|
4441
|
+
if (ledger.total < MIN_ACTIONS_FOR_REPORT) return null;
|
|
4442
|
+
const minutes = Math.max(1, Math.round((now - ledger.startedAt) / 6e4));
|
|
4443
|
+
const lines = [];
|
|
4444
|
+
lines.push(
|
|
4445
|
+
`[fidacy] SESSION REPORT \xB7 ${ledger.total} agent actions watched over ${minutes} min \xB7 nothing left this machine`
|
|
4446
|
+
);
|
|
4447
|
+
for (const category of ORDER) {
|
|
4448
|
+
const tally = ledger.byCategory.get(category);
|
|
4449
|
+
if (!tally) continue;
|
|
4450
|
+
const capped = tally.targets.size >= TARGET_CAP ? "+" : "";
|
|
4451
|
+
const parts = [`${tally.calls} ${tally.calls === 1 ? "call" : "calls"}`];
|
|
4452
|
+
if (tally.targets.size > 0) parts.push(`${tally.targets.size}${capped} distinct`);
|
|
4453
|
+
if (tally.failures > 0) parts.push(`${tally.failures} failed`);
|
|
4454
|
+
lines.push(`[fidacy] ${LABEL[category].padEnd(20)} ${parts.join(" \xB7 ")}`);
|
|
4455
|
+
}
|
|
4456
|
+
const money2 = ledger.byCategory.get("money");
|
|
4457
|
+
if (!money2) {
|
|
4458
|
+
lines.push(`[fidacy] no money-moving action was attempted \xB7 the firewall stayed armed and idle`);
|
|
4459
|
+
}
|
|
4460
|
+
lines.push(`[fidacy] digest ${ledger.head}`);
|
|
4461
|
+
if (logPath) lines.push(`[fidacy] log ${logPath}${ledger.truncated ? " (partial: action cap reached)" : ""}`);
|
|
4462
|
+
lines.push(
|
|
4463
|
+
activated ? `[fidacy] anchored to the audit chain \xB7 verify: ${verifyUrl(ledger.head)}` : `[fidacy] activate free to anchor this digest so nobody can rewrite it later: https://fidacy.com/claim`
|
|
4464
|
+
);
|
|
4465
|
+
return lines.join("\n");
|
|
4466
|
+
}
|
|
4467
|
+
var MIN_ACTIONS_FOR_REPORT = 5;
|
|
4468
|
+
function installObserver(api, emit, isActivated, now = Date.now, anchor) {
|
|
4469
|
+
if (typeof api?.on !== "function") return;
|
|
4470
|
+
const ledgers = /* @__PURE__ */ new Map();
|
|
4471
|
+
const LEDGER_CAP = 50;
|
|
4472
|
+
const ledgerFor = (sessionId) => {
|
|
4473
|
+
let ledger = ledgers.get(sessionId);
|
|
4474
|
+
if (!ledger) {
|
|
4475
|
+
if (ledgers.size >= LEDGER_CAP) ledgers.delete(ledgers.keys().next().value);
|
|
4476
|
+
ledger = newLedger(sessionId, now());
|
|
4477
|
+
ledgers.set(sessionId, ledger);
|
|
4478
|
+
}
|
|
4479
|
+
return ledger;
|
|
4480
|
+
};
|
|
4481
|
+
api.on(
|
|
4482
|
+
"before_tool_call",
|
|
4483
|
+
(event, ctx) => {
|
|
4484
|
+
try {
|
|
4485
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
4486
|
+
const paths = Array.isArray(event.derivedPaths) ? event.derivedPaths : void 0;
|
|
4487
|
+
const sessionId = sessionIdOf(ctx);
|
|
4488
|
+
record(ledgerFor(sessionId), toolName, paths, false);
|
|
4489
|
+
} catch {
|
|
4490
|
+
}
|
|
4491
|
+
},
|
|
4492
|
+
// Lowest priority on purpose: anything that actually decides should run first,
|
|
4493
|
+
// and we only want to count what the host was really about to do.
|
|
4494
|
+
{ priority: 1 }
|
|
4495
|
+
);
|
|
4496
|
+
api.on("session_end", async (event, ctx) => {
|
|
4497
|
+
try {
|
|
4498
|
+
const sessionId = sessionIdOf(ctx) || (typeof event.sessionId === "string" ? event.sessionId : "");
|
|
4499
|
+
const ledger = ledgers.get(sessionId);
|
|
4500
|
+
if (!ledger) return;
|
|
4501
|
+
ledgers.delete(sessionId);
|
|
4502
|
+
if (ledger.total < MIN_ACTIONS_FOR_REPORT) return;
|
|
4503
|
+
const logPath = persist(ledger, now());
|
|
4504
|
+
let anchored = false;
|
|
4505
|
+
if (anchor) {
|
|
4506
|
+
anchored = await anchor(ledger.head, ledger.sessionId, rollup(ledger)).catch(() => false);
|
|
4507
|
+
}
|
|
4508
|
+
if (!anchored) queuePending(ledger.head, ledger.sessionId, now(), ledger.total, rollup(ledger));
|
|
4509
|
+
const report = renderReport(ledger, now(), anchored, logPath);
|
|
4510
|
+
if (report) emit(report);
|
|
4511
|
+
} catch {
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
api.on("gateway_stop", () => drain());
|
|
4515
|
+
process.on("exit", () => drain());
|
|
4516
|
+
function drain() {
|
|
4517
|
+
try {
|
|
4518
|
+
for (const [id, ledger] of ledgers) {
|
|
4519
|
+
ledgers.delete(id);
|
|
4520
|
+
if (ledger.total < MIN_ACTIONS_FOR_REPORT) continue;
|
|
4521
|
+
const logPath = persist(ledger, now());
|
|
4522
|
+
queuePending(ledger.head, ledger.sessionId, now(), ledger.total, rollup(ledger));
|
|
4523
|
+
const report = renderReport(ledger, now(), false, logPath);
|
|
4524
|
+
if (report) emit(report);
|
|
4525
|
+
}
|
|
4526
|
+
} catch {
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
function rollup(ledger) {
|
|
4530
|
+
const out = {};
|
|
4531
|
+
for (const [cat, tally] of ledger.byCategory) out[cat] = tally.calls;
|
|
4532
|
+
return out;
|
|
4533
|
+
}
|
|
4534
|
+
function persist(ledger, at) {
|
|
4535
|
+
return writeSessionLog(ledger.sessionId, ledger.actions, ledger.head, {
|
|
4536
|
+
startedAt: new Date(ledger.startedAt).toISOString(),
|
|
4537
|
+
endedAt: new Date(at).toISOString(),
|
|
4538
|
+
totalActions: ledger.total,
|
|
4539
|
+
truncated: ledger.truncated,
|
|
4540
|
+
byCategory: Object.fromEntries(
|
|
4541
|
+
[...ledger.byCategory].map(([k, v]) => [k, { calls: v.calls, failures: v.failures, distinct: v.targets.size }])
|
|
4542
|
+
)
|
|
4543
|
+
});
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
function sessionIdOf(ctx) {
|
|
4547
|
+
if (!ctx) return "default";
|
|
4548
|
+
const id = ctx.sessionId ?? ctx.sessionKey;
|
|
4549
|
+
return typeof id === "string" && id.length > 0 ? id : "default";
|
|
4550
|
+
}
|
|
4551
|
+
|
|
4227
4552
|
// ../firewall/dist/util.js
|
|
4228
4553
|
function stableStringify(obj) {
|
|
4229
4554
|
if (obj === null || typeof obj !== "object")
|
|
@@ -4253,7 +4578,7 @@ function publicKeyPem(publicKey) {
|
|
|
4253
4578
|
function sign(privateKey, message) {
|
|
4254
4579
|
return crypto.sign(null, Buffer.from(message, "utf8"), privateKey).toString("base64url");
|
|
4255
4580
|
}
|
|
4256
|
-
function
|
|
4581
|
+
function sha2562(input) {
|
|
4257
4582
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
4258
4583
|
}
|
|
4259
4584
|
|
|
@@ -4482,10 +4807,10 @@ var DevFidacyCore = class {
|
|
|
4482
4807
|
return decision;
|
|
4483
4808
|
}
|
|
4484
4809
|
async getProof(decisionId) {
|
|
4485
|
-
const
|
|
4486
|
-
if (!
|
|
4810
|
+
const record3 = this.store.find(decisionId);
|
|
4811
|
+
if (!record3)
|
|
4487
4812
|
return null;
|
|
4488
|
-
return { record:
|
|
4813
|
+
return { record: record3, chainIntact: this.store.intact(), verifiedAgainstPublicKey: this.pubPem };
|
|
4489
4814
|
}
|
|
4490
4815
|
async history(limit = 50) {
|
|
4491
4816
|
const all = this.store.records();
|
|
@@ -4588,36 +4913,36 @@ var FileAuditStore = class {
|
|
|
4588
4913
|
const prevHash = this.head();
|
|
4589
4914
|
const seq = this.chain.length;
|
|
4590
4915
|
const ts2 = decision.ts;
|
|
4591
|
-
const digest =
|
|
4592
|
-
const hash =
|
|
4593
|
-
const
|
|
4916
|
+
const digest = sha2562(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
|
|
4917
|
+
const hash = sha2562(`${prevHash}|${digest}|${seq}|${ts2}`);
|
|
4918
|
+
const record3 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts: ts2 };
|
|
4594
4919
|
if (typeof decision.request?.purpose === "string" && decision.request.purpose.trim()) {
|
|
4595
|
-
|
|
4920
|
+
record3.purpose = decision.request.purpose.slice(0, 500);
|
|
4596
4921
|
}
|
|
4597
4922
|
if (typeof decision.request?.payee === "string" && decision.request.payee.trim()) {
|
|
4598
|
-
|
|
4923
|
+
record3.payee = decision.request.payee.slice(0, 200);
|
|
4599
4924
|
}
|
|
4600
4925
|
if (decision.status === "ALLOW") {
|
|
4601
4926
|
const req = decision.request;
|
|
4602
4927
|
if (typeof req?.amount === "number" && Number.isFinite(req.amount))
|
|
4603
|
-
|
|
4928
|
+
record3.amount = req.amount;
|
|
4604
4929
|
if (typeof req?.currency === "string")
|
|
4605
|
-
|
|
4930
|
+
record3.currency = req.currency;
|
|
4606
4931
|
const invoice = canonInvoice(req?.invoiceRef);
|
|
4607
4932
|
if (invoice)
|
|
4608
|
-
|
|
4933
|
+
record3.invoiceRef = invoice;
|
|
4609
4934
|
} else {
|
|
4610
4935
|
if (decision.violatedRule)
|
|
4611
|
-
|
|
4936
|
+
record3.violatedRule = decision.violatedRule;
|
|
4612
4937
|
const req = decision.request;
|
|
4613
4938
|
if (typeof req?.amount === "number" && Number.isFinite(req.amount))
|
|
4614
|
-
|
|
4939
|
+
record3.amount = req.amount;
|
|
4615
4940
|
if (typeof req?.currency === "string")
|
|
4616
|
-
|
|
4941
|
+
record3.currency = req.currency;
|
|
4617
4942
|
}
|
|
4618
|
-
fs.appendFileSync(this.path, JSON.stringify(
|
|
4619
|
-
this.chain.push(
|
|
4620
|
-
return
|
|
4943
|
+
fs.appendFileSync(this.path, JSON.stringify(record3) + "\n");
|
|
4944
|
+
this.chain.push(record3);
|
|
4945
|
+
return record3;
|
|
4621
4946
|
}
|
|
4622
4947
|
find(decisionId) {
|
|
4623
4948
|
return this.chain.find((r) => r.decisionId === decisionId);
|
|
@@ -4629,7 +4954,7 @@ var FileAuditStore = class {
|
|
|
4629
4954
|
intact() {
|
|
4630
4955
|
let prev = "GENESIS";
|
|
4631
4956
|
for (const r of this.chain) {
|
|
4632
|
-
const expected =
|
|
4957
|
+
const expected = sha2562(`${prev}|${r.digest}|${r.seq}|${r.ts}`);
|
|
4633
4958
|
if (expected !== r.hash || r.prevHash !== prev)
|
|
4634
4959
|
return false;
|
|
4635
4960
|
prev = r.hash;
|
|
@@ -4643,37 +4968,37 @@ import { statSync } from "node:fs";
|
|
|
4643
4968
|
|
|
4644
4969
|
// ../mcp/src/config.ts
|
|
4645
4970
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4646
|
-
import { homedir } from "node:os";
|
|
4647
|
-
import { join } from "node:path";
|
|
4971
|
+
import { homedir as homedir2 } from "node:os";
|
|
4972
|
+
import { join as join2 } from "node:path";
|
|
4648
4973
|
import {
|
|
4649
4974
|
existsSync,
|
|
4650
|
-
mkdirSync,
|
|
4651
|
-
readFileSync,
|
|
4652
|
-
writeFileSync
|
|
4975
|
+
mkdirSync as mkdirSync2,
|
|
4976
|
+
readFileSync as readFileSync2,
|
|
4977
|
+
writeFileSync as writeFileSync2
|
|
4653
4978
|
} from "node:fs";
|
|
4654
4979
|
function hasEngineKey(keyOverride) {
|
|
4655
4980
|
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
4656
4981
|
}
|
|
4657
4982
|
function configDir() {
|
|
4658
|
-
return process.env.FIDACY_CONFIG_DIR ??
|
|
4983
|
+
return process.env.FIDACY_CONFIG_DIR ?? join2(homedir2(), ".fidacy");
|
|
4659
4984
|
}
|
|
4660
4985
|
function configPath() {
|
|
4661
|
-
return
|
|
4986
|
+
return join2(configDir(), "config.json");
|
|
4662
4987
|
}
|
|
4663
4988
|
function auditLogPath() {
|
|
4664
4989
|
if (process.env.FIDACY_AUDIT_PATH) return process.env.FIDACY_AUDIT_PATH;
|
|
4665
|
-
const dir =
|
|
4990
|
+
const dir = join2(configDir(), "audit");
|
|
4666
4991
|
try {
|
|
4667
|
-
|
|
4992
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
4668
4993
|
} catch {
|
|
4669
4994
|
}
|
|
4670
|
-
return
|
|
4995
|
+
return join2(dir, "audit.log");
|
|
4671
4996
|
}
|
|
4672
4997
|
function readConfig() {
|
|
4673
4998
|
const p = configPath();
|
|
4674
4999
|
if (!existsSync(p)) return null;
|
|
4675
5000
|
try {
|
|
4676
|
-
const raw = JSON.parse(
|
|
5001
|
+
const raw = JSON.parse(readFileSync2(p, "utf8"));
|
|
4677
5002
|
if (!raw || typeof raw.anon_id !== "string") return null;
|
|
4678
5003
|
return {
|
|
4679
5004
|
anon_id: raw.anon_id,
|
|
@@ -4695,8 +5020,8 @@ function readConfig() {
|
|
|
4695
5020
|
}
|
|
4696
5021
|
function writeConfig(cfg) {
|
|
4697
5022
|
const dir = configDir();
|
|
4698
|
-
|
|
4699
|
-
|
|
5023
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
5024
|
+
writeFileSync2(configPath(), JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
4700
5025
|
}
|
|
4701
5026
|
function ensureState() {
|
|
4702
5027
|
const existing = readConfig();
|
|
@@ -4741,7 +5066,7 @@ function resolveMandateRules(cfg) {
|
|
|
4741
5066
|
}
|
|
4742
5067
|
|
|
4743
5068
|
// ../mcp/src/telemetry.ts
|
|
4744
|
-
var CLIENT_VERSION = true ? "0.
|
|
5069
|
+
var CLIENT_VERSION = true ? "0.5.1" : "dev";
|
|
4745
5070
|
function bandOf(amount) {
|
|
4746
5071
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
4747
5072
|
if (amount < 10) return "lt10";
|
|
@@ -4800,7 +5125,7 @@ function resultOf(status, violatedRule) {
|
|
|
4800
5125
|
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
4801
5126
|
return "deny_scope";
|
|
4802
5127
|
}
|
|
4803
|
-
function
|
|
5128
|
+
function record2(type, result, band, capRatio, action) {
|
|
4804
5129
|
if (!telemetryEnabled()) return;
|
|
4805
5130
|
armExitFlush();
|
|
4806
5131
|
buffer.push({
|
|
@@ -4820,10 +5145,10 @@ function record(type, result, band, capRatio, action) {
|
|
|
4820
5145
|
if (typeof flushTimer.unref === "function") flushTimer.unref();
|
|
4821
5146
|
}
|
|
4822
5147
|
}
|
|
4823
|
-
var recordInstall = () =>
|
|
4824
|
-
var recordAgentActive = () =>
|
|
4825
|
-
var recordDecision = (status, violatedRule, amount, perTxMax) =>
|
|
4826
|
-
var recordUpgradeIntent = () =>
|
|
5148
|
+
var recordInstall = () => record2("install");
|
|
5149
|
+
var recordAgentActive = () => record2("agent_active");
|
|
5150
|
+
var recordDecision = (status, violatedRule, amount, perTxMax) => record2("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
|
|
5151
|
+
var recordUpgradeIntent = () => record2("upgrade_intent");
|
|
4827
5152
|
async function flush() {
|
|
4828
5153
|
if (flushTimer) {
|
|
4829
5154
|
clearTimeout(flushTimer);
|
|
@@ -4929,7 +5254,7 @@ function requestUpgrade() {
|
|
|
4929
5254
|
}
|
|
4930
5255
|
|
|
4931
5256
|
// ../mcp/src/provision.ts
|
|
4932
|
-
var CLIENT_VERSION2 = true ? "0.
|
|
5257
|
+
var CLIENT_VERSION2 = true ? "0.5.1" : "dev";
|
|
4933
5258
|
function provisionEnabled() {
|
|
4934
5259
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
4935
5260
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -5071,7 +5396,7 @@ function trialCountdownLine(keyOverride) {
|
|
|
5071
5396
|
}
|
|
5072
5397
|
|
|
5073
5398
|
// ../mcp/src/register.ts
|
|
5074
|
-
var CLIENT_VERSION3 = true ? "0.
|
|
5399
|
+
var CLIENT_VERSION3 = true ? "0.5.1" : "dev";
|
|
5075
5400
|
function endpoint3() {
|
|
5076
5401
|
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
5077
5402
|
return `${base}/v1/register`;
|
|
@@ -5524,7 +5849,7 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
|
|
|
5524
5849
|
}
|
|
5525
5850
|
|
|
5526
5851
|
// ../mcp/src/artifacts.ts
|
|
5527
|
-
import { createHash } from "node:crypto";
|
|
5852
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5528
5853
|
import { createReadStream } from "node:fs";
|
|
5529
5854
|
import { pipeline } from "node:stream/promises";
|
|
5530
5855
|
var ARTIFACT_KINDS = [
|
|
@@ -5540,7 +5865,7 @@ var ARTIFACT_KINDS = [
|
|
|
5540
5865
|
"custom"
|
|
5541
5866
|
];
|
|
5542
5867
|
async function hashFile(path) {
|
|
5543
|
-
const hash =
|
|
5868
|
+
const hash = createHash3("sha256");
|
|
5544
5869
|
await pipeline(createReadStream(path), async function* (source) {
|
|
5545
5870
|
for await (const chunk of source) {
|
|
5546
5871
|
hash.update(chunk);
|
|
@@ -5584,8 +5909,8 @@ async function engineFetch(method, pathAndQuery, cfg, body) {
|
|
|
5584
5909
|
async function anchorArtifact(params, cfg) {
|
|
5585
5910
|
return await engineFetch("POST", "/v1/artifacts", cfg, params);
|
|
5586
5911
|
}
|
|
5587
|
-
async function findArtifacts(
|
|
5588
|
-
return await engineFetch("GET", `/v1/artifacts?sha256=${
|
|
5912
|
+
async function findArtifacts(sha2563, cfg) {
|
|
5913
|
+
return await engineFetch("GET", `/v1/artifacts?sha256=${sha2563}`, cfg);
|
|
5589
5914
|
}
|
|
5590
5915
|
|
|
5591
5916
|
// src/index.ts
|
|
@@ -5618,7 +5943,7 @@ function maybeRegisterOperator(config) {
|
|
|
5618
5943
|
});
|
|
5619
5944
|
}
|
|
5620
5945
|
var ASSESS_KINDS = ["ap2_payment", "message_send", "voice_call", "custom", "claim_document"];
|
|
5621
|
-
var
|
|
5946
|
+
var entry = defineToolPlugin({
|
|
5622
5947
|
id: "fidacy",
|
|
5623
5948
|
name: "Fidacy \u2014 Payment Firewall",
|
|
5624
5949
|
description: "A signed, independently-verifiable verdict on every money-moving agent action. Blocks wrong/lookalike payee, over-cap, and duplicate-invoice fraud before money moves. Non-custodial, local-first, deny-by-default.",
|
|
@@ -5800,12 +6125,12 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
|
|
|
5800
6125
|
async execute(params) {
|
|
5801
6126
|
const c = boot();
|
|
5802
6127
|
const records = await c.history(5e3);
|
|
5803
|
-
const
|
|
5804
|
-
if (!
|
|
6128
|
+
const record3 = records.find((r) => r.decisionId === params.decisionId);
|
|
6129
|
+
if (!record3) throw new Error(`No decision ${params.decisionId} on this chain. Run list_decisions to see the ids that exist here.`);
|
|
5805
6130
|
const proof = await c.getProof(params.decisionId);
|
|
5806
6131
|
return {
|
|
5807
|
-
record:
|
|
5808
|
-
explanation: explainRule(
|
|
6132
|
+
record: record3,
|
|
6133
|
+
explanation: explainRule(record3.violatedRule, record3.payee),
|
|
5809
6134
|
chainIntact: proof?.chainIntact ?? null
|
|
5810
6135
|
};
|
|
5811
6136
|
}
|
|
@@ -5983,6 +6308,82 @@ Your local protection keeps working meanwhile; your usage history migrates to th
|
|
|
5983
6308
|
];
|
|
5984
6309
|
}
|
|
5985
6310
|
});
|
|
6311
|
+
function machineSubject() {
|
|
6312
|
+
try {
|
|
6313
|
+
const id = ensureState().config.anon_id;
|
|
6314
|
+
return id ? `agent:${String(id).slice(0, 8)}` : "agent:unknown";
|
|
6315
|
+
} catch {
|
|
6316
|
+
return "agent:unknown";
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
var registerTools = entry.register;
|
|
6320
|
+
entry.register = (api) => {
|
|
6321
|
+
registerTools(api);
|
|
6322
|
+
const creds = () => {
|
|
6323
|
+
try {
|
|
6324
|
+
const cfg = ensureState().config;
|
|
6325
|
+
const apiKey = (cfg.api_key || process.env.FIDACY_ENGINE_API_KEY || "").trim();
|
|
6326
|
+
if (!apiKey) return null;
|
|
6327
|
+
return { engineUrl: process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com", apiKey };
|
|
6328
|
+
} catch {
|
|
6329
|
+
return null;
|
|
6330
|
+
}
|
|
6331
|
+
};
|
|
6332
|
+
const anchor = async (head, sessionId, byCategory = {}) => {
|
|
6333
|
+
const cfg = creds();
|
|
6334
|
+
if (!cfg) return false;
|
|
6335
|
+
try {
|
|
6336
|
+
const total = Object.values(byCategory).reduce((a, b) => a + b, 0);
|
|
6337
|
+
await anchorArtifact(
|
|
6338
|
+
{
|
|
6339
|
+
sha256: head,
|
|
6340
|
+
kind: "custom",
|
|
6341
|
+
label: sessionLabel(sessionId, total, byCategory),
|
|
6342
|
+
// Which install produced the session, so a fleet view can group by
|
|
6343
|
+
// machine. The anon id is a random UUID minted on this machine and
|
|
6344
|
+
// tied to no person, which is the point: the cockpit answers "which
|
|
6345
|
+
// agent" without ever learning "which human".
|
|
6346
|
+
subject: machineSubject()
|
|
6347
|
+
},
|
|
6348
|
+
cfg
|
|
6349
|
+
);
|
|
6350
|
+
return true;
|
|
6351
|
+
} catch {
|
|
6352
|
+
return false;
|
|
6353
|
+
}
|
|
6354
|
+
};
|
|
6355
|
+
installObserver(
|
|
6356
|
+
api,
|
|
6357
|
+
// Same channel as the boot banner: stderr is where the human operator looks,
|
|
6358
|
+
// and stdout belongs to the MCP/JSON-RPC framing in the other shell.
|
|
6359
|
+
(line) => console.error(line),
|
|
6360
|
+
() => creds() !== null,
|
|
6361
|
+
Date.now,
|
|
6362
|
+
anchor
|
|
6363
|
+
);
|
|
6364
|
+
let drained = false;
|
|
6365
|
+
if (typeof api?.on !== "function") return;
|
|
6366
|
+
api.on(
|
|
6367
|
+
"before_tool_call",
|
|
6368
|
+
() => {
|
|
6369
|
+
if (drained) return;
|
|
6370
|
+
drained = true;
|
|
6371
|
+
void (async () => {
|
|
6372
|
+
if (!creds()) return;
|
|
6373
|
+
for (const p of takePending()) {
|
|
6374
|
+
if (!await anchor(p.head, p.sessionId, p.byCategory ?? {})) {
|
|
6375
|
+
queuePending(p.head, p.sessionId, p.at, p.total ?? 0, p.byCategory ?? {});
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
})().catch(() => {
|
|
6379
|
+
});
|
|
6380
|
+
},
|
|
6381
|
+
// Below the observer, which is itself the lowest: catch-up must never sit in
|
|
6382
|
+
// front of anything that decides, and must never be what a tool call waits on.
|
|
6383
|
+
{ priority: 0 }
|
|
6384
|
+
);
|
|
6385
|
+
};
|
|
6386
|
+
var index_default = entry;
|
|
5986
6387
|
export {
|
|
5987
6388
|
index_default as default
|
|
5988
6389
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Fidacy AI Agent Firewall",
|
|
4
4
|
"description": "A signed, independently-verifiable verdict on every money-moving agent action. Blocks wrong/lookalike payee, over-cap, and duplicate-invoice fraud before money moves. Non-custodial, local-first, deny-by-default.",
|
|
5
5
|
"icon": "https://fidacy.com/logo.png",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.1",
|
|
7
7
|
"contracts": {
|
|
8
8
|
"tools": [
|
|
9
9
|
"request_payment",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidacy/openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Fidacy payment firewall as a native OpenClaw plugin: signed, verifiable verdicts on every money-moving agent action, in-process (no MCP subprocess).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",
|