agentbnb 2.2.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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;
@@ -458,7 +482,7 @@ function updateReputation(db, cardId, success, latencyMs) {
458
482
 
459
483
  // src/registry/matcher.ts
460
484
  function searchCards(db, query, filters = {}) {
461
- const words = query.trim().split(/\s+/).map((w) => w.replace(/"/g, "")).filter((w) => w.length > 0);
485
+ const words = query.trim().split(/\s+/).map((w) => w.replace(/["*^{}():]/g, "")).filter((w) => w.length > 0);
462
486
  if (words.length === 0) return [];
463
487
  const ftsQuery = words.map((w) => `"${w}"`).join(" OR ");
464
488
  const conditions = [];
@@ -537,9 +561,29 @@ function getBalance(db, owner) {
537
561
  const row = db.prepare("SELECT balance FROM credit_balances WHERE owner = ?").get(owner);
538
562
  return row?.balance ?? 0;
539
563
  }
564
+ function recordEarning(db, owner, amount, _cardId, receiptNonce) {
565
+ const now = (/* @__PURE__ */ new Date()).toISOString();
566
+ db.transaction(() => {
567
+ const existing = db.prepare(
568
+ "SELECT id FROM credit_transactions WHERE reference_id = ? AND reason = 'remote_earning'"
569
+ ).get(receiptNonce);
570
+ if (existing) return;
571
+ db.prepare(
572
+ "INSERT OR IGNORE INTO credit_balances (owner, balance, updated_at) VALUES (?, 0, ?)"
573
+ ).run(owner, now);
574
+ db.prepare(
575
+ "UPDATE credit_balances SET balance = balance + ?, updated_at = ? WHERE owner = ?"
576
+ ).run(amount, now, owner);
577
+ db.prepare(
578
+ "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
579
+ ).run(randomUUID(), owner, amount, "remote_earning", receiptNonce, now);
580
+ })();
581
+ }
540
582
 
541
583
  // src/gateway/server.ts
542
584
  import Fastify from "fastify";
585
+
586
+ // src/gateway/execute.ts
543
587
  import { randomUUID as randomUUID3 } from "crypto";
544
588
 
545
589
  // src/credit/escrow.ts
@@ -618,6 +662,256 @@ function releaseEscrow(db, escrowId) {
618
662
  });
619
663
  release();
620
664
  }
665
+ function confirmEscrowDebit(db, escrowId) {
666
+ const now = (/* @__PURE__ */ new Date()).toISOString();
667
+ const confirm = db.transaction(() => {
668
+ const escrow = db.prepare("SELECT id, owner, amount, status FROM credit_escrow WHERE id = ?").get(escrowId);
669
+ if (!escrow) {
670
+ throw new AgentBnBError(`Escrow not found: ${escrowId}`, "ESCROW_NOT_FOUND");
671
+ }
672
+ if (escrow.status !== "held") {
673
+ throw new AgentBnBError(
674
+ `Escrow ${escrowId} is already ${escrow.status}`,
675
+ "ESCROW_ALREADY_SETTLED"
676
+ );
677
+ }
678
+ db.prepare(
679
+ "UPDATE credit_escrow SET status = ?, settled_at = ? WHERE id = ?"
680
+ ).run("settled", now, escrowId);
681
+ db.prepare(
682
+ "INSERT INTO credit_transactions (id, owner, amount, reason, reference_id, created_at) VALUES (?, ?, ?, ?, ?, ?)"
683
+ ).run(randomUUID2(), escrow.owner, 0, "remote_settlement_confirmed", escrowId, now);
684
+ });
685
+ confirm();
686
+ }
687
+
688
+ // src/credit/signing.ts
689
+ import { generateKeyPairSync, sign, verify, createPublicKey, createPrivateKey } from "crypto";
690
+ import { writeFileSync, readFileSync, existsSync, chmodSync } from "fs";
691
+ import { join } from "path";
692
+ function generateKeyPair() {
693
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
694
+ publicKeyEncoding: { type: "spki", format: "der" },
695
+ privateKeyEncoding: { type: "pkcs8", format: "der" }
696
+ });
697
+ return {
698
+ publicKey: Buffer.from(publicKey),
699
+ privateKey: Buffer.from(privateKey)
700
+ };
701
+ }
702
+ function saveKeyPair(configDir, keys) {
703
+ const privatePath = join(configDir, "private.key");
704
+ const publicPath = join(configDir, "public.key");
705
+ writeFileSync(privatePath, keys.privateKey);
706
+ chmodSync(privatePath, 384);
707
+ writeFileSync(publicPath, keys.publicKey);
708
+ }
709
+ function loadKeyPair(configDir) {
710
+ const privatePath = join(configDir, "private.key");
711
+ const publicPath = join(configDir, "public.key");
712
+ if (!existsSync(privatePath) || !existsSync(publicPath)) {
713
+ throw new AgentBnBError("Keypair not found. Run `agentbnb init` to generate one.", "KEYPAIR_NOT_FOUND");
714
+ }
715
+ return {
716
+ publicKey: readFileSync(publicPath),
717
+ privateKey: readFileSync(privatePath)
718
+ };
719
+ }
720
+ function canonicalJson(data) {
721
+ return JSON.stringify(data, Object.keys(data).sort());
722
+ }
723
+ function signEscrowReceipt(data, privateKey) {
724
+ const message = Buffer.from(canonicalJson(data), "utf-8");
725
+ const keyObject = createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
726
+ const signature = sign(null, message, keyObject);
727
+ return signature.toString("base64url");
728
+ }
729
+ function verifyEscrowReceipt(data, signature, publicKey) {
730
+ try {
731
+ const message = Buffer.from(canonicalJson(data), "utf-8");
732
+ const keyObject = createPublicKey({ key: publicKey, format: "der", type: "spki" });
733
+ const sigBuffer = Buffer.from(signature, "base64url");
734
+ return verify(null, message, keyObject, sigBuffer);
735
+ } catch {
736
+ return false;
737
+ }
738
+ }
739
+
740
+ // src/credit/settlement.ts
741
+ function settleProviderEarning(providerDb, providerOwner, receipt) {
742
+ recordEarning(
743
+ providerDb,
744
+ providerOwner,
745
+ receipt.amount,
746
+ receipt.card_id,
747
+ receipt.nonce
748
+ );
749
+ return { settled: true };
750
+ }
751
+ function settleRequesterEscrow(requesterDb, escrowId) {
752
+ confirmEscrowDebit(requesterDb, escrowId);
753
+ }
754
+ function releaseRequesterEscrow(requesterDb, escrowId) {
755
+ releaseEscrow(requesterDb, escrowId);
756
+ }
757
+
758
+ // src/gateway/execute.ts
759
+ async function executeCapabilityRequest(opts) {
760
+ const {
761
+ registryDb,
762
+ creditDb,
763
+ cardId,
764
+ skillId,
765
+ params,
766
+ requester,
767
+ escrowReceipt: receipt,
768
+ skillExecutor,
769
+ handlerUrl,
770
+ timeoutMs = 3e4
771
+ } = opts;
772
+ const card = getCard(registryDb, cardId);
773
+ if (!card) {
774
+ return { success: false, error: { code: -32602, message: `Card not found: ${cardId}` } };
775
+ }
776
+ let creditsNeeded;
777
+ let cardName;
778
+ let resolvedSkillId;
779
+ const rawCard = card;
780
+ if (Array.isArray(rawCard["skills"])) {
781
+ const v2card = card;
782
+ const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
783
+ if (!skill) {
784
+ return { success: false, error: { code: -32602, message: `Skill not found: ${skillId}` } };
785
+ }
786
+ creditsNeeded = skill.pricing.credits_per_call;
787
+ cardName = skill.name;
788
+ resolvedSkillId = skill.id;
789
+ } else {
790
+ creditsNeeded = card.pricing.credits_per_call;
791
+ cardName = card.name;
792
+ }
793
+ let escrowId = null;
794
+ let isRemoteEscrow = false;
795
+ if (receipt) {
796
+ const { signature, ...receiptData2 } = receipt;
797
+ const publicKeyBuf = Buffer.from(receipt.requester_public_key, "hex");
798
+ const valid = verifyEscrowReceipt(receiptData2, signature, publicKeyBuf);
799
+ if (!valid) {
800
+ return { success: false, error: { code: -32603, message: "Invalid escrow receipt signature" } };
801
+ }
802
+ if (receipt.amount < creditsNeeded) {
803
+ return { success: false, error: { code: -32603, message: "Insufficient escrow amount" } };
804
+ }
805
+ const receiptAge = Date.now() - new Date(receipt.timestamp).getTime();
806
+ if (receiptAge > 5 * 60 * 1e3) {
807
+ return { success: false, error: { code: -32603, message: "Escrow receipt expired" } };
808
+ }
809
+ isRemoteEscrow = true;
810
+ } else {
811
+ try {
812
+ const balance = getBalance(creditDb, requester);
813
+ if (balance < creditsNeeded) {
814
+ return { success: false, error: { code: -32603, message: "Insufficient credits" } };
815
+ }
816
+ escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
817
+ } catch (err) {
818
+ const msg = err instanceof AgentBnBError ? err.message : "Failed to hold escrow";
819
+ return { success: false, error: { code: -32603, message: msg } };
820
+ }
821
+ }
822
+ const startMs = Date.now();
823
+ const receiptData = isRemoteEscrow ? { receipt_released: true } : void 0;
824
+ const handleFailure = (status, latencyMs, message) => {
825
+ if (!isRemoteEscrow && escrowId) releaseEscrow(creditDb, escrowId);
826
+ updateReputation(registryDb, cardId, false, latencyMs);
827
+ try {
828
+ insertRequestLog(registryDb, {
829
+ id: randomUUID3(),
830
+ card_id: cardId,
831
+ card_name: cardName,
832
+ skill_id: resolvedSkillId,
833
+ requester,
834
+ status,
835
+ latency_ms: latencyMs,
836
+ credits_charged: 0,
837
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
838
+ });
839
+ } catch {
840
+ }
841
+ return {
842
+ success: false,
843
+ error: { code: -32603, message, ...receiptData ? { data: receiptData } : {} }
844
+ };
845
+ };
846
+ const handleSuccess = (result, latencyMs) => {
847
+ if (isRemoteEscrow && receipt) {
848
+ settleProviderEarning(creditDb, card.owner, receipt);
849
+ } else if (escrowId) {
850
+ settleEscrow(creditDb, escrowId, card.owner);
851
+ }
852
+ updateReputation(registryDb, cardId, true, latencyMs);
853
+ try {
854
+ insertRequestLog(registryDb, {
855
+ id: randomUUID3(),
856
+ card_id: cardId,
857
+ card_name: cardName,
858
+ skill_id: resolvedSkillId,
859
+ requester,
860
+ status: "success",
861
+ latency_ms: latencyMs,
862
+ credits_charged: creditsNeeded,
863
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
864
+ });
865
+ } catch {
866
+ }
867
+ const successResult = isRemoteEscrow ? {
868
+ ...typeof result === "object" && result !== null ? result : { data: result },
869
+ receipt_settled: true,
870
+ receipt_nonce: receipt.nonce
871
+ } : result;
872
+ return { success: true, result: successResult };
873
+ };
874
+ if (skillExecutor) {
875
+ const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
876
+ try {
877
+ const execResult = await skillExecutor.execute(targetSkillId, params);
878
+ if (!execResult.success) {
879
+ return handleFailure("failure", execResult.latency_ms, execResult.error ?? "Execution failed");
880
+ }
881
+ return handleSuccess(execResult.result, execResult.latency_ms);
882
+ } catch (err) {
883
+ const message = err instanceof Error ? err.message : "Execution error";
884
+ return handleFailure("failure", Date.now() - startMs, message);
885
+ }
886
+ }
887
+ if (!handlerUrl) {
888
+ return handleFailure("failure", Date.now() - startMs, "No skill executor or handler URL configured");
889
+ }
890
+ const controller = new AbortController();
891
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
892
+ try {
893
+ const response = await fetch(handlerUrl, {
894
+ method: "POST",
895
+ headers: { "Content-Type": "application/json" },
896
+ body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
897
+ signal: controller.signal
898
+ });
899
+ clearTimeout(timer);
900
+ if (!response.ok) {
901
+ return handleFailure("failure", Date.now() - startMs, `Handler returned ${response.status}`);
902
+ }
903
+ const result = await response.json();
904
+ return handleSuccess(result, Date.now() - startMs);
905
+ } catch (err) {
906
+ clearTimeout(timer);
907
+ const isTimeout = err instanceof Error && err.name === "AbortError";
908
+ return handleFailure(
909
+ isTimeout ? "timeout" : "failure",
910
+ Date.now() - startMs,
911
+ isTimeout ? "Execution timeout" : "Handler error"
912
+ );
913
+ }
914
+ }
621
915
 
622
916
  // src/gateway/server.ts
623
917
  var VERSION = "0.0.1";
@@ -683,212 +977,2167 @@ function createGatewayServer(opts) {
683
977
  error: { code: -32602, message: "Invalid params: card_id required" }
684
978
  });
685
979
  }
686
- const card = getCard(registryDb, cardId);
687
- if (!card) {
688
- return reply.send({
689
- jsonrpc: "2.0",
690
- id,
691
- error: { code: -32602, message: `Card not found: ${cardId}` }
692
- });
693
- }
694
980
  const requester = params.requester ?? "unknown";
695
- let creditsNeeded;
696
- let cardName;
697
- let resolvedSkillId;
698
- const rawCard = card;
699
- if (Array.isArray(rawCard["skills"])) {
700
- const v2card = card;
701
- const skill = skillId ? v2card.skills.find((s) => s.id === skillId) : v2card.skills[0];
702
- if (!skill) {
703
- return reply.send({
704
- jsonrpc: "2.0",
705
- id,
706
- error: { code: -32602, message: `Skill not found: ${skillId}` }
707
- });
708
- }
709
- creditsNeeded = skill.pricing.credits_per_call;
710
- cardName = skill.name;
711
- resolvedSkillId = skill.id;
981
+ const receipt = params.escrow_receipt;
982
+ const result = await executeCapabilityRequest({
983
+ registryDb,
984
+ creditDb,
985
+ cardId,
986
+ skillId,
987
+ params,
988
+ requester,
989
+ escrowReceipt: receipt,
990
+ skillExecutor,
991
+ handlerUrl,
992
+ timeoutMs
993
+ });
994
+ if (result.success) {
995
+ return reply.send({ jsonrpc: "2.0", id, result: result.result });
712
996
  } else {
713
- creditsNeeded = card.pricing.credits_per_call;
714
- cardName = card.name;
997
+ return reply.send({ jsonrpc: "2.0", id, error: result.error });
998
+ }
999
+ });
1000
+ return fastify;
1001
+ }
1002
+
1003
+ // src/skills/executor.ts
1004
+ var SkillExecutor = class {
1005
+ skillMap;
1006
+ modeMap;
1007
+ /**
1008
+ * @param configs - Parsed SkillConfig array (from parseSkillsFile).
1009
+ * @param modes - Map from skill type string to its executor implementation.
1010
+ */
1011
+ constructor(configs, modes) {
1012
+ this.skillMap = new Map(configs.map((c) => [c.id, c]));
1013
+ this.modeMap = modes;
1014
+ }
1015
+ /**
1016
+ * Execute a skill by ID with the given input parameters.
1017
+ *
1018
+ * Dispatch order:
1019
+ * 1. Look up skill config by skillId.
1020
+ * 2. Find executor mode by config.type.
1021
+ * 3. Invoke mode.execute(), wrap with latency timing.
1022
+ * 4. Catch any thrown errors and return as ExecutionResult with success:false.
1023
+ *
1024
+ * @param skillId - The ID of the skill to execute.
1025
+ * @param params - Input parameters for the skill.
1026
+ * @returns ExecutionResult including success, result/error, and latency_ms.
1027
+ */
1028
+ async execute(skillId, params) {
1029
+ const startTime = Date.now();
1030
+ const config = this.skillMap.get(skillId);
1031
+ if (!config) {
1032
+ return {
1033
+ success: false,
1034
+ error: `Skill not found: "${skillId}"`,
1035
+ latency_ms: Date.now() - startTime
1036
+ };
1037
+ }
1038
+ const mode = this.modeMap.get(config.type);
1039
+ if (!mode) {
1040
+ return {
1041
+ success: false,
1042
+ error: `No executor registered for skill type "${config.type}" (skill: "${skillId}")`,
1043
+ latency_ms: Date.now() - startTime
1044
+ };
715
1045
  }
716
- let escrowId;
717
1046
  try {
718
- const balance = getBalance(creditDb, requester);
719
- if (balance < creditsNeeded) {
720
- return reply.send({
721
- jsonrpc: "2.0",
722
- id,
723
- error: { code: -32603, message: "Insufficient credits" }
724
- });
725
- }
726
- escrowId = holdEscrow(creditDb, requester, creditsNeeded, cardId);
1047
+ const modeResult = await mode.execute(config, params);
1048
+ return {
1049
+ ...modeResult,
1050
+ latency_ms: Date.now() - startTime
1051
+ };
727
1052
  } 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
- });
1053
+ const message = err instanceof Error ? err.message : String(err);
1054
+ return {
1055
+ success: false,
1056
+ error: message,
1057
+ latency_ms: Date.now() - startTime
1058
+ };
1059
+ }
1060
+ }
1061
+ /**
1062
+ * Returns the IDs of all registered skills.
1063
+ *
1064
+ * @returns Array of skill ID strings.
1065
+ */
1066
+ listSkills() {
1067
+ return Array.from(this.skillMap.keys());
1068
+ }
1069
+ /**
1070
+ * Returns the SkillConfig for a given skill ID, or undefined if not found.
1071
+ *
1072
+ * @param skillId - The skill ID to look up.
1073
+ * @returns The SkillConfig or undefined.
1074
+ */
1075
+ getSkillConfig(skillId) {
1076
+ return this.skillMap.get(skillId);
1077
+ }
1078
+ };
1079
+ function createSkillExecutor(configs, modes) {
1080
+ return new SkillExecutor(configs, modes);
1081
+ }
1082
+
1083
+ // src/skills/skill-config.ts
1084
+ import { z as z2 } from "zod";
1085
+ import yaml from "js-yaml";
1086
+ var PricingSchema = z2.object({
1087
+ credits_per_call: z2.number().nonnegative(),
1088
+ credits_per_minute: z2.number().nonnegative().optional(),
1089
+ free_tier: z2.number().nonnegative().optional()
1090
+ });
1091
+ var ApiAuthSchema = z2.discriminatedUnion("type", [
1092
+ z2.object({
1093
+ type: z2.literal("bearer"),
1094
+ token: z2.string()
1095
+ }),
1096
+ z2.object({
1097
+ type: z2.literal("apikey"),
1098
+ header: z2.string().default("X-API-Key"),
1099
+ key: z2.string()
1100
+ }),
1101
+ z2.object({
1102
+ type: z2.literal("basic"),
1103
+ username: z2.string(),
1104
+ password: z2.string()
1105
+ })
1106
+ ]);
1107
+ var ApiSkillConfigSchema = z2.object({
1108
+ id: z2.string().min(1),
1109
+ type: z2.literal("api"),
1110
+ name: z2.string().min(1),
1111
+ endpoint: z2.string().min(1),
1112
+ method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
1113
+ auth: ApiAuthSchema.optional(),
1114
+ input_mapping: z2.record(z2.string()).default({}),
1115
+ output_mapping: z2.record(z2.string()).default({}),
1116
+ pricing: PricingSchema,
1117
+ timeout_ms: z2.number().positive().default(3e4),
1118
+ retries: z2.number().nonnegative().int().default(0),
1119
+ provider: z2.string().optional()
1120
+ });
1121
+ var PipelineStepSchema = z2.union([
1122
+ z2.object({
1123
+ skill_id: z2.string().min(1),
1124
+ input_mapping: z2.record(z2.string()).default({})
1125
+ }),
1126
+ z2.object({
1127
+ command: z2.string().min(1),
1128
+ input_mapping: z2.record(z2.string()).default({})
1129
+ })
1130
+ ]);
1131
+ var PipelineSkillConfigSchema = z2.object({
1132
+ id: z2.string().min(1),
1133
+ type: z2.literal("pipeline"),
1134
+ name: z2.string().min(1),
1135
+ steps: z2.array(PipelineStepSchema).min(1),
1136
+ pricing: PricingSchema,
1137
+ timeout_ms: z2.number().positive().optional()
1138
+ });
1139
+ var OpenClawSkillConfigSchema = z2.object({
1140
+ id: z2.string().min(1),
1141
+ type: z2.literal("openclaw"),
1142
+ name: z2.string().min(1),
1143
+ agent_name: z2.string().min(1),
1144
+ channel: z2.enum(["telegram", "webhook", "process"]),
1145
+ pricing: PricingSchema,
1146
+ timeout_ms: z2.number().positive().optional()
1147
+ });
1148
+ var CommandSkillConfigSchema = z2.object({
1149
+ id: z2.string().min(1),
1150
+ type: z2.literal("command"),
1151
+ name: z2.string().min(1),
1152
+ command: z2.string().min(1),
1153
+ output_type: z2.enum(["json", "text", "file"]),
1154
+ allowed_commands: z2.array(z2.string()).optional(),
1155
+ working_dir: z2.string().optional(),
1156
+ timeout_ms: z2.number().positive().default(3e4),
1157
+ pricing: PricingSchema
1158
+ });
1159
+ var ConductorSkillConfigSchema = z2.object({
1160
+ id: z2.string().min(1),
1161
+ type: z2.literal("conductor"),
1162
+ name: z2.string().min(1),
1163
+ conductor_skill: z2.enum(["orchestrate", "plan"]),
1164
+ pricing: PricingSchema,
1165
+ timeout_ms: z2.number().positive().optional()
1166
+ });
1167
+ var SkillConfigSchema = z2.discriminatedUnion("type", [
1168
+ ApiSkillConfigSchema,
1169
+ PipelineSkillConfigSchema,
1170
+ OpenClawSkillConfigSchema,
1171
+ CommandSkillConfigSchema,
1172
+ ConductorSkillConfigSchema
1173
+ ]);
1174
+ var SkillsFileSchema = z2.object({
1175
+ skills: z2.array(SkillConfigSchema)
1176
+ });
1177
+ function expandEnvVars(value) {
1178
+ return value.replace(/\$\{([^}]+)\}/g, (_match, varName) => {
1179
+ if (/[.a-z]/.test(varName)) {
1180
+ return _match;
1181
+ }
1182
+ const envValue = process.env[varName];
1183
+ if (envValue === void 0) {
1184
+ throw new Error(`Environment variable "${varName}" is not defined`);
1185
+ }
1186
+ return envValue;
1187
+ });
1188
+ }
1189
+ function expandEnvVarsDeep(value) {
1190
+ if (typeof value === "string") {
1191
+ return expandEnvVars(value);
1192
+ }
1193
+ if (Array.isArray(value)) {
1194
+ return value.map(expandEnvVarsDeep);
1195
+ }
1196
+ if (value !== null && typeof value === "object") {
1197
+ const result = {};
1198
+ for (const [k, v] of Object.entries(value)) {
1199
+ result[k] = expandEnvVarsDeep(v);
1200
+ }
1201
+ return result;
1202
+ }
1203
+ return value;
1204
+ }
1205
+ function parseSkillsFile(yamlContent) {
1206
+ const raw = yaml.load(yamlContent);
1207
+ const expanded = expandEnvVarsDeep(raw);
1208
+ const result = SkillsFileSchema.parse(expanded);
1209
+ return result.skills;
1210
+ }
1211
+
1212
+ // src/skills/api-executor.ts
1213
+ function parseMappingTarget(mapping) {
1214
+ const dotIndex = mapping.indexOf(".");
1215
+ if (dotIndex < 0) {
1216
+ throw new Error(`Invalid input_mapping format: "${mapping}" (expected "target.key")`);
1217
+ }
1218
+ const target = mapping.slice(0, dotIndex);
1219
+ const key = mapping.slice(dotIndex + 1);
1220
+ if (!["body", "query", "path", "header"].includes(target)) {
1221
+ throw new Error(
1222
+ `Invalid mapping target "${target}" in "${mapping}" (must be body|query|path|header)`
1223
+ );
1224
+ }
1225
+ return { target, key };
1226
+ }
1227
+ function extractByPath(obj, dotPath) {
1228
+ const normalizedPath = dotPath.startsWith("response.") ? dotPath.slice("response.".length) : dotPath;
1229
+ const parts = normalizedPath.split(".");
1230
+ let current = obj;
1231
+ for (const part of parts) {
1232
+ if (current === null || typeof current !== "object") {
1233
+ return void 0;
1234
+ }
1235
+ current = current[part];
1236
+ }
1237
+ return current;
1238
+ }
1239
+ function buildAuthHeaders(auth) {
1240
+ if (!auth) return {};
1241
+ switch (auth.type) {
1242
+ case "bearer":
1243
+ return { Authorization: `Bearer ${auth.token}` };
1244
+ case "apikey":
1245
+ return { [auth.header]: auth.key };
1246
+ case "basic": {
1247
+ const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
1248
+ return { Authorization: `Basic ${encoded}` };
1249
+ }
1250
+ }
1251
+ }
1252
+ function applyInputMapping(params, mapping) {
1253
+ const body = {};
1254
+ const query = {};
1255
+ const pathParams = {};
1256
+ const headers = {};
1257
+ for (const [paramName, mappingValue] of Object.entries(mapping)) {
1258
+ const value = params[paramName];
1259
+ if (value === void 0) continue;
1260
+ const { target, key } = parseMappingTarget(mappingValue);
1261
+ switch (target) {
1262
+ case "body":
1263
+ body[key] = value;
1264
+ break;
1265
+ case "query":
1266
+ query[key] = String(value);
1267
+ break;
1268
+ case "path":
1269
+ pathParams[key] = String(value);
1270
+ break;
1271
+ case "header":
1272
+ headers[key] = String(value).replace(/[\r\n]/g, "");
1273
+ break;
1274
+ }
1275
+ }
1276
+ return { body, query, pathParams, headers };
1277
+ }
1278
+ function sleep(ms) {
1279
+ return new Promise((resolve) => setTimeout(resolve, ms));
1280
+ }
1281
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 503]);
1282
+ var ApiExecutor = class {
1283
+ /**
1284
+ * Execute an API call described by the given skill config.
1285
+ *
1286
+ * @param config - The validated SkillConfig (must be ApiSkillConfig).
1287
+ * @param params - Input parameters to map to the HTTP request.
1288
+ * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor).
1289
+ */
1290
+ async execute(config, params) {
1291
+ const apiConfig = config;
1292
+ const { body, query, pathParams, headers: mappedHeaders } = applyInputMapping(
1293
+ params,
1294
+ apiConfig.input_mapping
1295
+ );
1296
+ let url = apiConfig.endpoint;
1297
+ for (const [key, value] of Object.entries(pathParams)) {
1298
+ url = url.replace(`{${key}}`, encodeURIComponent(value));
1299
+ }
1300
+ if (Object.keys(query).length > 0) {
1301
+ const qs = new URLSearchParams(query).toString();
1302
+ url = `${url}?${qs}`;
1303
+ }
1304
+ const authHeaders = buildAuthHeaders(apiConfig.auth);
1305
+ const requestHeaders = {
1306
+ ...authHeaders,
1307
+ ...mappedHeaders
1308
+ };
1309
+ const hasBody = ["POST", "PUT"].includes(apiConfig.method);
1310
+ if (hasBody) {
1311
+ requestHeaders["Content-Type"] = "application/json";
734
1312
  }
735
- const startMs = Date.now();
736
- if (skillExecutor) {
737
- const targetSkillId = resolvedSkillId ?? skillId ?? cardId;
738
- let execResult;
1313
+ const maxAttempts = (apiConfig.retries ?? 0) + 1;
1314
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1315
+ if (attempt > 0) {
1316
+ await sleep(100 * Math.pow(2, attempt - 1));
1317
+ }
1318
+ const controller = new AbortController();
1319
+ const timeoutId = setTimeout(
1320
+ () => controller.abort(),
1321
+ apiConfig.timeout_ms ?? 3e4
1322
+ );
1323
+ let response;
739
1324
  try {
740
- execResult = await skillExecutor.execute(targetSkillId, params);
741
- } catch (err) {
742
- releaseEscrow(creditDb, escrowId);
743
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
744
- try {
745
- insertRequestLog(registryDb, {
746
- id: randomUUID3(),
747
- card_id: cardId,
748
- card_name: cardName,
749
- skill_id: resolvedSkillId,
750
- requester,
751
- status: "failure",
752
- latency_ms: Date.now() - startMs,
753
- credits_charged: 0,
754
- created_at: (/* @__PURE__ */ new Date()).toISOString()
755
- });
756
- } catch {
757
- }
758
- const message = err instanceof Error ? err.message : "Execution error";
759
- return reply.send({
760
- jsonrpc: "2.0",
761
- id,
762
- error: { code: -32603, message }
1325
+ response = await fetch(url, {
1326
+ method: apiConfig.method,
1327
+ headers: requestHeaders,
1328
+ body: hasBody ? JSON.stringify(body) : void 0,
1329
+ signal: controller.signal
763
1330
  });
764
- }
765
- if (!execResult.success) {
766
- releaseEscrow(creditDb, escrowId);
767
- updateReputation(registryDb, cardId, false, execResult.latency_ms);
768
- try {
769
- insertRequestLog(registryDb, {
770
- id: randomUUID3(),
771
- card_id: cardId,
772
- card_name: cardName,
773
- skill_id: resolvedSkillId,
774
- requester,
775
- status: "failure",
776
- latency_ms: execResult.latency_ms,
777
- credits_charged: 0,
778
- created_at: (/* @__PURE__ */ new Date()).toISOString()
779
- });
780
- } catch {
1331
+ } catch (err) {
1332
+ clearTimeout(timeoutId);
1333
+ const message = err instanceof Error ? err.message : String(err);
1334
+ const isAbort = err instanceof Error && err.name === "AbortError" || message.toLowerCase().includes("abort");
1335
+ if (isAbort) {
1336
+ return { success: false, error: `Request timeout after ${apiConfig.timeout_ms}ms` };
781
1337
  }
782
- return reply.send({
783
- jsonrpc: "2.0",
784
- id,
785
- error: { code: -32603, message: execResult.error ?? "Execution failed" }
786
- });
1338
+ return { success: false, error: message };
1339
+ } finally {
1340
+ clearTimeout(timeoutId);
787
1341
  }
788
- settleEscrow(creditDb, escrowId, card.owner);
789
- updateReputation(registryDb, cardId, true, execResult.latency_ms);
790
- try {
791
- insertRequestLog(registryDb, {
792
- id: randomUUID3(),
793
- card_id: cardId,
794
- card_name: cardName,
795
- skill_id: resolvedSkillId,
796
- requester,
797
- status: "success",
798
- latency_ms: execResult.latency_ms,
799
- credits_charged: creditsNeeded,
800
- created_at: (/* @__PURE__ */ new Date()).toISOString()
801
- });
802
- } catch {
1342
+ if (!response.ok && RETRYABLE_STATUSES.has(response.status) && attempt < maxAttempts - 1) {
1343
+ continue;
803
1344
  }
804
- return reply.send({ jsonrpc: "2.0", id, result: execResult.result });
805
- }
806
- const controller = new AbortController();
807
- const timer = setTimeout(() => controller.abort(), timeoutMs);
808
- try {
809
- const response = await fetch(handlerUrl, {
810
- method: "POST",
811
- headers: { "Content-Type": "application/json" },
812
- body: JSON.stringify({ card_id: cardId, skill_id: resolvedSkillId, params }),
813
- signal: controller.signal
814
- });
815
- clearTimeout(timer);
816
1345
  if (!response.ok) {
817
- releaseEscrow(creditDb, escrowId);
818
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
819
- try {
820
- insertRequestLog(registryDb, {
821
- id: randomUUID3(),
822
- card_id: cardId,
823
- card_name: cardName,
824
- skill_id: resolvedSkillId,
825
- requester,
826
- status: "failure",
827
- latency_ms: Date.now() - startMs,
828
- credits_charged: 0,
829
- created_at: (/* @__PURE__ */ new Date()).toISOString()
830
- });
831
- } catch {
832
- }
833
- return reply.send({
834
- jsonrpc: "2.0",
835
- id,
836
- error: { code: -32603, message: `Handler returned ${response.status}` }
837
- });
1346
+ return {
1347
+ success: false,
1348
+ error: `HTTP ${response.status} from ${apiConfig.endpoint}`
1349
+ };
838
1350
  }
839
- const result = await response.json();
840
- settleEscrow(creditDb, escrowId, card.owner);
841
- updateReputation(registryDb, cardId, true, Date.now() - startMs);
842
- try {
843
- insertRequestLog(registryDb, {
844
- id: randomUUID3(),
845
- card_id: cardId,
846
- card_name: cardName,
847
- skill_id: resolvedSkillId,
848
- requester,
849
- status: "success",
850
- latency_ms: Date.now() - startMs,
851
- credits_charged: creditsNeeded,
852
- created_at: (/* @__PURE__ */ new Date()).toISOString()
853
- });
854
- } catch {
1351
+ const responseBody = await response.json();
1352
+ const outputMapping = apiConfig.output_mapping;
1353
+ if (Object.keys(outputMapping).length === 0) {
1354
+ return { success: true, result: responseBody };
855
1355
  }
856
- return reply.send({ jsonrpc: "2.0", id, result });
857
- } catch (err) {
858
- clearTimeout(timer);
859
- releaseEscrow(creditDb, escrowId);
860
- updateReputation(registryDb, cardId, false, Date.now() - startMs);
861
- const isTimeout = err instanceof Error && err.name === "AbortError";
862
- try {
863
- insertRequestLog(registryDb, {
864
- id: randomUUID3(),
865
- card_id: cardId,
866
- card_name: cardName,
867
- skill_id: resolvedSkillId,
868
- requester,
869
- status: isTimeout ? "timeout" : "failure",
870
- latency_ms: Date.now() - startMs,
871
- credits_charged: 0,
872
- created_at: (/* @__PURE__ */ new Date()).toISOString()
873
- });
874
- } catch {
1356
+ const mappedOutput = {};
1357
+ for (const [outputKey, path] of Object.entries(outputMapping)) {
1358
+ mappedOutput[outputKey] = extractByPath(responseBody, path);
875
1359
  }
876
- return reply.send({
877
- jsonrpc: "2.0",
878
- id,
879
- error: { code: -32603, message: isTimeout ? "Execution timeout" : "Handler error" }
880
- });
1360
+ return { success: true, result: mappedOutput };
1361
+ }
1362
+ return { success: false, error: "Unexpected: retry loop exhausted" };
1363
+ }
1364
+ };
1365
+
1366
+ // src/skills/pipeline-executor.ts
1367
+ import { execFile } from "child_process";
1368
+ import { promisify } from "util";
1369
+
1370
+ // src/utils/interpolation.ts
1371
+ function resolvePath(obj, path) {
1372
+ const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter((s) => s.length > 0);
1373
+ let current = obj;
1374
+ for (const segment of segments) {
1375
+ if (current === null || current === void 0) {
1376
+ return void 0;
1377
+ }
1378
+ if (typeof current !== "object") {
1379
+ return void 0;
1380
+ }
1381
+ current = current[segment];
1382
+ }
1383
+ return current;
1384
+ }
1385
+ function interpolate(template, context) {
1386
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
1387
+ const resolved = resolvePath(context, expression.trim());
1388
+ if (resolved === void 0 || resolved === null) {
1389
+ return "";
1390
+ }
1391
+ if (typeof resolved === "object") {
1392
+ return JSON.stringify(resolved);
881
1393
  }
1394
+ return String(resolved);
1395
+ });
1396
+ }
1397
+ function interpolateObject(obj, context) {
1398
+ const result = {};
1399
+ for (const [key, value] of Object.entries(obj)) {
1400
+ result[key] = interpolateValue(value, context);
1401
+ }
1402
+ return result;
1403
+ }
1404
+ function interpolateValue(value, context) {
1405
+ if (typeof value === "string") {
1406
+ return interpolate(value, context);
1407
+ }
1408
+ if (Array.isArray(value)) {
1409
+ return value.map((item) => interpolateValue(item, context));
1410
+ }
1411
+ if (value !== null && typeof value === "object") {
1412
+ return interpolateObject(value, context);
1413
+ }
1414
+ return value;
1415
+ }
1416
+
1417
+ // src/skills/pipeline-executor.ts
1418
+ var execFileAsync = promisify(execFile);
1419
+ function shellEscape(value) {
1420
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1421
+ }
1422
+ function safeInterpolateCommand(template, context) {
1423
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
1424
+ const parts = expr.split(".");
1425
+ let current = context;
1426
+ for (const part of parts) {
1427
+ if (current === null || typeof current !== "object") return "";
1428
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
1429
+ if (bracketMatch) {
1430
+ current = current[bracketMatch[1]];
1431
+ if (Array.isArray(current)) {
1432
+ current = current[parseInt(bracketMatch[2], 10)];
1433
+ } else {
1434
+ return "";
1435
+ }
1436
+ } else {
1437
+ current = current[part];
1438
+ }
1439
+ }
1440
+ if (current === void 0 || current === null) return "";
1441
+ return shellEscape(String(current));
882
1442
  });
883
- return fastify;
884
1443
  }
1444
+ var PipelineExecutor = class {
1445
+ /**
1446
+ * @param skillExecutor - The parent SkillExecutor used to dispatch sub-skill calls.
1447
+ */
1448
+ constructor(skillExecutor) {
1449
+ this.skillExecutor = skillExecutor;
1450
+ }
1451
+ /**
1452
+ * Execute a pipeline skill config sequentially.
1453
+ *
1454
+ * Algorithm:
1455
+ * 1. Initialise context: { params, steps: [], prev: { result: null } }
1456
+ * 2. For each step:
1457
+ * a. Resolve input_mapping keys against current context via interpolateObject.
1458
+ * b. If step has `skill_id`: dispatch via skillExecutor.execute(). On failure → stop.
1459
+ * c. If step has `command`: interpolate command string, run via exec(). On non-zero exit → stop.
1460
+ * d. Store step result in context.steps[i] and context.prev.
1461
+ * 3. Return success with final step result (or null for empty pipeline).
1462
+ *
1463
+ * @param config - The PipelineSkillConfig for this skill.
1464
+ * @param params - Input parameters from the caller.
1465
+ * @returns Partial ExecutionResult (without latency_ms — added by SkillExecutor wrapper).
1466
+ */
1467
+ async execute(config, params) {
1468
+ const pipelineConfig = config;
1469
+ const steps = pipelineConfig.steps ?? [];
1470
+ if (steps.length === 0) {
1471
+ return { success: true, result: null };
1472
+ }
1473
+ const context = {
1474
+ params,
1475
+ steps: [],
1476
+ prev: { result: null }
1477
+ };
1478
+ for (let i = 0; i < steps.length; i++) {
1479
+ const step = steps[i];
1480
+ if (step === void 0) {
1481
+ return {
1482
+ success: false,
1483
+ error: `Step ${i} failed: step definition is undefined`
1484
+ };
1485
+ }
1486
+ const resolvedInputs = interpolateObject(
1487
+ step.input_mapping,
1488
+ context
1489
+ );
1490
+ let stepResult;
1491
+ if ("skill_id" in step && step.skill_id) {
1492
+ const subResult = await this.skillExecutor.execute(
1493
+ step.skill_id,
1494
+ resolvedInputs
1495
+ );
1496
+ if (!subResult.success) {
1497
+ return {
1498
+ success: false,
1499
+ error: `Step ${i} failed: ${subResult.error ?? "unknown error"}`
1500
+ };
1501
+ }
1502
+ stepResult = subResult.result;
1503
+ } else if ("command" in step && step.command) {
1504
+ const interpolatedCommand = safeInterpolateCommand(
1505
+ step.command,
1506
+ context
1507
+ );
1508
+ try {
1509
+ const { stdout } = await execFileAsync("/bin/sh", ["-c", interpolatedCommand], { timeout: 3e4 });
1510
+ stepResult = stdout.trim();
1511
+ } catch (err) {
1512
+ const message = err instanceof Error ? err.message : String(err);
1513
+ return {
1514
+ success: false,
1515
+ error: `Step ${i} failed: ${message}`
1516
+ };
1517
+ }
1518
+ } else {
1519
+ return {
1520
+ success: false,
1521
+ error: `Step ${i} failed: step must have either "skill_id" or "command"`
1522
+ };
1523
+ }
1524
+ context.steps.push({ result: stepResult });
1525
+ context.prev = { result: stepResult };
1526
+ }
1527
+ const lastStep = context.steps[context.steps.length - 1];
1528
+ return {
1529
+ success: true,
1530
+ result: lastStep !== void 0 ? lastStep.result : null
1531
+ };
1532
+ }
1533
+ };
1534
+
1535
+ // src/skills/openclaw-bridge.ts
1536
+ import { execFileSync } from "child_process";
1537
+ var DEFAULT_BASE_URL = "http://localhost:3000";
1538
+ var DEFAULT_TIMEOUT_MS = 6e4;
1539
+ function buildPayload(config, params) {
1540
+ return {
1541
+ task: config.name,
1542
+ params,
1543
+ source: "agentbnb",
1544
+ skill_id: config.id
1545
+ };
1546
+ }
1547
+ async function executeWebhook(config, payload) {
1548
+ const baseUrl = process.env["OPENCLAW_BASE_URL"] ?? DEFAULT_BASE_URL;
1549
+ const url = `${baseUrl}/openclaw/${config.agent_name}/task`;
1550
+ const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
1551
+ const controller = new AbortController();
1552
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1553
+ try {
1554
+ const response = await fetch(url, {
1555
+ method: "POST",
1556
+ headers: { "Content-Type": "application/json" },
1557
+ body: JSON.stringify(payload),
1558
+ signal: controller.signal
1559
+ });
1560
+ if (!response.ok) {
1561
+ return {
1562
+ success: false,
1563
+ error: `Webhook returned HTTP ${response.status}: ${response.statusText}`
1564
+ };
1565
+ }
1566
+ const result = await response.json();
1567
+ return { success: true, result };
1568
+ } catch (err) {
1569
+ if (err instanceof Error && err.name === "AbortError") {
1570
+ return {
1571
+ success: false,
1572
+ error: `OpenClaw webhook timed out after ${timeoutMs}ms`
1573
+ };
1574
+ }
1575
+ const message = err instanceof Error ? err.message : String(err);
1576
+ return { success: false, error: message };
1577
+ } finally {
1578
+ clearTimeout(timer);
1579
+ }
1580
+ }
1581
+ function validateAgentName(name) {
1582
+ return /^[a-zA-Z0-9._-]+$/.test(name);
1583
+ }
1584
+ function executeProcess(config, payload) {
1585
+ const timeoutMs = config.timeout_ms ?? DEFAULT_TIMEOUT_MS;
1586
+ if (!validateAgentName(config.agent_name)) {
1587
+ return {
1588
+ success: false,
1589
+ error: `Invalid agent name: "${config.agent_name}" (only alphanumeric, hyphens, underscores, dots allowed)`
1590
+ };
1591
+ }
1592
+ const inputJson = JSON.stringify(payload);
1593
+ try {
1594
+ const stdout = execFileSync("openclaw", ["run", config.agent_name, "--input", inputJson], {
1595
+ timeout: timeoutMs
1596
+ });
1597
+ const text = stdout.toString().trim();
1598
+ const result = JSON.parse(text);
1599
+ return { success: true, result };
1600
+ } catch (err) {
1601
+ const message = err instanceof Error ? err.message : String(err);
1602
+ return { success: false, error: message };
1603
+ }
1604
+ }
1605
+ async function executeTelegram(config, payload) {
1606
+ const token = process.env["TELEGRAM_BOT_TOKEN"];
1607
+ if (!token) {
1608
+ return {
1609
+ success: false,
1610
+ error: "TELEGRAM_BOT_TOKEN environment variable is not set"
1611
+ };
1612
+ }
1613
+ const chatId = process.env["TELEGRAM_CHAT_ID"];
1614
+ if (!chatId) {
1615
+ return {
1616
+ success: false,
1617
+ error: "TELEGRAM_CHAT_ID environment variable is not set"
1618
+ };
1619
+ }
1620
+ const text = `[AgentBnB] Skill: ${config.name} (${config.id})
1621
+ Agent: ${config.agent_name}
1622
+ Params: ${JSON.stringify(payload.params ?? {})}`;
1623
+ const url = `https://api.telegram.org/bot${token}/sendMessage`;
1624
+ try {
1625
+ await fetch(url, {
1626
+ method: "POST",
1627
+ headers: { "Content-Type": "application/json" },
1628
+ body: JSON.stringify({ chat_id: chatId, text })
1629
+ });
1630
+ return {
1631
+ success: true,
1632
+ result: { sent: true, channel: "telegram" }
1633
+ };
1634
+ } catch (err) {
1635
+ const message = err instanceof Error ? err.message : String(err);
1636
+ return { success: false, error: message };
1637
+ }
1638
+ }
1639
+ var OpenClawBridge = class {
1640
+ /**
1641
+ * Execute a skill with the given config and input parameters.
1642
+ *
1643
+ * @param config - The SkillConfig for this skill (must be type 'openclaw').
1644
+ * @param params - Input parameters passed by the caller.
1645
+ * @returns Partial ExecutionResult without latency_ms.
1646
+ */
1647
+ async execute(config, params) {
1648
+ const ocConfig = config;
1649
+ const payload = buildPayload(ocConfig, params);
1650
+ switch (ocConfig.channel) {
1651
+ case "webhook":
1652
+ return executeWebhook(ocConfig, payload);
1653
+ case "process":
1654
+ return executeProcess(ocConfig, payload);
1655
+ case "telegram":
1656
+ return executeTelegram(ocConfig, payload);
1657
+ default: {
1658
+ const unknownChannel = ocConfig.channel;
1659
+ return {
1660
+ success: false,
1661
+ error: `Unknown OpenClaw channel: "${String(unknownChannel)}"`
1662
+ };
1663
+ }
1664
+ }
1665
+ }
1666
+ };
1667
+
1668
+ // src/skills/command-executor.ts
1669
+ import { execFile as execFile2 } from "child_process";
1670
+ function shellEscape2(value) {
1671
+ return "'" + value.replace(/'/g, "'\\''") + "'";
1672
+ }
1673
+ function safeInterpolateCommand2(template, context) {
1674
+ return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
1675
+ const parts = expr.split(".");
1676
+ let current = context;
1677
+ for (const part of parts) {
1678
+ if (current === null || typeof current !== "object") return "";
1679
+ const bracketMatch = part.match(/^(\w+)\[(\d+)\]$/);
1680
+ if (bracketMatch) {
1681
+ current = current[bracketMatch[1]];
1682
+ if (Array.isArray(current)) {
1683
+ current = current[parseInt(bracketMatch[2], 10)];
1684
+ } else {
1685
+ return "";
1686
+ }
1687
+ } else {
1688
+ current = current[part];
1689
+ }
1690
+ }
1691
+ if (current === void 0 || current === null) return "";
1692
+ return shellEscape2(String(current));
1693
+ });
1694
+ }
1695
+ function execFileAsync2(file, args, options) {
1696
+ return new Promise((resolve, reject) => {
1697
+ execFile2(file, args, options, (error, stdout, stderr) => {
1698
+ const stdoutStr = typeof stdout === "string" ? stdout : stdout.toString();
1699
+ const stderrStr = typeof stderr === "string" ? stderr : stderr.toString();
1700
+ if (error) {
1701
+ const enriched = Object.assign(error, { stderr: stderrStr });
1702
+ reject(enriched);
1703
+ } else {
1704
+ resolve({ stdout: stdoutStr, stderr: stderrStr });
1705
+ }
1706
+ });
1707
+ });
1708
+ }
1709
+ var CommandExecutor = class {
1710
+ /**
1711
+ * Execute a command skill with the provided parameters.
1712
+ *
1713
+ * Steps:
1714
+ * 1. Security check: base command must be in `allowed_commands` if set.
1715
+ * 2. Interpolate `config.command` using `{ params }` context.
1716
+ * 3. Run via `child_process.exec` with timeout and cwd.
1717
+ * 4. Parse stdout based on `output_type`: text | json | file.
1718
+ *
1719
+ * @param config - Validated CommandSkillConfig.
1720
+ * @param params - Input parameters passed by the caller.
1721
+ * @returns Partial ExecutionResult (without latency_ms).
1722
+ */
1723
+ async execute(config, params) {
1724
+ const cmdConfig = config;
1725
+ const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
1726
+ if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
1727
+ if (!cmdConfig.allowed_commands.includes(baseCommand)) {
1728
+ return {
1729
+ success: false,
1730
+ error: `Command not allowed: "${baseCommand}". Allowed: ${cmdConfig.allowed_commands.join(", ")}`
1731
+ };
1732
+ }
1733
+ }
1734
+ const interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
1735
+ const timeout = cmdConfig.timeout_ms ?? 3e4;
1736
+ const cwd = cmdConfig.working_dir ?? process.cwd();
1737
+ let stdout;
1738
+ try {
1739
+ const result = await execFileAsync2("/bin/sh", ["-c", interpolatedCommand], {
1740
+ timeout,
1741
+ cwd,
1742
+ maxBuffer: 10 * 1024 * 1024
1743
+ // 10 MB
1744
+ });
1745
+ stdout = result.stdout;
1746
+ } catch (err) {
1747
+ if (err instanceof Error) {
1748
+ const message = err.message;
1749
+ const stderrContent = err.stderr ?? "";
1750
+ if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
1751
+ return {
1752
+ success: false,
1753
+ error: `Command timed out after ${timeout}ms`
1754
+ };
1755
+ }
1756
+ return {
1757
+ success: false,
1758
+ error: stderrContent.trim() || message
1759
+ };
1760
+ }
1761
+ return {
1762
+ success: false,
1763
+ error: String(err)
1764
+ };
1765
+ }
1766
+ const rawOutput = stdout.trim();
1767
+ switch (cmdConfig.output_type) {
1768
+ case "text":
1769
+ return { success: true, result: rawOutput };
1770
+ case "json": {
1771
+ try {
1772
+ const parsed = JSON.parse(rawOutput);
1773
+ return { success: true, result: parsed };
1774
+ } catch {
1775
+ return {
1776
+ success: false,
1777
+ error: `Failed to parse JSON output: ${rawOutput.slice(0, 100)}`
1778
+ };
1779
+ }
1780
+ }
1781
+ case "file":
1782
+ return { success: true, result: { file_path: rawOutput } };
1783
+ default:
1784
+ return {
1785
+ success: false,
1786
+ error: `Unknown output_type: ${String(cmdConfig.output_type)}`
1787
+ };
1788
+ }
1789
+ }
1790
+ };
1791
+
1792
+ // src/conductor/task-decomposer.ts
1793
+ import { randomUUID as randomUUID4 } from "crypto";
1794
+ var TEMPLATES = {
1795
+ "video-production": {
1796
+ keywords: ["video", "demo", "clip", "animation"],
1797
+ steps: [
1798
+ {
1799
+ description: "Generate script from task description",
1800
+ required_capability: "text_gen",
1801
+ estimated_credits: 2,
1802
+ depends_on_indices: []
1803
+ },
1804
+ {
1805
+ description: "Generate voiceover from script",
1806
+ required_capability: "tts",
1807
+ estimated_credits: 3,
1808
+ depends_on_indices: [0]
1809
+ },
1810
+ {
1811
+ description: "Generate video visuals from script",
1812
+ required_capability: "video_gen",
1813
+ estimated_credits: 5,
1814
+ depends_on_indices: [0]
1815
+ },
1816
+ {
1817
+ description: "Composite voiceover and video into final output",
1818
+ required_capability: "video_edit",
1819
+ estimated_credits: 3,
1820
+ depends_on_indices: [1, 2]
1821
+ }
1822
+ ]
1823
+ },
1824
+ "deep-analysis": {
1825
+ keywords: ["analyze", "analysis", "research", "report", "evaluate"],
1826
+ steps: [
1827
+ {
1828
+ description: "Research and gather relevant data",
1829
+ required_capability: "web_search",
1830
+ estimated_credits: 2,
1831
+ depends_on_indices: []
1832
+ },
1833
+ {
1834
+ description: "Analyze gathered data",
1835
+ required_capability: "text_gen",
1836
+ estimated_credits: 3,
1837
+ depends_on_indices: [0]
1838
+ },
1839
+ {
1840
+ description: "Summarize analysis findings",
1841
+ required_capability: "text_gen",
1842
+ estimated_credits: 2,
1843
+ depends_on_indices: [1]
1844
+ },
1845
+ {
1846
+ description: "Format into final report",
1847
+ required_capability: "text_gen",
1848
+ estimated_credits: 1,
1849
+ depends_on_indices: [2]
1850
+ }
1851
+ ]
1852
+ },
1853
+ "content-generation": {
1854
+ keywords: ["write", "blog", "article", "content", "post", "essay"],
1855
+ steps: [
1856
+ {
1857
+ description: "Create content outline",
1858
+ required_capability: "text_gen",
1859
+ estimated_credits: 1,
1860
+ depends_on_indices: []
1861
+ },
1862
+ {
1863
+ description: "Draft content from outline",
1864
+ required_capability: "text_gen",
1865
+ estimated_credits: 3,
1866
+ depends_on_indices: [0]
1867
+ },
1868
+ {
1869
+ description: "Review and refine draft",
1870
+ required_capability: "text_gen",
1871
+ estimated_credits: 2,
1872
+ depends_on_indices: [1]
1873
+ },
1874
+ {
1875
+ description: "Finalize and polish content",
1876
+ required_capability: "text_gen",
1877
+ estimated_credits: 1,
1878
+ depends_on_indices: [2]
1879
+ }
1880
+ ]
1881
+ }
1882
+ };
1883
+ function decompose(task, _availableCapabilities) {
1884
+ const lower = task.toLowerCase();
1885
+ for (const template of Object.values(TEMPLATES)) {
1886
+ const matched = template.keywords.some((kw) => lower.includes(kw));
1887
+ if (!matched) continue;
1888
+ const ids = template.steps.map(() => randomUUID4());
1889
+ return template.steps.map((step, i) => ({
1890
+ id: ids[i],
1891
+ description: step.description,
1892
+ required_capability: step.required_capability,
1893
+ params: {},
1894
+ depends_on: step.depends_on_indices.map((idx) => ids[idx]),
1895
+ estimated_credits: step.estimated_credits
1896
+ }));
1897
+ }
1898
+ return [];
1899
+ }
1900
+
1901
+ // src/gateway/client.ts
1902
+ import { randomUUID as randomUUID5 } from "crypto";
1903
+ async function requestCapability(opts) {
1904
+ const { gatewayUrl, token, cardId, params = {}, timeoutMs = 3e4, escrowReceipt } = opts;
1905
+ const id = randomUUID5();
1906
+ const payload = {
1907
+ jsonrpc: "2.0",
1908
+ id,
1909
+ method: "capability.execute",
1910
+ params: {
1911
+ card_id: cardId,
1912
+ ...params,
1913
+ ...escrowReceipt ? { escrow_receipt: escrowReceipt } : {}
1914
+ }
1915
+ };
1916
+ const controller = new AbortController();
1917
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1918
+ let response;
1919
+ try {
1920
+ response = await fetch(`${gatewayUrl}/rpc`, {
1921
+ method: "POST",
1922
+ headers: {
1923
+ "Content-Type": "application/json",
1924
+ Authorization: `Bearer ${token}`
1925
+ },
1926
+ body: JSON.stringify(payload),
1927
+ signal: controller.signal
1928
+ });
1929
+ } catch (err) {
1930
+ clearTimeout(timer);
1931
+ const isTimeout = err instanceof Error && err.name === "AbortError";
1932
+ throw new AgentBnBError(
1933
+ isTimeout ? "Request timed out" : `Network error: ${String(err)}`,
1934
+ isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
1935
+ );
1936
+ } finally {
1937
+ clearTimeout(timer);
1938
+ }
1939
+ const body = await response.json();
1940
+ if (body.error) {
1941
+ throw new AgentBnBError(body.error.message, `RPC_ERROR_${body.error.code}`);
1942
+ }
1943
+ return body.result;
1944
+ }
1945
+ async function requestViaRelay(relay, opts) {
1946
+ try {
1947
+ return await relay.request({
1948
+ targetOwner: opts.targetOwner,
1949
+ cardId: opts.cardId,
1950
+ skillId: opts.skillId,
1951
+ params: opts.params ?? {},
1952
+ escrowReceipt: opts.escrowReceipt,
1953
+ timeoutMs: opts.timeoutMs
1954
+ });
1955
+ } catch (err) {
1956
+ const message = err instanceof Error ? err.message : String(err);
1957
+ if (message.includes("timeout")) {
1958
+ throw new AgentBnBError(message, "TIMEOUT");
1959
+ }
1960
+ if (message.includes("offline")) {
1961
+ throw new AgentBnBError(message, "AGENT_OFFLINE");
1962
+ }
1963
+ throw new AgentBnBError(message, "RELAY_ERROR");
1964
+ }
1965
+ }
1966
+
1967
+ // src/autonomy/tiers.ts
1968
+ import { randomUUID as randomUUID6 } from "crypto";
1969
+
1970
+ // src/autonomy/pending-requests.ts
1971
+ import { randomUUID as randomUUID7 } from "crypto";
1972
+
1973
+ // src/cli/peers.ts
1974
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
1975
+ import { join as join3 } from "path";
1976
+
1977
+ // src/cli/config.ts
1978
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
1979
+ import { homedir } from "os";
1980
+ import { join as join2 } from "path";
1981
+
1982
+ // src/autonomy/auto-request.ts
1983
+ function minMaxNormalize(values) {
1984
+ if (values.length === 0) return [];
1985
+ if (values.length === 1) return [1];
1986
+ const min = Math.min(...values);
1987
+ const max = Math.max(...values);
1988
+ if (max === min) {
1989
+ return values.map(() => 1);
1990
+ }
1991
+ return values.map((v) => (v - min) / (max - min));
1992
+ }
1993
+ function scorePeers(candidates, selfOwner) {
1994
+ const eligible = candidates.filter((c) => c.card.owner !== selfOwner);
1995
+ if (eligible.length === 0) return [];
1996
+ const successRates = eligible.map((c) => c.card.metadata?.success_rate ?? 0.5);
1997
+ const costEfficiencies = eligible.map((c) => c.cost === 0 ? 1 : 1 / c.cost);
1998
+ const idleRates = eligible.map((c) => {
1999
+ const internal = c.card._internal;
2000
+ const idleRate = internal?.idle_rate;
2001
+ return typeof idleRate === "number" ? idleRate : 1;
2002
+ });
2003
+ const normSuccess = minMaxNormalize(successRates);
2004
+ const normCost = minMaxNormalize(costEfficiencies);
2005
+ const normIdle = minMaxNormalize(idleRates);
2006
+ const scored = eligible.map((c, i) => ({
2007
+ ...c,
2008
+ rawScore: (normSuccess[i] ?? 0) * (normCost[i] ?? 0) * (normIdle[i] ?? 0)
2009
+ }));
2010
+ scored.sort((a, b) => b.rawScore - a.rawScore);
2011
+ return scored;
2012
+ }
2013
+
2014
+ // src/conductor/capability-matcher.ts
2015
+ var MAX_ALTERNATIVES = 2;
2016
+ function matchSubTasks(opts) {
2017
+ const { db, subtasks, conductorOwner } = opts;
2018
+ return subtasks.map((subtask) => {
2019
+ const cards = searchCards(db, subtask.required_capability, { online: true });
2020
+ const candidates = [];
2021
+ for (const card of cards) {
2022
+ const cardAsV2 = card;
2023
+ if (Array.isArray(cardAsV2.skills)) {
2024
+ for (const skill of cardAsV2.skills) {
2025
+ candidates.push({
2026
+ card,
2027
+ cost: skill.pricing.credits_per_call,
2028
+ skillId: skill.id
2029
+ });
2030
+ }
2031
+ } else {
2032
+ candidates.push({
2033
+ card,
2034
+ cost: card.pricing.credits_per_call,
2035
+ skillId: void 0
2036
+ });
2037
+ }
2038
+ }
2039
+ const scored = scorePeers(candidates, conductorOwner);
2040
+ if (scored.length === 0) {
2041
+ return {
2042
+ subtask_id: subtask.id,
2043
+ selected_agent: "",
2044
+ selected_skill: "",
2045
+ score: 0,
2046
+ credits: 0,
2047
+ alternatives: []
2048
+ };
2049
+ }
2050
+ const top = scored[0];
2051
+ const alternatives = scored.slice(1, 1 + MAX_ALTERNATIVES).map((s) => ({
2052
+ agent: s.card.owner,
2053
+ skill: s.skillId ?? "",
2054
+ score: s.rawScore,
2055
+ credits: s.cost
2056
+ }));
2057
+ return {
2058
+ subtask_id: subtask.id,
2059
+ selected_agent: top.card.owner,
2060
+ selected_skill: top.skillId ?? "",
2061
+ score: top.rawScore,
2062
+ credits: top.cost,
2063
+ alternatives
2064
+ };
2065
+ });
2066
+ }
2067
+
2068
+ // src/conductor/budget-controller.ts
2069
+ var ORCHESTRATION_FEE = 5;
2070
+ var BudgetController = class {
2071
+ /**
2072
+ * Creates a new BudgetController.
2073
+ *
2074
+ * @param budgetManager - Underlying BudgetManager for reserve floor enforcement.
2075
+ * @param maxBudget - Hard ceiling for the orchestration run.
2076
+ */
2077
+ constructor(budgetManager, maxBudget) {
2078
+ this.budgetManager = budgetManager;
2079
+ this.maxBudget = maxBudget;
2080
+ }
2081
+ /**
2082
+ * Pre-calculates the total budget for an orchestration run.
2083
+ *
2084
+ * Sums all matched sub-task credits, adds the orchestration fee,
2085
+ * and determines whether approval is required (estimated > max).
2086
+ *
2087
+ * @param matches - MatchResult[] from the CapabilityMatcher.
2088
+ * @returns An ExecutionBudget with cost breakdown and approval status.
2089
+ */
2090
+ calculateBudget(matches) {
2091
+ const perTaskSpending = /* @__PURE__ */ new Map();
2092
+ let subTotal = 0;
2093
+ for (const match of matches) {
2094
+ perTaskSpending.set(match.subtask_id, match.credits);
2095
+ subTotal += match.credits;
2096
+ }
2097
+ const estimatedTotal = subTotal + ORCHESTRATION_FEE;
2098
+ return {
2099
+ estimated_total: estimatedTotal,
2100
+ max_budget: this.maxBudget,
2101
+ orchestration_fee: ORCHESTRATION_FEE,
2102
+ per_task_spending: perTaskSpending,
2103
+ requires_approval: estimatedTotal > this.maxBudget
2104
+ };
2105
+ }
2106
+ /**
2107
+ * Checks whether orchestration can proceed without explicit approval.
2108
+ *
2109
+ * Returns true only when:
2110
+ * 1. The budget does NOT require approval (estimated_total <= max_budget)
2111
+ * 2. The BudgetManager confirms sufficient credits (respecting reserve floor)
2112
+ *
2113
+ * @param budget - ExecutionBudget from calculateBudget().
2114
+ * @returns true if execution can proceed autonomously.
2115
+ */
2116
+ canExecute(budget) {
2117
+ if (budget.requires_approval) return false;
2118
+ return this.budgetManager.canSpend(budget.estimated_total);
2119
+ }
2120
+ /**
2121
+ * Checks budget after explicit user/agent approval.
2122
+ *
2123
+ * Ignores the requires_approval flag — used when the caller has already
2124
+ * obtained explicit approval for the over-budget orchestration.
2125
+ * Still enforces the reserve floor via BudgetManager.canSpend().
2126
+ *
2127
+ * @param budget - ExecutionBudget from calculateBudget().
2128
+ * @returns true if the agent has sufficient credits (reserve floor check only).
2129
+ */
2130
+ approveAndCheck(budget) {
2131
+ return this.budgetManager.canSpend(budget.estimated_total);
2132
+ }
2133
+ };
2134
+
2135
+ // src/conductor/card.ts
2136
+ var CONDUCTOR_OWNER = "agentbnb-conductor";
2137
+ var CONDUCTOR_CARD_ID = "00000000-0000-4000-8000-000000000001";
2138
+ function buildConductorCard() {
2139
+ const card = {
2140
+ spec_version: "2.0",
2141
+ id: CONDUCTOR_CARD_ID,
2142
+ owner: CONDUCTOR_OWNER,
2143
+ agent_name: "AgentBnB Conductor",
2144
+ skills: [
2145
+ {
2146
+ id: "orchestrate",
2147
+ name: "Task Orchestration",
2148
+ description: "Decomposes complex tasks and coordinates multi-agent execution",
2149
+ level: 3,
2150
+ inputs: [
2151
+ {
2152
+ name: "task",
2153
+ type: "text",
2154
+ description: "Natural language task description"
2155
+ }
2156
+ ],
2157
+ outputs: [
2158
+ {
2159
+ name: "result",
2160
+ type: "json",
2161
+ description: "Aggregated execution results"
2162
+ }
2163
+ ],
2164
+ pricing: { credits_per_call: 5 }
2165
+ },
2166
+ {
2167
+ id: "plan",
2168
+ name: "Execution Planning",
2169
+ description: "Returns an execution plan with cost estimate without executing",
2170
+ level: 1,
2171
+ inputs: [
2172
+ {
2173
+ name: "task",
2174
+ type: "text",
2175
+ description: "Natural language task description"
2176
+ }
2177
+ ],
2178
+ outputs: [
2179
+ {
2180
+ name: "plan",
2181
+ type: "json",
2182
+ description: "Execution plan with cost breakdown"
2183
+ }
2184
+ ],
2185
+ pricing: { credits_per_call: 1 }
2186
+ }
2187
+ ],
2188
+ availability: { online: true }
2189
+ };
2190
+ return CapabilityCardV2Schema.parse(card);
2191
+ }
2192
+ function registerConductorCard(db) {
2193
+ const card = buildConductorCard();
2194
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2195
+ const existing = db.prepare("SELECT id FROM capability_cards WHERE id = ?").get(card.id);
2196
+ if (existing) {
2197
+ db.prepare(
2198
+ "UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?"
2199
+ ).run(JSON.stringify(card), now, card.id);
2200
+ } else {
2201
+ db.prepare(
2202
+ "INSERT INTO capability_cards (id, owner, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?)"
2203
+ ).run(card.id, card.owner, JSON.stringify(card), now, now);
2204
+ }
2205
+ return card;
2206
+ }
2207
+
2208
+ // src/conductor/pipeline-orchestrator.ts
2209
+ function computeWaves(subtasks) {
2210
+ const waves = [];
2211
+ const completed = /* @__PURE__ */ new Set();
2212
+ const remaining = new Map(subtasks.map((s) => [s.id, s]));
2213
+ while (remaining.size > 0) {
2214
+ const wave = [];
2215
+ for (const [id, task] of remaining) {
2216
+ const depsResolved = task.depends_on.every((dep) => completed.has(dep));
2217
+ if (depsResolved) {
2218
+ wave.push(id);
2219
+ }
2220
+ }
2221
+ if (wave.length === 0) {
2222
+ break;
2223
+ }
2224
+ for (const id of wave) {
2225
+ remaining.delete(id);
2226
+ completed.add(id);
2227
+ }
2228
+ waves.push(wave);
2229
+ }
2230
+ return waves;
2231
+ }
2232
+ async function orchestrate(opts) {
2233
+ const { subtasks, matches, gatewayToken, resolveAgentUrl, timeoutMs = 3e4, maxBudget } = opts;
2234
+ const startTime = Date.now();
2235
+ if (subtasks.length === 0) {
2236
+ return {
2237
+ success: true,
2238
+ results: /* @__PURE__ */ new Map(),
2239
+ total_credits: 0,
2240
+ latency_ms: Date.now() - startTime
2241
+ };
2242
+ }
2243
+ const results = /* @__PURE__ */ new Map();
2244
+ const errors = [];
2245
+ let totalCredits = 0;
2246
+ const waves = computeWaves(subtasks);
2247
+ const subtaskMap = new Map(subtasks.map((s) => [s.id, s]));
2248
+ for (const wave of waves) {
2249
+ if (maxBudget !== void 0 && totalCredits >= maxBudget) {
2250
+ errors.push(`Budget exceeded: spent ${totalCredits} cr, max ${maxBudget} cr`);
2251
+ break;
2252
+ }
2253
+ const executableIds = [];
2254
+ for (const taskId of wave) {
2255
+ const m = matches.get(taskId);
2256
+ if (maxBudget !== void 0 && m && totalCredits + m.credits > maxBudget) {
2257
+ errors.push(`Skipping task ${taskId}: would exceed budget (${totalCredits} + ${m.credits} > ${maxBudget})`);
2258
+ continue;
2259
+ }
2260
+ executableIds.push(taskId);
2261
+ }
2262
+ const waveResults = await Promise.allSettled(
2263
+ executableIds.map(async (taskId) => {
2264
+ const subtask = subtaskMap.get(taskId);
2265
+ const m = matches.get(taskId);
2266
+ if (!m) {
2267
+ throw new Error(`No match found for subtask ${taskId}`);
2268
+ }
2269
+ const stepsContext = {};
2270
+ for (const [id, val] of results) {
2271
+ stepsContext[id] = val;
2272
+ }
2273
+ const interpContext = { steps: stepsContext, prev: void 0 };
2274
+ if (subtask.depends_on.length > 0) {
2275
+ const lastDep = subtask.depends_on[subtask.depends_on.length - 1];
2276
+ interpContext.prev = results.get(lastDep);
2277
+ }
2278
+ const interpolatedParams = interpolateObject(
2279
+ subtask.params,
2280
+ interpContext
2281
+ );
2282
+ const primary = resolveAgentUrl(m.selected_agent);
2283
+ try {
2284
+ const res = await requestCapability({
2285
+ gatewayUrl: primary.url,
2286
+ token: gatewayToken,
2287
+ cardId: primary.cardId,
2288
+ params: interpolatedParams,
2289
+ timeoutMs
2290
+ });
2291
+ return { taskId, result: res, credits: m.credits };
2292
+ } catch (primaryErr) {
2293
+ if (m.alternatives.length > 0) {
2294
+ const alt = m.alternatives[0];
2295
+ const altAgent = resolveAgentUrl(alt.agent);
2296
+ try {
2297
+ const altRes = await requestCapability({
2298
+ gatewayUrl: altAgent.url,
2299
+ token: gatewayToken,
2300
+ cardId: altAgent.cardId,
2301
+ params: interpolatedParams,
2302
+ timeoutMs
2303
+ });
2304
+ return { taskId, result: altRes, credits: alt.credits };
2305
+ } catch (altErr) {
2306
+ throw new Error(
2307
+ `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)}`
2308
+ );
2309
+ }
2310
+ }
2311
+ throw new Error(
2312
+ `Task ${taskId}: ${primaryErr instanceof Error ? primaryErr.message : String(primaryErr)}`
2313
+ );
2314
+ }
2315
+ })
2316
+ );
2317
+ for (const settlement of waveResults) {
2318
+ if (settlement.status === "fulfilled") {
2319
+ const { taskId, result, credits } = settlement.value;
2320
+ results.set(taskId, result);
2321
+ totalCredits += credits;
2322
+ } else {
2323
+ errors.push(settlement.reason instanceof Error ? settlement.reason.message : String(settlement.reason));
2324
+ }
2325
+ }
2326
+ }
2327
+ return {
2328
+ success: errors.length === 0,
2329
+ results,
2330
+ total_credits: totalCredits,
2331
+ latency_ms: Date.now() - startTime,
2332
+ errors: errors.length > 0 ? errors : void 0
2333
+ };
2334
+ }
2335
+
2336
+ // src/credit/budget.ts
2337
+ var DEFAULT_BUDGET_CONFIG = {
2338
+ reserve_credits: 20
2339
+ };
2340
+ var BudgetManager = class {
2341
+ /**
2342
+ * Creates a new BudgetManager.
2343
+ *
2344
+ * @param creditDb - The credit SQLite database instance.
2345
+ * @param owner - Agent owner identifier.
2346
+ * @param config - Budget configuration. Defaults to DEFAULT_BUDGET_CONFIG (20 credit reserve).
2347
+ */
2348
+ constructor(creditDb, owner, config = DEFAULT_BUDGET_CONFIG) {
2349
+ this.creditDb = creditDb;
2350
+ this.owner = owner;
2351
+ this.config = config;
2352
+ }
2353
+ /**
2354
+ * Returns the number of credits available for spending.
2355
+ * Computed as: max(0, balance - reserve_credits).
2356
+ * Always returns a non-negative number — never goes below zero.
2357
+ *
2358
+ * @returns Available credits (balance minus reserve, floored at 0).
2359
+ */
2360
+ availableCredits() {
2361
+ const balance = getBalance(this.creditDb, this.owner);
2362
+ return Math.max(0, balance - this.config.reserve_credits);
2363
+ }
2364
+ /**
2365
+ * Returns true if spending `amount` credits is permitted by budget rules.
2366
+ *
2367
+ * Rules:
2368
+ * - Zero-cost calls (amount <= 0) always return true.
2369
+ * - Any positive amount requires availableCredits() >= amount.
2370
+ * - If balance is at or below the reserve floor, all positive-cost calls return false.
2371
+ *
2372
+ * @param amount - Number of credits to spend.
2373
+ * @returns true if the spend is allowed, false if it would breach the reserve floor.
2374
+ */
2375
+ canSpend(amount) {
2376
+ if (amount <= 0) return true;
2377
+ return this.availableCredits() >= amount;
2378
+ }
2379
+ };
2380
+
2381
+ // src/conductor/conductor-mode.ts
2382
+ var ConductorMode = class {
2383
+ db;
2384
+ creditDb;
2385
+ conductorOwner;
2386
+ gatewayToken;
2387
+ resolveAgentUrl;
2388
+ maxBudget;
2389
+ constructor(opts) {
2390
+ this.db = opts.db;
2391
+ this.creditDb = opts.creditDb;
2392
+ this.conductorOwner = opts.conductorOwner;
2393
+ this.gatewayToken = opts.gatewayToken;
2394
+ this.resolveAgentUrl = opts.resolveAgentUrl;
2395
+ this.maxBudget = opts.maxBudget ?? 100;
2396
+ }
2397
+ /**
2398
+ * Execute a conductor skill with the given config and params.
2399
+ *
2400
+ * @param config - SkillConfig with type 'conductor' and conductor_skill field.
2401
+ * @param params - Must include `task` string.
2402
+ * @returns Execution result without latency_ms (added by SkillExecutor).
2403
+ */
2404
+ async execute(config, params) {
2405
+ const conductorSkill = config.conductor_skill;
2406
+ if (conductorSkill !== "orchestrate" && conductorSkill !== "plan") {
2407
+ return {
2408
+ success: false,
2409
+ error: `Unknown conductor skill: "${conductorSkill}"`
2410
+ };
2411
+ }
2412
+ const task = params.task;
2413
+ if (typeof task !== "string" || task.length === 0) {
2414
+ return {
2415
+ success: false,
2416
+ error: 'Missing or empty "task" parameter'
2417
+ };
2418
+ }
2419
+ const subtasks = decompose(task);
2420
+ if (subtasks.length === 0) {
2421
+ return {
2422
+ success: false,
2423
+ error: "No template matches task"
2424
+ };
2425
+ }
2426
+ const matchResults = matchSubTasks({
2427
+ db: this.db,
2428
+ subtasks,
2429
+ conductorOwner: this.conductorOwner
2430
+ });
2431
+ const budgetManager = new BudgetManager(this.creditDb, this.conductorOwner);
2432
+ const budgetController = new BudgetController(budgetManager, this.maxBudget);
2433
+ const executionBudget = budgetController.calculateBudget(matchResults);
2434
+ if (!budgetController.canExecute(executionBudget)) {
2435
+ return {
2436
+ success: false,
2437
+ error: `Budget exceeded: estimated ${executionBudget.estimated_total} cr, max ${this.maxBudget} cr`
2438
+ };
2439
+ }
2440
+ if (conductorSkill === "plan") {
2441
+ return {
2442
+ success: true,
2443
+ result: {
2444
+ subtasks,
2445
+ matches: matchResults,
2446
+ budget: executionBudget
2447
+ }
2448
+ };
2449
+ }
2450
+ const matchMap = new Map(
2451
+ matchResults.map((m) => [m.subtask_id, m])
2452
+ );
2453
+ const orchResult = await orchestrate({
2454
+ subtasks,
2455
+ matches: matchMap,
2456
+ gatewayToken: this.gatewayToken,
2457
+ resolveAgentUrl: this.resolveAgentUrl,
2458
+ maxBudget: this.maxBudget
2459
+ });
2460
+ const resultObj = {};
2461
+ for (const [key, value] of orchResult.results) {
2462
+ resultObj[key] = value;
2463
+ }
2464
+ return {
2465
+ success: orchResult.success,
2466
+ result: {
2467
+ plan: subtasks,
2468
+ execution: resultObj,
2469
+ total_credits: orchResult.total_credits,
2470
+ latency_ms: orchResult.latency_ms,
2471
+ errors: orchResult.errors
2472
+ },
2473
+ error: orchResult.success ? void 0 : orchResult.errors?.join("; ")
2474
+ };
2475
+ }
2476
+ };
2477
+
2478
+ // src/credit/escrow-receipt.ts
2479
+ import { z as z3 } from "zod";
2480
+ import { randomUUID as randomUUID8 } from "crypto";
2481
+ var EscrowReceiptSchema = z3.object({
2482
+ requester_owner: z3.string().min(1),
2483
+ requester_public_key: z3.string().min(1),
2484
+ amount: z3.number().positive(),
2485
+ card_id: z3.string().min(1),
2486
+ skill_id: z3.string().optional(),
2487
+ timestamp: z3.string(),
2488
+ nonce: z3.string().uuid(),
2489
+ signature: z3.string().min(1)
2490
+ });
2491
+ function createSignedEscrowReceipt(db, privateKey, publicKey, opts) {
2492
+ const escrowId = holdEscrow(db, opts.owner, opts.amount, opts.cardId);
2493
+ const receiptData = {
2494
+ requester_owner: opts.owner,
2495
+ requester_public_key: publicKey.toString("hex"),
2496
+ amount: opts.amount,
2497
+ card_id: opts.cardId,
2498
+ ...opts.skillId ? { skill_id: opts.skillId } : {},
2499
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2500
+ nonce: randomUUID8()
2501
+ };
2502
+ const signature = signEscrowReceipt(receiptData, privateKey);
2503
+ const receipt = {
2504
+ ...receiptData,
2505
+ signature
2506
+ };
2507
+ return { escrowId, receipt };
2508
+ }
2509
+
2510
+ // src/relay/types.ts
2511
+ import { z as z4 } from "zod";
2512
+ var RegisterMessageSchema = z4.object({
2513
+ type: z4.literal("register"),
2514
+ owner: z4.string().min(1),
2515
+ token: z4.string().min(1),
2516
+ card: z4.record(z4.unknown())
2517
+ // CapabilityCard (validated separately)
2518
+ });
2519
+ var RegisteredMessageSchema = z4.object({
2520
+ type: z4.literal("registered"),
2521
+ agent_id: z4.string()
2522
+ });
2523
+ var RelayRequestMessageSchema = z4.object({
2524
+ type: z4.literal("relay_request"),
2525
+ id: z4.string().uuid(),
2526
+ target_owner: z4.string().min(1),
2527
+ card_id: z4.string(),
2528
+ skill_id: z4.string().optional(),
2529
+ params: z4.record(z4.unknown()).default({}),
2530
+ requester: z4.string().optional(),
2531
+ escrow_receipt: z4.record(z4.unknown()).optional()
2532
+ });
2533
+ var IncomingRequestMessageSchema = z4.object({
2534
+ type: z4.literal("incoming_request"),
2535
+ id: z4.string().uuid(),
2536
+ from_owner: z4.string().min(1),
2537
+ card_id: z4.string(),
2538
+ skill_id: z4.string().optional(),
2539
+ params: z4.record(z4.unknown()).default({}),
2540
+ requester: z4.string().optional(),
2541
+ escrow_receipt: z4.record(z4.unknown()).optional()
2542
+ });
2543
+ var RelayResponseMessageSchema = z4.object({
2544
+ type: z4.literal("relay_response"),
2545
+ id: z4.string().uuid(),
2546
+ result: z4.unknown().optional(),
2547
+ error: z4.object({
2548
+ code: z4.number(),
2549
+ message: z4.string()
2550
+ }).optional()
2551
+ });
2552
+ var ResponseMessageSchema = z4.object({
2553
+ type: z4.literal("response"),
2554
+ id: z4.string().uuid(),
2555
+ result: z4.unknown().optional(),
2556
+ error: z4.object({
2557
+ code: z4.number(),
2558
+ message: z4.string()
2559
+ }).optional()
2560
+ });
2561
+ var ErrorMessageSchema = z4.object({
2562
+ type: z4.literal("error"),
2563
+ code: z4.string(),
2564
+ message: z4.string(),
2565
+ request_id: z4.string().optional()
2566
+ });
2567
+ var RelayMessageSchema = z4.discriminatedUnion("type", [
2568
+ RegisterMessageSchema,
2569
+ RegisteredMessageSchema,
2570
+ RelayRequestMessageSchema,
2571
+ IncomingRequestMessageSchema,
2572
+ RelayResponseMessageSchema,
2573
+ ResponseMessageSchema,
2574
+ ErrorMessageSchema
2575
+ ]);
2576
+
2577
+ // src/relay/websocket-relay.ts
2578
+ import { randomUUID as randomUUID9 } from "crypto";
2579
+ var RATE_LIMIT_MAX = 60;
2580
+ var RATE_LIMIT_WINDOW_MS = 6e4;
2581
+ var RELAY_TIMEOUT_MS = 3e4;
2582
+ function registerWebSocketRelay(server, db) {
2583
+ const connections = /* @__PURE__ */ new Map();
2584
+ const pendingRequests = /* @__PURE__ */ new Map();
2585
+ const rateLimits = /* @__PURE__ */ new Map();
2586
+ function checkRateLimit(owner) {
2587
+ const now = Date.now();
2588
+ const entry = rateLimits.get(owner);
2589
+ if (!entry || now >= entry.resetTime) {
2590
+ rateLimits.set(owner, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
2591
+ return true;
2592
+ }
2593
+ if (entry.count >= RATE_LIMIT_MAX) {
2594
+ return false;
2595
+ }
2596
+ entry.count++;
2597
+ return true;
2598
+ }
2599
+ function markOwnerOffline(owner) {
2600
+ try {
2601
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
2602
+ const rows = stmt.all(owner);
2603
+ for (const row of rows) {
2604
+ try {
2605
+ const card = JSON.parse(row.data);
2606
+ if (card.availability?.online) {
2607
+ card.availability.online = false;
2608
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2609
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
2610
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
2611
+ }
2612
+ } catch {
2613
+ }
2614
+ }
2615
+ } catch {
2616
+ }
2617
+ }
2618
+ function markOwnerOnline(owner) {
2619
+ try {
2620
+ const stmt = db.prepare("SELECT id, data FROM capability_cards WHERE owner = ?");
2621
+ const rows = stmt.all(owner);
2622
+ for (const row of rows) {
2623
+ try {
2624
+ const card = JSON.parse(row.data);
2625
+ card.availability = { ...card.availability, online: true };
2626
+ card.updated_at = (/* @__PURE__ */ new Date()).toISOString();
2627
+ const updateStmt = db.prepare("UPDATE capability_cards SET data = ?, updated_at = ? WHERE id = ?");
2628
+ updateStmt.run(JSON.stringify(card), card.updated_at, row.id);
2629
+ } catch {
2630
+ }
2631
+ }
2632
+ } catch {
2633
+ }
2634
+ }
2635
+ function upsertCard(cardData, owner) {
2636
+ const cardId = cardData.id;
2637
+ const existing = getCard(db, cardId);
2638
+ if (existing) {
2639
+ const updates = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
2640
+ updateCard(db, cardId, owner, updates);
2641
+ } else {
2642
+ const card = { ...cardData, availability: { ...cardData.availability ?? {}, online: true } };
2643
+ insertCard(db, card);
2644
+ }
2645
+ return cardId;
2646
+ }
2647
+ function logAgentJoined(owner, cardName, cardId) {
2648
+ try {
2649
+ insertRequestLog(db, {
2650
+ id: randomUUID9(),
2651
+ card_id: cardId,
2652
+ card_name: cardName,
2653
+ requester: owner,
2654
+ status: "success",
2655
+ latency_ms: 0,
2656
+ credits_charged: 0,
2657
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2658
+ action_type: "agent_joined"
2659
+ });
2660
+ } catch {
2661
+ }
2662
+ }
2663
+ function sendMessage(ws, msg) {
2664
+ if (ws.readyState === 1) {
2665
+ ws.send(JSON.stringify(msg));
2666
+ }
2667
+ }
2668
+ function handleRegister(ws, msg) {
2669
+ const { owner, card } = msg;
2670
+ const existing = connections.get(owner);
2671
+ if (existing && existing !== ws) {
2672
+ try {
2673
+ existing.close(1e3, "Replaced by new connection");
2674
+ } catch {
2675
+ }
2676
+ }
2677
+ connections.set(owner, ws);
2678
+ const cardId = upsertCard(card, owner);
2679
+ const cardName = card.name ?? card.agent_name ?? owner;
2680
+ logAgentJoined(owner, cardName, cardId);
2681
+ markOwnerOnline(owner);
2682
+ sendMessage(ws, { type: "registered", agent_id: cardId });
2683
+ }
2684
+ function handleRelayRequest(ws, msg, fromOwner) {
2685
+ if (!checkRateLimit(fromOwner)) {
2686
+ sendMessage(ws, {
2687
+ type: "error",
2688
+ code: "rate_limited",
2689
+ message: `Rate limit exceeded: max ${RATE_LIMIT_MAX} requests per minute`,
2690
+ request_id: msg.id
2691
+ });
2692
+ return;
2693
+ }
2694
+ const targetWs = connections.get(msg.target_owner);
2695
+ if (!targetWs || targetWs.readyState !== 1) {
2696
+ sendMessage(ws, {
2697
+ type: "response",
2698
+ id: msg.id,
2699
+ error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
2700
+ });
2701
+ return;
2702
+ }
2703
+ const timeout = setTimeout(() => {
2704
+ pendingRequests.delete(msg.id);
2705
+ sendMessage(ws, {
2706
+ type: "response",
2707
+ id: msg.id,
2708
+ error: { code: -32603, message: "Relay request timeout" }
2709
+ });
2710
+ }, RELAY_TIMEOUT_MS);
2711
+ pendingRequests.set(msg.id, { originOwner: fromOwner, timeout });
2712
+ sendMessage(targetWs, {
2713
+ type: "incoming_request",
2714
+ id: msg.id,
2715
+ from_owner: fromOwner,
2716
+ card_id: msg.card_id,
2717
+ skill_id: msg.skill_id,
2718
+ params: msg.params,
2719
+ requester: msg.requester ?? fromOwner,
2720
+ escrow_receipt: msg.escrow_receipt
2721
+ });
2722
+ }
2723
+ function handleRelayResponse(msg) {
2724
+ const pending = pendingRequests.get(msg.id);
2725
+ if (!pending) return;
2726
+ clearTimeout(pending.timeout);
2727
+ pendingRequests.delete(msg.id);
2728
+ const originWs = connections.get(pending.originOwner);
2729
+ if (originWs && originWs.readyState === 1) {
2730
+ sendMessage(originWs, {
2731
+ type: "response",
2732
+ id: msg.id,
2733
+ result: msg.result,
2734
+ error: msg.error
2735
+ });
2736
+ }
2737
+ }
2738
+ function handleDisconnect(owner) {
2739
+ if (!owner) return;
2740
+ connections.delete(owner);
2741
+ rateLimits.delete(owner);
2742
+ markOwnerOffline(owner);
2743
+ for (const [reqId, pending] of pendingRequests) {
2744
+ if (pending.originOwner === owner) {
2745
+ clearTimeout(pending.timeout);
2746
+ pendingRequests.delete(reqId);
2747
+ }
2748
+ }
2749
+ }
2750
+ server.get("/ws", { websocket: true }, (socket) => {
2751
+ let registeredOwner;
2752
+ socket.on("message", (raw) => {
2753
+ let data;
2754
+ try {
2755
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
2756
+ } catch {
2757
+ sendMessage(socket, { type: "error", code: "invalid_json", message: "Invalid JSON" });
2758
+ return;
2759
+ }
2760
+ const parsed = RelayMessageSchema.safeParse(data);
2761
+ if (!parsed.success) {
2762
+ sendMessage(socket, {
2763
+ type: "error",
2764
+ code: "invalid_message",
2765
+ message: `Invalid message: ${parsed.error.issues[0]?.message ?? "unknown error"}`
2766
+ });
2767
+ return;
2768
+ }
2769
+ const msg = parsed.data;
2770
+ switch (msg.type) {
2771
+ case "register":
2772
+ registeredOwner = msg.owner;
2773
+ handleRegister(socket, msg);
2774
+ break;
2775
+ case "relay_request":
2776
+ if (!registeredOwner) {
2777
+ sendMessage(socket, {
2778
+ type: "error",
2779
+ code: "not_registered",
2780
+ message: "Must send register message before relay requests"
2781
+ });
2782
+ return;
2783
+ }
2784
+ handleRelayRequest(socket, msg, registeredOwner);
2785
+ break;
2786
+ case "relay_response":
2787
+ handleRelayResponse(msg);
2788
+ break;
2789
+ default:
2790
+ break;
2791
+ }
2792
+ });
2793
+ socket.on("close", () => {
2794
+ handleDisconnect(registeredOwner);
2795
+ });
2796
+ socket.on("error", () => {
2797
+ handleDisconnect(registeredOwner);
2798
+ });
2799
+ });
2800
+ return {
2801
+ getOnlineCount: () => connections.size,
2802
+ getOnlineOwners: () => Array.from(connections.keys()),
2803
+ shutdown: () => {
2804
+ for (const [, ws] of connections) {
2805
+ try {
2806
+ ws.close(1001, "Server shutting down");
2807
+ } catch {
2808
+ }
2809
+ }
2810
+ connections.clear();
2811
+ for (const [, pending] of pendingRequests) {
2812
+ clearTimeout(pending.timeout);
2813
+ }
2814
+ pendingRequests.clear();
2815
+ rateLimits.clear();
2816
+ }
2817
+ };
2818
+ }
2819
+
2820
+ // src/relay/websocket-client.ts
2821
+ import WebSocket from "ws";
2822
+ import { randomUUID as randomUUID10 } from "crypto";
2823
+ var RelayClient = class {
2824
+ ws = null;
2825
+ opts;
2826
+ pendingRequests = /* @__PURE__ */ new Map();
2827
+ reconnectAttempts = 0;
2828
+ reconnectTimer = null;
2829
+ intentionalClose = false;
2830
+ registered = false;
2831
+ pongTimeout = null;
2832
+ pingInterval = null;
2833
+ constructor(opts) {
2834
+ this.opts = opts;
2835
+ }
2836
+ /**
2837
+ * Connect to the registry relay and register.
2838
+ * Resolves when registration is acknowledged.
2839
+ */
2840
+ async connect() {
2841
+ return new Promise((resolve, reject) => {
2842
+ this.intentionalClose = false;
2843
+ this.registered = false;
2844
+ const wsUrl = this.buildWsUrl();
2845
+ this.ws = new WebSocket(wsUrl);
2846
+ let resolved = false;
2847
+ this.ws.on("open", () => {
2848
+ this.reconnectAttempts = 0;
2849
+ this.startPingInterval();
2850
+ this.send({
2851
+ type: "register",
2852
+ owner: this.opts.owner,
2853
+ token: this.opts.token,
2854
+ card: this.opts.card
2855
+ });
2856
+ });
2857
+ this.ws.on("message", (raw) => {
2858
+ this.handleMessage(raw, (err) => {
2859
+ if (!resolved) {
2860
+ resolved = true;
2861
+ if (err) reject(err);
2862
+ else resolve();
2863
+ }
2864
+ });
2865
+ });
2866
+ this.ws.on("close", () => {
2867
+ this.cleanup();
2868
+ if (!this.intentionalClose) {
2869
+ if (!resolved) {
2870
+ resolved = true;
2871
+ reject(new Error("WebSocket closed before registration"));
2872
+ }
2873
+ this.scheduleReconnect();
2874
+ }
2875
+ });
2876
+ this.ws.on("error", (err) => {
2877
+ if (!resolved) {
2878
+ resolved = true;
2879
+ reject(err);
2880
+ }
2881
+ });
2882
+ setTimeout(() => {
2883
+ if (!resolved) {
2884
+ resolved = true;
2885
+ reject(new Error("Connection timeout"));
2886
+ this.ws?.close();
2887
+ }
2888
+ }, 1e4);
2889
+ });
2890
+ }
2891
+ /**
2892
+ * Disconnect from the registry relay.
2893
+ */
2894
+ disconnect() {
2895
+ this.intentionalClose = true;
2896
+ this.cleanup();
2897
+ if (this.ws) {
2898
+ try {
2899
+ this.ws.close(1e3, "Client disconnect");
2900
+ } catch {
2901
+ }
2902
+ this.ws = null;
2903
+ }
2904
+ for (const [id, pending] of this.pendingRequests) {
2905
+ clearTimeout(pending.timeout);
2906
+ pending.reject(new Error("Client disconnected"));
2907
+ this.pendingRequests.delete(id);
2908
+ }
2909
+ }
2910
+ /**
2911
+ * Send a relay request to another agent via the registry.
2912
+ * @returns The result from the target agent.
2913
+ */
2914
+ async request(opts) {
2915
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.registered) {
2916
+ throw new Error("Not connected to registry relay");
2917
+ }
2918
+ const id = randomUUID10();
2919
+ const timeoutMs = opts.timeoutMs ?? 3e4;
2920
+ return new Promise((resolve, reject) => {
2921
+ const timeout = setTimeout(() => {
2922
+ this.pendingRequests.delete(id);
2923
+ reject(new Error("Relay request timeout"));
2924
+ }, timeoutMs);
2925
+ this.pendingRequests.set(id, { resolve, reject, timeout });
2926
+ this.send({
2927
+ type: "relay_request",
2928
+ id,
2929
+ target_owner: opts.targetOwner,
2930
+ card_id: opts.cardId,
2931
+ skill_id: opts.skillId,
2932
+ params: opts.params,
2933
+ requester: opts.requester ?? this.opts.owner,
2934
+ escrow_receipt: opts.escrowReceipt
2935
+ });
2936
+ });
2937
+ }
2938
+ /** Whether the client is connected and registered */
2939
+ get isConnected() {
2940
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
2941
+ }
2942
+ // ── Private methods ─────────────────────────────────────────────────────────
2943
+ buildWsUrl() {
2944
+ let url = this.opts.registryUrl;
2945
+ if (url.startsWith("http://")) {
2946
+ url = "ws://" + url.slice(7);
2947
+ } else if (url.startsWith("https://")) {
2948
+ url = "wss://" + url.slice(8);
2949
+ } else if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
2950
+ url = "wss://" + url;
2951
+ }
2952
+ if (!url.endsWith("/ws")) {
2953
+ url = url.replace(/\/$/, "") + "/ws";
2954
+ }
2955
+ return url;
2956
+ }
2957
+ handleMessage(raw, onRegistered) {
2958
+ let data;
2959
+ try {
2960
+ data = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf-8"));
2961
+ } catch {
2962
+ return;
2963
+ }
2964
+ const parsed = RelayMessageSchema.safeParse(data);
2965
+ if (!parsed.success) return;
2966
+ const msg = parsed.data;
2967
+ switch (msg.type) {
2968
+ case "registered":
2969
+ this.registered = true;
2970
+ if (!this.opts.silent) {
2971
+ console.log(` \u2713 Registered with registry (agent_id: ${msg.agent_id})`);
2972
+ }
2973
+ onRegistered?.();
2974
+ break;
2975
+ case "incoming_request":
2976
+ this.handleIncomingRequest(msg);
2977
+ break;
2978
+ case "response":
2979
+ this.handleResponse(msg);
2980
+ break;
2981
+ case "error":
2982
+ this.handleError(msg);
2983
+ break;
2984
+ default:
2985
+ break;
2986
+ }
2987
+ }
2988
+ async handleIncomingRequest(msg) {
2989
+ try {
2990
+ const result = await this.opts.onRequest(msg);
2991
+ this.send({
2992
+ type: "relay_response",
2993
+ id: msg.id,
2994
+ result: result.result,
2995
+ error: result.error
2996
+ });
2997
+ } catch (err) {
2998
+ this.send({
2999
+ type: "relay_response",
3000
+ id: msg.id,
3001
+ error: {
3002
+ code: -32603,
3003
+ message: err instanceof Error ? err.message : "Internal error"
3004
+ }
3005
+ });
3006
+ }
3007
+ }
3008
+ handleResponse(msg) {
3009
+ const pending = this.pendingRequests.get(msg.id);
3010
+ if (!pending) return;
3011
+ clearTimeout(pending.timeout);
3012
+ this.pendingRequests.delete(msg.id);
3013
+ if (msg.error) {
3014
+ pending.reject(new Error(msg.error.message));
3015
+ } else {
3016
+ pending.resolve(msg.result);
3017
+ }
3018
+ }
3019
+ handleError(msg) {
3020
+ if (msg.request_id) {
3021
+ const pending = this.pendingRequests.get(msg.request_id);
3022
+ if (pending) {
3023
+ clearTimeout(pending.timeout);
3024
+ this.pendingRequests.delete(msg.request_id);
3025
+ pending.reject(new Error(`${msg.code}: ${msg.message}`));
3026
+ }
3027
+ }
3028
+ }
3029
+ send(msg) {
3030
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3031
+ this.ws.send(JSON.stringify(msg));
3032
+ }
3033
+ }
3034
+ startPingInterval() {
3035
+ this.stopPingInterval();
3036
+ this.pingInterval = setInterval(() => {
3037
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
3038
+ this.ws.ping();
3039
+ this.pongTimeout = setTimeout(() => {
3040
+ if (!this.opts.silent) {
3041
+ console.log(" \u26A0 Registry pong timeout, reconnecting...");
3042
+ }
3043
+ this.ws?.terminate();
3044
+ }, 15e3);
3045
+ }
3046
+ }, 3e4);
3047
+ this.ws?.on("pong", () => {
3048
+ if (this.pongTimeout) {
3049
+ clearTimeout(this.pongTimeout);
3050
+ this.pongTimeout = null;
3051
+ }
3052
+ });
3053
+ }
3054
+ stopPingInterval() {
3055
+ if (this.pingInterval) {
3056
+ clearInterval(this.pingInterval);
3057
+ this.pingInterval = null;
3058
+ }
3059
+ if (this.pongTimeout) {
3060
+ clearTimeout(this.pongTimeout);
3061
+ this.pongTimeout = null;
3062
+ }
3063
+ }
3064
+ cleanup() {
3065
+ this.stopPingInterval();
3066
+ this.registered = false;
3067
+ }
3068
+ scheduleReconnect() {
3069
+ if (this.intentionalClose) return;
3070
+ if (this.reconnectTimer) return;
3071
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
3072
+ this.reconnectAttempts++;
3073
+ if (!this.opts.silent) {
3074
+ console.log(` \u21BB Reconnecting to registry in ${delay / 1e3}s...`);
3075
+ }
3076
+ this.reconnectTimer = setTimeout(async () => {
3077
+ this.reconnectTimer = null;
3078
+ try {
3079
+ await this.connect();
3080
+ if (!this.opts.silent) {
3081
+ console.log(" \u2713 Reconnected to registry");
3082
+ }
3083
+ } catch {
3084
+ }
3085
+ }, delay);
3086
+ }
3087
+ };
885
3088
  export {
3089
+ ApiExecutor,
3090
+ ApiSkillConfigSchema,
3091
+ BudgetController,
3092
+ CONDUCTOR_OWNER,
886
3093
  CapabilityCardSchema,
3094
+ CommandExecutor,
3095
+ CommandSkillConfigSchema,
3096
+ ConductorMode,
3097
+ ConductorSkillConfigSchema,
3098
+ EscrowReceiptSchema,
3099
+ ORCHESTRATION_FEE,
3100
+ OpenClawBridge,
3101
+ OpenClawSkillConfigSchema,
3102
+ PipelineExecutor,
3103
+ PipelineSkillConfigSchema,
3104
+ RelayClient,
3105
+ RelayMessageSchema,
3106
+ SkillConfigSchema,
3107
+ SkillExecutor,
3108
+ SkillsFileSchema,
3109
+ TEMPLATES,
3110
+ applyInputMapping,
3111
+ buildAuthHeaders,
3112
+ buildConductorCard,
887
3113
  createGatewayServer,
3114
+ createSignedEscrowReceipt,
3115
+ createSkillExecutor,
3116
+ decompose,
3117
+ executeCapabilityRequest,
3118
+ expandEnvVars,
3119
+ extractByPath,
3120
+ generateKeyPair,
888
3121
  getBalance,
889
3122
  getCard,
890
3123
  insertCard,
3124
+ interpolate,
3125
+ interpolateObject,
3126
+ loadKeyPair,
3127
+ matchSubTasks,
891
3128
  openCreditDb,
892
3129
  openDatabase,
893
- searchCards
3130
+ orchestrate,
3131
+ parseSkillsFile,
3132
+ registerConductorCard,
3133
+ registerWebSocketRelay,
3134
+ releaseRequesterEscrow,
3135
+ requestViaRelay,
3136
+ resolvePath,
3137
+ saveKeyPair,
3138
+ searchCards,
3139
+ settleProviderEarning,
3140
+ settleRequesterEscrow,
3141
+ signEscrowReceipt,
3142
+ verifyEscrowReceipt
894
3143
  };