agentbnb 4.0.2 → 4.0.4
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/README.md +31 -2
- package/dist/card-RNEWSAQ6.js +88 -0
- package/dist/{chunk-7NA43XCG.js → chunk-5QGXARLJ.js} +4 -2
- package/dist/chunk-EVBX22YU.js +68 -0
- package/dist/chunk-JXEOE7HX.js +295 -0
- package/dist/chunk-UB2NPFC7.js +165 -0
- package/dist/cli/index.js +6 -4
- package/dist/conductor-mode-ESGFZ6T5.js +739 -0
- package/dist/{execute-PNGQOMYO.js → execute-QH6F54D7.js} +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -2
- package/dist/peers-E4MKNNDN.js +12 -0
- package/dist/{serve-skill-TPHZH6BS.js → serve-skill-Q6NHX2RA.js} +1 -1
- package/dist/{server-365V3GYD.js → server-B5E566CI.js} +1 -1
- package/dist/skills/agentbnb/bootstrap.js +2001 -0
- package/openclaw.plugin.json +54 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
## Get started in one command
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
openclaw install agentbnb
|
|
20
|
+
openclaw plugins install agentbnb
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Your agent joins the network, shares its idle skills, and earns credits from peers. Use those credits to access capabilities your agent never had.
|
|
@@ -27,7 +27,7 @@ Your agent joins the network, shares its idle skills, and earns credits from pee
|
|
|
27
27
|
|
|
28
28
|
| Tool | Command |
|
|
29
29
|
|------|---------|
|
|
30
|
-
| **OpenClaw** | `openclaw install agentbnb` |
|
|
30
|
+
| **OpenClaw** | `openclaw plugins install agentbnb` |
|
|
31
31
|
| **MCP (Claude Code / Cursor / Windsurf / Cline)** | `claude mcp add agentbnb -- agentbnb mcp-server` |
|
|
32
32
|
| **npm** | `npm install -g agentbnb` |
|
|
33
33
|
| **pnpm** | `pnpm add -g agentbnb` |
|
|
@@ -62,6 +62,35 @@ Read the full design philosophy in [AGENT-NATIVE-PROTOCOL.md](AGENT-NATIVE-PROTO
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
+
## First cross-machine transaction — live proof
|
|
66
|
+
|
|
67
|
+
On 2026-03-21, two physical machines completed a full E2E trade over the public relay:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Machine 2 (agent-2a44d8f0) hub.agentbnb.dev Machine 1 (Xiaoher-C)
|
|
71
|
+
│ │ │
|
|
72
|
+
│ agentbnb request --cost 5 │ │
|
|
73
|
+
│ ─────────────────────────────► │ │
|
|
74
|
+
│ │ hold 5 credits (escrow) │
|
|
75
|
+
│ │ ──────────────────────────► │
|
|
76
|
+
│ │ incoming_request │
|
|
77
|
+
│ │ ────────────────────────────►│
|
|
78
|
+
│ │ ElevenLabs TTS API │
|
|
79
|
+
│ │ ◄────│
|
|
80
|
+
│ │ relay_response (audio_base64│
|
|
81
|
+
│ │ ◄────────────────────────────│
|
|
82
|
+
│ │ settle 5 credits → Xiaoher-C│
|
|
83
|
+
│ result: { audio_base64: "..." } │ │
|
|
84
|
+
│ ◄─────────────────────────────── │ │
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- **No shared infrastructure** between the two machines — only the public relay
|
|
88
|
+
- **Credits moved**: 5 credits from `agent-2a44d8f0` → escrowed → settled to `Xiaoher-C`
|
|
89
|
+
- **Skill executed**: ElevenLabs TTS via `CommandExecutor` on Machine 1
|
|
90
|
+
- **Result**: MP3 audio delivered as base64 to Machine 2
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
65
94
|
## Agent Hub
|
|
66
95
|
|
|
67
96
|
<p align="center">
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CapabilityCardV2Schema
|
|
3
|
+
} from "./chunk-UB2NPFC7.js";
|
|
4
|
+
|
|
5
|
+
// src/conductor/card.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
var CONDUCTOR_OWNER = "agentbnb-conductor";
|
|
8
|
+
var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
|
|
9
|
+
function ownerToCardId(owner) {
|
|
10
|
+
const hash = createHash("sha256").update(owner).digest("hex").slice(0, 32);
|
|
11
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-8${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
12
|
+
}
|
|
13
|
+
function buildConductorCard(owner) {
|
|
14
|
+
const cardOwner = owner ?? CONDUCTOR_OWNER;
|
|
15
|
+
const cardId = owner ? ownerToCardId(owner) : CONDUCTOR_CARD_ID;
|
|
16
|
+
const card = {
|
|
17
|
+
spec_version: "2.0",
|
|
18
|
+
id: cardId,
|
|
19
|
+
owner: cardOwner,
|
|
20
|
+
agent_name: "AgentBnB Conductor",
|
|
21
|
+
skills: [
|
|
22
|
+
{
|
|
23
|
+
id: "orchestrate",
|
|
24
|
+
name: "Task Orchestration",
|
|
25
|
+
description: "Decomposes complex tasks and coordinates multi-agent execution",
|
|
26
|
+
level: 3,
|
|
27
|
+
inputs: [
|
|
28
|
+
{
|
|
29
|
+
name: "task",
|
|
30
|
+
type: "text",
|
|
31
|
+
description: "Natural language task description"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
outputs: [
|
|
35
|
+
{
|
|
36
|
+
name: "result",
|
|
37
|
+
type: "json",
|
|
38
|
+
description: "Aggregated execution results"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
pricing: { credits_per_call: 5 }
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "plan",
|
|
45
|
+
name: "Execution Planning",
|
|
46
|
+
description: "Returns an execution plan with cost estimate without executing",
|
|
47
|
+
level: 1,
|
|
48
|
+
inputs: [
|
|
49
|
+
{
|
|
50
|
+
name: "task",
|
|
51
|
+
type: "text",
|
|
52
|
+
description: "Natural language task description"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
outputs: [
|
|
56
|
+
{
|
|
57
|
+
name: "plan",
|
|
58
|
+
type: "json",
|
|
59
|
+
description: "Execution plan with cost breakdown"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
pricing: { credits_per_call: 1 }
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
availability: { online: true }
|
|
66
|
+
};
|
|
67
|
+
return CapabilityCardV2Schema.parse(card);
|
|
68
|
+
}
|
|
69
|
+
function registerConductorCard(db) {
|
|
70
|
+
const card = buildConductorCard();
|
|
71
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
72
|
+
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(card.id);
|
|
73
|
+
if (existing) {
|
|
74
|
+
db.prepare(
|
|
75
|
+
"UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
|
|
76
|
+
).run(JSON.stringify(card), now, card.id);
|
|
77
|
+
} else {
|
|
78
|
+
db.prepare(
|
|
79
|
+
"INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
|
80
|
+
).run(card.id, card.owner, JSON.stringify(card), now, now);
|
|
81
|
+
}
|
|
82
|
+
return card;
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
CONDUCTOR_OWNER,
|
|
86
|
+
buildConductorCard,
|
|
87
|
+
registerConductorCard
|
|
88
|
+
};
|
|
@@ -52,7 +52,8 @@ async function executeCapabilityRequest(opts) {
|
|
|
52
52
|
skillExecutor,
|
|
53
53
|
handlerUrl,
|
|
54
54
|
timeoutMs = 3e5,
|
|
55
|
-
onProgress
|
|
55
|
+
onProgress,
|
|
56
|
+
relayAuthorized = false
|
|
56
57
|
} = opts;
|
|
57
58
|
const card = getCard(registryDb, cardId);
|
|
58
59
|
if (!card) {
|
|
@@ -77,7 +78,8 @@ async function executeCapabilityRequest(opts) {
|
|
|
77
78
|
}
|
|
78
79
|
let escrowId = null;
|
|
79
80
|
let isRemoteEscrow = false;
|
|
80
|
-
if (
|
|
81
|
+
if (relayAuthorized) {
|
|
82
|
+
} else if (receipt) {
|
|
81
83
|
const { signature, ...receiptData2 } = receipt;
|
|
82
84
|
const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
|
|
83
85
|
const valid = verifyEscrowReceipt(receiptData2, signature, publicKeyBuf);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/cli/peers.ts
|
|
2
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3
|
+
import { join as join2 } from "path";
|
|
4
|
+
|
|
5
|
+
// src/cli/config.ts
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
function getConfigDir() {
|
|
10
|
+
return process.env["AGENTBNB_DIR"] ?? join(homedir(), ".agentbnb");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/cli/peers.ts
|
|
14
|
+
function getPeersPath() {
|
|
15
|
+
return join2(getConfigDir(), "peers.json");
|
|
16
|
+
}
|
|
17
|
+
function loadPeers() {
|
|
18
|
+
const peersPath = getPeersPath();
|
|
19
|
+
if (!existsSync2(peersPath)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const raw = readFileSync2(peersPath, "utf-8");
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writePeers(peers) {
|
|
30
|
+
const dir = getConfigDir();
|
|
31
|
+
if (!existsSync2(dir)) {
|
|
32
|
+
mkdirSync2(dir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
writeFileSync2(getPeersPath(), JSON.stringify(peers, null, 2), "utf-8");
|
|
35
|
+
}
|
|
36
|
+
function savePeer(peer) {
|
|
37
|
+
const peers = loadPeers();
|
|
38
|
+
const lowerName = peer.name.toLowerCase();
|
|
39
|
+
const existing = peers.findIndex((p) => p.name.toLowerCase() === lowerName);
|
|
40
|
+
if (existing >= 0) {
|
|
41
|
+
peers[existing] = peer;
|
|
42
|
+
} else {
|
|
43
|
+
peers.push(peer);
|
|
44
|
+
}
|
|
45
|
+
writePeers(peers);
|
|
46
|
+
}
|
|
47
|
+
function removePeer(name) {
|
|
48
|
+
const peers = loadPeers();
|
|
49
|
+
const lowerName = name.toLowerCase();
|
|
50
|
+
const filtered = peers.filter((p) => p.name.toLowerCase() !== lowerName);
|
|
51
|
+
if (filtered.length === peers.length) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
writePeers(filtered);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
function findPeer(name) {
|
|
58
|
+
const peers = loadPeers();
|
|
59
|
+
const lowerName = name.toLowerCase();
|
|
60
|
+
return peers.find((p) => p.name.toLowerCase() === lowerName) ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
loadPeers,
|
|
65
|
+
savePeer,
|
|
66
|
+
removePeer,
|
|
67
|
+
findPeer
|
|
68
|
+
};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentBnBError
|
|
3
|
+
} from "./chunk-UB2NPFC7.js";
|
|
4
|
+
|
|
5
|
+
// src/autonomy/tiers.ts
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
var DEFAULT_AUTONOMY_CONFIG = {
|
|
8
|
+
tier1_max_credits: 0,
|
|
9
|
+
tier2_max_credits: 0
|
|
10
|
+
};
|
|
11
|
+
function getAutonomyTier(creditAmount, config) {
|
|
12
|
+
if (creditAmount < config.tier1_max_credits) return 1;
|
|
13
|
+
if (creditAmount < config.tier2_max_credits) return 2;
|
|
14
|
+
return 3;
|
|
15
|
+
}
|
|
16
|
+
function insertAuditEvent(db, event) {
|
|
17
|
+
const isShareEvent = event.type === "auto_share" || event.type === "auto_share_notify" || event.type === "auto_share_pending";
|
|
18
|
+
const cardId = isShareEvent ? "system" : event.card_id;
|
|
19
|
+
const creditsCharged = isShareEvent ? 0 : event.credits;
|
|
20
|
+
const stmt = db.prepare(`
|
|
21
|
+
INSERT INTO request_log (
|
|
22
|
+
id, card_id, card_name, requester, status, latency_ms, credits_charged,
|
|
23
|
+
created_at, skill_id, action_type, tier_invoked
|
|
24
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
25
|
+
`);
|
|
26
|
+
stmt.run(
|
|
27
|
+
randomUUID(),
|
|
28
|
+
cardId,
|
|
29
|
+
"autonomy-audit",
|
|
30
|
+
"self",
|
|
31
|
+
"success",
|
|
32
|
+
0,
|
|
33
|
+
creditsCharged,
|
|
34
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
event.skill_id,
|
|
36
|
+
event.type,
|
|
37
|
+
event.tier_invoked
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/credit/ledger.ts
|
|
42
|
+
import Database from "better-sqlite3";
|
|
43
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
44
|
+
var CREDIT_SCHEMA = `
|
|
45
|
+
CREATE TABLE IF NOT EXISTS credit_balances (
|
|
46
|
+
owner TEXT PRIMARY KEY,
|
|
47
|
+
balance INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
updated_at TEXT NOT NULL
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
owner TEXT NOT NULL,
|
|
54
|
+
amount INTEGER NOT NULL,
|
|
55
|
+
reason TEXT NOT NULL,
|
|
56
|
+
reference_id TEXT,
|
|
57
|
+
created_at TEXT NOT NULL
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS credit_escrow (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
owner TEXT NOT NULL,
|
|
63
|
+
amount INTEGER NOT NULL,
|
|
64
|
+
card_id TEXT NOT NULL,
|
|
65
|
+
status TEXT NOT NULL DEFAULT 'held',
|
|
66
|
+
created_at TEXT NOT NULL,
|
|
67
|
+
settled_at TEXT
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_owner ON credit_transactions(owner, created_at);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_escrow_owner ON credit_escrow(owner);
|
|
72
|
+
`;
|
|
73
|
+
function openCreditDb(path = ":memory:") {
|
|
74
|
+
const db = new Database(path);
|
|
75
|
+
db.pragma("journal_mode = WAL");
|
|
76
|
+
db.pragma("foreign_keys = ON");
|
|
77
|
+
db.exec(CREDIT_SCHEMA);
|
|
78
|
+
return db;
|
|
79
|
+
}
|
|
80
|
+
function getBalance(db, owner) {
|
|
81
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
82
|
+
return row?.balance ?? 0;
|
|
83
|
+
}
|
|
84
|
+
function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
85
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
db.transaction(() => {
|
|
87
|
+
const existing = db.prepare(
|
|
88
|
+
"SELECT id FROM credit_transactions WHERE reference_id = ? AND reason = 'remote_earning'"
|
|
89
|
+
).get(receiptNonce);
|
|
90
|
+
if (existing) return;
|
|
91
|
+
db.prepare(
|
|
92
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
93
|
+
).run(owner, now);
|
|
94
|
+
db.prepare(
|
|
95
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
96
|
+
).run(amount, now, owner);
|
|
97
|
+
db.prepare(
|
|
98
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
99
|
+
).run(randomUUID2(), owner, amount, "remote_earning", receiptNonce, now);
|
|
100
|
+
})();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/credit/escrow.ts
|
|
104
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
105
|
+
function holdEscrow(db, owner, amount, cardId) {
|
|
106
|
+
const escrowId = randomUUID3();
|
|
107
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
108
|
+
const hold = db.transaction(() => {
|
|
109
|
+
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
110
|
+
if (!row || row.balance < amount) {
|
|
111
|
+
throw new AgentBnBError("Insufficient credits", "INSUFFICIENT_CREDITS");
|
|
112
|
+
}
|
|
113
|
+
db.prepare(
|
|
114
|
+
"UPDATE credit_balances SET balance = balance - ?, updated_at = ? WHERE owner = ? AND balance >= ?"
|
|
115
|
+
).run(amount, now, owner, amount);
|
|
116
|
+
db.prepare(
|
|
117
|
+
"INSERT INTO credit_escrow (id, owner, amount, card_id, status, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
118
|
+
).run(escrowId, owner, amount, cardId, "held", now);
|
|
119
|
+
db.prepare(
|
|
120
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
121
|
+
).run(randomUUID3(), owner, -amount, "escrow_hold", escrowId, now);
|
|
122
|
+
});
|
|
123
|
+
hold();
|
|
124
|
+
return escrowId;
|
|
125
|
+
}
|
|
126
|
+
function settleEscrow(db, escrowId, recipientOwner) {
|
|
127
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
128
|
+
const settle = db.transaction(() => {
|
|
129
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
130
|
+
if (!escrow) {
|
|
131
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
132
|
+
}
|
|
133
|
+
if (escrow.status !== "held") {
|
|
134
|
+
throw new AgentBnBError(
|
|
135
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
136
|
+
"ESCROW_ALREADY_SETTLED"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
db.prepare(
|
|
140
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
141
|
+
).run(recipientOwner, now);
|
|
142
|
+
db.prepare(
|
|
143
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
144
|
+
).run(escrow.amount, now, recipientOwner);
|
|
145
|
+
db.prepare(
|
|
146
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
147
|
+
).run("settled", now, escrowId);
|
|
148
|
+
db.prepare(
|
|
149
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
150
|
+
).run(randomUUID3(), recipientOwner, escrow.amount, "settlement", escrowId, now);
|
|
151
|
+
});
|
|
152
|
+
settle();
|
|
153
|
+
}
|
|
154
|
+
function releaseEscrow(db, escrowId) {
|
|
155
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
156
|
+
const release = db.transaction(() => {
|
|
157
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
158
|
+
if (!escrow) {
|
|
159
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
160
|
+
}
|
|
161
|
+
if (escrow.status !== "held") {
|
|
162
|
+
throw new AgentBnBError(
|
|
163
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
164
|
+
"ESCROW_ALREADY_SETTLED"
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
db.prepare(
|
|
168
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
169
|
+
).run(escrow.amount, now, escrow.owner);
|
|
170
|
+
db.prepare(
|
|
171
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
172
|
+
).run("released", now, escrowId);
|
|
173
|
+
db.prepare(
|
|
174
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
175
|
+
).run(randomUUID3(), escrow.owner, escrow.amount, "refund", escrowId, now);
|
|
176
|
+
});
|
|
177
|
+
release();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/utils/interpolation.ts
|
|
181
|
+
function resolvePath(obj, path) {
|
|
182
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((s) => s.length > 0);
|
|
183
|
+
let current = obj;
|
|
184
|
+
for (const segment of segments) {
|
|
185
|
+
if (current === null || current === void 0) {
|
|
186
|
+
return void 0;
|
|
187
|
+
}
|
|
188
|
+
if (typeof current !== "object") {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
current = current[segment];
|
|
192
|
+
}
|
|
193
|
+
return current;
|
|
194
|
+
}
|
|
195
|
+
function interpolate(template, context) {
|
|
196
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
|
|
197
|
+
const resolved = resolvePath(context, expression.trim());
|
|
198
|
+
if (resolved === void 0 || resolved === null) {
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
if (typeof resolved === "object") {
|
|
202
|
+
return JSON.stringify(resolved);
|
|
203
|
+
}
|
|
204
|
+
return String(resolved);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function interpolateObject(obj, context) {
|
|
208
|
+
const result = {};
|
|
209
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
210
|
+
result[key] = interpolateValue(value, context);
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
function interpolateValue(value, context) {
|
|
215
|
+
if (typeof value === "string") {
|
|
216
|
+
return interpolate(value, context);
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(value)) {
|
|
219
|
+
return value.map((item) => interpolateValue(item, context));
|
|
220
|
+
}
|
|
221
|
+
if (value !== null && typeof value === "object") {
|
|
222
|
+
return interpolateObject(value, context);
|
|
223
|
+
}
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/credit/signing.ts
|
|
228
|
+
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
229
|
+
import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
|
|
230
|
+
import { join } from "path";
|
|
231
|
+
function generateKeyPair() {
|
|
232
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
233
|
+
publicKeyEncoding: { type: "spki", format: "der" },
|
|
234
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
publicKey: Buffer.from(publicKey),
|
|
238
|
+
privateKey: Buffer.from(privateKey)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function saveKeyPair(configDir, keys) {
|
|
242
|
+
const privatePath = join(configDir, "private.key");
|
|
243
|
+
const publicPath = join(configDir, "public.key");
|
|
244
|
+
writeFileSync(privatePath, keys.privateKey);
|
|
245
|
+
chmodSync(privatePath, 384);
|
|
246
|
+
writeFileSync(publicPath, keys.publicKey);
|
|
247
|
+
}
|
|
248
|
+
function loadKeyPair(configDir) {
|
|
249
|
+
const privatePath = join(configDir, "private.key");
|
|
250
|
+
const publicPath = join(configDir, "public.key");
|
|
251
|
+
if (!existsSync(privatePath) || !existsSync(publicPath)) {
|
|
252
|
+
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
publicKey: readFileSync(publicPath),
|
|
256
|
+
privateKey: readFileSync(privatePath)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function canonicalJson(data) {
|
|
260
|
+
return JSON.stringify(data, Object.keys(data).sort());
|
|
261
|
+
}
|
|
262
|
+
function signEscrowReceipt(data, privateKey) {
|
|
263
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
264
|
+
const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
|
|
265
|
+
const signature = sign(null, message, keyObject);
|
|
266
|
+
return signature.toString("base64url");
|
|
267
|
+
}
|
|
268
|
+
function verifyEscrowReceipt(data, signature, publicKey) {
|
|
269
|
+
try {
|
|
270
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
271
|
+
const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
|
|
272
|
+
const sigBuffer = Buffer.from(signature, "base64url");
|
|
273
|
+
return verify(null, message, keyObject, sigBuffer);
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export {
|
|
280
|
+
openCreditDb,
|
|
281
|
+
getBalance,
|
|
282
|
+
recordEarning,
|
|
283
|
+
holdEscrow,
|
|
284
|
+
settleEscrow,
|
|
285
|
+
releaseEscrow,
|
|
286
|
+
interpolateObject,
|
|
287
|
+
generateKeyPair,
|
|
288
|
+
saveKeyPair,
|
|
289
|
+
loadKeyPair,
|
|
290
|
+
signEscrowReceipt,
|
|
291
|
+
verifyEscrowReceipt,
|
|
292
|
+
DEFAULT_AUTONOMY_CONFIG,
|
|
293
|
+
getAutonomyTier,
|
|
294
|
+
insertAuditEvent
|
|
295
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/types/index.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var IOSchemaSchema = z.object({
|
|
4
|
+
name: z.string(),
|
|
5
|
+
type: z.enum(["text", "json", "file", "audio", "image", "video", "stream"]),
|
|
6
|
+
description: z.string().optional(),
|
|
7
|
+
required: z.boolean().default(true),
|
|
8
|
+
schema: z.record(z.unknown()).optional()
|
|
9
|
+
// JSON Schema
|
|
10
|
+
});
|
|
11
|
+
var PoweredBySchema = z.object({
|
|
12
|
+
provider: z.string().min(1),
|
|
13
|
+
model: z.string().optional(),
|
|
14
|
+
tier: z.string().optional()
|
|
15
|
+
});
|
|
16
|
+
var CapabilityCardSchema = z.object({
|
|
17
|
+
spec_version: z.literal("1.0").default("1.0"),
|
|
18
|
+
id: z.string().uuid(),
|
|
19
|
+
owner: z.string().min(1),
|
|
20
|
+
name: z.string().min(1).max(100),
|
|
21
|
+
description: z.string().max(500),
|
|
22
|
+
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
23
|
+
inputs: z.array(IOSchemaSchema),
|
|
24
|
+
outputs: z.array(IOSchemaSchema),
|
|
25
|
+
pricing: z.object({
|
|
26
|
+
credits_per_call: z.number().nonnegative(),
|
|
27
|
+
credits_per_minute: z.number().nonnegative().optional(),
|
|
28
|
+
/** Number of free monthly calls. Shown as a "N free/mo" badge in the Hub. */
|
|
29
|
+
free_tier: z.number().nonnegative().optional()
|
|
30
|
+
}),
|
|
31
|
+
availability: z.object({
|
|
32
|
+
online: z.boolean(),
|
|
33
|
+
schedule: z.string().optional()
|
|
34
|
+
// cron expression
|
|
35
|
+
}),
|
|
36
|
+
powered_by: z.array(PoweredBySchema).optional(),
|
|
37
|
+
/**
|
|
38
|
+
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
39
|
+
* never transmitted beyond the local store.
|
|
40
|
+
*/
|
|
41
|
+
_internal: z.record(z.unknown()).optional(),
|
|
42
|
+
/** Public gateway URL where this agent accepts requests. Populated on remote publish. */
|
|
43
|
+
gateway_url: z.string().url().optional(),
|
|
44
|
+
metadata: z.object({
|
|
45
|
+
apis_used: z.array(z.string()).optional(),
|
|
46
|
+
avg_latency_ms: z.number().nonnegative().optional(),
|
|
47
|
+
success_rate: z.number().min(0).max(1).optional(),
|
|
48
|
+
tags: z.array(z.string()).optional()
|
|
49
|
+
}).optional(),
|
|
50
|
+
created_at: z.string().datetime().optional(),
|
|
51
|
+
updated_at: z.string().datetime().optional()
|
|
52
|
+
});
|
|
53
|
+
var SkillSchema = z.object({
|
|
54
|
+
/** Stable skill identifier, e.g. 'tts-elevenlabs'. Used for gateway routing and idle tracking. */
|
|
55
|
+
id: z.string().min(1),
|
|
56
|
+
name: z.string().min(1).max(100),
|
|
57
|
+
description: z.string().max(500),
|
|
58
|
+
level: z.union([z.literal(1), z.literal(2), z.literal(3)]),
|
|
59
|
+
/** Optional grouping category, e.g. 'tts' | 'video_gen' | 'code_review'. */
|
|
60
|
+
category: z.string().optional(),
|
|
61
|
+
inputs: z.array(IOSchemaSchema),
|
|
62
|
+
outputs: z.array(IOSchemaSchema),
|
|
63
|
+
pricing: z.object({
|
|
64
|
+
credits_per_call: z.number().nonnegative(),
|
|
65
|
+
credits_per_minute: z.number().nonnegative().optional(),
|
|
66
|
+
free_tier: z.number().nonnegative().optional()
|
|
67
|
+
}),
|
|
68
|
+
/** Per-skill online flag — overrides card-level availability for this skill. */
|
|
69
|
+
availability: z.object({ online: z.boolean() }).optional(),
|
|
70
|
+
powered_by: z.array(PoweredBySchema).optional(),
|
|
71
|
+
metadata: z.object({
|
|
72
|
+
apis_used: z.array(z.string()).optional(),
|
|
73
|
+
avg_latency_ms: z.number().nonnegative().optional(),
|
|
74
|
+
success_rate: z.number().min(0).max(1).optional(),
|
|
75
|
+
tags: z.array(z.string()).optional(),
|
|
76
|
+
capacity: z.object({
|
|
77
|
+
calls_per_hour: z.number().positive().default(60)
|
|
78
|
+
}).optional()
|
|
79
|
+
}).optional(),
|
|
80
|
+
/**
|
|
81
|
+
* Private per-skill metadata. Stripped from all API and CLI responses —
|
|
82
|
+
* never transmitted beyond the local store.
|
|
83
|
+
*/
|
|
84
|
+
_internal: z.record(z.unknown()).optional()
|
|
85
|
+
});
|
|
86
|
+
var SuitabilitySchema = z.object({
|
|
87
|
+
/** Use cases this agent/skill is optimised for. */
|
|
88
|
+
ideal_for: z.array(z.string()).optional(),
|
|
89
|
+
/** Scenarios this agent/skill cannot reliably handle. */
|
|
90
|
+
not_suitable_for: z.array(z.string()).optional(),
|
|
91
|
+
/** Domains explicitly excluded (used for routing exclusions in later phases). */
|
|
92
|
+
excluded_domains: z.array(z.string()).optional(),
|
|
93
|
+
/** Conditions that increase failure risk, shown as warnings in the Hub. */
|
|
94
|
+
risk_conditions: z.array(z.string()).optional(),
|
|
95
|
+
/** Recommended alternative when this agent is unsuitable. */
|
|
96
|
+
fallback_recommendation: z.string().optional()
|
|
97
|
+
});
|
|
98
|
+
var LearningSchema = z.object({
|
|
99
|
+
/** Known limitations that may affect reliability (self-declared). */
|
|
100
|
+
known_limitations: z.array(z.string()).optional(),
|
|
101
|
+
/** Common failure patterns observed by the provider. */
|
|
102
|
+
common_failure_patterns: z.array(z.string()).optional(),
|
|
103
|
+
/** Version-tagged improvements the provider has shipped. */
|
|
104
|
+
recent_improvements: z.array(z.object({
|
|
105
|
+
version: z.string(),
|
|
106
|
+
summary: z.string(),
|
|
107
|
+
timestamp: z.string()
|
|
108
|
+
})).optional(),
|
|
109
|
+
/** Structured critiques from external sources (phase 2+). */
|
|
110
|
+
critiques: z.array(z.object({
|
|
111
|
+
type: z.literal("structured"),
|
|
112
|
+
summary: z.string(),
|
|
113
|
+
source_tier: z.string(),
|
|
114
|
+
timestamp: z.string()
|
|
115
|
+
})).optional()
|
|
116
|
+
});
|
|
117
|
+
var CapabilityCardV2Schema = z.object({
|
|
118
|
+
spec_version: z.literal("2.0"),
|
|
119
|
+
id: z.string().uuid(),
|
|
120
|
+
owner: z.string().min(1),
|
|
121
|
+
/** Agent display name — was 'name' in v1.0. */
|
|
122
|
+
agent_name: z.string().min(1).max(100),
|
|
123
|
+
/** Short one-liner shown in Hub v2 Identity Header. */
|
|
124
|
+
short_description: z.string().max(200).optional(),
|
|
125
|
+
/** At least one skill is required. */
|
|
126
|
+
skills: z.array(SkillSchema).min(1),
|
|
127
|
+
availability: z.object({
|
|
128
|
+
online: z.boolean(),
|
|
129
|
+
schedule: z.string().optional()
|
|
130
|
+
}),
|
|
131
|
+
/** Optional deployment environment metadata. */
|
|
132
|
+
environment: z.object({
|
|
133
|
+
runtime: z.string(),
|
|
134
|
+
region: z.string().optional()
|
|
135
|
+
}).optional(),
|
|
136
|
+
/** Suitability metadata for Hub v2 profile and future routing warnings. */
|
|
137
|
+
suitability: SuitabilitySchema.optional(),
|
|
138
|
+
/** Learning signals — self-declared limitations, improvements, critiques. */
|
|
139
|
+
learning: LearningSchema.optional(),
|
|
140
|
+
/**
|
|
141
|
+
* Private per-card metadata. Stripped from all API and CLI responses —
|
|
142
|
+
* never transmitted beyond the local store.
|
|
143
|
+
*/
|
|
144
|
+
_internal: z.record(z.unknown()).optional(),
|
|
145
|
+
/** Public gateway URL where this agent accepts requests. Populated on remote publish. */
|
|
146
|
+
gateway_url: z.string().url().optional(),
|
|
147
|
+
created_at: z.string().datetime().optional(),
|
|
148
|
+
updated_at: z.string().datetime().optional()
|
|
149
|
+
});
|
|
150
|
+
var AnyCardSchema = z.discriminatedUnion("spec_version", [
|
|
151
|
+
CapabilityCardSchema,
|
|
152
|
+
CapabilityCardV2Schema
|
|
153
|
+
]);
|
|
154
|
+
var AgentBnBError = class extends Error {
|
|
155
|
+
constructor(message, code) {
|
|
156
|
+
super(message);
|
|
157
|
+
this.code = code;
|
|
158
|
+
this.name = "AgentBnBError";
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export {
|
|
163
|
+
CapabilityCardV2Schema,
|
|
164
|
+
AgentBnBError
|
|
165
|
+
};
|