agentbnb 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{card-P5C36VBD.js → card-EWIXC377.js} +1 -1
- package/dist/{chunk-V7M6GIJZ.js → chunk-2ETVQXP7.js} +13 -193
- package/dist/chunk-3Y36WQDV.js +70 -0
- package/dist/{chunk-TQMI73LL.js → chunk-7RU5INZI.js} +1 -0
- package/dist/chunk-MGHI67GR.js +257 -0
- package/dist/chunk-MZCNJ5PY.js +192 -0
- package/dist/{chunk-PJSYSVKN.js → chunk-QAY6XTT7.js} +4 -6
- package/dist/{chunk-EZB4RUIG.js → chunk-VCW7IDJM.js} +1 -1
- package/dist/cli/index.js +1176 -470
- package/dist/{conduct-JZJS2ZHA.js → conduct-5T3LGXMF.js} +10 -8
- package/dist/{conductor-mode-DJ3RIJ5T.js → conductor-mode-GPLAM2XO.js} +4 -3
- package/dist/execute-NZXTSSVV.js +9 -0
- package/dist/index.d.ts +1052 -10
- package/dist/index.js +1488 -249
- package/dist/websocket-client-5TIQDYQ4.js +275 -0
- package/package.json +16 -1
package/dist/index.js
CHANGED
|
@@ -432,6 +432,30 @@ function getCard(db, id) {
|
|
|
432
432
|
if (!row) return null;
|
|
433
433
|
return JSON.parse(row.data);
|
|
434
434
|
}
|
|
435
|
+
function updateCard(db, id, owner, updates) {
|
|
436
|
+
const existing = getCard(db, id);
|
|
437
|
+
if (!existing) {
|
|
438
|
+
throw new AgentBnBError(`Card not found: ${id}`, "NOT_FOUND");
|
|
439
|
+
}
|
|
440
|
+
if (existing.owner !== owner) {
|
|
441
|
+
throw new AgentBnBError("Forbidden: you do not own this card", "FORBIDDEN");
|
|
442
|
+
}
|
|
443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
444
|
+
const merged = { ...existing, ...updates, updated_at: now };
|
|
445
|
+
const parsed = CapabilityCardSchema.safeParse(merged);
|
|
446
|
+
if (!parsed.success) {
|
|
447
|
+
throw new AgentBnBError(
|
|
448
|
+
`Card validation failed: ${parsed.error.message}`,
|
|
449
|
+
"VALIDATION_ERROR"
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const stmt = db.prepare(`
|
|
453
|
+
UPDATE capability_cards
|
|
454
|
+
SET data = ?, updated_at = ?
|
|
455
|
+
WHERE id = ?
|
|
456
|
+
`);
|
|
457
|
+
stmt.run(JSON.stringify(parsed.data), now, id);
|
|
458
|
+
}
|
|
435
459
|
function updateReputation(db, cardId, success, latencyMs) {
|
|
436
460
|
const existing = getCard(db, cardId);
|
|
437
461
|
if (!existing) return;
|
|
@@ -455,6 +479,18 @@ function updateReputation(db, cardId, success, latencyMs) {
|
|
|
455
479
|
`);
|
|
456
480
|
stmt.run(JSON.stringify(updatedCard), now, cardId);
|
|
457
481
|
}
|
|
482
|
+
function listCards(db, owner) {
|
|
483
|
+
let stmt;
|
|
484
|
+
let rows;
|
|
485
|
+
if (owner !== void 0) {
|
|
486
|
+
stmt = db.prepare("SELECT data FROM capability_cards WHERE owner = ?");
|
|
487
|
+
rows = stmt.all(owner);
|
|
488
|
+
} else {
|
|
489
|
+
stmt = db.prepare("SELECT data FROM capability_cards");
|
|
490
|
+
rows = stmt.all();
|
|
491
|
+
}
|
|
492
|
+
return rows.map((row) => JSON.parse(row.data));
|
|
493
|
+
}
|
|
458
494
|
|
|
459
495
|
// src/registry/matcher.ts
|
|
460
496
|
function searchCards(db, query, filters = {}) {
|
|
@@ -558,6 +594,8 @@ function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
|
558
594
|
|
|
559
595
|
// src/gateway/server.ts
|
|
560
596
|
import Fastify from "fastify";
|
|
597
|
+
|
|
598
|
+
// src/gateway/execute.ts
|
|
561
599
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
562
600
|
|
|
563
601
|
// src/credit/escrow.ts
|
|
@@ -729,6 +767,164 @@ function releaseRequesterEscrow(requesterDb, escrowId) {
|
|
|
729
767
|
releaseEscrow(requesterDb, escrowId);
|
|
730
768
|
}
|
|
731
769
|
|
|
770
|
+
// src/gateway/execute.ts
|
|
771
|
+
async function executeCapabilityRequest(opts) {
|
|
772
|
+
const {
|
|
773
|
+
registryDb,
|
|
774
|
+
creditDb,
|
|
775
|
+
cardId,
|
|
776
|
+
skillId,
|
|
777
|
+
params,
|
|
778
|
+
requester,
|
|
779
|
+
escrowReceipt: receipt,
|
|
780
|
+
skillExecutor,
|
|
781
|
+
handlerUrl,
|
|
782
|
+
timeoutMs = 3e4
|
|
783
|
+
} = opts;
|
|
784
|
+
const card = getCard(registryDb, cardId);
|
|
785
|
+
if (!card) {
|
|
786
|
+
return { success: false, error: { code: -32602, message: `Card not found: ${cardId}` } };
|
|
787
|
+
}
|
|
788
|
+
let creditsNeeded;
|
|
789
|
+
let cardName;
|
|
790
|
+
let resolvedSkillId;
|
|
791
|
+
const rawCard = card;
|
|
792
|
+
if (Array.isArray(rawCard["skills"])) {
|
|
793
|
+
const v2card = card;
|
|
794
|
+
const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
|
|
795
|
+
if (!skill) {
|
|
796
|
+
return { success: false, error: { code: -32602, message: `Skill not found: ${skillId}` } };
|
|
797
|
+
}
|
|
798
|
+
creditsNeeded = skill.pricing.credits_per_call;
|
|
799
|
+
cardName = skill.name;
|
|
800
|
+
resolvedSkillId = skill.id;
|
|
801
|
+
} else {
|
|
802
|
+
creditsNeeded = card.pricing.credits_per_call;
|
|
803
|
+
cardName = card.name;
|
|
804
|
+
}
|
|
805
|
+
let escrowId = null;
|
|
806
|
+
let isRemoteEscrow = false;
|
|
807
|
+
if (receipt) {
|
|
808
|
+
const { signature, ...receiptData2 } = receipt;
|
|
809
|
+
const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
|
|
810
|
+
const valid = verifyEscrowReceipt(receiptData2, signature, publicKeyBuf);
|
|
811
|
+
if (!valid) {
|
|
812
|
+
return { success: false, error: { code: -32603, message: "Invalid escrow receipt signature" } };
|
|
813
|
+
}
|
|
814
|
+
if (receipt.amount < creditsNeeded) {
|
|
815
|
+
return { success: false, error: { code: -32603, message: "Insufficient escrow amount" } };
|
|
816
|
+
}
|
|
817
|
+
const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
|
|
818
|
+
if (receiptAge > 5 * 60 * 1e3) {
|
|
819
|
+
return { success: false, error: { code: -32603, message: "Escrow receipt expired" } };
|
|
820
|
+
}
|
|
821
|
+
isRemoteEscrow = true;
|
|
822
|
+
} else {
|
|
823
|
+
try {
|
|
824
|
+
const balance = getBalance(creditDb, requester);
|
|
825
|
+
if (balance < creditsNeeded) {
|
|
826
|
+
return { success: false, error: { code: -32603, message: "Insufficient credits" } };
|
|
827
|
+
}
|
|
828
|
+
escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
|
|
831
|
+
return { success: false, error: { code: -32603, message: msg } };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const startMs = Date.now();
|
|
835
|
+
const receiptData = isRemoteEscrow ? { receipt_released: true } : void 0;
|
|
836
|
+
const handleFailure = (status, latencyMs, message) => {
|
|
837
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
838
|
+
updateReputation(registryDb, cardId, false, latencyMs);
|
|
839
|
+
try {
|
|
840
|
+
insertRequestLog(registryDb, {
|
|
841
|
+
id: randomUUID3(),
|
|
842
|
+
card_id: cardId,
|
|
843
|
+
card_name: cardName,
|
|
844
|
+
skill_id: resolvedSkillId,
|
|
845
|
+
requester,
|
|
846
|
+
status,
|
|
847
|
+
latency_ms: latencyMs,
|
|
848
|
+
credits_charged: 0,
|
|
849
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
850
|
+
});
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
error: { code: -32603, message, ...receiptData ? { data: receiptData } : {} }
|
|
856
|
+
};
|
|
857
|
+
};
|
|
858
|
+
const handleSuccess = (result, latencyMs) => {
|
|
859
|
+
if (isRemoteEscrow && receipt) {
|
|
860
|
+
settleProviderEarning(creditDb, card.owner, receipt);
|
|
861
|
+
} else if (escrowId) {
|
|
862
|
+
settleEscrow(creditDb, escrowId, card.owner);
|
|
863
|
+
}
|
|
864
|
+
updateReputation(registryDb, cardId, true, latencyMs);
|
|
865
|
+
try {
|
|
866
|
+
insertRequestLog(registryDb, {
|
|
867
|
+
id: randomUUID3(),
|
|
868
|
+
card_id: cardId,
|
|
869
|
+
card_name: cardName,
|
|
870
|
+
skill_id: resolvedSkillId,
|
|
871
|
+
requester,
|
|
872
|
+
status: "success",
|
|
873
|
+
latency_ms: latencyMs,
|
|
874
|
+
credits_charged: creditsNeeded,
|
|
875
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
876
|
+
});
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
const successResult = isRemoteEscrow ? {
|
|
880
|
+
...typeof result === "object" && result !== null ? result : { data: result },
|
|
881
|
+
receipt_settled: true,
|
|
882
|
+
receipt_nonce: receipt.nonce
|
|
883
|
+
} : result;
|
|
884
|
+
return { success: true, result: successResult };
|
|
885
|
+
};
|
|
886
|
+
if (skillExecutor) {
|
|
887
|
+
const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
|
|
888
|
+
try {
|
|
889
|
+
const execResult = await skillExecutor.execute(targetSkillId, params);
|
|
890
|
+
if (!execResult.success) {
|
|
891
|
+
return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
|
|
892
|
+
}
|
|
893
|
+
return handleSuccess(execResult.result, execResult.latency_ms);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
const message = err instanceof Error ? err.message : "Execution error";
|
|
896
|
+
return handleFailure("failure", Date.now() - startMs, message);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (!handlerUrl) {
|
|
900
|
+
return handleFailure("failure", Date.now() - startMs, "No skill executor or handler URL configured");
|
|
901
|
+
}
|
|
902
|
+
const controller = new AbortController();
|
|
903
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
904
|
+
try {
|
|
905
|
+
const response = await fetch(handlerUrl, {
|
|
906
|
+
method: "POST",
|
|
907
|
+
headers: { "Content-Type": "application/json" },
|
|
908
|
+
body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
|
|
909
|
+
signal: controller.signal
|
|
910
|
+
});
|
|
911
|
+
clearTimeout(timer);
|
|
912
|
+
if (!response.ok) {
|
|
913
|
+
return handleFailure("failure", Date.now() - startMs, `Handler returned ${response.status}`);
|
|
914
|
+
}
|
|
915
|
+
const result = await response.json();
|
|
916
|
+
return handleSuccess(result, Date.now() - startMs);
|
|
917
|
+
} catch (err) {
|
|
918
|
+
clearTimeout(timer);
|
|
919
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
920
|
+
return handleFailure(
|
|
921
|
+
isTimeout ? "timeout" : "failure",
|
|
922
|
+
Date.now() - startMs,
|
|
923
|
+
isTimeout ? "Execution timeout" : "Handler error"
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
732
928
|
// src/gateway/server.ts
|
|
733
929
|
var VERSION = "0.0.1";
|
|
734
930
|
function createGatewayServer(opts) {
|
|
@@ -793,258 +989,24 @@ function createGatewayServer(opts) {
|
|
|
793
989
|
error: { code: -32602, message: "Invalid params: card_id required" }
|
|
794
990
|
});
|
|
795
991
|
}
|
|
796
|
-
const card = getCard(registryDb, cardId);
|
|
797
|
-
if (!card) {
|
|
798
|
-
return reply.send({
|
|
799
|
-
jsonrpc: "2.0",
|
|
800
|
-
id,
|
|
801
|
-
error: { code: -32602, message: `Card not found: ${cardId}` }
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
992
|
const requester = params.requester ?? "unknown";
|
|
805
|
-
let creditsNeeded;
|
|
806
|
-
let cardName;
|
|
807
|
-
let resolvedSkillId;
|
|
808
|
-
const rawCard = card;
|
|
809
|
-
if (Array.isArray(rawCard["skills"])) {
|
|
810
|
-
const v2card = card;
|
|
811
|
-
const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
|
|
812
|
-
if (!skill) {
|
|
813
|
-
return reply.send({
|
|
814
|
-
jsonrpc: "2.0",
|
|
815
|
-
id,
|
|
816
|
-
error: { code: -32602, message: `Skill not found: ${skillId}` }
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
creditsNeeded = skill.pricing.credits_per_call;
|
|
820
|
-
cardName = skill.name;
|
|
821
|
-
resolvedSkillId = skill.id;
|
|
822
|
-
} else {
|
|
823
|
-
creditsNeeded = card.pricing.credits_per_call;
|
|
824
|
-
cardName = card.name;
|
|
825
|
-
}
|
|
826
993
|
const receipt = params.escrow_receipt;
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
return reply.send({
|
|
842
|
-
jsonrpc: "2.0",
|
|
843
|
-
id,
|
|
844
|
-
error: { code: -32603, message: "Insufficient escrow amount" }
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
|
|
848
|
-
if (receiptAge > 5 * 60 * 1e3) {
|
|
849
|
-
return reply.send({
|
|
850
|
-
jsonrpc: "2.0",
|
|
851
|
-
id,
|
|
852
|
-
error: { code: -32603, message: "Escrow receipt expired" }
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
isRemoteEscrow = true;
|
|
994
|
+
const result = await executeCapabilityRequest({
|
|
995
|
+
registryDb,
|
|
996
|
+
creditDb,
|
|
997
|
+
cardId,
|
|
998
|
+
skillId,
|
|
999
|
+
params,
|
|
1000
|
+
requester,
|
|
1001
|
+
escrowReceipt: receipt,
|
|
1002
|
+
skillExecutor,
|
|
1003
|
+
handlerUrl,
|
|
1004
|
+
timeoutMs
|
|
1005
|
+
});
|
|
1006
|
+
if (result.success) {
|
|
1007
|
+
return reply.send({ jsonrpc: "2.0", id, result: result.result });
|
|
856
1008
|
} else {
|
|
857
|
-
|
|
858
|
-
const balance = getBalance(creditDb, requester);
|
|
859
|
-
if (balance < creditsNeeded) {
|
|
860
|
-
return reply.send({
|
|
861
|
-
jsonrpc: "2.0",
|
|
862
|
-
id,
|
|
863
|
-
error: { code: -32603, message: "Insufficient credits" }
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
|
|
867
|
-
} catch (err) {
|
|
868
|
-
const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
|
|
869
|
-
return reply.send({
|
|
870
|
-
jsonrpc: "2.0",
|
|
871
|
-
id,
|
|
872
|
-
error: { code: -32603, message: msg }
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
const startMs = Date.now();
|
|
877
|
-
if (skillExecutor) {
|
|
878
|
-
const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
|
|
879
|
-
let execResult;
|
|
880
|
-
try {
|
|
881
|
-
execResult = await skillExecutor.execute(targetSkillId, params);
|
|
882
|
-
} catch (err) {
|
|
883
|
-
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
884
|
-
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
885
|
-
try {
|
|
886
|
-
insertRequestLog(registryDb, {
|
|
887
|
-
id: randomUUID3(),
|
|
888
|
-
card_id: cardId,
|
|
889
|
-
card_name: cardName,
|
|
890
|
-
skill_id: resolvedSkillId,
|
|
891
|
-
requester,
|
|
892
|
-
status: "failure",
|
|
893
|
-
latency_ms: Date.now() - startMs,
|
|
894
|
-
credits_charged: 0,
|
|
895
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
896
|
-
});
|
|
897
|
-
} catch {
|
|
898
|
-
}
|
|
899
|
-
const message = err instanceof Error ? err.message : "Execution error";
|
|
900
|
-
return reply.send({
|
|
901
|
-
jsonrpc: "2.0",
|
|
902
|
-
id,
|
|
903
|
-
error: {
|
|
904
|
-
code: -32603,
|
|
905
|
-
message,
|
|
906
|
-
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
907
|
-
}
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
if (!execResult.success) {
|
|
911
|
-
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
912
|
-
updateReputation(registryDb, cardId, false, execResult.latency_ms);
|
|
913
|
-
try {
|
|
914
|
-
insertRequestLog(registryDb, {
|
|
915
|
-
id: randomUUID3(),
|
|
916
|
-
card_id: cardId,
|
|
917
|
-
card_name: cardName,
|
|
918
|
-
skill_id: resolvedSkillId,
|
|
919
|
-
requester,
|
|
920
|
-
status: "failure",
|
|
921
|
-
latency_ms: execResult.latency_ms,
|
|
922
|
-
credits_charged: 0,
|
|
923
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
924
|
-
});
|
|
925
|
-
} catch {
|
|
926
|
-
}
|
|
927
|
-
return reply.send({
|
|
928
|
-
jsonrpc: "2.0",
|
|
929
|
-
id,
|
|
930
|
-
error: {
|
|
931
|
-
code: -32603,
|
|
932
|
-
message: execResult.error ?? "Execution failed",
|
|
933
|
-
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
934
|
-
}
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
if (isRemoteEscrow && receipt) {
|
|
938
|
-
settleProviderEarning(creditDb, card.owner, receipt);
|
|
939
|
-
} else if (escrowId) {
|
|
940
|
-
settleEscrow(creditDb, escrowId, card.owner);
|
|
941
|
-
}
|
|
942
|
-
updateReputation(registryDb, cardId, true, execResult.latency_ms);
|
|
943
|
-
try {
|
|
944
|
-
insertRequestLog(registryDb, {
|
|
945
|
-
id: randomUUID3(),
|
|
946
|
-
card_id: cardId,
|
|
947
|
-
card_name: cardName,
|
|
948
|
-
skill_id: resolvedSkillId,
|
|
949
|
-
requester,
|
|
950
|
-
status: "success",
|
|
951
|
-
latency_ms: execResult.latency_ms,
|
|
952
|
-
credits_charged: creditsNeeded,
|
|
953
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
954
|
-
});
|
|
955
|
-
} catch {
|
|
956
|
-
}
|
|
957
|
-
const successResult = isRemoteEscrow ? { ...typeof execResult.result === "object" && execResult.result !== null ? execResult.result : { data: execResult.result }, receipt_settled: true, receipt_nonce: receipt.nonce } : execResult.result;
|
|
958
|
-
return reply.send({ jsonrpc: "2.0", id, result: successResult });
|
|
959
|
-
}
|
|
960
|
-
const controller = new AbortController();
|
|
961
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
962
|
-
try {
|
|
963
|
-
const response = await fetch(handlerUrl, {
|
|
964
|
-
method: "POST",
|
|
965
|
-
headers: { "Content-Type": "application/json" },
|
|
966
|
-
body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
|
|
967
|
-
signal: controller.signal
|
|
968
|
-
});
|
|
969
|
-
clearTimeout(timer);
|
|
970
|
-
if (!response.ok) {
|
|
971
|
-
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
972
|
-
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
973
|
-
try {
|
|
974
|
-
insertRequestLog(registryDb, {
|
|
975
|
-
id: randomUUID3(),
|
|
976
|
-
card_id: cardId,
|
|
977
|
-
card_name: cardName,
|
|
978
|
-
skill_id: resolvedSkillId,
|
|
979
|
-
requester,
|
|
980
|
-
status: "failure",
|
|
981
|
-
latency_ms: Date.now() - startMs,
|
|
982
|
-
credits_charged: 0,
|
|
983
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
984
|
-
});
|
|
985
|
-
} catch {
|
|
986
|
-
}
|
|
987
|
-
return reply.send({
|
|
988
|
-
jsonrpc: "2.0",
|
|
989
|
-
id,
|
|
990
|
-
error: {
|
|
991
|
-
code: -32603,
|
|
992
|
-
message: `Handler returned ${response.status}`,
|
|
993
|
-
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
const result = await response.json();
|
|
998
|
-
if (isRemoteEscrow && receipt) {
|
|
999
|
-
settleProviderEarning(creditDb, card.owner, receipt);
|
|
1000
|
-
} else if (escrowId) {
|
|
1001
|
-
settleEscrow(creditDb, escrowId, card.owner);
|
|
1002
|
-
}
|
|
1003
|
-
updateReputation(registryDb, cardId, true, Date.now() - startMs);
|
|
1004
|
-
try {
|
|
1005
|
-
insertRequestLog(registryDb, {
|
|
1006
|
-
id: randomUUID3(),
|
|
1007
|
-
card_id: cardId,
|
|
1008
|
-
card_name: cardName,
|
|
1009
|
-
skill_id: resolvedSkillId,
|
|
1010
|
-
requester,
|
|
1011
|
-
status: "success",
|
|
1012
|
-
latency_ms: Date.now() - startMs,
|
|
1013
|
-
credits_charged: creditsNeeded,
|
|
1014
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1015
|
-
});
|
|
1016
|
-
} catch {
|
|
1017
|
-
}
|
|
1018
|
-
const successResult = isRemoteEscrow ? { ...typeof result === "object" && result !== null ? result : { data: result }, receipt_settled: true, receipt_nonce: receipt.nonce } : result;
|
|
1019
|
-
return reply.send({ jsonrpc: "2.0", id, result: successResult });
|
|
1020
|
-
} catch (err) {
|
|
1021
|
-
clearTimeout(timer);
|
|
1022
|
-
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
1023
|
-
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
1024
|
-
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
1025
|
-
try {
|
|
1026
|
-
insertRequestLog(registryDb, {
|
|
1027
|
-
id: randomUUID3(),
|
|
1028
|
-
card_id: cardId,
|
|
1029
|
-
card_name: cardName,
|
|
1030
|
-
skill_id: resolvedSkillId,
|
|
1031
|
-
requester,
|
|
1032
|
-
status: isTimeout ? "timeout" : "failure",
|
|
1033
|
-
latency_ms: Date.now() - startMs,
|
|
1034
|
-
credits_charged: 0,
|
|
1035
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1036
|
-
});
|
|
1037
|
-
} catch {
|
|
1038
|
-
}
|
|
1039
|
-
return reply.send({
|
|
1040
|
-
jsonrpc: "2.0",
|
|
1041
|
-
id,
|
|
1042
|
-
error: {
|
|
1043
|
-
code: -32603,
|
|
1044
|
-
message: isTimeout ? "Execution timeout" : "Handler error",
|
|
1045
|
-
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1046
|
-
}
|
|
1047
|
-
});
|
|
1009
|
+
return reply.send({ jsonrpc: "2.0", id, error: result.error });
|
|
1048
1010
|
}
|
|
1049
1011
|
});
|
|
1050
1012
|
return fastify;
|
|
@@ -1992,6 +1954,27 @@ async function requestCapability(opts) {
|
|
|
1992
1954
|
}
|
|
1993
1955
|
return body.result;
|
|
1994
1956
|
}
|
|
1957
|
+
async function requestViaRelay(relay, opts) {
|
|
1958
|
+
try {
|
|
1959
|
+
return await relay.request({
|
|
1960
|
+
targetOwner: opts.targetOwner,
|
|
1961
|
+
cardId: opts.cardId,
|
|
1962
|
+
skillId: opts.skillId,
|
|
1963
|
+
params: opts.params ?? {},
|
|
1964
|
+
escrowReceipt: opts.escrowReceipt,
|
|
1965
|
+
timeoutMs: opts.timeoutMs
|
|
1966
|
+
});
|
|
1967
|
+
} catch (err) {
|
|
1968
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1969
|
+
if (message.includes("timeout")) {
|
|
1970
|
+
throw new AgentBnBError(message, "TIMEOUT");
|
|
1971
|
+
}
|
|
1972
|
+
if (message.includes("offline")) {
|
|
1973
|
+
throw new AgentBnBError(message, "AGENT_OFFLINE");
|
|
1974
|
+
}
|
|
1975
|
+
throw new AgentBnBError(message, "RELAY_ERROR");
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1995
1978
|
|
|
1996
1979
|
// src/autonomy/tiers.ts
|
|
1997
1980
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
@@ -2007,6 +1990,22 @@ import { join as join3 } from "path";
|
|
|
2007
1990
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
2008
1991
|
import { homedir } from "os";
|
|
2009
1992
|
import { join as join2 } from "path";
|
|
1993
|
+
function getConfigDir() {
|
|
1994
|
+
return process.env["AGENTBNB_DIR"] ?? join2(homedir(), ".agentbnb");
|
|
1995
|
+
}
|
|
1996
|
+
function getConfigPath() {
|
|
1997
|
+
return join2(getConfigDir(), "config.json");
|
|
1998
|
+
}
|
|
1999
|
+
function loadConfig() {
|
|
2000
|
+
const configPath = getConfigPath();
|
|
2001
|
+
if (!existsSync2(configPath)) return null;
|
|
2002
|
+
try {
|
|
2003
|
+
const raw = readFileSync2(configPath, "utf-8");
|
|
2004
|
+
return JSON.parse(raw);
|
|
2005
|
+
} catch {
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2010
2009
|
|
|
2011
2010
|
// src/autonomy/auto-request.ts
|
|
2012
2011
|
function minMaxNormalize(values) {
|
|
@@ -2535,7 +2534,1223 @@ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
|
2535
2534
|
};
|
|
2536
2535
|
return { escrowId, receipt };
|
|
2537
2536
|
}
|
|
2537
|
+
|
|
2538
|
+
// src/identity/identity.ts
|
|
2539
|
+
import { z as z4 } from "zod";
|
|
2540
|
+
import { createHash } from "crypto";
|
|
2541
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
2542
|
+
import { join as join4 } from "path";
|
|
2543
|
+
var AgentIdentitySchema = z4.object({
|
|
2544
|
+
/** Deterministic ID derived from public key: sha256(hex).slice(0, 16). */
|
|
2545
|
+
agent_id: z4.string().min(1),
|
|
2546
|
+
/** Human-readable owner name (from config or init). */
|
|
2547
|
+
owner: z4.string().min(1),
|
|
2548
|
+
/** Hex-encoded Ed25519 public key. */
|
|
2549
|
+
public_key: z4.string().min(1),
|
|
2550
|
+
/** ISO 8601 timestamp of identity creation. */
|
|
2551
|
+
created_at: z4.string().datetime(),
|
|
2552
|
+
/** Optional guarantor info if linked to a human. */
|
|
2553
|
+
guarantor: z4.object({
|
|
2554
|
+
github_login: z4.string().min(1),
|
|
2555
|
+
verified_at: z4.string().datetime()
|
|
2556
|
+
}).optional()
|
|
2557
|
+
});
|
|
2558
|
+
var AgentCertificateSchema = z4.object({
|
|
2559
|
+
identity: AgentIdentitySchema,
|
|
2560
|
+
/** ISO 8601 timestamp of certificate issuance. */
|
|
2561
|
+
issued_at: z4.string().datetime(),
|
|
2562
|
+
/** ISO 8601 timestamp of certificate expiry. */
|
|
2563
|
+
expires_at: z4.string().datetime(),
|
|
2564
|
+
/** Hex-encoded public key of the issuer (same as identity for self-signed). */
|
|
2565
|
+
issuer_public_key: z4.string().min(1),
|
|
2566
|
+
/** Base64url Ed25519 signature over { identity, issued_at, expires_at, issuer_public_key }. */
|
|
2567
|
+
signature: z4.string().min(1)
|
|
2568
|
+
});
|
|
2569
|
+
var IDENTITY_FILENAME = "identity.json";
|
|
2570
|
+
function deriveAgentId(publicKeyHex) {
|
|
2571
|
+
return createHash("sha256").update(publicKeyHex, "hex").digest("hex").slice(0, 16);
|
|
2572
|
+
}
|
|
2573
|
+
function createIdentity(configDir, owner) {
|
|
2574
|
+
if (!existsSync4(configDir)) {
|
|
2575
|
+
mkdirSync3(configDir, { recursive: true });
|
|
2576
|
+
}
|
|
2577
|
+
let keys;
|
|
2578
|
+
try {
|
|
2579
|
+
keys = loadKeyPair(configDir);
|
|
2580
|
+
} catch {
|
|
2581
|
+
keys = generateKeyPair();
|
|
2582
|
+
saveKeyPair(configDir, keys);
|
|
2583
|
+
}
|
|
2584
|
+
const publicKeyHex = keys.publicKey.toString("hex");
|
|
2585
|
+
const agentId = deriveAgentId(publicKeyHex);
|
|
2586
|
+
const identity = {
|
|
2587
|
+
agent_id: agentId,
|
|
2588
|
+
owner,
|
|
2589
|
+
public_key: publicKeyHex,
|
|
2590
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2591
|
+
};
|
|
2592
|
+
saveIdentity(configDir, identity);
|
|
2593
|
+
return identity;
|
|
2594
|
+
}
|
|
2595
|
+
function loadIdentity(configDir) {
|
|
2596
|
+
const filePath = join4(configDir, IDENTITY_FILENAME);
|
|
2597
|
+
if (!existsSync4(filePath)) return null;
|
|
2598
|
+
try {
|
|
2599
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2600
|
+
return AgentIdentitySchema.parse(JSON.parse(raw));
|
|
2601
|
+
} catch {
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
function saveIdentity(configDir, identity) {
|
|
2606
|
+
if (!existsSync4(configDir)) {
|
|
2607
|
+
mkdirSync3(configDir, { recursive: true });
|
|
2608
|
+
}
|
|
2609
|
+
const filePath = join4(configDir, IDENTITY_FILENAME);
|
|
2610
|
+
writeFileSync4(filePath, JSON.stringify(identity, null, 2), "utf-8");
|
|
2611
|
+
}
|
|
2612
|
+
function issueAgentCertificate(identity, privateKey) {
|
|
2613
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2614
|
+
const expiresAt = new Date(Date.now() + 365 * 24 * 60 * 60 * 1e3).toISOString();
|
|
2615
|
+
const payload = {
|
|
2616
|
+
identity,
|
|
2617
|
+
issued_at: issuedAt,
|
|
2618
|
+
expires_at: expiresAt,
|
|
2619
|
+
issuer_public_key: identity.public_key
|
|
2620
|
+
};
|
|
2621
|
+
const signature = signEscrowReceipt(payload, privateKey);
|
|
2622
|
+
return {
|
|
2623
|
+
identity,
|
|
2624
|
+
issued_at: issuedAt,
|
|
2625
|
+
expires_at: expiresAt,
|
|
2626
|
+
issuer_public_key: identity.public_key,
|
|
2627
|
+
signature
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
function verifyAgentCertificate(cert) {
|
|
2631
|
+
if (new Date(cert.expires_at) < /* @__PURE__ */ new Date()) {
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2634
|
+
const publicKeyHex = cert.issuer_public_key;
|
|
2635
|
+
const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
|
|
2636
|
+
const payload = {
|
|
2637
|
+
identity: cert.identity,
|
|
2638
|
+
issued_at: cert.issued_at,
|
|
2639
|
+
expires_at: cert.expires_at,
|
|
2640
|
+
issuer_public_key: cert.issuer_public_key
|
|
2641
|
+
};
|
|
2642
|
+
return verifyEscrowReceipt(payload, cert.signature, publicKeyBuf);
|
|
2643
|
+
}
|
|
2644
|
+
function ensureIdentity(configDir, owner) {
|
|
2645
|
+
const existing = loadIdentity(configDir);
|
|
2646
|
+
if (existing) return existing;
|
|
2647
|
+
return createIdentity(configDir, owner);
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// src/sdk/consumer.ts
|
|
2651
|
+
var AgentBnBConsumer = class {
|
|
2652
|
+
configDir;
|
|
2653
|
+
identity = null;
|
|
2654
|
+
keys = null;
|
|
2655
|
+
creditDb = null;
|
|
2656
|
+
constructor(opts) {
|
|
2657
|
+
this.configDir = opts?.configDir ?? getConfigDir();
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Loads agent identity and keypair from disk.
|
|
2661
|
+
* Creates identity if none exists (uses owner from config.json or generates one).
|
|
2662
|
+
*
|
|
2663
|
+
* @returns The loaded AgentIdentity.
|
|
2664
|
+
* @throws {AgentBnBError} if keypair is missing and cannot be created.
|
|
2665
|
+
*/
|
|
2666
|
+
authenticate() {
|
|
2667
|
+
const config = loadConfig();
|
|
2668
|
+
const owner = config?.owner ?? `agent-${Date.now().toString(36)}`;
|
|
2669
|
+
this.identity = ensureIdentity(this.configDir, owner);
|
|
2670
|
+
this.keys = loadKeyPair(this.configDir);
|
|
2671
|
+
return this.identity;
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Returns the cached identity. Throws if not yet authenticated.
|
|
2675
|
+
*/
|
|
2676
|
+
getIdentity() {
|
|
2677
|
+
if (!this.identity) {
|
|
2678
|
+
throw new AgentBnBError("Not authenticated. Call authenticate() first.", "NOT_AUTHENTICATED");
|
|
2679
|
+
}
|
|
2680
|
+
return this.identity;
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Requests a capability from a remote agent with full escrow lifecycle.
|
|
2684
|
+
*
|
|
2685
|
+
* 1. Creates a signed escrow receipt (holds credits locally)
|
|
2686
|
+
* 2. Sends the request to the target gateway
|
|
2687
|
+
* 3. Settles escrow on success, releases on failure
|
|
2688
|
+
*
|
|
2689
|
+
* @param opts - Request options including target, card, credits, and params.
|
|
2690
|
+
* @returns The result from the capability execution.
|
|
2691
|
+
* @throws {AgentBnBError} on insufficient credits, network error, or RPC error.
|
|
2692
|
+
*/
|
|
2693
|
+
async request(opts) {
|
|
2694
|
+
const identity = this.getIdentity();
|
|
2695
|
+
if (!this.keys) {
|
|
2696
|
+
throw new AgentBnBError("Keypair not loaded. Call authenticate() first.", "NOT_AUTHENTICATED");
|
|
2697
|
+
}
|
|
2698
|
+
const db = this.getCreditDb();
|
|
2699
|
+
const { escrowId, receipt } = createSignedEscrowReceipt(
|
|
2700
|
+
db,
|
|
2701
|
+
this.keys.privateKey,
|
|
2702
|
+
this.keys.publicKey,
|
|
2703
|
+
{
|
|
2704
|
+
owner: identity.owner,
|
|
2705
|
+
amount: opts.credits,
|
|
2706
|
+
cardId: opts.cardId,
|
|
2707
|
+
skillId: opts.skillId
|
|
2708
|
+
}
|
|
2709
|
+
);
|
|
2710
|
+
try {
|
|
2711
|
+
const result = await requestCapability({
|
|
2712
|
+
gatewayUrl: opts.gatewayUrl,
|
|
2713
|
+
token: opts.token,
|
|
2714
|
+
cardId: opts.cardId,
|
|
2715
|
+
params: opts.params,
|
|
2716
|
+
timeoutMs: opts.timeoutMs,
|
|
2717
|
+
escrowReceipt: receipt
|
|
2718
|
+
});
|
|
2719
|
+
settleRequesterEscrow(db, escrowId);
|
|
2720
|
+
return result;
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
releaseRequesterEscrow(db, escrowId);
|
|
2723
|
+
throw err;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Returns the current credit balance for this agent.
|
|
2728
|
+
*/
|
|
2729
|
+
getBalance() {
|
|
2730
|
+
const identity = this.getIdentity();
|
|
2731
|
+
const db = this.getCreditDb();
|
|
2732
|
+
return getBalance(db, identity.owner);
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Returns basic reputation data from the local credit database.
|
|
2736
|
+
* Note: success_rate is computed from local request history only.
|
|
2737
|
+
*/
|
|
2738
|
+
getReputation() {
|
|
2739
|
+
const identity = this.getIdentity();
|
|
2740
|
+
const db = this.getCreditDb();
|
|
2741
|
+
const stmt = db.prepare(`
|
|
2742
|
+
SELECT
|
|
2743
|
+
COUNT(*) as total,
|
|
2744
|
+
SUM(CASE WHEN status = 'settled' THEN 1 ELSE 0 END) as settled
|
|
2745
|
+
FROM credit_escrow
|
|
2746
|
+
WHERE owner = ?
|
|
2747
|
+
`);
|
|
2748
|
+
const row = stmt.get(identity.owner);
|
|
2749
|
+
const total = row?.total ?? 0;
|
|
2750
|
+
const settled = row?.settled ?? 0;
|
|
2751
|
+
return {
|
|
2752
|
+
success_rate: total > 0 ? settled / total : 1,
|
|
2753
|
+
total_requests: total
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Closes the credit database connection. Call when done.
|
|
2758
|
+
*/
|
|
2759
|
+
close() {
|
|
2760
|
+
if (this.creditDb) {
|
|
2761
|
+
this.creditDb.close();
|
|
2762
|
+
this.creditDb = null;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
/** Lazily opens and caches the credit database. */
|
|
2766
|
+
getCreditDb() {
|
|
2767
|
+
if (!this.creditDb) {
|
|
2768
|
+
const config = loadConfig();
|
|
2769
|
+
const creditDbPath = config?.credit_db_path ?? `${this.configDir}/credit.db`;
|
|
2770
|
+
this.creditDb = openCreditDb(creditDbPath);
|
|
2771
|
+
}
|
|
2772
|
+
return this.creditDb;
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
|
|
2776
|
+
// src/sdk/provider.ts
|
|
2777
|
+
var AgentBnBProvider = class {
|
|
2778
|
+
configDir;
|
|
2779
|
+
identity = null;
|
|
2780
|
+
registryDb = null;
|
|
2781
|
+
creditDb = null;
|
|
2782
|
+
gateway = null;
|
|
2783
|
+
constructor(opts) {
|
|
2784
|
+
this.configDir = opts?.configDir ?? getConfigDir();
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Loads agent identity from disk.
|
|
2788
|
+
* Creates identity if none exists.
|
|
2789
|
+
*
|
|
2790
|
+
* @returns The loaded AgentIdentity.
|
|
2791
|
+
*/
|
|
2792
|
+
authenticate() {
|
|
2793
|
+
const config = loadConfig();
|
|
2794
|
+
const owner = config?.owner ?? `agent-${Date.now().toString(36)}`;
|
|
2795
|
+
this.identity = ensureIdentity(this.configDir, owner);
|
|
2796
|
+
return this.identity;
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Returns the cached identity. Throws if not yet authenticated.
|
|
2800
|
+
*/
|
|
2801
|
+
getIdentity() {
|
|
2802
|
+
if (!this.identity) {
|
|
2803
|
+
throw new AgentBnBError("Not authenticated. Call authenticate() first.", "NOT_AUTHENTICATED");
|
|
2804
|
+
}
|
|
2805
|
+
return this.identity;
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Starts the gateway server to share capabilities.
|
|
2809
|
+
*
|
|
2810
|
+
* @param opts - Optional port and host configuration.
|
|
2811
|
+
* @returns Context with the gateway server and port.
|
|
2812
|
+
*/
|
|
2813
|
+
async startSharing(opts) {
|
|
2814
|
+
this.getIdentity();
|
|
2815
|
+
const config = loadConfig();
|
|
2816
|
+
const port = opts?.port ?? config?.gateway_port ?? 7700;
|
|
2817
|
+
const host = opts?.host ?? "0.0.0.0";
|
|
2818
|
+
const registryDb = this.getRegistryDb();
|
|
2819
|
+
const creditDb = this.getCreditDb();
|
|
2820
|
+
const token = config?.token ?? "";
|
|
2821
|
+
const gateway = createGatewayServer({
|
|
2822
|
+
registryDb,
|
|
2823
|
+
creditDb,
|
|
2824
|
+
tokens: [token],
|
|
2825
|
+
handlerUrl: `http://localhost:${port}`,
|
|
2826
|
+
silent: true
|
|
2827
|
+
});
|
|
2828
|
+
await gateway.listen({ port, host });
|
|
2829
|
+
this.gateway = gateway;
|
|
2830
|
+
return { gateway, port };
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Stops the gateway server.
|
|
2834
|
+
*/
|
|
2835
|
+
async stopSharing() {
|
|
2836
|
+
if (this.gateway) {
|
|
2837
|
+
await this.gateway.close();
|
|
2838
|
+
this.gateway = null;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Returns all capability cards owned by this agent.
|
|
2843
|
+
*/
|
|
2844
|
+
listCapabilities() {
|
|
2845
|
+
const identity = this.getIdentity();
|
|
2846
|
+
const db = this.getRegistryDb();
|
|
2847
|
+
return listCards(db, identity.owner);
|
|
2848
|
+
}
|
|
2849
|
+
/**
|
|
2850
|
+
* Returns the current credit balance for this agent.
|
|
2851
|
+
*/
|
|
2852
|
+
getBalance() {
|
|
2853
|
+
const identity = this.getIdentity();
|
|
2854
|
+
const db = this.getCreditDb();
|
|
2855
|
+
return getBalance(db, identity.owner);
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Closes all database connections and stops the gateway. Call when done.
|
|
2859
|
+
*/
|
|
2860
|
+
async close() {
|
|
2861
|
+
await this.stopSharing();
|
|
2862
|
+
if (this.registryDb) {
|
|
2863
|
+
this.registryDb.close();
|
|
2864
|
+
this.registryDb = null;
|
|
2865
|
+
}
|
|
2866
|
+
if (this.creditDb) {
|
|
2867
|
+
this.creditDb.close();
|
|
2868
|
+
this.creditDb = null;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
/** Lazily opens and caches the registry database. */
|
|
2872
|
+
getRegistryDb() {
|
|
2873
|
+
if (!this.registryDb) {
|
|
2874
|
+
const config = loadConfig();
|
|
2875
|
+
const dbPath = config?.db_path ?? `${this.configDir}/registry.db`;
|
|
2876
|
+
this.registryDb = openDatabase(dbPath);
|
|
2877
|
+
}
|
|
2878
|
+
return this.registryDb;
|
|
2879
|
+
}
|
|
2880
|
+
/** Lazily opens and caches the credit database. */
|
|
2881
|
+
getCreditDb() {
|
|
2882
|
+
if (!this.creditDb) {
|
|
2883
|
+
const config = loadConfig();
|
|
2884
|
+
const creditDbPath = config?.credit_db_path ?? `${this.configDir}/credit.db`;
|
|
2885
|
+
this.creditDb = openCreditDb(creditDbPath);
|
|
2886
|
+
}
|
|
2887
|
+
return this.creditDb;
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2891
|
+
// src/identity/guarantor.ts
|
|
2892
|
+
import { z as z5 } from "zod";
|
|
2893
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
2894
|
+
var MAX_AGENTS_PER_GUARANTOR = 10;
|
|
2895
|
+
var GUARANTOR_CREDIT_POOL = 50;
|
|
2896
|
+
var GuarantorRecordSchema = z5.object({
|
|
2897
|
+
id: z5.string().uuid(),
|
|
2898
|
+
github_login: z5.string().min(1),
|
|
2899
|
+
agent_count: z5.number().int().nonnegative(),
|
|
2900
|
+
credit_pool: z5.number().int().nonnegative(),
|
|
2901
|
+
created_at: z5.string().datetime()
|
|
2902
|
+
});
|
|
2903
|
+
var GUARANTOR_SCHEMA = `
|
|
2904
|
+
CREATE TABLE IF NOT EXISTS guarantors (
|
|
2905
|
+
id TEXT PRIMARY KEY,
|
|
2906
|
+
github_login TEXT UNIQUE NOT NULL,
|
|
2907
|
+
agent_count INTEGER NOT NULL DEFAULT 0,
|
|
2908
|
+
credit_pool INTEGER NOT NULL DEFAULT ${GUARANTOR_CREDIT_POOL},
|
|
2909
|
+
created_at TEXT NOT NULL
|
|
2910
|
+
);
|
|
2911
|
+
|
|
2912
|
+
CREATE TABLE IF NOT EXISTS agent_guarantors (
|
|
2913
|
+
agent_id TEXT PRIMARY KEY,
|
|
2914
|
+
guarantor_id TEXT NOT NULL,
|
|
2915
|
+
linked_at TEXT NOT NULL,
|
|
2916
|
+
FOREIGN KEY (guarantor_id) REFERENCES guarantors(id)
|
|
2917
|
+
);
|
|
2918
|
+
`;
|
|
2919
|
+
function ensureGuarantorTables(db) {
|
|
2920
|
+
db.exec(GUARANTOR_SCHEMA);
|
|
2921
|
+
}
|
|
2922
|
+
function registerGuarantor(db, githubLogin) {
|
|
2923
|
+
ensureGuarantorTables(db);
|
|
2924
|
+
const existing = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
|
|
2925
|
+
if (existing) {
|
|
2926
|
+
throw new AgentBnBError(
|
|
2927
|
+
`Guarantor already registered: ${githubLogin}`,
|
|
2928
|
+
"GUARANTOR_EXISTS"
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
const record = {
|
|
2932
|
+
id: randomUUID9(),
|
|
2933
|
+
github_login: githubLogin,
|
|
2934
|
+
agent_count: 0,
|
|
2935
|
+
credit_pool: GUARANTOR_CREDIT_POOL,
|
|
2936
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2937
|
+
};
|
|
2938
|
+
db.prepare(
|
|
2939
|
+
"INSERT INTO guarantors (id, github_login, agent_count, credit_pool, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
2940
|
+
).run(record.id, record.github_login, record.agent_count, record.credit_pool, record.created_at);
|
|
2941
|
+
return record;
|
|
2942
|
+
}
|
|
2943
|
+
function linkAgentToGuarantor(db, agentId, githubLogin) {
|
|
2944
|
+
ensureGuarantorTables(db);
|
|
2945
|
+
const guarantor = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
|
|
2946
|
+
if (!guarantor) {
|
|
2947
|
+
throw new AgentBnBError(
|
|
2948
|
+
`Guarantor not found: ${githubLogin}`,
|
|
2949
|
+
"GUARANTOR_NOT_FOUND"
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
if (guarantor["agent_count"] >= MAX_AGENTS_PER_GUARANTOR) {
|
|
2953
|
+
throw new AgentBnBError(
|
|
2954
|
+
`Maximum agents per guarantor reached (${MAX_AGENTS_PER_GUARANTOR})`,
|
|
2955
|
+
"MAX_AGENTS_EXCEEDED"
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2958
|
+
const existingLink = db.prepare("SELECT * FROM agent_guarantors WHERE agent_id = ?").get(agentId);
|
|
2959
|
+
if (existingLink) {
|
|
2960
|
+
throw new AgentBnBError(
|
|
2961
|
+
`Agent ${agentId} is already linked to a guarantor`,
|
|
2962
|
+
"AGENT_ALREADY_LINKED"
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
db.transaction(() => {
|
|
2966
|
+
db.prepare("INSERT INTO agent_guarantors (agent_id, guarantor_id, linked_at) VALUES (?, ?, ?)").run(
|
|
2967
|
+
agentId,
|
|
2968
|
+
guarantor["id"],
|
|
2969
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
2970
|
+
);
|
|
2971
|
+
db.prepare("UPDATE guarantors SET agent_count = agent_count + 1 WHERE id = ?").run(guarantor["id"]);
|
|
2972
|
+
})();
|
|
2973
|
+
return getGuarantor(db, githubLogin);
|
|
2974
|
+
}
|
|
2975
|
+
function getGuarantor(db, githubLogin) {
|
|
2976
|
+
ensureGuarantorTables(db);
|
|
2977
|
+
const row = db.prepare("SELECT * FROM guarantors WHERE github_login = ?").get(githubLogin);
|
|
2978
|
+
if (!row) return null;
|
|
2979
|
+
return {
|
|
2980
|
+
id: row["id"],
|
|
2981
|
+
github_login: row["github_login"],
|
|
2982
|
+
agent_count: row["agent_count"],
|
|
2983
|
+
credit_pool: row["credit_pool"],
|
|
2984
|
+
created_at: row["created_at"]
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2987
|
+
function getAgentGuarantor(db, agentId) {
|
|
2988
|
+
ensureGuarantorTables(db);
|
|
2989
|
+
const link = db.prepare(
|
|
2990
|
+
`SELECT g.* FROM guarantors g
|
|
2991
|
+
JOIN agent_guarantors ag ON ag.guarantor_id = g.id
|
|
2992
|
+
WHERE ag.agent_id = ?`
|
|
2993
|
+
).get(agentId);
|
|
2994
|
+
if (!link) return null;
|
|
2995
|
+
return {
|
|
2996
|
+
id: link["id"],
|
|
2997
|
+
github_login: link["github_login"],
|
|
2998
|
+
agent_count: link["agent_count"],
|
|
2999
|
+
credit_pool: link["credit_pool"],
|
|
3000
|
+
created_at: link["created_at"]
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function initiateGithubAuth() {
|
|
3004
|
+
return {
|
|
3005
|
+
auth_url: "https://github.com/login/oauth/authorize?client_id=PLACEHOLDER&scope=read:user",
|
|
3006
|
+
state: randomUUID9()
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
// src/relay/types.ts
|
|
3011
|
+
import { z as z6 } from "zod";
|
|
3012
|
+
var RegisterMessageSchema = z6.object({
|
|
3013
|
+
type: z6.literal("register"),
|
|
3014
|
+
owner: z6.string().min(1),
|
|
3015
|
+
token: z6.string().min(1),
|
|
3016
|
+
card: z6.record(z6.unknown())
|
|
3017
|
+
// CapabilityCard (validated separately)
|
|
3018
|
+
});
|
|
3019
|
+
var RegisteredMessageSchema = z6.object({
|
|
3020
|
+
type: z6.literal("registered"),
|
|
3021
|
+
agent_id: z6.string()
|
|
3022
|
+
});
|
|
3023
|
+
var RelayRequestMessageSchema = z6.object({
|
|
3024
|
+
type: z6.literal("relay_request"),
|
|
3025
|
+
id: z6.string().uuid(),
|
|
3026
|
+
target_owner: z6.string().min(1),
|
|
3027
|
+
card_id: z6.string(),
|
|
3028
|
+
skill_id: z6.string().optional(),
|
|
3029
|
+
params: z6.record(z6.unknown()).default({}),
|
|
3030
|
+
requester: z6.string().optional(),
|
|
3031
|
+
escrow_receipt: z6.record(z6.unknown()).optional()
|
|
3032
|
+
});
|
|
3033
|
+
var IncomingRequestMessageSchema = z6.object({
|
|
3034
|
+
type: z6.literal("incoming_request"),
|
|
3035
|
+
id: z6.string().uuid(),
|
|
3036
|
+
from_owner: z6.string().min(1),
|
|
3037
|
+
card_id: z6.string(),
|
|
3038
|
+
skill_id: z6.string().optional(),
|
|
3039
|
+
params: z6.record(z6.unknown()).default({}),
|
|
3040
|
+
requester: z6.string().optional(),
|
|
3041
|
+
escrow_receipt: z6.record(z6.unknown()).optional()
|
|
3042
|
+
});
|
|
3043
|
+
var RelayResponseMessageSchema = z6.object({
|
|
3044
|
+
type: z6.literal("relay_response"),
|
|
3045
|
+
id: z6.string().uuid(),
|
|
3046
|
+
result: z6.unknown().optional(),
|
|
3047
|
+
error: z6.object({
|
|
3048
|
+
code: z6.number(),
|
|
3049
|
+
message: z6.string()
|
|
3050
|
+
}).optional()
|
|
3051
|
+
});
|
|
3052
|
+
var ResponseMessageSchema = z6.object({
|
|
3053
|
+
type: z6.literal("response"),
|
|
3054
|
+
id: z6.string().uuid(),
|
|
3055
|
+
result: z6.unknown().optional(),
|
|
3056
|
+
error: z6.object({
|
|
3057
|
+
code: z6.number(),
|
|
3058
|
+
message: z6.string()
|
|
3059
|
+
}).optional()
|
|
3060
|
+
});
|
|
3061
|
+
var ErrorMessageSchema = z6.object({
|
|
3062
|
+
type: z6.literal("error"),
|
|
3063
|
+
code: z6.string(),
|
|
3064
|
+
message: z6.string(),
|
|
3065
|
+
request_id: z6.string().optional()
|
|
3066
|
+
});
|
|
3067
|
+
var RelayMessageSchema = z6.discriminatedUnion("type", [
|
|
3068
|
+
RegisterMessageSchema,
|
|
3069
|
+
RegisteredMessageSchema,
|
|
3070
|
+
RelayRequestMessageSchema,
|
|
3071
|
+
IncomingRequestMessageSchema,
|
|
3072
|
+
RelayResponseMessageSchema,
|
|
3073
|
+
ResponseMessageSchema,
|
|
3074
|
+
ErrorMessageSchema
|
|
3075
|
+
]);
|
|
3076
|
+
|
|
3077
|
+
// src/relay/websocket-relay.ts
|
|
3078
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
3079
|
+
var RATE_LIMIT_MAX = 60;
|
|
3080
|
+
var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
3081
|
+
var RELAY_TIMEOUT_MS = 3e4;
|
|
3082
|
+
function registerWebSocketRelay(server, db) {
|
|
3083
|
+
const connections = /* @__PURE__ */ new Map();
|
|
3084
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
3085
|
+
const rateLimits = /* @__PURE__ */ new Map();
|
|
3086
|
+
function checkRateLimit(owner) {
|
|
3087
|
+
const now = Date.now();
|
|
3088
|
+
const entry = rateLimits.get(owner);
|
|
3089
|
+
if (!entry || now >= entry.resetTime) {
|
|
3090
|
+
rateLimits.set(owner, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
|
|
3091
|
+
return true;
|
|
3092
|
+
}
|
|
3093
|
+
if (entry.count >= RATE_LIMIT_MAX) {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
entry.count++;
|
|
3097
|
+
return true;
|
|
3098
|
+
}
|
|
3099
|
+
function markOwnerOffline(owner) {
|
|
3100
|
+
try {
|
|
3101
|
+
const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
|
|
3102
|
+
const rows = stmt.all(owner);
|
|
3103
|
+
for (const row of rows) {
|
|
3104
|
+
try {
|
|
3105
|
+
const card = JSON.parse(row.data);
|
|
3106
|
+
if (card.availability?.online) {
|
|
3107
|
+
card.availability.online = false;
|
|
3108
|
+
card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3109
|
+
const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
|
|
3110
|
+
updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
|
|
3111
|
+
}
|
|
3112
|
+
} catch {
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
} catch {
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
function markOwnerOnline(owner) {
|
|
3119
|
+
try {
|
|
3120
|
+
const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
|
|
3121
|
+
const rows = stmt.all(owner);
|
|
3122
|
+
for (const row of rows) {
|
|
3123
|
+
try {
|
|
3124
|
+
const card = JSON.parse(row.data);
|
|
3125
|
+
card.availability = { ...card.availability, online: true };
|
|
3126
|
+
card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
3127
|
+
const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
|
|
3128
|
+
updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
} catch {
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
function upsertCard(cardData, owner) {
|
|
3136
|
+
const cardId = cardData.id;
|
|
3137
|
+
const existing = getCard(db, cardId);
|
|
3138
|
+
if (existing) {
|
|
3139
|
+
const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
|
|
3140
|
+
updateCard(db, cardId, owner, updates);
|
|
3141
|
+
} else {
|
|
3142
|
+
const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
|
|
3143
|
+
insertCard(db, card);
|
|
3144
|
+
}
|
|
3145
|
+
return cardId;
|
|
3146
|
+
}
|
|
3147
|
+
function logAgentJoined(owner, cardName, cardId) {
|
|
3148
|
+
try {
|
|
3149
|
+
insertRequestLog(db, {
|
|
3150
|
+
id: randomUUID10(),
|
|
3151
|
+
card_id: cardId,
|
|
3152
|
+
card_name: cardName,
|
|
3153
|
+
requester: owner,
|
|
3154
|
+
status: "success",
|
|
3155
|
+
latency_ms: 0,
|
|
3156
|
+
credits_charged: 0,
|
|
3157
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3158
|
+
action_type: "agent_joined"
|
|
3159
|
+
});
|
|
3160
|
+
} catch {
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
function sendMessage(ws, msg) {
|
|
3164
|
+
if (ws.readyState === 1) {
|
|
3165
|
+
ws.send(JSON.stringify(msg));
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
function handleRegister(ws, msg) {
|
|
3169
|
+
const { owner, card } = msg;
|
|
3170
|
+
const existing = connections.get(owner);
|
|
3171
|
+
if (existing && existing !== ws) {
|
|
3172
|
+
try {
|
|
3173
|
+
existing.close(1e3, "Replaced by new connection");
|
|
3174
|
+
} catch {
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
connections.set(owner, ws);
|
|
3178
|
+
const cardId = upsertCard(card, owner);
|
|
3179
|
+
const cardName = card.name ?? card.agent_name ?? owner;
|
|
3180
|
+
logAgentJoined(owner, cardName, cardId);
|
|
3181
|
+
markOwnerOnline(owner);
|
|
3182
|
+
sendMessage(ws, { type: "registered", agent_id: cardId });
|
|
3183
|
+
}
|
|
3184
|
+
function handleRelayRequest(ws, msg, fromOwner) {
|
|
3185
|
+
if (!checkRateLimit(fromOwner)) {
|
|
3186
|
+
sendMessage(ws, {
|
|
3187
|
+
type: "error",
|
|
3188
|
+
code: "rate_limited",
|
|
3189
|
+
message: `Rate limit exceeded: max ${RATE_LIMIT_MAX} requests per minute`,
|
|
3190
|
+
request_id: msg.id
|
|
3191
|
+
});
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
const targetWs = connections.get(msg.target_owner);
|
|
3195
|
+
if (!targetWs || targetWs.readyState !== 1) {
|
|
3196
|
+
sendMessage(ws, {
|
|
3197
|
+
type: "response",
|
|
3198
|
+
id: msg.id,
|
|
3199
|
+
error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
|
|
3200
|
+
});
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
const timeout = setTimeout(() => {
|
|
3204
|
+
pendingRequests.delete(msg.id);
|
|
3205
|
+
sendMessage(ws, {
|
|
3206
|
+
type: "response",
|
|
3207
|
+
id: msg.id,
|
|
3208
|
+
error: { code: -32603, message: "Relay request timeout" }
|
|
3209
|
+
});
|
|
3210
|
+
}, RELAY_TIMEOUT_MS);
|
|
3211
|
+
pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
|
|
3212
|
+
sendMessage(targetWs, {
|
|
3213
|
+
type: "incoming_request",
|
|
3214
|
+
id: msg.id,
|
|
3215
|
+
from_owner: fromOwner,
|
|
3216
|
+
card_id: msg.card_id,
|
|
3217
|
+
skill_id: msg.skill_id,
|
|
3218
|
+
params: msg.params,
|
|
3219
|
+
requester: msg.requester ?? fromOwner,
|
|
3220
|
+
escrow_receipt: msg.escrow_receipt
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
function handleRelayResponse(msg) {
|
|
3224
|
+
const pending = pendingRequests.get(msg.id);
|
|
3225
|
+
if (!pending) return;
|
|
3226
|
+
clearTimeout(pending.timeout);
|
|
3227
|
+
pendingRequests.delete(msg.id);
|
|
3228
|
+
const originWs = connections.get(pending.originOwner);
|
|
3229
|
+
if (originWs && originWs.readyState === 1) {
|
|
3230
|
+
sendMessage(originWs, {
|
|
3231
|
+
type: "response",
|
|
3232
|
+
id: msg.id,
|
|
3233
|
+
result: msg.result,
|
|
3234
|
+
error: msg.error
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
function handleDisconnect(owner) {
|
|
3239
|
+
if (!owner) return;
|
|
3240
|
+
connections.delete(owner);
|
|
3241
|
+
rateLimits.delete(owner);
|
|
3242
|
+
markOwnerOffline(owner);
|
|
3243
|
+
for (const [reqId, pending] of pendingRequests) {
|
|
3244
|
+
if (pending.originOwner === owner) {
|
|
3245
|
+
clearTimeout(pending.timeout);
|
|
3246
|
+
pendingRequests.delete(reqId);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
server.get("/ws", { websocket: true }, (rawSocket, _request) => {
|
|
3251
|
+
const socket = rawSocket;
|
|
3252
|
+
let registeredOwner;
|
|
3253
|
+
socket.on("message", (raw) => {
|
|
3254
|
+
let data;
|
|
3255
|
+
try {
|
|
3256
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
3257
|
+
} catch {
|
|
3258
|
+
sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
3262
|
+
if (!parsed.success) {
|
|
3263
|
+
sendMessage(socket, {
|
|
3264
|
+
type: "error",
|
|
3265
|
+
code: "invalid_message",
|
|
3266
|
+
message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
|
|
3267
|
+
});
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
const msg = parsed.data;
|
|
3271
|
+
switch (msg.type) {
|
|
3272
|
+
case "register":
|
|
3273
|
+
registeredOwner = msg.owner;
|
|
3274
|
+
handleRegister(socket, msg);
|
|
3275
|
+
break;
|
|
3276
|
+
case "relay_request":
|
|
3277
|
+
if (!registeredOwner) {
|
|
3278
|
+
sendMessage(socket, {
|
|
3279
|
+
type: "error",
|
|
3280
|
+
code: "not_registered",
|
|
3281
|
+
message: "Must send register message before relay requests"
|
|
3282
|
+
});
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
handleRelayRequest(socket, msg, registeredOwner);
|
|
3286
|
+
break;
|
|
3287
|
+
case "relay_response":
|
|
3288
|
+
handleRelayResponse(msg);
|
|
3289
|
+
break;
|
|
3290
|
+
default:
|
|
3291
|
+
break;
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
3294
|
+
socket.on("close", () => {
|
|
3295
|
+
handleDisconnect(registeredOwner);
|
|
3296
|
+
});
|
|
3297
|
+
socket.on("error", () => {
|
|
3298
|
+
handleDisconnect(registeredOwner);
|
|
3299
|
+
});
|
|
3300
|
+
});
|
|
3301
|
+
return {
|
|
3302
|
+
getOnlineCount: () => connections.size,
|
|
3303
|
+
getOnlineOwners: () => Array.from(connections.keys()),
|
|
3304
|
+
shutdown: () => {
|
|
3305
|
+
for (const [, ws] of connections) {
|
|
3306
|
+
try {
|
|
3307
|
+
ws.close(1001, "Server shutting down");
|
|
3308
|
+
} catch {
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
connections.clear();
|
|
3312
|
+
for (const [, pending] of pendingRequests) {
|
|
3313
|
+
clearTimeout(pending.timeout);
|
|
3314
|
+
}
|
|
3315
|
+
pendingRequests.clear();
|
|
3316
|
+
rateLimits.clear();
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
// src/relay/websocket-client.ts
|
|
3322
|
+
import WebSocket from "ws";
|
|
3323
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
3324
|
+
var RelayClient = class {
|
|
3325
|
+
ws = null;
|
|
3326
|
+
opts;
|
|
3327
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3328
|
+
reconnectAttempts = 0;
|
|
3329
|
+
reconnectTimer = null;
|
|
3330
|
+
intentionalClose = false;
|
|
3331
|
+
registered = false;
|
|
3332
|
+
pongTimeout = null;
|
|
3333
|
+
pingInterval = null;
|
|
3334
|
+
constructor(opts) {
|
|
3335
|
+
this.opts = opts;
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Connect to the registry relay and register.
|
|
3339
|
+
* Resolves when registration is acknowledged.
|
|
3340
|
+
*/
|
|
3341
|
+
async connect() {
|
|
3342
|
+
return new Promise((resolve, reject) => {
|
|
3343
|
+
this.intentionalClose = false;
|
|
3344
|
+
this.registered = false;
|
|
3345
|
+
const wsUrl = this.buildWsUrl();
|
|
3346
|
+
this.ws = new WebSocket(wsUrl);
|
|
3347
|
+
let resolved = false;
|
|
3348
|
+
this.ws.on("open", () => {
|
|
3349
|
+
this.reconnectAttempts = 0;
|
|
3350
|
+
this.startPingInterval();
|
|
3351
|
+
this.send({
|
|
3352
|
+
type: "register",
|
|
3353
|
+
owner: this.opts.owner,
|
|
3354
|
+
token: this.opts.token,
|
|
3355
|
+
card: this.opts.card
|
|
3356
|
+
});
|
|
3357
|
+
});
|
|
3358
|
+
this.ws.on("message", (raw) => {
|
|
3359
|
+
this.handleMessage(raw, (err) => {
|
|
3360
|
+
if (!resolved) {
|
|
3361
|
+
resolved = true;
|
|
3362
|
+
if (err) reject(err);
|
|
3363
|
+
else resolve();
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
});
|
|
3367
|
+
this.ws.on("close", () => {
|
|
3368
|
+
this.cleanup();
|
|
3369
|
+
if (!this.intentionalClose) {
|
|
3370
|
+
if (!resolved) {
|
|
3371
|
+
resolved = true;
|
|
3372
|
+
reject(new Error("WebSocket closed before registration"));
|
|
3373
|
+
}
|
|
3374
|
+
this.scheduleReconnect();
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
this.ws.on("error", (err) => {
|
|
3378
|
+
if (!resolved) {
|
|
3379
|
+
resolved = true;
|
|
3380
|
+
reject(err);
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
setTimeout(() => {
|
|
3384
|
+
if (!resolved) {
|
|
3385
|
+
resolved = true;
|
|
3386
|
+
reject(new Error("Connection timeout"));
|
|
3387
|
+
this.ws?.close();
|
|
3388
|
+
}
|
|
3389
|
+
}, 1e4);
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Disconnect from the registry relay.
|
|
3394
|
+
*/
|
|
3395
|
+
disconnect() {
|
|
3396
|
+
this.intentionalClose = true;
|
|
3397
|
+
this.cleanup();
|
|
3398
|
+
if (this.ws) {
|
|
3399
|
+
try {
|
|
3400
|
+
this.ws.close(1e3, "Client disconnect");
|
|
3401
|
+
} catch {
|
|
3402
|
+
}
|
|
3403
|
+
this.ws = null;
|
|
3404
|
+
}
|
|
3405
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
3406
|
+
clearTimeout(pending.timeout);
|
|
3407
|
+
pending.reject(new Error("Client disconnected"));
|
|
3408
|
+
this.pendingRequests.delete(id);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
/**
|
|
3412
|
+
* Send a relay request to another agent via the registry.
|
|
3413
|
+
* @returns The result from the target agent.
|
|
3414
|
+
*/
|
|
3415
|
+
async request(opts) {
|
|
3416
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
|
|
3417
|
+
throw new Error("Not connected to registry relay");
|
|
3418
|
+
}
|
|
3419
|
+
const id = randomUUID11();
|
|
3420
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
3421
|
+
return new Promise((resolve, reject) => {
|
|
3422
|
+
const timeout = setTimeout(() => {
|
|
3423
|
+
this.pendingRequests.delete(id);
|
|
3424
|
+
reject(new Error("Relay request timeout"));
|
|
3425
|
+
}, timeoutMs);
|
|
3426
|
+
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
3427
|
+
this.send({
|
|
3428
|
+
type: "relay_request",
|
|
3429
|
+
id,
|
|
3430
|
+
target_owner: opts.targetOwner,
|
|
3431
|
+
card_id: opts.cardId,
|
|
3432
|
+
skill_id: opts.skillId,
|
|
3433
|
+
params: opts.params,
|
|
3434
|
+
requester: opts.requester ?? this.opts.owner,
|
|
3435
|
+
escrow_receipt: opts.escrowReceipt
|
|
3436
|
+
});
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
/** Whether the client is connected and registered */
|
|
3440
|
+
get isConnected() {
|
|
3441
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
|
|
3442
|
+
}
|
|
3443
|
+
// ── Private methods ─────────────────────────────────────────────────────────
|
|
3444
|
+
buildWsUrl() {
|
|
3445
|
+
let url = this.opts.registryUrl;
|
|
3446
|
+
if (url.startsWith("http://")) {
|
|
3447
|
+
url = "ws://" + url.slice(7);
|
|
3448
|
+
} else if (url.startsWith("https://")) {
|
|
3449
|
+
url = "wss://" + url.slice(8);
|
|
3450
|
+
} else if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
|
|
3451
|
+
url = "wss://" + url;
|
|
3452
|
+
}
|
|
3453
|
+
if (!url.endsWith("/ws")) {
|
|
3454
|
+
url = url.replace(/\/$/, "") + "/ws";
|
|
3455
|
+
}
|
|
3456
|
+
return url;
|
|
3457
|
+
}
|
|
3458
|
+
handleMessage(raw, onRegistered) {
|
|
3459
|
+
let data;
|
|
3460
|
+
try {
|
|
3461
|
+
data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
|
|
3462
|
+
} catch {
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
const parsed = RelayMessageSchema.safeParse(data);
|
|
3466
|
+
if (!parsed.success) return;
|
|
3467
|
+
const msg = parsed.data;
|
|
3468
|
+
switch (msg.type) {
|
|
3469
|
+
case "registered":
|
|
3470
|
+
this.registered = true;
|
|
3471
|
+
if (!this.opts.silent) {
|
|
3472
|
+
console.log(` \u2713 Registered with registry (agent_id: ${msg.agent_id})`);
|
|
3473
|
+
}
|
|
3474
|
+
onRegistered?.();
|
|
3475
|
+
break;
|
|
3476
|
+
case "incoming_request":
|
|
3477
|
+
this.handleIncomingRequest(msg);
|
|
3478
|
+
break;
|
|
3479
|
+
case "response":
|
|
3480
|
+
this.handleResponse(msg);
|
|
3481
|
+
break;
|
|
3482
|
+
case "error":
|
|
3483
|
+
this.handleError(msg);
|
|
3484
|
+
break;
|
|
3485
|
+
default:
|
|
3486
|
+
break;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
async handleIncomingRequest(msg) {
|
|
3490
|
+
try {
|
|
3491
|
+
const result = await this.opts.onRequest(msg);
|
|
3492
|
+
this.send({
|
|
3493
|
+
type: "relay_response",
|
|
3494
|
+
id: msg.id,
|
|
3495
|
+
result: result.result,
|
|
3496
|
+
error: result.error
|
|
3497
|
+
});
|
|
3498
|
+
} catch (err) {
|
|
3499
|
+
this.send({
|
|
3500
|
+
type: "relay_response",
|
|
3501
|
+
id: msg.id,
|
|
3502
|
+
error: {
|
|
3503
|
+
code: -32603,
|
|
3504
|
+
message: err instanceof Error ? err.message : "Internal error"
|
|
3505
|
+
}
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
handleResponse(msg) {
|
|
3510
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
3511
|
+
if (!pending) return;
|
|
3512
|
+
clearTimeout(pending.timeout);
|
|
3513
|
+
this.pendingRequests.delete(msg.id);
|
|
3514
|
+
if (msg.error) {
|
|
3515
|
+
pending.reject(new Error(msg.error.message));
|
|
3516
|
+
} else {
|
|
3517
|
+
pending.resolve(msg.result);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
handleError(msg) {
|
|
3521
|
+
if (msg.request_id) {
|
|
3522
|
+
const pending = this.pendingRequests.get(msg.request_id);
|
|
3523
|
+
if (pending) {
|
|
3524
|
+
clearTimeout(pending.timeout);
|
|
3525
|
+
this.pendingRequests.delete(msg.request_id);
|
|
3526
|
+
pending.reject(new Error(`${msg.code}: ${msg.message}`));
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
send(msg) {
|
|
3531
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3532
|
+
this.ws.send(JSON.stringify(msg));
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
startPingInterval() {
|
|
3536
|
+
this.stopPingInterval();
|
|
3537
|
+
this.pingInterval = setInterval(() => {
|
|
3538
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3539
|
+
this.ws.ping();
|
|
3540
|
+
this.pongTimeout = setTimeout(() => {
|
|
3541
|
+
if (!this.opts.silent) {
|
|
3542
|
+
console.log(" \u26A0 Registry pong timeout, reconnecting...");
|
|
3543
|
+
}
|
|
3544
|
+
this.ws?.terminate();
|
|
3545
|
+
}, 15e3);
|
|
3546
|
+
}
|
|
3547
|
+
}, 3e4);
|
|
3548
|
+
this.ws?.on("pong", () => {
|
|
3549
|
+
if (this.pongTimeout) {
|
|
3550
|
+
clearTimeout(this.pongTimeout);
|
|
3551
|
+
this.pongTimeout = null;
|
|
3552
|
+
}
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
stopPingInterval() {
|
|
3556
|
+
if (this.pingInterval) {
|
|
3557
|
+
clearInterval(this.pingInterval);
|
|
3558
|
+
this.pingInterval = null;
|
|
3559
|
+
}
|
|
3560
|
+
if (this.pongTimeout) {
|
|
3561
|
+
clearTimeout(this.pongTimeout);
|
|
3562
|
+
this.pongTimeout = null;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
cleanup() {
|
|
3566
|
+
this.stopPingInterval();
|
|
3567
|
+
this.registered = false;
|
|
3568
|
+
}
|
|
3569
|
+
scheduleReconnect() {
|
|
3570
|
+
if (this.intentionalClose) return;
|
|
3571
|
+
if (this.reconnectTimer) return;
|
|
3572
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
3573
|
+
this.reconnectAttempts++;
|
|
3574
|
+
if (!this.opts.silent) {
|
|
3575
|
+
console.log(` \u21BB Reconnecting to registry in ${delay / 1e3}s...`);
|
|
3576
|
+
}
|
|
3577
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
3578
|
+
this.reconnectTimer = null;
|
|
3579
|
+
try {
|
|
3580
|
+
await this.connect();
|
|
3581
|
+
if (!this.opts.silent) {
|
|
3582
|
+
console.log(" \u2713 Reconnected to registry");
|
|
3583
|
+
}
|
|
3584
|
+
} catch {
|
|
3585
|
+
}
|
|
3586
|
+
}, delay);
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3590
|
+
// src/onboarding/index.ts
|
|
3591
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
3592
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
3593
|
+
import { join as join5 } from "path";
|
|
3594
|
+
|
|
3595
|
+
// src/cli/onboarding.ts
|
|
3596
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
3597
|
+
import { createConnection } from "net";
|
|
3598
|
+
var KNOWN_API_KEYS = [
|
|
3599
|
+
"OPENAI_API_KEY",
|
|
3600
|
+
"ANTHROPIC_API_KEY",
|
|
3601
|
+
"ELEVENLABS_API_KEY",
|
|
3602
|
+
"KLING_API_KEY",
|
|
3603
|
+
"STABILITY_API_KEY",
|
|
3604
|
+
"REPLICATE_API_TOKEN",
|
|
3605
|
+
"GOOGLE_API_KEY",
|
|
3606
|
+
"AZURE_OPENAI_API_KEY",
|
|
3607
|
+
"COHERE_API_KEY",
|
|
3608
|
+
"MISTRAL_API_KEY"
|
|
3609
|
+
];
|
|
3610
|
+
function detectApiKeys(knownKeys) {
|
|
3611
|
+
return knownKeys.filter((key) => key in process.env);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// src/onboarding/capability-templates.ts
|
|
3615
|
+
var API_PATTERNS = [
|
|
3616
|
+
{
|
|
3617
|
+
pattern: /openai|gpt-4|gpt-3|chatgpt|dall-e/i,
|
|
3618
|
+
capability: { key: "openai", name: "OpenAI Text Generation", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] }
|
|
3619
|
+
},
|
|
3620
|
+
{
|
|
3621
|
+
pattern: /elevenlabs|eleven.?labs/i,
|
|
3622
|
+
capability: { key: "elevenlabs", name: "ElevenLabs TTS", category: "TTS", credits_per_call: 5, tags: ["tts", "audio", "voice"] }
|
|
3623
|
+
},
|
|
3624
|
+
{
|
|
3625
|
+
pattern: /anthropic|claude/i,
|
|
3626
|
+
capability: { key: "anthropic", name: "Anthropic Claude", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] }
|
|
3627
|
+
},
|
|
3628
|
+
{
|
|
3629
|
+
pattern: /recraft/i,
|
|
3630
|
+
capability: { key: "recraft", name: "Recraft V4 Image Gen", category: "Image Gen", credits_per_call: 8, tags: ["image", "generation", "design"] }
|
|
3631
|
+
},
|
|
3632
|
+
{
|
|
3633
|
+
pattern: /kling/i,
|
|
3634
|
+
capability: { key: "kling", name: "Kling AI Video Gen", category: "Video Gen", credits_per_call: 10, tags: ["video", "generation"] }
|
|
3635
|
+
},
|
|
3636
|
+
{
|
|
3637
|
+
pattern: /stable.?diffusion|sdxl|comfyui/i,
|
|
3638
|
+
capability: { key: "stable-diffusion", name: "Stable Diffusion Image Gen", category: "Image Gen", credits_per_call: 6, tags: ["image", "generation", "diffusion"] }
|
|
3639
|
+
},
|
|
3640
|
+
{
|
|
3641
|
+
pattern: /whisper|speech.?to.?text|stt/i,
|
|
3642
|
+
capability: { key: "whisper", name: "Whisper Speech-to-Text", category: "STT", credits_per_call: 3, tags: ["stt", "audio", "transcription"] }
|
|
3643
|
+
},
|
|
3644
|
+
{
|
|
3645
|
+
pattern: /puppeteer|playwright|selenium/i,
|
|
3646
|
+
capability: { key: "puppeteer", name: "Web Scraping & Automation", category: "Web Scraping", credits_per_call: 2, tags: ["scraping", "automation", "browser"] }
|
|
3647
|
+
},
|
|
3648
|
+
{
|
|
3649
|
+
pattern: /ffmpeg/i,
|
|
3650
|
+
capability: { key: "ffmpeg", name: "FFmpeg Media Processing", category: "Media Processing", credits_per_call: 3, tags: ["media", "audio", "video", "processing"] }
|
|
3651
|
+
},
|
|
3652
|
+
{
|
|
3653
|
+
pattern: /tesseract|ocr/i,
|
|
3654
|
+
capability: { key: "tesseract", name: "OCR Text Extraction", category: "OCR", credits_per_call: 4, tags: ["ocr", "text", "extraction"] }
|
|
3655
|
+
}
|
|
3656
|
+
];
|
|
3657
|
+
var INTERACTIVE_TEMPLATES = [
|
|
3658
|
+
{ key: "openai", name: "Text Generation (GPT-4o / Claude / Gemini)", category: "Text Gen", credits_per_call: 3, tags: ["llm", "text", "generation"] },
|
|
3659
|
+
{ key: "image-gen", name: "Image Generation (DALL-E / Recraft / Stable Diffusion)", category: "Image Gen", credits_per_call: 8, tags: ["image", "generation"] },
|
|
3660
|
+
{ key: "tts", name: "TTS / Voice (ElevenLabs / Google TTS)", category: "TTS", credits_per_call: 5, tags: ["tts", "audio", "voice"] },
|
|
3661
|
+
{ key: "video-gen", name: "Video Generation (Kling / Runway)", category: "Video Gen", credits_per_call: 10, tags: ["video", "generation"] },
|
|
3662
|
+
{ key: "code-review", name: "Code Review / Analysis", category: "Code", credits_per_call: 3, tags: ["code", "review", "analysis"] },
|
|
3663
|
+
{ key: "scraping", name: "Web Scraping / Data Extraction", category: "Web Scraping", credits_per_call: 2, tags: ["scraping", "data", "extraction"] },
|
|
3664
|
+
{ key: "translation", name: "Translation", category: "Translation", credits_per_call: 3, tags: ["translation", "language", "text"] },
|
|
3665
|
+
{ key: "custom", name: "Custom (describe it)", category: "Custom", credits_per_call: 5, tags: ["custom"] }
|
|
3666
|
+
];
|
|
3667
|
+
|
|
3668
|
+
// src/onboarding/detect-from-docs.ts
|
|
3669
|
+
function detectFromDocs(content) {
|
|
3670
|
+
if (!content || content.trim().length === 0) {
|
|
3671
|
+
return [];
|
|
3672
|
+
}
|
|
3673
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3674
|
+
const results = [];
|
|
3675
|
+
for (const entry of API_PATTERNS) {
|
|
3676
|
+
if (entry.pattern.test(content) && !seen.has(entry.capability.key)) {
|
|
3677
|
+
seen.add(entry.capability.key);
|
|
3678
|
+
results.push({ ...entry.capability });
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
return results;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
// src/onboarding/interactive.ts
|
|
3685
|
+
import { createInterface } from "readline";
|
|
3686
|
+
|
|
3687
|
+
// src/onboarding/index.ts
|
|
3688
|
+
var DOC_FILES = ["SOUL.md", "CLAUDE.md", "AGENTS.md", "README.md"];
|
|
3689
|
+
function detectCapabilities(opts = {}) {
|
|
3690
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3691
|
+
if (opts.fromFile) {
|
|
3692
|
+
const filePath = opts.fromFile.startsWith("/") ? opts.fromFile : join5(cwd, opts.fromFile);
|
|
3693
|
+
if (existsSync5(filePath)) {
|
|
3694
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3695
|
+
const capabilities = detectFromDocs(content);
|
|
3696
|
+
if (capabilities.length > 0) {
|
|
3697
|
+
return { source: "docs", capabilities, sourceFile: filePath };
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
return { source: "none", capabilities: [] };
|
|
3701
|
+
}
|
|
3702
|
+
for (const fileName of DOC_FILES) {
|
|
3703
|
+
const filePath = join5(cwd, fileName);
|
|
3704
|
+
if (!existsSync5(filePath)) continue;
|
|
3705
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3706
|
+
if (fileName === "SOUL.md") {
|
|
3707
|
+
return { source: "soul", capabilities: [], soulContent: content, sourceFile: filePath };
|
|
3708
|
+
}
|
|
3709
|
+
const capabilities = detectFromDocs(content);
|
|
3710
|
+
if (capabilities.length > 0) {
|
|
3711
|
+
return { source: "docs", capabilities, sourceFile: filePath };
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
const envKeys = detectApiKeys(KNOWN_API_KEYS);
|
|
3715
|
+
if (envKeys.length > 0) {
|
|
3716
|
+
return { source: "env", capabilities: [], envKeys };
|
|
3717
|
+
}
|
|
3718
|
+
return { source: "none", capabilities: [] };
|
|
3719
|
+
}
|
|
3720
|
+
function capabilitiesToV2Card(capabilities, owner, agentName) {
|
|
3721
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3722
|
+
const skills = capabilities.map((cap) => ({
|
|
3723
|
+
id: cap.key,
|
|
3724
|
+
name: cap.name,
|
|
3725
|
+
description: `${cap.name} capability \u2014 ${cap.category}`,
|
|
3726
|
+
level: 1,
|
|
3727
|
+
category: cap.category.toLowerCase().replace(/\s+/g, "_"),
|
|
3728
|
+
inputs: [{ name: "input", type: "text", required: true }],
|
|
3729
|
+
outputs: [{ name: "output", type: "text", required: true }],
|
|
3730
|
+
pricing: { credits_per_call: cap.credits_per_call },
|
|
3731
|
+
availability: { online: true },
|
|
3732
|
+
metadata: {
|
|
3733
|
+
tags: cap.tags
|
|
3734
|
+
}
|
|
3735
|
+
}));
|
|
3736
|
+
const card = {
|
|
3737
|
+
spec_version: "2.0",
|
|
3738
|
+
id: randomUUID13(),
|
|
3739
|
+
owner,
|
|
3740
|
+
agent_name: agentName ?? owner,
|
|
3741
|
+
skills,
|
|
3742
|
+
availability: { online: true },
|
|
3743
|
+
created_at: now,
|
|
3744
|
+
updated_at: now
|
|
3745
|
+
};
|
|
3746
|
+
return CapabilityCardV2Schema.parse(card);
|
|
3747
|
+
}
|
|
2538
3748
|
export {
|
|
3749
|
+
API_PATTERNS,
|
|
3750
|
+
AgentBnBConsumer,
|
|
3751
|
+
AgentBnBProvider,
|
|
3752
|
+
AgentCertificateSchema,
|
|
3753
|
+
AgentIdentitySchema,
|
|
2539
3754
|
ApiExecutor,
|
|
2540
3755
|
ApiSkillConfigSchema,
|
|
2541
3756
|
BudgetController,
|
|
@@ -2546,11 +3761,17 @@ export {
|
|
|
2546
3761
|
ConductorMode,
|
|
2547
3762
|
ConductorSkillConfigSchema,
|
|
2548
3763
|
EscrowReceiptSchema,
|
|
3764
|
+
GUARANTOR_CREDIT_POOL,
|
|
3765
|
+
GuarantorRecordSchema,
|
|
3766
|
+
INTERACTIVE_TEMPLATES,
|
|
3767
|
+
MAX_AGENTS_PER_GUARANTOR,
|
|
2549
3768
|
ORCHESTRATION_FEE,
|
|
2550
3769
|
OpenClawBridge,
|
|
2551
3770
|
OpenClawSkillConfigSchema,
|
|
2552
3771
|
PipelineExecutor,
|
|
2553
3772
|
PipelineSkillConfigSchema,
|
|
3773
|
+
RelayClient,
|
|
3774
|
+
RelayMessageSchema,
|
|
2554
3775
|
SkillConfigSchema,
|
|
2555
3776
|
SkillExecutor,
|
|
2556
3777
|
SkillsFileSchema,
|
|
@@ -2558,18 +3779,31 @@ export {
|
|
|
2558
3779
|
applyInputMapping,
|
|
2559
3780
|
buildAuthHeaders,
|
|
2560
3781
|
buildConductorCard,
|
|
3782
|
+
capabilitiesToV2Card,
|
|
2561
3783
|
createGatewayServer,
|
|
3784
|
+
createIdentity,
|
|
2562
3785
|
createSignedEscrowReceipt,
|
|
2563
3786
|
createSkillExecutor,
|
|
2564
3787
|
decompose,
|
|
3788
|
+
deriveAgentId,
|
|
3789
|
+
detectCapabilities,
|
|
3790
|
+
detectFromDocs,
|
|
3791
|
+
ensureIdentity,
|
|
3792
|
+
executeCapabilityRequest,
|
|
2565
3793
|
expandEnvVars,
|
|
2566
3794
|
extractByPath,
|
|
2567
3795
|
generateKeyPair,
|
|
3796
|
+
getAgentGuarantor,
|
|
2568
3797
|
getBalance,
|
|
2569
3798
|
getCard,
|
|
3799
|
+
getGuarantor,
|
|
3800
|
+
initiateGithubAuth,
|
|
2570
3801
|
insertCard,
|
|
2571
3802
|
interpolate,
|
|
2572
3803
|
interpolateObject,
|
|
3804
|
+
issueAgentCertificate,
|
|
3805
|
+
linkAgentToGuarantor,
|
|
3806
|
+
loadIdentity,
|
|
2573
3807
|
loadKeyPair,
|
|
2574
3808
|
matchSubTasks,
|
|
2575
3809
|
openCreditDb,
|
|
@@ -2577,12 +3811,17 @@ export {
|
|
|
2577
3811
|
orchestrate,
|
|
2578
3812
|
parseSkillsFile,
|
|
2579
3813
|
registerConductorCard,
|
|
3814
|
+
registerGuarantor,
|
|
3815
|
+
registerWebSocketRelay,
|
|
2580
3816
|
releaseRequesterEscrow,
|
|
3817
|
+
requestViaRelay,
|
|
2581
3818
|
resolvePath,
|
|
3819
|
+
saveIdentity,
|
|
2582
3820
|
saveKeyPair,
|
|
2583
3821
|
searchCards,
|
|
2584
3822
|
settleProviderEarning,
|
|
2585
3823
|
settleRequesterEscrow,
|
|
2586
3824
|
signEscrowReceipt,
|
|
3825
|
+
verifyAgentCertificate,
|
|
2587
3826
|
verifyEscrowReceipt
|
|
2588
3827
|
};
|