@ouro.bot/cli 0.1.0-alpha.652 → 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.
Files changed (37) hide show
  1. package/changelog.json +13 -0
  2. package/dist/a2a/card.js +56 -0
  3. package/dist/a2a/client.js +143 -0
  4. package/dist/a2a/config.js +50 -0
  5. package/dist/a2a/onboarding.js +111 -0
  6. package/dist/a2a/server.js +498 -0
  7. package/dist/a2a/task-store.js +69 -0
  8. package/dist/a2a/types.js +3 -0
  9. package/dist/commerce/store.js +755 -0
  10. package/dist/commerce/types.js +3 -0
  11. package/dist/heart/daemon/cli-exec.js +119 -4
  12. package/dist/heart/daemon/cli-help.js +14 -2
  13. package/dist/heart/daemon/cli-parse.js +88 -4
  14. package/dist/heart/daemon/daemon.js +2 -1
  15. package/dist/heart/daemon/process-manager.js +2 -1
  16. package/dist/heart/daemon/runtime-logging.js +1 -1
  17. package/dist/heart/daemon/sense-manager.js +71 -15
  18. package/dist/heart/identity.js +4 -1
  19. package/dist/heart/sense-truth.js +2 -0
  20. package/dist/heart/turn-context.js +6 -0
  21. package/dist/mind/friends/channel.js +10 -1
  22. package/dist/mind/friends/resolver.js +13 -2
  23. package/dist/mind/friends/store-file.js +13 -0
  24. package/dist/mind/friends/types.js +1 -1
  25. package/dist/mind/prompt.js +11 -0
  26. package/dist/repertoire/guardrails.js +25 -2
  27. package/dist/repertoire/tools-a2a.js +283 -0
  28. package/dist/repertoire/tools-base.js +4 -0
  29. package/dist/repertoire/tools-commerce.js +253 -0
  30. package/dist/repertoire/tools-flight.js +68 -5
  31. package/dist/repertoire/tools-stripe.js +49 -7
  32. package/dist/repertoire/tools.js +50 -2
  33. package/dist/senses/a2a-entry.js +78 -0
  34. package/dist/senses/pipeline.js +13 -0
  35. package/dist/senses/shared-turn.js +30 -5
  36. package/package.json +1 -1
  37. package/skills/agent-commerce.md +17 -10
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commerceToolDefinitions = void 0;
4
+ const identity_1 = require("../heart/identity");
5
+ const runtime_1 = require("../nerves/runtime");
6
+ const store_1 = require("../commerce/store");
7
+ function requireFamilyContext(ctx) {
8
+ if (!ctx?.context?.friend?.id)
9
+ return "no friend context — cannot use commerce tools.";
10
+ if (ctx.context.friend.trustLevel !== "family")
11
+ return "commerce tools require family trust level.";
12
+ /* v8 ignore next -- no-agentRoot fallback depends on process argv; normal tool calls inject agentRoot @preserve */
13
+ if (ctx.agentRoot)
14
+ return { friendId: ctx.context.friend.id, agentRoot: ctx.agentRoot };
15
+ return { friendId: ctx.context.friend.id, agentRoot: (0, identity_1.getAgentRoot)() };
16
+ }
17
+ function parseItems(raw) {
18
+ if (!raw?.trim())
19
+ return undefined;
20
+ const parsed = JSON.parse(raw);
21
+ if (!Array.isArray(parsed))
22
+ throw new Error("items_json must be a JSON array");
23
+ return parsed.map((item) => {
24
+ if (!item || typeof item !== "object" || Array.isArray(item))
25
+ throw new Error("each item must be an object");
26
+ const record = item;
27
+ if (typeof record.name !== "string" || !record.name.trim())
28
+ throw new Error("each item needs a name");
29
+ return {
30
+ name: record.name,
31
+ ...(typeof record.quantity === "number" ? { quantity: record.quantity } : {}),
32
+ ...(typeof record.amount === "number" ? { amount: record.amount } : {}),
33
+ };
34
+ });
35
+ }
36
+ function parseExpiryMinutes(raw) {
37
+ if (!raw?.trim())
38
+ return undefined;
39
+ const value = Number.parseInt(raw, 10);
40
+ if (!Number.isInteger(value) || value <= 0)
41
+ throw new Error("expires_minutes must be a positive integer");
42
+ return value;
43
+ }
44
+ function parseExactAmount(raw) {
45
+ const value = raw?.trim();
46
+ if (!value || !/^\d+(?:\.\d{1,2})?$/.test(value))
47
+ throw new Error("amount must be an exact decimal with at most two places");
48
+ return Number(value);
49
+ }
50
+ function parseToolName(raw) {
51
+ const toolName = raw?.trim();
52
+ if (!toolName)
53
+ throw new Error("tool_name is required");
54
+ return toolName;
55
+ }
56
+ function parseConstraints(raw) {
57
+ if (!raw?.trim())
58
+ return undefined;
59
+ const parsed = JSON.parse(raw);
60
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
61
+ throw new Error("constraints_json must be a JSON object");
62
+ return Object.fromEntries(Object.entries(parsed).map(([key, value]) => [key, String(value)]));
63
+ }
64
+ function validateToolConstraints(toolName, constraints) {
65
+ if (toolName !== "stripe_create_card")
66
+ return;
67
+ if (!constraints?.type)
68
+ throw new Error("constraints_json.type is required for stripe_create_card");
69
+ if (!constraints.merchant_categories)
70
+ throw new Error("constraints_json.merchant_categories is required for stripe_create_card");
71
+ }
72
+ exports.commerceToolDefinitions = [
73
+ {
74
+ tool: {
75
+ type: "function",
76
+ function: {
77
+ name: "commerce_checkout_preview",
78
+ description: "Create an AP2-compatible local checkout preview before any payment or booking action. Requires family trust.",
79
+ parameters: {
80
+ type: "object",
81
+ properties: {
82
+ merchant: { type: "string", description: "Merchant, provider, or counterparty name." },
83
+ amount: { type: "string", description: "Exact total amount." },
84
+ currency: { type: "string", description: "Currency code, e.g. usd." },
85
+ tool_name: { type: "string", description: "Exact tool this authority may be used with, e.g. stripe_create_card, flight_hold, or flight_book." },
86
+ constraints_json: { type: "string", description: "JSON object of exact tool-argument constraints; required for stripe_create_card type and merchant_categories, and recommended for flight offer_id." },
87
+ reason: { type: "string", description: "Why this purchase/booking is being made." },
88
+ items_json: { type: "string", description: "Optional JSON array of {name, quantity, amount} items." },
89
+ expires_minutes: { type: "string", description: "Optional expiry window in minutes; defaults to 30." },
90
+ },
91
+ required: ["merchant", "amount", "currency", "tool_name", "reason"],
92
+ },
93
+ },
94
+ },
95
+ handler: async (args, ctx) => {
96
+ (0, runtime_1.emitNervesEvent)({
97
+ component: "repertoire",
98
+ event: "repertoire.tool_commerce_checkout_preview",
99
+ message: "commerce_checkout_preview invoked",
100
+ meta: { tool: "commerce_checkout_preview", merchant: args.merchant },
101
+ });
102
+ const guard = requireFamilyContext(ctx);
103
+ if (typeof guard === "string")
104
+ return guard;
105
+ try {
106
+ const expiresInMinutes = parseExpiryMinutes(args.expires_minutes);
107
+ const toolName = parseToolName(args.tool_name);
108
+ const constraints = parseConstraints(args.constraints_json);
109
+ const items = parseItems(args.items_json);
110
+ validateToolConstraints(toolName, constraints);
111
+ const record = (0, store_1.createCommercePreview)({
112
+ agentRoot: guard.agentRoot,
113
+ friendId: guard.friendId,
114
+ merchant: args.merchant,
115
+ amount: parseExactAmount(args.amount),
116
+ currency: args.currency,
117
+ allowedTools: [toolName],
118
+ constraints,
119
+ reason: args.reason,
120
+ items,
121
+ ...(expiresInMinutes ? { expiresInMinutes } : {}),
122
+ });
123
+ const confirmationMessage = (0, store_1.commerceConfirmationMessage)(record);
124
+ return JSON.stringify({
125
+ checkoutId: record.id,
126
+ merchant: record.merchant,
127
+ amount: record.amount,
128
+ currency: record.currency,
129
+ allowedTools: record.allowedTools,
130
+ constraints: record.constraints,
131
+ expiresAt: record.expiresAt,
132
+ digest: record.digest,
133
+ confirmationMessage,
134
+ next: `Ask the family user to send the confirmationMessage exactly in a new turn after reviewing merchant, amount, currency, tool, and constraints; then call commerce_checkout_commit from that same turn.`,
135
+ }, null, 2);
136
+ }
137
+ catch (error) {
138
+ return `commerce preview error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error parser failures */ String(error)}`;
139
+ }
140
+ },
141
+ summaryKeys: ["merchant", "amount", "currency"],
142
+ riskProfile: { mutates: "durable_state_write", risk: "high", reason: "creates a purchase mandate preview" },
143
+ },
144
+ {
145
+ tool: {
146
+ type: "function",
147
+ function: {
148
+ name: "commerce_checkout_commit",
149
+ description: "Confirm an exact checkout preview for the next matching payment/booking tool call. Requires family trust and confirmation=CONFIRM_PURCHASE.",
150
+ parameters: {
151
+ type: "object",
152
+ properties: {
153
+ checkout_id: { type: "string", description: "Checkout preview ID." },
154
+ digest: { type: "string", description: "Digest returned by commerce_checkout_preview." },
155
+ confirmation: { type: "string", description: "Must be CONFIRM_PURCHASE." },
156
+ },
157
+ required: ["checkout_id", "digest", "confirmation"],
158
+ },
159
+ },
160
+ },
161
+ handler: async (args, ctx) => {
162
+ (0, runtime_1.emitNervesEvent)({
163
+ component: "repertoire",
164
+ event: "repertoire.tool_commerce_checkout_commit",
165
+ message: "commerce_checkout_commit invoked",
166
+ meta: { tool: "commerce_checkout_commit", checkoutId: args.checkout_id },
167
+ });
168
+ const guard = requireFamilyContext(ctx);
169
+ if (typeof guard === "string")
170
+ return guard;
171
+ try {
172
+ const record = (0, store_1.confirmCommercePreview)({
173
+ agentRoot: guard.agentRoot,
174
+ checkoutId: args.checkout_id,
175
+ digest: args.digest,
176
+ confirmation: args.confirmation,
177
+ friendId: guard.friendId,
178
+ currentUserMessage: ctx?.currentUserMessage,
179
+ });
180
+ return JSON.stringify({
181
+ checkoutId: record.id,
182
+ status: record.status,
183
+ expiresAt: record.expiresAt,
184
+ use: "Call the approved payment or booking tool with the exact merchant/tool/amount/currency/constraint arguments; Ouro will consume the matching authority without exposing a bearer token.",
185
+ }, null, 2);
186
+ }
187
+ catch (error) {
188
+ return `commerce commit error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive non-Error store failures */ String(error)}`;
189
+ }
190
+ },
191
+ summaryKeys: ["checkout_id"],
192
+ riskProfile: { mutates: "durable_state_write", risk: "high", reason: "confirms a purchase mandate" },
193
+ },
194
+ {
195
+ tool: {
196
+ type: "function",
197
+ function: {
198
+ name: "commerce_receipt_get",
199
+ description: "Read a local commerce checkout/mandate receipt. Requires family trust.",
200
+ parameters: {
201
+ type: "object",
202
+ properties: {
203
+ checkout_id: { type: "string", description: "Checkout ID." },
204
+ },
205
+ required: ["checkout_id"],
206
+ },
207
+ },
208
+ },
209
+ handler: async (args, ctx) => {
210
+ (0, runtime_1.emitNervesEvent)({
211
+ component: "repertoire",
212
+ event: "repertoire.tool_commerce_receipt_get",
213
+ message: "commerce_receipt_get invoked",
214
+ meta: { tool: "commerce_receipt_get", checkoutId: args.checkout_id },
215
+ });
216
+ const guard = requireFamilyContext(ctx);
217
+ if (typeof guard === "string")
218
+ return guard;
219
+ const record = (0, store_1.readCommerceRecord)(guard.agentRoot, args.checkout_id);
220
+ return record ? JSON.stringify(record, null, 2) : `commerce checkout not found: ${args.checkout_id}`;
221
+ },
222
+ summaryKeys: ["checkout_id"],
223
+ },
224
+ {
225
+ tool: {
226
+ type: "function",
227
+ function: {
228
+ name: "commerce_access_log",
229
+ description: "Read the commerce authority access log. Requires family trust.",
230
+ parameters: {
231
+ type: "object",
232
+ properties: {
233
+ limit: { type: "string", description: "Number of recent entries to return; defaults to 20." },
234
+ },
235
+ },
236
+ },
237
+ },
238
+ handler: async (args, ctx) => {
239
+ (0, runtime_1.emitNervesEvent)({
240
+ component: "repertoire",
241
+ event: "repertoire.tool_commerce_access_log",
242
+ message: "commerce_access_log invoked",
243
+ meta: { tool: "commerce_access_log" },
244
+ });
245
+ const guard = requireFamilyContext(ctx);
246
+ if (typeof guard === "string")
247
+ return guard;
248
+ const limit = args.limit ? Number.parseInt(args.limit, 10) : 20;
249
+ return JSON.stringify((0, store_1.readCommerceAccessLog)(guard.agentRoot, Number.isInteger(limit) ? limit : 20), null, 2);
250
+ },
251
+ summaryKeys: ["limit"],
252
+ },
253
+ ];
@@ -6,6 +6,7 @@ const duffel_client_1 = require("./duffel-client");
6
6
  const user_profile_1 = require("./user-profile");
