@bivy/bivy 0.18.11-staging.11 → 0.18.11-staging.13
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.
|
@@ -121,8 +121,13 @@ export async function fetchPendingWork(cfg) {
|
|
|
121
121
|
/** Consume the authoritative claim snapshot, not the earlier queue listing. */
|
|
122
122
|
export async function claimWork(cfg, id) {
|
|
123
123
|
const res = await cp(cfg, "POST", `/node/work/${encodeURIComponent(id)}/claim`, randomUUID());
|
|
124
|
-
if (!res.ok)
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
// 409 is the normal "another node won" race; anything else would otherwise
|
|
126
|
+
// leave an item silently pending forever.
|
|
127
|
+
if (res.status !== 409)
|
|
128
|
+
console.warn(`[control-plane-tasks] claim ${id} failed: HTTP ${res.status}`);
|
|
125
129
|
return undefined;
|
|
130
|
+
}
|
|
126
131
|
const data = await res.json();
|
|
127
132
|
return data.item ?? { id }; // compatibility with older control planes
|
|
128
133
|
}
|
package/dist/server.js
CHANGED
|
@@ -545,7 +545,10 @@ const runDelegation = new RunDelegationService({
|
|
|
545
545
|
method: "POST",
|
|
546
546
|
body: JSON.stringify({
|
|
547
547
|
title: "Delegated Run",
|
|
548
|
-
body
|
|
548
|
+
// Only the target machine can open its Run body, so seal with ITS room
|
|
549
|
+
// key: this machine's own for local work, the sibling's (learned over
|
|
550
|
+
// the sealed pairing handshake) for another machine.
|
|
551
|
+
body: `bivy-room-v1:${targetId}:${seal(targetId === identity.nodeId ? pairingStore.roomKey() : await siblingRoomKey(targetId), input.instructions)}`,
|
|
549
552
|
repo: input.repo,
|
|
550
553
|
node: machine,
|
|
551
554
|
runtimeId: input.agent,
|
|
@@ -568,7 +571,8 @@ const runDelegation = new RunDelegationService({
|
|
|
568
571
|
if (!sessionId)
|
|
569
572
|
return undefined;
|
|
570
573
|
const routing = raw.routing && typeof raw.routing === "object" ? raw.routing : {};
|
|
571
|
-
|
|
574
|
+
// The queue routes by label `bivy/<machine>`.
|
|
575
|
+
const machine = typeof routing.nodeLabel === "string" && routing.nodeLabel ? routing.nodeLabel.replace(/^bivy\//, "") : identity.name;
|
|
572
576
|
if (machine === identity.name)
|
|
573
577
|
return delegationAnswer(sessionId);
|
|
574
578
|
const target = (await accountMachines()).find((m) => m.name === machine);
|
|
@@ -604,6 +608,19 @@ async function siblingRequest(nodeId, command, timeoutMs = 20_000) {
|
|
|
604
608
|
client.close();
|
|
605
609
|
}
|
|
606
610
|
}
|
|
611
|
+
/** Another machine's room key, from a sealed pairing with it. */
|
|
612
|
+
async function siblingRoomKey(nodeId) {
|
|
613
|
+
if (!sessionAdvertiseTarget)
|
|
614
|
+
throw new Error("This machine is not signed in to an account.");
|
|
615
|
+
const client = new SiblingClient({ controlPlaneUrl: sessionAdvertiseTarget.controlPlaneUrl, enrollmentToken: sessionAdvertiseTarget.enrollmentToken, siblingNodeId: nodeId, label: "Bivy delegation" });
|
|
616
|
+
try {
|
|
617
|
+
await client.connect();
|
|
618
|
+
return client.roomKey();
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
client.close();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
607
624
|
/** Agents this machine can run a delegated task with (installed ones). */
|
|
608
625
|
function localAgents() {
|
|
609
626
|
return runtimeList().filter((rt) => rt.status === "available").map((rt) => ({ id: rt.id, name: String(rt.displayName || rt.id) }));
|
|
@@ -4849,8 +4866,16 @@ async function runSessionTurn(record, prompt, signal) {
|
|
|
4849
4866
|
if (signal?.aborted)
|
|
4850
4867
|
throw signal.reason ?? new Error("Run cancelled");
|
|
4851
4868
|
let unsubscribe = () => { };
|
|
4869
|
+
// A turn the agent failed (usage limit, auth, crash) still ends with
|
|
4870
|
+
// agent_end; remember why so the Run fails instead of reporting success.
|
|
4871
|
+
let turnError = "";
|
|
4852
4872
|
const finished = new Promise((resolve) => {
|
|
4853
4873
|
unsubscribe = record.session.subscribe((event) => {
|
|
4874
|
+
const e = event;
|
|
4875
|
+
if (e.type === "session.error" && typeof e.error === "string")
|
|
4876
|
+
turnError ||= e.error;
|
|
4877
|
+
if (e.type === "message_end" && e.message?.stopReason === "error" && typeof e.message.errorMessage === "string")
|
|
4878
|
+
turnError ||= e.message.errorMessage;
|
|
4854
4879
|
if (event.type === "agent_end")
|
|
4855
4880
|
resolve();
|
|
4856
4881
|
});
|
|
@@ -4865,6 +4890,8 @@ async function runSessionTurn(record, prompt, signal) {
|
|
|
4865
4890
|
try {
|
|
4866
4891
|
await turnWatchdog.promptWithWatchdog(record, prompt);
|
|
4867
4892
|
await (signal ? Promise.race([finished, cancelled]) : finished);
|
|
4893
|
+
if (turnError)
|
|
4894
|
+
throw new Error(turnError);
|
|
4868
4895
|
}
|
|
4869
4896
|
finally {
|
|
4870
4897
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -5324,6 +5351,10 @@ async function delegationAnswer(sessionId) {
|
|
|
5324
5351
|
const message = messages[i];
|
|
5325
5352
|
if (message?.role !== "assistant")
|
|
5326
5353
|
continue;
|
|
5354
|
+
// A turn the agent failed (usage limit, auth) ends on an error marker, not
|
|
5355
|
+
// prose: hand the parent the reason instead of an empty answer.
|
|
5356
|
+
if (message.stopReason === "error" && typeof message.errorMessage === "string" && message.errorMessage.trim())
|
|
5357
|
+
return `The delegated agent failed: ${message.errorMessage.trim()}`;
|
|
5327
5358
|
const text = runtimeContentText(message.content).trim();
|
|
5328
5359
|
if (text)
|
|
5329
5360
|
return text;
|
|
@@ -30,6 +30,7 @@ export class SiblingClient {
|
|
|
30
30
|
opts;
|
|
31
31
|
ws;
|
|
32
32
|
cipher;
|
|
33
|
+
siblingRoomKey;
|
|
33
34
|
keypair = newDeviceKeypair();
|
|
34
35
|
reassembler = new FrameReassembler({ onReject: () => this.ws?.close() });
|
|
35
36
|
transfers = new EventTransferReceiver({
|
|
@@ -147,6 +148,7 @@ export class SiblingClient {
|
|
|
147
148
|
if (k === "pair.welcome") {
|
|
148
149
|
try {
|
|
149
150
|
const roomKey = acceptWelcome(this.keypair, { nodePublicKeyB64: String(p.nodePublicKeyB64 ?? ""), wrapped: String(p.wrapped ?? "") });
|
|
151
|
+
this.siblingRoomKey = roomKey;
|
|
150
152
|
this.cipher = new RoomCipher(roomKey);
|
|
151
153
|
this.resolvePaired?.();
|
|
152
154
|
}
|
|
@@ -169,6 +171,13 @@ export class SiblingClient {
|
|
|
169
171
|
}
|
|
170
172
|
this.opts.onEvent?.(event);
|
|
171
173
|
}
|
|
174
|
+
/** The sibling's room key, once paired: what seals content only it can read
|
|
175
|
+
* (e.g. a delegated Run's instructions queued for that machine). */
|
|
176
|
+
roomKey() {
|
|
177
|
+
if (!this.siblingRoomKey)
|
|
178
|
+
throw new Error("sibling client not paired");
|
|
179
|
+
return this.siblingRoomKey;
|
|
180
|
+
}
|
|
172
181
|
/** Send a command and await its correlated reply event (by requestId). */
|
|
173
182
|
request(command, timeoutMs = 30_000) {
|
|
174
183
|
const requestId = `repl-${++this.reqSeq}-${Date.now()}`;
|
package/package.json
CHANGED