agentbnb 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/card-P5C36VBD.js +81 -0
- package/dist/chunk-BEI5MTNZ.js +91 -0
- package/dist/chunk-EZB4RUIG.js +372 -0
- package/dist/chunk-PJSYSVKN.js +491 -0
- package/dist/chunk-TQMI73LL.js +125 -0
- package/dist/chunk-V7M6GIJZ.js +666 -0
- package/dist/cli/index.js +495 -1353
- package/dist/conduct-JZJS2ZHA.js +115 -0
- package/dist/conductor-mode-DJ3RIJ5T.js +111 -0
- package/dist/index.d.ts +2174 -120
- package/dist/index.js +1721 -27
- package/dist/peers-G36URZYB.js +12 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -458,7 +458,7 @@ function updateReputation(db, cardId, success, latencyMs) {
|
|
|
458
458
|
|
|
459
459
|
// src/registry/matcher.ts
|
|
460
460
|
function searchCards(db, query, filters = {}) {
|
|
461
|
-
const words = query.trim().split(/\s+/).map((w) => w.replace(/"/g, "")).filter((w) => w.length > 0);
|
|
461
|
+
const words = query.trim().split(/\s+/).map((w) => w.replace(/["*^{}():]/g, "")).filter((w) => w.length > 0);
|
|
462
462
|
if (words.length === 0) return [];
|
|
463
463
|
const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
|
|
464
464
|
const conditions = [];
|
|
@@ -537,6 +537,24 @@ function getBalance(db, owner) {
|
|
|
537
537
|
const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
|
|
538
538
|
return row?.balance ?? 0;
|
|
539
539
|
}
|
|
540
|
+
function recordEarning(db, owner, amount, _cardId, receiptNonce) {
|
|
541
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
542
|
+
db.transaction(() => {
|
|
543
|
+
const existing = db.prepare(
|
|
544
|
+
"SELECT id FROM credit_transactions WHERE reference_id = ? AND reason = 'remote_earning'"
|
|
545
|
+
).get(receiptNonce);
|
|
546
|
+
if (existing) return;
|
|
547
|
+
db.prepare(
|
|
548
|
+
"INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
|
|
549
|
+
).run(owner, now);
|
|
550
|
+
db.prepare(
|
|
551
|
+
"UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
|
|
552
|
+
).run(amount, now, owner);
|
|
553
|
+
db.prepare(
|
|
554
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
555
|
+
).run(randomUUID(), owner, amount, "remote_earning", receiptNonce, now);
|
|
556
|
+
})();
|
|
557
|
+
}
|
|
540
558
|
|
|
541
559
|
// src/gateway/server.ts
|
|
542
560
|
import Fastify from "fastify";
|
|
@@ -618,6 +636,98 @@ function releaseEscrow(db, escrowId) {
|
|
|
618
636
|
});
|
|
619
637
|
release();
|
|
620
638
|
}
|
|
639
|
+
function confirmEscrowDebit(db, escrowId) {
|
|
640
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
641
|
+
const confirm = db.transaction(() => {
|
|
642
|
+
const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
|
|
643
|
+
if (!escrow) {
|
|
644
|
+
throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
|
|
645
|
+
}
|
|
646
|
+
if (escrow.status !== "held") {
|
|
647
|
+
throw new AgentBnBError(
|
|
648
|
+
`Escrow ${escrowId} is already ${escrow.status}`,
|
|
649
|
+
"ESCROW_ALREADY_SETTLED"
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
db.prepare(
|
|
653
|
+
"UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
|
|
654
|
+
).run("settled", now, escrowId);
|
|
655
|
+
db.prepare(
|
|
656
|
+
"INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
657
|
+
).run(randomUUID2(), escrow.owner, 0, "remote_settlement_confirmed", escrowId, now);
|
|
658
|
+
});
|
|
659
|
+
confirm();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/credit/signing.ts
|
|
663
|
+
import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
|
|
664
|
+
import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
|
|
665
|
+
import { join } from "path";
|
|
666
|
+
function generateKeyPair() {
|
|
667
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
|
|
668
|
+
publicKeyEncoding: { type: "spki", format: "der" },
|
|
669
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
670
|
+
});
|
|
671
|
+
return {
|
|
672
|
+
publicKey: Buffer.from(publicKey),
|
|
673
|
+
privateKey: Buffer.from(privateKey)
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function saveKeyPair(configDir, keys) {
|
|
677
|
+
const privatePath = join(configDir, "private.key");
|
|
678
|
+
const publicPath = join(configDir, "public.key");
|
|
679
|
+
writeFileSync(privatePath, keys.privateKey);
|
|
680
|
+
chmodSync(privatePath, 384);
|
|
681
|
+
writeFileSync(publicPath, keys.publicKey);
|
|
682
|
+
}
|
|
683
|
+
function loadKeyPair(configDir) {
|
|
684
|
+
const privatePath = join(configDir, "private.key");
|
|
685
|
+
const publicPath = join(configDir, "public.key");
|
|
686
|
+
if (!existsSync(privatePath) || !existsSync(publicPath)) {
|
|
687
|
+
throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
publicKey: readFileSync(publicPath),
|
|
691
|
+
privateKey: readFileSync(privatePath)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function canonicalJson(data) {
|
|
695
|
+
return JSON.stringify(data, Object.keys(data).sort());
|
|
696
|
+
}
|
|
697
|
+
function signEscrowReceipt(data, privateKey) {
|
|
698
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
699
|
+
const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
|
|
700
|
+
const signature = sign(null, message, keyObject);
|
|
701
|
+
return signature.toString("base64url");
|
|
702
|
+
}
|
|
703
|
+
function verifyEscrowReceipt(data, signature, publicKey) {
|
|
704
|
+
try {
|
|
705
|
+
const message = Buffer.from(canonicalJson(data), "utf-8");
|
|
706
|
+
const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
|
|
707
|
+
const sigBuffer = Buffer.from(signature, "base64url");
|
|
708
|
+
return verify(null, message, keyObject, sigBuffer);
|
|
709
|
+
} catch {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/credit/settlement.ts
|
|
715
|
+
function settleProviderEarning(providerDb, providerOwner, receipt) {
|
|
716
|
+
recordEarning(
|
|
717
|
+
providerDb,
|
|
718
|
+
providerOwner,
|
|
719
|
+
receipt.amount,
|
|
720
|
+
receipt.card_id,
|
|
721
|
+
receipt.nonce
|
|
722
|
+
);
|
|
723
|
+
return { settled: true };
|
|
724
|
+
}
|
|
725
|
+
function settleRequesterEscrow(requesterDb, escrowId) {
|
|
726
|
+
confirmEscrowDebit(requesterDb, escrowId);
|
|
727
|
+
}
|
|
728
|
+
function releaseRequesterEscrow(requesterDb, escrowId) {
|
|
729
|
+
releaseEscrow(requesterDb, escrowId);
|
|
730
|
+
}
|
|
621
731
|
|
|
622
732
|
// src/gateway/server.ts
|
|
623
733
|
var VERSION = "0.0.1";
|
|
@@ -713,24 +823,55 @@ function createGatewayServer(opts) {
|
|
|
713
823
|
creditsNeeded = card.pricing.credits_per_call;
|
|
714
824
|
cardName = card.name;
|
|
715
825
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
826
|
+
const receipt = params.escrow_receipt;
|
|
827
|
+
let escrowId = null;
|
|
828
|
+
let isRemoteEscrow = false;
|
|
829
|
+
if (receipt) {
|
|
830
|
+
const { signature, ...receiptData } = receipt;
|
|
831
|
+
const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
|
|
832
|
+
const valid = verifyEscrowReceipt(receiptData, signature, publicKeyBuf);
|
|
833
|
+
if (!valid) {
|
|
720
834
|
return reply.send({
|
|
721
835
|
jsonrpc: "2.0",
|
|
722
836
|
id,
|
|
723
|
-
error: { code: -32603, message: "
|
|
837
|
+
error: { code: -32603, message: "Invalid escrow receipt signature" }
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
if (receipt.amount < creditsNeeded) {
|
|
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;
|
|
856
|
+
} else {
|
|
857
|
+
try {
|
|
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 }
|
|
724
873
|
});
|
|
725
874
|
}
|
|
726
|
-
escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
|
|
727
|
-
} catch (err) {
|
|
728
|
-
const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
|
|
729
|
-
return reply.send({
|
|
730
|
-
jsonrpc: "2.0",
|
|
731
|
-
id,
|
|
732
|
-
error: { code: -32603, message: msg }
|
|
733
|
-
});
|
|
734
875
|
}
|
|
735
876
|
const startMs = Date.now();
|
|
736
877
|
if (skillExecutor) {
|
|
@@ -739,7 +880,7 @@ function createGatewayServer(opts) {
|
|
|
739
880
|
try {
|
|
740
881
|
execResult = await skillExecutor.execute(targetSkillId, params);
|
|
741
882
|
} catch (err) {
|
|
742
|
-
releaseEscrow(creditDb, escrowId);
|
|
883
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
743
884
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
744
885
|
try {
|
|
745
886
|
insertRequestLog(registryDb, {
|
|
@@ -759,11 +900,15 @@ function createGatewayServer(opts) {
|
|
|
759
900
|
return reply.send({
|
|
760
901
|
jsonrpc: "2.0",
|
|
761
902
|
id,
|
|
762
|
-
error: {
|
|
903
|
+
error: {
|
|
904
|
+
code: -32603,
|
|
905
|
+
message,
|
|
906
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
907
|
+
}
|
|
763
908
|
});
|
|
764
909
|
}
|
|
765
910
|
if (!execResult.success) {
|
|
766
|
-
releaseEscrow(creditDb, escrowId);
|
|
911
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
767
912
|
updateReputation(registryDb, cardId, false, execResult.latency_ms);
|
|
768
913
|
try {
|
|
769
914
|
insertRequestLog(registryDb, {
|
|
@@ -782,10 +927,18 @@ function createGatewayServer(opts) {
|
|
|
782
927
|
return reply.send({
|
|
783
928
|
jsonrpc: "2.0",
|
|
784
929
|
id,
|
|
785
|
-
error: {
|
|
930
|
+
error: {
|
|
931
|
+
code: -32603,
|
|
932
|
+
message: execResult.error ?? "Execution failed",
|
|
933
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
934
|
+
}
|
|
786
935
|
});
|
|
787
936
|
}
|
|
788
|
-
|
|
937
|
+
if (isRemoteEscrow && receipt) {
|
|
938
|
+
settleProviderEarning(creditDb, card.owner, receipt);
|
|
939
|
+
} else if (escrowId) {
|
|
940
|
+
settleEscrow(creditDb, escrowId, card.owner);
|
|
941
|
+
}
|
|
789
942
|
updateReputation(registryDb, cardId, true, execResult.latency_ms);
|
|
790
943
|
try {
|
|
791
944
|
insertRequestLog(registryDb, {
|
|
@@ -801,7 +954,8 @@ function createGatewayServer(opts) {
|
|
|
801
954
|
});
|
|
802
955
|
} catch {
|
|
803
956
|
}
|
|
804
|
-
|
|
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 });
|
|
805
959
|
}
|
|
806
960
|
const controller = new AbortController();
|
|
807
961
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -814,7 +968,7 @@ function createGatewayServer(opts) {
|
|
|
814
968
|
});
|
|
815
969
|
clearTimeout(timer);
|
|
816
970
|
if (!response.ok) {
|
|
817
|
-
releaseEscrow(creditDb, escrowId);
|
|
971
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
818
972
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
819
973
|
try {
|
|
820
974
|
insertRequestLog(registryDb, {
|
|
@@ -833,11 +987,19 @@ function createGatewayServer(opts) {
|
|
|
833
987
|
return reply.send({
|
|
834
988
|
jsonrpc: "2.0",
|
|
835
989
|
id,
|
|
836
|
-
error: {
|
|
990
|
+
error: {
|
|
991
|
+
code: -32603,
|
|
992
|
+
message: `Handler returned ${response.status}`,
|
|
993
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
994
|
+
}
|
|
837
995
|
});
|
|
838
996
|
}
|
|
839
997
|
const result = await response.json();
|
|
840
|
-
|
|
998
|
+
if (isRemoteEscrow && receipt) {
|
|
999
|
+
settleProviderEarning(creditDb, card.owner, receipt);
|
|
1000
|
+
} else if (escrowId) {
|
|
1001
|
+
settleEscrow(creditDb, escrowId, card.owner);
|
|
1002
|
+
}
|
|
841
1003
|
updateReputation(registryDb, cardId, true, Date.now() - startMs);
|
|
842
1004
|
try {
|
|
843
1005
|
insertRequestLog(registryDb, {
|
|
@@ -853,10 +1015,11 @@ function createGatewayServer(opts) {
|
|
|
853
1015
|
});
|
|
854
1016
|
} catch {
|
|
855
1017
|
}
|
|
856
|
-
|
|
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 });
|
|
857
1020
|
} catch (err) {
|
|
858
1021
|
clearTimeout(timer);
|
|
859
|
-
releaseEscrow(creditDb, escrowId);
|
|
1022
|
+
if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
|
|
860
1023
|
updateReputation(registryDb, cardId, false, Date.now() - startMs);
|
|
861
1024
|
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
862
1025
|
try {
|
|
@@ -876,19 +1039,1550 @@ function createGatewayServer(opts) {
|
|
|
876
1039
|
return reply.send({
|
|
877
1040
|
jsonrpc: "2.0",
|
|
878
1041
|
id,
|
|
879
|
-
error: {
|
|
1042
|
+
error: {
|
|
1043
|
+
code: -32603,
|
|
1044
|
+
message: isTimeout ? "Execution timeout" : "Handler error",
|
|
1045
|
+
...isRemoteEscrow ? { data: { receipt_released: true } } : {}
|
|
1046
|
+
}
|
|
880
1047
|
});
|
|
881
1048
|
}
|
|
882
1049
|
});
|
|
883
1050
|
return fastify;
|
|
884
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
// src/skills/executor.ts
|
|
1054
|
+
var SkillExecutor = class {
|
|
1055
|
+
skillMap;
|
|
1056
|
+
modeMap;
|
|
1057
|
+
/**
|
|
1058
|
+
* @param configs - Parsed SkillConfig array (from parseSkillsFile).
|
|
1059
|
+
* @param modes - Map from skill type string to its executor implementation.
|
|
1060
|
+
*/
|
|
1061
|
+
constructor(configs, modes) {
|
|
1062
|
+
this.skillMap = new Map(configs.map((c) => [c.id, c]));
|
|
1063
|
+
this.modeMap = modes;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Execute a skill by ID with the given input parameters.
|
|
1067
|
+
*
|
|
1068
|
+
* Dispatch order:
|
|
1069
|
+
* 1. Look up skill config by skillId.
|
|
1070
|
+
* 2. Find executor mode by config.type.
|
|
1071
|
+
* 3. Invoke mode.execute(), wrap with latency timing.
|
|
1072
|
+
* 4. Catch any thrown errors and return as ExecutionResult with success:false.
|
|
1073
|
+
*
|
|
1074
|
+
* @param skillId - The ID of the skill to execute.
|
|
1075
|
+
* @param params - Input parameters for the skill.
|
|
1076
|
+
* @returns ExecutionResult including success, result/error, and latency_ms.
|
|
1077
|
+
*/
|
|
1078
|
+
async execute(skillId, params) {
|
|
1079
|
+
const startTime = Date.now();
|
|
1080
|
+
const config = this.skillMap.get(skillId);
|
|
1081
|
+
if (!config) {
|
|
1082
|
+
return {
|
|
1083
|
+
success: false,
|
|
1084
|
+
error: `Skill not found: "${skillId}"`,
|
|
1085
|
+
latency_ms: Date.now() - startTime
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
const mode = this.modeMap.get(config.type);
|
|
1089
|
+
if (!mode) {
|
|
1090
|
+
return {
|
|
1091
|
+
success: false,
|
|
1092
|
+
error: `No executor registered for skill type "${config.type}" (skill: "${skillId}")`,
|
|
1093
|
+
latency_ms: Date.now() - startTime
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
try {
|
|
1097
|
+
const modeResult = await mode.execute(config, params);
|
|
1098
|
+
return {
|
|
1099
|
+
...modeResult,
|
|
1100
|
+
latency_ms: Date.now() - startTime
|
|
1101
|
+
};
|
|
1102
|
+
} catch (err) {
|
|
1103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1104
|
+
return {
|
|
1105
|
+
success: false,
|
|
1106
|
+
error: message,
|
|
1107
|
+
latency_ms: Date.now() - startTime
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Returns the IDs of all registered skills.
|
|
1113
|
+
*
|
|
1114
|
+
* @returns Array of skill ID strings.
|
|
1115
|
+
*/
|
|
1116
|
+
listSkills() {
|
|
1117
|
+
return Array.from(this.skillMap.keys());
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Returns the SkillConfig for a given skill ID, or undefined if not found.
|
|
1121
|
+
*
|
|
1122
|
+
* @param skillId - The skill ID to look up.
|
|
1123
|
+
* @returns The SkillConfig or undefined.
|
|
1124
|
+
*/
|
|
1125
|
+
getSkillConfig(skillId) {
|
|
1126
|
+
return this.skillMap.get(skillId);
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
function createSkillExecutor(configs, modes) {
|
|
1130
|
+
return new SkillExecutor(configs, modes);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// src/skills/skill-config.ts
|
|
1134
|
+
import { z as z2 } from "zod";
|
|
1135
|
+
import yaml from "js-yaml";
|
|
1136
|
+
var PricingSchema = z2.object({
|
|
1137
|
+
credits_per_call: z2.number().nonnegative(),
|
|
1138
|
+
credits_per_minute: z2.number().nonnegative().optional(),
|
|
1139
|
+
free_tier: z2.number().nonnegative().optional()
|
|
1140
|
+
});
|
|
1141
|
+
var ApiAuthSchema = z2.discriminatedUnion("type", [
|
|
1142
|
+
z2.object({
|
|
1143
|
+
type: z2.literal("bearer"),
|
|
1144
|
+
token: z2.string()
|
|
1145
|
+
}),
|
|
1146
|
+
z2.object({
|
|
1147
|
+
type: z2.literal("apikey"),
|
|
1148
|
+
header: z2.string().default("X-API-Key"),
|
|
1149
|
+
key: z2.string()
|
|
1150
|
+
}),
|
|
1151
|
+
z2.object({
|
|
1152
|
+
type: z2.literal("basic"),
|
|
1153
|
+
username: z2.string(),
|
|
1154
|
+
password: z2.string()
|
|
1155
|
+
})
|
|
1156
|
+
]);
|
|
1157
|
+
var ApiSkillConfigSchema = z2.object({
|
|
1158
|
+
id: z2.string().min(1),
|
|
1159
|
+
type: z2.literal("api"),
|
|
1160
|
+
name: z2.string().min(1),
|
|
1161
|
+
endpoint: z2.string().min(1),
|
|
1162
|
+
method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
|
|
1163
|
+
auth: ApiAuthSchema.optional(),
|
|
1164
|
+
input_mapping: z2.record(z2.string()).default({}),
|
|
1165
|
+
output_mapping: z2.record(z2.string()).default({}),
|
|
1166
|
+
pricing: PricingSchema,
|
|
1167
|
+
timeout_ms: z2.number().positive().default(3e4),
|
|
1168
|
+
retries: z2.number().nonnegative().int().default(0),
|
|
1169
|
+
provider: z2.string().optional()
|
|
1170
|
+
});
|
|
1171
|
+
var PipelineStepSchema = z2.union([
|
|
1172
|
+
z2.object({
|
|
1173
|
+
skill_id: z2.string().min(1),
|
|
1174
|
+
input_mapping: z2.record(z2.string()).default({})
|
|
1175
|
+
}),
|
|
1176
|
+
z2.object({
|
|
1177
|
+
command: z2.string().min(1),
|
|
1178
|
+
input_mapping: z2.record(z2.string()).default({})
|
|
1179
|
+
})
|
|
1180
|
+
]);
|
|
1181
|
+
var PipelineSkillConfigSchema = z2.object({
|
|
1182
|
+
id: z2.string().min(1),
|
|
1183
|
+
type: z2.literal("pipeline"),
|
|
1184
|
+
name: z2.string().min(1),
|
|
1185
|
+
steps: z2.array(PipelineStepSchema).min(1),
|
|
1186
|
+
pricing: PricingSchema,
|
|
1187
|
+
timeout_ms: z2.number().positive().optional()
|
|
1188
|
+
});
|
|
1189
|
+
var OpenClawSkillConfigSchema = z2.object({
|
|
1190
|
+
id: z2.string().min(1),
|
|
1191
|
+
type: z2.literal("openclaw"),
|
|
1192
|
+
name: z2.string().min(1),
|
|
1193
|
+
agent_name: z2.string().min(1),
|
|
1194
|
+
channel: z2.enum(["telegram", "webhook", "process"]),
|
|
1195
|
+
pricing: PricingSchema,
|
|
1196
|
+
timeout_ms: z2.number().positive().optional()
|
|
1197
|
+
});
|
|
1198
|
+
var CommandSkillConfigSchema = z2.object({
|
|
1199
|
+
id: z2.string().min(1),
|
|
1200
|
+
type: z2.literal("command"),
|
|
1201
|
+
name: z2.string().min(1),
|
|
1202
|
+
command: z2.string().min(1),
|
|
1203
|
+
output_type: z2.enum(["json", "text", "file"]),
|
|
1204
|
+
allowed_commands: z2.array(z2.string()).optional(),
|
|
1205
|
+
working_dir: z2.string().optional(),
|
|
1206
|
+
timeout_ms: z2.number().positive().default(3e4),
|
|
1207
|
+
pricing: PricingSchema
|
|
1208
|
+
});
|
|
1209
|
+
var ConductorSkillConfigSchema = z2.object({
|
|
1210
|
+
id: z2.string().min(1),
|
|
1211
|
+
type: z2.literal("conductor"),
|
|
1212
|
+
name: z2.string().min(1),
|
|
1213
|
+
conductor_skill: z2.enum(["orchestrate", "plan"]),
|
|
1214
|
+
pricing: PricingSchema,
|
|
1215
|
+
timeout_ms: z2.number().positive().optional()
|
|
1216
|
+
});
|
|
1217
|
+
var SkillConfigSchema = z2.discriminatedUnion("type", [
|
|
1218
|
+
ApiSkillConfigSchema,
|
|
1219
|
+
PipelineSkillConfigSchema,
|
|
1220
|
+
OpenClawSkillConfigSchema,
|
|
1221
|
+
CommandSkillConfigSchema,
|
|
1222
|
+
ConductorSkillConfigSchema
|
|
1223
|
+
]);
|
|
1224
|
+
var SkillsFileSchema = z2.object({
|
|
1225
|
+
skills: z2.array(SkillConfigSchema)
|
|
1226
|
+
});
|
|
1227
|
+
function expandEnvVars(value) {
|
|
1228
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
|
|
1229
|
+
if (/[.a-z]/.test(varName)) {
|
|
1230
|
+
return _match;
|
|
1231
|
+
}
|
|
1232
|
+
const envValue = process.env[varName];
|
|
1233
|
+
if (envValue === void 0) {
|
|
1234
|
+
throw new Error(`Environment variable "${varName}" is not defined`);
|
|
1235
|
+
}
|
|
1236
|
+
return envValue;
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
function expandEnvVarsDeep(value) {
|
|
1240
|
+
if (typeof value === "string") {
|
|
1241
|
+
return expandEnvVars(value);
|
|
1242
|
+
}
|
|
1243
|
+
if (Array.isArray(value)) {
|
|
1244
|
+
return value.map(expandEnvVarsDeep);
|
|
1245
|
+
}
|
|
1246
|
+
if (value !== null && typeof value === "object") {
|
|
1247
|
+
const result = {};
|
|
1248
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1249
|
+
result[k] = expandEnvVarsDeep(v);
|
|
1250
|
+
}
|
|
1251
|
+
return result;
|
|
1252
|
+
}
|
|
1253
|
+
return value;
|
|
1254
|
+
}
|
|
1255
|
+
function parseSkillsFile(yamlContent) {
|
|
1256
|
+
const raw = yaml.load(yamlContent);
|
|
1257
|
+
const expanded = expandEnvVarsDeep(raw);
|
|
1258
|
+
const result = SkillsFileSchema.parse(expanded);
|
|
1259
|
+
return result.skills;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/skills/api-executor.ts
|
|
1263
|
+
function parseMappingTarget(mapping) {
|
|
1264
|
+
const dotIndex = mapping.indexOf(".");
|
|
1265
|
+
if (dotIndex < 0) {
|
|
1266
|
+
throw new Error(`Invalid input_mapping format: "${mapping}" (expected "target.key")`);
|
|
1267
|
+
}
|
|
1268
|
+
const target = mapping.slice(0, dotIndex);
|
|
1269
|
+
const key = mapping.slice(dotIndex + 1);
|
|
1270
|
+
if (!["body", "query", "path", "header"].includes(target)) {
|
|
1271
|
+
throw new Error(
|
|
1272
|
+
`Invalid mapping target "${target}" in "${mapping}" (must be body|query|path|header)`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
return { target, key };
|
|
1276
|
+
}
|
|
1277
|
+
function extractByPath(obj, dotPath) {
|
|
1278
|
+
const normalizedPath = dotPath.startsWith("response.") ? dotPath.slice("response.".length) : dotPath;
|
|
1279
|
+
const parts = normalizedPath.split(".");
|
|
1280
|
+
let current = obj;
|
|
1281
|
+
for (const part of parts) {
|
|
1282
|
+
if (current === null || typeof current !== "object") {
|
|
1283
|
+
return void 0;
|
|
1284
|
+
}
|
|
1285
|
+
current = current[part];
|
|
1286
|
+
}
|
|
1287
|
+
return current;
|
|
1288
|
+
}
|
|
1289
|
+
function buildAuthHeaders(auth) {
|
|
1290
|
+
if (!auth) return {};
|
|
1291
|
+
switch (auth.type) {
|
|
1292
|
+
case "bearer":
|
|
1293
|
+
return { Authorization: `Bearer ${auth.token}` };
|
|
1294
|
+
case "apikey":
|
|
1295
|
+
return { [auth.header]: auth.key };
|
|
1296
|
+
case "basic": {
|
|
1297
|
+
const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
1298
|
+
return { Authorization: `Basic ${encoded}` };
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
function applyInputMapping(params, mapping) {
|
|
1303
|
+
const body = {};
|
|
1304
|
+
const query = {};
|
|
1305
|
+
const pathParams = {};
|
|
1306
|
+
const headers = {};
|
|
1307
|
+
for (const [paramName, mappingValue] of Object.entries(mapping)) {
|
|
1308
|
+
const value = params[paramName];
|
|
1309
|
+
if (value === void 0) continue;
|
|
1310
|
+
const { target, key } = parseMappingTarget(mappingValue);
|
|
1311
|
+
switch (target) {
|
|
1312
|
+
case "body":
|
|
1313
|
+
body[key] = value;
|
|
1314
|
+
break;
|
|
1315
|
+
case "query":
|
|
1316
|
+
query[key] = String(value);
|
|
1317
|
+
break;
|
|
1318
|
+
case "path":
|
|
1319
|
+
pathParams[key] = String(value);
|
|
1320
|
+
break;
|
|
1321
|
+
case "header":
|
|
1322
|
+
headers[key] = String(value).replace(/[\r\n]/g, "");
|
|
1323
|
+
break;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return { body, query, pathParams, headers };
|
|
1327
|
+
}
|
|
1328
|
+
function sleep(ms) {
|
|
1329
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1330
|
+
}
|
|
1331
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 503]);
|
|
1332
|
+
var ApiExecutor = class {
|
|
1333
|
+
/**
|
|
1334
|
+
* Execute an API call described by the given skill config.
|
|
1335
|
+
*
|
|
1336
|
+
* @param config - The validated SkillConfig (must be ApiSkillConfig).
|
|
1337
|
+
* @param params - Input parameters to map to the HTTP request.
|
|
1338
|
+
* @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor).
|
|
1339
|
+
*/
|
|
1340
|
+
async execute(config, params) {
|
|
1341
|
+
const apiConfig = config;
|
|
1342
|
+
const { body, query, pathParams, headers: mappedHeaders } = applyInputMapping(
|
|
1343
|
+
params,
|
|
1344
|
+
apiConfig.input_mapping
|
|
1345
|
+
);
|
|
1346
|
+
let url = apiConfig.endpoint;
|
|
1347
|
+
for (const [key, value] of Object.entries(pathParams)) {
|
|
1348
|
+
url = url.replace(`{${key}}`, encodeURIComponent(value));
|
|
1349
|
+
}
|
|
1350
|
+
if (Object.keys(query).length > 0) {
|
|
1351
|
+
const qs = new URLSearchParams(query).toString();
|
|
1352
|
+
url = `${url}?${qs}`;
|
|
1353
|
+
}
|
|
1354
|
+
const authHeaders = buildAuthHeaders(apiConfig.auth);
|
|
1355
|
+
const requestHeaders = {
|
|
1356
|
+
...authHeaders,
|
|
1357
|
+
...mappedHeaders
|
|
1358
|
+
};
|
|
1359
|
+
const hasBody = ["POST", "PUT"].includes(apiConfig.method);
|
|
1360
|
+
if (hasBody) {
|
|
1361
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
1362
|
+
}
|
|
1363
|
+
const maxAttempts = (apiConfig.retries ?? 0) + 1;
|
|
1364
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1365
|
+
if (attempt > 0) {
|
|
1366
|
+
await sleep(100 * Math.pow(2, attempt - 1));
|
|
1367
|
+
}
|
|
1368
|
+
const controller = new AbortController();
|
|
1369
|
+
const timeoutId = setTimeout(
|
|
1370
|
+
() => controller.abort(),
|
|
1371
|
+
apiConfig.timeout_ms ?? 3e4
|
|
1372
|
+
);
|
|
1373
|
+
let response;
|
|
1374
|
+
try {
|
|
1375
|
+
response = await fetch(url, {
|
|
1376
|
+
method: apiConfig.method,
|
|
1377
|
+
headers: requestHeaders,
|
|
1378
|
+
body: hasBody ? JSON.stringify(body) : void 0,
|
|
1379
|
+
signal: controller.signal
|
|
1380
|
+
});
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
clearTimeout(timeoutId);
|
|
1383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1384
|
+
const isAbort = err instanceof Error && err.name === "AbortError" || message.toLowerCase().includes("abort");
|
|
1385
|
+
if (isAbort) {
|
|
1386
|
+
return { success: false, error: `Request timeout after ${apiConfig.timeout_ms}ms` };
|
|
1387
|
+
}
|
|
1388
|
+
return { success: false, error: message };
|
|
1389
|
+
} finally {
|
|
1390
|
+
clearTimeout(timeoutId);
|
|
1391
|
+
}
|
|
1392
|
+
if (!response.ok && RETRYABLE_STATUSES.has(response.status) && attempt < maxAttempts - 1) {
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
if (!response.ok) {
|
|
1396
|
+
return {
|
|
1397
|
+
success: false,
|
|
1398
|
+
error: `HTTP ${response.status} from ${apiConfig.endpoint}`
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
const responseBody = await response.json();
|
|
1402
|
+
const outputMapping = apiConfig.output_mapping;
|
|
1403
|
+
if (Object.keys(outputMapping).length === 0) {
|
|
1404
|
+
return { success: true, result: responseBody };
|
|
1405
|
+
}
|
|
1406
|
+
const mappedOutput = {};
|
|
1407
|
+
for (const [outputKey, path] of Object.entries(outputMapping)) {
|
|
1408
|
+
mappedOutput[outputKey] = extractByPath(responseBody, path);
|
|
1409
|
+
}
|
|
1410
|
+
return { success: true, result: mappedOutput };
|
|
1411
|
+
}
|
|
1412
|
+
return { success: false, error: "Unexpected: retry loop exhausted" };
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
// src/skills/pipeline-executor.ts
|
|
1417
|
+
import { execFile } from "child_process";
|
|
1418
|
+
import { promisify } from "util";
|
|
1419
|
+
|
|
1420
|
+
// src/utils/interpolation.ts
|
|
1421
|
+
function resolvePath(obj, path) {
|
|
1422
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((s) => s.length > 0);
|
|
1423
|
+
let current = obj;
|
|
1424
|
+
for (const segment of segments) {
|
|
1425
|
+
if (current === null || current === void 0) {
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
if (typeof current !== "object") {
|
|
1429
|
+
return void 0;
|
|
1430
|
+
}
|
|
1431
|
+
current = current[segment];
|
|
1432
|
+
}
|
|
1433
|
+
return current;
|
|
1434
|
+
}
|
|
1435
|
+
function interpolate(template, context) {
|
|
1436
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
|
|
1437
|
+
const resolved = resolvePath(context, expression.trim());
|
|
1438
|
+
if (resolved === void 0 || resolved === null) {
|
|
1439
|
+
return "";
|
|
1440
|
+
}
|
|
1441
|
+
if (typeof resolved === "object") {
|
|
1442
|
+
return JSON.stringify(resolved);
|
|
1443
|
+
}
|
|
1444
|
+
return String(resolved);
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function interpolateObject(obj, context) {
|
|
1448
|
+
const result = {};
|
|
1449
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1450
|
+
result[key] = interpolateValue(value, context);
|
|
1451
|
+
}
|
|
1452
|
+
return result;
|
|
1453
|
+
}
|
|
1454
|
+
function interpolateValue(value, context) {
|
|
1455
|
+
if (typeof value === "string") {
|
|
1456
|
+
return interpolate(value, context);
|
|
1457
|
+
}
|
|
1458
|
+
if (Array.isArray(value)) {
|
|
1459
|
+
return value.map((item) => interpolateValue(item, context));
|
|
1460
|
+
}
|
|
1461
|
+
if (value !== null && typeof value === "object") {
|
|
1462
|
+
return interpolateObject(value, context);
|
|
1463
|
+
}
|
|
1464
|
+
return value;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/skills/pipeline-executor.ts
|
|
1468
|
+
var execFileAsync = promisify(execFile);
|
|
1469
|
+
function shellEscape(value) {
|
|
1470
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
1471
|
+
}
|
|
1472
|
+
function safeInterpolateCommand(template, context) {
|
|
1473
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
1474
|
+
const parts = expr.split(".");
|
|
1475
|
+
let current = context;
|
|
1476
|
+
for (const part of parts) {
|
|
1477
|
+
if (current === null || typeof current !== "object") return "";
|
|
1478
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
1479
|
+
if (bracketMatch) {
|
|
1480
|
+
current = current[bracketMatch[1]];
|
|
1481
|
+
if (Array.isArray(current)) {
|
|
1482
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
1483
|
+
} else {
|
|
1484
|
+
return "";
|
|
1485
|
+
}
|
|
1486
|
+
} else {
|
|
1487
|
+
current = current[part];
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (current === void 0 || current === null) return "";
|
|
1491
|
+
return shellEscape(String(current));
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
var PipelineExecutor = class {
|
|
1495
|
+
/**
|
|
1496
|
+
* @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
|
|
1497
|
+
*/
|
|
1498
|
+
constructor(skillExecutor) {
|
|
1499
|
+
this.skillExecutor = skillExecutor;
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Execute a pipeline skill config sequentially.
|
|
1503
|
+
*
|
|
1504
|
+
* Algorithm:
|
|
1505
|
+
* 1. Initialise context: { params, steps: [], prev: { result: null } }
|
|
1506
|
+
* 2. For each step:
|
|
1507
|
+
* a. Resolve input_mapping keys against current context via interpolateObject.
|
|
1508
|
+
* b. If step has `skill_id`: dispatch via skillExecutor.execute(). On failure → stop.
|
|
1509
|
+
* c. If step has `command`: interpolate command string, run via exec(). On non-zero exit → stop.
|
|
1510
|
+
* d. Store step result in context.steps[i] and context.prev.
|
|
1511
|
+
* 3. Return success with final step result (or null for empty pipeline).
|
|
1512
|
+
*
|
|
1513
|
+
* @param config - The PipelineSkillConfig for this skill.
|
|
1514
|
+
* @param params - Input parameters from the caller.
|
|
1515
|
+
* @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
|
|
1516
|
+
*/
|
|
1517
|
+
async execute(config, params) {
|
|
1518
|
+
const pipelineConfig = config;
|
|
1519
|
+
const steps = pipelineConfig.steps ?? [];
|
|
1520
|
+
if (steps.length === 0) {
|
|
1521
|
+
return { success: true, result: null };
|
|
1522
|
+
}
|
|
1523
|
+
const context = {
|
|
1524
|
+
params,
|
|
1525
|
+
steps: [],
|
|
1526
|
+
prev: { result: null }
|
|
1527
|
+
};
|
|
1528
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1529
|
+
const step = steps[i];
|
|
1530
|
+
if (step === void 0) {
|
|
1531
|
+
return {
|
|
1532
|
+
success: false,
|
|
1533
|
+
error: `Step ${i} failed: step definition is undefined`
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
const resolvedInputs = interpolateObject(
|
|
1537
|
+
step.input_mapping,
|
|
1538
|
+
context
|
|
1539
|
+
);
|
|
1540
|
+
let stepResult;
|
|
1541
|
+
if ("skill_id" in step && step.skill_id) {
|
|
1542
|
+
const subResult = await this.skillExecutor.execute(
|
|
1543
|
+
step.skill_id,
|
|
1544
|
+
resolvedInputs
|
|
1545
|
+
);
|
|
1546
|
+
if (!subResult.success) {
|
|
1547
|
+
return {
|
|
1548
|
+
success: false,
|
|
1549
|
+
error: `Step ${i} failed: ${subResult.error ?? "unknown error"}`
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
stepResult = subResult.result;
|
|
1553
|
+
} else if ("command" in step && step.command) {
|
|
1554
|
+
const interpolatedCommand = safeInterpolateCommand(
|
|
1555
|
+
step.command,
|
|
1556
|
+
context
|
|
1557
|
+
);
|
|
1558
|
+
try {
|
|
1559
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
|
|
1560
|
+
stepResult = stdout.trim();
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1563
|
+
return {
|
|
1564
|
+
success: false,
|
|
1565
|
+
error: `Step ${i} failed: ${message}`
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
} else {
|
|
1569
|
+
return {
|
|
1570
|
+
success: false,
|
|
1571
|
+
error: `Step ${i} failed: step must have either "skill_id" or "command"`
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
context.steps.push({ result: stepResult });
|
|
1575
|
+
context.prev = { result: stepResult };
|
|
1576
|
+
}
|
|
1577
|
+
const lastStep = context.steps[context.steps.length - 1];
|
|
1578
|
+
return {
|
|
1579
|
+
success: true,
|
|
1580
|
+
result: lastStep !== void 0 ? lastStep.result : null
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
// src/skills/openclaw-bridge.ts
|
|
1586
|
+
import { execFileSync } from "child_process";
|
|
1587
|
+
var DEFAULT_BASE_URL = "http://localhost:3000";
|
|
1588
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
1589
|
+
function buildPayload(config, params) {
|
|
1590
|
+
return {
|
|
1591
|
+
task: config.name,
|
|
1592
|
+
params,
|
|
1593
|
+
source: "agentbnb",
|
|
1594
|
+
skill_id: config.id
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
async function executeWebhook(config, payload) {
|
|
1598
|
+
const baseUrl = process.env["OPENCLAW_BASE_URL"] ?? DEFAULT_BASE_URL;
|
|
1599
|
+
const url = `${baseUrl}/openclaw/${config.agent_name}/task`;
|
|
1600
|
+
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
1601
|
+
const controller = new AbortController();
|
|
1602
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1603
|
+
try {
|
|
1604
|
+
const response = await fetch(url, {
|
|
1605
|
+
method: "POST",
|
|
1606
|
+
headers: { "Content-Type": "application/json" },
|
|
1607
|
+
body: JSON.stringify(payload),
|
|
1608
|
+
signal: controller.signal
|
|
1609
|
+
});
|
|
1610
|
+
if (!response.ok) {
|
|
1611
|
+
return {
|
|
1612
|
+
success: false,
|
|
1613
|
+
error: `Webhook returned HTTP ${response.status}: ${response.statusText}`
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
const result = await response.json();
|
|
1617
|
+
return { success: true, result };
|
|
1618
|
+
} catch (err) {
|
|
1619
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1620
|
+
return {
|
|
1621
|
+
success: false,
|
|
1622
|
+
error: `OpenClaw webhook timed out after ${timeoutMs}ms`
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1626
|
+
return { success: false, error: message };
|
|
1627
|
+
} finally {
|
|
1628
|
+
clearTimeout(timer);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function validateAgentName(name) {
|
|
1632
|
+
return /^[a-zA-Z0-9._-]+$/.test(name);
|
|
1633
|
+
}
|
|
1634
|
+
function executeProcess(config, payload) {
|
|
1635
|
+
const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
1636
|
+
if (!validateAgentName(config.agent_name)) {
|
|
1637
|
+
return {
|
|
1638
|
+
success: false,
|
|
1639
|
+
error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
const inputJson = JSON.stringify(payload);
|
|
1643
|
+
try {
|
|
1644
|
+
const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
|
|
1645
|
+
timeout: timeoutMs
|
|
1646
|
+
});
|
|
1647
|
+
const text = stdout.toString().trim();
|
|
1648
|
+
const result = JSON.parse(text);
|
|
1649
|
+
return { success: true, result };
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1652
|
+
return { success: false, error: message };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
async function executeTelegram(config, payload) {
|
|
1656
|
+
const token = process.env["TELEGRAM_BOT_TOKEN"];
|
|
1657
|
+
if (!token) {
|
|
1658
|
+
return {
|
|
1659
|
+
success: false,
|
|
1660
|
+
error: "TELEGRAM_BOT_TOKEN environment variable is not set"
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
const chatId = process.env["TELEGRAM_CHAT_ID"];
|
|
1664
|
+
if (!chatId) {
|
|
1665
|
+
return {
|
|
1666
|
+
success: false,
|
|
1667
|
+
error: "TELEGRAM_CHAT_ID environment variable is not set"
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
const text = `[AgentBnB] Skill: ${config.name} (${config.id})
|
|
1671
|
+
Agent: ${config.agent_name}
|
|
1672
|
+
Params: ${JSON.stringify(payload.params ?? {})}`;
|
|
1673
|
+
const url = `https://api.telegram.org/bot${token}/sendMessage`;
|
|
1674
|
+
try {
|
|
1675
|
+
await fetch(url, {
|
|
1676
|
+
method: "POST",
|
|
1677
|
+
headers: { "Content-Type": "application/json" },
|
|
1678
|
+
body: JSON.stringify({ chat_id: chatId, text })
|
|
1679
|
+
});
|
|
1680
|
+
return {
|
|
1681
|
+
success: true,
|
|
1682
|
+
result: { sent: true, channel: "telegram" }
|
|
1683
|
+
};
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1686
|
+
return { success: false, error: message };
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
var OpenClawBridge = class {
|
|
1690
|
+
/**
|
|
1691
|
+
* Execute a skill with the given config and input parameters.
|
|
1692
|
+
*
|
|
1693
|
+
* @param config - The SkillConfig for this skill (must be type 'openclaw').
|
|
1694
|
+
* @param params - Input parameters passed by the caller.
|
|
1695
|
+
* @returns Partial ExecutionResult without latency_ms.
|
|
1696
|
+
*/
|
|
1697
|
+
async execute(config, params) {
|
|
1698
|
+
const ocConfig = config;
|
|
1699
|
+
const payload = buildPayload(ocConfig, params);
|
|
1700
|
+
switch (ocConfig.channel) {
|
|
1701
|
+
case "webhook":
|
|
1702
|
+
return executeWebhook(ocConfig, payload);
|
|
1703
|
+
case "process":
|
|
1704
|
+
return executeProcess(ocConfig, payload);
|
|
1705
|
+
case "telegram":
|
|
1706
|
+
return executeTelegram(ocConfig, payload);
|
|
1707
|
+
default: {
|
|
1708
|
+
const unknownChannel = ocConfig.channel;
|
|
1709
|
+
return {
|
|
1710
|
+
success: false,
|
|
1711
|
+
error: `Unknown OpenClaw channel: "${String(unknownChannel)}"`
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// src/skills/command-executor.ts
|
|
1719
|
+
import { execFile as execFile2 } from "child_process";
|
|
1720
|
+
function shellEscape2(value) {
|
|
1721
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
1722
|
+
}
|
|
1723
|
+
function safeInterpolateCommand2(template, context) {
|
|
1724
|
+
return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
|
|
1725
|
+
const parts = expr.split(".");
|
|
1726
|
+
let current = context;
|
|
1727
|
+
for (const part of parts) {
|
|
1728
|
+
if (current === null || typeof current !== "object") return "";
|
|
1729
|
+
const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
|
|
1730
|
+
if (bracketMatch) {
|
|
1731
|
+
current = current[bracketMatch[1]];
|
|
1732
|
+
if (Array.isArray(current)) {
|
|
1733
|
+
current = current[parseInt(bracketMatch[2], 10)];
|
|
1734
|
+
} else {
|
|
1735
|
+
return "";
|
|
1736
|
+
}
|
|
1737
|
+
} else {
|
|
1738
|
+
current = current[part];
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (current === void 0 || current === null) return "";
|
|
1742
|
+
return shellEscape2(String(current));
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
function execFileAsync2(file, args, options) {
|
|
1746
|
+
return new Promise((resolve, reject) => {
|
|
1747
|
+
execFile2(file, args, options, (error, stdout, stderr) => {
|
|
1748
|
+
const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
|
|
1749
|
+
const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
|
|
1750
|
+
if (error) {
|
|
1751
|
+
const enriched = Object.assign(error, { stderr: stderrStr });
|
|
1752
|
+
reject(enriched);
|
|
1753
|
+
} else {
|
|
1754
|
+
resolve({ stdout: stdoutStr, stderr: stderrStr });
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
var CommandExecutor = class {
|
|
1760
|
+
/**
|
|
1761
|
+
* Execute a command skill with the provided parameters.
|
|
1762
|
+
*
|
|
1763
|
+
* Steps:
|
|
1764
|
+
* 1. Security check: base command must be in `allowed_commands` if set.
|
|
1765
|
+
* 2. Interpolate `config.command` using `{ params }` context.
|
|
1766
|
+
* 3. Run via `child_process.exec` with timeout and cwd.
|
|
1767
|
+
* 4. Parse stdout based on `output_type`: text | json | file.
|
|
1768
|
+
*
|
|
1769
|
+
* @param config - Validated CommandSkillConfig.
|
|
1770
|
+
* @param params - Input parameters passed by the caller.
|
|
1771
|
+
* @returns Partial ExecutionResult (without latency_ms).
|
|
1772
|
+
*/
|
|
1773
|
+
async execute(config, params) {
|
|
1774
|
+
const cmdConfig = config;
|
|
1775
|
+
const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
|
|
1776
|
+
if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
|
|
1777
|
+
if (!cmdConfig.allowed_commands.includes(baseCommand)) {
|
|
1778
|
+
return {
|
|
1779
|
+
success: false,
|
|
1780
|
+
error: `Command not allowed: "${baseCommand}". Allowed: ${cmdConfig.allowed_commands.join(", ")}`
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
|
|
1785
|
+
const timeout = cmdConfig.timeout_ms ?? 3e4;
|
|
1786
|
+
const cwd = cmdConfig.working_dir ?? process.cwd();
|
|
1787
|
+
let stdout;
|
|
1788
|
+
try {
|
|
1789
|
+
const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
|
|
1790
|
+
timeout,
|
|
1791
|
+
cwd,
|
|
1792
|
+
maxBuffer: 10 * 1024 * 1024
|
|
1793
|
+
// 10 MB
|
|
1794
|
+
});
|
|
1795
|
+
stdout = result.stdout;
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
if (err instanceof Error) {
|
|
1798
|
+
const message = err.message;
|
|
1799
|
+
const stderrContent = err.stderr ?? "";
|
|
1800
|
+
if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
|
|
1801
|
+
return {
|
|
1802
|
+
success: false,
|
|
1803
|
+
error: `Command timed out after ${timeout}ms`
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
success: false,
|
|
1808
|
+
error: stderrContent.trim() || message
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
return {
|
|
1812
|
+
success: false,
|
|
1813
|
+
error: String(err)
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
const rawOutput = stdout.trim();
|
|
1817
|
+
switch (cmdConfig.output_type) {
|
|
1818
|
+
case "text":
|
|
1819
|
+
return { success: true, result: rawOutput };
|
|
1820
|
+
case "json": {
|
|
1821
|
+
try {
|
|
1822
|
+
const parsed = JSON.parse(rawOutput);
|
|
1823
|
+
return { success: true, result: parsed };
|
|
1824
|
+
} catch {
|
|
1825
|
+
return {
|
|
1826
|
+
success: false,
|
|
1827
|
+
error: `Failed to parse JSON output: ${rawOutput.slice(0, 100)}`
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
case "file":
|
|
1832
|
+
return { success: true, result: { file_path: rawOutput } };
|
|
1833
|
+
default:
|
|
1834
|
+
return {
|
|
1835
|
+
success: false,
|
|
1836
|
+
error: `Unknown output_type: ${String(cmdConfig.output_type)}`
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
// src/conductor/task-decomposer.ts
|
|
1843
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1844
|
+
var TEMPLATES = {
|
|
1845
|
+
"video-production": {
|
|
1846
|
+
keywords: ["video", "demo", "clip", "animation"],
|
|
1847
|
+
steps: [
|
|
1848
|
+
{
|
|
1849
|
+
description: "Generate script from task description",
|
|
1850
|
+
required_capability: "text_gen",
|
|
1851
|
+
estimated_credits: 2,
|
|
1852
|
+
depends_on_indices: []
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
description: "Generate voiceover from script",
|
|
1856
|
+
required_capability: "tts",
|
|
1857
|
+
estimated_credits: 3,
|
|
1858
|
+
depends_on_indices: [0]
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
description: "Generate video visuals from script",
|
|
1862
|
+
required_capability: "video_gen",
|
|
1863
|
+
estimated_credits: 5,
|
|
1864
|
+
depends_on_indices: [0]
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
description: "Composite voiceover and video into final output",
|
|
1868
|
+
required_capability: "video_edit",
|
|
1869
|
+
estimated_credits: 3,
|
|
1870
|
+
depends_on_indices: [1, 2]
|
|
1871
|
+
}
|
|
1872
|
+
]
|
|
1873
|
+
},
|
|
1874
|
+
"deep-analysis": {
|
|
1875
|
+
keywords: ["analyze", "analysis", "research", "report", "evaluate"],
|
|
1876
|
+
steps: [
|
|
1877
|
+
{
|
|
1878
|
+
description: "Research and gather relevant data",
|
|
1879
|
+
required_capability: "web_search",
|
|
1880
|
+
estimated_credits: 2,
|
|
1881
|
+
depends_on_indices: []
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
description: "Analyze gathered data",
|
|
1885
|
+
required_capability: "text_gen",
|
|
1886
|
+
estimated_credits: 3,
|
|
1887
|
+
depends_on_indices: [0]
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
description: "Summarize analysis findings",
|
|
1891
|
+
required_capability: "text_gen",
|
|
1892
|
+
estimated_credits: 2,
|
|
1893
|
+
depends_on_indices: [1]
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
description: "Format into final report",
|
|
1897
|
+
required_capability: "text_gen",
|
|
1898
|
+
estimated_credits: 1,
|
|
1899
|
+
depends_on_indices: [2]
|
|
1900
|
+
}
|
|
1901
|
+
]
|
|
1902
|
+
},
|
|
1903
|
+
"content-generation": {
|
|
1904
|
+
keywords: ["write", "blog", "article", "content", "post", "essay"],
|
|
1905
|
+
steps: [
|
|
1906
|
+
{
|
|
1907
|
+
description: "Create content outline",
|
|
1908
|
+
required_capability: "text_gen",
|
|
1909
|
+
estimated_credits: 1,
|
|
1910
|
+
depends_on_indices: []
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
description: "Draft content from outline",
|
|
1914
|
+
required_capability: "text_gen",
|
|
1915
|
+
estimated_credits: 3,
|
|
1916
|
+
depends_on_indices: [0]
|
|
1917
|
+
},
|
|
1918
|
+
{
|
|
1919
|
+
description: "Review and refine draft",
|
|
1920
|
+
required_capability: "text_gen",
|
|
1921
|
+
estimated_credits: 2,
|
|
1922
|
+
depends_on_indices: [1]
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
description: "Finalize and polish content",
|
|
1926
|
+
required_capability: "text_gen",
|
|
1927
|
+
estimated_credits: 1,
|
|
1928
|
+
depends_on_indices: [2]
|
|
1929
|
+
}
|
|
1930
|
+
]
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
function decompose(task, _availableCapabilities) {
|
|
1934
|
+
const lower = task.toLowerCase();
|
|
1935
|
+
for (const template of Object.values(TEMPLATES)) {
|
|
1936
|
+
const matched = template.keywords.some((kw) => lower.includes(kw));
|
|
1937
|
+
if (!matched) continue;
|
|
1938
|
+
const ids = template.steps.map(() => randomUUID4());
|
|
1939
|
+
return template.steps.map((step, i) => ({
|
|
1940
|
+
id: ids[i],
|
|
1941
|
+
description: step.description,
|
|
1942
|
+
required_capability: step.required_capability,
|
|
1943
|
+
params: {},
|
|
1944
|
+
depends_on: step.depends_on_indices.map((idx) => ids[idx]),
|
|
1945
|
+
estimated_credits: step.estimated_credits
|
|
1946
|
+
}));
|
|
1947
|
+
}
|
|
1948
|
+
return [];
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// src/gateway/client.ts
|
|
1952
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
1953
|
+
async function requestCapability(opts) {
|
|
1954
|
+
const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt } = opts;
|
|
1955
|
+
const id = randomUUID5();
|
|
1956
|
+
const payload = {
|
|
1957
|
+
jsonrpc: "2.0",
|
|
1958
|
+
id,
|
|
1959
|
+
method: "capability.execute",
|
|
1960
|
+
params: {
|
|
1961
|
+
card_id: cardId,
|
|
1962
|
+
...params,
|
|
1963
|
+
...escrowReceipt ? { escrow_receipt: escrowReceipt } : {}
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
const controller = new AbortController();
|
|
1967
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1968
|
+
let response;
|
|
1969
|
+
try {
|
|
1970
|
+
response = await fetch(`${gatewayUrl}/rpc`, {
|
|
1971
|
+
method: "POST",
|
|
1972
|
+
headers: {
|
|
1973
|
+
"Content-Type": "application/json",
|
|
1974
|
+
Authorization: `Bearer ${token}`
|
|
1975
|
+
},
|
|
1976
|
+
body: JSON.stringify(payload),
|
|
1977
|
+
signal: controller.signal
|
|
1978
|
+
});
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
clearTimeout(timer);
|
|
1981
|
+
const isTimeout = err instanceof Error && err.name === "AbortError";
|
|
1982
|
+
throw new AgentBnBError(
|
|
1983
|
+
isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
|
|
1984
|
+
isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
|
|
1985
|
+
);
|
|
1986
|
+
} finally {
|
|
1987
|
+
clearTimeout(timer);
|
|
1988
|
+
}
|
|
1989
|
+
const body = await response.json();
|
|
1990
|
+
if (body.error) {
|
|
1991
|
+
throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
|
|
1992
|
+
}
|
|
1993
|
+
return body.result;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// src/autonomy/tiers.ts
|
|
1997
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
1998
|
+
|
|
1999
|
+
// src/autonomy/pending-requests.ts
|
|
2000
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
2001
|
+
|
|
2002
|
+
// src/cli/peers.ts
|
|
2003
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
2004
|
+
import { join as join3 } from "path";
|
|
2005
|
+
|
|
2006
|
+
// src/cli/config.ts
|
|
2007
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
2008
|
+
import { homedir } from "os";
|
|
2009
|
+
import { join as join2 } from "path";
|
|
2010
|
+
|
|
2011
|
+
// src/autonomy/auto-request.ts
|
|
2012
|
+
function minMaxNormalize(values) {
|
|
2013
|
+
if (values.length === 0) return [];
|
|
2014
|
+
if (values.length === 1) return [1];
|
|
2015
|
+
const min = Math.min(...values);
|
|
2016
|
+
const max = Math.max(...values);
|
|
2017
|
+
if (max === min) {
|
|
2018
|
+
return values.map(() => 1);
|
|
2019
|
+
}
|
|
2020
|
+
return values.map((v) => (v - min) / (max - min));
|
|
2021
|
+
}
|
|
2022
|
+
function scorePeers(candidates, selfOwner) {
|
|
2023
|
+
const eligible = candidates.filter((c) => c.card.owner !== selfOwner);
|
|
2024
|
+
if (eligible.length === 0) return [];
|
|
2025
|
+
const successRates = eligible.map((c) => c.card.metadata?.success_rate ?? 0.5);
|
|
2026
|
+
const costEfficiencies = eligible.map((c) => c.cost === 0 ? 1 : 1 / c.cost);
|
|
2027
|
+
const idleRates = eligible.map((c) => {
|
|
2028
|
+
const internal = c.card._internal;
|
|
2029
|
+
const idleRate = internal?.idle_rate;
|
|
2030
|
+
return typeof idleRate === "number" ? idleRate : 1;
|
|
2031
|
+
});
|
|
2032
|
+
const normSuccess = minMaxNormalize(successRates);
|
|
2033
|
+
const normCost = minMaxNormalize(costEfficiencies);
|
|
2034
|
+
const normIdle = minMaxNormalize(idleRates);
|
|
2035
|
+
const scored = eligible.map((c, i) => ({
|
|
2036
|
+
...c,
|
|
2037
|
+
rawScore: (normSuccess[i] ?? 0) * (normCost[i] ?? 0) * (normIdle[i] ?? 0)
|
|
2038
|
+
}));
|
|
2039
|
+
scored.sort((a, b) => b.rawScore - a.rawScore);
|
|
2040
|
+
return scored;
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
// src/conductor/capability-matcher.ts
|
|
2044
|
+
var MAX_ALTERNATIVES = 2;
|
|
2045
|
+
function matchSubTasks(opts) {
|
|
2046
|
+
const { db, subtasks, conductorOwner } = opts;
|
|
2047
|
+
return subtasks.map((subtask) => {
|
|
2048
|
+
const cards = searchCards(db, subtask.required_capability, { online: true });
|
|
2049
|
+
const candidates = [];
|
|
2050
|
+
for (const card of cards) {
|
|
2051
|
+
const cardAsV2 = card;
|
|
2052
|
+
if (Array.isArray(cardAsV2.skills)) {
|
|
2053
|
+
for (const skill of cardAsV2.skills) {
|
|
2054
|
+
candidates.push({
|
|
2055
|
+
card,
|
|
2056
|
+
cost: skill.pricing.credits_per_call,
|
|
2057
|
+
skillId: skill.id
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
} else {
|
|
2061
|
+
candidates.push({
|
|
2062
|
+
card,
|
|
2063
|
+
cost: card.pricing.credits_per_call,
|
|
2064
|
+
skillId: void 0
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
const scored = scorePeers(candidates, conductorOwner);
|
|
2069
|
+
if (scored.length === 0) {
|
|
2070
|
+
return {
|
|
2071
|
+
subtask_id: subtask.id,
|
|
2072
|
+
selected_agent: "",
|
|
2073
|
+
selected_skill: "",
|
|
2074
|
+
score: 0,
|
|
2075
|
+
credits: 0,
|
|
2076
|
+
alternatives: []
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
const top = scored[0];
|
|
2080
|
+
const alternatives = scored.slice(1, 1 + MAX_ALTERNATIVES).map((s) => ({
|
|
2081
|
+
agent: s.card.owner,
|
|
2082
|
+
skill: s.skillId ?? "",
|
|
2083
|
+
score: s.rawScore,
|
|
2084
|
+
credits: s.cost
|
|
2085
|
+
}));
|
|
2086
|
+
return {
|
|
2087
|
+
subtask_id: subtask.id,
|
|
2088
|
+
selected_agent: top.card.owner,
|
|
2089
|
+
selected_skill: top.skillId ?? "",
|
|
2090
|
+
score: top.rawScore,
|
|
2091
|
+
credits: top.cost,
|
|
2092
|
+
alternatives
|
|
2093
|
+
};
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// src/conductor/budget-controller.ts
|
|
2098
|
+
var ORCHESTRATION_FEE = 5;
|
|
2099
|
+
var BudgetController = class {
|
|
2100
|
+
/**
|
|
2101
|
+
* Creates a new BudgetController.
|
|
2102
|
+
*
|
|
2103
|
+
* @param budgetManager - Underlying BudgetManager for reserve floor enforcement.
|
|
2104
|
+
* @param maxBudget - Hard ceiling for the orchestration run.
|
|
2105
|
+
*/
|
|
2106
|
+
constructor(budgetManager, maxBudget) {
|
|
2107
|
+
this.budgetManager = budgetManager;
|
|
2108
|
+
this.maxBudget = maxBudget;
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Pre-calculates the total budget for an orchestration run.
|
|
2112
|
+
*
|
|
2113
|
+
* Sums all matched sub-task credits, adds the orchestration fee,
|
|
2114
|
+
* and determines whether approval is required (estimated > max).
|
|
2115
|
+
*
|
|
2116
|
+
* @param matches - MatchResult[] from the CapabilityMatcher.
|
|
2117
|
+
* @returns An ExecutionBudget with cost breakdown and approval status.
|
|
2118
|
+
*/
|
|
2119
|
+
calculateBudget(matches) {
|
|
2120
|
+
const perTaskSpending = /* @__PURE__ */ new Map();
|
|
2121
|
+
let subTotal = 0;
|
|
2122
|
+
for (const match of matches) {
|
|
2123
|
+
perTaskSpending.set(match.subtask_id, match.credits);
|
|
2124
|
+
subTotal += match.credits;
|
|
2125
|
+
}
|
|
2126
|
+
const estimatedTotal = subTotal + ORCHESTRATION_FEE;
|
|
2127
|
+
return {
|
|
2128
|
+
estimated_total: estimatedTotal,
|
|
2129
|
+
max_budget: this.maxBudget,
|
|
2130
|
+
orchestration_fee: ORCHESTRATION_FEE,
|
|
2131
|
+
per_task_spending: perTaskSpending,
|
|
2132
|
+
requires_approval: estimatedTotal > this.maxBudget
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Checks whether orchestration can proceed without explicit approval.
|
|
2137
|
+
*
|
|
2138
|
+
* Returns true only when:
|
|
2139
|
+
* 1. The budget does NOT require approval (estimated_total <= max_budget)
|
|
2140
|
+
* 2. The BudgetManager confirms sufficient credits (respecting reserve floor)
|
|
2141
|
+
*
|
|
2142
|
+
* @param budget - ExecutionBudget from calculateBudget().
|
|
2143
|
+
* @returns true if execution can proceed autonomously.
|
|
2144
|
+
*/
|
|
2145
|
+
canExecute(budget) {
|
|
2146
|
+
if (budget.requires_approval) return false;
|
|
2147
|
+
return this.budgetManager.canSpend(budget.estimated_total);
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Checks budget after explicit user/agent approval.
|
|
2151
|
+
*
|
|
2152
|
+
* Ignores the requires_approval flag — used when the caller has already
|
|
2153
|
+
* obtained explicit approval for the over-budget orchestration.
|
|
2154
|
+
* Still enforces the reserve floor via BudgetManager.canSpend().
|
|
2155
|
+
*
|
|
2156
|
+
* @param budget - ExecutionBudget from calculateBudget().
|
|
2157
|
+
* @returns true if the agent has sufficient credits (reserve floor check only).
|
|
2158
|
+
*/
|
|
2159
|
+
approveAndCheck(budget) {
|
|
2160
|
+
return this.budgetManager.canSpend(budget.estimated_total);
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
|
|
2164
|
+
// src/conductor/card.ts
|
|
2165
|
+
var CONDUCTOR_OWNER = "agentbnb-conductor";
|
|
2166
|
+
var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
|
|
2167
|
+
function buildConductorCard() {
|
|
2168
|
+
const card = {
|
|
2169
|
+
spec_version: "2.0",
|
|
2170
|
+
id: CONDUCTOR_CARD_ID,
|
|
2171
|
+
owner: CONDUCTOR_OWNER,
|
|
2172
|
+
agent_name: "AgentBnB Conductor",
|
|
2173
|
+
skills: [
|
|
2174
|
+
{
|
|
2175
|
+
id: "orchestrate",
|
|
2176
|
+
name: "Task Orchestration",
|
|
2177
|
+
description: "Decomposes complex tasks and coordinates multi-agent execution",
|
|
2178
|
+
level: 3,
|
|
2179
|
+
inputs: [
|
|
2180
|
+
{
|
|
2181
|
+
name: "task",
|
|
2182
|
+
type: "text",
|
|
2183
|
+
description: "Natural language task description"
|
|
2184
|
+
}
|
|
2185
|
+
],
|
|
2186
|
+
outputs: [
|
|
2187
|
+
{
|
|
2188
|
+
name: "result",
|
|
2189
|
+
type: "json",
|
|
2190
|
+
description: "Aggregated execution results"
|
|
2191
|
+
}
|
|
2192
|
+
],
|
|
2193
|
+
pricing: { credits_per_call: 5 }
|
|
2194
|
+
},
|
|
2195
|
+
{
|
|
2196
|
+
id: "plan",
|
|
2197
|
+
name: "Execution Planning",
|
|
2198
|
+
description: "Returns an execution plan with cost estimate without executing",
|
|
2199
|
+
level: 1,
|
|
2200
|
+
inputs: [
|
|
2201
|
+
{
|
|
2202
|
+
name: "task",
|
|
2203
|
+
type: "text",
|
|
2204
|
+
description: "Natural language task description"
|
|
2205
|
+
}
|
|
2206
|
+
],
|
|
2207
|
+
outputs: [
|
|
2208
|
+
{
|
|
2209
|
+
name: "plan",
|
|
2210
|
+
type: "json",
|
|
2211
|
+
description: "Execution plan with cost breakdown"
|
|
2212
|
+
}
|
|
2213
|
+
],
|
|
2214
|
+
pricing: { credits_per_call: 1 }
|
|
2215
|
+
}
|
|
2216
|
+
],
|
|
2217
|
+
availability: { online: true }
|
|
2218
|
+
};
|
|
2219
|
+
return CapabilityCardV2Schema.parse(card);
|
|
2220
|
+
}
|
|
2221
|
+
function registerConductorCard(db) {
|
|
2222
|
+
const card = buildConductorCard();
|
|
2223
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2224
|
+
const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(card.id);
|
|
2225
|
+
if (existing) {
|
|
2226
|
+
db.prepare(
|
|
2227
|
+
"UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
|
|
2228
|
+
).run(JSON.stringify(card), now, card.id);
|
|
2229
|
+
} else {
|
|
2230
|
+
db.prepare(
|
|
2231
|
+
"INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
|
|
2232
|
+
).run(card.id, card.owner, JSON.stringify(card), now, now);
|
|
2233
|
+
}
|
|
2234
|
+
return card;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/conductor/pipeline-orchestrator.ts
|
|
2238
|
+
function computeWaves(subtasks) {
|
|
2239
|
+
const waves = [];
|
|
2240
|
+
const completed = /* @__PURE__ */ new Set();
|
|
2241
|
+
const remaining = new Map(subtasks.map((s) => [s.id, s]));
|
|
2242
|
+
while (remaining.size > 0) {
|
|
2243
|
+
const wave = [];
|
|
2244
|
+
for (const [id, task] of remaining) {
|
|
2245
|
+
const depsResolved = task.depends_on.every((dep) => completed.has(dep));
|
|
2246
|
+
if (depsResolved) {
|
|
2247
|
+
wave.push(id);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (wave.length === 0) {
|
|
2251
|
+
break;
|
|
2252
|
+
}
|
|
2253
|
+
for (const id of wave) {
|
|
2254
|
+
remaining.delete(id);
|
|
2255
|
+
completed.add(id);
|
|
2256
|
+
}
|
|
2257
|
+
waves.push(wave);
|
|
2258
|
+
}
|
|
2259
|
+
return waves;
|
|
2260
|
+
}
|
|
2261
|
+
async function orchestrate(opts) {
|
|
2262
|
+
const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
|
|
2263
|
+
const startTime = Date.now();
|
|
2264
|
+
if (subtasks.length === 0) {
|
|
2265
|
+
return {
|
|
2266
|
+
success: true,
|
|
2267
|
+
results: /* @__PURE__ */ new Map(),
|
|
2268
|
+
total_credits: 0,
|
|
2269
|
+
latency_ms: Date.now() - startTime
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
const results = /* @__PURE__ */ new Map();
|
|
2273
|
+
const errors = [];
|
|
2274
|
+
let totalCredits = 0;
|
|
2275
|
+
const waves = computeWaves(subtasks);
|
|
2276
|
+
const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
|
|
2277
|
+
for (const wave of waves) {
|
|
2278
|
+
if (maxBudget !== void 0 && totalCredits >= maxBudget) {
|
|
2279
|
+
errors.push(`Budget exceeded: spent ${totalCredits} cr, max ${maxBudget} cr`);
|
|
2280
|
+
break;
|
|
2281
|
+
}
|
|
2282
|
+
const executableIds = [];
|
|
2283
|
+
for (const taskId of wave) {
|
|
2284
|
+
const m = matches.get(taskId);
|
|
2285
|
+
if (maxBudget !== void 0 && m && totalCredits + m.credits > maxBudget) {
|
|
2286
|
+
errors.push(`Skipping task ${taskId}: would exceed budget (${totalCredits} + ${m.credits} > ${maxBudget})`);
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
executableIds.push(taskId);
|
|
2290
|
+
}
|
|
2291
|
+
const waveResults = await Promise.allSettled(
|
|
2292
|
+
executableIds.map(async (taskId) => {
|
|
2293
|
+
const subtask = subtaskMap.get(taskId);
|
|
2294
|
+
const m = matches.get(taskId);
|
|
2295
|
+
if (!m) {
|
|
2296
|
+
throw new Error(`No match found for subtask ${taskId}`);
|
|
2297
|
+
}
|
|
2298
|
+
const stepsContext = {};
|
|
2299
|
+
for (const [id, val] of results) {
|
|
2300
|
+
stepsContext[id] = val;
|
|
2301
|
+
}
|
|
2302
|
+
const interpContext = { steps: stepsContext, prev: void 0 };
|
|
2303
|
+
if (subtask.depends_on.length > 0) {
|
|
2304
|
+
const lastDep = subtask.depends_on[subtask.depends_on.length - 1];
|
|
2305
|
+
interpContext.prev = results.get(lastDep);
|
|
2306
|
+
}
|
|
2307
|
+
const interpolatedParams = interpolateObject(
|
|
2308
|
+
subtask.params,
|
|
2309
|
+
interpContext
|
|
2310
|
+
);
|
|
2311
|
+
const primary = resolveAgentUrl(m.selected_agent);
|
|
2312
|
+
try {
|
|
2313
|
+
const res = await requestCapability({
|
|
2314
|
+
gatewayUrl: primary.url,
|
|
2315
|
+
token: gatewayToken,
|
|
2316
|
+
cardId: primary.cardId,
|
|
2317
|
+
params: interpolatedParams,
|
|
2318
|
+
timeoutMs
|
|
2319
|
+
});
|
|
2320
|
+
return { taskId, result: res, credits: m.credits };
|
|
2321
|
+
} catch (primaryErr) {
|
|
2322
|
+
if (m.alternatives.length > 0) {
|
|
2323
|
+
const alt = m.alternatives[0];
|
|
2324
|
+
const altAgent = resolveAgentUrl(alt.agent);
|
|
2325
|
+
try {
|
|
2326
|
+
const altRes = await requestCapability({
|
|
2327
|
+
gatewayUrl: altAgent.url,
|
|
2328
|
+
token: gatewayToken,
|
|
2329
|
+
cardId: altAgent.cardId,
|
|
2330
|
+
params: interpolatedParams,
|
|
2331
|
+
timeoutMs
|
|
2332
|
+
});
|
|
2333
|
+
return { taskId, result: altRes, credits: alt.credits };
|
|
2334
|
+
} catch (altErr) {
|
|
2335
|
+
throw new Error(
|
|
2336
|
+
`Task ${taskId}: primary (${m.selected_agent}) failed: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}; alternative (${alt.agent}) failed: ${altErr instanceof Error ? altErr.message : String(altErr)}`
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
throw new Error(
|
|
2341
|
+
`Task ${taskId}: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
})
|
|
2345
|
+
);
|
|
2346
|
+
for (const settlement of waveResults) {
|
|
2347
|
+
if (settlement.status === "fulfilled") {
|
|
2348
|
+
const { taskId, result, credits } = settlement.value;
|
|
2349
|
+
results.set(taskId, result);
|
|
2350
|
+
totalCredits += credits;
|
|
2351
|
+
} else {
|
|
2352
|
+
errors.push(settlement.reason instanceof Error ? settlement.reason.message : String(settlement.reason));
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return {
|
|
2357
|
+
success: errors.length === 0,
|
|
2358
|
+
results,
|
|
2359
|
+
total_credits: totalCredits,
|
|
2360
|
+
latency_ms: Date.now() - startTime,
|
|
2361
|
+
errors: errors.length > 0 ? errors : void 0
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// src/credit/budget.ts
|
|
2366
|
+
var DEFAULT_BUDGET_CONFIG = {
|
|
2367
|
+
reserve_credits: 20
|
|
2368
|
+
};
|
|
2369
|
+
var BudgetManager = class {
|
|
2370
|
+
/**
|
|
2371
|
+
* Creates a new BudgetManager.
|
|
2372
|
+
*
|
|
2373
|
+
* @param creditDb - The credit SQLite database instance.
|
|
2374
|
+
* @param owner - Agent owner identifier.
|
|
2375
|
+
* @param config - Budget configuration. Defaults to DEFAULT_BUDGET_CONFIG (20 credit reserve).
|
|
2376
|
+
*/
|
|
2377
|
+
constructor(creditDb, owner, config = DEFAULT_BUDGET_CONFIG) {
|
|
2378
|
+
this.creditDb = creditDb;
|
|
2379
|
+
this.owner = owner;
|
|
2380
|
+
this.config = config;
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Returns the number of credits available for spending.
|
|
2384
|
+
* Computed as: max(0, balance - reserve_credits).
|
|
2385
|
+
* Always returns a non-negative number — never goes below zero.
|
|
2386
|
+
*
|
|
2387
|
+
* @returns Available credits (balance minus reserve, floored at 0).
|
|
2388
|
+
*/
|
|
2389
|
+
availableCredits() {
|
|
2390
|
+
const balance = getBalance(this.creditDb, this.owner);
|
|
2391
|
+
return Math.max(0, balance - this.config.reserve_credits);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Returns true if spending `amount` credits is permitted by budget rules.
|
|
2395
|
+
*
|
|
2396
|
+
* Rules:
|
|
2397
|
+
* - Zero-cost calls (amount <= 0) always return true.
|
|
2398
|
+
* - Any positive amount requires availableCredits() >= amount.
|
|
2399
|
+
* - If balance is at or below the reserve floor, all positive-cost calls return false.
|
|
2400
|
+
*
|
|
2401
|
+
* @param amount - Number of credits to spend.
|
|
2402
|
+
* @returns true if the spend is allowed, false if it would breach the reserve floor.
|
|
2403
|
+
*/
|
|
2404
|
+
canSpend(amount) {
|
|
2405
|
+
if (amount <= 0) return true;
|
|
2406
|
+
return this.availableCredits() >= amount;
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
|
|
2410
|
+
// src/conductor/conductor-mode.ts
|
|
2411
|
+
var ConductorMode = class {
|
|
2412
|
+
db;
|
|
2413
|
+
creditDb;
|
|
2414
|
+
conductorOwner;
|
|
2415
|
+
gatewayToken;
|
|
2416
|
+
resolveAgentUrl;
|
|
2417
|
+
maxBudget;
|
|
2418
|
+
constructor(opts) {
|
|
2419
|
+
this.db = opts.db;
|
|
2420
|
+
this.creditDb = opts.creditDb;
|
|
2421
|
+
this.conductorOwner = opts.conductorOwner;
|
|
2422
|
+
this.gatewayToken = opts.gatewayToken;
|
|
2423
|
+
this.resolveAgentUrl = opts.resolveAgentUrl;
|
|
2424
|
+
this.maxBudget = opts.maxBudget ?? 100;
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Execute a conductor skill with the given config and params.
|
|
2428
|
+
*
|
|
2429
|
+
* @param config - SkillConfig with type 'conductor' and conductor_skill field.
|
|
2430
|
+
* @param params - Must include `task` string.
|
|
2431
|
+
* @returns Execution result without latency_ms (added by SkillExecutor).
|
|
2432
|
+
*/
|
|
2433
|
+
async execute(config, params) {
|
|
2434
|
+
const conductorSkill = config.conductor_skill;
|
|
2435
|
+
if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
|
|
2436
|
+
return {
|
|
2437
|
+
success: false,
|
|
2438
|
+
error: `Unknown conductor skill: "${conductorSkill}"`
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
const task = params.task;
|
|
2442
|
+
if (typeof task !== "string" || task.length === 0) {
|
|
2443
|
+
return {
|
|
2444
|
+
success: false,
|
|
2445
|
+
error: 'Missing or empty "task" parameter'
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
const subtasks = decompose(task);
|
|
2449
|
+
if (subtasks.length === 0) {
|
|
2450
|
+
return {
|
|
2451
|
+
success: false,
|
|
2452
|
+
error: "No template matches task"
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
const matchResults = matchSubTasks({
|
|
2456
|
+
db: this.db,
|
|
2457
|
+
subtasks,
|
|
2458
|
+
conductorOwner: this.conductorOwner
|
|
2459
|
+
});
|
|
2460
|
+
const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
|
|
2461
|
+
const budgetController = new BudgetController(budgetManager, this.maxBudget);
|
|
2462
|
+
const executionBudget = budgetController.calculateBudget(matchResults);
|
|
2463
|
+
if (!budgetController.canExecute(executionBudget)) {
|
|
2464
|
+
return {
|
|
2465
|
+
success: false,
|
|
2466
|
+
error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
if (conductorSkill === "plan") {
|
|
2470
|
+
return {
|
|
2471
|
+
success: true,
|
|
2472
|
+
result: {
|
|
2473
|
+
subtasks,
|
|
2474
|
+
matches: matchResults,
|
|
2475
|
+
budget: executionBudget
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
const matchMap = new Map(
|
|
2480
|
+
matchResults.map((m) => [m.subtask_id, m])
|
|
2481
|
+
);
|
|
2482
|
+
const orchResult = await orchestrate({
|
|
2483
|
+
subtasks,
|
|
2484
|
+
matches: matchMap,
|
|
2485
|
+
gatewayToken: this.gatewayToken,
|
|
2486
|
+
resolveAgentUrl: this.resolveAgentUrl,
|
|
2487
|
+
maxBudget: this.maxBudget
|
|
2488
|
+
});
|
|
2489
|
+
const resultObj = {};
|
|
2490
|
+
for (const [key, value] of orchResult.results) {
|
|
2491
|
+
resultObj[key] = value;
|
|
2492
|
+
}
|
|
2493
|
+
return {
|
|
2494
|
+
success: orchResult.success,
|
|
2495
|
+
result: {
|
|
2496
|
+
plan: subtasks,
|
|
2497
|
+
execution: resultObj,
|
|
2498
|
+
total_credits: orchResult.total_credits,
|
|
2499
|
+
latency_ms: orchResult.latency_ms,
|
|
2500
|
+
errors: orchResult.errors
|
|
2501
|
+
},
|
|
2502
|
+
error: orchResult.success ? void 0 : orchResult.errors?.join("; ")
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
|
|
2507
|
+
// src/credit/escrow-receipt.ts
|
|
2508
|
+
import { z as z3 } from "zod";
|
|
2509
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
2510
|
+
var EscrowReceiptSchema = z3.object({
|
|
2511
|
+
requester_owner: z3.string().min(1),
|
|
2512
|
+
requester_public_key: z3.string().min(1),
|
|
2513
|
+
amount: z3.number().positive(),
|
|
2514
|
+
card_id: z3.string().min(1),
|
|
2515
|
+
skill_id: z3.string().optional(),
|
|
2516
|
+
timestamp: z3.string(),
|
|
2517
|
+
nonce: z3.string().uuid(),
|
|
2518
|
+
signature: z3.string().min(1)
|
|
2519
|
+
});
|
|
2520
|
+
function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
|
|
2521
|
+
const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
|
|
2522
|
+
const receiptData = {
|
|
2523
|
+
requester_owner: opts.owner,
|
|
2524
|
+
requester_public_key: publicKey.toString("hex"),
|
|
2525
|
+
amount: opts.amount,
|
|
2526
|
+
card_id: opts.cardId,
|
|
2527
|
+
...opts.skillId ? { skill_id: opts.skillId } : {},
|
|
2528
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2529
|
+
nonce: randomUUID8()
|
|
2530
|
+
};
|
|
2531
|
+
const signature = signEscrowReceipt(receiptData, privateKey);
|
|
2532
|
+
const receipt = {
|
|
2533
|
+
...receiptData,
|
|
2534
|
+
signature
|
|
2535
|
+
};
|
|
2536
|
+
return { escrowId, receipt };
|
|
2537
|
+
}
|
|
885
2538
|
export {
|
|
2539
|
+
ApiExecutor,
|
|
2540
|
+
ApiSkillConfigSchema,
|
|
2541
|
+
BudgetController,
|
|
2542
|
+
CONDUCTOR_OWNER,
|
|
886
2543
|
CapabilityCardSchema,
|
|
2544
|
+
CommandExecutor,
|
|
2545
|
+
CommandSkillConfigSchema,
|
|
2546
|
+
ConductorMode,
|
|
2547
|
+
ConductorSkillConfigSchema,
|
|
2548
|
+
EscrowReceiptSchema,
|
|
2549
|
+
ORCHESTRATION_FEE,
|
|
2550
|
+
OpenClawBridge,
|
|
2551
|
+
OpenClawSkillConfigSchema,
|
|
2552
|
+
PipelineExecutor,
|
|
2553
|
+
PipelineSkillConfigSchema,
|
|
2554
|
+
SkillConfigSchema,
|
|
2555
|
+
SkillExecutor,
|
|
2556
|
+
SkillsFileSchema,
|
|
2557
|
+
TEMPLATES,
|
|
2558
|
+
applyInputMapping,
|
|
2559
|
+
buildAuthHeaders,
|
|
2560
|
+
buildConductorCard,
|
|
887
2561
|
createGatewayServer,
|
|
2562
|
+
createSignedEscrowReceipt,
|
|
2563
|
+
createSkillExecutor,
|
|
2564
|
+
decompose,
|
|
2565
|
+
expandEnvVars,
|
|
2566
|
+
extractByPath,
|
|
2567
|
+
generateKeyPair,
|
|
888
2568
|
getBalance,
|
|
889
2569
|
getCard,
|
|
890
2570
|
insertCard,
|
|
2571
|
+
interpolate,
|
|
2572
|
+
interpolateObject,
|
|
2573
|
+
loadKeyPair,
|
|
2574
|
+
matchSubTasks,
|
|
891
2575
|
openCreditDb,
|
|
892
2576
|
openDatabase,
|
|
893
|
-
|
|
2577
|
+
orchestrate,
|
|
2578
|
+
parseSkillsFile,
|
|
2579
|
+
registerConductorCard,
|
|
2580
|
+
releaseRequesterEscrow,
|
|
2581
|
+
resolvePath,
|
|
2582
|
+
saveKeyPair,
|
|
2583
|
+
searchCards,
|
|
2584
|
+
settleProviderEarning,
|
|
2585
|
+
settleRequesterEscrow,
|
|
2586
|
+
signEscrowReceipt,
|
|
2587
|
+
verifyEscrowReceipt
|
|
894
2588
|
};
|