7
7
  const credential_access_1 = require("./credential-access");
8
8
  const runtime_1 = require("../nerves/runtime");
9
+ const store_1 = require("../commerce/store");
9
10
  // Lazy-initialized Duffel client singleton
10
11
  let _duffelClient = null;
11
12
  async function getDuffelClient() {
@@ -29,6 +30,43 @@ function requireFriendContext(ctx) {
29
30
  }
30
31
  return { friendId: ctx.context.friend.id };
31
32
  }
33
+ function parseExactAmount(raw) {
34
+ const value = raw.trim();
35
+ if (!/^\d+(?:\.\d{1,2})?$/.test(value))
36
+ throw new Error("amount must be an exact decimal with at most two places");
37
+ return Number(value);
38
+ }
39
+ function consumeReservedCommerce(ctx, toolName) {
40
+ if (!ctx?.commerceAuthority || !ctx.agentRoot)
41
+ return null;
42
+ const result = (0, store_1.consumeReservedCommerceAuthority)({
43
+ agentRoot: ctx.agentRoot,
44
+ checkoutId: ctx.commerceAuthority.checkoutId,
45
+ reservationToken: ctx.commerceAuthority.reservationToken,
46
+ toolName,
47
+ friendId: ctx.context?.friend?.id,
48
+ });
49
+ return result.ok ? null : `commerce authority consume error: ${result.reason}`;
50
+ }
51
+ function markCommerceAttempted(ctx, toolName) {
52
+ if (!ctx?.commerceAuthority || !ctx.agentRoot)
53
+ return null;
54
+ const result = (0, store_1.markReservedCommerceAuthorityAttempted)({
55
+ agentRoot: ctx.agentRoot,
56
+ checkoutId: ctx.commerceAuthority.checkoutId,
57
+ reservationToken: ctx.commerceAuthority.reservationToken,
58
+ toolName,
59
+ friendId: ctx.context?.friend?.id,
60
+ });
61
+ return result.ok ? null : `commerce authority attempt error: ${result.reason}`;
62
+ }
63
+ function normalizeCurrency(raw) {
64
+ return raw.trim().toLowerCase();
65
+ }
66
+ function orderMatchesApprovedTotal(input, amount, currency) {
67
+ const orderAmount = parseExactAmount(input.totalAmount);
68
+ return orderAmount === amount && normalizeCurrency(input.totalCurrency) === normalizeCurrency(currency);
69
+ }
32
70
  exports.flightToolDefinitions = [
33
71
  {
34
72
  tool: {
@@ -89,13 +127,16 @@ exports.flightToolDefinitions = [
89
127
  type: "function",
90
128
  function: {
91
129
  name: "flight_hold",
92
- description: "Hold a flight offer for a short period before committing to book. Not all airlines support holds.",
130
+ description: "Hold a flight offer for a short period before committing to book. Not all airlines support holds. Requires a matching confirmed commerce checkout.",
93
131
  parameters: {
94
132
  type: "object",
95
133
  properties: {
96
134
  offer_id: { type: "string", description: "The Duffel offer ID to hold" },
135
+ amount: { type: "string", description: "Exact hold amount from the approved commerce preview." },
136
+ currency: { type: "string", description: "Currency code from the approved commerce preview, e.g. usd." },
137
+ commerce_authority: { type: "string", description: "Optional explicit authority token for external/manual flows; normally omit so Ouro consumes the matching confirmed checkout." },
97
138
  },
98
- required: ["offer_id"],
139
+ required: ["offer_id", "amount", "currency"],
99
140
  },
100
141
  },
101
142
  },
@@ -111,9 +152,19 @@ exports.flightToolDefinitions = [
111
152
  return guard;
112
153
  // Hold functionality would call Duffel's offer hold API.
113
154
  // For pre-build, we return a structured acknowledgment.
155
+ const amount = parseExactAmount(args.amount);
156
+ const attemptError = markCommerceAttempted(ctx, "flight_hold");
157
+ if (attemptError)
158
+ return attemptError;
159
+ const consumeError = consumeReservedCommerce(ctx, "flight_hold");
160
+ /* v8 ignore next -- hold pre-build has no provider callback between attempt and consume; this branch is race-defense for file/process interference @preserve */
161
+ if (consumeError)
162
+ return consumeError;
114
163
  return JSON.stringify({
115
164
  status: "hold_requested",
116
165
  offerId: args.offer_id,
166
+ amount,
167
+ currency: args.currency,
117
168
  message: "Hold requested. Confirm or cancel before the hold expires.",
118
169
  });
119
170
  },
@@ -125,13 +176,14 @@ exports.flightToolDefinitions = [
125
176
  type: "function",
126
177
  function: {
127
178
  name: "flight_book",
128
- description: "Book a flight. Pulls passenger name/DOB/passport from the user's profile. Creates a virtual card, books the flight, then deactivates the card. Requires family trust level.",
179
+ description: "Book a flight. Pulls passenger name/DOB/passport from the user's profile. Creates a virtual card, books the flight, then deactivates the card. Requires family trust level and a matching confirmed commerce checkout.",
129
180
  parameters: {
130
181
  type: "object",
131
182
  properties: {
132
183
  offer_id: { type: "string", description: "The Duffel offer ID to book" },
133
184
  amount: { type: "string", description: "Expected total amount in dollars" },
134
185
  currency: { type: "string", description: "Currency code (e.g. 'usd')" },
186
+ commerce_authority: { type: "string", description: "Optional explicit authority token for external/manual flows; normally omit so Ouro consumes the matching confirmed checkout." },
135
187
  },
136
188
  required: ["offer_id", "amount", "currency"],
137
189
  },
@@ -148,6 +200,8 @@ exports.flightToolDefinitions = [
148
200
  if (typeof guard === "string")
149
201
  return guard;
150
202
  try {
203
+ const amount = parseExactAmount(args.amount);
204
+ const currency = normalizeCurrency(args.currency);
151
205
  const store = (0, credential_access_1.getCredentialStore)();
152
206
  // Get passenger data from profile
153
207
  const legalName = await (0, user_profile_1.getUserProfileField)(guard.friendId, "legalName", store);
@@ -157,6 +211,9 @@ exports.flightToolDefinitions = [
157
211
  const dateOfBirth = await (0, user_profile_1.getUserProfileField)(guard.friendId, "dateOfBirth", store);
158
212
  const passport = await (0, user_profile_1.getUserProfileField)(guard.friendId, "passport", store);
159
213
  const client = await getDuffelClient();
214
+ const attemptError = markCommerceAttempted(ctx, "flight_book");
215
+ if (attemptError)
216
+ return attemptError;
160
217
  const result = await client.createOrder({
161
218
  offerId: args.offer_id,
162
219
  passengers: [{
@@ -169,9 +226,15 @@ exports.flightToolDefinitions = [
169
226
  passportCountry: passport?.country,
170
227
  passportExpiry: passport?.expiry,
171
228
  }],
172
- amount: parseFloat(args.amount),
173
- currency: args.currency,
229
+ amount,
230
+ currency,
174
231
  });
232
+ if (!orderMatchesApprovedTotal(result, amount, currency)) {
233
+ return `booking error: completed order total ${result.totalAmount} ${result.totalCurrency} does not match approved ${args.amount} ${args.currency}`;
234
+ }
235
+ const consumeError = consumeReservedCommerce(ctx, "flight_book");
236
+ if (consumeError)
237
+ return consumeError;
175
238
  return JSON.stringify(result, null, 2);
176
239
  }
177
240
  catch (err) {
@@ -4,6 +4,7 @@ exports.stripeToolDefinitions = void 0;
4
4
  exports.resetStripeClient = resetStripeClient;
5
5
  const stripe_client_1 = require("./stripe-client");
6
6
  const runtime_1 = require("../nerves/runtime");
7
+ const store_1 = require("../commerce/store");
7
8
  // Lazy-initialized Stripe client singleton
8
9
  let _stripeClient = null;
9
10
  async function getStripeClient() {
@@ -21,13 +22,43 @@ function requireFamilyContext(ctx) {
21
22
  }
22
23
  return { friendId: ctx.context.friend.id };
23
24
  }
25
+ function parseExactSpendLimit(raw) {
26
+ const value = raw.trim();
27
+ if (!/^\d+(?:\.\d{1,2})?$/.test(value))
28
+ throw new Error("spend_limit must be an exact decimal with at most two places");
29
+ return Number(value);
30
+ }
31
+ function consumeReservedCommerce(ctx, toolName) {
32
+ if (!ctx?.commerceAuthority || !ctx.agentRoot)
33
+ return null;
34
+ const result = (0, store_1.consumeReservedCommerceAuthority)({
35
+ agentRoot: ctx.agentRoot,
36
+ checkoutId: ctx.commerceAuthority.checkoutId,
37
+ reservationToken: ctx.commerceAuthority.reservationToken,
38
+ toolName,
39
+ friendId: ctx.context?.friend?.id,
40
+ });
41
+ return result.ok ? null : `commerce authority consume error: ${result.reason}`;
42
+ }
43
+ function markCommerceAttempted(ctx, toolName) {
44
+ if (!ctx?.commerceAuthority || !ctx.agentRoot)
45
+ return null;
46
+ const result = (0, store_1.markReservedCommerceAuthorityAttempted)({
47
+ agentRoot: ctx.agentRoot,
48
+ checkoutId: ctx.commerceAuthority.checkoutId,
49
+ reservationToken: ctx.commerceAuthority.reservationToken,
50
+ toolName,
51
+ friendId: ctx.context?.friend?.id,
52
+ });
53
+ return result.ok ? null : `commerce authority attempt error: ${result.reason}`;
54
+ }
24
55
  exports.stripeToolDefinitions = [
25
56
  {
26
57
  tool: {
27
58
  type: "function",
28
59
  function: {
29
60
  name: "stripe_create_card",
30
- description: "Create a virtual card for a transaction. Returns card ID and last 4 digits (never the full card number). Requires family trust level.",
61
+ description: "Create a virtual card for an approved transaction. Returns card ID and last 4 digits (never the full card number). Requires family trust level and a matching confirmed commerce checkout.",
31
62
  parameters: {
32
63
  type: "object",
33
64
  properties: {
@@ -46,10 +77,14 @@ exports.stripeToolDefinitions = [
46
77
  },
47
78
  merchant_categories: {
48
79
  type: "string",
49
- description: "Comma-separated allowed merchant categories (optional)",
80
+ description: "Comma-separated allowed merchant categories from the confirmed commerce constraints.",
81
+ },
82
+ commerce_authority: {
83
+ type: "string",
84
+ description: "Optional explicit authority token for external/manual flows; normally omit so Ouro consumes the matching confirmed checkout without exposing a bearer token.",
50
85
  },
51
86
  },
52
- required: ["type", "spend_limit", "currency"],
87
+ required: ["type", "spend_limit", "currency", "merchant_categories"],
53
88
  },
54
89
  },
55
90
  },
@@ -64,16 +99,23 @@ exports.stripeToolDefinitions = [
64
99
  if (typeof guard === "string")
65
100
  return guard;
66
101
  try {
102
+ const categories = args.merchant_categories.split(",").map((c) => c.trim()).filter(Boolean);
103
+ if (categories.length === 0)
104
+ throw new Error("merchant_categories is required");
105
+ const spendLimit = parseExactSpendLimit(args.spend_limit);
106
+ const attemptError = markCommerceAttempted(ctx, "stripe_create_card");
107
+ if (attemptError)
108
+ return attemptError;
67
109
  const client = await getStripeClient();
68
- const categories = args.merchant_categories
69
- ? args.merchant_categories.split(",").map((c) => c.trim())
70
- : undefined;
71
110
  const card = await client.createVirtualCard({
72
111
  type: args.type,
73
- spendLimit: parseFloat(args.spend_limit),
112
+ spendLimit,
74
113
  currency: args.currency,
75
114
  merchantCategories: categories,
76
115
  });
116
+ const consumeError = consumeReservedCommerce(ctx, "stripe_create_card");
117
+ if (consumeError)
118
+ return consumeError;
77
119
  return JSON.stringify({
78
120
  cardId: card.cardId,
79
121
  last4: card.last4,
@@ -15,6 +15,7 @@ const tools_bundle_1 = require("./tools-bundle");
15
15
  const runtime_1 = require("../nerves/runtime");
16
16
  const guardrails_1 = require("./guardrails");
17
17
  const identity_1 = require("../heart/identity");
18
+ const store_1 = require("../commerce/store");
18
19
  const tools_surface_1 = require("./tools-surface");
19
20
  const mcp_tools_1 = require("./mcp-tools");
20
21
  const tools_voice_1 = require("./tools-voice");
@@ -40,6 +41,7 @@ var tools_surface_2 = require("./tools-surface");
40
41
  Object.defineProperty(exports, "surfaceToolDef", { enumerable: true, get: function () { return tools_surface_2.surfaceToolDef; } });
41
42
  // All tool definitions in a single registry
42
43
  const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, ...tools_bundle_1.bundleToolDefinitions, ...tools_voice_1.voiceToolDefinitions, tools_surface_1.surfaceToolDefinition];
44
+ const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
43
45
  // MCP tool definitions — populated each time getToolsForChannel() is called with an mcpManager.
44
46
  // Kept separate from allDefinitions so execTool can find them.
45
47
  let mcpDefinitions = [];
@@ -251,7 +253,8 @@ async function execTool(name, args, ctx) {
251
253
  const guardContext = {
252
254
  readPaths: tools_base_1.editFileReadTracker,
253
255
  trustLevel: ctx?.context?.friend?.trustLevel,
254
- agentRoot: safeGetAgentRoot(),
256
+ agentRoot: ctx?.agentRoot ?? safeGetAgentRoot(),
257
+ friendId: ctx?.context?.friend?.id,
255
258
  ...(mcpDef?.mcpServer ? { mcpServerName: mcpDef.mcpServer } : {}),
256
259
  ...(ctx?.context?.isGroupChat !== undefined ? { isGroupChat: (ctx?.context).isGroupChat } : {}),
257
260
  };
@@ -267,8 +270,37 @@ async function execTool(name, args, ctx) {
267
270
  });
268
271
  return guardResult.reason;
269
272
  }
273
+ const commerceReservation = COMMERCE_AUTHORITY_TOOLS.has(name) && guardContext.agentRoot
274
+ ? (0, store_1.reserveCommerceAuthority)({
275
+ agentRoot: guardContext.agentRoot,
276
+ token: guardArgs.commerce_authority,
277
+ toolName: name,
278
+ args: guardArgs,
279
+ friendId: guardContext.friendId,
280
+ })
281
+ : null;
282
+ if (commerceReservation && !commerceReservation.ok) {
283
+ (0, runtime_1.emitNervesEvent)({
284
+ level: "warn",
285
+ event: "tool.guardrail_block",
286
+ component: "tools",
287
+ message: "guardrail blocked tool execution",
288
+ meta: { name, reason: commerceReservation.reason },
289
+ });
290
+ return `commerce authority required: ${commerceReservation.reason}`;
291
+ }
292
+ const toolContext = commerceReservation?.ok
293
+ ? {
294
+ ...ctx,
295
+ agentRoot: guardContext.agentRoot,
296
+ commerceAuthority: {
297
+ checkoutId: commerceReservation.checkoutId,
298
+ reservationToken: commerceReservation.reservationToken,
299
+ },
300
+ }
301
+ : ctx;
270
302
  try {
271
- const result = await def.handler(args, ctx);
303
+ const result = await def.handler(args, toolContext);
272
304
  (0, runtime_1.emitNervesEvent)({
273
305
  event: "tool.end",
274
306
  component: "tools",
@@ -287,6 +319,22 @@ async function execTool(name, args, ctx) {
287
319
  });
288
320
  throw error;
289
321
  }
322
+ finally {
323
+ if (commerceReservation?.ok && guardContext.agentRoot) {
324
+ try {
325
+ (0, store_1.releaseReservedCommerceAuthority)({
326
+ agentRoot: guardContext.agentRoot,
327
+ checkoutId: commerceReservation.checkoutId,
328
+ reservationToken: commerceReservation.reservationToken,
329
+ toolName: name,
330
+ friendId: guardContext.friendId,
331
+ });
332
+ }
333
+ catch {
334
+ /* v8 ignore next -- external tool result/error should not be masked by best-effort reservation cleanup @preserve */
335
+ }
336
+ }
337
+ }
290
338
  }
291
339
  function summarizeKeyValues(args, keys, maxValueLength = 60) {
292
340
  const parts = [];
@@ -0,0 +1,78 @@
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
+ const agentArgIndex = process.argv.indexOf("--agent");
37
+ const agentName = agentArgIndex >= 0 ? process.argv[agentArgIndex + 1] : undefined;
38
+ if (!agentName) {
39
+ // eslint-disable-next-line no-console -- pre-boot guard: --agent check before imports
40
+ console.error("Missing required --agent <name> argument.\nUsage: node dist/senses/a2a-entry.js --agent ouroboros [--port 18920] [--base-url https://agent.example]");
41
+ process.exit(1);
42
+ }
43
+ function argValue(name) {
44
+ const index = process.argv.indexOf(name);
45
+ return index >= 0 ? process.argv[index + 1] : undefined;
46
+ }
47
+ const runtime_logging_1 = require("../heart/daemon/runtime-logging");
48
+ const runtime_1 = require("../nerves/runtime");
49
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("a2a");
50
+ (0, runtime_1.emitNervesEvent)({
51
+ component: "senses",
52
+ event: "senses.entry_boot",
53
+ message: "booting A2A entrypoint",
54
+ meta: { entry: "a2a", agentName },
55
+ });
56
+ Promise.resolve().then(() => __importStar(require("../a2a/server"))).then(async ({ startA2AServer }) => {
57
+ const rawPort = argValue("--port");
58
+ const port = rawPort ? Number.parseInt(rawPort, 10) : undefined;
59
+ await startA2AServer({
60
+ agentName,
61
+ ...(argValue("--host") ? { host: argValue("--host") } : {}),
62
+ ...(Number.isInteger(port) ? { port } : {}),
63
+ ...(argValue("--base-url") ? { baseUrl: argValue("--base-url") } : {}),
64
+ ...(argValue("--path") ? { path: argValue("--path") } : {}),
65
+ });
66
+ })
67
+ .catch((error) => {
68
+ (0, runtime_1.emitNervesEvent)({
69
+ level: "error",
70
+ component: "senses",
71
+ event: "senses.entry_error",
72
+ message: "A2A entrypoint failed",
73
+ meta: { entry: "a2a", agentName, error: error instanceof Error ? error.message : String(error) },
74
+ });
75
+ // eslint-disable-next-line no-console -- fatal startup guard for sense process
76
+ console.error(error instanceof Error ? error.message : String(error));
77
+ process.exit(1);
78
+ });