@fidacy/openclaw-plugin 0.2.11 → 0.4.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/dist/index.js +138 -6
- package/openclaw.plugin.json +10 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4684,7 +4684,10 @@ function readConfig() {
|
|
|
4684
4684
|
// reset every once-per-install nudge into an every-time nag.
|
|
4685
4685
|
created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
|
|
4686
4686
|
nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
|
|
4687
|
-
decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0
|
|
4687
|
+
decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
|
|
4688
|
+
hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
|
|
4689
|
+
operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
|
|
4690
|
+
registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
|
|
4688
4691
|
};
|
|
4689
4692
|
} catch {
|
|
4690
4693
|
return null;
|
|
@@ -4736,7 +4739,7 @@ function resolveMandateRules(cfg) {
|
|
|
4736
4739
|
}
|
|
4737
4740
|
|
|
4738
4741
|
// ../mcp/src/telemetry.ts
|
|
4739
|
-
var CLIENT_VERSION = true ? "0.
|
|
4742
|
+
var CLIENT_VERSION = true ? "0.4.0" : "dev";
|
|
4740
4743
|
function bandOf(amount) {
|
|
4741
4744
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
4742
4745
|
if (amount < 10) return "lt10";
|
|
@@ -4924,7 +4927,7 @@ function requestUpgrade() {
|
|
|
4924
4927
|
}
|
|
4925
4928
|
|
|
4926
4929
|
// ../mcp/src/provision.ts
|
|
4927
|
-
var CLIENT_VERSION2 = true ? "0.
|
|
4930
|
+
var CLIENT_VERSION2 = true ? "0.4.0" : "dev";
|
|
4928
4931
|
function provisionEnabled() {
|
|
4929
4932
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
4930
4933
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -5014,6 +5017,20 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
|
|
|
5014
5017
|
}
|
|
5015
5018
|
return null;
|
|
5016
5019
|
}
|
|
5020
|
+
function hostedWallCta(feature, billing) {
|
|
5021
|
+
const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
|
|
5022
|
+
if (hasEngineKey()) {
|
|
5023
|
+
const where2 = billing?.billingUrl ?? "https://app.fidacy.com/billing";
|
|
5024
|
+
const what = billing?.reason === "spending_cap_reached" ? `This account hit the monthly spending cap you set, so ${noun} are paused.` : billing?.detail ?? `This account's included ${noun} for the month are used up.`;
|
|
5025
|
+
return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where2}. Pay-as-you-go needs no plan and starts at $0.04 per verdict, dropping to $0.025 past 50k and $0.015 past 250k; committed plans price below that curve.`;
|
|
5026
|
+
}
|
|
5027
|
+
const url = claimUrl();
|
|
5028
|
+
const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
|
|
5029
|
+
if (nudgeOnce("hosted_wall")) {
|
|
5030
|
+
return `You've used this install's free allowance for ${noun} \u2014 the proofs anyone can re-verify at fidacy.com/verify, court-grade, not a local-only check. Keep them flowing: for your operator, ${cta}.`;
|
|
5031
|
+
}
|
|
5032
|
+
return `Free allowance for ${noun} reached. For your operator: ${cta}.`;
|
|
5033
|
+
}
|
|
5017
5034
|
function noKeyCta() {
|
|
5018
5035
|
const url = claimUrl();
|
|
5019
5036
|
return url ? `free in ~1 min for your operator: open ${url} (this install's history migrates), or set FIDACY_ENGINE_API_KEY if you already have one` : `free in ~1 min: call the upgrade tool (your history migrates), or set FIDACY_ENGINE_API_KEY if you already have one`;
|
|
@@ -5051,6 +5068,90 @@ function trialCountdownLine(keyOverride) {
|
|
|
5051
5068
|
return ` [fidacy] ${left} free anonymous decision${left === 1 ? "" : "s"} left on this install before the firewall requires activation. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
|
|
5052
5069
|
}
|
|
5053
5070
|
|
|
5071
|
+
// ../mcp/src/register.ts
|
|
5072
|
+
var CLIENT_VERSION3 = true ? "0.4.0" : "dev";
|
|
5073
|
+
function endpoint3() {
|
|
5074
|
+
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
5075
|
+
return `${base}/v1/register`;
|
|
5076
|
+
}
|
|
5077
|
+
function looksLikeEmail(s) {
|
|
5078
|
+
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
|
|
5079
|
+
}
|
|
5080
|
+
async function registerEmail(email, shell = "mcp") {
|
|
5081
|
+
const cfg = readConfig();
|
|
5082
|
+
if (!cfg) return "skipped";
|
|
5083
|
+
const clean = email.trim();
|
|
5084
|
+
if (!looksLikeEmail(clean)) return "skipped";
|
|
5085
|
+
if (cfg.registered_email && cfg.registered_email.toLowerCase() === clean.toLowerCase()) return "skipped";
|
|
5086
|
+
try {
|
|
5087
|
+
const res = await fetch(endpoint3(), {
|
|
5088
|
+
method: "POST",
|
|
5089
|
+
headers: { "content-type": "application/json" },
|
|
5090
|
+
body: JSON.stringify({ anon_id: cfg.anon_id, email: clean, client_version: CLIENT_VERSION3, shell })
|
|
5091
|
+
});
|
|
5092
|
+
if (res.status === 429) return "rate_limited";
|
|
5093
|
+
if (!res.ok) return "failed";
|
|
5094
|
+
const fresh = readConfig() ?? cfg;
|
|
5095
|
+
writeConfig({ ...fresh, operator_email: clean, registered_email: clean });
|
|
5096
|
+
return "registered";
|
|
5097
|
+
} catch {
|
|
5098
|
+
return "failed";
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
|
|
5102
|
+
// ../mcp/src/protection.ts
|
|
5103
|
+
var BRAND = "Fidacy AI Agent Firewall";
|
|
5104
|
+
var FREE_MONTHLY_VERDICTS = 300;
|
|
5105
|
+
function markHostedLapsed() {
|
|
5106
|
+
const cfg = readConfig();
|
|
5107
|
+
if (!cfg || cfg.hosted_lapsed_at) return;
|
|
5108
|
+
cfg.hosted_lapsed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5109
|
+
try {
|
|
5110
|
+
writeConfig(cfg);
|
|
5111
|
+
} catch {
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
function clearHostedLapsed() {
|
|
5115
|
+
const cfg = readConfig();
|
|
5116
|
+
if (!cfg?.hosted_lapsed_at) return;
|
|
5117
|
+
delete cfg.hosted_lapsed_at;
|
|
5118
|
+
try {
|
|
5119
|
+
writeConfig(cfg);
|
|
5120
|
+
} catch {
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
function isHostedLapsed() {
|
|
5124
|
+
return Boolean(readConfig()?.hosted_lapsed_at);
|
|
5125
|
+
}
|
|
5126
|
+
function protectionState(decisionsUsed2, keyOverride) {
|
|
5127
|
+
if (hasEngineKey(keyOverride)) return isHostedLapsed() ? "unproven" : "protected_keyed";
|
|
5128
|
+
if (decisionsUsed2 >= FREE_DECISIONS) return "halted";
|
|
5129
|
+
return FREE_DECISIONS - decisionsUsed2 <= 5 ? "protected_low" : "protected_trial";
|
|
5130
|
+
}
|
|
5131
|
+
function where() {
|
|
5132
|
+
return claimUrl() ?? "https://fidacy.com/claim";
|
|
5133
|
+
}
|
|
5134
|
+
function protectionBadge() {
|
|
5135
|
+
return `\u{1F6E1} ${BRAND} blocked this payment. Your agent was protected.`;
|
|
5136
|
+
}
|
|
5137
|
+
function protectionWarning(decisionsUsed2, keyOverride) {
|
|
5138
|
+
const state = protectionState(decisionsUsed2, keyOverride);
|
|
5139
|
+
const left = Math.max(0, FREE_DECISIONS - decisionsUsed2);
|
|
5140
|
+
switch (state) {
|
|
5141
|
+
case "protected_trial":
|
|
5142
|
+
return null;
|
|
5143
|
+
// working and nothing is expiring soon: silence is the respect.
|
|
5144
|
+
case "protected_low":
|
|
5145
|
+
return `\u26A0 ${BRAND}: ${left} free decision${left === 1 ? "" : "s"} left, then this agent's payments STOP DEAD. Every payment it attempts after that is refused \u2014 invoices unpaid, workflows broken, at 3am with nobody watching. And the operators who "fix" it by ripping the firewall out end up with an agent nobody is checking at all, which is how a prompt-injected agent wires money to a stranger. Keep it alive, free, in ~2 minutes: ${where()} \u2014 no card, this install's block history moves with you, plus ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
5146
|
+
case "halted":
|
|
5147
|
+
return `\u26D4 ${BRAND} IS NOT ACTIVATED \u2014 YOUR AGENT CAN NO LONGER PAY ANYTHING. Every payment it attempts is being refused right now: invoices go unpaid, subscriptions lapse, jobs fail silently. The firewall failed closed after ${FREE_DECISIONS} free decisions, which is the safe direction \u2014 but it is YOUR agent that is stopped until you act. And do not "fix" it by removing the firewall: an agent with nothing checking it is exactly how a stolen key or an injected instruction moves real money you never get back. Activate free in ~2 minutes at ${where()}: no card, your block history moves with you, ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
5148
|
+
case "unproven":
|
|
5149
|
+
return `\u26D4 ${BRAND}: YOUR AGENT IS EXPOSED RIGHT NOW. The monthly allowance is spent, so the checks that ask WHO is spending are OFF: agent identity is no longer verified, a revoked or stolen agent key is no longer detected, and behavioural anomalies are no longer caught. Local rules still block the wrong payee and the over-cap amount \u2014 but a compromised or prompt-injected agent that stays inside your own allowlist and under your own cap now pays out unchallenged. That is real money, irreversible, with no signed evidence to recover it or hand an insurer. Turn the checks back on: https://app.fidacy.com/billing \u2014 pay-as-you-go, no plan, from $0.04 per payment checked. One prevented payment pays for years of it.`;
|
|
5150
|
+
case "protected_keyed":
|
|
5151
|
+
return null;
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5054
5155
|
// ../mcp/src/reporting.ts
|
|
5055
5156
|
function withinDays(records, days, now = Date.now()) {
|
|
5056
5157
|
if (!Number.isFinite(days) || days <= 0) return [...records];
|
|
@@ -5456,6 +5557,11 @@ function boot() {
|
|
|
5456
5557
|
function subjectOf(config) {
|
|
5457
5558
|
return config.subject ?? process.env.FIDACY_SUBJECT ?? "agent:demo";
|
|
5458
5559
|
}
|
|
5560
|
+
function maybeRegisterOperator(config) {
|
|
5561
|
+
const email = (config.operatorEmail ?? process.env.FIDACY_OPERATOR_EMAIL ?? "").trim();
|
|
5562
|
+
if (email) void registerEmail(email, "openclaw-plugin").catch(() => {
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
5459
5565
|
var ASSESS_KINDS = ["ap2_payment", "message_send", "voice_call", "custom", "claim_document"];
|
|
5460
5566
|
var index_default = defineToolPlugin({
|
|
5461
5567
|
id: "fidacy",
|
|
@@ -5463,6 +5569,17 @@ var index_default = defineToolPlugin({
|
|
|
5463
5569
|
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.",
|
|
5464
5570
|
configSchema: typebox_exports.Object(
|
|
5465
5571
|
{
|
|
5572
|
+
// FIRST field on purpose: this is the install-time ask. OpenClaw renders
|
|
5573
|
+
// configSchema when the operator sets the plugin up, so the email is
|
|
5574
|
+
// collected at the one moment a HUMAN is present and paying attention —
|
|
5575
|
+
// no prompt is possible later (the plugin runs headless inside the agent).
|
|
5576
|
+
// Optional by design: a blank field must never block the install, and the
|
|
5577
|
+
// firewall protects with or without it.
|
|
5578
|
+
operatorEmail: typebox_exports.Optional(
|
|
5579
|
+
typebox_exports.String({
|
|
5580
|
+
description: "Your email (optional). Ties this agent's protection history to you so it can move into a free account, and lets Fidacy reach you about this install. Never shared; remove any time."
|
|
5581
|
+
})
|
|
5582
|
+
),
|
|
5466
5583
|
engineApiKey: typebox_exports.Optional(
|
|
5467
5584
|
typebox_exports.String({
|
|
5468
5585
|
description: "Fidacy engine API key (fky_live_/fky_test_) enabling signed verdicts via assess_action. Falls back to FIDACY_ENGINE_API_KEY."
|
|
@@ -5505,9 +5622,11 @@ var index_default = defineToolPlugin({
|
|
|
5505
5622
|
)
|
|
5506
5623
|
}),
|
|
5507
5624
|
async execute(params, config) {
|
|
5625
|
+
maybeRegisterOperator(config);
|
|
5508
5626
|
const gate = activationGate(config.engineApiKey);
|
|
5509
5627
|
if (gate) {
|
|
5510
|
-
|
|
5628
|
+
const halted = protectionWarning(decisionsUsed(), config.engineApiKey) ?? gate.message;
|
|
5629
|
+
return { status: "DENY", decisionId: "activation_required", violatedRule: "activation_required", message: halted };
|
|
5511
5630
|
}
|
|
5512
5631
|
const c = boot();
|
|
5513
5632
|
let sentinelAlerts = [];
|
|
@@ -5520,9 +5639,11 @@ var index_default = defineToolPlugin({
|
|
|
5520
5639
|
const sentinelLine = d.status === "ALLOW" ? renderAlertLine(sentinelAlerts) : "";
|
|
5521
5640
|
const base = d.status === "ALLOW" ? `ALLOW (decision ${d.decisionId})${params.invoiceRef ? ` for invoice ${params.invoiceRef}` : ""}.${sentinelLine} To settle, call the executor with the SAME payee, amount, currency, and idempotencyKey, and set "grant" to EXACTLY this signed value:
|
|
5522
5641
|
${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}. No grant issued, this payment cannot proceed. The denial itself is recorded in the tamper-evident, hash-chained audit: call get_audit_proof with decisionId ${d.decisionId} for the proof of what was blocked.${d.violatedRule?.startsWith("payee_not_in_allowlist") ? ` If the user trusts this payee, add "${params.payee}" to mandate.payees in ~/.fidacy/config.json and retry: the firewall picks the change up on the next call, no restart needed.` : ""}`;
|
|
5642
|
+
const badge = d.status === "DENY" ? protectionBadge() : "";
|
|
5523
5643
|
const nudge = decisionNudge(d.status, d.violatedRule, "fidacy_upgrade");
|
|
5524
5644
|
const countdown = trialCountdownLine(config.engineApiKey);
|
|
5525
|
-
const
|
|
5645
|
+
const lapse = isHostedLapsed() ? protectionWarning(decisionsUsed(), config.engineApiKey) : "";
|
|
5646
|
+
const message = [badge, base, nudge, countdown, lapse].filter(Boolean).join(" ");
|
|
5526
5647
|
return { status: d.status, decisionId: d.decisionId, grant: d.grant, violatedRule: d.violatedRule, message };
|
|
5527
5648
|
}
|
|
5528
5649
|
}),
|
|
@@ -5666,9 +5787,14 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
|
|
|
5666
5787
|
{ kind: params.kind, mandate: params.mandate, mandateType: params.mandateType, idempotencyKey: params.idempotencyKey, spendingMandate: params.spendingMandate, a2a: params.a2a },
|
|
5667
5788
|
{ engineUrl, apiKey }
|
|
5668
5789
|
);
|
|
5790
|
+
clearHostedLapsed();
|
|
5669
5791
|
return { summary: `${r.decision} (score ${r.score}) signed by ${r.signingKeyId}`, ...r };
|
|
5670
5792
|
} catch (e) {
|
|
5671
5793
|
if (e instanceof AssessError) {
|
|
5794
|
+
if (e.status === 402) {
|
|
5795
|
+
markHostedLapsed();
|
|
5796
|
+
throw new Error(hostedWallCta("verdict", e.billing));
|
|
5797
|
+
}
|
|
5672
5798
|
const reasons = e.rejection_reasons?.length ? " (" + e.rejection_reasons.map((x) => x.key).join(",") + ")" : "";
|
|
5673
5799
|
throw new Error(`ASSESS ${e.status}: ${e.type}${reasons}`);
|
|
5674
5800
|
}
|
|
@@ -5720,7 +5846,13 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
|
|
|
5720
5846
|
...r
|
|
5721
5847
|
};
|
|
5722
5848
|
} catch (e) {
|
|
5723
|
-
if (e instanceof AssessError)
|
|
5849
|
+
if (e instanceof AssessError) {
|
|
5850
|
+
if (e.status === 402) {
|
|
5851
|
+
markHostedLapsed();
|
|
5852
|
+
throw new Error(hostedWallCta("anchor", e.billing));
|
|
5853
|
+
}
|
|
5854
|
+
throw new Error(`ANCHOR ${e.status}: ${e.type}`);
|
|
5855
|
+
}
|
|
5724
5856
|
throw new Error("ANCHOR failed: unexpected error");
|
|
5725
5857
|
}
|
|
5726
5858
|
}
|
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.4.0",
|
|
7
7
|
"contracts": {
|
|
8
8
|
"tools": [
|
|
9
9
|
"request_payment",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"type": "object",
|
|
24
24
|
"additionalProperties": false,
|
|
25
25
|
"properties": {
|
|
26
|
+
"operatorEmail": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Your email (optional). Ties this agent's protection history to you so it can move into a free account, and lets Fidacy reach you about this install. Never shared; remove any time."
|
|
29
|
+
},
|
|
26
30
|
"engineApiKey": {
|
|
27
31
|
"type": "string",
|
|
28
32
|
"description": "Fidacy engine API key (fky_live_/fky_test_) enabling signed verdicts via assess_action. Falls back to FIDACY_ENGINE_API_KEY."
|
|
@@ -38,6 +42,11 @@
|
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
"uiHints": {
|
|
45
|
+
"operatorEmail": {
|
|
46
|
+
"label": "Your email (optional)",
|
|
47
|
+
"placeholder": "you@company.com",
|
|
48
|
+
"help": "Ties this agent's protection history to you so it can move into a free account, and lets Fidacy reach you about this install. Optional — the firewall works without it."
|
|
49
|
+
},
|
|
41
50
|
"engineApiKey": {
|
|
42
51
|
"label": "Engine API key",
|
|
43
52
|
"placeholder": "fky_live_...",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidacy/openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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)",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"typebox": "1.1.39",
|
|
58
58
|
"typescript": "^5.6.3",
|
|
59
59
|
"@fidacy/firewall": "0.1.1",
|
|
60
|
-
"@fidacy/mcp": "0.
|
|
60
|
+
"@fidacy/mcp": "0.5.1"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "node scripts/bundle.mjs",
|