@ouro.bot/cli 0.1.0-alpha.653 → 0.1.0-alpha.654
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 +7 -0
- package/dist/a2a/card.js +56 -0
- package/dist/a2a/client.js +143 -0
- package/dist/a2a/config.js +50 -0
- package/dist/a2a/onboarding.js +111 -0
- package/dist/a2a/server.js +498 -0
- package/dist/a2a/task-store.js +69 -0
- package/dist/a2a/types.js +3 -0
- package/dist/commerce/store.js +755 -0
- package/dist/commerce/types.js +3 -0
- package/dist/heart/daemon/cli-exec.js +118 -3
- package/dist/heart/daemon/cli-help.js +14 -2
- package/dist/heart/daemon/cli-parse.js +88 -4
- package/dist/heart/daemon/daemon.js +2 -1
- package/dist/heart/daemon/process-manager.js +2 -1
- package/dist/heart/daemon/runtime-logging.js +1 -1
- package/dist/heart/daemon/sense-manager.js +71 -15
- package/dist/heart/identity.js +4 -1
- package/dist/heart/sense-truth.js +2 -0
- package/dist/heart/turn-context.js +6 -0
- package/dist/mind/friends/channel.js +10 -1
- package/dist/mind/friends/resolver.js +13 -2
- package/dist/mind/friends/store-file.js +13 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +11 -0
- package/dist/repertoire/guardrails.js +25 -2
- package/dist/repertoire/tools-a2a.js +283 -0
- package/dist/repertoire/tools-base.js +4 -0
- package/dist/repertoire/tools-commerce.js +253 -0
- package/dist/repertoire/tools-flight.js +68 -5
- package/dist/repertoire/tools-stripe.js +49 -7
- package/dist/repertoire/tools.js +50 -2
- package/dist/senses/a2a-entry.js +78 -0
- package/dist/senses/pipeline.js +13 -0
- package/dist/senses/shared-turn.js +30 -5
- package/package.json +1 -1
- package/skills/agent-commerce.md +17 -10
package/dist/heart/identity.js
CHANGED
|
@@ -137,6 +137,7 @@ exports.DEFAULT_AGENT_SENSES = {
|
|
|
137
137
|
bluebubbles: { enabled: false },
|
|
138
138
|
mail: { enabled: false },
|
|
139
139
|
voice: { enabled: false },
|
|
140
|
+
a2a: { enabled: false },
|
|
140
141
|
};
|
|
141
142
|
function normalizeSenses(value, configFile) {
|
|
142
143
|
const defaults = {
|
|
@@ -145,6 +146,7 @@ function normalizeSenses(value, configFile) {
|
|
|
145
146
|
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
146
147
|
mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
|
|
147
148
|
voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
|
|
149
|
+
a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
|
|
148
150
|
};
|
|
149
151
|
if (value === undefined) {
|
|
150
152
|
return defaults;
|
|
@@ -160,7 +162,7 @@ function normalizeSenses(value, configFile) {
|
|
|
160
162
|
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
161
163
|
}
|
|
162
164
|
const raw = value;
|
|
163
|
-
const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice"];
|
|
165
|
+
const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice", "a2a"];
|
|
164
166
|
for (const senseName of senseNames) {
|
|
165
167
|
const rawSense = raw[senseName];
|
|
166
168
|
if (rawSense === undefined) {
|
|
@@ -204,6 +206,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
204
206
|
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
205
207
|
mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
|
|
206
208
|
voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
|
|
209
|
+
a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
|
|
207
210
|
},
|
|
208
211
|
phrases: {
|
|
209
212
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
@@ -9,6 +9,7 @@ const SENSES = [
|
|
|
9
9
|
{ sense: "bluebubbles", label: "BlueBubbles", daemonManaged: true },
|
|
10
10
|
{ sense: "mail", label: "Mail", daemonManaged: true },
|
|
11
11
|
{ sense: "voice", label: "Voice", daemonManaged: true },
|
|
12
|
+
{ sense: "a2a", label: "A2A", daemonManaged: true },
|
|
12
13
|
];
|
|
13
14
|
function configuredSenses(senses) {
|
|
14
15
|
const configured = senses ?? {};
|
|
@@ -19,6 +20,7 @@ function configuredSenses(senses) {
|
|
|
19
20
|
bluebubbles: configured.bluebubbles ?? { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
20
21
|
mail: configured.mail ?? { ...identity_1.DEFAULT_AGENT_SENSES.mail },
|
|
21
22
|
voice: configured.voice ?? { ...identity_1.DEFAULT_AGENT_SENSES.voice },
|
|
23
|
+
a2a: configured.a2a ?? { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
@@ -140,6 +140,7 @@ function readSenseStatusLines() {
|
|
|
140
140
|
bluebubbles: configuredSenses.bluebubbles ?? { enabled: false },
|
|
141
141
|
mail: configuredSenses.mail ?? { enabled: false },
|
|
142
142
|
voice: configuredSenses.voice ?? { enabled: false },
|
|
143
|
+
a2a: configuredSenses.a2a ?? { enabled: false },
|
|
143
144
|
};
|
|
144
145
|
const payload = (0, config_1.loadConfig)();
|
|
145
146
|
const agentName = (0, identity_1.getAgentName)();
|
|
@@ -183,6 +184,7 @@ function readSenseStatusLines() {
|
|
|
183
184
|
: voiceConversationEngine === "openai-realtime"
|
|
184
185
|
? openAIRealtimeVoiceReady
|
|
185
186
|
: cascadeVoiceReady,
|
|
187
|
+
a2a: true,
|
|
186
188
|
};
|
|
187
189
|
const rows = [
|
|
188
190
|
{ label: "CLI", status: "interactive" },
|
|
@@ -202,6 +204,10 @@ function readSenseStatusLines() {
|
|
|
202
204
|
label: "Voice",
|
|
203
205
|
status: !senses.voice.enabled ? "disabled" : configured.voice ? "ready" : "needs_config",
|
|
204
206
|
},
|
|
207
|
+
{
|
|
208
|
+
label: "A2A",
|
|
209
|
+
status: senses.a2a.enabled ? "ready" : "disabled",
|
|
210
|
+
},
|
|
205
211
|
];
|
|
206
212
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
207
213
|
}
|
|
@@ -7,7 +7,7 @@ exports.getChannelCapabilities = getChannelCapabilities;
|
|
|
7
7
|
exports.isRemoteChannel = isRemoteChannel;
|
|
8
8
|
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
9
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
10
|
-
const AGENT_FACING_CHANNELS = new Set(["inner", "mcp"]);
|
|
10
|
+
const AGENT_FACING_CHANNELS = new Set(["inner", "mcp", "a2a"]);
|
|
11
11
|
function channelToFacing(channel) {
|
|
12
12
|
const facing = channel && AGENT_FACING_CHANNELS.has(channel) ? "agent" : "human";
|
|
13
13
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -64,6 +64,15 @@ const CHANNEL_CAPABILITIES = {
|
|
|
64
64
|
supportsRichCards: false,
|
|
65
65
|
maxMessageLength: Infinity,
|
|
66
66
|
},
|
|
67
|
+
a2a: {
|
|
68
|
+
channel: "a2a",
|
|
69
|
+
senseType: "open",
|
|
70
|
+
availableIntegrations: [],
|
|
71
|
+
supportsMarkdown: true,
|
|
72
|
+
supportsStreaming: false,
|
|
73
|
+
supportsRichCards: false,
|
|
74
|
+
maxMessageLength: Infinity,
|
|
75
|
+
},
|
|
67
76
|
inner: {
|
|
68
77
|
channel: "inner",
|
|
69
78
|
senseType: "internal",
|
|
@@ -87,6 +87,7 @@ class FriendResolver {
|
|
|
87
87
|
hasAnyFriends = false;
|
|
88
88
|
}
|
|
89
89
|
const isFirstImprint = !hasAnyFriends;
|
|
90
|
+
const isA2AAgent = this.params.provider === "a2a-agent";
|
|
90
91
|
// BlueBubbles group chats route through here as `imessage-handle` with an
|
|
91
92
|
// externalId of the form `group:any;+;<chatHash>`. When the harness auto-
|
|
92
93
|
// creates the group friend at stranger trust, we mark the record so that
|
|
@@ -105,8 +106,8 @@ class FriendResolver {
|
|
|
105
106
|
const friend = {
|
|
106
107
|
id: (0, crypto_1.randomUUID)(),
|
|
107
108
|
name: this.params.displayName,
|
|
108
|
-
role: isFirstImprint ? "primary" : "stranger",
|
|
109
|
-
trustLevel: isFirstImprint ? "family" : "stranger",
|
|
109
|
+
role: isA2AAgent ? "agent-peer" : isFirstImprint ? "primary" : "stranger",
|
|
110
|
+
trustLevel: isA2AAgent ? "stranger" : isFirstImprint ? "family" : "stranger",
|
|
110
111
|
connections: [],
|
|
111
112
|
externalIds: [externalId],
|
|
112
113
|
tenantMemberships,
|
|
@@ -116,6 +117,16 @@ class FriendResolver {
|
|
|
116
117
|
createdAt: now,
|
|
117
118
|
updatedAt: now,
|
|
118
119
|
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
120
|
+
kind: isA2AAgent ? "agent" : "human",
|
|
121
|
+
...(isA2AAgent ? {
|
|
122
|
+
agentMeta: {
|
|
123
|
+
bundleName: this.params.displayName,
|
|
124
|
+
familiarity: 0,
|
|
125
|
+
sharedMissions: [],
|
|
126
|
+
outcomes: [],
|
|
127
|
+
a2a: { agentId: this.params.externalId },
|
|
128
|
+
},
|
|
129
|
+
} : {}),
|
|
119
130
|
};
|
|
120
131
|
// Persist -- log and continue on failure (D16)
|
|
121
132
|
try {
|
|
@@ -190,8 +190,21 @@ class FileFriendStore {
|
|
|
190
190
|
familiarity: typeof meta.familiarity === "number" ? meta.familiarity : 0,
|
|
191
191
|
sharedMissions: Array.isArray(meta.sharedMissions) ? meta.sharedMissions : [],
|
|
192
192
|
outcomes: Array.isArray(meta.outcomes) ? meta.outcomes : [],
|
|
193
|
+
...(this.normalizeA2AMeta(meta.a2a) ? { a2a: this.normalizeA2AMeta(meta.a2a) } : {}),
|
|
193
194
|
};
|
|
194
195
|
}
|
|
196
|
+
normalizeA2AMeta(raw) {
|
|
197
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
198
|
+
return undefined;
|
|
199
|
+
const meta = raw;
|
|
200
|
+
const a2a = {
|
|
201
|
+
...(typeof meta.cardUrl === "string" ? { cardUrl: meta.cardUrl } : {}),
|
|
202
|
+
...(typeof meta.endpointUrl === "string" ? { endpointUrl: meta.endpointUrl } : {}),
|
|
203
|
+
...(typeof meta.agentId === "string" ? { agentId: meta.agentId } : {}),
|
|
204
|
+
...(typeof meta.protocolVersion === "string" ? { protocolVersion: meta.protocolVersion } : {}),
|
|
205
|
+
};
|
|
206
|
+
return Object.keys(a2a).length > 0 ? a2a : undefined;
|
|
207
|
+
}
|
|
195
208
|
async readJson(filePath) {
|
|
196
209
|
try {
|
|
197
210
|
const raw = await fsPromises.readFile(filePath, "utf-8");
|
|
@@ -7,7 +7,7 @@ exports.isIdentityProvider = isIdentityProvider;
|
|
|
7
7
|
exports.isIntegration = isIntegration;
|
|
8
8
|
exports.isTrustedLevel = isTrustedLevel;
|
|
9
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
10
|
-
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle", "email-address"]);
|
|
10
|
+
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle", "email-address", "a2a-agent"]);
|
|
11
11
|
function isIdentityProvider(value) {
|
|
12
12
|
(0, runtime_1.emitNervesEvent)({
|
|
13
13
|
component: "friends",
|
package/dist/mind/prompt.js
CHANGED
|
@@ -346,6 +346,7 @@ const PROCESS_TYPE_LABELS = {
|
|
|
346
346
|
bluebubbles: "bluebubbles handler",
|
|
347
347
|
mail: "mail handler",
|
|
348
348
|
voice: "voice handler",
|
|
349
|
+
a2a: "a2a handler",
|
|
349
350
|
mcp: "mcp bridge",
|
|
350
351
|
};
|
|
351
352
|
function processTypeLabel(channel) {
|
|
@@ -409,6 +410,9 @@ function runtimeInfoSection(channel, options) {
|
|
|
409
410
|
else if (channel === "mail") {
|
|
410
411
|
lines.push("i am responding from an agent mail session. i keep the response clear, auditable, and grounded in visible mail facts.");
|
|
411
412
|
}
|
|
413
|
+
else if (channel === "a2a") {
|
|
414
|
+
lines.push("i am responding through the A2A sense to another agent peer. i treat the peer as an agent friend record, keep the exchange task-oriented and auditable, and rely on the existing friend trust model for authority.");
|
|
415
|
+
}
|
|
412
416
|
else if (channel === "voice") {
|
|
413
417
|
lines.push("i am responding in a live voice session. the person is waiting in real time, so i answer early and often, keep spoken turns to one or two short sentences, stay interruption-friendly, avoid markdown and lists unless explicitly asked, and use speak before any tool work that may take more than a moment. if a later transcript says the caller interrupted or followed up while i was speaking, i prioritize that newest utterance before continuing the older answer. the overview shows the text transcript as the durable record.");
|
|
414
418
|
}
|
|
@@ -435,6 +439,7 @@ function localSenseStatusLines() {
|
|
|
435
439
|
bluebubbles: configuredSenses.bluebubbles ?? { enabled: false },
|
|
436
440
|
mail: configuredSenses.mail ?? { enabled: false },
|
|
437
441
|
voice: configuredSenses.voice ?? { enabled: false },
|
|
442
|
+
a2a: configuredSenses.a2a ?? { enabled: false },
|
|
438
443
|
};
|
|
439
444
|
const payload = (0, config_1.loadConfig)();
|
|
440
445
|
const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)((0, identity_1.getAgentName)());
|
|
@@ -457,6 +462,7 @@ function localSenseStatusLines() {
|
|
|
457
462
|
&& (hasTextField(integrations, "elevenLabsVoiceId") || hasTextField(portableVoice, "elevenLabsVoiceId"))
|
|
458
463
|
&& hasTextField(voice, "whisperCliPath")
|
|
459
464
|
&& hasTextField(voice, "whisperModelPath"),
|
|
465
|
+
a2a: true,
|
|
460
466
|
};
|
|
461
467
|
const rows = [
|
|
462
468
|
{ label: "CLI", status: "interactive" },
|
|
@@ -476,6 +482,10 @@ function localSenseStatusLines() {
|
|
|
476
482
|
label: "Voice",
|
|
477
483
|
status: !senses.voice.enabled ? "disabled" : configured.voice ? "ready" : "needs_config",
|
|
478
484
|
},
|
|
485
|
+
{
|
|
486
|
+
label: "A2A",
|
|
487
|
+
status: senses.a2a.enabled ? "ready" : "disabled",
|
|
488
|
+
},
|
|
479
489
|
];
|
|
480
490
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
481
491
|
}
|
|
@@ -493,6 +503,7 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
|
|
|
493
503
|
lines.push("If asked how to enable another sense, I explain the relevant agent.json senses entry and required agent-vault runtime/config fields instead of guessing.");
|
|
494
504
|
lines.push("teams setup truth: run `ouro connect teams --agent <agent>` from the connect bay; it stores Teams runtime/config fields and enables `senses.teams.enabled`.");
|
|
495
505
|
lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent>` from the connect bay; it stores this machine's BlueBubbles URL/password/listener config in the agent vault machine runtime item.");
|
|
506
|
+
lines.push("a2a setup truth: run `ouro connect a2a --agent <agent>` to enable the A2A sense, `ouro a2a card --agent <agent> --base-url <public-url>` to publish an agent card, and `ouro a2a onboard --agent <agent> --card-url <peer-card-url>` to add a peer as an agent friend. A2A uses the existing friend trust model, not a separate trust registry.");
|
|
496
507
|
lines.push("mail setup AX: if a human asks me to set up email, I do not hand them a terminal checklist. I guide the flow end-to-end: name the current phase, run agent-runnable commands myself with shell/tools when available, ask the human only for human-required facts or browser actions, wait for their reply, verify the result, then continue.");
|
|
497
508
|
lines.push("mail setup hard rule: never tell the human to run `ouro account ensure`, `ouro connect mail`, `ouro mail import-mbox`, `ouro status`, or `ouro doctor` for setup. Say what I am about to run, run it myself, and report the result. If my current surface cannot run shell/tools, I ask for a tool-capable Ouro setup session or companion to continue; I do not offload CLI operation to the human.");
|
|
498
509
|
lines.push("mail setup truth: Agent Mail uses Mailroom, not HEY OAuth/IMAP. For the full work substrate account, the agent-runnable command is `ouro account ensure --agent <agent> --owner-email <email> --source hey`; use `ouro connect mail --agent <agent> --owner-email <email> --source hey` for mail-only repair/provisioning, or `--no-delegated-source` for native-only mail. The detailed runbook is `docs/agent-mail-setup.md`.");
|
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("node:fs"));
|
|
|
39
39
|
const path = __importStar(require("node:path"));
|
|
40
40
|
const types_1 = require("../mind/friends/types");
|
|
41
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
const store_1 = require("../commerce/store");
|
|
42
43
|
const deny = (reason) => ({ allowed: false, reason });
|
|
43
44
|
const allow = { allowed: true };
|
|
44
45
|
// --- reason templates ---
|
|
@@ -292,6 +293,9 @@ const CREDENTIAL_TRUSTED_TOOLS = new Set(["credential_get", "credential_list"]);
|
|
|
292
293
|
// advisory and geocode are public APIs but gated for consistency)
|
|
293
294
|
// Flight search is also friend+ (read-only, no payment)
|
|
294
295
|
const TRAVEL_TRUSTED_TOOLS = new Set(["weather_lookup", "travel_advisory", "geocode_search", "flight_search"]);
|
|
296
|
+
const A2A_TRUSTED_TOOLS = new Set(["a2a_list_peers", "a2a_send_message", "a2a_get_task"]);
|
|
297
|
+
const COMMERCE_FAMILY_TOOLS = new Set(["commerce_checkout_preview", "commerce_checkout_commit", "commerce_receipt_get", "commerce_access_log"]);
|
|
298
|
+
const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
|
|
295
299
|
const MAIL_FAMILY_TOOLS = new Set(["mail_screener", "mail_decide", "mail_access_log", "mail_send", "mail_index_refresh"]);
|
|
296
300
|
const MAIL_DELEGATED_READ_TOOLS = new Set(["mail_recent", "mail_search"]);
|
|
297
301
|
function mailTrustGuardrail(toolName, args, context) {
|
|
@@ -315,18 +319,34 @@ function mailTrustGuardrail(toolName, args, context) {
|
|
|
315
319
|
return allow;
|
|
316
320
|
}
|
|
317
321
|
function checkCredentialTrustGuardrails(toolName, context) {
|
|
318
|
-
if (CREDENTIAL_FAMILY_TOOLS.has(toolName)) {
|
|
322
|
+
if (CREDENTIAL_FAMILY_TOOLS.has(toolName) || COMMERCE_FAMILY_TOOLS.has(toolName)) {
|
|
319
323
|
if (context.trustLevel === "family")
|
|
320
324
|
return allow;
|
|
321
325
|
return deny(REASONS.needsTrust);
|
|
322
326
|
}
|
|
323
|
-
if (CREDENTIAL_TRUSTED_TOOLS.has(toolName) || TRAVEL_TRUSTED_TOOLS.has(toolName)) {
|
|
327
|
+
if (CREDENTIAL_TRUSTED_TOOLS.has(toolName) || TRAVEL_TRUSTED_TOOLS.has(toolName) || A2A_TRUSTED_TOOLS.has(toolName)) {
|
|
324
328
|
if ((0, types_1.isTrustedLevel)(context.trustLevel))
|
|
325
329
|
return allow;
|
|
326
330
|
return deny(REASONS.needsTrust);
|
|
327
331
|
}
|
|
328
332
|
return allow;
|
|
329
333
|
}
|
|
334
|
+
function checkCommerceAuthorityGuardrails(toolName, args, context) {
|
|
335
|
+
if (!COMMERCE_AUTHORITY_TOOLS.has(toolName))
|
|
336
|
+
return allow;
|
|
337
|
+
if (!context.agentRoot)
|
|
338
|
+
return deny("commerce authority unavailable: agent root could not be resolved.");
|
|
339
|
+
const result = (0, store_1.validateCommerceAuthority)({
|
|
340
|
+
agentRoot: context.agentRoot,
|
|
341
|
+
token: args.commerce_authority,
|
|
342
|
+
toolName,
|
|
343
|
+
args,
|
|
344
|
+
friendId: context.friendId,
|
|
345
|
+
});
|
|
346
|
+
if (result.ok)
|
|
347
|
+
return allow;
|
|
348
|
+
return deny(`commerce authority required: ${result.reason}`);
|
|
349
|
+
}
|
|
330
350
|
function checkFirstClassMcpTrust(context) {
|
|
331
351
|
if (!context.mcpServerName)
|
|
332
352
|
return allow;
|
|
@@ -347,6 +367,9 @@ function checkTrustLevelGuardrails(toolName, args, context) {
|
|
|
347
367
|
const credentialResult = checkCredentialTrustGuardrails(toolName, context);
|
|
348
368
|
if (!credentialResult.allowed)
|
|
349
369
|
return credentialResult;
|
|
370
|
+
const commerceAuthorityResult = checkCommerceAuthorityGuardrails(toolName, args, context);
|
|
371
|
+
if (!commerceAuthorityResult.allowed)
|
|
372
|
+
return commerceAuthorityResult;
|
|
350
373
|
// First-class MCP tool trust (e.g. browser_navigate) — applies at all trust levels
|
|
351
374
|
const firstClassMcpResult = checkFirstClassMcpTrust(context);
|
|
352
375
|
if (!firstClassMcpResult.allowed)
|
|
@@ -0,0 +1,283 @@
|
|
|
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.a2aToolDefinitions = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const identity_1 = require("../heart/identity");
|
|
39
|
+
const runtime_credentials_1 = require("../heart/runtime-credentials");
|
|
40
|
+
const types_1 = require("../mind/friends/types");
|
|
41
|
+
const store_file_1 = require("../mind/friends/store-file");
|
|
42
|
+
const runtime_1 = require("../nerves/runtime");
|
|
43
|
+
const client_1 = require("../a2a/client");
|
|
44
|
+
const outboundTaskTokens = new Map();
|
|
45
|
+
function tokenKey(ctx, friendId, taskId) {
|
|
46
|
+
return `${ctx?.agentRoot ?? "ambient"}\n${friendId}\n${taskId}`;
|
|
47
|
+
}
|
|
48
|
+
function rememberTaskToken(ctx, friendId, task) {
|
|
49
|
+
/* v8 ignore next -- defensive metadata-shape guard; protocol tasks from Ouro servers always use object metadata @preserve */
|
|
50
|
+
const token = task.metadata?.a2a && typeof task.metadata.a2a === "object" && !Array.isArray(task.metadata.a2a)
|
|
51
|
+
? task.metadata.a2a.accessToken
|
|
52
|
+
: undefined;
|
|
53
|
+
if (typeof token === "string" && token.trim()) {
|
|
54
|
+
outboundTaskTokens.set(tokenKey(ctx, friendId, task.id), token.trim());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function rememberedTaskToken(ctx, friendId, taskId) {
|
|
58
|
+
return outboundTaskTokens.get(tokenKey(ctx, friendId, taskId));
|
|
59
|
+
}
|
|
60
|
+
function redactTaskToken(task) {
|
|
61
|
+
const a2a = task.metadata?.a2a;
|
|
62
|
+
if (!a2a || typeof a2a !== "object" || Array.isArray(a2a) || !("accessToken" in a2a))
|
|
63
|
+
return task;
|
|
64
|
+
const { accessToken: _accessToken, ...safeA2A } = a2a;
|
|
65
|
+
return {
|
|
66
|
+
...task,
|
|
67
|
+
metadata: {
|
|
68
|
+
...task.metadata,
|
|
69
|
+
a2a: safeA2A,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function storeFor(ctx) {
|
|
74
|
+
if (ctx?.friendStore)
|
|
75
|
+
return ctx.friendStore;
|
|
76
|
+
/* v8 ignore next -- no-agentRoot fallback depends on process argv; normal tool calls inject agentRoot @preserve */
|
|
77
|
+
if (ctx?.agentRoot)
|
|
78
|
+
return new store_file_1.FileFriendStore(path.join(ctx.agentRoot, "friends"));
|
|
79
|
+
return new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
|
|
80
|
+
}
|
|
81
|
+
function requireTrustedRequester(ctx) {
|
|
82
|
+
if (!ctx?.context?.friend?.id)
|
|
83
|
+
return "no friend context — cannot use A2A tools.";
|
|
84
|
+
if (!(0, types_1.isTrustedLevel)(ctx.context.friend.trustLevel))
|
|
85
|
+
return "A2A tools require friend or family trust.";
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function isA2APeer(friend) {
|
|
89
|
+
return friend.kind === "agent" && friend.externalIds.some((id) => id.provider === "a2a-agent");
|
|
90
|
+
}
|
|
91
|
+
function agentNameFromRoot(agentRoot) {
|
|
92
|
+
if (!agentRoot)
|
|
93
|
+
return undefined;
|
|
94
|
+
const base = path.basename(agentRoot);
|
|
95
|
+
return base.endsWith(".ouro") ? base.slice(0, -".ouro".length) : undefined;
|
|
96
|
+
}
|
|
97
|
+
function textField(record, key) {
|
|
98
|
+
const value = record?.[key];
|
|
99
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
100
|
+
}
|
|
101
|
+
function localA2ACardUrl(agentRoot) {
|
|
102
|
+
const agentName = agentNameFromRoot(agentRoot);
|
|
103
|
+
if (!agentName)
|
|
104
|
+
return undefined;
|
|
105
|
+
const result = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agentName);
|
|
106
|
+
if (!result.ok)
|
|
107
|
+
return undefined;
|
|
108
|
+
const a2a = result.config.a2a;
|
|
109
|
+
if (!a2a || typeof a2a !== "object" || Array.isArray(a2a))
|
|
110
|
+
return undefined;
|
|
111
|
+
const publicUrl = textField(a2a, "publicUrl");
|
|
112
|
+
return publicUrl ? `${publicUrl.replace(/\/+$/, "")}/.well-known/agent-card.json` : undefined;
|
|
113
|
+
}
|
|
114
|
+
async function resolveA2AEndpoint(friend) {
|
|
115
|
+
const metadata = friend.agentMeta?.a2a;
|
|
116
|
+
if (metadata?.endpointUrl) {
|
|
117
|
+
return { endpointUrl: metadata.endpointUrl, agentId: metadata.agentId, peerName: friend.name };
|
|
118
|
+
}
|
|
119
|
+
if (metadata?.cardUrl) {
|
|
120
|
+
const card = await (0, client_1.fetchA2AAgentCard)(metadata.cardUrl);
|
|
121
|
+
const endpointUrl = (0, client_1.endpointForCard)(card);
|
|
122
|
+
/* v8 ignore next -- fetchA2AAgentCard rejects cards without a usable endpoint before tools see them @preserve */
|
|
123
|
+
if (!endpointUrl)
|
|
124
|
+
throw new Error("A2A card has no JSONRPC endpoint");
|
|
125
|
+
/* v8 ignore next -- empty card names are invalid in practice; friend-name fallback is defensive @preserve */
|
|
126
|
+
const peerName = card.name || friend.name;
|
|
127
|
+
return { endpointUrl, agentId: metadata.agentId ?? endpointUrl, peerName };
|
|
128
|
+
}
|
|
129
|
+
const external = friend.externalIds.find((id) => id.provider === "a2a-agent");
|
|
130
|
+
if (external?.externalId?.startsWith("http")) {
|
|
131
|
+
const card = await (0, client_1.fetchA2AAgentCard)(external.externalId);
|
|
132
|
+
const endpointUrl = (0, client_1.endpointForCard)(card);
|
|
133
|
+
/* v8 ignore next -- fetchA2AAgentCard rejects cards without a usable endpoint before tools see them @preserve */
|
|
134
|
+
if (!endpointUrl)
|
|
135
|
+
throw new Error("A2A card has no JSONRPC endpoint");
|
|
136
|
+
/* v8 ignore next -- empty card names are invalid in practice; friend-name fallback is defensive @preserve */
|
|
137
|
+
const peerName = card.name || friend.name;
|
|
138
|
+
return { endpointUrl, agentId: external.externalId, peerName };
|
|
139
|
+
}
|
|
140
|
+
throw new Error("A2A peer has no endpointUrl or cardUrl in agentMeta.a2a");
|
|
141
|
+
}
|
|
142
|
+
exports.a2aToolDefinitions = [
|
|
143
|
+
{
|
|
144
|
+
tool: {
|
|
145
|
+
type: "function",
|
|
146
|
+
function: {
|
|
147
|
+
name: "a2a_list_peers",
|
|
148
|
+
description: "List onboarded A2A agent peers from the friend model. Requires friend or family trust.",
|
|
149
|
+
parameters: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
handler: async (_args, ctx) => {
|
|
156
|
+
(0, runtime_1.emitNervesEvent)({
|
|
157
|
+
component: "repertoire",
|
|
158
|
+
event: "repertoire.tool_a2a_list_peers",
|
|
159
|
+
message: "a2a_list_peers invoked",
|
|
160
|
+
meta: { tool: "a2a_list_peers" },
|
|
161
|
+
});
|
|
162
|
+
const guard = requireTrustedRequester(ctx);
|
|
163
|
+
if (guard)
|
|
164
|
+
return guard;
|
|
165
|
+
const store = storeFor(ctx);
|
|
166
|
+
const listAll = store.listAll;
|
|
167
|
+
if (!listAll)
|
|
168
|
+
return "friend store does not support listing.";
|
|
169
|
+
const peers = (await listAll.call(store)).filter(isA2APeer);
|
|
170
|
+
return JSON.stringify(peers.map((peer) => ({
|
|
171
|
+
id: peer.id,
|
|
172
|
+
name: peer.name,
|
|
173
|
+
trustLevel: peer.trustLevel ?? "friend",
|
|
174
|
+
endpointUrl: peer.agentMeta?.a2a?.endpointUrl,
|
|
175
|
+
cardUrl: peer.agentMeta?.a2a?.cardUrl,
|
|
176
|
+
})), null, 2);
|
|
177
|
+
},
|
|
178
|
+
summaryKeys: [],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
tool: {
|
|
182
|
+
type: "function",
|
|
183
|
+
function: {
|
|
184
|
+
name: "a2a_send_message",
|
|
185
|
+
description: "Send a message to a trusted A2A agent peer. The target peer must be an agent friend at friend/family trust.",
|
|
186
|
+
parameters: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
friend_id: { type: "string", description: "Friend record ID for the A2A agent peer." },
|
|
190
|
+
message: { type: "string", description: "Message or task request to send." },
|
|
191
|
+
session_key: { type: "string", description: "Optional A2A context/session key." },
|
|
192
|
+
},
|
|
193
|
+
required: ["friend_id", "message"],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
handler: async (args, ctx) => {
|
|
198
|
+
(0, runtime_1.emitNervesEvent)({
|
|
199
|
+
component: "repertoire",
|
|
200
|
+
event: "repertoire.tool_a2a_send_message",
|
|
201
|
+
message: "a2a_send_message invoked",
|
|
202
|
+
meta: { tool: "a2a_send_message", friendId: args.friend_id },
|
|
203
|
+
});
|
|
204
|
+
const guard = requireTrustedRequester(ctx);
|
|
205
|
+
if (guard)
|
|
206
|
+
return guard;
|
|
207
|
+
const peer = await storeFor(ctx).get(args.friend_id);
|
|
208
|
+
if (!peer || !isA2APeer(peer))
|
|
209
|
+
return `A2A peer not found: ${args.friend_id}`;
|
|
210
|
+
if (!(0, types_1.isTrustedLevel)(peer.trustLevel))
|
|
211
|
+
return "target A2A peer must be friend or family trust before outbound messages.";
|
|
212
|
+
try {
|
|
213
|
+
const endpoint = await resolveA2AEndpoint(peer);
|
|
214
|
+
const task = await (0, client_1.sendA2AMessage)({
|
|
215
|
+
endpointUrl: endpoint.endpointUrl,
|
|
216
|
+
message: args.message,
|
|
217
|
+
senderAgentId: agentNameFromRoot(ctx?.agentRoot) ?? "ouro-agent",
|
|
218
|
+
senderName: agentNameFromRoot(ctx?.agentRoot) ?? "Ouro agent",
|
|
219
|
+
senderCardUrl: localA2ACardUrl(ctx?.agentRoot),
|
|
220
|
+
sessionKey: args.session_key,
|
|
221
|
+
});
|
|
222
|
+
rememberTaskToken(ctx, peer.id, task);
|
|
223
|
+
return JSON.stringify(redactTaskToken(task), null, 2);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
return `A2A send error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error transport failures */ String(error)}`;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
summaryKeys: ["friend_id", "session_key"],
|
|
230
|
+
riskProfile: { mutates: "external_side_effect", risk: "high", reason: "sends a message to a remote agent peer" },
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
tool: {
|
|
234
|
+
type: "function",
|
|
235
|
+
function: {
|
|
236
|
+
name: "a2a_get_task",
|
|
237
|
+
description: "Fetch a task from a trusted A2A agent peer.",
|
|
238
|
+
parameters: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
friend_id: { type: "string", description: "Friend record ID for the A2A agent peer." },
|
|
242
|
+
task_id: { type: "string", description: "Remote A2A task ID." },
|
|
243
|
+
access_token: { type: "string", description: "Optional task access token from an external A2A response; omitted for tasks sent through a2a_send_message because Ouro stores it out of transcript." },
|
|
244
|
+
},
|
|
245
|
+
required: ["friend_id", "task_id"],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
handler: async (args, ctx) => {
|
|
250
|
+
(0, runtime_1.emitNervesEvent)({
|
|
251
|
+
component: "repertoire",
|
|
252
|
+
event: "repertoire.tool_a2a_get_task",
|
|
253
|
+
message: "a2a_get_task invoked",
|
|
254
|
+
meta: { tool: "a2a_get_task", friendId: args.friend_id, taskId: args.task_id },
|
|
255
|
+
});
|
|
256
|
+
const guard = requireTrustedRequester(ctx);
|
|
257
|
+
if (guard)
|
|
258
|
+
return guard;
|
|
259
|
+
const peer = await storeFor(ctx).get(args.friend_id);
|
|
260
|
+
if (!peer || !isA2APeer(peer))
|
|
261
|
+
return `A2A peer not found: ${args.friend_id}`;
|
|
262
|
+
if (!(0, types_1.isTrustedLevel)(peer.trustLevel))
|
|
263
|
+
return "target A2A peer must be friend or family trust before task lookup.";
|
|
264
|
+
try {
|
|
265
|
+
const endpoint = await resolveA2AEndpoint(peer);
|
|
266
|
+
const task = await (0, client_1.getA2ATask)({
|
|
267
|
+
endpointUrl: endpoint.endpointUrl,
|
|
268
|
+
taskId: args.task_id,
|
|
269
|
+
accessToken: args.access_token ?? rememberedTaskToken(ctx, peer.id, args.task_id),
|
|
270
|
+
senderAgentId: agentNameFromRoot(ctx?.agentRoot) ?? "ouro-agent",
|
|
271
|
+
senderName: agentNameFromRoot(ctx?.agentRoot) ?? "Ouro agent",
|
|
272
|
+
senderCardUrl: localA2ACardUrl(ctx?.agentRoot),
|
|
273
|
+
});
|
|
274
|
+
rememberTaskToken(ctx, peer.id, task);
|
|
275
|
+
return JSON.stringify(redactTaskToken(task), null, 2);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
return `A2A task error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error transport failures */ String(error)}`;
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
summaryKeys: ["friend_id", "task_id"],
|
|
282
|
+
},
|
|
283
|
+
];
|
|
@@ -19,6 +19,8 @@ const tools_flight_1 = require("./tools-flight");
|
|
|
19
19
|
const tools_attachments_1 = require("./tools-attachments");
|
|
20
20
|
const tools_mail_1 = require("./tools-mail");
|
|
21
21
|
const tools_trip_1 = require("./tools-trip");
|
|
22
|
+
const tools_a2a_1 = require("./tools-a2a");
|
|
23
|
+
const tools_commerce_1 = require("./tools-commerce");
|
|
22
24
|
const tools_awaiting_1 = require("./tools-awaiting");
|
|
23
25
|
const tools_obligations_1 = require("./tools-obligations");
|
|
24
26
|
const tools_evolution_1 = require("./tools-evolution");
|
|
@@ -58,6 +60,8 @@ exports.baseToolDefinitions = [
|
|
|
58
60
|
...tools_attachments_1.attachmentToolDefinitions,
|
|
59
61
|
...tools_mail_1.mailToolDefinitions,
|
|
60
62
|
...tools_trip_1.tripToolDefinitions,
|
|
63
|
+
...tools_a2a_1.a2aToolDefinitions,
|
|
64
|
+
...tools_commerce_1.commerceToolDefinitions,
|
|
61
65
|
...tools_awaiting_1.awaitingToolDefinitions,
|
|
62
66
|
...tools_obligations_1.obligationToolDefinitions,
|
|
63
67
|
...tools_evolution_1.evolutionToolDefinitions,
|