@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,88 @@
1
+ # Accelerate managed MCP starter
2
+
3
+ The starter uses the same versioned profile as the reference demo: `managed_review_v1`. It quietly creates one managed browser, waits until payment for private OTP and masked selection, verifies CVV, fills, then delivers a safe filled-form image. A purchase review gives a full 300 seconds from the provider-confirmed sent timestamp for a simple approve/decline decision. The default purpose is `sandbox_fill_only`: its image is informational, submission is permanently disallowed, and the agent cancels after image delivery.
4
+
5
+ The default runnable mode is **synthetic**. It uses the real SDK, managed MCP adapter and official stdio protocol against an in-process fixture. It simulates private verification and review delivery; no real credentials, wallet, browser, OTP, CVV, provider attachment or purchase are used. This is a contract smoke test, not live qualification.
6
+
7
+ ## Install and run
8
+
9
+ Requires Node.js 22 or newer. Install the exact SDK 0.9.0 archive and checksum supplied by Accelerate; [release distribution](https://github.com/weaccelerateinc/accelerate-agent-sdk/blob/main/docs/releases.md) records verified availability. Do not assume npm publication.
10
+
11
+ ```sh
12
+ npx --no-install accelerate-agent init ./accelerate-demo
13
+ cd accelerate-demo
14
+ npm install /absolute/path/to/accelerate-agent-sdk-0.9.0.tgz
15
+ npm test
16
+ npm run smoke
17
+ ```
18
+
19
+ The install resolves pinned MCP libraries and creates `package-lock.json`; commit that lockfile. The starter is private and is not an npm publication target.
20
+
21
+ `smoke` discovers the actual MCP tools, creates silently, shops before verification, requests payment, observes simulated selection, applies details, freezes the quote, verifies private fill permission, fills, simulates safe review delivery and cancels. It verifies that no submit tool exists. Smoke always forces synthetic mode and does not inherit Accelerate secrets.
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "accelerate-demo": {
27
+ "command": "node",
28
+ "args": ["/absolute/path/to/accelerate-demo/server.mjs"],
29
+ "env": { "ACCELERATE_STARTER_MODE": "synthetic" }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Stdout is reserved for protocol messages. No browser or HTTP UI is started. Tool arguments cannot supply the shopper, workspace, conversation, phone, profile or creation idempotency key. Never copy the fixture's in-memory identity or ownership state into live mode.
36
+
37
+ ## Connect your authenticated backend
38
+
39
+ Live mode uses the same profile and stays fill-only by default. Configure these only in the authenticated backend process:
40
+
41
+ ```dotenv
42
+ ACCELERATE_STARTER_MODE=live
43
+ ACCELERATE_API_KEY=<your tenant API key>
44
+ ACCELERATE_MERCHANT_ID=<your merchant UUID>
45
+ ACCELERATE_CONTEXT_MODULE=/absolute/server/path/your-trusted-context.mjs
46
+ ```
47
+
48
+ The service origin defaults to Accelerate's managed service. No Kernel, extension, VGS, executor or processor secret is required. The starter does not load `.env` automatically; use the platform's secret settings. `accelerate-agent doctor` checks the key/API pair, not downstream browser readiness or merchant qualification.
49
+
50
+ Provide `createTrustedHost({client,privateChat,deliverReview})` in the server-owned module using `trusted-context.example.mjs`. It deliberately fails until connected to the real application; it provides no pretend authentication. A stdio process must be bound to the intended verified principal. Do not expose it as an anonymous network server or treat `_meta` as identity.
51
+
52
+ The host connects four durable hooks plus its private sender:
53
+
54
+ - `resolveContext(extra)` derives verified `{customer_id,workspace_id,conversation_id,phone_number}` from existing server authentication.
55
+ - `withOwnedCheckout(context,id,work)` holds the durable owner lock, authorizes all exact saved fields, invokes `work` once and returns its result.
56
+ - `withCheckoutCreation(context,work)` serializes the exact server-owned attempt with `{idempotency_key,persistCreated}`. Preserve an unresolved-attempt fence after uncertain dispatch or persistence; never create a replacement automatically.
57
+ - `dispatchCheckoutEvent(context,event)` durably accepts and deduplicates the event. `payment_requested` starts private chat only at payment. `approval_requested` retrieves the private CVV prompt after the exact quote is frozen. `review_ready` invokes `await deliverReview(context,event)` from that same durable handler and resolves only after actual provider delivery and service acknowledgement. Preserve pending safe-event work on failure; the runtime verifies the acknowledgement before returning fill success. No model handles the image or private state.
58
+ - `sendPrivateReview(context,message)` uploads and sends the exact supplied PNG and text together to the authenticated private conversation. Honor the stable key, abort signal and delivery deadline, and return the actual provider `{message_id,sent_at}` only after sent confirmation. A queued message is insufficient.
59
+
60
+ The runtime reuses `deliverPrivateCheckoutReview` to validate image/quote metadata, render the exact total, acknowledge the provider receipt and wipe image bytes. The customer supplies native transport; there is no public media URL, new operator credential or supplied provider authenticator. Recover safe lifecycle/delivery events through the durable outbox; never recover by repeating fill or creating another checkout.
61
+
62
+ Private OTP, selection, CVV and decisions enter through a separate authenticated webhook **before** model prompts, history, tracing, analytics or ordinary queues. Reuse the [private ingress contract](https://github.com/weaccelerateinc/accelerate-agent-sdk/blob/main/docs/private-chat.md) and [ingress example](https://github.com/weaccelerateinc/accelerate-agent-sdk/blob/main/examples/private-chat-ingress.js). They require exact ownership, provider signature verification, stable message IDs and trusted timestamps. Erase handled private transport bodies, including before same-ID provider NACKs; errors never fall through to the model.
63
+
64
+ ## Run the supported sandbox flow
65
+
66
+ The current safe capture is qualified only for the configured sandbox merchant's `https://demos.weaccelerate.com/normal/payment` page. Start at `https://demos.weaccelerate.com/normal`; use synthetic initial contact fields, continue to payment, and apply the private selected address there. Unsupported pages fail closed. Merchant ID alone does not qualify other layouts.
67
+
68
+ 1. Start and shop in the same managed browser without sending wallet prompts.
69
+ 2. At payment, call `accelerate_checkout_request_payment`; complete private OTP and masked card selection. Wait for `selection_ready`.
70
+ 3. Apply the selected address, read final shipping/tax-inclusive pricing and freeze that exact quote.
71
+ 4. Complete private CVV for fill permission. No purchase approval is requested before filling.
72
+ 5. Fill the verified revision once. The private handler delivers the actual safe form image and exact total: PAN stars plus last four, visible expiry and CVV stars.
73
+ 6. For the default fill-only checkout, require successful informational image delivery, then cancel the same checkout. Submit is absent and forbidden. Do not inspect the populated browser.
74
+
75
+ For an explicitly enabled purchase integration, the same private image asks for simple approve/decline and grants a full five-minute decision window from the actual provider receipt. `ready_for_submit` is insufficient: wait for `purchase_approved:true`, then use a separate explicit owner-authorized submit operation before the returned deadline. After qualification, purchase mode requires both of these backend settings:
76
+
77
+ ```dotenv
78
+ ACCELERATE_STARTER_PURPOSE=purchase
79
+ ACCELERATE_ENABLE_PURCHASE_SUBMIT=true
80
+ ```
81
+
82
+ The model cannot enable these settings. `purpose:purchase` without the submit flag still exposes no submit tool.
83
+
84
+ ## Explicit legacy compatibility
85
+
86
+ The supported profile is fixed: payment-time verification, after-fill review and 300000ms. Conflicting timing or approval overrides are rejected. Existing `before_fill` deployments can explicitly register `profile:'legacy_before_fill_v1'`; its default timing is `on_start`, with deliberate `verificationTiming:'on_payment'` available for the deferred legacy flow. It requests revision/code approval before fresh CVV and uses the original short submit lease after filling. It has no post-fill human review. Existing checkouts retain their original policy; never switch a checkout between profiles or silently downgrade after an unsupported capture.
87
+
88
+ See [the managed MCP guide](https://github.com/weaccelerateinc/accelerate-agent-sdk/blob/main/docs/managed-mcp.md) and [new customer integration](https://github.com/weaccelerateinc/accelerate-agent-sdk/blob/main/docs/new-customer-integration.md) for the full contract. Synthetic smoke, connection checks and one sandbox fill do not certify PCI scope or production payments.
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+ *.log
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "accelerate-managed-mcp-starter",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=22"
8
+ },
9
+ "scripts": {
10
+ "start": "node server.mjs",
11
+ "smoke": "node smoke.mjs",
12
+ "test": "node --test tests/*.test.mjs"
13
+ },
14
+ "dependencies": {
15
+ "@weaccelerateinc/agent-sdk": "0.10.3",
16
+ "@modelcontextprotocol/server": "2.3.1"
17
+ },
18
+ "devDependencies": {
19
+ "@modelcontextprotocol/client": "2.3.1"
20
+ }
21
+ }
@@ -0,0 +1,61 @@
1
+ import { isAbsolute } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { AccelerateClient } from '@weaccelerateinc/agent-sdk';
4
+ import { AcceleratePrivateChatClient, deliverPrivateCheckoutReview } from '@weaccelerateinc/agent-sdk/private-chat';
5
+ import { registerManagedCheckoutTools } from '@weaccelerateinc/agent-sdk/managed-mcp';
6
+ import { createSyntheticHost } from './synthetic.mjs';
7
+
8
+ const requiredHooks = ['resolveContext', 'withOwnedCheckout', 'withCheckoutCreation', 'dispatchCheckoutEvent'];
9
+ export async function configureStarter(server, env = process.env) {
10
+ const mode = env.ACCELERATE_STARTER_MODE ?? 'synthetic';
11
+ const purpose = env.ACCELERATE_STARTER_PURPOSE ?? 'sandbox_fill_only';
12
+ const enableSubmit = env.ACCELERATE_ENABLE_PURCHASE_SUBMIT === 'true';
13
+ if (!['sandbox_fill_only', 'purchase'].includes(purpose) ||
14
+ env.ACCELERATE_ENABLE_PURCHASE_SUBMIT !== undefined && !['true', 'false'].includes(env.ACCELERATE_ENABLE_PURCHASE_SUBMIT) ||
15
+ enableSubmit && purpose !== 'purchase') throw Error('Unsupported server purchase policy.');
16
+ let host, privateChat;
17
+ const matches = (state, event) => state.checkout.id === event.checkout.id && state.checkout.revision === event.checkout.revision &&
18
+ state.checkout.purpose === purpose && state.checkout.approval_policy === 'after_fill' &&
19
+ state.checkout.status === 'ready_for_submit' && state.checkout.quote && event.checkout.quote &&
20
+ ['checkout_origin', 'amount_cents', 'currency'].every(key => state.checkout.quote[key] === event.checkout.quote[key]);
21
+ const delivered = state => state?.review?.delivered === true && state.review.approval_window_ms === 300000 &&
22
+ (purpose === 'sandbox_fill_only' ? state.stage === 'review_delivered' : ['approval_required', 'approved_for_submit'].includes(state.stage));
23
+ // Invoke from the host's durable event worker, including retries of that same
24
+ // event. This is private host code, never an MCP tool or a model callback.
25
+ const deliverReview = async (context, event) => {
26
+ if (event?.version !== 'v1' || event.kind !== 'review_ready' || event.checkout?.approval_policy !== 'after_fill' ||
27
+ event.checkout.purpose !== purpose || event.checkout.status !== 'ready_for_submit') throw Error('Private review delivery is unavailable.');
28
+ const binding = Object.fromEntries(['customer_id', 'workspace_id', 'conversation_id'].map(key => [key, context[key]]));
29
+ const state = await privateChat.chat.get(event.checkout.id, binding);
30
+ if (!matches(state, event) || state.review?.approval_window_ms !== 300000 ||
31
+ typeof state.review.delivery_expires_at !== 'string') throw Error('Private review delivery is unavailable.');
32
+ if (delivered(state)) return state;
33
+ return deliverPrivateCheckoutReview({ client: privateChat, state, binding,
34
+ messageId: event.idempotency_key, send: message => host.sendPrivateReview(context, message) });
35
+ };
36
+ if (mode === 'synthetic') { host = createSyntheticHost({ purpose, deliverReview }); privateChat = host.privateChat; }
37
+ else if (mode === 'live') {
38
+ // This path is configured by the server operator, never an MCP argument.
39
+ if (typeof env.ACCELERATE_CONTEXT_MODULE !== 'string' || !isAbsolute(env.ACCELERATE_CONTEXT_MODULE)) {
40
+ throw Error('A trusted absolute server context module is required.');
41
+ }
42
+ const module = await import(pathToFileURL(env.ACCELERATE_CONTEXT_MODULE).href);
43
+ if (typeof module.createTrustedHost !== 'function') throw Error('The trusted context module is incomplete.');
44
+ const client = AccelerateClient.fromEnv(env); privateChat = AcceleratePrivateChatClient.fromEnv(env);
45
+ host = { ...await module.createTrustedHost({ client, privateChat, deliverReview }), client };
46
+ } else throw Error('Unsupported starter mode.');
47
+ if ([...requiredHooks, 'sendPrivateReview'].some(key => typeof host?.[key] !== 'function')) throw Error('Authenticated server hooks and private review delivery are required.');
48
+ const dispatchCheckoutEvent = async (context, event) => {
49
+ await host.dispatchCheckoutEvent(context, event);
50
+ if (event.kind === 'review_ready') {
51
+ const binding = Object.fromEntries(['customer_id', 'workspace_id', 'conversation_id'].map(key => [key, context[key]]));
52
+ const state = await privateChat.chat.get(event.checkout.id, binding);
53
+ if (!matches(state, event) || !delivered(state)) throw Error('Private review delivery is unconfirmed.');
54
+ }
55
+ };
56
+ registerManagedCheckoutTools(server, { client: host.client,
57
+ resolveContext: host.resolveContext, withOwnedCheckout: host.withOwnedCheckout,
58
+ withCheckoutCreation: host.withCheckoutCreation, dispatchCheckoutEvent,
59
+ profile: 'managed_review_v1', purpose, enableSubmit });
60
+ return { mode, counters: host.counters };
61
+ }
@@ -0,0 +1,13 @@
1
+ import { McpServer } from '@modelcontextprotocol/server';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
3
+ import { configureStarter } from './runtime.mjs';
4
+
5
+ const server = new McpServer({ name: 'accelerate-managed-starter', version: '0.1.0' });
6
+ try {
7
+ await configureStarter(server);
8
+ server.onerror = () => {}; // Never print protocol arguments or provider errors.
9
+ await server.connect(new StdioServerTransport());
10
+ } catch {
11
+ process.stderr.write(JSON.stringify({ ok: false, code: 'starter_configuration_unavailable' }) + '\n');
12
+ process.exitCode = 1;
13
+ }
@@ -0,0 +1,55 @@
1
+ import assert from 'node:assert/strict';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { Client } from '@modelcontextprotocol/client';
4
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
5
+
6
+ const client = new Client({ name: 'accelerate-starter-smoke', version: '0.1.0' });
7
+ const transport = new StdioClientTransport({ command: process.execPath,
8
+ args: [fileURLToPath(new URL('./server.mjs', import.meta.url))],
9
+ env: { ...getDefaultEnvironment(), ACCELERATE_STARTER_MODE: 'synthetic' }, stderr: 'pipe' });
10
+ let complete = false;
11
+ const timer = setTimeout(() => { void transport.close(); }, 15000);
12
+ try {
13
+ await client.connect(transport);
14
+ const tools = await client.listTools();
15
+ assert.equal(tools.tools.length, 8);
16
+ assert.equal(tools.tools.some(tool => tool.name === 'accelerate_checkout_request_payment'), true);
17
+ assert.equal(tools.tools.some(tool => tool.name === 'accelerate_checkout_submit'), false);
18
+ const call = async (name, args) => {
19
+ const result = await client.callTool({ name, arguments: args });
20
+ const value = result.structuredContent ?? JSON.parse(result.content.find(part => part.type === 'text').text);
21
+ assert.equal(value.ok, true); return value;
22
+ };
23
+ const checkoutUrl = 'https://demos.weaccelerate.com/normal/payment';
24
+ const checkout = (await call('accelerate_checkout_start', { start_url: checkoutUrl })).checkout;
25
+ assert.equal(checkout.purpose, 'sandbox_fill_only'); assert.equal(checkout.submit_allowed, false);
26
+ assert.equal(checkout.approval_policy, 'after_fill'); assert.equal(checkout.purchase_approved, false);
27
+ assert.equal(checkout.status, 'wallet_required'); assert.equal(checkout.card, undefined);
28
+ const id = checkout.id;
29
+ assert.equal((await call('accelerate_checkout_status', { checkout_id: id })).checkout.status, 'wallet_required');
30
+ await call('accelerate_checkout_browser', { checkout_id: id, operation: 'browser_snapshot', arguments: { filter: 'all' } });
31
+ assert.equal((await call('accelerate_checkout_status', { checkout_id: id })).checkout.status, 'wallet_required');
32
+ const requested = await call('accelerate_checkout_request_payment', { checkout_id: id });
33
+ assert.equal(requested.checkout.status, 'wallet_required');
34
+ assert.equal((await call('accelerate_checkout_status', { checkout_id: id })).checkout.status, 'selection_ready');
35
+ await call('accelerate_checkout_apply_details', { checkout_id: id, checkout_url: checkoutUrl });
36
+ const quote = (await call('accelerate_checkout_request_approval', { checkout_id: id, checkout_url: checkoutUrl,
37
+ amount_cents: 100, currency: 'USD', checkout_total_selector: '#checkout-total', submit_selector: '#place-order' })).checkout;
38
+ assert.equal((await call('accelerate_checkout_status', { checkout_id: id })).checkout.status, 'approved');
39
+ assert.equal((await call('accelerate_checkout_fill', { checkout_id: id, revision: quote.revision })).checkout.status, 'ready_for_submit');
40
+ const reviewed = (await call('accelerate_checkout_status', { checkout_id: id })).checkout;
41
+ assert.equal(reviewed.purchase_approved, false);
42
+ const remaining = Date.parse(reviewed.submit_expires_at) - Date.now();
43
+ assert.ok(remaining > 290000 && remaining <= 300000);
44
+ assert.equal((await call('accelerate_checkout_cancel', { checkout_id: id })).checkout.status, 'cancelled');
45
+ complete = true;
46
+ process.stdout.write(JSON.stringify({ ok: true, synthetic_only: true, mcp_protocol: true,
47
+ profile: 'managed_review_v1', verification_deferred: true, private_verification_before_fill: true,
48
+ private_review_receipt_acknowledged: true, approval_window_ms: 300000, purchase_approval_requested: false,
49
+ submit_tool_absent: true, fill_ready: true, cancelled: true, live_checkout_requests: 0 }) + '\n');
50
+ } catch {
51
+ process.stderr.write(JSON.stringify({ ok: false, code: 'synthetic_smoke_failed' }) + '\n');
52
+ } finally {
53
+ clearTimeout(timer); await client.close().catch(() => {}); await transport.close().catch(() => {});
54
+ process.exitCode = complete ? 0 : 1;
55
+ }
@@ -0,0 +1,163 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { AccelerateClient } from '@weaccelerateinc/agent-sdk';
3
+ import { AcceleratePrivateChatClient, deliverPrivateCheckoutReview } from '@weaccelerateinc/agent-sdk/private-chat';
4
+
5
+ const context = Object.freeze({ customer_id: 'synthetic-customer', workspace_id: 'synthetic-workspace',
6
+ conversation_id: 'synthetic-direct-conversation', phone_number: '+15555550123' });
7
+ const origin = 'https://synthetic.accelerate.invalid';
8
+ export const syntheticCheckoutUrl = 'https://demos.weaccelerate.com/normal/payment';
9
+ const invalid = () => { throw Error('Synthetic checkout request rejected.'); };
10
+ const digest = value => createHash('sha256').update(value).digest('base64url');
11
+ const png = () => Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 1, 2, 3]);
12
+
13
+ /** In-process fixtures only: no network, source, browser, raw OTP or security code.
14
+ * Private verification is simulated; safe image delivery uses the real SDK. */
15
+ export function createSyntheticHost({ purpose = 'sandbox_fill_only', deliverReview } = {}) {
16
+ if (!['sandbox_fill_only', 'purchase'].includes(purpose)) invalid();
17
+ const records = new Map(), owners = new Map(), events = new Set(), messages = new Map();
18
+ const calls = { create: 0, browser: 0, payment_requested: 0, approval_requested: 0,
19
+ private_verification: 0, fill: 0, review: 0, review_sent: 0, review_acknowledged: 0,
20
+ purchase_decision: 0, submit: 0, cancel: 0, external: 0 };
21
+ let tail = Promise.resolve();
22
+ const locked = async work => {
23
+ const previous = tail; let release;
24
+ tail = new Promise(resolve => { release = resolve; }); await previous;
25
+ try { return await work(); } finally { release(); }
26
+ };
27
+ const view = record => ({ id: record.id, status: record.status, revision: record.revision,
28
+ expires_at: record.expires_at, purpose, submit_allowed: purpose === 'purchase',
29
+ approval_policy: 'after_fill', purchase_approved: record.purchaseApproved === true,
30
+ retry_safe: false, payment_confirmed: false,
31
+ ...(record.selected ? { card: { brand: 'Visa', last4: '4242' }, address: { label: 'Saved address ***23' } } : {}),
32
+ ...(record.quote ? { quote: record.quote } : {}),
33
+ ...(record.review ? { submit_expires_at: record.review.expires_at } : {}) });
34
+ const privateView = record => ({ checkout: view(record), stage: record.stage, handled: true, duplicate: false,
35
+ cards: [], addresses: [],
36
+ ...(['cvv_required', 'review_delivery_required', 'review_delivered', 'approval_required', 'approved_for_submit'].includes(record.stage)
37
+ ? { approval: { revision: record.revision, ...record.quote } } : {}),
38
+ ...(record.review ? { review: { ...record.review } } : {}) });
39
+ const fetchImpl = async (url, request) => {
40
+ const parsed = new URL(url);
41
+ if (parsed.origin !== origin) { calls.external++; invalid(); }
42
+ const body = request.body ? JSON.parse(request.body) : undefined;
43
+ if (parsed.pathname === '/v1/checkouts' && request.method === 'POST') {
44
+ if (body.interaction_mode !== 'chat' || body.purpose !== purpose || body.approval_policy !== 'after_fill' ||
45
+ Object.keys(context).some(key => body[key] !== context[key]) || body.start_url !== syntheticCheckoutUrl) invalid();
46
+ const id = 'mcs_' + digest(body.idempotency_key);
47
+ if (!records.has(id)) { calls.create++; records.set(id, { id, status: 'wallet_required', stage: 'not_started', revision: 0,
48
+ expires_at: new Date(Date.now() + 900000).toISOString(), selected: false }); }
49
+ return Response.json(view(records.get(id)), { status: 201 });
50
+ }
51
+ const match = /^\/v1\/checkouts\/(mcs_[A-Za-z0-9_-]{43})(?:\/(browser|details|approval|fill|submit|cancel|private-chat\/(?:state|review|review-delivered|reply)))?$/.exec(parsed.pathname);
52
+ if (!match || !records.has(match[1])) invalid();
53
+ const record = records.get(match[1]), action = match[2];
54
+ if (!action && request.method === 'GET') return Response.json(view(record));
55
+ if (request.method !== 'POST') invalid();
56
+ if (action?.startsWith('private-chat/')) {
57
+ if (['customer_id', 'workspace_id', 'conversation_id'].some(key => body[key] !== context[key])) invalid();
58
+ if (action === 'private-chat/state') return Response.json(privateView(record));
59
+ if (action === 'private-chat/review' && record.stage === 'review_delivery_required' && Date.now() < Date.parse(record.review.delivery_expires_at)) {
60
+ calls.review++; record.imageRetrieved = true;
61
+ return new Response(png(), { headers: { 'Content-Type': 'image/png',
62
+ 'X-Accelerate-Review-Id': record.review.id, 'X-Accelerate-Review-Expires-At': record.review.expires_at,
63
+ 'X-Accelerate-Review-Amount-Cents': String(record.quote.amount_cents), 'X-Accelerate-Review-Currency': 'USD',
64
+ 'X-Accelerate-Review-Approval-Window-Ms': '300000', 'X-Accelerate-Review-Delivery-Expires-At': record.review.delivery_expires_at } });
65
+ }
66
+ if (action === 'private-chat/review-delivered') {
67
+ const receipt = messages.get(body.message_id);
68
+ if (!record.imageRetrieved || body.review_id !== record.review?.id || !receipt || receipt.checkout !== record.id ||
69
+ receipt.sent_at !== body.sent_at || Date.parse(body.sent_at) > Date.now() ||
70
+ Date.parse(body.sent_at) >= Date.parse(record.review.delivery_expires_at)) invalid();
71
+ if (!record.review.delivered) { calls.review_acknowledged++; record.review.delivered = true;
72
+ record.review.expires_at = new Date(Date.parse(body.sent_at) + 300000).toISOString();
73
+ record.stage = purpose === 'purchase' ? 'approval_required' : 'review_delivered'; }
74
+ return Response.json(privateView(record));
75
+ }
76
+ if (action === 'private-chat/reply' && purpose === 'purchase' && record.stage === 'approval_required' &&
77
+ record.review?.delivered && typeof body.sent_at === 'string' && Date.parse(body.sent_at) <= Date.now() &&
78
+ Date.parse(body.sent_at) > Date.parse(record.review.expires_at) - 300000 && Date.now() < Date.parse(record.review.expires_at)) {
79
+ if (!['approve', 'decline'].includes(body.text)) invalid();
80
+ calls.purchase_decision++;
81
+ if (body.text === 'approve') { record.purchaseApproved = true; record.stage = 'approved_for_submit'; }
82
+ else { record.status = 'cancelled'; record.stage = 'closed'; delete record.review; }
83
+ return Response.json(privateView(record));
84
+ }
85
+ invalid();
86
+ }
87
+ if (action === 'browser' && ['wallet_required', 'selection_ready', 'details_ready'].includes(record.status)) {
88
+ calls.browser++;
89
+ return Response.json({ content: [{ type: 'text',
90
+ text: 'Synthetic demo checkout\nUSD $1.00\n#checkout-total\n#place-order\n[e1_1] Button Continue' }] });
91
+ }
92
+ if (action === 'details' && record.status === 'selection_ready' && body.checkout_url === syntheticCheckoutUrl) record.status = 'details_ready';
93
+ else if (action === 'approval' && record.status === 'details_ready' && body.checkout_url === syntheticCheckoutUrl &&
94
+ body.amount_cents === 100 && body.currency === 'USD') {
95
+ record.status = 'approval_required'; record.stage = 'cvv_required'; record.revision++;
96
+ record.quote = { checkout_origin: 'https://demos.weaccelerate.com', amount_cents: 100, currency: 'USD' };
97
+ } else if (action === 'fill' && record.status === 'approved' && body.revision === record.revision) {
98
+ calls.fill++; record.status = 'ready_for_submit'; record.stage = 'review_delivery_required';
99
+ record.review = { id: 'rvr_' + digest(record.id + ':' + record.revision), delivered: false, approval_window_ms: 300000,
100
+ expires_at: new Date(Date.now() + 420000).toISOString(), delivery_expires_at: new Date(Date.now() + 60000).toISOString() };
101
+ } else if (action === 'cancel') { calls.cancel++; record.status = 'cancelled'; record.stage = 'closed'; record.purchaseApproved = false; delete record.review; }
102
+ else if (action === 'submit') {
103
+ calls.submit++;
104
+ if (purpose !== 'purchase' || !record.purchaseApproved || record.stage !== 'approved_for_submit' ||
105
+ body.revision !== record.revision || Date.now() >= Date.parse(record.review.expires_at)) return Response.json({ error: { code: 'submit_unavailable' } }, { status: 409 });
106
+ record.status = 'submit_attempted'; record.stage = 'closed'; record.purchaseApproved = false; delete record.review;
107
+ } else invalid();
108
+ return Response.json(view(record));
109
+ };
110
+ const options = { baseUrl: origin, apiKey: 'synthetic_tenant_not_a_real_key'.padEnd(48, 'x'),
111
+ merchantId: 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee', fetchImpl };
112
+ const client = new AccelerateClient(options), privateChat = new AcceleratePrivateChatClient(options);
113
+ const host = { client, privateChat,
114
+ resolveContext: async () => ({ ...context }),
115
+ withOwnedCheckout: async (current, id, work) => locked(async () => {
116
+ const saved = owners.get(id);
117
+ if (!saved || Object.keys(context).some(key => current[key] !== context[key] || saved[key] !== current[key])) invalid();
118
+ return work({ ...saved });
119
+ }),
120
+ withCheckoutCreation: async (current, work) => locked(() => {
121
+ if (Object.keys(context).some(key => current[key] !== context[key])) invalid();
122
+ return work({ idempotency_key: 'synthetic-exact-attempt-1', persistCreated: async saved => {
123
+ if (Object.keys(context).some(key => saved[key] !== context[key])) invalid();
124
+ owners.set(saved.checkout_id, { ...saved });
125
+ } });
126
+ }),
127
+ sendPrivateReview: async (current, message) => {
128
+ if (Object.keys(context).some(key => current[key] !== context[key]) || message.contentType !== 'image/png' ||
129
+ !message.bytes.equals(png()) || message.signal.aborted || Date.now() >= Date.parse(message.delivery_expires_at)) invalid();
130
+ const record = [...records.values()].find(value => value.stage === 'review_delivery_required');
131
+ if (!record || !message.text.includes('1.00 USD') || (purpose === 'sandbox_fill_only'
132
+ ? !message.text.includes('No purchase approval is needed') || message.text.includes('Do you approve')
133
+ : !message.text.includes('5 minutes from this message'))) invalid();
134
+ const message_id = 'synthetic-review-' + digest(message.idempotency_key);
135
+ if (!messages.has(message_id)) { calls.review_sent++; messages.set(message_id, { checkout: record.id, sent_at: new Date(Date.now()).toISOString() }); }
136
+ return { message_id, sent_at: messages.get(message_id).sent_at };
137
+ },
138
+ dispatchCheckoutEvent: async (current, event) => {
139
+ if (Object.keys(context).some(key => current[key] !== context[key]) ||
140
+ event.version !== 'v1' || !['payment_requested', 'approval_requested', 'review_ready'].includes(event.kind) ||
141
+ event.idempotency_key !== 'accelerate-checkout-event:' + createHash('sha256').update(JSON.stringify([
142
+ 'v1', event.kind, event.checkout.id, ['approval_requested', 'review_ready'].includes(event.kind) ? event.checkout.revision : 0])).digest('hex') ||
143
+ !owners.has(event.checkout.id)) invalid();
144
+ if (events.has(event.idempotency_key)) return;
145
+ const record = records.get(event.checkout.id);
146
+ if (event.kind === 'payment_requested' && record.status === 'wallet_required') {
147
+ calls.payment_requested++; record.selected = true; record.status = 'selection_ready'; record.stage = 'preparing_checkout'; record.revision = 1;
148
+ } else if (event.kind === 'approval_requested' && record.stage === 'cvv_required') {
149
+ calls.approval_requested++; calls.private_verification++; record.status = 'approved'; record.stage = 'verified';
150
+ } else if (event.kind === 'review_ready' && record.stage === 'review_delivery_required') {
151
+ if (deliverReview) await deliverReview(current, event);
152
+ else await deliverPrivateCheckoutReview({ client: privateChat, state: privateView(record),
153
+ binding: Object.fromEntries(['customer_id', 'workspace_id', 'conversation_id'].map(key => [key, context[key]])),
154
+ messageId: event.idempotency_key, send: message => host.sendPrivateReview(current, message) });
155
+ } else invalid();
156
+ // Only receipt acknowledgement completes this event; a failure retains
157
+ // this exact event for redelivery, without replaying private filling.
158
+ events.add(event.idempotency_key);
159
+ },
160
+ counters: () => ({ ...calls }),
161
+ };
162
+ return host;
163
+ }