@weaccelerateinc/agent-sdk 0.0.0-stage → 0.11.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 (52) hide show
  1. package/README.md +151 -2
  2. package/approved-extension-releases/0.0.0.1.json +41 -0
  3. package/approved-extension-releases/0.0.0.2.json +34 -0
  4. package/approved-extension.d.ts +24 -0
  5. package/approved-extension.js +90 -0
  6. package/browser-connector.d.ts +109 -0
  7. package/browser-connector.js +218 -0
  8. package/browser-operations.d.ts +56 -0
  9. package/browser-operations.js +144 -0
  10. package/browser-use-host.d.ts +46 -0
  11. package/browser-use-host.js +149 -0
  12. package/checkout-diagnostics.d.ts +34 -0
  13. package/checkout-diagnostics.js +81 -0
  14. package/cli.js +105 -0
  15. package/customer.env.example +6 -0
  16. package/host-adapter.d.ts +90 -0
  17. package/host-adapter.js +357 -0
  18. package/index.d.ts +253 -0
  19. package/index.js +504 -0
  20. package/linq-messaging.d.ts +45 -0
  21. package/linq-messaging.js +123 -0
  22. package/managed-mcp.d.ts +156 -0
  23. package/managed-mcp.js +427 -0
  24. package/package.json +114 -4
  25. package/partner-starter/.env.example +13 -0
  26. package/partner-starter/README.md +61 -0
  27. package/partner-starter/gitignore.template +5 -0
  28. package/partner-starter/package.json +21 -0
  29. package/partner-starter/runtime.mjs +33 -0
  30. package/partner-starter/server.mjs +13 -0
  31. package/partner-starter/smoke.mjs +61 -0
  32. package/partner-starter/synthetic.mjs +256 -0
  33. package/partner-starter/tests/starter.test.mjs +119 -0
  34. package/partner-starter/trusted-context.example.mjs +23 -0
  35. package/partner.d.ts +27 -0
  36. package/partner.js +53 -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 +316 -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 +82 -0
  45. package/starter/gitignore.template +5 -0
  46. package/starter/package.json +21 -0
  47. package/starter/runtime.mjs +66 -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 +224 -0
  52. package/starter/trusted-context.example.mjs +49 -0
