@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 +6 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/a2a/delegated-command.js +61 -1
- package/dist/heart/awaiting/a2a-await-delivery.js +76 -0
- package/dist/heart/daemon/daemon-entry.js +2 -0
- package/dist/repertoire/tools-awaiting.js +37 -4
- package/dist/senses/a2a-entry.js +2 -1
- package/dist/senses/private-runtime.js +54 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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,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.
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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)
|
package/dist/senses/a2a-entry.js
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
140
|
+
throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is incomplete or legacy");
|
|
136
141
|
}
|
|
137
142
|
if (values.some((value) => value === null))
|
|
138
|
-
throw new
|
|
143
|
+
throw new InvalidRelationshipAwaitProvenanceError("Relationship await provenance is incomplete or legacy");
|
|
139
144
|
const [friendId, channel, key] = values;
|
|
140
|
-
if (channel
|
|
141
|
-
throw new
|
|
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
|
-
:
|
|
164
|
-
|
|
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 ?
|
|
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.
|
|
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.
|
|
1235
|
+
(0, tools_awaiting_1.failBrokenAwait)(agentRoot, agentName, parsedAwait.name, error.reason);
|
|
1192
1236
|
throw error;
|
|
1193
1237
|
}
|
|
1194
1238
|
};
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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.
|
|
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",
|