@weaccelerateinc/agent-sdk 0.0.0-stage → 0.10.3

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 (52) hide show
  1. package/README.md +149 -2
  2. package/approved-extension-releases/0.0.0.1.json +39 -0
  3. package/approved-extension-releases/0.0.0.2.json +32 -0
  4. package/approved-extension.d.ts +24 -0
  5. package/approved-extension.js +90 -0
  6. package/browser-connector.d.ts +87 -0
  7. package/browser-connector.js +179 -0
  8. package/browser-operations.d.ts +56 -0
  9. package/browser-operations.js +144 -0
  10. package/browser-use-host.d.ts +43 -0
  11. package/browser-use-host.js +147 -0
  12. package/checkout-diagnostics.d.ts +23 -0
  13. package/checkout-diagnostics.js +67 -0
  14. package/cli.js +102 -0
  15. package/customer.env.example +6 -0
  16. package/host-adapter.d.ts +90 -0
  17. package/host-adapter.js +353 -0
  18. package/index.d.ts +209 -0
  19. package/index.js +441 -0
  20. package/linq-messaging.d.ts +45 -0
  21. package/linq-messaging.js +123 -0
  22. package/managed-mcp.d.ts +150 -0
  23. package/managed-mcp.js +382 -0
  24. package/package.json +114 -4
  25. package/partner-starter/.env.example +11 -0
  26. package/partner-starter/README.md +48 -0
  27. package/partner-starter/gitignore.template +5 -0
  28. package/partner-starter/package.json +21 -0
  29. package/partner-starter/runtime.mjs +32 -0
  30. package/partner-starter/server.mjs +13 -0
  31. package/partner-starter/smoke.mjs +56 -0
  32. package/partner-starter/synthetic.mjs +226 -0
  33. package/partner-starter/tests/starter.test.mjs +69 -0
  34. package/partner-starter/trusted-context.example.mjs +20 -0
  35. package/partner.d.ts +27 -0
  36. package/partner.js +49 -0
  37. package/postgres-host.d.ts +14 -0
  38. package/postgres-host.js +102 -0
  39. package/private-chat.d.ts +104 -0
  40. package/private-chat.js +309 -0
  41. package/private-ingress.d.ts +46 -0
  42. package/private-ingress.js +132 -0
  43. package/starter/.env.example +19 -0
  44. package/starter/README.md +88 -0
  45. package/starter/gitignore.template +5 -0
  46. package/starter/package.json +21 -0
  47. package/starter/runtime.mjs +61 -0
  48. package/starter/server.mjs +13 -0
  49. package/starter/smoke.mjs +55 -0
  50. package/starter/synthetic.mjs +163 -0
  51. package/starter/tests/starter.test.mjs +256 -0
  52. package/starter/trusted-context.example.mjs +49 -0