package/managed-mcp.js ADDED
@@ -0,0 +1,427 @@
1
+ import { AccelerateClient, AccelerateError } from './index.js';
2
+ import { browserOperationJsonSchema, parseBrowserOperation, normalizeBrowserOperation } from './browser-operations.js';
3
+ import { checkoutDiagnosticsJsonSchema, validateCheckoutDiagnostics, validateCheckoutReconciliation } from './checkout-diagnostics.js';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const ID = /^mcs_[A-Za-z0-9_-]{43}$/;
7
+ const OWNER = /^[A-Za-z0-9_.:-]{1,500}$/;
8
+ const PHONE = /^\+[1-9]\d{6,14}$/;
9
+ const CONTEXT_KEYS = ['customer_id', 'workspace_id', 'conversation_id', 'phone_number'];
10
+ const STATUSES = ['wallet_required', 'selection_ready', 'details_ready', 'approval_required', 'approved',
11
+ 'ready_for_submit', 'final_click_armed', 'cancelled', 'expired', 'failed', 'submit_attempted', 'outcome_unknown'];
12
+ const NEXT_ACTIONS = ['check_configuration', 'check_request', 'check_status', 'complete_wallet',
13
+ 'complete_verification', 'refresh_quote', 'refresh_revision', 'reconcile_order', 'stop'];
14
+ // Errors that prove the final-click arm was not created. Everything else after an arm request is an unknown outcome.
15
+ const PRE_ARM_REFUSALS = new Set(['approval_required', 'revision_conflict', 'checkout_expired', 'checkout_closed', 'customer_click_required',
16
+ 'permission_denied', 'submit_unavailable', 'capability_disabled']);
17
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value) &&
18
+ [Object.prototype, null].includes(Object.getPrototypeOf(value));
19
+ const check = (value, code = 'invalid_input') => { if (!value) throw new AccelerateError(code); };
20
+ function shape(value, required, optional = [], code = 'invalid_input') {
21
+ check(object(value) && required.every(key => Object.hasOwn(value, key)) &&
22
+ Object.keys(value).every(key => required.includes(key) || optional.includes(key)), code);
23
+ }
24
+ function freeze(value) {
25
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
26
+ for (const child of Object.values(value)) freeze(child);
27
+ Object.freeze(value);
28
+ }
29
+ return value;
30
+ }
31
+ /** Versioned server-selected flow policies. Merchant/layout qualification and
32
+ * the actual delivered five-minute window are verified by the managed service
33
+ * and private review sender; these constants grant no payment capability. */
34
+ export const managedCheckoutProfiles = freeze({
35
+ managed_review_v2: { id: 'managed_review_v2', verificationTiming: 'on_payment',
36
+ approvalPolicy: 'after_fill', approvalWindowMs: 300000, fillAfterVerification: true },
37
+ managed_review_v1: { id: 'managed_review_v1', verificationTiming: 'on_payment',
38
+ approvalPolicy: 'after_fill', approvalWindowMs: 300000 },
39
+ legacy_before_fill_v1: { id: 'legacy_before_fill_v1', verificationTiming: 'on_start',
40
+ approvalPolicy: 'before_fill' },
41
+ });
42
+ function flowProfile({ profile, verificationTiming, approvalPolicy }) {
43
+ const id = profile === undefined ? approvalPolicy === 'before_fill' ? 'legacy_before_fill_v1' : 'managed_review_v1' : profile;
44
+ check(typeof id === 'string' && Object.hasOwn(managedCheckoutProfiles, id));
45
+ const selected = managedCheckoutProfiles[id];
46
+ const timing = verificationTiming === undefined ? selected.verificationTiming : verificationTiming;
47
+ const approval = approvalPolicy === undefined ? selected.approvalPolicy : approvalPolicy;
48
+ check(approval === selected.approvalPolicy && ['on_start', 'on_payment'].includes(timing) &&
49
+ (id === 'legacy_before_fill_v1' || timing === selected.verificationTiming));
50
+ return freeze({ ...selected, verificationTiming: timing });
51
+ }
52
+ const schemaObject = (properties, required = Object.keys(properties)) => ({ type: 'object', properties,
53
+ required, additionalProperties: false });
54
+ const stringSchema = maxLength => ({ type: 'string', minLength: 1, maxLength });
55
+ const idSchema = { type: 'string', pattern: ID.source };
56
+ const revisionSchema = { type: 'integer', minimum: 1, maximum: 2147483647 };
57
+ const urlSchema = { ...stringSchema(2048), format: 'uri', description: 'Actual public HTTPS merchant checkout URL. No credentials or private browser URLs.' };
58
+ const timestampSchema = { type: 'string', format: 'date-time' };
59
+ const nullableText = max => ({ anyOf: [stringSchema(max), { type: 'null' }] });
60
+ const checkoutSchema = schemaObject({
61
+ id: idSchema, status: { type: 'string', enum: STATUSES }, revision: { type: 'integer', minimum: 0, maximum: 2147483647 },
62
+ expires_at: timestampSchema, purpose: { type: 'string', enum: ['purchase', 'sandbox_source_comparison', 'sandbox_fill_only'] },
63
+ approval_policy: { const: 'after_fill' }, purchase_approved: { type: 'boolean' },
64
+ fill_after_verification: { const: true },
65
+ final_click_actor: { const: 'customer' }, submit_actor: { const: 'customer' },
66
+ submit_allowed: { type: 'boolean' }, retry_safe: { const: false }, payment_confirmed: { const: false },
67
+ card: schemaObject({ last4: { anyOf: [{ type: 'string', pattern: '^\\d{4}$' }, { type: 'null' }] }, brand: nullableText(40) }),
68
+ address: schemaObject({ label: nullableText(400), city: nullableText(128), state: nullableText(128), postal_code_mask: nullableText(128) }, ['label']),
69
+ quote: schemaObject({ checkout_origin: urlSchema, amount_cents: revisionSchema, currency: { const: 'USD' } }),
70
+ submit_expires_at: timestampSchema, diagnostics: checkoutDiagnosticsJsonSchema,
71
+ }, ['id', 'status', 'revision', 'expires_at', 'retry_safe', 'payment_confirmed']);
72
+ const browserSchema = schemaObject({ content: { type: 'array', minItems: 1, maxItems: 20,
73
+ items: schemaObject({ type: { const: 'text' }, text: { type: 'string', maxLength: 24000 } }) } });
74
+ const errorSchema = schemaObject({ code: { type: 'string', pattern: '^[a-z][a-z0-9_]{0,63}$' }, message: stringSchema(500),
75
+ status: { type: 'integer', minimum: 0, maximum: 599 }, next_action: { type: 'string', enum: NEXT_ACTIONS },
76
+ retry_safe: { const: false }, payment_confirmed: { const: false },
77
+ support_reference: { type: 'string', pattern: '^(?:acs_|arq_)[a-f0-9]{32}$' },
78
+ }, ['code', 'message', 'status', 'next_action', 'retry_safe', 'payment_confirmed']);
79
+ const failureSchema = schemaObject({ ok: { const: false }, error: errorSchema, checkout: checkoutSchema }, ['ok', 'error']);
80
+ const outputSchema = kind => ({ type: 'object', oneOf: [schemaObject({ ok: { const: true }, [kind]: kind === 'browser' ? browserSchema : checkoutSchema }), failureSchema] });
81
+ const idInput = schemaObject({ checkout_id: idSchema });
82
+ const revisionInput = schemaObject({ checkout_id: idSchema, revision: revisionSchema });
83
+ const browserInput = { ...browserOperationJsonSchema, oneOf: browserOperationJsonSchema.oneOf.map(branch => ({ ...branch,
84
+ properties: { checkout_id: idSchema, ...branch.properties }, required: ['checkout_id', ...branch.required] })) };
85
+
86
+ /** One model-visible contract. Private authentication and reply APIs are deliberately absent. */
87
+ export const managedCheckoutToolDefinitions = freeze([
88
+ { name: 'accelerate_checkout_start', title: 'Start Accelerate checkout',
89
+ description: 'Start one owned chat checkout in Accelerate’s managed private browser. Identity, phone, immutable test/purchase policy and creation idempotency come from your authenticated server. The default managed_review_v1 profile defers verification until payment: prepare the purchase before calling accelerate_checkout_request_payment. Legacy verification at creation requires deliberate server configuration. Shopper verification stays private in chat. Does not submit payment. Never restart an uncertain purchase.',
90
+ inputSchema: schemaObject({ start_url: urlSchema }), outputSchema: outputSchema('checkout') },
91
+ { name: 'accelerate_checkout_status', title: 'Read Accelerate checkout status',
92
+ description: 'Read safe status, current revision and optional fixed recovery guidance for this owned checkout. No card credentials, private wallet tokens or browser connection are returned. Status and guidance do not confirm an order or authorize retries.',
93
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), readOnly: true },
94
+ { name: 'accelerate_checkout_browser', title: 'Prepare merchant checkout',
95
+ description: 'Prepare this same managed checkout browser with restricted text observations, public navigation and safe actions. In deferred verification mode, shop and reach payment before requesting wallet verification; wallet_required does not prevent preparation. Each result replaces ALL earlier element refs; use only the latest refs and batch at most eight steps. No scripts, screenshots, card entry or final purchase. Generic inspection is fenced after private handover.',
96
+ inputSchema: browserInput, outputSchema: outputSchema('browser') },
97
+ { name: 'accelerate_checkout_request_payment', title: 'Start private payment verification',
98
+ description: 'At the merchant payment step, request private OTP and masked wallet selection for this existing owned browser. Available only with deferred verification and while wallet_required. Request this only when the observed merchant page requires payment information, never merely because status is wallet_required or diagnostics suggest private chat. Fresh CVV is requested privately after the final quote is verified; with after_fill policy the customer reviews and approves only after private filling. Does not approve, fill or submit. Private dispatch is deduplicated for this checkout; check this existing checkout after uncertain delivery instead of creating a replacement.',
99
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), paymentOnly: true },
100
+ { name: 'accelerate_checkout_apply_details', title: 'Apply selected wallet address',
101
+ description: 'Privately apply the shopper’s selected saved address to the current merchant checkout before final shipping, tax and total. The agent supplies only the checkout URL; address values stay private. Reinspect the actual final total afterward.',
102
+ inputSchema: schemaObject({ checkout_id: idSchema, checkout_url: urlSchema }), outputSchema: outputSchema('checkout') },
103
+ { name: 'accelerate_checkout_request_approval', title: 'Request exact checkout approval',
104
+ description: 'Freeze the actual final merchant URL, USD cents, total control and final-submit control. Accelerate independently verifies the page total. With after_fill policy this requests private CVV for filling, then the customer reviews a masked screenshot and approves the final amount afterward. With before_fill policy it requests approval first. This call never fills or submits.',
105
+ inputSchema: schemaObject({ checkout_id: idSchema, checkout_url: urlSchema, amount_cents: revisionSchema, currency: { const: 'USD' },
106
+ checkout_total_selector: stringSchema(500), submit_selector: stringSchema(200) }), outputSchema: outputSchema('checkout') },
107
+ { name: 'accelerate_checkout_fill', title: 'Fill approved checkout privately',
108
+ description: 'Fill this exact CVV-verified revision once through Accelerate’s private extension. No automatic submission. The default managed_review_v1 profile requires private ingress to deliver the masked filled review and final total, then wait for explicit customer approval. Its five-minute decision window starts at confirmed private message delivery; the private sender verifies the actual window. purchase_approved must be true before submit. Observe the returned submit deadline and cancel fill-only tests after review. Never replay a failed or uncertain fill.',
109
+ inputSchema: revisionInput, outputSchema: outputSchema('checkout'), destructive: true },
110
+ { name: 'accelerate_checkout_cancel', title: 'Cancel owned checkout',
111
+ description: 'Cancel this exact owned checkout and request private browser cleanup. Cancellation does not reverse an attempted order or prove that no purchase occurred. Keep uncertain-payment fences until independently reconciled.',
112
+ inputSchema: idInput, outputSchema: outputSchema('checkout'), destructive: true },
113
+ { name: 'accelerate_checkout_submit', title: 'Explicitly submit approved purchase',
114
+ description: 'Explicit final purchase decision for this exact owned revision while its returned ready claim remains valid. Accelerate never clicks: this arms one short-lived click, your own backend performs the single physical final click in the customer’s existing browser, then Accelerate observes the outcome once. Never automatically invoke after fill or retry after uncertainty. The default review profile requires the customer’s private post-fill approval first. submit_attempted is not order or payment confirmation. Available only when the server explicitly enables purchase submission.',
115
+ inputSchema: revisionInput, outputSchema: outputSchema('checkout'), destructive: true, submitOnly: true },
116
+ ].map(value => ({ ...value, annotations: { readOnlyHint: value.readOnly === true, idempotentHint: value.readOnly === true,
117
+ destructiveHint: value.destructive === true, openWorldHint: true } })));
118
+
119
+ function safeText(value, max = 1000, multiline = false) {
120
+ check(typeof value === 'string' && value.length > 0 && value.length <= max &&
121
+ !(multiline ? /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u : /[\u0000-\u001f\u007f]/u).test(value), 'invalid_response');
122
+ const flat = value.replace(/[\r\n\t]/g, ' ');
123
+ check(!/\b(?:session_token|handoff_token|submit_token|id_token|authorizationToken|api_key|hmac_secret)\b/iu.test(flat), 'invalid_response');
124
+ for (let start = 0; start < flat.length; start += 1000) {
125
+ try { normalizeBrowserOperation('browser_act', { steps: [{ type: 'type', text: flat.slice(start, start + 2000) }] }); }
126
+ catch { throw new AccelerateError('invalid_response'); }
127
+ }
128
+ return value;
129
+ }
130
+ function timestamp(value) { check(typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/u.test(value) && Number.isFinite(Date.parse(value)), 'invalid_response'); return value; }
131
+ function projectCheckout(value) {
132
+ shape(value, ['id', 'status', 'revision', 'expires_at', 'retry_safe', 'payment_confirmed'],
133
+ ['purpose', 'submit_allowed', 'approval_policy', 'purchase_approved', 'fill_after_verification', 'card', 'address', 'quote', 'submit_expires_at', 'diagnostics', 'reconciliation', 'final_click_actor', 'submit_actor'], 'invalid_response');
134
+ check(typeof value.id === 'string' && ID.test(value.id) && STATUSES.includes(value.status) && Number.isSafeInteger(value.revision) &&
135
+ value.revision >= 0 && value.revision <= 2147483647 && value.retry_safe === false && value.payment_confirmed === false, 'invalid_response');
136
+ const result = { id: value.id, status: value.status, revision: value.revision, expires_at: timestamp(value.expires_at), retry_safe: false, payment_confirmed: false };
137
+ if (value.purpose !== undefined || value.submit_allowed !== undefined) {
138
+ check(['purchase', 'sandbox_source_comparison', 'sandbox_fill_only'].includes(value.purpose) && value.submit_allowed === (value.purpose === 'purchase'), 'invalid_response');
139
+ result.purpose = value.purpose; result.submit_allowed = value.submit_allowed;
140
+ }
141
+ if (value.approval_policy !== undefined || value.purchase_approved !== undefined) {
142
+ check(value.approval_policy === 'after_fill' && typeof value.purchase_approved === 'boolean' && (!value.purchase_approved || ['ready_for_submit', 'final_click_armed'].includes(value.status) && value.purpose === 'purchase'), 'invalid_response');
143
+ result.approval_policy = value.approval_policy; result.purchase_approved = value.purchase_approved;
144
+ }
145
+ if (value.fill_after_verification !== undefined) {
146
+ check(value.fill_after_verification === true && value.approval_policy === 'after_fill' &&
147
+ ['purchase', 'sandbox_fill_only'].includes(value.purpose), 'invalid_response');
148
+ result.fill_after_verification = true;
149
+ }
150
+ // The customer's own backend/browser performs any final click; Accelerate never does.
151
+ if (value.final_click_actor !== undefined) {
152
+ check(value.final_click_actor === 'customer' && value.purpose === 'purchase', 'invalid_response');
153
+ result.final_click_actor = 'customer';
154
+ }
155
+ if (value.submit_actor !== undefined) {
156
+ check(value.submit_actor === 'customer' && value.final_click_actor === 'customer' && ['submit_attempted', 'outcome_unknown'].includes(value.status), 'invalid_response');
157
+ result.submit_actor = 'customer';
158
+ }
159
+ // An attested attempt names the customer; an unknown outcome cannot assert that a click happened.
160
+ if (value.status === 'submit_attempted' && value.purpose === 'purchase') check(value.submit_actor === 'customer', 'invalid_response');
161
+ if (value.card !== undefined) {
162
+ shape(value.card, ['last4', 'brand'], [], 'invalid_response');
163
+ check(value.card.last4 === null || typeof value.card.last4 === 'string' && /^\d{4}$/u.test(value.card.last4), 'invalid_response');
164
+ result.card = { last4: value.card.last4, brand: value.card.brand === null ? null : safeText(value.card.brand, 40) };
165
+ }
166
+ if (value.address !== undefined) {
167
+ shape(value.address, ['label'], ['city', 'state', 'postal_code_mask'], 'invalid_response');
168
+ result.address = { label: value.address.label === null ? null : safeText(value.address.label, 400) };
169
+ for (const key of ['city', 'state', 'postal_code_mask']) if (value.address[key] !== undefined) result.address[key] =
170
+ value.address[key] === null ? null : safeText(value.address[key], 128);
171
+ check(result.address.postal_code_mask == null || /[*•]/u.test(result.address.postal_code_mask), 'invalid_response');
172
+ }
173
+ if (value.quote !== undefined) {
174
+ shape(value.quote, ['checkout_origin', 'amount_cents', 'currency'], [], 'invalid_response');
175
+ const url = new URL(value.quote.checkout_origin);
176
+ check(url.origin === value.quote.checkout_origin && Number.isSafeInteger(value.quote.amount_cents) && value.quote.amount_cents > 0 &&
177
+ value.quote.amount_cents <= 2147483647 && value.quote.currency === 'USD', 'invalid_response');
178
+ try { normalizeBrowserOperation('navigate', { url: value.quote.checkout_origin }); } catch { throw new AccelerateError('invalid_response'); }
179
+ result.quote = { checkout_origin: value.quote.checkout_origin, amount_cents: value.quote.amount_cents, currency: 'USD' };
180
+ }
181
+ if (value.submit_expires_at !== undefined) result.submit_expires_at = timestamp(value.submit_expires_at);
182
+ // A backend record: checked here so a reconciled checkout stays readable, and never projected to the agent.
183
+ if (value.reconciliation !== undefined) { try { validateCheckoutReconciliation(value.reconciliation, value.status); } catch { check(false, 'invalid_response'); } }
184
+ if (value.diagnostics !== undefined) result.diagnostics = JSON.parse(JSON.stringify(validateCheckoutDiagnostics(value.diagnostics, result)));
185
+ return result;
186
+ }
187
+ function projectBrowser(value) {
188
+ shape(value, ['content'], [], 'invalid_response'); check(Array.isArray(value.content) && value.content.length >= 1 && value.content.length <= 20, 'invalid_response');
189
+ let total = 0;
190
+ const content = value.content.map(part => {
191
+ shape(part, ['type', 'text'], [], 'invalid_response'); check(part.type === 'text', 'invalid_response');
192
+ // An empty text block is allowed by the ordinary browser result contract.
193
+ const text = part.text === '' ? '' : safeText(part.text, 24000, true); total += Buffer.byteLength(text, 'utf8');
194
+ return { type: 'text', text };
195
+ });
196
+ check(total <= 24000, 'invalid_response'); return { content };
197
+ }
198
+ function projectError(error) {
199
+ let safe;
200
+ try { safe = error instanceof AccelerateError ? new AccelerateError(error.code, error.status, error.supportReference) : new AccelerateError('request_failed'); }
201
+ catch { safe = new AccelerateError('request_failed'); }
202
+ return { code: safe.code, message: safe.message, status: safe.status, next_action: safe.nextAction,
203
+ retry_safe: false, payment_confirmed: false, ...(typeof safe.supportReference === 'string' && /^(?:acs_|arq_)[a-f0-9]{32}$/u.test(safe.supportReference)
204
+ ? { support_reference: safe.supportReference } : {}) };
205
+ }
206
+ function parseOutput(value, kind) {
207
+ if (value?.ok === false) {
208
+ shape(value, ['ok', 'error'], ['checkout'], 'invalid_response');
209
+ shape(value.error, ['code', 'message', 'status', 'next_action', 'retry_safe', 'payment_confirmed'], ['support_reference'], 'invalid_response');
210
+ const expected = projectError(new AccelerateError(value.error.code, value.error.status, value.error.support_reference));
211
+ check(Object.keys(expected).length === Object.keys(value.error).length && Object.keys(expected).every(key => expected[key] === value.error[key]), 'invalid_response');
212
+ if (value.checkout !== undefined) check(value.error.code === 'checkout_event_unconfirmed', 'invalid_response');
213
+ return { ok: false, error: expected, ...(value.checkout !== undefined ? { checkout: projectCheckout(value.checkout) } : {}) };
214
+ }
215
+ shape(value, ['ok', kind], [], 'invalid_response'); check(value.ok === true, 'invalid_response');
216
+ return { ok: true, [kind]: kind === 'browser' ? projectBrowser(value.browser) : projectCheckout(value.checkout) };
217
+ }
218
+ function parseInput(name, value) {
219
+ const base = name === 'accelerate_checkout_start' ? ['start_url'] : name === 'accelerate_checkout_browser'
220
+ ? ['checkout_id', 'operation', 'arguments'] : name === 'accelerate_checkout_apply_details' ? ['checkout_id', 'checkout_url']
221
+ : name === 'accelerate_checkout_request_approval' ? ['checkout_id', 'checkout_url', 'amount_cents', 'currency', 'checkout_total_selector', 'submit_selector']
222
+ : ['accelerate_checkout_fill', 'accelerate_checkout_submit'].includes(name) ? ['checkout_id', 'revision'] : ['checkout_id'];
223
+ shape(value, base);
224
+ if (value.checkout_id !== undefined) check(typeof value.checkout_id === 'string' && ID.test(value.checkout_id));
225
+ if (name === 'accelerate_checkout_browser') return { checkout_id: value.checkout_id, ...parseBrowserOperation({ operation: value.operation, arguments: value.arguments }) };
226
+ for (const key of ['start_url', 'checkout_url']) if (value[key] !== undefined) normalizeBrowserOperation('navigate', { url: value[key] });
227
+ if (value.revision !== undefined) check(Number.isSafeInteger(value.revision) && value.revision >= 1 && value.revision <= 2147483647);
228
+ if (name === 'accelerate_checkout_request_approval') {
229
+ check(Number.isSafeInteger(value.amount_cents) && value.amount_cents > 0 && value.amount_cents <= 2147483647 && value.currency === 'USD');
230
+ for (const [key, max] of [['checkout_total_selector', 500], ['submit_selector', 200]]) {
231
+ check(typeof value[key] === 'string' && value[key].length > 0 && value[key].length <= max && !/[\u0000-\u001f\u007f]/u.test(value[key]));
232
+ normalizeBrowserOperation('browser_act', { steps: [{ type: 'type', text: value[key] }] });
233
+ }
234
+ }
235
+ return JSON.parse(JSON.stringify(value));
236
+ }
237
+ function standardSchema(json, parse) {
238
+ return freeze({ '~standard': { version: 1, vendor: 'accelerate-managed-checkout',
239
+ validate(value) { try { return { value: parse(value) }; } catch { return { issues: [{ message: 'Invalid Accelerate managed checkout tool value.' }] }; } },
240
+ jsonSchema: { input: () => json, output: () => json } } });
241
+ }
242
+ function contextInput(value) {
243
+ shape(value, CONTEXT_KEYS, [], 'permission_denied');
244
+ check(CONTEXT_KEYS.slice(0, 3).every(key => typeof value[key] === 'string' && OWNER.test(value[key])) &&
245
+ typeof value.phone_number === 'string' && PHONE.test(value.phone_number), 'permission_denied');
246
+ return Object.freeze(Object.fromEntries(CONTEXT_KEYS.map(key => [key, value[key]])));
247
+ }
248
+ function requestOptions(extra) { return extra?.signal instanceof AbortSignal ? { signal: extra.signal } : {}; }
249
+
250
+ /**
251
+ * Register on an authenticated CUSTOMER server. The host's hooks resolve verified
252
+ * identity and hold the durable ownership/uncertainty fence; MCP arguments and
253
+ * extra/_meta are never authentication proof by themselves. Hooks invoke work
254
+ * exactly once, return its result and never retry it. A failed creation/persistence
255
+ * acknowledgement must keep a durable uncertainty fence, not create a replacement.
256
+ */
257
+ export function registerManagedCheckoutTools(server, { client = AccelerateClient.fromEnv(), resolveContext,
258
+ withOwnedCheckout, withCheckoutCreation, dispatchCheckoutEvent, purpose = 'sandbox_fill_only', enableSubmit = false,
259
+ profile, verificationTiming: requestedTiming, approvalPolicy: requestedApproval, browserMode = 'accelerate_managed', browserConnector } = {}) {
260
+ const { verificationTiming, approvalPolicy, fillAfterVerification = false } = flowProfile({ profile, verificationTiming: requestedTiming, approvalPolicy: requestedApproval });
261
+ check(server && typeof server.registerTool === 'function' && client?.checkouts &&
262
+ ['create', 'get', 'browser', 'applyDetails', 'requestApproval', 'fill', 'submit', 'cancel'].every(key => typeof client.checkouts[key] === 'function') &&
263
+ (!enableSubmit || ['armFinalClick', 'completeFinalClick'].every(key => typeof client.checkouts[key] === 'function')) &&
264
+ // Purchases are never clicked by Accelerate or by Accelerate's private Kernel browser: only the customer's own browser host can click.
265
+ (purpose !== 'purchase' || browserMode === 'customer_backend_trusted' && ['prepareFinalClick', 'performFinalClick'].every(key => typeof browserConnector?.[key] === 'function')) &&
266
+ [resolveContext, withOwnedCheckout, withCheckoutCreation, dispatchCheckoutEvent].every(value => typeof value === 'function') &&
267
+ ['purchase', 'sandbox_fill_only'].includes(purpose) && typeof enableSubmit === 'boolean' && (!enableSubmit || purpose === 'purchase') &&
268
+ ['on_start', 'on_payment'].includes(verificationTiming) && ['before_fill', 'after_fill'].includes(approvalPolicy) &&
269
+ ['accelerate_managed', 'customer_backend_trusted'].includes(browserMode) &&
270
+ (browserMode !== 'customer_backend_trusted' || verificationTiming === 'on_payment' && approvalPolicy === 'after_fill') &&
271
+ (browserConnector === undefined || browserMode === 'customer_backend_trusted' && browserConnector?.contract === 'accelerate_existing_browser_v1' &&
272
+ ['prepareAttachment', 'commitAttachment', 'assertPaymentReady', 'confirmCleanup'].every(key => typeof browserConnector[key] === 'function')));
273
+ const requirePolicy = checkout => {
274
+ check(purpose !== 'purchase' || checkout.final_click_actor === 'customer', 'invalid_response');
275
+ check(approvalPolicy === 'after_fill' ? checkout.approval_policy === 'after_fill' && typeof checkout.purchase_approved === 'boolean'
276
+ : checkout.approval_policy === undefined && checkout.purchase_approved === undefined, 'invalid_response');
277
+ return checkout;
278
+ };
279
+ const registered = [];
280
+ for (const definition of managedCheckoutToolDefinitions) {
281
+ if (fillAfterVerification && definition.name === 'accelerate_checkout_fill') continue;
282
+ if (definition.submitOnly && !enableSubmit) continue;
283
+ if (definition.paymentOnly && verificationTiming !== 'on_payment') continue;
284
+ const kind = definition.name === 'accelerate_checkout_browser' ? 'browser' : 'checkout';
285
+ const customerDescriptions = {
286
+ accelerate_checkout_start: 'At the merchant payment step, attach the exact existing HTTPS browser document selected by the authenticated backend. Finish shopping, cart changes, all required non-secret contact/shipping fields, any required cardholder-name (cc-name) field and navigation before attachment; use ordinary customer details or ask for missing non-secret details. Leave card number/PAN, expiry and CVV empty; never obtain them through ordinary browser tools. It never creates a replacement browser. A configured connector checks ownership, extension readiness, recording policy, lifetime and automation fencing before private verification. The executor must admit actual unambiguous card fields, supported frames and safe masked review in that document before OTP or CVV. Compatible forms are detected across merchants; unsupported or ambiguous layouts stop before private collection. Identity, phone, browser connection and immutable policy are never model inputs. Continue with accelerate_checkout_request_payment. Does not fill or submit. Never restart an uncertain checkout.',
287
+ accelerate_checkout_browser: 'Read this exact attached checkout with snapshot, text, find or bounded wait before quote handover. Cart changes, required non-secret contact/shipping and cardholder-name fields, and navigation must finish in the platform browser before attachment. Card number/PAN, expiry and CVV remain private and must never be entered through ordinary browser tools. Generic browser actions, scripts, screenshots and credential inspection are forbidden. Each result replaces all previous element refs. Once the quote is frozen, ordinary inspection is fenced; use safe checkout status only. The private masked review is delivered outside model tools.',
288
+ };
289
+ const description = fillAfterVerification && definition.name === 'accelerate_checkout_request_approval'
290
+ ? 'Verify the actual final merchant quote and explicitly arm one private card fill for this exact revision, card, address and existing browser. Successful private CVV verification immediately continues the armed fill in Accelerate’s backend, without a model roundtrip. The private ingress sends the masked filled review and final total. Do not call a manual fill or submit automatically; final purchase approval and the explicit agent submit remain separate.'
291
+ : browserMode === 'customer_backend_trusted' ? customerDescriptions[definition.name] ?? definition.description : definition.description;
292
+ registered.push(server.registerTool(definition.name, { title: definition.title, description,
293
+ inputSchema: standardSchema(definition.inputSchema, value => parseInput(definition.name, value)),
294
+ outputSchema: standardSchema(definition.outputSchema, value => parseOutput(value, kind)), annotations: definition.annotations,
295
+ }, async (args, extra) => {
296
+ let creationStarted = false, creationAcknowledged = false;
297
+ try {
298
+ // Re-resolve for EVERY invocation; never cache a shopper across calls.
299
+ const context = contextInput(await resolveContext(extra));
300
+ let input;
301
+ try { input = parseInput(definition.name, args); } catch { throw new AccelerateError('invalid_input'); }
302
+ const options = requestOptions(extra);
303
+ let admitting = true;
304
+ const requireAdmission = () => check(admitting, 'operation_conflict');
305
+ const dispatch = async (kind, checkout) => {
306
+ requireAdmission();
307
+ const key = createHash('sha256').update(JSON.stringify(['v1', kind, checkout.id, ['approval_requested', 'review_ready'].includes(kind) ? checkout.revision : 0])).digest('hex');
308
+ const event = freeze({ version: 'v1', kind, idempotency_key: 'accelerate-checkout-event:' + key, checkout: projectCheckout(checkout) });
309
+ try { await dispatchCheckoutEvent(context, event); requireAdmission(); return { ok: true, checkout }; }
310
+ catch { requireAdmission(); return { ok: false, error: projectError(new AccelerateError('checkout_event_unconfirmed')), checkout }; }
311
+ };
312
+ let called = false, completed = false, repeated = false, expected;
313
+ const once = work => value => {
314
+ const pending = (async () => {
315
+ if (!admitting || called) { repeated = true; throw new AccelerateError('operation_conflict'); }
316
+ called = true;
317
+ const output = await work(value); requireAdmission(); completed = true; expected = output; return output;
318
+ })();
319
+ // A broken host must not turn a deliberately rejected late callback
320
+ // into an unhandled process rejection. The returned promise still rejects.
321
+ void pending.catch(() => {}); return pending;
322
+ };
323
+ let result;
324
+ try {
325
+ if (definition.name === 'accelerate_checkout_start') {
326
+ result = await withCheckoutCreation(context, once(async scope => {
327
+ shape(scope, ['idempotency_key', 'persistCreated'], browserMode === 'customer_backend_trusted' && !browserConnector ? ['browser', 'browser_resource'] : []);
328
+ check(typeof scope.idempotency_key === 'string' && scope.idempotency_key.length >= 1 && scope.idempotency_key.length <= 200 &&
329
+ !/[\u0000-\u001f\u007f]/u.test(scope.idempotency_key) && typeof scope.persistCreated === 'function');
330
+ const attachment = browserConnector ? await browserConnector.prepareAttachment(context, {
331
+ attempt_key: scope.idempotency_key, checkout_url: input.start_url,
332
+ }, options) : undefined;
333
+ requireAdmission();
334
+ const browser = attachment ? attachment.browser : scope.browser;
335
+ if (browserMode === 'customer_backend_trusted') {
336
+ shape(browser, ['provider', 'browser_id', 'custody_mode'], browser?.provider === 'connector' ? ['connector_id'] : [], 'permission_denied');
337
+ const valid = browser.provider === 'browser_use'
338
+ ? typeof browser.browser_id === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/u.test(browser.browser_id)
339
+ : browser.provider === 'connector' && typeof browser.connector_id === 'string' && /^[a-z][a-z0-9_-]{0,63}$/u.test(browser.connector_id) &&
340
+ typeof browser.browser_id === 'string' && /^[A-Za-z0-9_.:-]{1,200}$/u.test(browser.browser_id);
341
+ check(valid && browser.custody_mode === browserMode, 'permission_denied');
342
+ }
343
+ creationStarted = true;
344
+ const created = await client.checkouts.create({ ...context, start_url: input.start_url,
345
+ idempotency_key: scope.idempotency_key, interaction_mode: 'chat', purpose,
346
+ ...(purpose === 'purchase' ? { final_click_actor: 'customer' } : {}),
347
+ ...(browserMode === 'customer_backend_trusted' ? { browser: { ...browser } } : {}),
348
+ ...(attachment?.browser_resource ?? scope.browser_resource ? { browser_resource: attachment?.browser_resource ?? scope.browser_resource } : {}),
349
+ ...(approvalPolicy === 'after_fill' ? { approval_policy: approvalPolicy } : {}),
350
+ ...(fillAfterVerification ? { fill_after_verification: true } : {}) }, options);
351
+ requireAdmission(); const checkout = requirePolicy(projectCheckout(created));
352
+ check(approvalPolicy !== 'after_fill' || checkout.purchase_approved === false, 'invalid_response');
353
+ check(!fillAfterVerification || checkout.fill_after_verification === true, 'invalid_response');
354
+ check(checkout.purpose === purpose && checkout.submit_allowed === (purpose === 'purchase'), 'invalid_response');
355
+ await scope.persistCreated(Object.freeze({ checkout_id: checkout.id, ...context })); requireAdmission();
356
+ if (browserConnector) { await browserConnector.commitAttachment(context, { checkout_id: checkout.id, attachment }, options); requireAdmission(); }
357
+ creationAcknowledged = true;
358
+ return verificationTiming === 'on_payment' ? { ok: true, checkout } : dispatch('created', checkout);
359
+ }), Object.freeze({ start_url: input.start_url }));
360
+ } else {
361
+ result = await withOwnedCheckout(context, input.checkout_id, once(async owned => {
362
+ shape(owned, ['checkout_id', ...CONTEXT_KEYS], [], 'permission_denied');
363
+ check(owned.checkout_id === input.checkout_id && CONTEXT_KEYS.every(key => owned[key] === context[key]), 'permission_denied');
364
+ const id = input.checkout_id;
365
+ let value;
366
+ if (['accelerate_checkout_status', 'accelerate_checkout_request_payment'].includes(definition.name)) value = await client.checkouts.get(id, options);
367
+ else if (definition.name === 'accelerate_checkout_browser') value = await client.checkouts.browser(id, { operation: input.operation, arguments: input.arguments }, options);
368
+ else if (definition.name === 'accelerate_checkout_apply_details') value = await client.checkouts.applyDetails(id, { checkout_url: input.checkout_url }, options);
369
+ else if (definition.name === 'accelerate_checkout_request_approval') {
370
+ if (browserConnector) { await browserConnector.assertPaymentReady(context, { checkout_id: id, checkout_url: input.checkout_url }, options); requireAdmission(); }
371
+ const { checkout_id: _id, ...approval } = input;
372
+ value = await client.checkouts.requestApproval(id, { ...approval, ...(fillAfterVerification ? { fill_after_verification: true } : {}) }, options);
373
+ check(!fillAfterVerification || value.fill_after_verification === true, 'invalid_response');
374
+ } else if (definition.name === 'accelerate_checkout_fill') value = await client.checkouts.fill(id, { revision: input.revision }, options);
375
+ else if (definition.name === 'accelerate_checkout_submit') {
376
+ check(enableSubmit && purpose === 'purchase' && typeof browserConnector?.performFinalClick === 'function' && typeof browserConnector.prepareFinalClick === 'function', 'permission_denied');
377
+ // prepare -> arm -> customer-owned host click -> complete, exactly once. The arm token and target stay in this scope.
378
+ // Slow provider/extension readiness runs BEFORE the arm so it cannot consume the short click window.
379
+ const preparation = await browserConnector.prepareFinalClick(context, { checkout_id: id }, options); requireAdmission();
380
+ let armed;
381
+ try { armed = await client.checkouts.armFinalClick(id, { revision: input.revision }, options); }
382
+ catch (error) {
383
+ // Only an explicit refusal proves nothing was armed. Transport, schema, timeout and upstream failures may have burned the arm.
384
+ if (error instanceof AccelerateError && PRE_ARM_REFUSALS.has(error.code)) throw error;
385
+ throw new AccelerateError('outcome_unknown');
386
+ }
387
+ try {
388
+ await browserConnector.performFinalClick(context, { checkout_id: id, preparation,
389
+ checkout_url: armed.target.checkout_url, submit_selector: armed.target.submit_selector,
390
+ armed_expires_at: armed.armed_expires_at }, options);
391
+ } catch { /* Host failure is not retried; the service observation below is the only truth. */ }
392
+ try { value = await client.checkouts.completeFinalClick(id, { revision: input.revision, arm_id: armed.arm_id }, options); }
393
+ catch { throw new AccelerateError('outcome_unknown'); }
394
+ // After a click attempt any unusable observation is an uncertain outcome, never a retryable failure.
395
+ try { requirePolicy(projectCheckout(value)); } catch { throw new AccelerateError('outcome_unknown'); }
396
+ check(['submit_attempted', 'outcome_unknown'].includes(value?.status) && value.final_click_actor === 'customer' &&
397
+ (value.status === 'outcome_unknown' || value.submit_actor === 'customer'), 'outcome_unknown');
398
+ }
399
+ else value = await client.checkouts.cancel(id, options);
400
+ requireAdmission();
401
+ const projected = kind === 'browser' ? projectBrowser(value) : requirePolicy(projectCheckout(value));
402
+ if (kind === 'checkout') check(projected.id === id, 'invalid_response');
403
+ if (definition.name === 'accelerate_checkout_request_payment') {
404
+ check(projected.status === 'wallet_required', 'checkout_frozen');
405
+ if (browserConnector) { await browserConnector.assertPaymentReady(context, { checkout_id: id }, options); requireAdmission(); }
406
+ return dispatch('payment_requested', projected);
407
+ }
408
+ if (browserConnector && ['accelerate_checkout_cancel', 'accelerate_checkout_status'].includes(definition.name) &&
409
+ projected.diagnostics?.cleanup_status === 'confirmed') {
410
+ await browserConnector.confirmCleanup(context, { checkout_id: id }, options); requireAdmission();
411
+ }
412
+ if (definition.name === 'accelerate_checkout_fill' && approvalPolicy === 'after_fill') return dispatch('review_ready', projected);
413
+ return definition.name === 'accelerate_checkout_request_approval' ? dispatch('approval_requested', projected) : { ok: true, [kind]: projected };
414
+ }), Object.freeze({ operation: definition.name, ...(input.revision === undefined ? {} : { revision: input.revision }) }));
415
+ }
416
+ } finally { admitting = false; }
417
+ check(called && completed && !repeated && result === expected, 'operation_conflict');
418
+ const payload = parseOutput(result, kind);
419
+ return { ...(payload.ok === false ? { isError: true } : {}), content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload };
420
+ } catch (error) {
421
+ const payload = { ok: false, error: projectError(creationStarted && !creationAcknowledged ? new AccelerateError('outcome_unknown') : error) };
422
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(payload) }], structuredContent: payload };
423
+ }
424
+ }));
425
+ }
426
+ return Object.freeze(registered);
427
+ }
package/package.json CHANGED
@@ -1,6 +1,116 @@
1
1
  {
2
2
  "name": "@weaccelerateinc/agent-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.11.0",
4
+ "description": "Server-only payment SDK, existing-browser connectors and private host adapters for agent platforms",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "types": "./index.d.ts",
8
+ "bin": {
9
+ "accelerate-agent": "./cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./index.js"
15
+ },
16
+ "./browser-operations": {
17
+ "types": "./browser-operations.d.ts",
18
+ "import": "./browser-operations.js"
19
+ },
20
+ "./private-chat": {
21
+ "types": "./private-chat.d.ts",
22
+ "import": "./private-chat.js"
23
+ },
24
+ "./managed-mcp": {
25
+ "types": "./managed-mcp.d.ts",
26
+ "import": "./managed-mcp.js"
27
+ },
28
+ "./approved-extension": {
29
+ "types": "./approved-extension.d.ts",
30
+ "import": "./approved-extension.js"
31
+ },
32
+ "./browser-connector": {
33
+ "types": "./browser-connector.d.ts",
34
+ "import": "./browser-connector.js"
35
+ },
36
+ "./host-adapter": {
37
+ "types": "./host-adapter.d.ts",
38
+ "import": "./host-adapter.js"
39
+ },
40
+ "./postgres-host": {
41
+ "types": "./postgres-host.d.ts",
42
+ "import": "./postgres-host.js"
43
+ },
44
+ "./private-ingress": {
45
+ "types": "./private-ingress.d.ts",
46
+ "import": "./private-ingress.js"
47
+ },
48
+ "./linq-messaging": {
49
+ "types": "./linq-messaging.d.ts",
50
+ "import": "./linq-messaging.js"
51
+ },
52
+ "./partner": {
53
+ "types": "./partner.d.ts",
54
+ "import": "./partner.js"
55
+ },
56
+ "./approved-extension-releases/*": "./approved-extension-releases/*",
57
+ "./browser-use-host": {
58
+ "types": "./browser-use-host.d.ts",
59
+ "import": "./browser-use-host.js"
60
+ }
61
+ },
62
+ "files": [
63
+ "index.js",
64
+ "index.d.ts",
65
+ "browser-operations.js",
66
+ "browser-operations.d.ts",
67
+ "private-chat.js",
68
+ "private-chat.d.ts",
69
+ "cli.js",
70
+ "customer.env.example",
71
+ "README.md",
72
+ "managed-mcp.js",
73
+ "managed-mcp.d.ts",
74
+ "checkout-diagnostics.js",
75
+ "checkout-diagnostics.d.ts",
76
+ "starter",
77
+ "approved-extension.js",
78
+ "approved-extension.d.ts",
79
+ "browser-connector.js",
80
+ "browser-connector.d.ts",
81
+ "host-adapter.js",
82
+ "host-adapter.d.ts",
83
+ "postgres-host.js",
84
+ "postgres-host.d.ts",
85
+ "private-ingress.js",
86
+ "private-ingress.d.ts",
87
+ "linq-messaging.js",
88
+ "linq-messaging.d.ts",
89
+ "partner.js",
90
+ "partner.d.ts",
91
+ "approved-extension-releases",
92
+ "partner-starter",
93
+ "browser-use-host.js",
94
+ "browser-use-host.d.ts"
95
+ ],
96
+ "engines": {
97
+ "node": ">=22"
98
+ },
99
+ "scripts": {
100
+ "test": "node --test test/*.test.js"
101
+ },
102
+ "license": "UNLICENSED",
103
+ "repository": {
104
+ "type": "git",
105
+ "url": "git+https://github.com/weaccelerateinc/accelerate.git",
106
+ "directory": "products/agent-sdk/packages/sdk"
107
+ },
108
+ "homepage": "https://github.com/weaccelerateinc/accelerate/tree/main/products/agent-sdk#readme",
109
+ "bugs": {
110
+ "url": "https://github.com/weaccelerateinc/accelerate/issues"
111
+ },
112
+ "publishConfig": {
113
+ "access": "public",
114
+ "registry": "https://registry.npmjs.org/"
115
+ }
116
+ }
@@ -0,0 +1,13 @@
1
+ # Synthetic mode makes no external requests and needs no credentials.
2
+ ACCELERATE_STARTER_MODE=synthetic
3
+ # Explicit new profile: private CVV continues the armed fill in the backend.
4
+ ACCELERATE_CHECKOUT_PROFILE=managed_review_v2
5
+ ACCELERATE_STARTER_PURPOSE=purchase
6
+ ACCELERATE_ENABLE_PURCHASE_SUBMIT=true
7
+ # For a fill-only test, set purpose to sandbox_fill_only and submit to false.
8
+ # This file is a template; export values or load it with your server environment.
9
+ # Live: operator-controlled absolute file exporting createPartnerDependencies().
10
+ # ACCELERATE_CONTEXT_MODULE=/absolute/protected/partner-host.mjs
11
+ # ACCELERATE_API_KEY=<tenant server key>
12
+ # ACCELERATE_MERCHANT_ID=<provisioned merchant UUID>
13
+ # ACCELERATE_BASE_URL=https://accelerate-agent-sdk.vercel.app