agentbnb 6.0.0 → 7.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/README.md +245 -39
- package/dist/{card-REW7BSWW.js → card-EX2EYGCZ.js} +1 -1
- package/dist/{chunk-C6KPAFCC.js → chunk-3LWBH7P3.js} +94 -3
- package/dist/{chunk-YRRVFTDR.js → chunk-5AAFG2V2.js} +3 -3
- package/dist/{chunk-C2T4BMRW.js → chunk-5GME4KJZ.js} +8 -6
- package/dist/{chunk-VPQ44XKE.js → chunk-64AK4FJM.js} +2 -2
- package/dist/{chunk-2TLZ6G2B.js → chunk-7EF3HYVZ.js} +158 -18
- package/dist/{chunk-JR6TJDIF.js → chunk-ALX4WS3A.js} +9 -4
- package/dist/{chunk-7XHDSWRD.js → chunk-B2VJTKO5.js} +2 -2
- package/dist/{chunk-TR6UZDNX.js → chunk-C537SFHV.js} +8 -6
- package/dist/{chunk-F53QQIM2.js → chunk-CUONY5TO.js} +1 -1
- package/dist/chunk-D6RKW2XG.js +395 -0
- package/dist/{chunk-NYV3NE5Z.js → chunk-E2OKP5CY.js} +4 -4
- package/dist/{chunk-TQDV254A.js → chunk-FTZTEHYG.js} +117 -117
- package/dist/{chunk-Y7T6IMM3.js → chunk-GKVTD4EZ.js} +1 -1
- package/dist/{chunk-VMH2YS2I.js → chunk-KF3TZHA5.js} +1 -1
- package/dist/{chunk-574W3HHE.js → chunk-LJM7FHPM.js} +1 -1
- package/dist/chunk-NWIQJ2CL.js +108 -0
- package/dist/{chunk-KA2VIEGM.js → chunk-O2OYBAVR.js} +1 -1
- package/dist/{chunk-NQTE577Q.js → chunk-OCSU2S6W.js} +9 -0
- package/dist/{chunk-PSQHUZ7X.js → chunk-OH7BP5NP.js} +1 -1
- package/dist/chunk-SSK653A6.js +169 -0
- package/dist/{chunk-BP3L2TET.js → chunk-TBJ3FZKZ.js} +2 -2
- package/dist/{chunk-3CIMVISQ.js → chunk-WVY2W7AA.js} +4 -0
- package/dist/{chunk-OZXCRLP3.js → chunk-X32NE6V4.js} +5 -2
- package/dist/{chunk-7YLFLC5C.js → chunk-YHY7OG6S.js} +5 -5
- package/dist/{chunk-JOY533UH.js → chunk-Z4MCGKTL.js} +6 -2
- package/dist/cli/index.js +530 -269
- package/dist/{client-HRYRJKSA.js → client-HKV3QWZ3.js} +3 -3
- package/dist/{conduct-LF6FYPLD.js → conduct-W6XF6DJW.js} +14 -13
- package/dist/conduct-YB64OHI6.js +22 -0
- package/dist/{conductor-mode-NUDQLZFM.js → conductor-mode-2GSLHVN6.js} +7 -4
- package/dist/{conductor-mode-YQ6QSPPT.js → conductor-mode-AKREGDIU.js} +11 -10
- package/dist/execute-AYQWORVH.js +15 -0
- package/dist/{execute-ITHIYYOX.js → execute-EPE6MZLT.js} +4 -3
- package/dist/index.d.ts +502 -12
- package/dist/index.js +706 -53
- package/dist/{process-guard-QCCBGILS.js → process-guard-GH5LRNWO.js} +1 -1
- package/dist/{publish-capability-TS6CNR5G.js → publish-capability-AH2HDW54.js} +3 -3
- package/dist/reliability-metrics-QG7WC5QK.js +18 -0
- package/dist/{request-P6QCTCCG.js → request-HCCXSKAY.js} +15 -14
- package/dist/{serve-skill-EZOL7UYN.js → serve-skill-SZAQT5T5.js} +9 -8
- package/dist/{server-3G6ZTASA.js → server-MHMAYXWZ.js} +12 -11
- package/dist/{service-coordinator-CRSE4GWC.js → service-coordinator-WGH6B2VT.js} +572 -68
- package/dist/{skill-config-4W5W5O6T.js → skill-config-FETXPNVP.js} +1 -1
- package/dist/skills/agentbnb/bootstrap.js +594 -73
- package/dist/{websocket-client-WRN3HO73.js → websocket-client-4Z5P54RU.js} +1 -1
- package/dist/websocket-client-QOVARTRN.js +7 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +18 -11
- package/skills/agentbnb/bootstrap.test.ts +9 -0
- package/skills/agentbnb/bootstrap.ts +51 -26
- package/skills/agentbnb/install.sh +0 -0
- package/dist/chunk-QT7TEVNV.js +0 -82
- package/dist/chunk-RVYQSC6L.js +0 -212
- package/dist/conduct-QAFZIEY6.js +0 -21
- package/dist/execute-PNJFABVJ.js +0 -14
- package/dist/websocket-client-6IIDGXKB.js +0 -7
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeCapabilityBatch,
|
|
3
|
+
executeCapabilityRequest
|
|
4
|
+
} from "./chunk-C537SFHV.js";
|
|
1
5
|
import {
|
|
2
6
|
StructuredFeedbackSchema
|
|
3
7
|
} from "./chunk-AUBHR7HH.js";
|
|
4
8
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./chunk-TR6UZDNX.js";
|
|
9
|
+
RelayMessageSchema
|
|
10
|
+
} from "./chunk-SSK653A6.js";
|
|
8
11
|
import {
|
|
9
12
|
KNOWN_API_KEYS,
|
|
10
13
|
announceGateway,
|
|
@@ -12,22 +15,19 @@ import {
|
|
|
12
15
|
detectApiKeys,
|
|
13
16
|
getPricingStats,
|
|
14
17
|
stopAnnouncement
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-FTZTEHYG.js";
|
|
16
19
|
import {
|
|
17
20
|
deriveAgentId
|
|
18
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OH7BP5NP.js";
|
|
22
|
+
import "./chunk-X32NE6V4.js";
|
|
19
23
|
import {
|
|
20
24
|
createLedger,
|
|
21
25
|
identityAuthPlugin
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-OZXCRLP3.js";
|
|
26
|
+
} from "./chunk-5AAFG2V2.js";
|
|
24
27
|
import {
|
|
25
28
|
ApiSkillConfigSchema,
|
|
26
29
|
parseSkillsFile
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import {
|
|
29
|
-
RelayMessageSchema
|
|
30
|
-
} from "./chunk-QT7TEVNV.js";
|
|
30
|
+
} from "./chunk-OCSU2S6W.js";
|
|
31
31
|
import {
|
|
32
32
|
interpolateObject
|
|
33
33
|
} from "./chunk-3MJT4PZG.js";
|
|
@@ -37,13 +37,13 @@ import {
|
|
|
37
37
|
insertAuditEvent,
|
|
38
38
|
listPendingRequests,
|
|
39
39
|
resolvePendingRequest
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-GKVTD4EZ.js";
|
|
41
41
|
import {
|
|
42
42
|
buildReputationMap,
|
|
43
43
|
computeReputation,
|
|
44
44
|
filterCards,
|
|
45
45
|
searchCards
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-LJM7FHPM.js";
|
|
47
47
|
import {
|
|
48
48
|
bootstrapAgent,
|
|
49
49
|
getBalance,
|
|
@@ -53,11 +53,12 @@ import {
|
|
|
53
53
|
openCreditDb,
|
|
54
54
|
releaseEscrow,
|
|
55
55
|
settleEscrow
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-D6RKW2XG.js";
|
|
57
|
+
import "./chunk-NWIQJ2CL.js";
|
|
57
58
|
import {
|
|
58
59
|
generateKeyPair,
|
|
59
60
|
verifyEscrowReceipt
|
|
60
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-CUONY5TO.js";
|
|
61
62
|
import {
|
|
62
63
|
getConfigDir
|
|
63
64
|
} from "./chunk-75OC6E4F.js";
|
|
@@ -80,11 +81,11 @@ import {
|
|
|
80
81
|
updateCard,
|
|
81
82
|
updateSkillAvailability,
|
|
82
83
|
updateSkillIdleRate
|
|
83
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-O2OYBAVR.js";
|
|
84
85
|
import {
|
|
85
86
|
AgentBnBError,
|
|
86
87
|
AnyCardSchema
|
|
87
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-WVY2W7AA.js";
|
|
88
89
|
|
|
89
90
|
// src/runtime/agent-runtime.ts
|
|
90
91
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -93,13 +94,16 @@ import { readFileSync, existsSync } from "fs";
|
|
|
93
94
|
var SkillExecutor = class {
|
|
94
95
|
skillMap;
|
|
95
96
|
modeMap;
|
|
97
|
+
concurrencyGuard;
|
|
96
98
|
/**
|
|
97
99
|
* @param configs - Parsed SkillConfig array (from parseSkillsFile).
|
|
98
100
|
* @param modes - Map from skill type string to its executor implementation.
|
|
101
|
+
* @param concurrencyGuard - Optional ConcurrencyGuard to enforce max_concurrent limits.
|
|
99
102
|
*/
|
|
100
|
-
constructor(configs, modes) {
|
|
103
|
+
constructor(configs, modes, concurrencyGuard) {
|
|
101
104
|
this.skillMap = new Map(configs.map((c) => [c.id, c]));
|
|
102
105
|
this.modeMap = modes;
|
|
106
|
+
this.concurrencyGuard = concurrencyGuard;
|
|
103
107
|
}
|
|
104
108
|
/**
|
|
105
109
|
* Execute a skill by ID with the given input parameters.
|
|
@@ -132,6 +136,18 @@ var SkillExecutor = class {
|
|
|
132
136
|
latency_ms: Date.now() - startTime
|
|
133
137
|
};
|
|
134
138
|
}
|
|
139
|
+
const maxConcurrent = config.capacity?.max_concurrent ?? Infinity;
|
|
140
|
+
if (this.concurrencyGuard && maxConcurrent !== Infinity) {
|
|
141
|
+
if (!this.concurrencyGuard.canAccept(skillId, maxConcurrent)) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: `Skill ${skillId} at capacity (${maxConcurrent} concurrent max)`,
|
|
145
|
+
latency_ms: Date.now() - startTime,
|
|
146
|
+
failure_reason: "overload"
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
this.concurrencyGuard.acquire(skillId);
|
|
150
|
+
}
|
|
135
151
|
try {
|
|
136
152
|
const modeResult = await mode.execute(config, params, onProgress);
|
|
137
153
|
return {
|
|
@@ -145,6 +161,10 @@ var SkillExecutor = class {
|
|
|
145
161
|
error: message,
|
|
146
162
|
latency_ms: Date.now() - startTime
|
|
147
163
|
};
|
|
164
|
+
} finally {
|
|
165
|
+
if (this.concurrencyGuard && maxConcurrent !== Infinity) {
|
|
166
|
+
this.concurrencyGuard.release(skillId);
|
|
167
|
+
}
|
|
148
168
|
}
|
|
149
169
|
}
|
|
150
170
|
/**
|
|
@@ -165,8 +185,8 @@ var SkillExecutor = class {
|
|
|
165
185
|
return this.skillMap.get(skillId);
|
|
166
186
|
}
|
|
167
187
|
};
|
|
168
|
-
function createSkillExecutor(configs, modes) {
|
|
169
|
-
return new SkillExecutor(configs, modes);
|
|
188
|
+
function createSkillExecutor(configs, modes, concurrencyGuard) {
|
|
189
|
+
return new SkillExecutor(configs, modes, concurrencyGuard);
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
// src/skills/api-executor.ts
|
|
@@ -584,7 +604,8 @@ var OpenClawBridge = class {
|
|
|
584
604
|
};
|
|
585
605
|
|
|
586
606
|
// src/skills/command-executor.ts
|
|
587
|
-
import {
|
|
607
|
+
import { spawn } from "child_process";
|
|
608
|
+
var KILL_GRACE_MS = 5e3;
|
|
588
609
|
function shellEscape2(value) {
|
|
589
610
|
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
590
611
|
}
|
|
@@ -610,29 +631,89 @@ function safeInterpolateCommand2(template, context) {
|
|
|
610
631
|
return shellEscape2(String(current));
|
|
611
632
|
});
|
|
612
633
|
}
|
|
613
|
-
function
|
|
634
|
+
function spawnWithKill(command, options, registry) {
|
|
614
635
|
return new Promise((resolve2, reject) => {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
636
|
+
const child = spawn("/bin/sh", ["-c", `${command} < /dev/null`], {
|
|
637
|
+
cwd: options.cwd,
|
|
638
|
+
env: options.env,
|
|
639
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
640
|
+
detached: true
|
|
641
|
+
});
|
|
642
|
+
registry.add(child);
|
|
643
|
+
let stdout = "";
|
|
644
|
+
let stderr = "";
|
|
645
|
+
let timedOut = false;
|
|
646
|
+
let killed = false;
|
|
647
|
+
let killTimer;
|
|
648
|
+
child.stdout.on("data", (chunk) => {
|
|
649
|
+
if (stdout.length < options.maxBuffer) {
|
|
650
|
+
stdout += chunk.toString();
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
child.stderr.on("data", (chunk) => {
|
|
654
|
+
if (stderr.length < options.maxBuffer) {
|
|
655
|
+
stderr += chunk.toString();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
const killGroup = (signal) => {
|
|
659
|
+
try {
|
|
660
|
+
process.kill(-child.pid, signal);
|
|
661
|
+
} catch {
|
|
662
|
+
try {
|
|
663
|
+
child.kill(signal);
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
const timeoutId = setTimeout(() => {
|
|
669
|
+
timedOut = true;
|
|
670
|
+
killGroup("SIGTERM");
|
|
671
|
+
killTimer = setTimeout(() => {
|
|
672
|
+
if (!killed) {
|
|
673
|
+
killGroup("SIGKILL");
|
|
674
|
+
}
|
|
675
|
+
}, KILL_GRACE_MS);
|
|
676
|
+
killTimer.unref();
|
|
677
|
+
}, options.timeout);
|
|
678
|
+
child.on("close", (_code, _signal) => {
|
|
679
|
+
killed = true;
|
|
680
|
+
clearTimeout(timeoutId);
|
|
681
|
+
if (killTimer) clearTimeout(killTimer);
|
|
682
|
+
registry.delete(child);
|
|
683
|
+
if (timedOut) {
|
|
684
|
+
const err = new Error(`Command timed out after ${options.timeout}ms`);
|
|
685
|
+
err.code = "ETIMEDOUT";
|
|
686
|
+
reject(err);
|
|
687
|
+
} else if (_code !== 0) {
|
|
688
|
+
const err = new Error(stderr.trim() || `Process exited with code ${_code}`);
|
|
689
|
+
Object.assign(err, { stderr: stderr.trim() });
|
|
690
|
+
reject(err);
|
|
621
691
|
} else {
|
|
622
|
-
resolve2({ stdout
|
|
692
|
+
resolve2({ stdout, stderr });
|
|
623
693
|
}
|
|
624
694
|
});
|
|
695
|
+
child.on("error", (err) => {
|
|
696
|
+
killed = true;
|
|
697
|
+
clearTimeout(timeoutId);
|
|
698
|
+
registry.delete(child);
|
|
699
|
+
reject(err);
|
|
700
|
+
});
|
|
625
701
|
});
|
|
626
702
|
}
|
|
627
703
|
var CommandExecutor = class {
|
|
704
|
+
/** Active child processes — killed on shutdown(). */
|
|
705
|
+
activeProcesses = /* @__PURE__ */ new Set();
|
|
706
|
+
/** In-flight execution count per skill ID for concurrency limiting. */
|
|
707
|
+
inflight = /* @__PURE__ */ new Map();
|
|
628
708
|
/**
|
|
629
709
|
* Execute a command skill with the provided parameters.
|
|
630
710
|
*
|
|
631
711
|
* Steps:
|
|
632
|
-
* 1.
|
|
633
|
-
* 2.
|
|
634
|
-
* 3.
|
|
635
|
-
* 4.
|
|
712
|
+
* 1. Concurrency check: reject if at capacity.max_concurrent limit.
|
|
713
|
+
* 2. Security check: base command must be in `allowed_commands` if set.
|
|
714
|
+
* 3. Interpolate `config.command` using `{ params }` context.
|
|
715
|
+
* 4. Run via spawn with SIGTERM→SIGKILL timeout handling.
|
|
716
|
+
* 5. Parse stdout based on `output_type`: text | json | file.
|
|
636
717
|
*
|
|
637
718
|
* @param config - Validated CommandSkillConfig.
|
|
638
719
|
* @param params - Input parameters passed by the caller.
|
|
@@ -640,43 +721,73 @@ var CommandExecutor = class {
|
|
|
640
721
|
*/
|
|
641
722
|
async execute(config, params) {
|
|
642
723
|
const cmdConfig = config;
|
|
724
|
+
const maxConcurrent = cmdConfig.capacity?.max_concurrent;
|
|
725
|
+
if (maxConcurrent !== void 0) {
|
|
726
|
+
const current = this.inflight.get(cmdConfig.id) ?? 0;
|
|
727
|
+
if (current >= maxConcurrent) {
|
|
728
|
+
return {
|
|
729
|
+
success: false,
|
|
730
|
+
error: `Skill "${cmdConfig.id}" at max concurrency (${maxConcurrent}). Try again later.`
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
643
734
|
const baseCommand = cmdConfig.command.trim().split(/\s+/)[0] ?? "";
|
|
644
735
|
if (cmdConfig.allowed_commands && cmdConfig.allowed_commands.length > 0) {
|
|
645
|
-
|
|
736
|
+
const effectiveAllowed = cmdConfig.claude_code ? [.../* @__PURE__ */ new Set([...cmdConfig.allowed_commands, "claude"])] : cmdConfig.allowed_commands;
|
|
737
|
+
const commandToCheck = cmdConfig.claude_code ? "claude" : baseCommand;
|
|
738
|
+
if (!effectiveAllowed.includes(commandToCheck)) {
|
|
646
739
|
return {
|
|
647
740
|
success: false,
|
|
648
|
-
error: `Command not allowed: "${
|
|
741
|
+
error: `Command not allowed: "${commandToCheck}". Allowed: ${effectiveAllowed.join(", ")}`
|
|
649
742
|
};
|
|
650
743
|
}
|
|
651
744
|
}
|
|
652
|
-
|
|
745
|
+
let interpolatedCommand;
|
|
746
|
+
if (cmdConfig.claude_code) {
|
|
747
|
+
const parts = ["claude", "--print"];
|
|
748
|
+
if (cmdConfig.claude_code.auto_mode) {
|
|
749
|
+
parts.push("--dangerously-skip-permissions");
|
|
750
|
+
}
|
|
751
|
+
if (cmdConfig.claude_code.model) {
|
|
752
|
+
parts.push("--model", cmdConfig.claude_code.model);
|
|
753
|
+
}
|
|
754
|
+
if (cmdConfig.claude_code.system_prompt) {
|
|
755
|
+
parts.push("-p", shellEscape2(cmdConfig.claude_code.system_prompt));
|
|
756
|
+
}
|
|
757
|
+
const interpolatedPrompt = safeInterpolateCommand2(cmdConfig.command, { params });
|
|
758
|
+
interpolatedCommand = parts.join(" ") + " " + interpolatedPrompt;
|
|
759
|
+
} else {
|
|
760
|
+
interpolatedCommand = safeInterpolateCommand2(cmdConfig.command, { params });
|
|
761
|
+
}
|
|
653
762
|
const timeout = cmdConfig.timeout_ms ?? 3e4;
|
|
654
763
|
const cwd = cmdConfig.working_dir ?? process.cwd();
|
|
655
|
-
let stdout;
|
|
656
764
|
const env = { ...process.env };
|
|
657
765
|
delete env["CLAUDECODE"];
|
|
766
|
+
this.inflight.set(cmdConfig.id, (this.inflight.get(cmdConfig.id) ?? 0) + 1);
|
|
767
|
+
let stdout;
|
|
658
768
|
try {
|
|
659
|
-
const result = await
|
|
769
|
+
const result = await spawnWithKill(interpolatedCommand, {
|
|
660
770
|
timeout,
|
|
661
771
|
cwd,
|
|
662
772
|
env,
|
|
663
773
|
maxBuffer: 10 * 1024 * 1024
|
|
664
774
|
// 10 MB
|
|
665
|
-
});
|
|
775
|
+
}, this.activeProcesses);
|
|
666
776
|
stdout = result.stdout;
|
|
667
777
|
} catch (err) {
|
|
778
|
+
this.decrementInflight(cmdConfig.id);
|
|
668
779
|
if (err instanceof Error) {
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
if (message.includes("timed out") || message.includes("ETIMEDOUT") || err.code === "ETIMEDOUT") {
|
|
780
|
+
const code = err.code;
|
|
781
|
+
if (code === "ETIMEDOUT" || err.message.includes("timed out")) {
|
|
672
782
|
return {
|
|
673
783
|
success: false,
|
|
674
784
|
error: `Command timed out after ${timeout}ms`
|
|
675
785
|
};
|
|
676
786
|
}
|
|
787
|
+
const stderrContent = err.stderr ?? "";
|
|
677
788
|
return {
|
|
678
789
|
success: false,
|
|
679
|
-
error: stderrContent.trim() || message
|
|
790
|
+
error: stderrContent.trim() || err.message
|
|
680
791
|
};
|
|
681
792
|
}
|
|
682
793
|
return {
|
|
@@ -684,6 +795,7 @@ var CommandExecutor = class {
|
|
|
684
795
|
error: String(err)
|
|
685
796
|
};
|
|
686
797
|
}
|
|
798
|
+
this.decrementInflight(cmdConfig.id);
|
|
687
799
|
const rawOutput = stdout.trim();
|
|
688
800
|
switch (cmdConfig.output_type) {
|
|
689
801
|
case "text":
|
|
@@ -708,6 +820,48 @@ var CommandExecutor = class {
|
|
|
708
820
|
};
|
|
709
821
|
}
|
|
710
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Kill all active child processes. Called during service shutdown
|
|
825
|
+
* to prevent zombie processes.
|
|
826
|
+
*/
|
|
827
|
+
shutdown() {
|
|
828
|
+
for (const child of this.activeProcesses) {
|
|
829
|
+
try {
|
|
830
|
+
process.kill(-child.pid, "SIGTERM");
|
|
831
|
+
} catch {
|
|
832
|
+
try {
|
|
833
|
+
child.kill("SIGTERM");
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const pid = child.pid;
|
|
838
|
+
const timer = setTimeout(() => {
|
|
839
|
+
try {
|
|
840
|
+
process.kill(-pid, "SIGKILL");
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
}, KILL_GRACE_MS);
|
|
844
|
+
timer.unref();
|
|
845
|
+
}
|
|
846
|
+
this.activeProcesses.clear();
|
|
847
|
+
}
|
|
848
|
+
/** Returns the number of currently active child processes. */
|
|
849
|
+
get activeCount() {
|
|
850
|
+
return this.activeProcesses.size;
|
|
851
|
+
}
|
|
852
|
+
/** Returns the in-flight count for a specific skill ID. */
|
|
853
|
+
getInflight(skillId) {
|
|
854
|
+
return this.inflight.get(skillId) ?? 0;
|
|
855
|
+
}
|
|
856
|
+
/** Decrement the inflight counter for a skill ID. */
|
|
857
|
+
decrementInflight(skillId) {
|
|
858
|
+
const current = this.inflight.get(skillId) ?? 0;
|
|
859
|
+
if (current <= 1) {
|
|
860
|
+
this.inflight.delete(skillId);
|
|
861
|
+
} else {
|
|
862
|
+
this.inflight.set(skillId, current - 1);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
711
865
|
};
|
|
712
866
|
|
|
713
867
|
// src/runtime/agent-runtime.ts
|
|
@@ -725,6 +879,8 @@ var AgentRuntime = class {
|
|
|
725
879
|
* Undefined if no skills.yaml was provided or the file does not exist.
|
|
726
880
|
*/
|
|
727
881
|
skillExecutor;
|
|
882
|
+
/** Reference to CommandExecutor for shutdown cleanup of child processes. */
|
|
883
|
+
commandExecutor;
|
|
728
884
|
draining = false;
|
|
729
885
|
orphanedEscrowAgeMinutes;
|
|
730
886
|
skillsYamlPath;
|
|
@@ -790,8 +946,8 @@ var AgentRuntime = class {
|
|
|
790
946
|
}
|
|
791
947
|
const modes = /* @__PURE__ */ new Map();
|
|
792
948
|
if (this.conductorEnabled) {
|
|
793
|
-
const { ConductorMode } = await import("./conductor-mode-
|
|
794
|
-
const { registerConductorCard, CONDUCTOR_OWNER } = await import("./card-
|
|
949
|
+
const { ConductorMode } = await import("./conductor-mode-AKREGDIU.js");
|
|
950
|
+
const { registerConductorCard, CONDUCTOR_OWNER } = await import("./card-EX2EYGCZ.js");
|
|
795
951
|
const { loadPeers } = await import("./peers-K7FSHPN3.js");
|
|
796
952
|
registerConductorCard(this.registryDb);
|
|
797
953
|
const resolveAgentUrl = (owner) => {
|
|
@@ -838,10 +994,11 @@ var AgentRuntime = class {
|
|
|
838
994
|
const executor = createSkillExecutor(configs, modes);
|
|
839
995
|
if (hasSkillsYaml) {
|
|
840
996
|
const pipelineExecutor = new PipelineExecutor(executor);
|
|
997
|
+
this.commandExecutor = new CommandExecutor();
|
|
841
998
|
modes.set("api", new ApiExecutor());
|
|
842
999
|
modes.set("pipeline", pipelineExecutor);
|
|
843
1000
|
modes.set("openclaw", new OpenClawBridge());
|
|
844
|
-
modes.set("command",
|
|
1001
|
+
modes.set("command", this.commandExecutor);
|
|
845
1002
|
}
|
|
846
1003
|
this.skillExecutor = executor;
|
|
847
1004
|
}
|
|
@@ -874,6 +1031,9 @@ var AgentRuntime = class {
|
|
|
874
1031
|
return;
|
|
875
1032
|
}
|
|
876
1033
|
this.draining = true;
|
|
1034
|
+
if (this.commandExecutor) {
|
|
1035
|
+
this.commandExecutor.shutdown();
|
|
1036
|
+
}
|
|
877
1037
|
for (const job of this.jobs) {
|
|
878
1038
|
job.stop();
|
|
879
1039
|
}
|
|
@@ -1114,6 +1274,78 @@ function releaseForRelay(creditDb, escrowId) {
|
|
|
1114
1274
|
releaseEscrow(creditDb, escrowId);
|
|
1115
1275
|
}
|
|
1116
1276
|
|
|
1277
|
+
// src/relay/relay-escrow.ts
|
|
1278
|
+
function verifyRelaySignature(data, signature, publicKeyHex) {
|
|
1279
|
+
try {
|
|
1280
|
+
const publicKeyBuf = Buffer.from(publicKeyHex, "hex");
|
|
1281
|
+
return verifyEscrowReceipt(data, signature, publicKeyBuf);
|
|
1282
|
+
} catch {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
function processEscrowHold(creditDb, consumerAgentId, providerAgentId, skillId, amount, requestId, signature, publicKeyHex) {
|
|
1287
|
+
if (signature && publicKeyHex) {
|
|
1288
|
+
const signData = {
|
|
1289
|
+
consumer_agent_id: consumerAgentId,
|
|
1290
|
+
provider_agent_id: providerAgentId,
|
|
1291
|
+
skill_id: skillId,
|
|
1292
|
+
amount,
|
|
1293
|
+
request_id: requestId
|
|
1294
|
+
};
|
|
1295
|
+
if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
|
|
1296
|
+
throw new Error("Invalid consumer signature on escrow hold");
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
const escrowId = holdEscrow(creditDb, consumerAgentId, amount, `${providerAgentId}:${skillId}`);
|
|
1300
|
+
const remaining = getBalance(creditDb, consumerAgentId);
|
|
1301
|
+
return {
|
|
1302
|
+
escrow_id: escrowId,
|
|
1303
|
+
hold_amount: amount,
|
|
1304
|
+
consumer_remaining: remaining
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function processEscrowSettle(creditDb, escrowId, success, providerAgentId, signature, publicKeyHex, consumerAgentId) {
|
|
1308
|
+
if (signature && publicKeyHex && consumerAgentId) {
|
|
1309
|
+
const signData = {
|
|
1310
|
+
escrow_id: escrowId,
|
|
1311
|
+
success,
|
|
1312
|
+
consumer_agent_id: consumerAgentId
|
|
1313
|
+
};
|
|
1314
|
+
if (!verifyRelaySignature(signData, signature, publicKeyHex)) {
|
|
1315
|
+
throw new Error("Invalid consumer signature on escrow settle");
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
const escrowRow = creditDb.prepare("SELECT amount, owner FROM credit_escrow WHERE id = ? AND status = ?").get(escrowId, "held");
|
|
1319
|
+
if (!escrowRow) {
|
|
1320
|
+
throw new Error(`Escrow not found or already settled: ${escrowId}`);
|
|
1321
|
+
}
|
|
1322
|
+
const NETWORK_FEE_RATE = 0.05;
|
|
1323
|
+
if (success) {
|
|
1324
|
+
settleEscrow(creditDb, escrowId, providerAgentId);
|
|
1325
|
+
const networkFee = Math.floor(escrowRow.amount * NETWORK_FEE_RATE);
|
|
1326
|
+
const providerAmount = escrowRow.amount - networkFee;
|
|
1327
|
+
return {
|
|
1328
|
+
escrow_id: escrowId,
|
|
1329
|
+
provider_earned: providerAmount,
|
|
1330
|
+
network_fee: networkFee,
|
|
1331
|
+
consumer_remaining: getBalance(creditDb, escrowRow.owner),
|
|
1332
|
+
provider_balance: getBalance(creditDb, providerAgentId)
|
|
1333
|
+
};
|
|
1334
|
+
} else {
|
|
1335
|
+
releaseEscrow(creditDb, escrowId);
|
|
1336
|
+
return {
|
|
1337
|
+
escrow_id: escrowId,
|
|
1338
|
+
provider_earned: 0,
|
|
1339
|
+
network_fee: 0,
|
|
1340
|
+
consumer_remaining: getBalance(creditDb, escrowRow.owner),
|
|
1341
|
+
provider_balance: getBalance(creditDb, providerAgentId)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function settleWithNetworkFee(creditDb, escrowId, providerOwner) {
|
|
1346
|
+
return processEscrowSettle(creditDb, escrowId, true, providerOwner);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1117
1349
|
// src/hub-agent/relay-bridge.ts
|
|
1118
1350
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1119
1351
|
|
|
@@ -1417,9 +1649,17 @@ var RATE_LIMIT_WINDOW_MS = 6e4;
|
|
|
1417
1649
|
var RELAY_TIMEOUT_MS = 3e5;
|
|
1418
1650
|
function registerWebSocketRelay(server, db, creditDb) {
|
|
1419
1651
|
const connections = /* @__PURE__ */ new Map();
|
|
1652
|
+
const agentIdToOwner = /* @__PURE__ */ new Map();
|
|
1420
1653
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
1421
1654
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
1655
|
+
const agentCapacities = /* @__PURE__ */ new Map();
|
|
1422
1656
|
let onAgentOnlineCallback;
|
|
1657
|
+
function resolveConnectionKey(target) {
|
|
1658
|
+
const ownerFromAgentId = agentIdToOwner.get(target);
|
|
1659
|
+
if (ownerFromAgentId && connections.has(ownerFromAgentId)) return ownerFromAgentId;
|
|
1660
|
+
if (connections.has(target)) return target;
|
|
1661
|
+
return void 0;
|
|
1662
|
+
}
|
|
1423
1663
|
function checkRateLimit(owner) {
|
|
1424
1664
|
const now = Date.now();
|
|
1425
1665
|
const entry = rateLimits.get(owner);
|
|
@@ -1523,6 +1763,20 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1523
1763
|
}
|
|
1524
1764
|
}
|
|
1525
1765
|
connections.set(owner, ws);
|
|
1766
|
+
if (msg.agent_id) {
|
|
1767
|
+
agentIdToOwner.set(msg.agent_id, owner);
|
|
1768
|
+
}
|
|
1769
|
+
if (msg.agents && msg.agents.length > 0) {
|
|
1770
|
+
for (const agentEntry of msg.agents) {
|
|
1771
|
+
agentIdToOwner.set(agentEntry.agent_id, owner);
|
|
1772
|
+
for (const agentCard of agentEntry.cards) {
|
|
1773
|
+
try {
|
|
1774
|
+
upsertCard(agentCard, owner);
|
|
1775
|
+
} catch {
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1526
1780
|
const isEphemeral = owner.includes(":req:");
|
|
1527
1781
|
if (isEphemeral) {
|
|
1528
1782
|
const cardId2 = card.id ?? owner;
|
|
@@ -1566,12 +1820,13 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1566
1820
|
});
|
|
1567
1821
|
return;
|
|
1568
1822
|
}
|
|
1569
|
-
const
|
|
1823
|
+
const targetKey = resolveConnectionKey(msg.target_agent_id ?? msg.target_owner);
|
|
1824
|
+
const targetWs = targetKey ? connections.get(targetKey) : void 0;
|
|
1570
1825
|
if (!targetWs || targetWs.readyState !== 1) {
|
|
1571
1826
|
sendMessage(ws, {
|
|
1572
1827
|
type: "response",
|
|
1573
1828
|
id: msg.id,
|
|
1574
|
-
error: { code: -32603, message: `Agent offline: ${msg.target_owner}` }
|
|
1829
|
+
error: { code: -32603, message: `Agent offline: ${msg.target_agent_id ?? msg.target_owner}` }
|
|
1575
1830
|
});
|
|
1576
1831
|
return;
|
|
1577
1832
|
}
|
|
@@ -1685,7 +1940,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1685
1940
|
if (pending.escrowId && creditDb) {
|
|
1686
1941
|
try {
|
|
1687
1942
|
if (msg.error === void 0) {
|
|
1688
|
-
|
|
1943
|
+
settleWithNetworkFee(creditDb, pending.escrowId, pending.targetOwner);
|
|
1689
1944
|
} else {
|
|
1690
1945
|
releaseForRelay(creditDb, pending.escrowId);
|
|
1691
1946
|
}
|
|
@@ -1722,6 +1977,13 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1722
1977
|
if (!owner) return;
|
|
1723
1978
|
connections.delete(owner);
|
|
1724
1979
|
rateLimits.delete(owner);
|
|
1980
|
+
agentCapacities.delete(owner);
|
|
1981
|
+
for (const [agentId, o] of agentIdToOwner) {
|
|
1982
|
+
if (o === owner) {
|
|
1983
|
+
agentIdToOwner.delete(agentId);
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1725
1987
|
markOwnerOffline(owner);
|
|
1726
1988
|
for (const [reqId, pending] of pendingRequests) {
|
|
1727
1989
|
if (pending.targetOwner === owner) {
|
|
@@ -1755,6 +2017,99 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1755
2017
|
}
|
|
1756
2018
|
}
|
|
1757
2019
|
}
|
|
2020
|
+
function handleHeartbeat(msg) {
|
|
2021
|
+
agentCapacities.set(msg.owner, msg.capacity);
|
|
2022
|
+
}
|
|
2023
|
+
function handleEscrowHold(ws, msg) {
|
|
2024
|
+
if (!creditDb) {
|
|
2025
|
+
sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
try {
|
|
2029
|
+
const result = processEscrowHold(
|
|
2030
|
+
creditDb,
|
|
2031
|
+
msg.consumer_agent_id,
|
|
2032
|
+
msg.provider_agent_id,
|
|
2033
|
+
msg.skill_id,
|
|
2034
|
+
msg.amount,
|
|
2035
|
+
msg.request_id,
|
|
2036
|
+
msg.signature,
|
|
2037
|
+
msg.public_key
|
|
2038
|
+
);
|
|
2039
|
+
sendMessage(ws, {
|
|
2040
|
+
type: "escrow_hold_confirmed",
|
|
2041
|
+
request_id: msg.request_id,
|
|
2042
|
+
escrow_id: result.escrow_id,
|
|
2043
|
+
hold_amount: result.hold_amount,
|
|
2044
|
+
consumer_remaining: result.consumer_remaining
|
|
2045
|
+
});
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
const errMsg = err instanceof Error ? err.message : "Escrow hold failed";
|
|
2048
|
+
const code = errMsg.includes("INSUFFICIENT_CREDITS") ? "insufficient_credits" : "escrow_hold_failed";
|
|
2049
|
+
sendMessage(ws, { type: "error", code, message: errMsg, request_id: msg.request_id });
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function handleEscrowSettle(ws, msg) {
|
|
2053
|
+
if (!creditDb) {
|
|
2054
|
+
sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
try {
|
|
2058
|
+
const escrow = creditDb.prepare("SELECT card_id FROM credit_escrow WHERE id = ? AND status = ?").get(msg.escrow_id, "held");
|
|
2059
|
+
if (!escrow) {
|
|
2060
|
+
sendMessage(ws, { type: "error", code: "escrow_not_found", message: `Escrow not found: ${msg.escrow_id}`, request_id: msg.request_id });
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
const providerAgentId = escrow.card_id.split(":")[0];
|
|
2064
|
+
const result = processEscrowSettle(
|
|
2065
|
+
creditDb,
|
|
2066
|
+
msg.escrow_id,
|
|
2067
|
+
msg.success,
|
|
2068
|
+
providerAgentId,
|
|
2069
|
+
msg.signature,
|
|
2070
|
+
msg.public_key,
|
|
2071
|
+
msg.consumer_agent_id
|
|
2072
|
+
);
|
|
2073
|
+
sendMessage(ws, {
|
|
2074
|
+
type: "escrow_settled",
|
|
2075
|
+
escrow_id: result.escrow_id,
|
|
2076
|
+
request_id: msg.request_id,
|
|
2077
|
+
provider_earned: result.provider_earned,
|
|
2078
|
+
network_fee: result.network_fee,
|
|
2079
|
+
consumer_remaining: result.consumer_remaining,
|
|
2080
|
+
provider_balance: result.provider_balance
|
|
2081
|
+
});
|
|
2082
|
+
const providerKey = resolveConnectionKey(providerAgentId);
|
|
2083
|
+
if (providerKey) {
|
|
2084
|
+
const providerWs = connections.get(providerKey);
|
|
2085
|
+
if (providerWs && providerWs.readyState === 1) {
|
|
2086
|
+
sendMessage(providerWs, {
|
|
2087
|
+
type: "escrow_settled",
|
|
2088
|
+
escrow_id: result.escrow_id,
|
|
2089
|
+
request_id: msg.request_id,
|
|
2090
|
+
provider_earned: result.provider_earned,
|
|
2091
|
+
network_fee: result.network_fee,
|
|
2092
|
+
consumer_remaining: result.consumer_remaining,
|
|
2093
|
+
provider_balance: result.provider_balance
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
sendMessage(ws, { type: "error", code: "escrow_settle_failed", message: err instanceof Error ? err.message : "Settlement failed", request_id: msg.request_id });
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
function handleBalanceSync(ws, msg) {
|
|
2102
|
+
if (!creditDb) {
|
|
2103
|
+
sendMessage(ws, { type: "error", code: "no_credit_db", message: "Credit system not available" });
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
const balance = getBalance(creditDb, msg.agent_id);
|
|
2107
|
+
sendMessage(ws, {
|
|
2108
|
+
type: "balance_sync_response",
|
|
2109
|
+
agent_id: msg.agent_id,
|
|
2110
|
+
balance
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
1758
2113
|
void server.register(async (app) => {
|
|
1759
2114
|
app.get("/ws", { websocket: true }, (rawSocket, _request) => {
|
|
1760
2115
|
const socket = rawSocket;
|
|
@@ -1800,15 +2155,33 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1800
2155
|
case "relay_progress":
|
|
1801
2156
|
handleRelayProgress(msg);
|
|
1802
2157
|
break;
|
|
2158
|
+
case "heartbeat":
|
|
2159
|
+
handleHeartbeat(msg);
|
|
2160
|
+
break;
|
|
2161
|
+
// V8 Phase 2: Explicit escrow messages
|
|
2162
|
+
case "escrow_hold":
|
|
2163
|
+
handleEscrowHold(socket, msg);
|
|
2164
|
+
break;
|
|
2165
|
+
case "escrow_settle":
|
|
2166
|
+
handleEscrowSettle(socket, msg);
|
|
2167
|
+
break;
|
|
2168
|
+
case "balance_sync":
|
|
2169
|
+
handleBalanceSync(socket, msg);
|
|
2170
|
+
break;
|
|
1803
2171
|
default:
|
|
1804
2172
|
break;
|
|
1805
2173
|
}
|
|
1806
2174
|
})();
|
|
1807
2175
|
});
|
|
2176
|
+
const pingInterval = setInterval(() => {
|
|
2177
|
+
if (socket.readyState === 1) socket.ping();
|
|
2178
|
+
}, 3e4);
|
|
1808
2179
|
socket.on("close", () => {
|
|
2180
|
+
clearInterval(pingInterval);
|
|
1809
2181
|
handleDisconnect(registeredOwner);
|
|
1810
2182
|
});
|
|
1811
2183
|
socket.on("error", () => {
|
|
2184
|
+
clearInterval(pingInterval);
|
|
1812
2185
|
handleDisconnect(registeredOwner);
|
|
1813
2186
|
});
|
|
1814
2187
|
});
|
|
@@ -1829,6 +2202,7 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1829
2202
|
}
|
|
1830
2203
|
pendingRequests.clear();
|
|
1831
2204
|
rateLimits.clear();
|
|
2205
|
+
agentCapacities.clear();
|
|
1832
2206
|
},
|
|
1833
2207
|
setOnAgentOnline: (cb) => {
|
|
1834
2208
|
onAgentOnlineCallback = cb;
|
|
@@ -1837,7 +2211,9 @@ function registerWebSocketRelay(server, db, creditDb) {
|
|
|
1837
2211
|
getPendingRequests: () => pendingRequests,
|
|
1838
2212
|
sendMessage: (ws, msg) => {
|
|
1839
2213
|
sendMessage(ws, msg);
|
|
1840
|
-
}
|
|
2214
|
+
},
|
|
2215
|
+
getAgentCapacity: (owner) => agentCapacities.get(owner),
|
|
2216
|
+
getAllCapacities: () => new Map(agentCapacities)
|
|
1841
2217
|
};
|
|
1842
2218
|
}
|
|
1843
2219
|
|
|
@@ -3108,7 +3484,7 @@ function createRegistryServer(opts) {
|
|
|
3108
3484
|
openapi: "3.0.3",
|
|
3109
3485
|
info: {
|
|
3110
3486
|
title: "AgentBnB Registry API",
|
|
3111
|
-
description: "
|
|
3487
|
+
description: "Where AI agents hire AI agents \u2014 discover, publish, and coordinate agent capabilities",
|
|
3112
3488
|
version: "3.1.6"
|
|
3113
3489
|
},
|
|
3114
3490
|
servers: [{ url: "/", description: "Registry server" }],
|
|
@@ -3140,7 +3516,7 @@ function createRegistryServer(opts) {
|
|
|
3140
3516
|
});
|
|
3141
3517
|
void server.register(cors, {
|
|
3142
3518
|
origin: true,
|
|
3143
|
-
methods: ["GET", "POST", "PATCH", "OPTIONS"],
|
|
3519
|
+
methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
|
|
3144
3520
|
allowedHeaders: ["Content-Type", "Authorization", "X-Agent-PublicKey", "X-Agent-Signature", "X-Agent-Timestamp"]
|
|
3145
3521
|
});
|
|
3146
3522
|
void server.register(fastifyWebsocket);
|
|
@@ -3303,7 +3679,9 @@ function createRegistryServer(opts) {
|
|
|
3303
3679
|
const trustStmt = db.prepare(`
|
|
3304
3680
|
SELECT cc.owner,
|
|
3305
3681
|
COUNT(rl.id) as total_exec,
|
|
3306
|
-
SUM(CASE WHEN rl.status IN ('success','failure','timeout','refunded')
|
|
3682
|
+
SUM(CASE WHEN rl.status IN ('success','failure','timeout','refunded')
|
|
3683
|
+
AND (rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error'))
|
|
3684
|
+
THEN 1 ELSE 0 END) as terminal_exec,
|
|
3307
3685
|
SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as success_exec,
|
|
3308
3686
|
AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency
|
|
3309
3687
|
FROM capability_cards cc
|
|
@@ -3537,21 +3915,32 @@ function createRegistryServer(opts) {
|
|
|
3537
3915
|
api.delete("/cards/:id", {
|
|
3538
3916
|
schema: {
|
|
3539
3917
|
tags: ["cards"],
|
|
3540
|
-
summary: "Delete a capability card",
|
|
3918
|
+
summary: "Delete a capability card (requires Bearer auth)",
|
|
3919
|
+
security: [{ bearerAuth: [] }],
|
|
3541
3920
|
params: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
3542
3921
|
response: {
|
|
3543
|
-
|
|
3922
|
+
204: { type: "null", description: "Card deleted successfully" },
|
|
3923
|
+
401: { type: "object", properties: { error: { type: "string" } } },
|
|
3924
|
+
403: { type: "object", properties: { error: { type: "string" } } },
|
|
3544
3925
|
404: { type: "object", properties: { error: { type: "string" } } }
|
|
3545
3926
|
}
|
|
3546
3927
|
}
|
|
3547
3928
|
}, async (request, reply) => {
|
|
3929
|
+
const auth = request.headers.authorization;
|
|
3930
|
+
const token = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : null;
|
|
3931
|
+
if (!token || !opts.ownerApiKey || token !== opts.ownerApiKey) {
|
|
3932
|
+
return reply.code(401).send({ error: "Unauthorized" });
|
|
3933
|
+
}
|
|
3548
3934
|
const { id } = request.params;
|
|
3549
3935
|
const card = getCard(db, id);
|
|
3550
3936
|
if (!card) {
|
|
3551
3937
|
return reply.code(404).send({ error: "Not found" });
|
|
3552
3938
|
}
|
|
3939
|
+
if (opts.ownerName && card.owner !== opts.ownerName) {
|
|
3940
|
+
return reply.code(403).send({ error: "Forbidden" });
|
|
3941
|
+
}
|
|
3553
3942
|
db.prepare("DELETE FROM capability_cards WHERE id = ?").run(id);
|
|
3554
|
-
return reply.send(
|
|
3943
|
+
return reply.code(204).send();
|
|
3555
3944
|
});
|
|
3556
3945
|
api.get("/api/agents", {
|
|
3557
3946
|
schema: {
|
|
@@ -3636,7 +4025,8 @@ function createRegistryServer(opts) {
|
|
|
3636
4025
|
const lastActive = lastActiveRow?.last_req ?? memberRow?.latest ?? joinedAt;
|
|
3637
4026
|
const metricsStmt = db.prepare(`
|
|
3638
4027
|
SELECT
|
|
3639
|
-
|
|
4028
|
+
SUM(CASE WHEN rl.failure_reason IS NULL OR rl.failure_reason IN ('bad_execution','auth_error')
|
|
4029
|
+
THEN 1 ELSE 0 END) as total,
|
|
3640
4030
|
SUM(CASE WHEN rl.status = 'success' THEN 1 ELSE 0 END) as successes,
|
|
3641
4031
|
AVG(CASE WHEN rl.status = 'success' THEN rl.latency_ms END) as avg_latency,
|
|
3642
4032
|
COUNT(DISTINCT rl.requester) as unique_requesters,
|
|
@@ -4229,6 +4619,110 @@ function createRegistryServer(opts) {
|
|
|
4229
4619
|
});
|
|
4230
4620
|
});
|
|
4231
4621
|
}
|
|
4622
|
+
api.get("/api/providers/:owner/reliability", {
|
|
4623
|
+
schema: {
|
|
4624
|
+
tags: ["agents"],
|
|
4625
|
+
summary: "Get provider reliability metrics",
|
|
4626
|
+
params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
|
|
4627
|
+
response: {
|
|
4628
|
+
200: {
|
|
4629
|
+
type: "object",
|
|
4630
|
+
properties: {
|
|
4631
|
+
current_streak: { type: "integer" },
|
|
4632
|
+
longest_streak: { type: "integer" },
|
|
4633
|
+
total_hires: { type: "integer" },
|
|
4634
|
+
repeat_hires: { type: "integer" },
|
|
4635
|
+
repeat_hire_rate: { type: "number" },
|
|
4636
|
+
avg_feedback_score: { type: "number" },
|
|
4637
|
+
availability_rate: { type: "number" }
|
|
4638
|
+
}
|
|
4639
|
+
},
|
|
4640
|
+
404: { type: "object", properties: { error: { type: "string" } } }
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
}, async (request, reply) => {
|
|
4644
|
+
const { owner } = request.params;
|
|
4645
|
+
if (!opts.creditDb) {
|
|
4646
|
+
return reply.code(404).send({ error: "Credit system not enabled" });
|
|
4647
|
+
}
|
|
4648
|
+
const { getReliabilityMetrics } = await import("./reliability-metrics-QG7WC5QK.js");
|
|
4649
|
+
const metrics = getReliabilityMetrics(opts.creditDb, owner);
|
|
4650
|
+
if (!metrics) {
|
|
4651
|
+
return reply.code(404).send({ error: "No reliability data for this provider" });
|
|
4652
|
+
}
|
|
4653
|
+
return reply.send(metrics);
|
|
4654
|
+
});
|
|
4655
|
+
api.get("/api/fleet/:owner", {
|
|
4656
|
+
schema: {
|
|
4657
|
+
tags: ["agents"],
|
|
4658
|
+
summary: "Agent fleet overview for an owner",
|
|
4659
|
+
params: { type: "object", properties: { owner: { type: "string" } }, required: ["owner"] },
|
|
4660
|
+
response: {
|
|
4661
|
+
200: { type: "object", properties: { agents: { type: "array" } } }
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
}, async (request, reply) => {
|
|
4665
|
+
const { owner } = request.params;
|
|
4666
|
+
const rows = db.prepare(
|
|
4667
|
+
"SELECT id, data FROM capability_cards WHERE owner = ?"
|
|
4668
|
+
).all(owner);
|
|
4669
|
+
const agents = [];
|
|
4670
|
+
for (const row of rows) {
|
|
4671
|
+
try {
|
|
4672
|
+
const card = JSON.parse(row.data);
|
|
4673
|
+
let earnings = 0;
|
|
4674
|
+
let spend = 0;
|
|
4675
|
+
if (opts.creditDb) {
|
|
4676
|
+
const earningRow = opts.creditDb.prepare(
|
|
4677
|
+
"SELECT COALESCE(SUM(amount), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'settlement' AND amount > 0"
|
|
4678
|
+
).get(owner);
|
|
4679
|
+
earnings = earningRow.total;
|
|
4680
|
+
const spendRow = opts.creditDb.prepare(
|
|
4681
|
+
"SELECT COALESCE(SUM(ABS(amount)), 0) as total FROM credit_transactions WHERE owner = ? AND reason = 'escrow_hold'"
|
|
4682
|
+
).get(owner);
|
|
4683
|
+
spend = spendRow.total;
|
|
4684
|
+
}
|
|
4685
|
+
const successCount = db.prepare(
|
|
4686
|
+
"SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status = 'success' AND (action_type IS NULL OR action_type = 'auto_share')"
|
|
4687
|
+
).get(row.id).cnt;
|
|
4688
|
+
const failureCount = db.prepare(
|
|
4689
|
+
"SELECT COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status IN ('failure', 'timeout', 'refunded') AND (action_type IS NULL OR action_type = 'auto_share')"
|
|
4690
|
+
).get(row.id).cnt;
|
|
4691
|
+
const totalExec = successCount + failureCount;
|
|
4692
|
+
const successRate = totalExec > 0 ? successCount / totalExec : 0;
|
|
4693
|
+
let failureBreakdown = {};
|
|
4694
|
+
try {
|
|
4695
|
+
const failureRows = db.prepare(
|
|
4696
|
+
"SELECT failure_reason, COUNT(*) as cnt FROM request_log WHERE card_id = ? AND status IN ('failure', 'timeout', 'refunded') AND failure_reason IS NOT NULL GROUP BY failure_reason"
|
|
4697
|
+
).all(row.id);
|
|
4698
|
+
for (const fr of failureRows) {
|
|
4699
|
+
failureBreakdown[fr.failure_reason] = fr.cnt;
|
|
4700
|
+
}
|
|
4701
|
+
} catch {
|
|
4702
|
+
}
|
|
4703
|
+
let reliability = null;
|
|
4704
|
+
if (opts.creditDb) {
|
|
4705
|
+
const { getReliabilityMetrics } = await import("./reliability-metrics-QG7WC5QK.js");
|
|
4706
|
+
reliability = getReliabilityMetrics(opts.creditDb, owner);
|
|
4707
|
+
}
|
|
4708
|
+
agents.push({
|
|
4709
|
+
id: row.id,
|
|
4710
|
+
name: card.name ?? card.agent_name ?? owner,
|
|
4711
|
+
online: card.availability?.online ?? false,
|
|
4712
|
+
current_load: 0,
|
|
4713
|
+
// Will be populated from relay heartbeat data in future
|
|
4714
|
+
success_rate: successRate,
|
|
4715
|
+
total_executions: totalExec,
|
|
4716
|
+
earnings,
|
|
4717
|
+
spend,
|
|
4718
|
+
failure_breakdown: failureBreakdown,
|
|
4719
|
+
reliability
|
|
4720
|
+
});
|
|
4721
|
+
} catch {
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
return reply.send({ agents });
|
|
4725
|
+
});
|
|
4232
4726
|
});
|
|
4233
4727
|
return { server, relayState };
|
|
4234
4728
|
}
|
|
@@ -4327,7 +4821,7 @@ var IdleMonitor = class {
|
|
|
4327
4821
|
};
|
|
4328
4822
|
|
|
4329
4823
|
// src/runtime/service-coordinator.ts
|
|
4330
|
-
import { spawn } from "child_process";
|
|
4824
|
+
import { spawn as spawn2 } from "child_process";
|
|
4331
4825
|
import { createRequire } from "module";
|
|
4332
4826
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
4333
4827
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -4355,10 +4849,14 @@ var ServiceCoordinator = class {
|
|
|
4355
4849
|
if (health.ok && health.agentbnb) {
|
|
4356
4850
|
return "already_running";
|
|
4357
4851
|
}
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4852
|
+
if (opts?.foreground) {
|
|
4853
|
+
this.guard.release();
|
|
4854
|
+
} else {
|
|
4855
|
+
throw new AgentBnBError(
|
|
4856
|
+
`AgentBnB lock exists but health check failed (pid=${running.pid}, port=${running.port})`,
|
|
4857
|
+
"SERVICE_UNHEALTHY"
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4362
4860
|
}
|
|
4363
4861
|
if (opts?.foreground) {
|
|
4364
4862
|
return this.startInProcess(opts);
|
|
@@ -4473,7 +4971,7 @@ var ServiceCoordinator = class {
|
|
|
4473
4971
|
console.log("Conductor mode enabled \u2014 orchestrate/plan skills available via gateway");
|
|
4474
4972
|
}
|
|
4475
4973
|
if (opts.conductorEnabled && this.config.conductor?.public) {
|
|
4476
|
-
const { buildConductorCard } = await import("./card-
|
|
4974
|
+
const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
|
|
4477
4975
|
const conductorCard = buildConductorCard(this.config.owner);
|
|
4478
4976
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4479
4977
|
const existing = this.runtime.registryDb.prepare("SELECT id FROM capability_cards WHERE id = ?").get(conductorCard.id);
|
|
@@ -4530,8 +5028,8 @@ var ServiceCoordinator = class {
|
|
|
4530
5028
|
}
|
|
4531
5029
|
}
|
|
4532
5030
|
if (opts.registryUrl && opts.relay) {
|
|
4533
|
-
const { RelayClient } = await import("./websocket-client-
|
|
4534
|
-
const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-
|
|
5031
|
+
const { RelayClient } = await import("./websocket-client-QOVARTRN.js");
|
|
5032
|
+
const { executeCapabilityRequest: executeCapabilityRequest2 } = await import("./execute-AYQWORVH.js");
|
|
4535
5033
|
const cards = listCards(this.runtime.registryDb, this.config.owner);
|
|
4536
5034
|
const card = cards[0] ?? {
|
|
4537
5035
|
id: randomUUID6(),
|
|
@@ -4547,7 +5045,7 @@ var ServiceCoordinator = class {
|
|
|
4547
5045
|
};
|
|
4548
5046
|
const additionalCards = [];
|
|
4549
5047
|
if (this.config.conductor?.public) {
|
|
4550
|
-
const { buildConductorCard } = await import("./card-
|
|
5048
|
+
const { buildConductorCard } = await import("./card-EX2EYGCZ.js");
|
|
4551
5049
|
additionalCards.push(
|
|
4552
5050
|
buildConductorCard(this.config.owner)
|
|
4553
5051
|
);
|
|
@@ -4556,6 +5054,7 @@ var ServiceCoordinator = class {
|
|
|
4556
5054
|
this.relayClient = new RelayClient({
|
|
4557
5055
|
registryUrl: opts.registryUrl,
|
|
4558
5056
|
owner: this.config.owner,
|
|
5057
|
+
agent_id: this.config.agent_id,
|
|
4559
5058
|
token: this.config.token,
|
|
4560
5059
|
card,
|
|
4561
5060
|
cards: additionalCards.length > 0 ? additionalCards : void 0,
|
|
@@ -4638,7 +5137,7 @@ var ServiceCoordinator = class {
|
|
|
4638
5137
|
const runtime = loadPersistedRuntime(getConfigDir());
|
|
4639
5138
|
const nodeExec = resolveNodeExecutable(runtime);
|
|
4640
5139
|
const cliArgs = resolveCliLaunchArgs(this.buildServeArgs(opts));
|
|
4641
|
-
const child =
|
|
5140
|
+
const child = spawn2(nodeExec, cliArgs, {
|
|
4642
5141
|
detached: true,
|
|
4643
5142
|
stdio: "ignore",
|
|
4644
5143
|
env: { ...process.env }
|
|
@@ -4797,7 +5296,6 @@ var ServiceCoordinator = class {
|
|
|
4797
5296
|
})();
|
|
4798
5297
|
};
|
|
4799
5298
|
};
|
|
4800
|
-
var require2 = createRequire(import.meta.url);
|
|
4801
5299
|
function loadPersistedRuntime(configDir) {
|
|
4802
5300
|
const runtimePath = join2(configDir, "runtime.json");
|
|
4803
5301
|
if (!existsSync3(runtimePath)) return null;
|
|
@@ -4825,6 +5323,12 @@ function resolveNodeExecutable(runtime) {
|
|
|
4825
5323
|
return process.execPath;
|
|
4826
5324
|
}
|
|
4827
5325
|
function resolveCliLaunchArgs(serveArgs) {
|
|
5326
|
+
const require2 = createRequire(import.meta.url);
|
|
5327
|
+
try {
|
|
5328
|
+
const distCli2 = require2.resolve("agentbnb/dist/cli/index.js");
|
|
5329
|
+
return [distCli2, "serve", ...serveArgs];
|
|
5330
|
+
} catch {
|
|
5331
|
+
}
|
|
4828
5332
|
const projectRoot = resolve(dirname2(fileURLToPath2(import.meta.url)), "..", "..");
|
|
4829
5333
|
const distCli = join2(projectRoot, "dist", "cli", "index.js");
|
|
4830
5334
|
if (existsSync3(distCli)) {
|