@trucore/openclaw-atf 0.1.1 → 0.2.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/LICENSE +21 -0
- package/README.md +179 -12
- package/examples/README.md +134 -0
- package/examples/disable-plugin.json +3 -0
- package/examples/full-config.json +12 -0
- package/examples/minimal-enable.json +5 -0
- package/examples/prefer-api.json +8 -0
- package/examples/prefer-cli.json +7 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
- package/src/adoption_advisor.mjs +425 -0
- package/src/backend.mjs +230 -0
- package/src/billing_claim.mjs +630 -0
- package/src/config.mjs +317 -0
- package/src/contracts/deny_codes.mjs +217 -0
- package/src/contracts/index.mjs +52 -0
- package/src/contracts/result_builder.mjs +132 -0
- package/src/contracts/schemas.mjs +402 -0
- package/src/contracts/status_codes.mjs +148 -0
- package/src/doctor.mjs +207 -0
- package/src/index.mjs +1168 -70
- package/src/tool_response.mjs +181 -0
package/src/index.mjs
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @trucore/openclaw-atf — OpenClaw plugin: ATF agent tools
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
10
|
-
* 6.
|
|
4
|
+
* Registers OPTIONAL tools for OpenClaw agents:
|
|
5
|
+
* 1. atf_health — check ATF CLI / API availability
|
|
6
|
+
* 2. atf_discover — fetch/summarize ATF manifest
|
|
7
|
+
* 3. atf_bootstrap_plan — generate bootstrap steps (plan only, no side effects)
|
|
8
|
+
* 4. atf_bootstrap_execute_safe — run safe bootstrap steps locally
|
|
9
|
+
* 5. atf_protect_intent — protect a DeFi intent and return receipt + decision
|
|
10
|
+
* 6. atf_verify_receipt — verify a receipt deterministically
|
|
11
|
+
* 7. atf_report_savings — generate receipt-backed "ATF saved you" report
|
|
12
|
+
* 8. atf_integration_doctor — integration readiness / troubleshooting
|
|
13
|
+
* 9. atf_bot_preflight — pre-session readiness check (go/no-go)
|
|
14
|
+
* 10. atf_tx_explain — explain a receipt or deny decision in human terms
|
|
15
|
+
* 11. atf_billing_info — billing/pricing metadata for advanced bot package
|
|
16
|
+
* 12. atf_adoption_advisor — deterministic adoption recommendation for bots
|
|
17
|
+
* 13. atf_billing_claim — verify on-chain payment and process billing claim
|
|
18
|
+
*
|
|
19
|
+
* Exports:
|
|
20
|
+
* - default: atfPlugin(api) — legacy single-function entry
|
|
21
|
+
* - register(api) — OpenClaw lifecycle: register tools
|
|
22
|
+
* - activate(api) — OpenClaw lifecycle: activate plugin (config validation)
|
|
23
|
+
* - deactivate() — OpenClaw lifecycle: deactivate plugin
|
|
24
|
+
* - PLUGIN_ID — canonical plugin identity constant
|
|
25
|
+
* - PLUGIN_VERSION — plugin version constant
|
|
26
|
+
* - buildHumanSummary(reportJson) — human messaging helper
|
|
27
|
+
* - REASON_CODE_CATALOG — deny reason code explanations
|
|
28
|
+
* - ATF_BILLING_MANIFEST — machine-readable billing metadata
|
|
29
|
+
* - evaluateAdoption — adoption advisor evaluation function
|
|
30
|
+
* - TRIGGER_MODEL — frozen trigger definitions
|
|
31
|
+
* - INSTALL_GUIDANCE — frozen install/enable guidance
|
|
32
|
+
* - SAFE_CALL_SEQUENCE — frozen default call sequence
|
|
33
|
+
* - verifyBillingClaim — billing claim verification function
|
|
34
|
+
* - CLAIM_STATUS — claim status enum
|
|
35
|
+
* - CLAIM_DENY_CODES — claim deny code catalog
|
|
36
|
+
* - TREASURY_ADDRESS — canonical treasury address
|
|
37
|
+
* - USDC_MINT — canonical USDC mint address
|
|
38
|
+
* - CONTRACT_FAMILIES — universal contract family definitions
|
|
39
|
+
* - TOOL_FAMILY_MAP — tool → contract family mapping
|
|
40
|
+
* - CANONICAL_TOOLS — frozen list of all tool names
|
|
41
|
+
* - CANONICAL_TOOL_COUNT — number of canonical tools
|
|
11
42
|
*
|
|
12
43
|
* Uses ONLY built-in Node modules: child_process, fs, path, os, url.
|
|
13
44
|
* No network calls unless config.safety.allowNetwork = true.
|
|
@@ -17,8 +48,59 @@
|
|
|
17
48
|
import { execFile } from "node:child_process";
|
|
18
49
|
import { promisify } from "node:util";
|
|
19
50
|
|
|
51
|
+
// Config validation — used by activate() for non-blocking warnings.
|
|
52
|
+
// Imported as a module reference so activate() can call it synchronously.
|
|
53
|
+
import * as _configModule from "./config.mjs";
|
|
54
|
+
|
|
55
|
+
// Adoption advisor — deterministic trigger evaluation and recommendation.
|
|
56
|
+
import {
|
|
57
|
+
evaluateAdoption,
|
|
58
|
+
TRIGGER_MODEL,
|
|
59
|
+
INSTALL_GUIDANCE,
|
|
60
|
+
SAFE_CALL_SEQUENCE,
|
|
61
|
+
} from "./adoption_advisor.mjs";
|
|
62
|
+
|
|
63
|
+
// Billing claim verification — deterministic on-chain payment verification.
|
|
64
|
+
import {
|
|
65
|
+
verifyBillingClaim,
|
|
66
|
+
CLAIM_STATUS,
|
|
67
|
+
CLAIM_DENY_CODES,
|
|
68
|
+
TREASURY_ADDRESS,
|
|
69
|
+
USDC_MINT,
|
|
70
|
+
KNOWN_PACKAGES,
|
|
71
|
+
checkDuplicate,
|
|
72
|
+
_resetClaimTracking,
|
|
73
|
+
} from "./billing_claim.mjs";
|
|
74
|
+
|
|
75
|
+
// Standardized response helpers — OpenClaw adapter envelope builders.
|
|
76
|
+
import {
|
|
77
|
+
nativeToolSuccess,
|
|
78
|
+
nativeToolError,
|
|
79
|
+
RESPONSE_STATUS,
|
|
80
|
+
} from "./tool_response.mjs";
|
|
81
|
+
|
|
82
|
+
// Universal contract layer — canonical schemas, codes, and result builders.
|
|
83
|
+
// Re-exported so consumers can access the universal layer through the plugin.
|
|
84
|
+
import {
|
|
85
|
+
REASON_CODE_CATALOG as _REASON_CODE_CATALOG,
|
|
86
|
+
CONTRACT_FAMILIES,
|
|
87
|
+
TOOL_FAMILY_MAP,
|
|
88
|
+
CANONICAL_TOOLS,
|
|
89
|
+
CANONICAL_TOOL_COUNT,
|
|
90
|
+
} from "./contracts/index.mjs";
|
|
91
|
+
|
|
20
92
|
const execFileAsync = promisify(execFile);
|
|
21
93
|
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Canonical plugin identity — use ONLY these constants everywhere
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/** Canonical plugin ID. Must match openclaw.plugin.json `id` field. */
|
|
99
|
+
export const PLUGIN_ID = "trucore-atf";
|
|
100
|
+
|
|
101
|
+
/** Plugin version. Must match package.json + openclaw.plugin.json `version`. */
|
|
102
|
+
export const PLUGIN_VERSION = "0.2.0";
|
|
103
|
+
|
|
22
104
|
// ---------------------------------------------------------------------------
|
|
23
105
|
// Default config
|
|
24
106
|
// ---------------------------------------------------------------------------
|
|
@@ -89,33 +171,6 @@ function tryParseJson(raw) {
|
|
|
89
171
|
}
|
|
90
172
|
}
|
|
91
173
|
|
|
92
|
-
/**
|
|
93
|
-
* Build an OpenClaw tool content response.
|
|
94
|
-
* @param {string} summary
|
|
95
|
-
* @param {unknown} data
|
|
96
|
-
* @returns {{ content: Array<{type:string, text?:string, json?:unknown}> }}
|
|
97
|
-
*/
|
|
98
|
-
function toolResponse(summary, data) {
|
|
99
|
-
const items = [{ type: "text", text: summary }];
|
|
100
|
-
if (data !== undefined && data !== null) {
|
|
101
|
-
if (typeof data === "string") {
|
|
102
|
-
items.push({ type: "text", text: data });
|
|
103
|
-
} else {
|
|
104
|
-
items.push({ type: "json", json: data });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return { content: items };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Build an error response without throwing.
|
|
112
|
-
* @param {string} message
|
|
113
|
-
* @returns {{ content: Array<{type:string, text:string}>, isError: boolean }}
|
|
114
|
-
*/
|
|
115
|
-
function errorResponse(message) {
|
|
116
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
174
|
// ---------------------------------------------------------------------------
|
|
120
175
|
// Tool: atf_discover
|
|
121
176
|
// ---------------------------------------------------------------------------
|
|
@@ -132,7 +187,8 @@ async function toolAtfDiscover(cfg, params = {}) {
|
|
|
132
187
|
const { safety, atfBaseUrl } = cfg;
|
|
133
188
|
|
|
134
189
|
if (!safety.allowNetwork) {
|
|
135
|
-
return
|
|
190
|
+
return nativeToolSuccess(
|
|
191
|
+
"atf_discover",
|
|
136
192
|
"ATF discovery requires network access.",
|
|
137
193
|
{
|
|
138
194
|
instructions:
|
|
@@ -150,6 +206,7 @@ async function toolAtfDiscover(cfg, params = {}) {
|
|
|
150
206
|
toolcard_url:
|
|
151
207
|
"https://raw.githubusercontent.com/trucore-ai/agent-transaction-firewall/main/docs/agent/atf_toolcard.json",
|
|
152
208
|
},
|
|
209
|
+
{ status: RESPONSE_STATUS.UNAVAILABLE },
|
|
153
210
|
);
|
|
154
211
|
}
|
|
155
212
|
|
|
@@ -161,20 +218,24 @@ async function toolAtfDiscover(cfg, params = {}) {
|
|
|
161
218
|
let manifest;
|
|
162
219
|
try {
|
|
163
220
|
const res = await fetch(base, {
|
|
164
|
-
headers: { "User-Agent": "openclaw-atf-plugin/0.
|
|
221
|
+
headers: { "User-Agent": "openclaw-atf-plugin/0.2.0" },
|
|
165
222
|
signal: AbortSignal.timeout(10_000),
|
|
166
223
|
});
|
|
167
224
|
if (!res.ok) {
|
|
168
|
-
return
|
|
225
|
+
return nativeToolError(
|
|
226
|
+
"atf_discover",
|
|
169
227
|
`ATF manifest fetch failed: HTTP ${res.status} from ${base}`,
|
|
170
228
|
);
|
|
171
229
|
}
|
|
172
230
|
manifest = await res.json();
|
|
173
231
|
} catch (err) {
|
|
174
|
-
return
|
|
232
|
+
return nativeToolError(
|
|
233
|
+
"atf_discover",
|
|
234
|
+
`ATF manifest fetch error: ${err.message}`,
|
|
235
|
+
);
|
|
175
236
|
}
|
|
176
237
|
|
|
177
|
-
const
|
|
238
|
+
const manifestSummary = {
|
|
178
239
|
id: manifest.id ?? manifest.product ?? "trucore-atf",
|
|
179
240
|
version: manifest.version ?? "unknown",
|
|
180
241
|
capabilities: manifest.capabilities ?? [],
|
|
@@ -185,9 +246,10 @@ async function toolAtfDiscover(cfg, params = {}) {
|
|
|
185
246
|
trust_signals: manifest.trust_signals ?? {},
|
|
186
247
|
};
|
|
187
248
|
|
|
188
|
-
return
|
|
249
|
+
return nativeToolSuccess(
|
|
250
|
+
"atf_discover",
|
|
189
251
|
"ATF manifest fetched. Key fields summarised below.",
|
|
190
|
-
|
|
252
|
+
manifestSummary,
|
|
191
253
|
);
|
|
192
254
|
}
|
|
193
255
|
|
|
@@ -209,16 +271,18 @@ async function toolAtfBootstrapPlan(cfg, params = {}) {
|
|
|
209
271
|
const result = await runAtfCli(atfCli, args);
|
|
210
272
|
|
|
211
273
|
if (result.exitCode !== 0) {
|
|
212
|
-
return
|
|
274
|
+
return nativeToolError(
|
|
275
|
+
"atf_bootstrap_plan",
|
|
213
276
|
`atf bootstrap plan failed (exit ${result.exitCode}).\n` +
|
|
214
277
|
`stderr: ${result.stderr}\nstdout: ${result.stdout}`,
|
|
215
278
|
);
|
|
216
279
|
}
|
|
217
280
|
|
|
218
281
|
const parsed = tryParseJson(result.stdout);
|
|
219
|
-
return
|
|
282
|
+
return nativeToolSuccess(
|
|
283
|
+
"atf_bootstrap_plan",
|
|
220
284
|
`Bootstrap plan for recipe '${recipe}' (plan only — no steps executed).`,
|
|
221
|
-
parsed,
|
|
285
|
+
typeof parsed === "object" && parsed !== null ? parsed : { raw: parsed },
|
|
222
286
|
);
|
|
223
287
|
}
|
|
224
288
|
|
|
@@ -237,9 +301,11 @@ async function toolAtfBootstrapExecuteSafe(cfg, params = {}) {
|
|
|
237
301
|
const { atfCli, safety } = cfg;
|
|
238
302
|
|
|
239
303
|
if (!safety.allowExecuteSafe) {
|
|
240
|
-
return
|
|
304
|
+
return nativeToolError(
|
|
305
|
+
"atf_bootstrap_execute_safe",
|
|
241
306
|
"atf_bootstrap_execute_safe is disabled. " +
|
|
242
307
|
"Set safety.allowExecuteSafe = true in the plugin config to enable.",
|
|
308
|
+
{ remediation: ["Set safety.allowExecuteSafe = true in plugin config."] },
|
|
243
309
|
);
|
|
244
310
|
}
|
|
245
311
|
|
|
@@ -261,8 +327,9 @@ async function toolAtfBootstrapExecuteSafe(cfg, params = {}) {
|
|
|
261
327
|
const parsed = tryParseJson(result.stdout);
|
|
262
328
|
|
|
263
329
|
if (result.exitCode !== 0) {
|
|
264
|
-
// Non-fatal: return partial result + stderr
|
|
265
|
-
return
|
|
330
|
+
// Non-fatal: return partial result + stderr with degraded status
|
|
331
|
+
return nativeToolSuccess(
|
|
332
|
+
"atf_bootstrap_execute_safe",
|
|
266
333
|
`Bootstrap execute-safe completed with non-zero exit (${result.exitCode}). ` +
|
|
267
334
|
`Check executed_steps for partial results.`,
|
|
268
335
|
{
|
|
@@ -270,12 +337,14 @@ async function toolAtfBootstrapExecuteSafe(cfg, params = {}) {
|
|
|
270
337
|
stderr: result.stderr,
|
|
271
338
|
result: parsed,
|
|
272
339
|
},
|
|
340
|
+
{ status: RESPONSE_STATUS.DEGRADED },
|
|
273
341
|
);
|
|
274
342
|
}
|
|
275
343
|
|
|
276
|
-
return
|
|
344
|
+
return nativeToolSuccess(
|
|
345
|
+
"atf_bootstrap_execute_safe",
|
|
277
346
|
`Bootstrap execute-safe complete for recipe '${recipe}'.${dryRun ? " (dry-run)" : ""}`,
|
|
278
|
-
parsed,
|
|
347
|
+
typeof parsed === "object" && parsed !== null ? parsed : { raw: parsed },
|
|
279
348
|
);
|
|
280
349
|
}
|
|
281
350
|
|
|
@@ -294,8 +363,10 @@ async function toolAtfProtectIntent(cfg, params = {}) {
|
|
|
294
363
|
const { intentJson, exposureHints } = params;
|
|
295
364
|
|
|
296
365
|
if (!intentJson || typeof intentJson !== "object") {
|
|
297
|
-
return
|
|
366
|
+
return nativeToolError(
|
|
367
|
+
"atf_protect_intent",
|
|
298
368
|
"atf_protect_intent requires 'intentJson' (object) input.",
|
|
369
|
+
{ remediation: ["Provide an intentJson object with chain_id, intent_type, and intent fields."] },
|
|
299
370
|
);
|
|
300
371
|
}
|
|
301
372
|
|
|
@@ -322,31 +393,37 @@ async function toolAtfProtectIntent(cfg, params = {}) {
|
|
|
322
393
|
method: "POST",
|
|
323
394
|
headers: {
|
|
324
395
|
"Content-Type": "application/json",
|
|
325
|
-
"User-Agent": "openclaw-atf-plugin/0.
|
|
396
|
+
"User-Agent": "openclaw-atf-plugin/0.2.0",
|
|
326
397
|
},
|
|
327
398
|
body: payloadStr,
|
|
328
399
|
signal: AbortSignal.timeout(15_000),
|
|
329
400
|
});
|
|
330
401
|
} catch (err) {
|
|
331
|
-
return
|
|
402
|
+
return nativeToolError(
|
|
403
|
+
"atf_protect_intent",
|
|
404
|
+
`ATF API protect call failed: ${err.message}`,
|
|
405
|
+
);
|
|
332
406
|
}
|
|
333
407
|
|
|
334
408
|
let data;
|
|
335
409
|
try {
|
|
336
410
|
data = await res.json();
|
|
337
411
|
} catch {
|
|
338
|
-
return
|
|
412
|
+
return nativeToolError(
|
|
413
|
+
"atf_protect_intent",
|
|
339
414
|
`ATF API protect response not JSON (HTTP ${res.status}).`,
|
|
340
415
|
);
|
|
341
416
|
}
|
|
342
417
|
|
|
343
418
|
if (!res.ok) {
|
|
344
|
-
return
|
|
419
|
+
return nativeToolError(
|
|
420
|
+
"atf_protect_intent",
|
|
345
421
|
`ATF API protect HTTP ${res.status}: ${JSON.stringify(data)}`,
|
|
346
422
|
);
|
|
347
423
|
}
|
|
348
424
|
|
|
349
|
-
return
|
|
425
|
+
return nativeToolSuccess(
|
|
426
|
+
"atf_protect_intent",
|
|
350
427
|
`ATF protect decision: ${data.allow ? "ALLOW" : "DENY"}.` +
|
|
351
428
|
(data.reason_codes?.length
|
|
352
429
|
? ` Reason codes: ${data.reason_codes.join(", ")}.`
|
|
@@ -379,18 +456,20 @@ async function toolAtfProtectIntent(cfg, params = {}) {
|
|
|
379
456
|
typeof parsed === "object" && parsed !== null ? parsed.allow : null;
|
|
380
457
|
|
|
381
458
|
if (decision.exitCode !== 0 && typeof parsed !== "object") {
|
|
382
|
-
return
|
|
459
|
+
return nativeToolError(
|
|
460
|
+
"atf_protect_intent",
|
|
383
461
|
`ATF protect failed (exit ${decision.exitCode}).\n` +
|
|
384
462
|
`stderr: ${decision.stderr}\nstdout: ${decision.stdout}`,
|
|
385
463
|
);
|
|
386
464
|
}
|
|
387
465
|
|
|
388
|
-
return
|
|
466
|
+
return nativeToolSuccess(
|
|
467
|
+
"atf_protect_intent",
|
|
389
468
|
`ATF protect decision: ${allow === true ? "ALLOW" : allow === false ? "DENY" : "see json"}.` +
|
|
390
469
|
(typeof parsed === "object" && parsed?.reason_codes?.length
|
|
391
470
|
? ` Reason codes: ${parsed.reason_codes.join(", ")}.`
|
|
392
471
|
: ""),
|
|
393
|
-
parsed,
|
|
472
|
+
typeof parsed === "object" && parsed !== null ? parsed : { raw: parsed },
|
|
394
473
|
);
|
|
395
474
|
}
|
|
396
475
|
|
|
@@ -409,9 +488,11 @@ async function toolAtfVerifyReceipt(cfg, params = {}) {
|
|
|
409
488
|
const { receipt } = params;
|
|
410
489
|
|
|
411
490
|
if (!receipt) {
|
|
412
|
-
return
|
|
491
|
+
return nativeToolError(
|
|
492
|
+
"atf_verify_receipt",
|
|
413
493
|
"atf_verify_receipt requires 'receipt' param " +
|
|
414
494
|
"(receipt JSON string, object, or file path).",
|
|
495
|
+
{ remediation: ["Provide a receipt JSON object, JSON string, or file path."] },
|
|
415
496
|
);
|
|
416
497
|
}
|
|
417
498
|
|
|
@@ -461,7 +542,8 @@ async function toolAtfVerifyReceipt(cfg, params = {}) {
|
|
|
461
542
|
const parsed = tryParseJson(result.stdout);
|
|
462
543
|
|
|
463
544
|
if (result.exitCode !== 0) {
|
|
464
|
-
return
|
|
545
|
+
return nativeToolError(
|
|
546
|
+
"atf_verify_receipt",
|
|
465
547
|
`ATF verify failed (exit ${result.exitCode}).\n` +
|
|
466
548
|
`stderr: ${result.stderr}\nstdout: ${result.stdout}`,
|
|
467
549
|
);
|
|
@@ -470,9 +552,10 @@ async function toolAtfVerifyReceipt(cfg, params = {}) {
|
|
|
470
552
|
const verified =
|
|
471
553
|
typeof parsed === "object" && parsed !== null ? parsed.verified : null;
|
|
472
554
|
|
|
473
|
-
return
|
|
555
|
+
return nativeToolSuccess(
|
|
556
|
+
"atf_verify_receipt",
|
|
474
557
|
`Receipt verification: ${verified === true ? "VERIFIED" : verified === false ? "FAILED" : "see json"}.`,
|
|
475
|
-
parsed,
|
|
558
|
+
typeof parsed === "object" && parsed !== null ? parsed : { raw: parsed },
|
|
476
559
|
);
|
|
477
560
|
}
|
|
478
561
|
|
|
@@ -500,7 +583,8 @@ async function toolAtfReportSavings(cfg, params = {}) {
|
|
|
500
583
|
const parsed = tryParseJson(result.stdout);
|
|
501
584
|
|
|
502
585
|
if (result.exitCode !== 0) {
|
|
503
|
-
return
|
|
586
|
+
return nativeToolError(
|
|
587
|
+
"atf_report_savings",
|
|
504
588
|
`ATF report savings failed (exit ${result.exitCode}).\n` +
|
|
505
589
|
`stderr: ${result.stderr}\nstdout: ${result.stdout}`,
|
|
506
590
|
);
|
|
@@ -510,7 +594,11 @@ async function toolAtfReportSavings(cfg, params = {}) {
|
|
|
510
594
|
typeof parsed === "object" ? parsed : {},
|
|
511
595
|
);
|
|
512
596
|
|
|
513
|
-
return
|
|
597
|
+
return nativeToolSuccess(
|
|
598
|
+
"atf_report_savings",
|
|
599
|
+
summary,
|
|
600
|
+
typeof parsed === "object" && parsed !== null ? parsed : { raw: parsed },
|
|
601
|
+
);
|
|
514
602
|
}
|
|
515
603
|
|
|
516
604
|
// ---------------------------------------------------------------------------
|
|
@@ -649,6 +737,678 @@ export function buildHumanSummary(reportJson) {
|
|
|
649
737
|
return lines.join("\n");
|
|
650
738
|
}
|
|
651
739
|
|
|
740
|
+
// ---------------------------------------------------------------------------
|
|
741
|
+
// Tool: atf_health — lightweight availability check
|
|
742
|
+
// ---------------------------------------------------------------------------
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Check whether the ATF CLI or API backend is reachable.
|
|
746
|
+
* Never throws. Returns structured health status with backend resolution.
|
|
747
|
+
* Uses standardized nativeToolSuccess/nativeToolError response shapes.
|
|
748
|
+
*
|
|
749
|
+
* @param {Record<string, unknown>} cfg Resolved config.
|
|
750
|
+
* @returns {Promise<object>}
|
|
751
|
+
*/
|
|
752
|
+
async function toolAtfHealth(cfg) {
|
|
753
|
+
const { atfCli, prefer, atfBaseUrl } = cfg;
|
|
754
|
+
|
|
755
|
+
// Use shared backend probes
|
|
756
|
+
const { probeCliAvailable, probeApiAvailable, resolveBackend } =
|
|
757
|
+
await import("./backend.mjs");
|
|
758
|
+
|
|
759
|
+
const cli = await probeCliAvailable(atfCli);
|
|
760
|
+
const api = await probeApiAvailable(atfBaseUrl);
|
|
761
|
+
const backend = await resolveBackend(cfg, { cli, api });
|
|
762
|
+
|
|
763
|
+
const anyAvailable = cli.available === true || api.available === true;
|
|
764
|
+
|
|
765
|
+
const summary = anyAvailable
|
|
766
|
+
? `ATF backend available (prefer=${prefer}, effective=${backend.effective_backend}).` +
|
|
767
|
+
(cli.available ? ` CLI: ${cli.version}.` : "") +
|
|
768
|
+
(api.available ? ` API: ${atfBaseUrl}.` : "") +
|
|
769
|
+
(backend.fallback_occurred ? ` [FALLBACK: ${backend.fallback_reason}]` : "")
|
|
770
|
+
: "ATF backend unavailable. CLI and API both unreachable. " +
|
|
771
|
+
"Tools will return errors until a backend is available.";
|
|
772
|
+
|
|
773
|
+
const status = anyAvailable
|
|
774
|
+
? (backend.fallback_occurred ? RESPONSE_STATUS.DEGRADED : RESPONSE_STATUS.OK)
|
|
775
|
+
: RESPONSE_STATUS.UNAVAILABLE;
|
|
776
|
+
|
|
777
|
+
return nativeToolSuccess("atf_health", summary, {
|
|
778
|
+
healthy: anyAvailable,
|
|
779
|
+
cli,
|
|
780
|
+
api,
|
|
781
|
+
}, {
|
|
782
|
+
status,
|
|
783
|
+
backend,
|
|
784
|
+
warnings: backend.warnings,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ---------------------------------------------------------------------------
|
|
789
|
+
// Tool: atf_integration_doctor — readiness / troubleshooting
|
|
790
|
+
// ---------------------------------------------------------------------------
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Run the integration doctor and return a structured readiness report.
|
|
794
|
+
* Answers: Is the plugin loaded? CLI/API available? Tools registered?
|
|
795
|
+
* What should the operator do next?
|
|
796
|
+
* Uses standardized nativeToolSuccess response shape.
|
|
797
|
+
*
|
|
798
|
+
* @param {Record<string, unknown>} cfg Resolved config.
|
|
799
|
+
* @param {string[]} registeredToolNames Names of tools registered with this plugin.
|
|
800
|
+
* @returns {Promise<object>}
|
|
801
|
+
*/
|
|
802
|
+
async function toolAtfIntegrationDoctor(cfg, registeredToolNames) {
|
|
803
|
+
const { runDoctor } = await import("./doctor.mjs");
|
|
804
|
+
const { resolveBackend, probeCliAvailable, probeApiAvailable } =
|
|
805
|
+
await import("./backend.mjs");
|
|
806
|
+
|
|
807
|
+
const report = await runDoctor(cfg, registeredToolNames);
|
|
808
|
+
|
|
809
|
+
// Resolve backend for meta
|
|
810
|
+
const cli = await probeCliAvailable(cfg?.atfCli ?? "atf");
|
|
811
|
+
const api = await probeApiAvailable(cfg?.atfBaseUrl);
|
|
812
|
+
const backend = await resolveBackend(cfg, { cli, api });
|
|
813
|
+
|
|
814
|
+
const statusLine = {
|
|
815
|
+
ok: "Integration healthy. All systems operational.",
|
|
816
|
+
degraded: "Integration degraded. Some capabilities limited — see warnings.",
|
|
817
|
+
misconfigured: "Integration misconfigured. Check warnings and remediation steps.",
|
|
818
|
+
unavailable: "Integration unavailable. No ATF backend reachable.",
|
|
819
|
+
}[report.status] ?? `Integration status: ${report.status}`;
|
|
820
|
+
|
|
821
|
+
const responseStatus = {
|
|
822
|
+
ok: RESPONSE_STATUS.OK,
|
|
823
|
+
degraded: RESPONSE_STATUS.DEGRADED,
|
|
824
|
+
misconfigured: RESPONSE_STATUS.ERROR,
|
|
825
|
+
unavailable: RESPONSE_STATUS.UNAVAILABLE,
|
|
826
|
+
}[report.status] ?? RESPONSE_STATUS.ERROR;
|
|
827
|
+
|
|
828
|
+
return nativeToolSuccess("atf_integration_doctor", statusLine, report, {
|
|
829
|
+
status: responseStatus,
|
|
830
|
+
backend,
|
|
831
|
+
warnings: report.warnings,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ---------------------------------------------------------------------------
|
|
836
|
+
// Reason code catalog — sourced from universal contract layer
|
|
837
|
+
// ---------------------------------------------------------------------------
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Canonical reason code catalog: re-exported from the universal contract
|
|
841
|
+
* layer (contracts/deny_codes.mjs). Used by atf_tx_explain and any future
|
|
842
|
+
* surface that needs to explain a deny decision.
|
|
843
|
+
*
|
|
844
|
+
* @type {Readonly<Record<string, {category: string, explanation: string, remediation: string}>>}
|
|
845
|
+
*/
|
|
846
|
+
export const REASON_CODE_CATALOG = _REASON_CODE_CATALOG;
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
// ---------------------------------------------------------------------------
|
|
850
|
+
// Tool: atf_bot_preflight — pre-session readiness check
|
|
851
|
+
// ---------------------------------------------------------------------------
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Lightweight pre-session readiness check. Returns a structured go/no-go
|
|
855
|
+
* signal based on backend availability and config validity. Thinner than
|
|
856
|
+
* the full integration doctor — designed for agents to call before starting
|
|
857
|
+
* a bot session.
|
|
858
|
+
*
|
|
859
|
+
* Uses shared backend resolution + config validation. Never throws.
|
|
860
|
+
*
|
|
861
|
+
* @param {Record<string, unknown>} cfg Resolved config.
|
|
862
|
+
* @param {Record<string, unknown>} [rawUserConfig] Original user config for validation.
|
|
863
|
+
* @returns {Promise<object>}
|
|
864
|
+
*/
|
|
865
|
+
async function toolAtfBotPreflight(cfg, rawUserConfig) {
|
|
866
|
+
const { probeCliAvailable, probeApiAvailable, resolveBackend } =
|
|
867
|
+
await import("./backend.mjs");
|
|
868
|
+
const { validateConfig } = await import("./config.mjs");
|
|
869
|
+
|
|
870
|
+
const cli = await probeCliAvailable(cfg?.atfCli ?? "atf");
|
|
871
|
+
const api = await probeApiAvailable(cfg?.atfBaseUrl);
|
|
872
|
+
const backend = await resolveBackend(cfg, { cli, api });
|
|
873
|
+
const configResult = validateConfig(rawUserConfig ?? cfg);
|
|
874
|
+
|
|
875
|
+
const backendReady = backend.effective_backend !== "none";
|
|
876
|
+
const configValid = configResult.valid;
|
|
877
|
+
const ready = backendReady && configValid;
|
|
878
|
+
|
|
879
|
+
/** @type {string[]} */
|
|
880
|
+
const warnings = [...backend.warnings];
|
|
881
|
+
/** @type {string[]} */
|
|
882
|
+
const remediation = [];
|
|
883
|
+
|
|
884
|
+
if (!configValid) {
|
|
885
|
+
for (const err of configResult.errors) {
|
|
886
|
+
warnings.push(`Config: ${err}`);
|
|
887
|
+
}
|
|
888
|
+
for (const rem of configResult.remediation) {
|
|
889
|
+
remediation.push(rem);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
for (const w of configResult.warnings) {
|
|
893
|
+
warnings.push(`Config: ${w}`);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
if (!backendReady) {
|
|
897
|
+
remediation.push("Install ATF CLI: npm install -g @trucore/atf");
|
|
898
|
+
if (cfg?.prefer === "api") {
|
|
899
|
+
remediation.push("Or set atfBaseUrl to a reachable ATF API.");
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const status = ready
|
|
904
|
+
? (backend.fallback_occurred ? RESPONSE_STATUS.DEGRADED : RESPONSE_STATUS.OK)
|
|
905
|
+
: RESPONSE_STATUS.UNAVAILABLE;
|
|
906
|
+
|
|
907
|
+
const summary = ready
|
|
908
|
+
? `Preflight OK. Backend: ${backend.effective_backend}.` +
|
|
909
|
+
(backend.fallback_occurred ? ` [FALLBACK: ${backend.fallback_reason}]` : "") +
|
|
910
|
+
" Ready to protect intents."
|
|
911
|
+
: "Preflight FAILED. " +
|
|
912
|
+
(!backendReady ? "No ATF backend available. " : "") +
|
|
913
|
+
(!configValid ? "Config has errors. " : "") +
|
|
914
|
+
"Run atf_integration_doctor for full details.";
|
|
915
|
+
|
|
916
|
+
return nativeToolSuccess("atf_bot_preflight", summary, {
|
|
917
|
+
ready,
|
|
918
|
+
backend_ready: backendReady,
|
|
919
|
+
config_valid: configValid,
|
|
920
|
+
config_errors: configResult.errors,
|
|
921
|
+
config_warnings: configResult.warnings,
|
|
922
|
+
cli_available: cli.available,
|
|
923
|
+
api_available: api.available,
|
|
924
|
+
remediation,
|
|
925
|
+
}, {
|
|
926
|
+
status,
|
|
927
|
+
backend,
|
|
928
|
+
warnings,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// ---------------------------------------------------------------------------
|
|
933
|
+
// Tool: atf_tx_explain — explain a receipt or deny decision
|
|
934
|
+
// ---------------------------------------------------------------------------
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Explain a receipt or deny decision in human-readable terms.
|
|
938
|
+
* Maps reason codes to explanations from the REASON_CODE_CATALOG.
|
|
939
|
+
* Thin adapter: does not re-evaluate policy, only explains.
|
|
940
|
+
*
|
|
941
|
+
* Accepts either:
|
|
942
|
+
* - { reasonCodes: ["CODE1", "CODE2"] }
|
|
943
|
+
* - { receipt: { reason_codes: [...], ... } }
|
|
944
|
+
*
|
|
945
|
+
* @param {Record<string, unknown>} _cfg Resolved config (unused, for consistency).
|
|
946
|
+
* @param {{ reasonCodes?: string[], receipt?: object }} params
|
|
947
|
+
* @returns {object}
|
|
948
|
+
*/
|
|
949
|
+
function toolAtfTxExplain(_cfg, params = {}) {
|
|
950
|
+
const { reasonCodes: rawCodes, receipt } = params;
|
|
951
|
+
|
|
952
|
+
// Extract reason codes from receipt or params
|
|
953
|
+
let codes = [];
|
|
954
|
+
let decision = null;
|
|
955
|
+
let receiptMeta = null;
|
|
956
|
+
|
|
957
|
+
if (receipt && typeof receipt === "object") {
|
|
958
|
+
codes = receipt.reason_codes ?? receipt.reasonCodes ?? [];
|
|
959
|
+
decision = receipt.decision?.status ?? receipt.status ?? null;
|
|
960
|
+
receiptMeta = {
|
|
961
|
+
content_hash: receipt.content_hash ?? null,
|
|
962
|
+
intent_hash: receipt.intent_hash ?? null,
|
|
963
|
+
chain_id: receipt.chain_id ?? null,
|
|
964
|
+
intent_type: receipt.intent_type ?? null,
|
|
965
|
+
venue: receipt.venue ?? null,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (Array.isArray(rawCodes) && rawCodes.length > 0) {
|
|
970
|
+
codes = rawCodes;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (!Array.isArray(codes) || codes.length === 0) {
|
|
974
|
+
return nativeToolError(
|
|
975
|
+
"atf_tx_explain",
|
|
976
|
+
"atf_tx_explain requires 'reasonCodes' (string[]) or 'receipt' (object with reason_codes).",
|
|
977
|
+
{ remediation: ["Provide reasonCodes array or a receipt object with reason_codes field."] },
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Map each code to explanation
|
|
982
|
+
const explanations = codes.map((code) => {
|
|
983
|
+
const entry = REASON_CODE_CATALOG[code];
|
|
984
|
+
if (entry) {
|
|
985
|
+
return {
|
|
986
|
+
code,
|
|
987
|
+
category: entry.category,
|
|
988
|
+
explanation: entry.explanation,
|
|
989
|
+
remediation: entry.remediation,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
code,
|
|
994
|
+
category: "unknown",
|
|
995
|
+
explanation: `Reason code '${code}' is not in the known catalog. Check docs/DENY_CODES.md for the full list.`,
|
|
996
|
+
remediation: "Consult ATF documentation for this deny code.",
|
|
997
|
+
};
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// Build human summary
|
|
1001
|
+
const lines = [];
|
|
1002
|
+
if (decision) {
|
|
1003
|
+
lines.push(`Decision: ${decision}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (receiptMeta?.venue) {
|
|
1006
|
+
lines.push(`Venue: ${receiptMeta.venue}`);
|
|
1007
|
+
}
|
|
1008
|
+
if (receiptMeta?.intent_type) {
|
|
1009
|
+
lines.push(`Intent type: ${receiptMeta.intent_type}`);
|
|
1010
|
+
}
|
|
1011
|
+
lines.push("");
|
|
1012
|
+
lines.push(`${codes.length} reason code${codes.length !== 1 ? "s" : ""} explained:`);
|
|
1013
|
+
for (const exp of explanations) {
|
|
1014
|
+
lines.push(` • ${exp.code}: ${exp.explanation}`);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const summary = lines.join("\n");
|
|
1018
|
+
|
|
1019
|
+
return nativeToolSuccess("atf_tx_explain", summary, {
|
|
1020
|
+
decision,
|
|
1021
|
+
reason_codes: codes,
|
|
1022
|
+
explanations,
|
|
1023
|
+
receipt_meta: receiptMeta,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// ---------------------------------------------------------------------------
|
|
1028
|
+
// Billing manifest — machine-readable pricing/package metadata
|
|
1029
|
+
// ---------------------------------------------------------------------------
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* ATF billing manifest — machine-readable metadata for the Advanced Bot Package.
|
|
1033
|
+
*
|
|
1034
|
+
* Bots use this to discover pricing, accepted payment rails, and activation
|
|
1035
|
+
* guidance without needing to parse docs or reverse-engineer billing logic.
|
|
1036
|
+
*
|
|
1037
|
+
* Exported so tests can validate structural integrity.
|
|
1038
|
+
*
|
|
1039
|
+
* @type {Readonly<Record<string, unknown>>}
|
|
1040
|
+
*/
|
|
1041
|
+
export const ATF_BILLING_MANIFEST = Object.freeze({
|
|
1042
|
+
schema_version: "1",
|
|
1043
|
+
product: "TruCore ATF",
|
|
1044
|
+
id: "trucore-atf",
|
|
1045
|
+
billing_manifest_version: "1.0.0",
|
|
1046
|
+
packages: Object.freeze([
|
|
1047
|
+
Object.freeze({
|
|
1048
|
+
package_id: "atf_free_core",
|
|
1049
|
+
package_name: "ATF Free Core",
|
|
1050
|
+
package_tier: "free",
|
|
1051
|
+
summary:
|
|
1052
|
+
"Core transaction firewall capabilities — always free, no payment required.",
|
|
1053
|
+
billing_model: "free",
|
|
1054
|
+
price: null,
|
|
1055
|
+
}),
|
|
1056
|
+
Object.freeze({
|
|
1057
|
+
package_id: "atf_advanced_bot",
|
|
1058
|
+
package_name: "ATF Advanced Bot Package",
|
|
1059
|
+
package_tier: "advanced",
|
|
1060
|
+
summary:
|
|
1061
|
+
"Premium capabilities for professional bot operators — " +
|
|
1062
|
+
"priority support, advanced analytics, and premium integrations.",
|
|
1063
|
+
billing_model: "subscription",
|
|
1064
|
+
billing_interval: "monthly",
|
|
1065
|
+
price: Object.freeze({
|
|
1066
|
+
amount: "25.00",
|
|
1067
|
+
currency: "USDC",
|
|
1068
|
+
chain: "solana",
|
|
1069
|
+
}),
|
|
1070
|
+
price_alternatives: Object.freeze([
|
|
1071
|
+
Object.freeze({
|
|
1072
|
+
amount: "0.1",
|
|
1073
|
+
currency: "SOL",
|
|
1074
|
+
chain: "solana",
|
|
1075
|
+
note: "Approximate SOL equivalent (~0.1 SOL/month) — confirm current rate before sending.",
|
|
1076
|
+
}),
|
|
1077
|
+
]),
|
|
1078
|
+
status: "accepting_payments",
|
|
1079
|
+
}),
|
|
1080
|
+
]),
|
|
1081
|
+
payment_rails: Object.freeze({
|
|
1082
|
+
chain: "solana",
|
|
1083
|
+
network: "mainnet-beta",
|
|
1084
|
+
accepted_assets: Object.freeze([
|
|
1085
|
+
Object.freeze({
|
|
1086
|
+
asset: "USDC",
|
|
1087
|
+
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
1088
|
+
decimals: 6,
|
|
1089
|
+
preferred: true,
|
|
1090
|
+
}),
|
|
1091
|
+
Object.freeze({
|
|
1092
|
+
asset: "SOL",
|
|
1093
|
+
mint: "native",
|
|
1094
|
+
decimals: 9,
|
|
1095
|
+
preferred: false,
|
|
1096
|
+
}),
|
|
1097
|
+
]),
|
|
1098
|
+
recipient: Object.freeze({
|
|
1099
|
+
address: "GFHVjxUiPWLs214QZToMvJ8PcacBkkzpiQugE5gXGSmx",
|
|
1100
|
+
label: "TruCore Treasury",
|
|
1101
|
+
note: "TruCore treasury on Solana mainnet-beta. " +
|
|
1102
|
+
"Verify this address before sending funds.",
|
|
1103
|
+
}),
|
|
1104
|
+
memo_requirements: Object.freeze({
|
|
1105
|
+
required: true,
|
|
1106
|
+
format: "atf:<package_id>:<your_identifier>",
|
|
1107
|
+
example: "atf:atf_advanced_bot:my-bot-v1",
|
|
1108
|
+
}),
|
|
1109
|
+
confirmation_expectations: Object.freeze({
|
|
1110
|
+
confirmations: "confirmed",
|
|
1111
|
+
estimated_time_seconds: 5,
|
|
1112
|
+
}),
|
|
1113
|
+
}),
|
|
1114
|
+
free_vs_advanced: Object.freeze({
|
|
1115
|
+
free_core_tools: Object.freeze([
|
|
1116
|
+
"atf_health",
|
|
1117
|
+
"atf_discover",
|
|
1118
|
+
"atf_bootstrap_plan",
|
|
1119
|
+
"atf_bootstrap_execute_safe",
|
|
1120
|
+
"atf_protect_intent",
|
|
1121
|
+
"atf_verify_receipt",
|
|
1122
|
+
"atf_report_savings",
|
|
1123
|
+
"atf_integration_doctor",
|
|
1124
|
+
"atf_bot_preflight",
|
|
1125
|
+
"atf_tx_explain",
|
|
1126
|
+
"atf_billing_info",
|
|
1127
|
+
"atf_adoption_advisor",
|
|
1128
|
+
"atf_billing_claim",
|
|
1129
|
+
]),
|
|
1130
|
+
advanced_package_features: Object.freeze([
|
|
1131
|
+
"priority_support",
|
|
1132
|
+
"advanced_analytics",
|
|
1133
|
+
"custom_policy_review",
|
|
1134
|
+
"early_access_modules",
|
|
1135
|
+
"priority_bug_fixes",
|
|
1136
|
+
"multi_bot_oversight",
|
|
1137
|
+
"bot_governor",
|
|
1138
|
+
"escalation_controls",
|
|
1139
|
+
"circuit_breaker",
|
|
1140
|
+
]),
|
|
1141
|
+
note: "All core ATF enforcement tools remain free. " +
|
|
1142
|
+
"The advanced package adds premium support surfaces and " +
|
|
1143
|
+
"future operator controls. No existing free tool will be " +
|
|
1144
|
+
"moved behind a paywall.",
|
|
1145
|
+
}),
|
|
1146
|
+
activation: Object.freeze({
|
|
1147
|
+
method: "pay_then_claim",
|
|
1148
|
+
current_reality:
|
|
1149
|
+
"Payment is accepted on-chain. Claim verification is automated via " +
|
|
1150
|
+
"atf_billing_claim — submit your transaction signature and receive a " +
|
|
1151
|
+
"deterministic verification result. Entitlement provisioning after " +
|
|
1152
|
+
"verification is currently semi-automated.",
|
|
1153
|
+
automated_entitlement: "claim_verification",
|
|
1154
|
+
support_contact: "support@trucore.xyz",
|
|
1155
|
+
}),
|
|
1156
|
+
warnings: Object.freeze([
|
|
1157
|
+
"Early-stage billing — pricing and payment flow may change. Always check this manifest for current rates.",
|
|
1158
|
+
"SOL pricing is approximate — confirm current SOL/USD exchange rate before sending.",
|
|
1159
|
+
"Claim verification is automated via atf_billing_claim. Entitlement provisioning after verification is semi-automated.",
|
|
1160
|
+
"Anti-replay duplicate detection is in-memory and resets on process restart.",
|
|
1161
|
+
"Pricing is subject to change — always check this manifest for current rates.",
|
|
1162
|
+
"This billing manifest is informational — it does not execute payments.",
|
|
1163
|
+
]),
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
// ---------------------------------------------------------------------------
|
|
1167
|
+
// Tool: atf_adoption_advisor — deterministic adoption recommendation
|
|
1168
|
+
// ---------------------------------------------------------------------------
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Evaluate bot capability signals and return a deterministic adoption
|
|
1172
|
+
* recommendation including install_needed, recommended_tier, reason_codes,
|
|
1173
|
+
* call sequence, and install guidance.
|
|
1174
|
+
*
|
|
1175
|
+
* Read-only, pure metadata. No side effects, no network calls.
|
|
1176
|
+
*
|
|
1177
|
+
* @param {Record<string, unknown>} _cfg Resolved config (unused).
|
|
1178
|
+
* @param {{ signals?: Record<string, boolean> }} params
|
|
1179
|
+
* @returns {object}
|
|
1180
|
+
*/
|
|
1181
|
+
function toolAtfAdoptionAdvisor(_cfg, params = {}) {
|
|
1182
|
+
const { signals } = params;
|
|
1183
|
+
|
|
1184
|
+
// Validate signals shape
|
|
1185
|
+
if (signals !== undefined && signals !== null) {
|
|
1186
|
+
if (typeof signals !== "object" || Array.isArray(signals)) {
|
|
1187
|
+
return nativeToolError(
|
|
1188
|
+
"atf_adoption_advisor",
|
|
1189
|
+
"Invalid 'signals' parameter: must be an object mapping " +
|
|
1190
|
+
"trigger_id keys to boolean values. Example: " +
|
|
1191
|
+
'{"bot_handles_real_capital": true, "bot_retries_automatically": false}',
|
|
1192
|
+
{
|
|
1193
|
+
remediation: [
|
|
1194
|
+
"Provide signals as an object: {trigger_id: true/false, ...}",
|
|
1195
|
+
"Omit signals entirely to evaluate with no triggers matched.",
|
|
1196
|
+
"Valid trigger_ids: " +
|
|
1197
|
+
TRIGGER_MODEL.map((t) => t.trigger_id).join(", "),
|
|
1198
|
+
],
|
|
1199
|
+
},
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const result = evaluateAdoption(signals ?? {});
|
|
1205
|
+
|
|
1206
|
+
const summary = result.install_needed
|
|
1207
|
+
? `ATF adoption recommended (${result.recommended_tier}). ` +
|
|
1208
|
+
`${result.matched_triggers.length} trigger(s) matched. ` +
|
|
1209
|
+
`Highest severity: ${result.highest_severity}.`
|
|
1210
|
+
: "ATF adoption not currently needed. No triggers matched. " +
|
|
1211
|
+
"Monitor for future trigger activation.";
|
|
1212
|
+
|
|
1213
|
+
return nativeToolSuccess("atf_adoption_advisor", summary, result);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// ---------------------------------------------------------------------------
|
|
1217
|
+
// Tool: atf_billing_info — billing / pricing metadata discovery
|
|
1218
|
+
// ---------------------------------------------------------------------------
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Return machine-readable billing and package metadata for ATF.
|
|
1222
|
+
*
|
|
1223
|
+
* Read-only metadata tool — does NOT execute payments or check entitlements.
|
|
1224
|
+
* Bots use this to discover:
|
|
1225
|
+
* - which packages/tiers exist (free core vs advanced)
|
|
1226
|
+
* - pricing and accepted payment assets
|
|
1227
|
+
* - payment rail details (chain, recipient, memo format)
|
|
1228
|
+
* - activation/claim flow guidance
|
|
1229
|
+
* - free-vs-advanced capability split
|
|
1230
|
+
*
|
|
1231
|
+
* Always succeeds (pure metadata, no backend dependency).
|
|
1232
|
+
*
|
|
1233
|
+
* @param {Record<string, unknown>} _cfg Resolved config (unused, for consistency).
|
|
1234
|
+
* @param {{ packageId?: string }} params
|
|
1235
|
+
* @returns {object}
|
|
1236
|
+
*/
|
|
1237
|
+
function toolAtfBillingInfo(_cfg, params = {}) {
|
|
1238
|
+
const { packageId } = params;
|
|
1239
|
+
|
|
1240
|
+
// If a specific package is requested, filter
|
|
1241
|
+
if (packageId && typeof packageId === "string") {
|
|
1242
|
+
const pkg = ATF_BILLING_MANIFEST.packages.find(
|
|
1243
|
+
(p) => p.package_id === packageId,
|
|
1244
|
+
);
|
|
1245
|
+
if (!pkg) {
|
|
1246
|
+
return nativeToolError(
|
|
1247
|
+
"atf_billing_info",
|
|
1248
|
+
`Unknown package_id: '${packageId}'. ` +
|
|
1249
|
+
`Available: ${ATF_BILLING_MANIFEST.packages.map((p) => p.package_id).join(", ")}.`,
|
|
1250
|
+
{
|
|
1251
|
+
remediation: [
|
|
1252
|
+
"Use one of the available package IDs, or omit packageId to see all packages.",
|
|
1253
|
+
],
|
|
1254
|
+
},
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
return nativeToolSuccess(
|
|
1259
|
+
"atf_billing_info",
|
|
1260
|
+
`Billing info for package '${pkg.package_name}' (${pkg.package_tier} tier).`,
|
|
1261
|
+
{
|
|
1262
|
+
package: pkg,
|
|
1263
|
+
payment_rails: ATF_BILLING_MANIFEST.payment_rails,
|
|
1264
|
+
activation: ATF_BILLING_MANIFEST.activation,
|
|
1265
|
+
warnings: ATF_BILLING_MANIFEST.warnings,
|
|
1266
|
+
},
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Full manifest
|
|
1271
|
+
return nativeToolSuccess(
|
|
1272
|
+
"atf_billing_info",
|
|
1273
|
+
"ATF billing metadata: 2 packages (free core + advanced bot). " +
|
|
1274
|
+
"Payment accepted on Solana (USDC preferred). " +
|
|
1275
|
+
"See result for full pricing, payment rails, and activation guidance.",
|
|
1276
|
+
{
|
|
1277
|
+
packages: ATF_BILLING_MANIFEST.packages,
|
|
1278
|
+
payment_rails: ATF_BILLING_MANIFEST.payment_rails,
|
|
1279
|
+
free_vs_advanced: ATF_BILLING_MANIFEST.free_vs_advanced,
|
|
1280
|
+
activation: ATF_BILLING_MANIFEST.activation,
|
|
1281
|
+
warnings: ATF_BILLING_MANIFEST.warnings,
|
|
1282
|
+
},
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// ---------------------------------------------------------------------------
|
|
1287
|
+
// Tool: atf_billing_claim — verify on-chain payment and process claim
|
|
1288
|
+
// ---------------------------------------------------------------------------
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Verify an on-chain Solana payment and process a billing claim for
|
|
1292
|
+
* the ATF Advanced Bot Package.
|
|
1293
|
+
*
|
|
1294
|
+
* Checks: tx exists, finalized, recipient = treasury, asset + amount match
|
|
1295
|
+
* package pricing, no duplicate claim.
|
|
1296
|
+
*
|
|
1297
|
+
* @param {Record<string, unknown>} cfg Resolved config.
|
|
1298
|
+
* @param {object} params
|
|
1299
|
+
* @param {string} params.tx_signature Solana transaction signature.
|
|
1300
|
+
* @param {string} params.wallet Claimant wallet address.
|
|
1301
|
+
* @param {string} params.package_id Package being claimed.
|
|
1302
|
+
* @param {string} [params.asset_symbol] Optional asset hint: "USDC" or "SOL".
|
|
1303
|
+
* @param {string} [params.cluster] Optional cluster: "mainnet-beta" (default).
|
|
1304
|
+
* @returns {Promise<object>}
|
|
1305
|
+
*/
|
|
1306
|
+
async function toolAtfBillingClaim(cfg, params = {}) {
|
|
1307
|
+
const { tx_signature, wallet, package_id, asset_symbol, cluster } = params;
|
|
1308
|
+
|
|
1309
|
+
// Build fetchTransaction from config if RPC access is available
|
|
1310
|
+
let fetchTransaction = null;
|
|
1311
|
+
const rpcUrl = cfg?.solanaRpcUrl;
|
|
1312
|
+
if (rpcUrl && typeof rpcUrl === "string") {
|
|
1313
|
+
fetchTransaction = async (sig, _cluster) => {
|
|
1314
|
+
const body = JSON.stringify({
|
|
1315
|
+
jsonrpc: "2.0",
|
|
1316
|
+
id: 1,
|
|
1317
|
+
method: "getTransaction",
|
|
1318
|
+
params: [
|
|
1319
|
+
sig,
|
|
1320
|
+
{
|
|
1321
|
+
encoding: "jsonParsed",
|
|
1322
|
+
maxSupportedTransactionVersion: 0,
|
|
1323
|
+
commitment: "confirmed",
|
|
1324
|
+
},
|
|
1325
|
+
],
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
const res = await fetch(rpcUrl, {
|
|
1329
|
+
method: "POST",
|
|
1330
|
+
headers: { "Content-Type": "application/json" },
|
|
1331
|
+
body,
|
|
1332
|
+
signal: AbortSignal.timeout(15_000),
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
if (!res.ok) {
|
|
1336
|
+
throw new Error(`RPC HTTP ${res.status}`);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const json = await res.json();
|
|
1340
|
+
if (json.error) {
|
|
1341
|
+
throw new Error(json.error.message ?? JSON.stringify(json.error));
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const tx = json.result;
|
|
1345
|
+
if (!tx) return null;
|
|
1346
|
+
|
|
1347
|
+
// Attach confirmationStatus from RPC response context if available
|
|
1348
|
+
if (!tx.confirmationStatus && json.result?.slot) {
|
|
1349
|
+
tx.confirmationStatus = "confirmed";
|
|
1350
|
+
}
|
|
1351
|
+
return tx;
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const claimResult = await verifyBillingClaim(
|
|
1356
|
+
{ tx_signature, wallet, package_id, asset_symbol, cluster },
|
|
1357
|
+
{ fetchTransaction },
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
// Map claim result to native response envelope
|
|
1361
|
+
if (claimResult.claim_status === CLAIM_STATUS.VERIFIED) {
|
|
1362
|
+
return nativeToolSuccess(
|
|
1363
|
+
"atf_billing_claim",
|
|
1364
|
+
`Billing claim verified: ${claimResult.amount_received} ` +
|
|
1365
|
+
`${claimResult.asset_symbol} received for package ` +
|
|
1366
|
+
`'${claimResult.package_id}'. Entitlement: ${claimResult.entitlement_status}.`,
|
|
1367
|
+
claimResult,
|
|
1368
|
+
{ warnings: claimResult.warnings },
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Denied or error
|
|
1373
|
+
const summary =
|
|
1374
|
+
`Billing claim ${claimResult.claim_status}: ` +
|
|
1375
|
+
`${claimResult.deny_code ?? "unknown error"}. ` +
|
|
1376
|
+
(claimResult.remediation.length > 0
|
|
1377
|
+
? claimResult.remediation[0]
|
|
1378
|
+
: "See result for details.");
|
|
1379
|
+
|
|
1380
|
+
return nativeToolError("atf_billing_claim", summary, {
|
|
1381
|
+
remediation: claimResult.remediation,
|
|
1382
|
+
warnings: claimResult.warnings,
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// ---------------------------------------------------------------------------
|
|
1387
|
+
// Safe handler wrapper — graceful degradation for all tool handlers
|
|
1388
|
+
// ---------------------------------------------------------------------------
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Wrap a tool handler so runtime errors never crash the gateway.
|
|
1392
|
+
* Returns an errorResponse on any uncaught exception.
|
|
1393
|
+
*
|
|
1394
|
+
* @param {string} toolName
|
|
1395
|
+
* @param {Function} handler
|
|
1396
|
+
* @returns {Function}
|
|
1397
|
+
*/
|
|
1398
|
+
function safeHandler(toolName, handler) {
|
|
1399
|
+
return async (params) => {
|
|
1400
|
+
try {
|
|
1401
|
+
return await handler(params);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
return nativeToolError(
|
|
1404
|
+
toolName,
|
|
1405
|
+
`[${PLUGIN_ID}] ${toolName} failed: ${err?.message ?? String(err)}. ` +
|
|
1406
|
+
"ATF core CLI fallback is still available.",
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
652
1412
|
// ---------------------------------------------------------------------------
|
|
653
1413
|
// Plugin entry-point
|
|
654
1414
|
// ---------------------------------------------------------------------------
|
|
@@ -660,11 +1420,42 @@ export function buildHumanSummary(reportJson) {
|
|
|
660
1420
|
* All tools are registered as OPTIONAL — agents / operators must allowlist
|
|
661
1421
|
* them before they can be used.
|
|
662
1422
|
*
|
|
1423
|
+
* Safe: catches all registration errors, logs warnings, never throws.
|
|
1424
|
+
*
|
|
663
1425
|
* @param {{ registerTool: Function, config: Record<string, unknown> }} api
|
|
664
1426
|
*/
|
|
665
1427
|
export default function atfPlugin(api) {
|
|
1428
|
+
if (!api || typeof api.registerTool !== "function") {
|
|
1429
|
+
// Defensive: if api is missing or malformed, log and bail out silently.
|
|
1430
|
+
// This prevents crashing the OpenClaw gateway on bad plugin load.
|
|
1431
|
+
if (typeof console !== "undefined") {
|
|
1432
|
+
console.warn(
|
|
1433
|
+
`[${PLUGIN_ID}] Plugin loaded with invalid api object. ` +
|
|
1434
|
+
"Skipping tool registration. ATF CLI fallback is still available.",
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
666
1440
|
const cfg = resolveConfig(api?.config ?? {});
|
|
667
1441
|
|
|
1442
|
+
// ---- Tool 0: atf_health -------------------------------------------------
|
|
1443
|
+
api.registerTool({
|
|
1444
|
+
name: "atf_health",
|
|
1445
|
+
optional: true,
|
|
1446
|
+
description:
|
|
1447
|
+
"Check ATF CLI and API backend availability. " +
|
|
1448
|
+
"Returns structured health status including version, reachability, " +
|
|
1449
|
+
"and preferred mode. Use this to verify ATF is ready before calling " +
|
|
1450
|
+
"other ATF tools.",
|
|
1451
|
+
inputSchema: {
|
|
1452
|
+
type: "object",
|
|
1453
|
+
properties: {},
|
|
1454
|
+
additionalProperties: false,
|
|
1455
|
+
},
|
|
1456
|
+
handler: safeHandler("atf_health", () => toolAtfHealth(cfg)),
|
|
1457
|
+
});
|
|
1458
|
+
|
|
668
1459
|
// ---- Tool 1: atf_discover -----------------------------------------------
|
|
669
1460
|
api.registerTool({
|
|
670
1461
|
name: "atf_discover",
|
|
@@ -685,7 +1476,7 @@ export default function atfPlugin(api) {
|
|
|
685
1476
|
},
|
|
686
1477
|
additionalProperties: false,
|
|
687
1478
|
},
|
|
688
|
-
handler: (params) => toolAtfDiscover(cfg, params),
|
|
1479
|
+
handler: safeHandler("atf_discover", (params) => toolAtfDiscover(cfg, params)),
|
|
689
1480
|
});
|
|
690
1481
|
|
|
691
1482
|
// ---- Tool 2: atf_bootstrap_plan -----------------------------------------
|
|
@@ -709,7 +1500,7 @@ export default function atfPlugin(api) {
|
|
|
709
1500
|
},
|
|
710
1501
|
additionalProperties: false,
|
|
711
1502
|
},
|
|
712
|
-
handler: (params) => toolAtfBootstrapPlan(cfg, params),
|
|
1503
|
+
handler: safeHandler("atf_bootstrap_plan", (params) => toolAtfBootstrapPlan(cfg, params)),
|
|
713
1504
|
});
|
|
714
1505
|
|
|
715
1506
|
// ---- Tool 3: atf_bootstrap_execute_safe ---------------------------------
|
|
@@ -736,7 +1527,7 @@ export default function atfPlugin(api) {
|
|
|
736
1527
|
},
|
|
737
1528
|
additionalProperties: false,
|
|
738
1529
|
},
|
|
739
|
-
handler: (params) => toolAtfBootstrapExecuteSafe(cfg, params),
|
|
1530
|
+
handler: safeHandler("atf_bootstrap_execute_safe", (params) => toolAtfBootstrapExecuteSafe(cfg, params)),
|
|
740
1531
|
});
|
|
741
1532
|
|
|
742
1533
|
// ---- Tool 4: atf_protect_intent -----------------------------------------
|
|
@@ -772,7 +1563,7 @@ export default function atfPlugin(api) {
|
|
|
772
1563
|
},
|
|
773
1564
|
additionalProperties: false,
|
|
774
1565
|
},
|
|
775
|
-
handler: (params) => toolAtfProtectIntent(cfg, params),
|
|
1566
|
+
handler: safeHandler("atf_protect_intent", (params) => toolAtfProtectIntent(cfg, params)),
|
|
776
1567
|
});
|
|
777
1568
|
|
|
778
1569
|
// ---- Tool 5: atf_verify_receipt -----------------------------------------
|
|
@@ -794,7 +1585,7 @@ export default function atfPlugin(api) {
|
|
|
794
1585
|
},
|
|
795
1586
|
additionalProperties: false,
|
|
796
1587
|
},
|
|
797
|
-
handler: (params) => toolAtfVerifyReceipt(cfg, params),
|
|
1588
|
+
handler: safeHandler("atf_verify_receipt", (params) => toolAtfVerifyReceipt(cfg, params)),
|
|
798
1589
|
});
|
|
799
1590
|
|
|
800
1591
|
// ---- Tool 6: atf_report_savings -----------------------------------------
|
|
@@ -822,6 +1613,313 @@ export default function atfPlugin(api) {
|
|
|
822
1613
|
},
|
|
823
1614
|
additionalProperties: false,
|
|
824
1615
|
},
|
|
825
|
-
handler: (params) => toolAtfReportSavings(cfg, params),
|
|
1616
|
+
handler: safeHandler("atf_report_savings", (params) => toolAtfReportSavings(cfg, params)),
|
|
826
1617
|
});
|
|
1618
|
+
|
|
1619
|
+
// ---- Tool 7: atf_integration_doctor ------------------------------------
|
|
1620
|
+
// Collect registered tool names for the doctor report.
|
|
1621
|
+
// We need to register it after all other tools so it can see them.
|
|
1622
|
+
const registeredToolNames = [...api.tools?.keys?.() ?? []];
|
|
1623
|
+
// The doctor + remaining tools are about to be added
|
|
1624
|
+
registeredToolNames.push(
|
|
1625
|
+
"atf_integration_doctor",
|
|
1626
|
+
"atf_bot_preflight",
|
|
1627
|
+
"atf_tx_explain",
|
|
1628
|
+
"atf_billing_info",
|
|
1629
|
+
"atf_adoption_advisor",
|
|
1630
|
+
"atf_billing_claim",
|
|
1631
|
+
);
|
|
1632
|
+
|
|
1633
|
+
api.registerTool({
|
|
1634
|
+
name: "atf_integration_doctor",
|
|
1635
|
+
optional: true,
|
|
1636
|
+
description:
|
|
1637
|
+
"Run ATF integration readiness check. Reports plugin loading status, " +
|
|
1638
|
+
"CLI/API availability, backend preference vs effective backend, " +
|
|
1639
|
+
"registered tools, configuration warnings, and remediation steps. " +
|
|
1640
|
+
"Use this to validate and troubleshoot the ATF OpenClaw integration.",
|
|
1641
|
+
inputSchema: {
|
|
1642
|
+
type: "object",
|
|
1643
|
+
properties: {},
|
|
1644
|
+
additionalProperties: false,
|
|
1645
|
+
},
|
|
1646
|
+
handler: safeHandler(
|
|
1647
|
+
"atf_integration_doctor",
|
|
1648
|
+
() => toolAtfIntegrationDoctor(cfg, registeredToolNames),
|
|
1649
|
+
),
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
// ---- Tool 8: atf_bot_preflight ------------------------------------------
|
|
1653
|
+
api.registerTool({
|
|
1654
|
+
name: "atf_bot_preflight",
|
|
1655
|
+
optional: true,
|
|
1656
|
+
description:
|
|
1657
|
+
"Pre-session readiness check: is ATF ready to protect intents right now? " +
|
|
1658
|
+
"Returns a structured go/no-go signal with backend availability, " +
|
|
1659
|
+
"config validity, and remediation steps. Lighter than atf_integration_doctor — " +
|
|
1660
|
+
"use this before starting a bot session to confirm ATF is operational.",
|
|
1661
|
+
inputSchema: {
|
|
1662
|
+
type: "object",
|
|
1663
|
+
properties: {},
|
|
1664
|
+
additionalProperties: false,
|
|
1665
|
+
},
|
|
1666
|
+
handler: safeHandler(
|
|
1667
|
+
"atf_bot_preflight",
|
|
1668
|
+
() => toolAtfBotPreflight(cfg, api?.config),
|
|
1669
|
+
),
|
|
1670
|
+
});
|
|
1671
|
+
|
|
1672
|
+
// ---- Tool 9: atf_tx_explain ---------------------------------------------
|
|
1673
|
+
api.registerTool({
|
|
1674
|
+
name: "atf_tx_explain",
|
|
1675
|
+
optional: true,
|
|
1676
|
+
description:
|
|
1677
|
+
"Explain an ATF deny decision or receipt in human terms. " +
|
|
1678
|
+
"Maps each reason code to a structured explanation with category, " +
|
|
1679
|
+
"plain-language description, and remediation guidance. " +
|
|
1680
|
+
"Does not re-evaluate policy — only explains existing decisions. " +
|
|
1681
|
+
"Accepts either 'reasonCodes' (string[]) or 'receipt' (object with reason_codes).",
|
|
1682
|
+
inputSchema: {
|
|
1683
|
+
type: "object",
|
|
1684
|
+
properties: {
|
|
1685
|
+
reasonCodes: {
|
|
1686
|
+
type: "array",
|
|
1687
|
+
items: { type: "string" },
|
|
1688
|
+
description:
|
|
1689
|
+
"Array of ATF reason codes to explain (e.g. ['DEX_VENUE_NOT_ALLOWED']).",
|
|
1690
|
+
},
|
|
1691
|
+
receipt: {
|
|
1692
|
+
type: "object",
|
|
1693
|
+
description:
|
|
1694
|
+
"An ATF receipt or decision object with a reason_codes field. " +
|
|
1695
|
+
"If provided, reason codes are extracted from this object.",
|
|
1696
|
+
},
|
|
1697
|
+
},
|
|
1698
|
+
additionalProperties: false,
|
|
1699
|
+
},
|
|
1700
|
+
handler: safeHandler(
|
|
1701
|
+
"atf_tx_explain",
|
|
1702
|
+
(params) => toolAtfTxExplain(cfg, params),
|
|
1703
|
+
),
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// ---- Tool 10: atf_billing_info ------------------------------------------
|
|
1707
|
+
api.registerTool({
|
|
1708
|
+
name: "atf_billing_info",
|
|
1709
|
+
optional: true,
|
|
1710
|
+
description:
|
|
1711
|
+
"Discover ATF billing, pricing, and package metadata. " +
|
|
1712
|
+
"Returns machine-readable info about free-core vs advanced-package tiers, " +
|
|
1713
|
+
"accepted payment assets (USDC, SOL on Solana), payment rail details, " +
|
|
1714
|
+
"and activation/claim flow guidance. " +
|
|
1715
|
+
"Read-only metadata — does NOT execute payments.",
|
|
1716
|
+
inputSchema: {
|
|
1717
|
+
type: "object",
|
|
1718
|
+
properties: {
|
|
1719
|
+
packageId: {
|
|
1720
|
+
type: "string",
|
|
1721
|
+
description:
|
|
1722
|
+
"Optional package ID to filter (e.g. 'atf_free_core' or 'atf_advanced_bot'). " +
|
|
1723
|
+
"Omit to see all packages.",
|
|
1724
|
+
},
|
|
1725
|
+
},
|
|
1726
|
+
additionalProperties: false,
|
|
1727
|
+
},
|
|
1728
|
+
handler: safeHandler(
|
|
1729
|
+
"atf_billing_info",
|
|
1730
|
+
(params) => toolAtfBillingInfo(cfg, params),
|
|
1731
|
+
),
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
// ---- Tool 11: atf_adoption_advisor --------------------------------------
|
|
1735
|
+
api.registerTool({
|
|
1736
|
+
name: "atf_adoption_advisor",
|
|
1737
|
+
optional: true,
|
|
1738
|
+
description:
|
|
1739
|
+
"Evaluate bot capability signals and return a deterministic ATF " +
|
|
1740
|
+
"adoption recommendation. Returns install_needed, recommended_tier " +
|
|
1741
|
+
"(free_core / advanced_bot / none), matched triggers with severity, " +
|
|
1742
|
+
"safe default call sequence, install/enable guidance, and next actions. " +
|
|
1743
|
+
"Read-only, pure metadata — no side effects.",
|
|
1744
|
+
inputSchema: {
|
|
1745
|
+
type: "object",
|
|
1746
|
+
properties: {
|
|
1747
|
+
signals: {
|
|
1748
|
+
type: "object",
|
|
1749
|
+
description:
|
|
1750
|
+
"Map of trigger_id to boolean. Example: " +
|
|
1751
|
+
'{"bot_handles_real_capital": true, "bot_retries_automatically": false}. ' +
|
|
1752
|
+
"Valid trigger_ids: bot_handles_real_capital, " +
|
|
1753
|
+
"bot_signs_or_submits_transactions, bot_retries_automatically, " +
|
|
1754
|
+
"bot_lacks_audit_trail, bot_operates_without_spending_limits, " +
|
|
1755
|
+
"operator_cannot_explain_failures, " +
|
|
1756
|
+
"bot_uses_multiple_dexes_or_protocols, " +
|
|
1757
|
+
"bot_runs_unattended, bot_needs_operator_controls. " +
|
|
1758
|
+
"Omit to evaluate with no triggers (returns install_needed=false).",
|
|
1759
|
+
additionalProperties: { type: "boolean" },
|
|
1760
|
+
},
|
|
1761
|
+
},
|
|
1762
|
+
additionalProperties: false,
|
|
1763
|
+
},
|
|
1764
|
+
handler: safeHandler(
|
|
1765
|
+
"atf_adoption_advisor",
|
|
1766
|
+
(params) => toolAtfAdoptionAdvisor(cfg, params),
|
|
1767
|
+
),
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
// ---- Tool 12: atf_billing_claim ----------------------------------------
|
|
1771
|
+
api.registerTool({
|
|
1772
|
+
name: "atf_billing_claim",
|
|
1773
|
+
optional: true,
|
|
1774
|
+
description:
|
|
1775
|
+
"Verify an on-chain Solana payment and process a billing claim for " +
|
|
1776
|
+
"the ATF Advanced Bot Package. Checks transaction existence, finality, " +
|
|
1777
|
+
"treasury recipient, asset type (USDC or SOL), and amount against " +
|
|
1778
|
+
"published pricing. Returns a deterministic claim result with " +
|
|
1779
|
+
"entitlement status. Requires tx_signature, wallet, and package_id.",
|
|
1780
|
+
inputSchema: {
|
|
1781
|
+
type: "object",
|
|
1782
|
+
properties: {
|
|
1783
|
+
tx_signature: {
|
|
1784
|
+
type: "string",
|
|
1785
|
+
description:
|
|
1786
|
+
"Solana transaction signature (base58). " +
|
|
1787
|
+
"The payment transaction to verify.",
|
|
1788
|
+
},
|
|
1789
|
+
wallet: {
|
|
1790
|
+
type: "string",
|
|
1791
|
+
description:
|
|
1792
|
+
"Claimant wallet address (Solana public key). " +
|
|
1793
|
+
"The wallet that made the payment.",
|
|
1794
|
+
},
|
|
1795
|
+
package_id: {
|
|
1796
|
+
type: "string",
|
|
1797
|
+
description:
|
|
1798
|
+
"Package to claim. Currently: 'atf_advanced_bot'. " +
|
|
1799
|
+
"Use atf_billing_info to discover available packages.",
|
|
1800
|
+
},
|
|
1801
|
+
asset_symbol: {
|
|
1802
|
+
type: "string",
|
|
1803
|
+
description:
|
|
1804
|
+
"Optional: asset hint — 'USDC' or 'SOL'. " +
|
|
1805
|
+
"If omitted, both are checked automatically.",
|
|
1806
|
+
},
|
|
1807
|
+
cluster: {
|
|
1808
|
+
type: "string",
|
|
1809
|
+
description:
|
|
1810
|
+
"Optional: Solana cluster — 'mainnet-beta' (default). " +
|
|
1811
|
+
"Production entitlements require mainnet-beta.",
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
required: ["tx_signature", "wallet", "package_id"],
|
|
1815
|
+
additionalProperties: false,
|
|
1816
|
+
},
|
|
1817
|
+
handler: safeHandler(
|
|
1818
|
+
"atf_billing_claim",
|
|
1819
|
+
(params) => toolAtfBillingClaim(cfg, params),
|
|
1820
|
+
),
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// ---------------------------------------------------------------------------
|
|
1825
|
+
// OpenClaw lifecycle exports: register / activate / deactivate
|
|
1826
|
+
// ---------------------------------------------------------------------------
|
|
1827
|
+
|
|
1828
|
+
/**
|
|
1829
|
+
* OpenClaw lifecycle: register tools.
|
|
1830
|
+
* Equivalent to the default export (atfPlugin). Provided as a named export
|
|
1831
|
+
* to match the expected OpenClaw plugin contract.
|
|
1832
|
+
*
|
|
1833
|
+
* Safe: never throws. If api is invalid, logs a warning and returns.
|
|
1834
|
+
*
|
|
1835
|
+
* @param {{ registerTool: Function, config: Record<string, unknown> }} api
|
|
1836
|
+
*/
|
|
1837
|
+
export function register(api) {
|
|
1838
|
+
try {
|
|
1839
|
+
atfPlugin(api);
|
|
1840
|
+
} catch (err) {
|
|
1841
|
+
if (typeof console !== "undefined") {
|
|
1842
|
+
console.warn(
|
|
1843
|
+
`[${PLUGIN_ID}] register() failed: ${err?.message ?? String(err)}. ` +
|
|
1844
|
+
"ATF CLI fallback is still available.",
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* OpenClaw lifecycle: activate plugin.
|
|
1852
|
+
* Called after register() succeeds. Runs non-blocking config validation
|
|
1853
|
+
* and surfaces warnings/errors to the operator via console.warn.
|
|
1854
|
+
*
|
|
1855
|
+
* CRITICAL RULE: Config validation NEVER blocks activation.
|
|
1856
|
+
* Invalid config → warn + degrade gracefully. Never throw.
|
|
1857
|
+
*
|
|
1858
|
+
* @param {{ config?: Record<string, unknown> }} [ctx]
|
|
1859
|
+
*/
|
|
1860
|
+
export function activate(ctx) {
|
|
1861
|
+
try {
|
|
1862
|
+
// Import config validation lazily to avoid circular dependency
|
|
1863
|
+
// at module-evaluation time (config.mjs is side-effect-free).
|
|
1864
|
+
const { validateConfig, formatActivationWarnings } = /** @type {any} */ (
|
|
1865
|
+
// Dynamic import would be async—config.mjs is already loaded at this
|
|
1866
|
+
// point (register imports it transitively via doctor.mjs), so we use
|
|
1867
|
+
// a synchronous re-export pattern instead. The helper is pure &
|
|
1868
|
+
// deterministic, so calling it synchronously is safe.
|
|
1869
|
+
_configModule
|
|
1870
|
+
);
|
|
1871
|
+
|
|
1872
|
+
const rawConfig = ctx?.config ?? undefined;
|
|
1873
|
+
const result = validateConfig(rawConfig);
|
|
1874
|
+
const lines = formatActivationWarnings(result);
|
|
1875
|
+
|
|
1876
|
+
if (lines.length > 0 && typeof console !== "undefined") {
|
|
1877
|
+
for (const line of lines) {
|
|
1878
|
+
console.warn(line);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
} catch {
|
|
1882
|
+
// Activation must NEVER fail. Swallow any unexpected error.
|
|
1883
|
+
if (typeof console !== "undefined") {
|
|
1884
|
+
console.warn(
|
|
1885
|
+
`[${PLUGIN_ID}] activate() config validation failed unexpectedly. ` +
|
|
1886
|
+
"Plugin is still operational. Run atf_integration_doctor for details.",
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/**
|
|
1893
|
+
* OpenClaw lifecycle: deactivate plugin.
|
|
1894
|
+
* Called when the plugin is disabled or the gateway shuts down.
|
|
1895
|
+
* Currently a no-op. Provided for forward compatibility.
|
|
1896
|
+
*/
|
|
1897
|
+
export function deactivate() {
|
|
1898
|
+
// No-op: ATF holds no persistent connections or state.
|
|
827
1899
|
}
|
|
1900
|
+
|
|
1901
|
+
// REASON_CODE_CATALOG is exported at definition site (above).
|
|
1902
|
+
|
|
1903
|
+
// Re-export adoption advisor primitives for external consumers.
|
|
1904
|
+
export { evaluateAdoption, TRIGGER_MODEL, INSTALL_GUIDANCE, SAFE_CALL_SEQUENCE };
|
|
1905
|
+
|
|
1906
|
+
// Re-export billing claim primitives for external consumers and tests.
|
|
1907
|
+
export {
|
|
1908
|
+
verifyBillingClaim,
|
|
1909
|
+
CLAIM_STATUS,
|
|
1910
|
+
CLAIM_DENY_CODES,
|
|
1911
|
+
TREASURY_ADDRESS,
|
|
1912
|
+
USDC_MINT,
|
|
1913
|
+
KNOWN_PACKAGES,
|
|
1914
|
+
checkDuplicate,
|
|
1915
|
+
_resetClaimTracking,
|
|
1916
|
+
};
|
|
1917
|
+
|
|
1918
|
+
// Re-export universal contract layer for external consumers,
|
|
1919
|
+
// future adapters, and SDK/CLI surfaces.
|
|
1920
|
+
export {
|
|
1921
|
+
CONTRACT_FAMILIES,
|
|
1922
|
+
TOOL_FAMILY_MAP,
|
|
1923
|
+
CANONICAL_TOOLS,
|
|
1924
|
+
CANONICAL_TOOL_COUNT,
|
|
1925
|
+
};
|