@ouro.bot/cli 0.1.0-alpha.859 → 0.1.0-alpha.860

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/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.860",
6
+ "changes": [
7
+ "Awaits filed from an A2A delegated command now run: the follow-up carries the delegating owner's authority narrowed to the peer's profile, its outcome reaches the owner on Telegram as the agent, and an await that can never run is canceled with a reason and reported instead of failing silently on every wake."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.859",
6
12
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.859",
2
+ "runtimeVersion": "0.1.0-alpha.860",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.859</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.860</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -1,11 +1,71 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DELEGATED_BANNER = void 0;
36
+ exports.DELEGATED_BANNER = exports.A2A_PRINCIPAL_PROFILE_ID = void 0;
37
+ exports.delegatedCommandWasNoticed = delegatedCommandWasNoticed;
4
38
  exports.delegatedCommandNotice = delegatedCommandNotice;
5
39
  exports.admitDelegatedCommand = admitDelegatedCommand;
40
+ const crypto_1 = require("crypto");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
6
43
  const runtime_1 = require("../nerves/runtime");
7
44
  const relationship_authorization_1 = require("../repertoire/relationship-authorization");
8
45
  const NOTICE_EXCERPT_CHARS = 200;
46
+ /** The relationship profile that names the principal a delegated peer acts for. */
47
+ exports.A2A_PRINCIPAL_PROFILE_ID = "sanctuary-owner";
48
+ /**
49
+ * True when the owner was told about delegated command `commandId` in their own chat: admission only runs a command
50
+ * after that notice is delivered, and the Telegram effect journal keeps the notice under a key derived from the
51
+ * command id. A request id with no such record never came through a signed, owner-notified delegated command.
52
+ * A missing record is `false`; an unreadable or malformed one throws, so the caller retries instead of deciding.
53
+ */
54
+ function delegatedCommandWasNoticed(agentRoot, commandId, principalFriendId) {
55
+ const idempotencyKey = `owner-notice:delegated:${commandId}`;
56
+ const file = path.join(agentRoot, "state", "telegram", "effects", `${(0, crypto_1.createHash)("sha256").update(idempotencyKey).digest("hex")}.json`);
57
+ let raw;
58
+ try {
59
+ raw = fs.readFileSync(file, "utf8");
60
+ }
61
+ catch (error) {
62
+ if (error.code === "ENOENT")
63
+ return false;
64
+ throw error;
65
+ }
66
+ const artifact = JSON.parse(raw);
67
+ return artifact.idempotencyKey === idempotencyKey && artifact.authorClass === "butler" && artifact.target?.friendId === principalFriendId;
68
+ }
9
69
  /** Matches the banner only the server may put in front of an admitted command. */
10
70
  exports.DELEGATED_BANNER = /^\s*\[\s*delegated command/iu;
11
71
  function delegatedCommandNotice(input) {
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createA2AAwaitOwnerDeliverer = createA2AAwaitOwnerDeliverer;
37
+ const path = __importStar(require("path"));
38
+ const friends_1 = require("@ouro.bot/friends");
39
+ const identity_1 = require("../identity");
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ /**
42
+ * An A2A peer is a client that calls this agent's server; there is no channel back to it. When an await filed from
43
+ * an A2A conversation resolves or expires, the outcome goes to the agent's owner in the owner's own Telegram chat,
44
+ * written by the agent as itself (an owner notice, the same path that announces delegated commands). Nothing is
45
+ * sent as the owner or to the peer, and the notice id is the await's delivery id, so a retry cannot double-send.
46
+ */
47
+ const NOTICE_ID_MAX_BYTES = 512;
48
+ function createA2AAwaitOwnerDeliverer(agentName, notifyOwner = defaultNotifyOwner(agentName)) {
49
+ return async (request) => {
50
+ if (!request.deliveryId || Buffer.byteLength(request.deliveryId) > NOTICE_ID_MAX_BYTES) {
51
+ return { status: "blocked", detail: "A2A await follow-up is missing a valid delivery id" };
52
+ }
53
+ try {
54
+ const peer = await new friends_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(agentName), "friends")).get(request.friendId);
55
+ const from = peer?.name ?? "a connected agent";
56
+ await notifyOwner({ noticeId: request.deliveryId, text: `Follow-up on a request from ${from}: ${request.content}` });
57
+ return { status: "delivered_now", detail: "sent to the owner's Telegram chat as the agent" };
58
+ }
59
+ catch (error) {
60
+ (0, runtime_1.emitNervesEvent)({
61
+ level: "error",
62
+ component: "daemon",
63
+ event: "daemon.await_a2a_owner_notice_error",
64
+ message: "A2A await follow-up could not be delivered to the owner",
65
+ meta: { agentName, friendId: request.friendId, error: error instanceof Error ? error.message : String(error) },
66
+ });
67
+ return { status: "failed", detail: error instanceof Error ? error.message : String(error) };
68
+ }
69
+ };
70
+ }
71
+ function defaultNotifyOwner(agentName) {
72
+ return async (input) => {
73
+ const { sendTelegramOwnerNotice } = await Promise.resolve().then(() => __importStar(require("../../senses/telegram")));
74
+ await sendTelegramOwnerNotice(agentName, { ...input, signal: AbortSignal.timeout(30_000) });
75
+ };
76
+ }
@@ -55,6 +55,7 @@ const habit_scheduler_1 = require("../habits/habit-scheduler");
55
55
  const habit_migration_1 = require("../habits/habit-migration");
56
56
  const await_scheduler_1 = require("../awaiting/await-scheduler");
57
57
  const await_expiry_1 = require("../awaiting/await-expiry");
58
+ const a2a_await_delivery_1 = require("../awaiting/a2a-await-delivery");
58
59
  const await_runtime_state_1 = require("../awaiting/await-runtime-state");
59
60
  const os_cron_deps_1 = require("./os-cron-deps");
60
61
  const os_cron_1 = require("./os-cron");
@@ -828,6 +829,7 @@ void (0, daemon_bootstrap_startup_1.startDaemonAfterContainerCredentialBootstrap
828
829
  deliveryDeps: {
829
830
  agentName: agent,
830
831
  deliverers: {
832
+ a2a: (0, a2a_await_delivery_1.createA2AAwaitOwnerDeliverer)(agent),
831
833
  telegram: async (request) => {
832
834
  const { sendTelegramAwaitFollowUp } = await Promise.resolve().then(() => __importStar(require("../../senses/telegram")));
833
835
  return sendTelegramAwaitFollowUp(agent, request);
@@ -39,6 +39,8 @@ exports.setAwaitToolDeps = setAwaitToolDeps;
39
39
  exports.resetAwaitToolDeps = resetAwaitToolDeps;
40
40
  exports.cancelAwaitTool = cancelAwaitTool;
41
41
  exports.cancelStaleAwait = cancelStaleAwait;
42
+ exports.isBrokenBindingReason = isBrokenBindingReason;
43
+ exports.failBrokenAwait = failBrokenAwait;
42
44
  exports.inspectRelationshipFollowUp = inspectRelationshipFollowUp;
43
45
  exports.hasActiveRelationshipFollowUp = hasActiveRelationshipFollowUp;
44
46
  exports.hasActiveExternalEventAwait = hasActiveExternalEventAwait;
@@ -51,6 +53,7 @@ const session_events_1 = require("../heart/session-events");
51
53
  const runtime_1 = require("../nerves/runtime");
52
54
  const await_parser_1 = require("../heart/awaiting/await-parser");
53
55
  const await_alert_1 = require("../heart/awaiting/await-alert");
56
+ const a2a_await_delivery_1 = require("../heart/awaiting/a2a-await-delivery");
54
57
  const pending_1 = require("../mind/pending");
55
58
  const router_1 = require("../heart/external-events/router");
56
59
  const obligations_1 = require("../arc/obligations");
@@ -115,9 +118,8 @@ function resetAwaitToolDeps() {
115
118
  injected = {};
116
119
  }
117
120
  function resolveDeliveryDeps(agentName) {
118
- if (injected.buildDeliveryDeps)
119
- return injected.buildDeliveryDeps(agentName);
120
- return defaultDeliveryDeps(agentName);
121
+ const deps = injected.buildDeliveryDeps ? injected.buildDeliveryDeps(agentName) : defaultDeliveryDeps(agentName);
122
+ return { ...deps, deliverers: { a2a: (0, a2a_await_delivery_1.createA2AAwaitOwnerDeliverer)(agentName), ...deps.deliverers } };
121
123
  }
122
124
  function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel, sessionKey, requestId) {
123
125
  const nameError = validateName(args.name);
@@ -360,6 +362,34 @@ function cancelStaleAwait(agentRoot, agentName, name, reason) {
360
362
  if (result.canceled !== name)
361
363
  throw new Error(`Stale await could not be archived for repair: ${result.error ?? name}`);
362
364
  }
365
+ /** Binding losses that mean the await is broken, as opposed to finished (fulfilled, expired). */
366
+ function isBrokenBindingReason(reason) {
367
+ return reason === "request obligation is missing" || reason === "request obligation binding no longer matches";
368
+ }
369
+ /**
370
+ * An await that can never run is ended, not retried: archived as canceled with the reason, logged as an error, and
371
+ * the private runtime is told once that the follow-up it promised will not happen. Without this the await stayed
372
+ * pending and every scheduled wake failed again, silently, for days.
373
+ */
374
+ function failBrokenAwait(agentRoot, agentName, awaitName, reason) {
375
+ cancelStaleAwait(agentRoot, agentName, awaitName, reason);
376
+ (0, runtime_1.emitNervesEvent)({
377
+ level: "error",
378
+ component: "senses",
379
+ event: "senses.relationship_await_failed",
380
+ message: "relationship await could not run and was canceled",
381
+ meta: { agentName, awaitName, reason },
382
+ });
383
+ (0, pending_1.queuePendingMessageOnce)((0, pending_1.getPrivateRuntimePendingDir)(agentName), {
384
+ from: agentName,
385
+ friendId: "self",
386
+ channel: "inner",
387
+ key: "dialog",
388
+ content: `my await "${awaitName}" was canceled because it could not run (${reason}). the follow-up it promised did not happen; i should tell my owner and decide whether to file it again.`,
389
+ timestamp: Date.now(),
390
+ packetId: `await-failed:${awaitName}`,
391
+ });
392
+ }
363
393
  function inspectRelationshipFollowUp(agentRoot, input) {
364
394
  const awaiting = readAwaitDefinition(agentRoot, input.awaitName);
365
395
  if (!awaiting)
@@ -523,7 +553,10 @@ exports.awaitingToolDefinitions = [
523
553
  binding = inspectRelationshipFollowUp(agentRoot, { friendId: session.friendId, channel: session.channel, key: session.key, requestId, awaitName });
524
554
  }
525
555
  if (binding && !binding.active) {
526
- cancelStaleAwait(agentRoot, agentName, awaitName, binding.reason);
556
+ if (isBrokenBindingReason(binding.reason))
557
+ failBrokenAwait(agentRoot, agentName, awaitName, binding.reason);
558
+ else
559
+ cancelStaleAwait(agentRoot, agentName, awaitName, binding.reason);
527
560
  return JSON.stringify({ error: binding.reason });
528
561
  }
529
562
  if (!binding?.active)
@@ -46,6 +46,7 @@ function argValue(name) {
46
46
  }
47
47
  const runtime_logging_1 = require("../heart/daemon/runtime-logging");
48
48
  const runtime_1 = require("../nerves/runtime");
49
+ const delegated_command_1 = require("../a2a/delegated-command");
49
50
  (0, runtime_logging_1.configureDaemonRuntimeLogger)("a2a");
50
51
  (0, runtime_1.emitNervesEvent)({
51
52
  component: "senses",
@@ -99,7 +100,7 @@ Promise.all([
99
100
  // one is announced in the owner's Telegram chat before it runs.
100
101
  ...(agentName === "sanctuary" ? {
101
102
  delegation: {
102
- principalProfileId: "sanctuary-owner",
103
+ principalProfileId: delegated_command_1.A2A_PRINCIPAL_PROFILE_ID,
103
104
  notifyPrincipal: async (notice) => {
104
105
  const { sendTelegramOwnerNotice } = await Promise.resolve().then(() => __importStar(require("./telegram")));
105
106
  await sendTelegramOwnerNotice(agentName, { ...notice, signal: AbortSignal.timeout(30_000) });
@@ -57,6 +57,7 @@ const prompt_1 = require("../mind/prompt");
57
57
  const mcp_manager_1 = require("../repertoire/mcp-manager");
58
58
  const tools_1 = require("../repertoire/tools");
59
59
  const tools_awaiting_1 = require("../repertoire/tools-awaiting");
60
+ const delegated_command_1 = require("../a2a/delegated-command");
60
61
  const relationship_authorization_1 = require("../repertoire/relationship-authorization");
61
62
  const run_ledger_1 = require("../heart/run-ledger");
62
63
  const bundle_manifest_1 = require("../mind/bundle-manifest");
@@ -99,6 +100,10 @@ const DEFAULT_PRIVATE_RUNTIME_INSTINCTS = [
99
100
  enabled: true,
100
101
  },
101
102
  ];
103
+ /** The await file's provenance can never be satisfied; waking it again cannot help. */
104
+ class InvalidRelationshipAwaitProvenanceError extends Error {
105
+ }
106
+ const RELATIONSHIP_AWAIT_CHANNELS = new Set(["telegram", "a2a"]);
102
107
  class StaleRelationshipAwaitError extends Error {
103
108
  reason;
104
109
  constructor(reason) {
@@ -124,7 +129,7 @@ function relationshipAwaitCoordinates(awaitFile) {
124
129
  if (!awaitFile.request_id) {
125
130
  if (awaitFile.filed_from === "external-event" && awaitFile.filed_for_friend_id && awaitFile.filed_from_key) {
126
131
  if (Buffer.byteLength(awaitFile.filed_for_friend_id) > 256 || Buffer.byteLength(awaitFile.filed_from_key) > 1_024)
127
- throw new Error("Relationship await provenance is invalid");
132
+ throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is invalid");
128
133
  return { friendId: awaitFile.filed_for_friend_id, channel: awaitFile.filed_from, key: awaitFile.filed_from_key, requestId: null };
129
134
  }
130
135
  const isSystemAwait = awaitFile.filed_for_friend_id === null
@@ -132,16 +137,38 @@ function relationshipAwaitCoordinates(awaitFile) {
132
137
  && (awaitFile.filed_from === null || awaitFile.filed_from === "unknown" || awaitFile.filed_from === "cli");
133
138
  if (isSystemAwait)
134
139
  return null;
135
- throw new Error("Relationship await provenance is incomplete or legacy");
140
+ throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is incomplete or legacy");
136
141
  }
137
142
  if (values.some((value) => value === null))
138
- throw new Error("Relationship await provenance is incomplete or legacy");
143
+ throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is incomplete or legacy");
139
144
  const [friendId, channel, key] = values;
140
- if (channel !== "telegram" || Buffer.byteLength(friendId) > 256 || Buffer.byteLength(channel) > 64 || Buffer.byteLength(key) > 1_024 || Buffer.byteLength(awaitFile.request_id) > 256) {
141
- throw new Error("Relationship await provenance is invalid");
145
+ if (!RELATIONSHIP_AWAIT_CHANNELS.has(channel) || Buffer.byteLength(friendId) > 256 || Buffer.byteLength(channel) > 64 || Buffer.byteLength(key) > 1_024 || Buffer.byteLength(awaitFile.request_id) > 256) {
146
+ throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is invalid");
142
147
  }
143
148
  return { friendId, channel, key, requestId: awaitFile.request_id };
144
149
  }
150
+ /**
151
+ * An A2A peer is reactive-only: it cannot be pushed to, and on its own it has no authority to be followed up with.
152
+ * Only a peer still holding the operator-set principal-command grant keeps follow-up authority, and that authority
153
+ * is the delegating owner's, resolved exactly as the delegated command resolved it. Revoking the grant ends it.
154
+ */
155
+ async function resolveA2APrincipal(agentRoot, store, peer, requestId) {
156
+ if (peer.delegationGrant?.scope !== "principal_commands" || peer.trustLevel !== "family")
157
+ throw new StaleRelationshipAwaitError("A2A peer no longer holds a delegation grant");
158
+ // listAll() swallows an unreadable directory or record as "no friend": only a verified complete read may end an await.
159
+ const entries = fs.readdirSync(path.join(agentRoot, "friends")).filter((entry) => entry.endsWith(".json"));
160
+ const friends = await store.listAll();
161
+ if (friends.length !== entries.length)
162
+ throw new Error("Relationship await authority friends could not be read completely");
163
+ const owners = friends.filter((candidate) => candidate.capabilityProfileId === delegated_command_1.A2A_PRINCIPAL_PROFILE_ID
164
+ && candidate.trustLevel === "family" && candidate.admissionState === "active");
165
+ if (owners.length !== 1 || owners[0].id === peer.id)
166
+ throw new StaleRelationshipAwaitError("delegating owner cannot be resolved");
167
+ const owner = owners[0];
168
+ if (!(0, delegated_command_1.delegatedCommandWasNoticed)(agentRoot, requestId, owner.id))
169
+ throw new StaleRelationshipAwaitError("request was not a delegated command the owner was notified of");
170
+ return owner;
171
+ }
145
172
  async function resolveRelationshipAwaitAuthority(agentRoot, store, registry, coordinates) {
146
173
  const friend = await store.get(coordinates.friendId);
147
174
  if (!friend) {
@@ -158,10 +185,15 @@ async function resolveRelationshipAwaitAuthority(agentRoot, store, registry, coo
158
185
  if (coordinates.channel === "external-event" && (friend.capabilityProfileId !== "sanctuary-owner" || !registry.profiles["sanctuary-event"])) {
159
186
  throw new StaleRelationshipAwaitError("admission or profile is not active");
160
187
  }
188
+ const subjectFriend = coordinates.channel === "a2a" ? await resolveA2APrincipal(agentRoot, store, friend, coordinates.requestId) : friend;
161
189
  const authorization = coordinates.channel === "external-event"
162
190
  ? (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend, registry, profileId: "sanctuary-event", requestPhase: "follow_up" })
163
- : (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend, registry, requestId: coordinates.requestId, requestPhase: "follow_up" });
164
- if (authorization.subject.friendId !== coordinates.friendId)
191
+ : coordinates.channel === "a2a"
192
+ // The follow-up carries the delegating owner's authority (as the delegated command did), but the turn profile
193
+ // is the peer's own, which can only narrow it: never wider than a peer chat turn or the original command.
194
+ ? (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend: subjectFriend, registry, profileId: friend.capabilityProfileId, requestId: coordinates.requestId, requestPhase: "follow_up" })
195
+ : (0, relationship_authorization_2.createRelationshipAuthorizationEvaluator)({ friend, registry, requestId: coordinates.requestId, requestPhase: "follow_up" });
196
+ if (authorization.subject.friendId !== subjectFriend.id)
165
197
  throw new Error("Relationship await authority friend is ambiguous");
166
198
  if (!authorization.authorizeTool("resolve_await").allowed)
167
199
  throw new StaleRelationshipAwaitError("admission or profile is not active");
@@ -1157,7 +1189,16 @@ async function runPrivateRuntimeTurn(options) {
1157
1189
  const selfContext = { friend: selfFriend, channel: innerCapabilities };
1158
1190
  const mcpOwner = { agentName, agentRoot: (0, identity_1.getAgentRoot)(agentName) };
1159
1191
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)(mcpOwner) ?? undefined;
1160
- const relationshipAwaitCoordinatesValue = parsedAwait ? relationshipAwaitCoordinates(parsedAwait) : null;
1192
+ const relationshipAwaitCoordinatesValue = parsedAwait ? (() => {
1193
+ try {
1194
+ return relationshipAwaitCoordinates(parsedAwait);
1195
+ }
1196
+ catch (error) {
1197
+ // relationshipAwaitCoordinates only ever throws provenance errors: none of them can be fixed by waking again.
1198
+ (0, tools_awaiting_1.failBrokenAwait)((0, identity_1.getAgentRoot)(agentName), agentName, parsedAwait.name, error.message);
1199
+ throw error;
1200
+ }
1201
+ })() : null;
1161
1202
  const relationshipAwait = relationshipAwaitCoordinatesValue
1162
1203
  ? await (async () => {
1163
1204
  const agentRoot = (0, identity_1.getAgentRoot)(agentName);
@@ -1175,7 +1216,10 @@ async function runPrivateRuntimeTurn(options) {
1175
1216
  now: now().getTime(),
1176
1217
  });
1177
1218
  if (!binding.active) {
1178
- (0, tools_awaiting_1.cancelStaleAwait)(agentRoot, agentName, parsedAwait.name, binding.reason);
1219
+ if ((0, tools_awaiting_1.isBrokenBindingReason)(binding.reason))
1220
+ (0, tools_awaiting_1.failBrokenAwait)(agentRoot, agentName, parsedAwait.name, binding.reason);
1221
+ else
1222
+ (0, tools_awaiting_1.cancelStaleAwait)(agentRoot, agentName, parsedAwait.name, binding.reason);
1179
1223
  throw new Error(`Relationship await authority binding is missing, stale, or ambiguous: ${binding.reason}`);
1180
1224
  }
1181
1225
  };
@@ -1188,7 +1232,7 @@ async function runPrivateRuntimeTurn(options) {
1188
1232
  }
1189
1233
  catch (error) {
1190
1234
  if (error instanceof StaleRelationshipAwaitError)
1191
- (0, tools_awaiting_1.cancelStaleAwait)(agentRoot, agentName, parsedAwait.name, error.reason);
1235
+ (0, tools_awaiting_1.failBrokenAwait)(agentRoot, agentName, parsedAwait.name, error.reason);
1192
1236
  throw error;
1193
1237
  }
1194
1238
  };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.859",
3
+ "version": "0.1.0-alpha.860",
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.859",
9
+ "version": "0.1.0-alpha.860",
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.859",
3
+ "version": "0.1.0-alpha.860",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },