xytara 2.5.0 → 2.6.0

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 (61) hide show
  1. package/.env.example +29 -0
  2. package/BSV_TERANODE_SETUP.md +43 -0
  3. package/README.md +66 -7
  4. package/REAL_PAYMENT_SETUP.md +61 -0
  5. package/RELEASE_NOTES.md +12 -6
  6. package/SERVICE_CONTRACT.md +31 -0
  7. package/START_HERE.md +107 -6
  8. package/TREASURY_DESTINATIONS.example.json +66 -0
  9. package/TREASURY_DESTINATIONS.production.template.json +108 -0
  10. package/adapters/examples/langchain-reference-execution-adapter.js +81 -0
  11. package/adapters/examples/langchain-reference-execution-adapter.manifest.json +79 -0
  12. package/adapters/examples/langgraph-reference-execution-adapter.js +81 -0
  13. package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +79 -0
  14. package/bin/xytara-first-run.js +244 -0
  15. package/bin/xytara.js +30 -5
  16. package/integrations/dispatch.js +7 -2
  17. package/integrations/registry.js +123 -2
  18. package/lib/activity_audit_contract.js +322 -0
  19. package/lib/adapter_depth_contract.js +156 -0
  20. package/lib/bolt_lane_contract.js +55 -0
  21. package/lib/bolt_payment_front.js +172 -0
  22. package/lib/capability_execution_truth.js +269 -0
  23. package/lib/capability_registry.js +39 -2
  24. package/lib/checkout_event_security.js +142 -0
  25. package/lib/command_flow.js +169 -36
  26. package/lib/commerce_artifacts.js +23 -4
  27. package/lib/commerce_checkout.js +134 -4
  28. package/lib/commerce_client.js +20 -0
  29. package/lib/commerce_economics.js +109 -9
  30. package/lib/commerce_runtime.js +38 -2
  31. package/lib/commerce_shell.js +4 -2
  32. package/lib/external_execution_runtime.js +132 -0
  33. package/lib/go_live_operator_pack.js +339 -0
  34. package/lib/http_transport.js +32 -0
  35. package/lib/integration_matrix_contract.js +6 -0
  36. package/lib/l402_lane_contract.js +55 -0
  37. package/lib/l402_payment_front.js +192 -0
  38. package/lib/network_transport.js +110 -0
  39. package/lib/operator_intelligence.js +215 -1
  40. package/lib/payment_config.js +80 -3
  41. package/lib/payment_front_registry.js +53 -0
  42. package/lib/payment_protocol_contract.js +165 -0
  43. package/lib/payment_verification.js +23 -3
  44. package/lib/pricing_optimization_contract.js +556 -0
  45. package/lib/release_history.js +15 -0
  46. package/lib/request_rate_limit.js +144 -0
  47. package/lib/runtime_bridge.js +10 -2
  48. package/lib/runtime_operations_worker.js +282 -0
  49. package/lib/settlement_bsv_live.js +79 -4
  50. package/lib/stripe_mpp_lane_contract.js +3 -0
  51. package/lib/stripe_mpp_payment_front.js +158 -0
  52. package/lib/treasury_destinations_contract.js +543 -0
  53. package/lib/treasury_egress_policy_contract.js +91 -0
  54. package/package.json +14 -21
  55. package/server.js +627 -88
  56. package/OPERATIONS_RUNBOOK.md +0 -66
  57. package/OPERATOR_START_HERE.md +0 -63
  58. package/PROGRAM_COMPLETE_RELEASE.md +0 -63
  59. package/PROGRAM_STATUS.md +0 -57
  60. package/PUBLISH_PLAN.md +0 -14
  61. package/RELEASE_CHECKLIST.md +0 -16