@@ -0,0 +1,309 @@
1
+ import { AccelerateClient, AccelerateError } from './index.js';
2
+ import { createHash } from 'node:crypto';
3
+
4
+ const idPattern = /^mcs_[A-Za-z0-9_-]{43}$/;
5
+ const identityPattern = /^[A-Za-z0-9_.:-]{1,500}$/;
6
+ // Native checkout reviews retain full lossless PNGs, including product photos.
7
+ // This limit applies only to the private review image, not JSON responses.
8
+ const maximumReviewImageBytes = 8 * 1024 * 1024;
9
+ const stages = new Set(['not_started', 'identity_required', 'otp_required', 'selection_required', 'preparing_checkout', 'approval_required', 'cvv_required', 'verified', 'review_delivery_required', 'review_delivered', 'approved_for_submit', 'closed']);
10
+ const statuses = new Set(['wallet_required', 'selection_ready', 'details_ready', 'approval_required', 'approved', 'ready_for_submit', 'cancelled', 'expired', 'failed', 'submit_attempted', 'outcome_unknown']);
11
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value));
12
+ const check = (value, code = 'invalid_input') => { if (!value) throw new AccelerateError(code); };
13
+ function keys(value, allowed, required = allowed, code = 'invalid_input') {
14
+ check(object(value) && Object.keys(value).every(key => allowed.includes(key)) && required.every(key => Object.hasOwn(value, key)), code);
15
+ }
16
+ function safe(value, max) {
17
+ check(typeof value === 'string' && value.length <= max && !/[\x00-\x1f\x7f]|(?:\d[ -]?){13,19}|\b(?:0[1-9]|1[0-2])\s*\/\s*(?:20)?\d{2}\b|aw[12]\.|(?:tok_|vgs_|Bearer\s|(?:wss?|devtools|chrome-extension):\/\/)|\b(?:session_token|handoff_token|submit_token|api_key|cvv|cvc|otp)\b/i.test(value), 'invalid_response');
18
+ }
19
+ function integer(value, min = 1) { check(Number.isSafeInteger(value) && value >= min && value <= 2147483647, 'invalid_response'); }
20
+ function date(value) { check(typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/.test(value) && Number.isFinite(Date.parse(value)), 'invalid_response'); }
21
+ function card(value) {
22
+ keys(value, ['last4', 'brand'], undefined, 'invalid_response');
23
+ check(value.last4 === null || typeof value.last4 === 'string' && /^\d{4}$/.test(value.last4), 'invalid_response');
24
+ if (value.brand !== null) safe(value.brand, 40);
25
+ }
26
+ function address(value) {
27
+ keys(value, ['label', 'city', 'state', 'postal_code_mask'], ['label'], 'invalid_response');
28
+ for (const [key, max] of [['label', 400], ['city', 128], ['state', 64]]) if (value[key] != null) safe(value[key], max);
29
+ check(value.postal_code_mask == null || typeof value.postal_code_mask === 'string' && /^\*{3}[A-Za-z0-9 -]{1,2}$/.test(value.postal_code_mask), 'invalid_response');
30
+ }
31
+ function quote(value, revision = false, afterFill = false) {
32
+ keys(value, ['checkout_origin', 'amount_cents', 'currency', ...(revision ? ['revision', ...(!afterFill ? ['approval_code'] : [])] : [])], undefined, 'invalid_response');
33
+ let parsed; try { parsed = new URL(value.checkout_origin); } catch { check(false, 'invalid_response'); }
34
+ check(parsed.protocol === 'https:' && parsed.origin === value.checkout_origin && !parsed.username && !parsed.password && !parsed.port, 'invalid_response');
35
+ integer(value.amount_cents); check(value.currency === 'USD', 'invalid_response');
36
+ if (revision) { integer(value.revision); if (!afterFill) check(typeof value.approval_code === 'string' && /^[a-f0-9]{12}$/.test(value.approval_code), 'invalid_response'); }
37
+ }
38
+ function state(value, expectedId) {
39
+ keys(value, ['checkout', 'stage', 'handled', 'duplicate', 'cards', 'addresses', 'identity_field', 'approval', 'selection_error', 'review'], ['checkout', 'stage', 'handled', 'duplicate', 'cards', 'addresses'], 'invalid_response');
40
+ keys(value.checkout, ['id', 'status', 'revision', 'expires_at', 'card', 'address', 'quote', 'submit_expires_at', 'purpose', 'submit_allowed', 'approval_policy', 'purchase_approved', 'fill_after_verification', 'retry_safe', 'payment_confirmed'],
41
+ ['id', 'status', 'revision', 'expires_at', 'retry_safe', 'payment_confirmed'], 'invalid_response');
42
+ const checkout = value.checkout;
43
+ if (checkout.purpose !== undefined || checkout.submit_allowed !== undefined) check(
44
+ ['purchase', 'sandbox_source_comparison', 'sandbox_fill_only'].includes(checkout.purpose) &&
45
+ checkout.submit_allowed === (checkout.purpose === 'purchase'), 'invalid_response');
46
+ check(checkout.id === expectedId && statuses.has(checkout.status) && checkout.retry_safe === false && checkout.payment_confirmed === false, 'invalid_response');
47
+ if (checkout.approval_policy !== undefined || checkout.purchase_approved !== undefined) check(checkout.approval_policy === 'after_fill' && typeof checkout.purchase_approved === 'boolean' && (!checkout.purchase_approved || checkout.status === 'ready_for_submit' && checkout.purpose === 'purchase'), 'invalid_response');
48
+ if (checkout.fill_after_verification !== undefined) check(checkout.fill_after_verification === true && checkout.approval_policy === 'after_fill' &&
49
+ ['purchase', 'sandbox_fill_only'].includes(checkout.purpose), 'invalid_response');
50
+ integer(checkout.revision, 0); date(checkout.expires_at); if (checkout.submit_expires_at !== undefined) date(checkout.submit_expires_at);
51
+ if (checkout.card !== undefined) card(checkout.card); if (checkout.address !== undefined) address(checkout.address); if (checkout.quote !== undefined) quote(checkout.quote);
52
+ check(stages.has(value.stage) && typeof value.handled === 'boolean' && typeof value.duplicate === 'boolean' &&
53
+ Array.isArray(value.cards) && value.cards.length <= 100 && Array.isArray(value.addresses) && value.addresses.length <= 100, 'invalid_response');
54
+ for (const [index, choice] of value.cards.entries()) { keys(choice, ['choice', 'last4', 'brand'], undefined, 'invalid_response');
55
+ check(choice.choice === index + 1, 'invalid_response'); card({ last4: choice.last4, brand: choice.brand }); }
56
+ for (const [index, choice] of value.addresses.entries()) { keys(choice, ['choice', 'label', 'city', 'state', 'postal_code_mask'], ['choice', 'label'], 'invalid_response');
57
+ check(choice.choice === index + 1, 'invalid_response'); const { choice: _, ...mask } = choice; address(mask); }
58
+ check(value.stage === 'selection_required' || value.cards.length === 0 && value.addresses.length === 0, 'invalid_response');
59
+ check(value.selection_error === undefined || value.stage === 'selection_required' &&
60
+ ['invalid_choice', 'address_required'].includes(value.selection_error), 'invalid_response');
61
+ check(value.stage === 'identity_required' ? ['first_name', 'last_name', 'email'].includes(value.identity_field) : value.identity_field === undefined, 'invalid_response');
62
+ if (value.approval !== undefined) { quote(value.approval, true, checkout.approval_policy === 'after_fill'); check(value.approval.revision === checkout.revision &&
63
+ ['approval_required', 'cvv_required', 'review_delivery_required', 'review_delivered', 'approved_for_submit'].includes(value.stage) &&
64
+ checkout.quote?.checkout_origin === value.approval.checkout_origin && checkout.quote.amount_cents === value.approval.amount_cents &&
65
+ checkout.quote.currency === value.approval.currency, 'invalid_response'); }
66
+ check(!['approval_required', 'cvv_required', 'review_delivery_required', 'review_delivered', 'approved_for_submit'].includes(value.stage) || value.approval !== undefined, 'invalid_response');
67
+ if (value.review !== undefined) { keys(value.review, ['id', 'expires_at', 'delivered', 'approval_window_ms', 'delivery_expires_at'], ['id', 'expires_at', 'delivered'], 'invalid_response');
68
+ check(checkout.approval_policy === 'after_fill' && checkout.status === 'ready_for_submit' && /^rvr_[A-Za-z0-9_-]{43}$/.test(value.review.id) && typeof value.review.delivered === 'boolean', 'invalid_response'); date(value.review.expires_at);
69
+ check(value.review.expires_at === checkout.submit_expires_at && (value.stage === 'review_delivery_required' ? !value.review.delivered : value.review.delivered), 'invalid_response'); }
70
+ if (value.review?.approval_window_ms !== undefined || value.review?.delivery_expires_at !== undefined) {
71
+ check(value.review.approval_window_ms === 300000, 'invalid_response'); date(value.review.delivery_expires_at);
72
+ check(Date.parse(value.review.delivery_expires_at) < Date.parse(value.review.expires_at) &&
73
+ (value.review.delivered || Date.parse(value.review.expires_at) - Date.parse(value.review.delivery_expires_at) >= 300000), 'invalid_response');
74
+ }
75
+ if (['review_delivery_required', 'review_delivered', 'approved_for_submit'].includes(value.stage)) check(value.review !== undefined, 'invalid_response');
76
+ if (checkout.approval_policy === 'after_fill') {
77
+ if (value.stage === 'review_delivery_required') check(checkout.purchase_approved === false, 'invalid_response');
78
+ if (value.stage === 'cvv_required') check(checkout.status === 'approval_required' && value.review === undefined, 'invalid_response');
79
+ if (value.stage === 'verified') check(checkout.status === 'approved', 'invalid_response');
80
+ if (value.stage === 'approval_required') check(checkout.status === 'ready_for_submit' && checkout.purpose === 'purchase' && value.review?.delivered === true && checkout.purchase_approved === false, 'invalid_response');
81
+ if (value.stage === 'approved_for_submit') check(checkout.purchase_approved === true && checkout.purpose === 'purchase', 'invalid_response');
82
+ if (value.stage === 'review_delivered') check(checkout.purpose === 'sandbox_fill_only' && checkout.purchase_approved === false, 'invalid_response');
83
+ }
84
+ return value;
85
+ }
86
+ function binding(input, reply = false, delivered = false) {
87
+ keys(input, ['customer_id', 'workspace_id', 'conversation_id', ...(reply ? ['message_id', 'text', 'sent_at', 'reply_to_message_id'] : delivered ? ['review_id', 'message_id', 'sent_at'] : [])], ['customer_id', 'workspace_id', 'conversation_id', ...(reply ? ['message_id', 'text'] : delivered ? ['review_id', 'message_id', 'sent_at'] : [])]);
88
+ for (const key of ['customer_id', 'workspace_id', 'conversation_id']) check(typeof input[key] === 'string' && identityPattern.test(input[key]));
89
+ if (reply) check(typeof input.message_id === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(input.message_id) &&
90
+ typeof input.text === 'string' && input.text.length > 0 && input.text.length <= 2000 && !/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(input.text));
91
+ if (input.sent_at !== undefined) check(typeof input.sent_at === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/.test(input.sent_at) && Number.isFinite(Date.parse(input.sent_at)));
92
+ if (input.reply_to_message_id !== undefined) check(typeof input.reply_to_message_id === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(input.reply_to_message_id));
93
+ if (delivered) check(typeof input.review_id === 'string' && /^rvr_[A-Za-z0-9_-]{43}$/.test(input.review_id) && typeof input.message_id === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(input.message_id));
94
+ }
95
+
96
+ function presentation(value, options) {
97
+ keys(options, ['messageId']);
98
+ check(typeof options.messageId === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(options.messageId));
99
+ check(object(value) && object(value.checkout) && typeof value.checkout.id === 'string' && idPattern.test(value.checkout.id), 'invalid_response');
100
+ return state(value, value.checkout.id);
101
+ }
102
+ function deliveryKey(kind, value, messageId, text) {
103
+ return `accelerate-${kind}:` + createHash('sha256').update(JSON.stringify([
104
+ value.checkout.id, value.stage, value.checkout.revision, value.identity_field ?? null,
105
+ value.selection_error ?? null, messageId, text,
106
+ ])).digest('hex');
107
+ }
108
+
109
+ /** Render only validated masked state. This does not authenticate a webhook or authorize a checkout. */
110
+ export function renderPrivateChatState(value, options) {
111
+ const response = presentation(value, options);
112
+ if (!response.handled) return undefined;
113
+ const fillOnly = response.checkout.purpose === 'sandbox_fill_only', afterFill = response.checkout.approval_policy === 'after_fill';
114
+ let text;
115
+ switch (response.stage) {
116
+ case 'not_started':
117
+ text = 'Ask the assistant to resume your existing Accelerate checkout.'; break;
118
+ case 'identity_required':
119
+ text = `Accelerate needs your ${response.identity_field === 'first_name' ? 'first name' : response.identity_field === 'last_name' ? 'last name' : 'email address'} to connect your wallet. Reply here privately, or reply cancel to stop.`; break;
120
+ case 'otp_required':
121
+ text = 'Accelerate sent a wallet verification code to your phone. Reply here with that code. Accelerate handles this reply privately and keeps it out of assistant prompts, history and memory. Reply cancel to stop.'; break;
122
+ case 'selection_required': {
123
+ const card = response.cards[0], address = response.addresses[0], multiple = response.addresses.length > 1;
124
+ if (!card) { text = 'No selectable saved card is available in your Accelerate wallet. Reply cancel to stop.'; break; }
125
+ const addressChoices = response.addresses.map(choice => `${multiple ? `Address ${choice.choice}` : 'Saved address'}: ${choice.label ?? 'Saved address'}${choice.city ? `, ${choice.city}` : ''}${choice.state ? `, ${choice.state}` : ''}${choice.postal_code_mask ? ` ${choice.postal_code_mask}` : ''}`);
126
+ const instructions = multiple && address
127
+ ? `Reply wallet CARD_NUMBER ADDRESS_NUMBER, for example wallet ${card.choice} ${address.choice}. Both numbers are required because you have multiple saved addresses.`
128
+ : `Reply Card ${card.choice} or Wallet ${card.choice}, using the number of your chosen card.${address ? ' Your saved address will be used automatically.' : ' No saved address is available.'}`;
129
+ const addressRequired = response.selection_error === 'address_required' && multiple;
130
+ text = [
131
+ response.selection_error
132
+ ? addressRequired ? 'Choose which saved address to use with your card. Include both choice numbers.' : 'That card or address choice is unavailable. Choose from the previous list.'
133
+ : multiple ? 'Choose your Accelerate wallet details:' : 'Choose your Accelerate wallet card:',
134
+ ...(!response.selection_error ? response.cards.map(choice => `Card ${choice.choice}: ${choice.brand ?? 'Card'} •••• ${choice.last4 ?? 'unavailable'}`) : []),
135
+ ...(!response.selection_error || addressRequired ? addressChoices : []), instructions,
136
+ 'Reply cancel to stop. Do not send card numbers or expiration dates.',
137
+ ].join('\n'); break;
138
+ }
139
+ case 'preparing_checkout':
140
+ if (afterFill) { text = 'Accelerate wallet details selected. The selected card will be filled privately after security-code verification. No purchase will be made until you review the filled checkout and approve the final amount.' + (fillOnly ? ' This card-fill test cannot submit an order.' : ''); break; }
141
+ text = fillOnly
142
+ ? 'Accelerate wallet details selected. You will approve the exact merchant and total here before private card filling. This card-fill test cannot submit an order.'
143
+ : 'Accelerate wallet details selected. The assistant will continue this same checkout. You will approve its exact merchant and total here before payment.'; break;
144
+ case 'review_delivery_required':
145
+ case 'approval_required': {
146
+ const quote = response.approval;
147
+ if (afterFill) { text = `Your checkout is filled. Review the attached masked payment form.\nMerchant: ${new URL(quote.checkout_origin).hostname}\nFinal total: ${(quote.amount_cents / 100).toFixed(2)} ${quote.currency}\nCard: ${response.checkout.card?.brand ?? 'Card'} •••• ${response.checkout.card?.last4 ?? 'selected'}\n` + (fillOnly ? 'This card-fill test cannot submit an order. No purchase approval is needed; the checkout will be closed after this review.' : 'Do you approve this purchase? Reply approve or yes to approve, or decline or no to cancel. ' +
148
+ (response.review.approval_window_ms === 300000 && !response.review.delivered ? 'You have 5 minutes from this message to decide; ' : 'Approval expires at ' + new Date(response.review.expires_at).toISOString().slice(0, 19).replace('T', ' ') + ' UTC; ') + 'no payment has been submitted.'); break; }
149
+ text = `${fillOnly ? 'Approve this Accelerate card-fill test?' : 'Approve this Accelerate purchase?'}\nMerchant: ${new URL(quote.checkout_origin).hostname}\nTotal: ${(quote.amount_cents / 100).toFixed(2)} ${quote.currency}\nCard: ${response.checkout.card?.brand ?? 'Card'} •••• ${response.checkout.card?.last4 ?? 'selected'}\nAddress: ${response.checkout.address?.label ?? 'Selected address'}\nReply approve ${quote.revision} ${quote.approval_code} to authorize ${fillOnly ? 'private filling only. Order submission is disabled' : 'this exact checkout'}, or cancel to stop.`; break;
150
+ }
151
+ case 'cvv_required':
152
+ text = (afterFill ? 'We will fill your selected card privately so you can review the checkout before any purchase approval. ' : '') + `Reply here with the 3–4 digit security code for ${response.checkout.card?.brand ?? 'your selected card'} •••• ${response.checkout.card?.last4 ?? ''}. Accelerate handles this reply privately and keeps it out of assistant prompts, history and memory. Reply cancel to stop.`; break;
153
+ case 'review_delivered':
154
+ text = 'Your card-fill test is complete. The attached form shows masked card details and the final amount. Order submission is disabled; this checkout will be closed.'; break;
155
+ case 'approved_for_submit':
156
+ text = 'You approved this exact filled checkout and final total. The assistant can now request the final purchase click. No payment has been submitted yet.'; break;
157
+ case 'verified':
158
+ if (afterFill) { text = 'Your card was verified privately. Accelerate will fill it, then send a masked checkout image and final amount for your review. No purchase has been approved.'; break; }
159
+ text = fillOnly
160
+ ? 'Accelerate verified this card-fill test privately. The assistant will stop after filling; order submission is disabled. Your security code was withheld from assistant prompts, history and memory.'
161
+ : 'Accelerate verified your approved checkout privately. The assistant will continue this same checkout. Your security code was withheld from assistant prompts, history and memory.'; break;
162
+ case 'closed':
163
+ text = fillOnly ? `Your Accelerate card-fill test is ${response.checkout.status}. Order submission is disabled. Ask the assistant for its safe status; do not resend a security code.` : `Your Accelerate checkout is ${response.checkout.status}. A submit attempt does not confirm a purchase. Ask the assistant for the safe status; do not resend a security code.`; break;
164
+ }
165
+ return Object.freeze({ text, idempotency_key: deliveryKey('chat', response, options.messageId, text) });
166
+ }
167
+
168
+ /** Safe progress only. The caller must persist stage transitions and deduplicate dispatch before resuming its agent. */
169
+ export function privateChatProgress(value, options) {
170
+ const response = presentation(value, options);
171
+ if (!response.handled) return undefined;
172
+ const messages = {
173
+ preparing_checkout: '[Accelerate private progress] Wallet selection is complete. Check the existing Accelerate checkout status and continue preparing that same checkout.',
174
+ approved_for_submit: '[Accelerate private progress] The customer approved the current filled checkout and final amount. Read its safe status; submit only that checkout once if submission is enabled. Do not repeat private card filling.',
175
+ verified: '[Accelerate private progress] Checkout approval and private verification are complete. Check the existing Accelerate checkout status and continue only that approved checkout. Do not request or repeat security codes.',
176
+ closed: '[Accelerate private progress] The checkout closed. Check its safe status, respect cancellation and reconcile any uncertain payment. Do not repeat a purchase.',
177
+ };
178
+ if (!Object.hasOwn(messages, response.stage)) return undefined;
179
+ const text = (response.stage === 'verified' && response.checkout.approval_policy === 'after_fill' ? response.checkout.fill_after_verification === true ? '[Accelerate private progress] Private card verification is complete. Accelerate continues the staged fill automatically and delivers the masked review privately. Read only this checkout’s safe status; do not call fill, request another security code or submit.' : '[Accelerate private progress] Private card verification is complete. Fill the existing checkout once, then send the safe filled review privately and wait for final customer approval. Do not submit yet.' : messages[response.stage]) + (response.checkout.purpose === 'sandbox_fill_only' ? ' This is a card-fill test; order submission is disabled.' : '');
180
+ return Object.freeze({ stage: response.stage, text, idempotency_key: deliveryKey('progress', response, options.messageId, text) });
181
+ }
182
+
183
+ /** Provider-neutral private delivery. The host authenticates the destination and
184
+ * the send receipt; this helper never receives provider credentials or a public
185
+ * media URL. Only send the supplied image and prompt together in one message. */
186
+ export async function deliverPrivateCheckoutReview(options) {
187
+ keys(options, ['client', 'state', 'binding', 'messageId', 'send', 'signal'], ['client', 'state', 'binding', 'messageId', 'send']);
188
+ check(options.client instanceof AcceleratePrivateChatClient && typeof options.send === 'function' &&
189
+ (options.signal === undefined || options.signal instanceof AbortSignal));
190
+ binding(options.binding);
191
+ const response = presentation(options.state, { messageId: options.messageId });
192
+ check(response.checkout.approval_policy === 'after_fill' && response.stage === 'review_delivery_required' && response.handled &&
193
+ response.review?.approval_window_ms === 300000 && response.review.delivery_expires_at && response.approval, 'invalid_response');
194
+ const deadline = Date.parse(response.review.delivery_expires_at);
195
+ check(deadline > Date.now(), 'checkout_expired');
196
+ const controller = new AbortController(), timer = setTimeout(() => controller.abort(), Math.max(1, Math.min(2147483647, deadline - Date.now()))); timer.unref?.();
197
+ const signal = AbortSignal.any([controller.signal, ...(options.signal ? [options.signal] : [])]);
198
+ let image;
199
+ const bounded = pending => new Promise((resolve, reject) => {
200
+ const abort = () => { signal.removeEventListener('abort', abort); reject(new AccelerateError('request_cancelled')); };
201
+ signal.addEventListener('abort', abort, { once: true }); if (signal.aborted) abort();
202
+ Promise.resolve(pending).then(value => { signal.removeEventListener('abort', abort);
203
+ if (signal.aborted) reject(new AccelerateError('request_cancelled')); else resolve(value); },
204
+ () => { signal.removeEventListener('abort', abort); reject(new AccelerateError('request_failed')); });
205
+ });
206
+ try {
207
+ signal.throwIfAborted();
208
+ image = await options.client.chat.review(response.checkout.id, options.binding, { signal });
209
+ check(image.review_id === response.review.id && image.expires_at === response.review.expires_at &&
210
+ image.approval_window_ms === response.review.approval_window_ms && image.delivery_expires_at === response.review.delivery_expires_at &&
211
+ image.amount_cents === response.approval.amount_cents && image.currency === response.approval.currency, 'invalid_response');
212
+ const prompt = renderPrivateChatState(response, { messageId: options.messageId });
213
+ signal.throwIfAborted(); check(Date.now() < deadline, 'checkout_expired');
214
+ const receipt = await bounded(options.send({ bytes: image.bytes, contentType: 'image/png', text: prompt.text,
215
+ idempotency_key: prompt.idempotency_key, delivery_expires_at: new Date(deadline).toISOString(), signal }));
216
+ keys(receipt, ['message_id', 'sent_at'], undefined, 'invalid_response');
217
+ check(typeof receipt.message_id === 'string' && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(receipt.message_id), 'invalid_response'); date(receipt.sent_at);
218
+ const sentAt = Date.parse(receipt.sent_at), approvalExpiresAt = sentAt + 300000;
219
+ check(sentAt <= Date.now() && sentAt < deadline && approvalExpiresAt <= Date.parse(image.expires_at), 'invalid_response');
220
+ const delivered = await options.client.chat.reviewDelivered(response.checkout.id, { ...options.binding, review_id: image.review_id,
221
+ message_id: receipt.message_id, sent_at: receipt.sent_at }, { signal });
222
+ check(delivered.checkout.id === response.checkout.id && delivered.checkout.approval_policy === 'after_fill' && delivered.checkout.revision === response.checkout.revision &&
223
+ delivered.checkout.purpose === response.checkout.purpose && delivered.checkout.purchase_approved === false &&
224
+ delivered.stage === (response.checkout.purpose === 'sandbox_fill_only' ? 'review_delivered' : 'approval_required') &&
225
+ delivered.review?.id === response.review.id && delivered.review.delivered === true &&
226
+ delivered.review.approval_window_ms === 300000 && delivered.review.delivery_expires_at === response.review.delivery_expires_at &&
227
+ Date.parse(delivered.review.expires_at) === approvalExpiresAt && approvalExpiresAt > Date.now() &&
228
+ delivered.approval?.revision === response.approval.revision &&
229
+ delivered.approval.checkout_origin === response.approval.checkout_origin &&
230
+ delivered.approval.amount_cents === response.approval.amount_cents && delivered.approval.currency === response.approval.currency,
231
+ 'invalid_response');
232
+ return delivered;
233
+ } catch (error) {
234
+ if (signal.aborted) throw new AccelerateError(options.signal?.aborted ? 'request_cancelled' : 'request_timeout');
235
+ throw error instanceof AccelerateError ? error : new AccelerateError('request_failed');
236
+ } finally { if (Buffer.isBuffer(image?.bytes)) image.bytes.fill(0); clearTimeout(timer); controller.abort(); }
237
+ }
238
+
239
+ /** Private webhook-ingress client. Never expose these methods as agent tools or log their inputs. */
240
+ export class AcceleratePrivateChatClient {
241
+ #origin; #apiKey; #merchantId; #fetch;
242
+ static fromEnv(env = process.env) { return AccelerateClient.fromEnv.call(this, env); }
243
+ constructor(options = {}) {
244
+ // Keep exactly the normal SDK's server-only runtime and credential checks.
245
+ new AccelerateClient(options);
246
+ this.#origin = new URL(options.baseUrl).origin; this.#apiKey = options.apiKey;
247
+ this.#merchantId = options.merchantId.toLowerCase(); this.#fetch = options.fetchImpl ?? globalThis.fetch;
248
+ this.chat = Object.freeze({
249
+ start: async (id, input, options) => this.#request(id, 'start', input, options),
250
+ get: async (id, input, options) => this.#request(id, 'state', input, options),
251
+ reply: async (id, input, options) => { try { return await this.#request(id, 'reply', input, options); }
252
+ finally { if (object(input) && Object.hasOwn(input, 'text')) input.text = ''; } },
253
+ cancel: async (id, input, options) => this.#request(id, 'cancel', input, options),
254
+ review: async (id, input, options) => this.#request(id, 'review', input, options),
255
+ reviewDelivered: async (id, input, options) => this.#request(id, 'review-delivered', input, options),
256
+ });
257
+ }
258
+ async #request(id, action, input, options = {}) {
259
+ check(typeof id === 'string' && idPattern.test(id)); binding(input, action === 'reply', action === 'review-delivered');
260
+ keys(options, ['signal'], []); check(options.signal === undefined || options.signal instanceof AbortSignal);
261
+ const controller = new AbortController(), timer = setTimeout(() => controller.abort(), action === 'reply' ? 215000 : action === 'start' ? 125000 : 45000); timer.unref?.();
262
+ const signal = AbortSignal.any([controller.signal, ...(options.signal ? [options.signal] : [])]);
263
+ const url = `${this.#origin}/v1/checkouts/${id}/private-chat/${action}`;
264
+ let response, reader, body = JSON.stringify(input), finished = false;
265
+ const bounded = pending => new Promise((resolve, reject) => {
266
+ const abort = () => { signal.removeEventListener('abort', abort); reject(new AccelerateError('request_cancelled')); };
267
+ signal.addEventListener('abort', abort, { once: true }); if (signal.aborted) abort();
268
+ Promise.resolve(pending).then(value => { signal.removeEventListener('abort', abort);
269
+ if (signal.aborted) { Promise.resolve(value?.body?.cancel()).catch(() => {}); reject(new AccelerateError('request_cancelled')); } else resolve(value); },
270
+ () => { signal.removeEventListener('abort', abort); reject(new AccelerateError('request_failed')); });
271
+ });
272
+ try {
273
+ signal.throwIfAborted();
274
+ response = await bounded(this.#fetch(url, { method: 'POST', redirect: 'error', cache: 'no-store', credentials: 'omit', referrerPolicy: 'no-referrer', signal,
275
+ headers: { Authorization: `Bearer ${this.#apiKey}`, 'X-Accelerate-Merchant-Id': this.#merchantId, Accept: 'application/json', 'Content-Type': 'application/json' }, body }));
276
+ body = '';
277
+ if (action === 'review' && response.ok) {
278
+ check(!response.redirected && response.status === 200 && (!response.url || response.url === url) && /^image\/png(?:\s*;|$)/i.test(response.headers.get('content-type') ?? ''), 'invalid_response');
279
+ const review_id = response.headers.get('x-accelerate-review-id'), expires_at = response.headers.get('x-accelerate-review-expires-at'), amount = response.headers.get('x-accelerate-review-amount-cents'), currency = response.headers.get('x-accelerate-review-currency');
280
+ check(/^rvr_[A-Za-z0-9_-]{43}$/.test(review_id ?? '') && /^[1-9]\d{0,9}$/.test(amount ?? '') && currency === 'USD', 'invalid_response'); date(expires_at); integer(Number(amount));
281
+ const approvalWindow = response.headers.get('x-accelerate-review-approval-window-ms'), deliveryExpiry = response.headers.get('x-accelerate-review-delivery-expires-at');
282
+ if (approvalWindow !== null || deliveryExpiry !== null) { check(approvalWindow === '300000', 'invalid_response'); date(deliveryExpiry);
283
+ check(Date.parse(deliveryExpiry) < Date.parse(expires_at), 'invalid_response'); }
284
+ const length = response.headers.get('content-length'); check(length === null || /^\d+$/.test(length) && Number(length) <= maximumReviewImageBytes, 'invalid_response');
285
+ reader = response.body?.getReader(); check(reader, 'invalid_response'); let size = 0, bytes; const chunks = [];
286
+ try { while (true) { const next = await bounded(reader.read()); signal.throwIfAborted(); if (next.done) { finished = true; break; } size += next.value.byteLength; check(size <= maximumReviewImageBytes, 'invalid_response'); chunks.push(Buffer.from(next.value)); }
287
+ bytes = Buffer.concat(chunks); check(bytes.length > 8 && bytes.subarray(0, 8).equals(Buffer.from([137,80,78,71,13,10,26,10])), 'invalid_response');
288
+ const image = { bytes, review_id, expires_at, ...(approvalWindow === '300000' ? { approval_window_ms: 300000, delivery_expires_at: deliveryExpiry } : {}),
289
+ amount_cents: Number(amount), currency }; bytes = undefined; return image;
290
+ } finally { bytes?.fill(0); for (const chunk of chunks) chunk.fill(0); }
291
+ }
292
+ check(!response.redirected && (response.status === 200 || response.status >= 400) && (!response.url || response.url === url) && /^application\/json(?:\s*;|$)/i.test(response.headers.get('content-type') ?? ''), 'invalid_response');
293
+ const limit = response.ok ? 65536 : 4096, length = response.headers.get('content-length');
294
+ check(length === null || /^\d+$/.test(length) && Number(length) <= limit, 'invalid_response');
295
+ reader = response.body?.getReader(); check(reader, 'invalid_response');
296
+ let size = 0, raw = ''; const decoder = new TextDecoder('utf-8', { fatal: true });
297
+ while (true) { const next = await bounded(reader.read()); signal.throwIfAborted();
298
+ if (next.done) { finished = true; break; } size += next.value.byteLength; check(size <= limit, 'invalid_response'); raw += decoder.decode(next.value, { stream: true }); }
299
+ const value = JSON.parse(raw + decoder.decode()); raw = '';
300
+ if (!response.ok) throw new AccelerateError(value?.error?.code, response.status);
301
+ return state(value, id);
302
+ } catch (error) {
303
+ if (signal.aborted) throw new AccelerateError(options.signal?.aborted ? 'request_cancelled' : 'request_timeout');
304
+ throw error instanceof AccelerateError ? error : new AccelerateError('request_failed');
305
+ } finally { body = ''; clearTimeout(timer); controller.abort();
306
+ if (!finished) try { Promise.resolve(reader?.cancel() ?? response?.body?.cancel()).catch(() => {}); } catch {}
307
+ try { reader?.releaseLock(); } catch {} }
308
+ }
309
+ }
@@ -0,0 +1,46 @@
1
+ import type { BrowserConnector } from './browser-connector.js';
2
+ import type { AccelerateClient } from './index.js';
3
+ import type { ManagedCheckoutContext } from './managed-mcp.js';
4
+ import type { DurableCheckoutHost, HostOwner } from './host-adapter.js';
5
+ import type { AcceleratePrivateChatClient, PrivateChatPrompt, PrivateChatProgress, PrivateReviewSend } from './private-chat.js';
6
+
7
+ export interface PrivateMessageReceipt { message_id: string; sent_at: string }
8
+ export interface PrivateMessagingAdapter {
9
+ /** Verify the established direct recipient against authenticated context on
10
+ * every send. Provider retries must deduplicate this exact idempotency key. */
11
+ sendText(context: Readonly<ManagedCheckoutContext>, message: PrivateChatPrompt,
12
+ options?: { signal?: AbortSignal }): Promise<PrivateMessageReceipt>;
13
+ /** Exact SDK-rendered PNG and total text in ONE private native message. Return
14
+ * only a provider-confirmed sent/delivered/read timestamp, never queue time. */
15
+ sendReview(context: Readonly<ManagedCheckoutContext>, message: PrivateReviewSend): Promise<PrivateMessageReceipt>;
16
+ }
17
+ export type AuthenticatedPrivateMessage = { kind?: 'message'; authenticated: true; direct: true;
18
+ context: ManagedCheckoutContext; message_id: string; text: string;
19
+ /** Omit for reconciled/historical events. Never fabricate from receipt time. */
20
+ sent_at?: string; reply_to_message_id?: string };
21
+ export interface PrivateCheckoutIngress<Request = unknown> {
22
+ /** Call BEFORE framework parsing/caching/tracing/queues. Always erases rawBody
23
+ * and transient parsed text. Ordinary traffic is admitted only through the
24
+ * callback as a new minimal event, never by passing the original envelope. */
25
+ handle(request: Request, rawBody: Uint8Array): Promise<Readonly<{ handled: boolean; retry?: true }>>;
26
+ /** Safely recover pending lifecycle/prompt/progress deliveries only. Never
27
+ * repeats fill or submit. Requires authenticated context from the host. */
28
+ drain(context: ManagedCheckoutContext): Promise<number>;
29
+ }
30
+ export function isCredentialLikePrivateReply(text: unknown): boolean;
31
+ export function createPrivateCheckoutIngress<Request = unknown>(options: {
32
+ authenticate(request: Request, rawBody: Uint8Array): Promise<AuthenticatedPrivateMessage | { kind: 'ignore' }>;
33
+ host: DurableCheckoutHost;
34
+ privateChat: AcceleratePrivateChatClient;
35
+ messaging: PrivateMessagingAdapter;
36
+ /** Ordinary text only, rebuilt from an allowlist after private interception. */
37
+ admitOrdinaryMessage(event: { binding: HostOwner; message_id: string; text: string }): Promise<void>;
38
+ /** Durably deduplicate the fixed safe event before resuming the model. Never
39
+ * append the original provider envelope, reply context or attachments. */
40
+ dispatchSafeProgress(progress: PrivateChatProgress & { binding: HostOwner; checkout_id: string }): Promise<void>;
41
+ /** Enables cleanup after durable fill-only review ACK; failure never retries
42
+ * filling. Purchase checkouts retain separate explicit agent submission. */
43
+ client?: AccelerateClient;
44
+ /** Rechecks readiness before OTP/CVV lifecycle delivery and independently confirms cleanup. */
45
+ browserConnector?: BrowserConnector;
46
+ }): Readonly<PrivateCheckoutIngress<Request>>;
@@ -0,0 +1,132 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { AccelerateError } from './index.js';
3
+ import { authenticatedCheckoutContext } from './host-adapter.js';
4
+ import { deliverPrivateCheckoutReview } from './private-chat.js';
5
+
6
+ const MESSAGE = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/;
7
+ const PENDING = new Set(['identity_required', 'otp_required', 'selection_required', 'cvv_required', 'approval_required', 'review_delivery_required']);
8
+ const TERMINAL = new Set(['cancelled', 'expired', 'failed', 'outcome_unknown', 'submit_attempted']);
9
+ const check = condition => { if (!condition) throw new AccelerateError('invalid_input'); };
10
+ function digest(value) { return createHash('sha256').update(value).digest('hex'); }
11
+ /** Defense in depth for unsolicited secrets; active private challenges consume
12
+ * EVERY reply, including prose that this heuristic cannot recognize. */
13
+ export function isCredentialLikePrivateReply(text) {
14
+ return typeof text === 'string' && (/^\s*\d{3,8}\s*$/.test(text) || /(?:\d[ -]?){13,19}|\b(?:0[1-9]|1[0-2])\s*\/\s*(?:20)?\d{2}\b|\b(?:cvv|cvc|otp|(?:verification|security)\s*code|expiry|expiration)\b.{0,40}\d|\b(?:card|wallet)\s+\d+(?:\s+\d+)?\b|#ticket=[A-Za-z0-9_-]{43}/i.test(text));
15
+ }
16
+ function clearBody(body) { if (body instanceof Uint8Array) body.fill(0); }
17
+ function clearReply(reply) { if (reply && Object.hasOwn(reply, 'text')) { try { reply.text = ''; } catch {} } }
18
+ function replyMetadata(verified) {
19
+ const metadata = {};
20
+ if (verified.sent_at !== undefined) {
21
+ check(typeof verified.sent_at === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(verified.sent_at) && Number.isFinite(Date.parse(verified.sent_at)));
22
+ metadata.sent_at = new Date(verified.sent_at).toISOString();
23
+ }
24
+ if (verified.reply_to_message_id !== undefined) { check(MESSAGE.test(verified.reply_to_message_id)); metadata.reply_to_message_id = verified.reply_to_message_id; }
25
+ return metadata;
26
+ }
27
+
28
+ /** Provider-neutral, pre-framework private ingress and SAFE outbox processor.
29
+ * No callbacks may log/cache raw request bodies. authenticate must verify the
30
+ * real request signature/forwarder and resolve an established direct recipient. */
31
+ export function createPrivateCheckoutIngress({ authenticate, host, privateChat, messaging, admitOrdinaryMessage,
32
+ dispatchSafeProgress, client, browserConnector } = {}) {
33
+ check([authenticate, host?.withPrivateCheckout, host?.drainOutbox, privateChat?.chat?.reply, privateChat?.chat?.get,
34
+ privateChat?.chat?.start, privateChat?.chat?.cancel, messaging?.sendText, messaging?.sendReview, admitOrdinaryMessage, dispatchSafeProgress].every(value => typeof value === 'function'));
35
+ if (browserConnector !== undefined) check(typeof browserConnector.assertPaymentReady === 'function' && typeof browserConnector.confirmCleanup === 'function');
36
+ if (client !== undefined) check(typeof client.checkouts?.get === 'function' && typeof client.checkouts?.cancel === 'function');
37
+ const completeFillOnly = async (context, state) => {
38
+ if (state.stage !== 'review_delivered' || state.checkout.purpose !== 'sandbox_fill_only' || !client) return;
39
+ // Review ACK has already committed. Cancellation is a distinct recovery-safe
40
+ // action. No failure may turn this into a new fill or a replacement checkout.
41
+ const result = await host.withOwnedCheckout(context, state.checkout.id, async () => ({ checkout: await client.checkouts.cancel(state.checkout.id) }), { operation: 'accelerate_checkout_cancel' });
42
+ check(result.checkout.diagnostics?.cleanup_status === 'confirmed');
43
+ if (browserConnector) {
44
+ await browserConnector.confirmCleanup(context, { checkout_id: state.checkout.id });
45
+ await host.confirmBrowserCleanup(context, state.checkout.id);
46
+ }
47
+ };
48
+ const saveAndDeliver = async (context, owned, response, messageId) => {
49
+ if (response.stage === 'review_delivery_required') {
50
+ await owned.savePrivateState(response, { messageId, suppressPrompt: true });
51
+ const delivered = await deliverPrivateCheckoutReview({ client: privateChat, state: response,
52
+ binding: { customer_id: context.customer_id, workspace_id: context.workspace_id, conversation_id: context.conversation_id },
53
+ messageId, send: message => host.withPrivateDelivery(context, message.idempotency_key, () => messaging.sendReview(context, message)) });
54
+ await owned.savePrivateState(delivered, { messageId, suppressPrompt: true });
55
+ await completeFillOnly(context, delivered);
56
+ return delivered;
57
+ }
58
+ await owned.savePrivateState(response, { messageId });
59
+ await completeFillOnly(context, response);
60
+ return response;
61
+ };
62
+ const drain = context => host.drainOutbox(context, async (item, trusted) => {
63
+ if (item.kind === 'private_prompt') {
64
+ await messaging.sendText(trusted, { text: item.payload.text, idempotency_key: item.id }); return;
65
+ }
66
+ if (item.kind === 'safe_progress') { await dispatchSafeProgress({ binding: {
67
+ customer_id: trusted.customer_id, workspace_id: trusted.workspace_id, conversation_id: trusted.conversation_id,
68
+ }, ...item.payload }); return; }
69
+ const event = item.payload;
70
+ if (browserConnector && ['created', 'payment_requested', 'approval_requested'].includes(event.kind)) await browserConnector.assertPaymentReady(trusted, { checkout_id: event.checkout.id });
71
+ await host.withPrivateCheckout(trusted, { operation: event.kind === 'payment_requested' || event.kind === 'created' ? 'private_start' : 'private_state' }, async owned => {
72
+ check(owned.checkout_id === event.checkout.id);
73
+ const binding = { customer_id: trusted.customer_id, workspace_id: trusted.workspace_id, conversation_id: trusted.conversation_id };
74
+ const response = await (['created', 'payment_requested'].includes(event.kind) ? privateChat.chat.start(owned.checkout_id, binding) : privateChat.chat.get(owned.checkout_id, binding));
75
+ await saveAndDeliver(trusted, owned, response, `event-${digest(item.id)}`);
76
+ });
77
+ });
78
+ return Object.freeze({ drain,
79
+ async handle(request, rawBody) {
80
+ let verified, context, messageId;
81
+ try {
82
+ check(rawBody instanceof Uint8Array);
83
+ verified = await authenticate(request, rawBody);
84
+ // Withhold the original envelope even for admitted ordinary messages.
85
+ // Only a new minimal event can cross the AI/history boundary below.
86
+ clearBody(rawBody);
87
+ if (verified?.kind === 'ignore') return Object.freeze({ handled: true });
88
+ check(verified?.authenticated === true && verified.direct === true);
89
+ context = authenticatedCheckoutContext(verified.context); messageId = verified.message_id;
90
+ check(typeof messageId === 'string' && MESSAGE.test(messageId) && typeof verified.text === 'string' && verified.text.length <= 2000 && !/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(verified.text));
91
+ const metadata = replyMetadata(verified), text = verified.text.trim(), credentialLike = isCredentialLikePrivateReply(text);
92
+ const current = await host.withPrivateCheckout(context, { operation: 'private_state' }, async owned => ({ id: owned.checkout_id, stage: owned.previous_stage, status: owned.checkout?.status }));
93
+ if (!current || !PENDING.has(current.stage) && (TERMINAL.has(current.status) || current.stage === 'not_started' || current.stage === undefined)) {
94
+ if (!credentialLike) {
95
+ await admitOrdinaryMessage({ binding: { customer_id: context.customer_id, workspace_id: context.workspace_id, conversation_id: context.conversation_id }, message_id: messageId, text: verified.text });
96
+ return Object.freeze({ handled: false });
97
+ }
98
+ return Object.freeze({ handled: true });
99
+ }
100
+ const isStatus = /^status$/i.test(text), isCancel = /^cancel$/i.test(text);
101
+ let serviceHandled = true;
102
+ await host.withPrivateCheckout(context, { operation: isStatus ? 'private_state' : isCancel ? 'private_cancel' : 'private_reply',
103
+ ...(!isStatus && !isCancel ? { message_id: messageId } : {}) }, async owned => {
104
+ const binding = { customer_id: context.customer_id, workspace_id: context.workspace_id, conversation_id: context.conversation_id };
105
+ let response;
106
+ if (isStatus) response = await privateChat.chat.get(owned.checkout_id, binding);
107
+ else if (isCancel) response = await privateChat.chat.cancel(owned.checkout_id, binding);
108
+ else {
109
+ const input = { ...binding, message_id: messageId, text: text || '[non-text reply]', ...metadata };
110
+ try { response = await privateChat.chat.reply(owned.checkout_id, input); } finally { input.text = ''; }
111
+ }
112
+ const consume = response.handled || credentialLike || PENDING.has(current.stage);
113
+ serviceHandled = consume;
114
+ await saveAndDeliver(context, owned, consume ? { ...response, handled: true } : response, messageId);
115
+ });
116
+ await drain(context);
117
+ if (!serviceHandled) await admitOrdinaryMessage({ binding: { customer_id: context.customer_id, workspace_id: context.workspace_id, conversation_id: context.conversation_id }, message_id: messageId, text: verified.text });
118
+ return Object.freeze({ handled: serviceHandled });
119
+ } catch {
120
+ // Neither raw provider errors nor sensitive input escape to framework
121
+ // logs, model tool results, tracing, history, memory or durable jobs.
122
+ if (context && messageId && MESSAGE.test(messageId)) {
123
+ try { await messaging.sendText(context, {
124
+ text: 'The private Accelerate step could not be confirmed. Your reply was withheld from the assistant. Reply status to check this existing checkout; do not restart or repeat a potentially completed purchase.',
125
+ idempotency_key: `accelerate-private-error:${digest(messageId)}`,
126
+ }); } catch {}
127
+ }
128
+ return Object.freeze({ handled: true, retry: true });
129
+ } finally { clearBody(rawBody); clearReply(verified); }
130
+ },
131
+ });
132
+ }
@@ -0,0 +1,19 @@
1
+ # Default: no credentials, network, wallet, browser or purchase.
2
+ ACCELERATE_STARTER_MODE=synthetic
3
+ # Supported managed_review_v1: verify at payment, fill, then private review.
4
+ # The default test never asks for purchase approval and cannot submit an order.
5
+ ACCELERATE_STARTER_PURPOSE=sandbox_fill_only
6
+ ACCELERATE_ENABLE_PURCHASE_SUBMIT=false
7
+
8
+ # Real sandbox mode requires your existing authenticated backend context module.
9
+ # ACCELERATE_STARTER_MODE=live
10
+ # ACCELERATE_CONTEXT_MODULE=/absolute/server/path/trusted-context.mjs
11
+ # ACCELERATE_API_KEY=
12
+ # ACCELERATE_MERCHANT_ID=
13
+ # ACCELERATE_BASE_URL=https://accelerate-agent-sdk.vercel.app
14
+
15
+ # Only after qualifying the merchant and testing your private provider ingress:
16
+ # ACCELERATE_STARTER_PURPOSE=purchase
17
+ # ACCELERATE_ENABLE_PURCHASE_SUBMIT=true
18
+ # This exposes a separate agent submit tool; private customer approval never
19
+ # invokes it automatically. A five-minute review starts at confirmed delivery.