@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: `bivy-room-v1:${targetId}:${seal(pairingStore.roomKey(), input.instructions)}`,
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
- const machine = typeof routing.nodeLabel === "string" && routing.nodeLabel ? routing.nodeLabel : identity.name;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.18.11-staging.11",
3
+ "version": "0.18.11-staging.13",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",