@@ -1499,6 +1499,26 @@ function summarizeDeliveryPayload(payload) {
1499
1499
  status: delivery.status || null,
1500
1500
  attempt_count: Number(delivery.attempt_count || 0),
1501
1501
  last_attempt_at_iso: delivery.last_attempt_at_iso || null,
1502
+ callback_delivery_status: delivery.callback_delivery && delivery.callback_delivery.status
1503
+ ? delivery.callback_delivery.status
1504
+ : null,
1505
+ callback_delivery_attempt_count: Number(
1506
+ delivery.callback_delivery && delivery.callback_delivery.attempt_count
1507
+ ? delivery.callback_delivery.attempt_count
1508
+ : 0
1509
+ ),
1510
+ callback_next_attempt_at_iso: delivery.callback_delivery && delivery.callback_delivery.next_attempt_at_iso
1511
+ ? delivery.callback_delivery.next_attempt_at_iso
1512
+ : null,
1513
+ callback_delivered_at_iso: delivery.callback_delivery && delivery.callback_delivery.delivered_at_iso
1514
+ ? delivery.callback_delivery.delivered_at_iso
1515
+ : null,
1516
+ callback_failed_at_iso: delivery.callback_delivery && delivery.callback_delivery.failed_at_iso
1517
+ ? delivery.callback_delivery.failed_at_iso
1518
+ : null,
1519
+ callback_last_error: delivery.callback_delivery && delivery.callback_delivery.last_error
1520
+ ? delivery.callback_delivery.last_error
1521
+ : null,
1502
1522
  created_at_iso: delivery.created_at_iso || null,
1503
1523
  delivered_at_iso: delivery.delivered_at_iso || null,
1504
1524
  history_count: Array.isArray(delivery.history) ? delivery.history.length : 0
@@ -226,6 +226,8 @@ function applyRailCredits(state, body) {
226
226
  currency: normalizeString(payload.currency, null),
227
227
  purchase_intent_id: normalizeString(payload.purchase_intent_id, null),
228
228
  provider_reference: normalizeString(payload.provider_reference, null),
229
+ normalized_event_kind: "mint",
230
+ reverses_credit_event_id: null,
229
231
  created_at_iso: nowIso(),
230
232
  applied_status: "recorded"
231
233
  };
@@ -233,6 +235,92 @@ function applyRailCredits(state, body) {
233
235
  return event;
234
236
  }
235
237
 
238
+ function reverseRailCredits(state, body) {
239
+ const payload = ensureObject(body);
240
+ const accountId = normalizeString(payload.account_id, "acct_demo");
241
+ const originalCreditEventId = normalizeString(payload.credit_event_id, null);
242
+ const purchaseIntentId = normalizeString(payload.purchase_intent_id, null);
243
+ const providerReference = normalizeString(payload.provider_reference, null);
244
+ const credits = ensureArray(state.economicsRailCredits);
245
+ const original = credits.find((entry) => {
246
+ if (!entry || entry.account_id !== accountId || entry.normalized_event_kind === "reverse") return false;
247
+ if (originalCreditEventId && entry.credit_event_id === originalCreditEventId) return true;
248
+ if (purchaseIntentId && entry.purchase_intent_id === purchaseIntentId) return true;
249
+ if (providerReference && entry.provider_reference === providerReference) return true;
250
+ return false;
251
+ });
252
+
253
+ if (!original) {
254
+ return {
255
+ ok: false,
256
+ reason: "credit_event_not_found",
257
+ account_id: accountId,
258
+ credit_event_id: originalCreditEventId,
259
+ purchase_intent_id: purchaseIntentId
260
+ };
261
+ }
262
+
263
+ const existingReversal = credits.find((entry) =>
264
+ entry
265
+ && entry.account_id === accountId
266
+ && entry.normalized_event_kind === "reverse"
267
+ && entry.reverses_credit_event_id === original.credit_event_id
268
+ );
269
+ if (existingReversal) {
270
+ return {
271
+ ok: true,
272
+ reversal: existingReversal,
273
+ original,
274
+ already_reversed: true
275
+ };
276
+ }
277
+
278
+ const reversalUnits = Number.isFinite(Number(payload.units))
279
+ ? Math.max(0, Number(payload.units))
280
+ : Math.max(0, Number(original.units || 0));
281
+ const reversal = {
282
+ credit_event_id: normalizeString(payload.reversal_credit_event_id, `credit_reverse_${state.economicsRailCredits.length + 1}`),
283
+ account_id: accountId,
284
+ rail_kind: normalizeString(payload.rail_kind, original.rail_kind || "payment_credit"),
285
+ units: reversalUnits,
286
+ preview_status: "admissible",
287
+ normalized_event_kind: "reverse",
288
+ reverses_credit_event_id: original.credit_event_id,
289
+ amount_minor: Number.isFinite(Number(payload.amount_minor))
290
+ ? Number(payload.amount_minor)
291
+ : Number.isFinite(Number(original.amount_minor))
292
+ ? Number(original.amount_minor)
293
+ : null,
294
+ currency: normalizeString(payload.currency, original.currency || null),
295
+ purchase_intent_id: purchaseIntentId || original.purchase_intent_id || null,
296
+ provider_reference: providerReference || original.provider_reference || null,
297
+ reason: normalizeString(payload.reason, "hosted_checkout_refund_compensation"),
298
+ created_at_iso: nowIso(),
299
+ applied_status: "recorded"
300
+ };
301
+ state.economicsRailCredits = [reversal, ...credits].slice(0, 200);
302
+ return {
303
+ ok: true,
304
+ reversal,
305
+ original,
306
+ already_reversed: false
307
+ };
308
+ }
309
+
310
+ function summarizeRailCreditLifecycle(credits) {
311
+ const allCredits = Array.isArray(credits) ? credits : [];
312
+ return {
313
+ mint_count: allCredits.filter((entry) => entry && entry.normalized_event_kind !== "reverse").length,
314
+ reverse_count: allCredits.filter((entry) => entry && entry.normalized_event_kind === "reverse").length,
315
+ minted_units: allCredits
316
+ .filter((entry) => entry && entry.normalized_event_kind !== "reverse")
317
+ .reduce((sum, entry) => sum + Number(entry.units || 0), 0),
318
+ reversed_units: allCredits
319
+ .filter((entry) => entry && entry.normalized_event_kind === "reverse")
320
+ .reduce((sum, entry) => sum + Number(entry.units || 0), 0)
321
+ };
322
+ }
323
+
236
324
  function listEntitlementsForAccount(state, accountId) {
237
325
  return Array.from(state.economicsEntitlements.values()).filter((item) => item && item.account_id === accountId);
238
326
  }
@@ -493,9 +581,11 @@ function buildCreditBalanceSummary(state, accountId) {
493
581
  const credits = listRailCreditsForAccount(state, accountId);
494
582
  const allocations = listAllocationEventsForAccount(state, accountId);
495
583
  const spends = listCreditSpendsForAccount(state, accountId);
584
+ const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
496
585
  const issuedUnits = allocations.filter((entry) => entry.direction === "mint").reduce((sum, entry) => sum + (entry.units || 0), 0)
497
- + credits.reduce((sum, entry) => sum + (entry.units || 0), 0);
498
- const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0);
586
+ + railCreditLifecycle.minted_units;
587
+ const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
588
+ + railCreditLifecycle.reversed_units;
499
589
  const consumedUnits = spends.reduce((sum, entry) => sum + (entry.units || 0), 0);
500
590
  return {
501
591
  account_id: accountId,
@@ -574,10 +664,11 @@ function buildWalletLifecycleSummary(state, accountId) {
574
664
  const railCredits = listRailCreditsForAccount(state, accountId);
575
665
  const creditSpends = listCreditSpendsForAccount(state, accountId);
576
666
  const usageMeters = listUsageMetersForAccount(state, accountId);
667
+ const railCreditLifecycle = summarizeRailCreditLifecycle(railCredits);
577
668
  const mintedUnits = allocations
578
669
  .filter((entry) => entry && entry.direction === "mint")
579
670
  .reduce((sum, entry) => sum + Number(entry.units || 0), 0)
580
- + railCredits.reduce((sum, entry) => sum + Number(entry.units || 0), 0);
671
+ + railCreditLifecycle.minted_units;
581
672
  const reservedUnits = allocations
582
673
  .filter((entry) => entry && entry.direction === "reserve")
583
674
  .reduce((sum, entry) => sum + Number(entry.units || 0), 0);
@@ -596,7 +687,8 @@ function buildWalletLifecycleSummary(state, accountId) {
596
687
  .reduce((sum, entry) => sum + Number(entry.units || 0), 0);
597
688
  const reversedUnits = allocations
598
689
  .filter((entry) => entry && entry.direction === "reverse")
599
- .reduce((sum, entry) => sum + Number(entry.units || 0), 0);
690
+ .reduce((sum, entry) => sum + Number(entry.units || 0), 0)
691
+ + railCreditLifecycle.reversed_units;
600
692
  const committedUnits = explicitCommittedUnits + spendCommittedUnits;
601
693
  const openReservedUnits = Math.max(0, reservedUnits - releasedUnits);
602
694
  const correctionState = reversedUnits > 0 ? "corrective_activity_present" : "none";
@@ -635,7 +727,7 @@ function buildWalletLifecycleSummary(state, accountId) {
635
727
  record_interpretation: {
636
728
  mint_sources: {
637
729
  allocation_mint_count: allocations.filter((entry) => entry && entry.direction === "mint").length,
638
- rail_credit_count: railCredits.length
730
+ rail_credit_count: railCreditLifecycle.mint_count
639
731
  },
640
732
  reserve_sources: {
641
733
  allocation_reserve_count: allocations.filter((entry) => entry && entry.direction === "reserve").length
@@ -649,7 +741,8 @@ function buildWalletLifecycleSummary(state, accountId) {
649
741
  allocation_release_count: allocations.filter((entry) => entry && entry.direction === "release").length
650
742
  },
651
743
  reverse_sources: {
652
- allocation_reverse_count: allocations.filter((entry) => entry && entry.direction === "reverse").length
744
+ allocation_reverse_count: allocations.filter((entry) => entry && entry.direction === "reverse").length,
745
+ rail_credit_reverse_count: railCreditLifecycle.reverse_count
653
746
  }
654
747
  },
655
748
  linked_surfaces: {
@@ -1388,9 +1481,11 @@ function buildTreasurySummary(state, accountId) {
1388
1481
  const spends = listCreditSpendsForAccount(state, accountId);
1389
1482
  const paymentLedger = listPaymentLedgerForAccount(state, accountId);
1390
1483
  const budgets = listMapValuesByAccount(state.economicsBudgets, accountId);
1484
+ const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
1391
1485
  const mintedUnits = allocations.filter((entry) => entry.direction === "mint").reduce((sum, entry) => sum + (entry.units || 0), 0)
1392
- + credits.reduce((sum, entry) => sum + (entry.units || 0), 0);
1393
- const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0);
1486
+ + railCreditLifecycle.minted_units;
1487
+ const reversedUnits = allocations.filter((entry) => entry.direction === "reverse").reduce((sum, entry) => sum + (entry.units || 0), 0)
1488
+ + railCreditLifecycle.reversed_units;
1394
1489
  const consumedUnits = spends.reduce((sum, entry) => sum + (entry.units || 0), 0);
1395
1490
  const settledMinor = paymentLedger.reduce((sum, entry) => sum + (entry.amount_minor || 0), 0);
1396
1491
  const committedUnits = listTransactionsForAccount(state, accountId).length;
@@ -3596,6 +3691,7 @@ function buildNetworkParticipationPackage(state, accountId) {
3596
3691
 
3597
3692
  function buildRailSummary(state, accountId) {
3598
3693
  const credits = listRailCreditsForAccount(state, accountId);
3694
+ const railCreditLifecycle = summarizeRailCreditLifecycle(credits);
3599
3695
  const paymentLedger = listPaymentLedgerForAccount(state, accountId);
3600
3696
  const spends = listCreditSpendsForAccount(state, accountId);
3601
3697
  const entitlements = listEntitlementsForAccount(state, accountId);
@@ -3603,7 +3699,10 @@ function buildRailSummary(state, accountId) {
3603
3699
  return {
3604
3700
  account_id: accountId,
3605
3701
  credit_event_count: credits.length,
3606
- credit_units_total: credits.reduce((sum, entry) => sum + (entry.units || 0), 0),
3702
+ credit_mint_count: railCreditLifecycle.mint_count,
3703
+ credit_reverse_count: railCreditLifecycle.reverse_count,
3704
+ credit_units_total: railCreditLifecycle.minted_units,
3705
+ reversed_credit_units_total: railCreditLifecycle.reversed_units,
3607
3706
  credit_spend_count: spends.length,
3608
3707
  entitlement_count: entitlements.length,
3609
3708
  low_balance_entitlement_count: replenishment.low_balance_count,
@@ -3811,6 +3910,7 @@ module.exports = {
3811
3910
  recordAllocationEvent,
3812
3911
  previewRailCredits,
3813
3912
  applyRailCredits,
3913
+ reverseRailCredits,
3814
3914
  buildCreditBalanceSummary,
3815
3915
  buildWalletSummary,
3816
3916
  buildWalletLifecycleSummary,
@@ -65,6 +65,24 @@ function buildDeliveryEvent(event, delivery, atIso, extra) {
65
65
  };
66
66
  }
67
67
 
68
+ function buildCallbackDeliveryState(callbackUrl, deliveredAtIso, options = {}) {
69
+ if (!callbackUrl) return null;
70
+ const opts = options || {};
71
+ const initialStatus = opts.initial_status || (deliveredAtIso ? "queued" : "pending_execution");
72
+ return {
73
+ status: initialStatus,
74
+ attempt_count: 0,
75
+ max_attempts: null,
76
+ last_attempt_at_iso: null,
77
+ next_attempt_at_iso: deliveredAtIso || null,
78
+ delivered_at_iso: null,
79
+ failed_at_iso: null,
80
+ last_error: null,
81
+ last_response_status: null,
82
+ signature_mode: "hmac_sha256"
83
+ };
84
+ }
85
+
68
86
  function completePendingExecution(state, transactionId) {
69
87
  if (!transactionId || !state.pendingExecutions.has(transactionId) || !state.transactions.has(transactionId)) {
70
88
  return state.transactions.get(transactionId) || null;
@@ -142,7 +160,16 @@ function completePendingExecution(state, transactionId) {
142
160
  buildDeliveryEvent("delivered", { ...delivery, status: "delivered", proof_ref: next.receipt.proof_ref }, completedAtIso, {
143
161
  attempt_number: (delivery.attempt_count || 0) + 1
144
162
  })
145
- ]
163
+ ],
164
+ callback_delivery: delivery.callback_url
165
+ ? {
166
+ ...(delivery.callback_delivery || buildCallbackDeliveryState(delivery.callback_url, completedAtIso)),
167
+ status: "queued",
168
+ next_attempt_at_iso: completedAtIso,
169
+ last_error: null,
170
+ failed_at_iso: null
171
+ }
172
+ : null
146
173
  };
147
174
  state.deliveries.set(pending.deliveryId, nextDelivery);
148
175
  }
@@ -161,7 +188,8 @@ function createCommandResult(state, payload, quote, options) {
161
188
  const deferredCompletion = payload && payload.defer_completion === true;
162
189
  const { transaction, receipt, command } = createCommandArtifacts(payload, quote, {
163
190
  deferredCompletion,
164
- paymentVerification: opts.paymentVerification || null
191
+ paymentVerification: opts.paymentVerification || null,
192
+ externalExecution: opts.externalExecution || null
165
193
  });
166
194
  const result = { transaction, receipt, command };
167
195
  state.transactions.set(transaction.transaction_id, result);
@@ -230,6 +258,13 @@ function createCommandResult(state, payload, quote, options) {
230
258
  delivered_at_iso: deferredCompletion ? null : receipt.created_at_iso,
231
259
  attempt_count: deferredCompletion ? 0 : 1,
232
260
  last_attempt_at_iso: deferredCompletion ? null : receipt.created_at_iso,
261
+ callback_delivery: buildCallbackDeliveryState(
262
+ transaction.callback_url,
263
+ deferredCompletion ? null : receipt.created_at_iso,
264
+ {
265
+ initial_status: deferredCompletion ? "pending_execution" : "queued"
266
+ }
267
+ ),
233
268
  history: [
234
269
  buildDeliveryEvent("created", {
235
270
  callback_url: transaction.callback_url,
@@ -263,6 +298,7 @@ function createCommandResult(state, payload, quote, options) {
263
298
  }
264
299
 
265
300
  module.exports = {
301
+ buildCallbackDeliveryState,
266
302
  completePendingExecution,
267
303
  createCommandResult,
268
304
  createDispute,
@@ -167,8 +167,10 @@ function buildOperatorShellSummary(input) {
167
167
  coordination_zone_count: Number(coordinationAdminView.zone_count || coordinationOperatorView.zone_count || 0),
168
168
  hosted_checkout_purchase_intent_count: Number(hostedCheckoutOperatorSummary.purchase_intent_count || 0),
169
169
  hosted_checkout_refund_review_required_count: Number(hostedCheckoutOperatorSummary.refund_review_required_count || 0),
170
- hosted_checkout_execution_dispatched_count: Number(hostedCheckoutOperatorSummary.execution_dispatched_count || 0)
171
- ,
170
+ hosted_checkout_execution_dispatched_count: Number(hostedCheckoutOperatorSummary.execution_dispatched_count || 0),
171
+ hosted_checkout_auto_compensated_count: Number(hostedCheckoutOperatorSummary.auto_compensated_count || 0),
172
+ hosted_checkout_compensation_review_required_count: Number(hostedCheckoutOperatorSummary.compensation_review_required_count || 0),
173
+ hosted_checkout_compensation_failed_count: Number(hostedCheckoutOperatorSummary.compensation_failed_count || 0),
172
174
  low_balance_entitlement_count: Number(entitlementReplenishmentSummary.low_balance_count || 0),
173
175
  depleted_entitlement_count: Number(entitlementReplenishmentSummary.depleted_count || 0),
174
176
  runtime_snapshot_enabled: runtimeDurability.enabled === true,
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ const { buildFetchOptionsForPurpose } = require("./network_transport");
4
+
5
+ function normalizeString(value, fallback = "") {
6
+ const raw = typeof value === "string" ? value.trim() : "";
7
+ return raw || fallback;
8
+ }
9
+
10
+ function ensureObject(value) {
11
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
12
+ }
13
+
14
+ function loadExecutionTargetRegistry() {
15
+ const raw = String(process.env.XYTARA_EXECUTION_TARGETS_JSON || "").trim();
16
+ if (!raw) return [];
17
+ let parsed = [];
18
+ try {
19
+ parsed = JSON.parse(raw);
20
+ } catch (_) {
21
+ return [];
22
+ }
23
+ if (!Array.isArray(parsed)) return [];
24
+ return parsed
25
+ .map((entry, index) => {
26
+ const item = ensureObject(entry);
27
+ const targetId = normalizeString(item.target_id, `execution_target_${index + 1}`);
28
+ const endpointUrl = normalizeString(item.endpoint_url);
29
+ if (!endpointUrl) return null;
30
+ return {
31
+ target_id: targetId,
32
+ endpoint_url: endpointUrl,
33
+ task_ref: normalizeString(item.task_ref) || null,
34
+ tool_name: normalizeString(item.tool_name) || null,
35
+ integration_id: normalizeString(item.integration_id) || null,
36
+ provider: normalizeString(item.provider, "external_execution"),
37
+ auth_token: normalizeString(item.auth_token) || null,
38
+ timeout_ms: Math.max(500, Number(item.timeout_ms || 10000) || 10000),
39
+ enabled: item.enabled !== false
40
+ };
41
+ })
42
+ .filter(Boolean);
43
+ }
44
+
45
+ function resolveExecutionTarget(payload, quote) {
46
+ const registry = loadExecutionTargetRegistry();
47
+ if (registry.length < 1) return null;
48
+ const body = ensureObject(payload && payload.body);
49
+ const taskRefs = Array.isArray(payload && payload.tasks) && payload.tasks.length > 0
50
+ ? payload.tasks.map((task) => normalizeString(task && task.task_ref)).filter(Boolean)
51
+ : [normalizeString(payload && payload.task_ref)].filter(Boolean);
52
+ const toolName = normalizeString(body.tool_name) || null;
53
+ const integrationIds = new Set(
54
+ []
55
+ .concat(normalizeString(payload && payload.integration_id) ? [normalizeString(payload && payload.integration_id)] : [])
56
+ .concat(Array.isArray(payload && payload.integration_ids) ? payload.integration_ids.map((value) => normalizeString(value)).filter(Boolean) : [])
57
+ .concat(Array.isArray(quote && quote.integration_ids) ? quote.integration_ids.map((value) => normalizeString(value)).filter(Boolean) : [])
58
+ );
59
+
60
+ return registry.find((target) => {
61
+ if (!target.enabled) return false;
62
+ if (target.task_ref && !taskRefs.includes(target.task_ref)) return false;
63
+ if (target.tool_name && target.tool_name !== toolName) return false;
64
+ if (target.integration_id && !integrationIds.has(target.integration_id)) return false;
65
+ return true;
66
+ }) || null;
67
+ }
68
+
69
+ async function executeExternalTarget(target, payload, quote, authorizationContext) {
70
+ if (!target) return null;
71
+ const controller = new AbortController();
72
+ const timer = setTimeout(() => controller.abort(), target.timeout_ms);
73
+ const body = JSON.stringify({
74
+ command: payload && payload.command ? payload.command : "command.execute",
75
+ task_ref: payload && payload.task_ref ? payload.task_ref : null,
76
+ tasks: Array.isArray(payload && payload.tasks) ? payload.tasks : [],
77
+ body: ensureObject(payload && payload.body),
78
+ account_id: payload && payload.account_id ? payload.account_id : null,
79
+ quote,
80
+ authorization_context: authorizationContext || null
81
+ });
82
+ try {
83
+ const response = await fetch(target.endpoint_url, {
84
+ method: "POST",
85
+ headers: {
86
+ "content-type": "application/json",
87
+ ...(target.auth_token ? { authorization: `Bearer ${target.auth_token}` } : {})
88
+ },
89
+ body,
90
+ signal: controller.signal,
91
+ ...buildFetchOptionsForPurpose("external_execution")
92
+ });
93
+ const text = await response.text();
94
+ let parsed = null;
95
+ try {
96
+ parsed = text ? JSON.parse(text) : null;
97
+ } catch (_) {
98
+ parsed = null;
99
+ }
100
+ if (!response.ok) {
101
+ return {
102
+ ok: false,
103
+ status: 502,
104
+ reason: "external_execution_target_rejected",
105
+ details: parsed || text || null,
106
+ provider: target.provider
107
+ };
108
+ }
109
+ return {
110
+ ok: true,
111
+ provider: target.provider,
112
+ target_id: target.target_id,
113
+ target_response: parsed || null
114
+ };
115
+ } catch (error) {
116
+ return {
117
+ ok: false,
118
+ status: error && error.name === "AbortError" ? 504 : 502,
119
+ reason: error && error.name === "AbortError" ? "external_execution_timeout" : "external_execution_failed",
120
+ details: error && error.message ? error.message : "external execution failed",
121
+ provider: target.provider
122
+ };
123
+ } finally {
124
+ clearTimeout(timer);
125
+ }
126
+ }
127
+
128
+ module.exports = {
129
+ executeExternalTarget,
130
+ loadExecutionTargetRegistry,
131
+ resolveExecutionTarget
132
+ };