@ouro.bot/cli 0.1.0-alpha.989 → 0.1.0-alpha.990

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.
@@ -44,6 +44,7 @@ const client_1 = require("../a2a/client");
44
44
  const onboarding_1 = require("../a2a/onboarding");
45
45
  const identity_2 = require("../a2a/identity");
46
46
  const transport_1 = require("../a2a/transport");
47
+ const relay_contacts_1 = require("../a2a/relay-contacts");
47
48
  const delegation_stores_1 = require("../a2a/delegation-stores");
48
49
  const mission_result_wire_1 = require("../a2a/mission-result-wire");
49
50
  const outboundTaskTokens = new Map();
@@ -190,9 +191,10 @@ async function sealEnvelopeToPeer(input) {
190
191
  message: "sealing outbound A2A envelope to a DID-keyed peer",
191
192
  meta: { selfDid: input.self.did, peerDid: input.did, rung: reachability.rung, friendsKind: input.friendsKind },
192
193
  });
194
+ const relay = relayTransportFor(input);
193
195
  const result = await (0, a2a_client_1.sendShare)({
194
196
  sodium: input.sodium,
195
- transport: (0, transport_1.makeA2ATransport)(),
197
+ transport: relay.transport,
196
198
  fromIdentity: input.self,
197
199
  toPeer: { a2a: input.peer.agentMeta?.a2a },
198
200
  recipientDid: input.did,
@@ -204,6 +206,36 @@ async function sealEnvelopeToPeer(input) {
204
206
  if (!result.ok) {
205
207
  throw new Error(`sealed send failed (${result.reason})`);
206
208
  }
209
+ return relay.delivered();
210
+ }
211
+ /**
212
+ * The transport for one send to a DID-keyed peer: direct first, then the relay contact the peer shared (if cached). A
213
+ * relay that refuses the contact marks it stale. After a direct delivery, the peer is online, so its contact is
214
+ * refreshed in the background when it is missing, stale or a day old.
215
+ */
216
+ function relayTransportFor(input) {
217
+ const cached = (0, relay_contacts_1.readRelayContact)(input.agentRoot, input.did);
218
+ let delivery = { rung: "direct", directMayHaveArrived: false };
219
+ const transport = (0, transport_1.makeA2ATransport)({
220
+ ...(cached?.contact ? { relayContact: cached.contact } : {}),
221
+ onRelayContactRefused: () => (0, relay_contacts_1.markRelayContactStale)(input.agentRoot, input.did),
222
+ onDelivered: (rung, detail) => {
223
+ delivery = { rung, directMayHaveArrived: detail?.directMayHaveArrived ?? false };
224
+ const endpointUrl = input.peer.agentMeta?.a2a?.endpointUrl;
225
+ if (rung !== "direct" || !endpointUrl || !(0, relay_contacts_1.relayContactNeedsRefresh)(cached, Date.now()))
226
+ return;
227
+ const cardUrl = input.peer.agentMeta?.a2a?.cardUrl ?? new URL("/.well-known/agent-card.json", endpointUrl).toString();
228
+ void (0, relay_contacts_1.refreshRelayContact)({ agentRoot: input.agentRoot, peerDid: input.did, cardUrl, identity: input.self });
229
+ },
230
+ });
231
+ return { transport, delivered: () => delivery };
232
+ }
233
+ /** The tool wording for a sealed delivery. */
234
+ function deliveryText(peerName, did, delivery, what = "sealed message") {
235
+ if (delivery.rung === "direct")
236
+ return `${what} delivered to ${peerName} (${did}) over the direct rung.`;
237
+ const head = delivery.directMayHaveArrived ? `direct delivery to ${peerName} (${did}) failed and the ${what} may have arrived; a copy` : `${peerName} (${did}) is not reachable directly; the ${what}`;
238
+ return `${head} is queued at ${peerName}'s relay and is delivered when ${peerName} comes back online.`;
207
239
  }
208
240
  /**
209
241
  * Seal an outbound free-text message to a DID-keyed peer over the `direct` rung. The
@@ -221,8 +253,8 @@ async function sealAndSendToPeer(input) {
221
253
  note: input.message,
222
254
  issuedAt: new Date().toISOString(),
223
255
  };
224
- await sealEnvelopeToPeer({
225
- self: { did: self.did, keyId: self.keyId, ed25519Priv: self.ed25519Priv },
256
+ return await sealEnvelopeToPeer({
257
+ agentRoot: input.agentRoot, self,
226
258
  sodium, peer: input.peer, did: input.did, envelope, friendsKind: "coordination",
227
259
  });
228
260
  }
@@ -389,14 +421,17 @@ exports.a2aToolDefinitions = [
389
421
  const did = peerDid(peer);
390
422
  if (did) {
391
423
  try {
392
- await sealAndSendToPeer({
393
- /* v8 ignore next -- in-turn a2a_send_message always carries a bundle agentRoot; the ambient-name fallback is defensive @preserve */
394
- agentName: agentNameFromRoot(ctx?.agentRoot) ?? "ouro-agent",
424
+ /* v8 ignore next -- in-turn a2a_send_message always carries a bundle agentRoot; the ambient-name fallback is defensive @preserve */
425
+ const agentName = agentNameFromRoot(ctx?.agentRoot) ?? "ouro-agent";
426
+ const delivery = await sealAndSendToPeer({
427
+ agentName,
428
+ /* v8 ignore next -- same defensive ambient fallback as agentName @preserve */
429
+ agentRoot: ctx?.agentRoot ?? (0, identity_1.getAgentRoot)(agentName),
395
430
  peer,
396
431
  did,
397
432
  message: args.message,
398
433
  });
399
- return `sealed message delivered to ${peer.name} (${did}) over the direct rung.`;
434
+ return deliveryText(peer.name, did, delivery);
400
435
  }
401
436
  catch (error) {
402
437
  return `A2A sealed send error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error transport failures @preserve */ String(error)}`;
@@ -534,8 +569,10 @@ exports.a2aToolDefinitions = [
534
569
  }
535
570
  const requestId = prepared.envelope.task?.requestId;
536
571
  // Seal + send the coordination envelope to the peer over direct.
537
- await sealEnvelopeToPeer({
538
- self: { did: self.did, keyId: self.keyId, ed25519Priv: self.ed25519Priv },
572
+ const delivery = await sealEnvelopeToPeer({
573
+ /* v8 ignore next -- in-turn coordinate always carries a bundle agentRoot; the ambient fallback is defensive @preserve */
574
+ agentRoot: ctx?.agentRoot ?? (0, identity_1.getAgentRoot)(agentName),
575
+ self,
539
576
  sodium, peer, did,
540
577
  envelope: prepared.envelope,
541
578
  friendsKind: "coordination",
@@ -546,7 +583,8 @@ exports.a2aToolDefinitions = [
546
583
  message: "coordination delegated + sealed to peer",
547
584
  meta: { tool: "coordinate", peerDid: did, missionKey: args.mission_key, requestId },
548
585
  });
549
- return `coordinated: delegated "${args.task_summary}" to ${peer.name} on mission ${args.mission_key} (requestId ${requestId}).`;
586
+ const queued = delivery.rung === "relay" ? ` ${deliveryText(peer.name, did, delivery, "delegation")}` : "";
587
+ return `coordinated: delegated "${args.task_summary}" to ${peer.name} on mission ${args.mission_key} (requestId ${requestId}).${queued}`;
550
588
  }
551
589
  catch (error) {
552
590
  return `coordinate error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error failures @preserve */ String(error)}`;
@@ -686,8 +724,13 @@ exports.a2aToolDefinitions = [
686
724
  message: "delivering sealed mission result over the direct rung",
687
725
  meta: { selfDid: self.did, toDid: delegatorDid, requestId: args.request_id },
688
726
  });
689
- await (0, client_1.postA2AMessageEnvelope)({ endpointUrl, message });
690
- return `result sent to ${delegator.name} (${delegatorDid}) for requestId ${args.request_id}.`;
727
+ /* v8 ignore next -- in-turn send_result always carries a bundle agentRoot; the ambient fallback is defensive @preserve */
728
+ const relay = relayTransportFor({ agentRoot: ctx?.agentRoot ?? (0, identity_1.getAgentRoot)(agentName), self, peer: delegator, did: delegatorDid });
729
+ await relay.transport.send({ rung: "direct", address: endpointUrl }, message);
730
+ const delivery = relay.delivered();
731
+ return delivery.rung === "direct"
732
+ ? `result sent to ${delegator.name} (${delegatorDid}) for requestId ${args.request_id}.`
733
+ : `${deliveryText(delegator.name, delegatorDid, delivery, "result")} (requestId ${args.request_id})`;
691
734
  }
692
735
  catch (error) {
693
736
  return `send_result error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error failures @preserve */ String(error)}`;
@@ -62,8 +62,9 @@ Promise.all([
62
62
  Promise.resolve().then(() => __importStar(require("../heart/machine-identity"))),
63
63
  Promise.resolve().then(() => __importStar(require("@ouro.bot/friends/a2a-client"))),
64
64
  Promise.resolve().then(() => __importStar(require("../a2a/escalation-wiring"))),
65
+ Promise.resolve().then(() => __importStar(require("../a2a/relay-config-vault"))),
65
66
  ])
66
- .then(async ([{ startA2AServer }, { loadOrMintA2AIdentity, readStoredA2ASeed, publishOwnA2ADid }, { waitForRuntimeCredentialBootstrap, readMachineRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, mergeMachineRuntimeCredentialConfig }, { loadOrCreateMachineIdentity }, { ready }, { escalationOptionsFor, sendOwnerNoticeViaTelegram },]) => {
67
+ .then(async ([{ startA2AServer }, { loadOrMintA2AIdentity, readStoredA2ASeed, publishOwnA2ADid }, { waitForRuntimeCredentialBootstrap, readMachineRuntimeCredentialConfig, refreshMachineRuntimeCredentialConfig, mergeMachineRuntimeCredentialConfig }, { loadOrCreateMachineIdentity }, { ready }, { escalationOptionsFor, sendOwnerNoticeViaTelegram }, { relayConfigFrom, vaultRelayConfigStore },]) => {
67
68
  const os = await Promise.resolve().then(() => __importStar(require("node:os")));
68
69
  // Wait for the daemon to deliver the machine-local runtime config (carrying the
69
70
  // a2a identity seed) into the in-memory cache before we read or mint identity.
@@ -94,6 +95,7 @@ Promise.all([
94
95
  },
95
96
  });
96
97
  publishOwnA2ADid((0, identity_1.getAgentRoot)(agentName), identity.did);
98
+ const relaySettings = relayConfigFrom(machineConfig);
97
99
  const rawPort = argValue("--port");
98
100
  const port = rawPort ? Number.parseInt(rawPort, 10) : undefined;
99
101
  await startA2AServer({
@@ -113,6 +115,11 @@ Promise.all([
113
115
  } : {}),
114
116
  ...escalationOptionsFor(agentName, (notice) => sendOwnerNoticeViaTelegram(agentName, notice)),
115
117
  notifyOwner: (notice) => sendOwnerNoticeViaTelegram(agentName, notice),
118
+ // A friends-relay inbox for messages sent while this agent is offline, when the operator set a2a.relay.url
119
+ // (or registrations on relays it moved away from still have to be drained and removed).
120
+ ...(relaySettings.url || relaySettings.previousRegistrations?.length
121
+ ? { relay: { config: vaultRelayConfigStore(agentName, machineId), notifyOwner: (notice) => sendOwnerNoticeViaTelegram(agentName, notice) } }
122
+ : {}),
116
123
  ...(argValue("--host") ? { host: argValue("--host") } : {}),
117
124
  ...(Number.isInteger(port) ? { port } : {}),
118
125
  ...(argValue("--base-url") ? { baseUrl: argValue("--base-url") } : {}),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.989",
3
+ "version": "0.1.0-alpha.990",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@ouro.bot/cli",
9
- "version": "0.1.0-alpha.989",
9
+ "version": "0.1.0-alpha.990",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.989",
3
+ "version": "0.1.0-alpha.990",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },