From 3caeda7d0d6a062b49277f5a7aa7d0c2d9697f81 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 03:16:54 +0530 Subject: [PATCH 01/11] Add explicit gating payment error helpers --- .../services/payments/payment-errors.test.ts | 51 +++++++++++++++ src/lib/services/payments/payment-errors.ts | 63 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/lib/services/payments/payment-errors.test.ts create mode 100644 src/lib/services/payments/payment-errors.ts diff --git a/src/lib/services/payments/payment-errors.test.ts b/src/lib/services/payments/payment-errors.test.ts new file mode 100644 index 0000000..ddf0bb5 --- /dev/null +++ b/src/lib/services/payments/payment-errors.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'bun:test'; +import { + PAYMENT_PENDING_ERROR_CODE, + PAYMENT_REQUIRED_ERROR_CODE, + extractExplicitGatingError, + isPaymentPendingError, + isPaymentRequiredError, + serializeExplicitGatingError +} from './payment-errors'; + +describe('explicit gating payment errors', () => { + it('extracts and serializes payment-required errors without changing data', () => { + const data = { + instructions: 'Pay before retrying', + payment_options: [{ pmi: 'bitcoin-lightning-bolt11', amount: 21, pay_req: 'lnbc...' }] + }; + const error = Object.assign(new Error('Payment required'), { + code: PAYMENT_REQUIRED_ERROR_CODE, + data + }); + + const extracted = extractExplicitGatingError(error); + + expect(extracted).toEqual({ + code: PAYMENT_REQUIRED_ERROR_CODE, + message: 'Payment required', + data + }); + expect(extracted?.data).toBe(data); + expect(extracted && isPaymentRequiredError(extracted)).toBe(true); + expect(extracted && serializeExplicitGatingError(extracted)).toBe( + JSON.stringify({ code: PAYMENT_REQUIRED_ERROR_CODE, message: 'Payment required', data }) + ); + }); + + it('extracts payment-pending errors', () => { + const extracted = extractExplicitGatingError({ + code: PAYMENT_PENDING_ERROR_CODE, + message: 'Payment pending', + data: { retry_after: 2 } + }); + + expect(extracted && isPaymentPendingError(extracted)).toBe(true); + expect(extracted?.data).toEqual({ retry_after: 2 }); + }); + + it('ignores unrelated errors', () => { + expect(extractExplicitGatingError(new Error('No payment'))).toBeNull(); + expect(extractExplicitGatingError({ code: -32000, message: 'Generic error' })).toBeNull(); + }); +}); diff --git a/src/lib/services/payments/payment-errors.ts b/src/lib/services/payments/payment-errors.ts new file mode 100644 index 0000000..8877eb5 --- /dev/null +++ b/src/lib/services/payments/payment-errors.ts @@ -0,0 +1,63 @@ +export const PAYMENT_REQUIRED_ERROR_CODE = -32042; +export const PAYMENT_PENDING_ERROR_CODE = -32043; + +export interface PaymentRequiredError { + code: typeof PAYMENT_REQUIRED_ERROR_CODE; + message: string; + data: Record; +} + +export interface PaymentPendingError { + code: typeof PAYMENT_PENDING_ERROR_CODE; + message: string; + data: Record; +} + +export type ExplicitGatingError = PaymentRequiredError | PaymentPendingError; + +type ErrorCandidate = { + code?: unknown; + message?: unknown; + data?: unknown; +}; + +function isErrorCandidate(error: unknown): error is ErrorCandidate { + return typeof error === 'object' && error !== null; +} + +function isExplicitGatingCode( + code: unknown +): code is typeof PAYMENT_REQUIRED_ERROR_CODE | typeof PAYMENT_PENDING_ERROR_CODE { + return code === PAYMENT_REQUIRED_ERROR_CODE || code === PAYMENT_PENDING_ERROR_CODE; +} + +export function extractExplicitGatingError(error: unknown): ExplicitGatingError | null { + if (!isErrorCandidate(error) || !isExplicitGatingCode(error.code)) { + return null; + } + + return { + code: error.code, + message: typeof error.message === 'string' ? error.message : 'Payment required', + data: + typeof error.data === 'object' && error.data !== null + ? (error.data as Record) + : {} + }; +} + +export function serializeExplicitGatingError(error: ExplicitGatingError): string { + return JSON.stringify({ + code: error.code, + message: error.message, + data: error.data + }); +} + +export function isPaymentRequiredError(error: ExplicitGatingError): error is PaymentRequiredError { + return error.code === PAYMENT_REQUIRED_ERROR_CODE; +} + +export function isPaymentPendingError(error: ExplicitGatingError): error is PaymentPendingError { + return error.code === PAYMENT_PENDING_ERROR_CODE; +} From a6874613c2e4650b3b0ee067ee11a63240b769b7 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 03:16:59 +0530 Subject: [PATCH 02/11] Add payment interaction mode negotiation --- src/lib/services/mcpClient.svelte.ts | 69 +++++++++++++++++++++------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/src/lib/services/mcpClient.svelte.ts b/src/lib/services/mcpClient.svelte.ts index f9d9fed..e6144cd 100644 --- a/src/lib/services/mcpClient.svelte.ts +++ b/src/lib/services/mcpClient.svelte.ts @@ -16,14 +16,18 @@ import { } from '@contextvm/mcp-sdk/types.js'; import { SvelteMap } from 'svelte/reactivity'; import { paymentNotificationsService } from '$lib/services/payments/payment-notifications.svelte'; -import { withClientPayments, PMI_BITCOIN_LIGHTNING_BOLT11 } from '@contextvm/sdk'; -import { UiOnlyPaymentHandler } from '$lib/services/payments/ui-payment-handler'; +import { withClientPayments } from '@contextvm/sdk'; import type { Transport } from '@contextvm/mcp-sdk/shared/transport.js'; import type { ProgressCallback } from '@contextvm/mcp-sdk/shared/protocol.js'; import { decodeServerIdentifier } from '$lib/utils'; import type { PaymentAcceptedNotification, PaymentRejectedNotification } from '@contextvm/sdk'; import { z } from 'zod'; +export type PaymentInteractionMode = 'transparent' | 'explicit_gating'; +type PaymentNegotiatingTransport = NostrClientTransport & { + getEffectivePaymentInteraction?: () => PaymentInteractionMode | undefined; +}; + const PaymentAcceptedNotificationSchema = z.object({ jsonrpc: z.literal('2.0').optional(), method: z.literal(PAYMENT_ACCEPTED_METHOD), @@ -58,6 +62,7 @@ export class McpClientService { private clientTransports = new SvelteMap(); private connectionStates = new SvelteMap(); private serverIdentifiers = new SvelteMap(); + private connectionModes = new SvelteMap(); private static readonly clientConfig = { name: 'ContextVM Web Client', version: '1.0.0' @@ -137,8 +142,10 @@ export class McpClientService { paymentNotificationsService.clearServer(this.getServerKey(serverIdentifier)); } - // Helper method to create a new transport for a client - private createTransport(serverIdentifier: string): Transport { + private createTransport( + serverIdentifier: string, + paymentInteraction?: PaymentInteractionMode + ): Transport { // Check if user is logged in const currentAccount = activeAccount.getValue(); if (!currentAccount) { @@ -159,15 +166,9 @@ export class McpClientService { }); this.clientTransports.set(serverKey, baseTransport); - // UI-only payments integration using the SDK middleware. - // - advertises PMIs via `pmi` tags - // - captures payment_required in our handler - // - does not attempt to pay - const uiHandler = new UiOnlyPaymentHandler({ - pmi: PMI_BITCOIN_LIGHTNING_BOLT11, - serverPubkey: preferredIdentifier - }); - return withClientPayments(baseTransport, { handlers: [uiHandler] }); + return withClientPayments(baseTransport, { + ...(paymentInteraction ? { paymentInteraction } : {}) + } as Parameters[1]); } /** @@ -206,6 +207,31 @@ export class McpClientService { ?.getServerPromptsListEvent(); } + public setConnectionMode(serverIdentifier: string, mode: PaymentInteractionMode): void { + this.connectionModes.set(this.getServerKey(serverIdentifier), mode); + } + + public getConnectionMode(serverIdentifier: string): PaymentInteractionMode { + return this.connectionModes.get(this.getServerKey(serverIdentifier)) ?? 'transparent'; + } + + public getEffectivePaymentMode(serverIdentifier: string): PaymentInteractionMode | undefined { + return ( + this.clientTransports.get(this.getServerKey(serverIdentifier)) as + | PaymentNegotiatingTransport + | undefined + )?.getEffectivePaymentInteraction?.(); + } + + public async reconnectWithMode( + serverIdentifier: string, + mode: PaymentInteractionMode + ): Promise { + this.setConnectionMode(serverIdentifier, mode); + await this.disconnect(serverIdentifier, { keepMode: true }); + await this.getClient(serverIdentifier); + } + // Reconnect all existing clients using their preferred server identifier. public async reconnectAllClients(): Promise { for (const [serverPubkey, client] of this.clients) { @@ -218,7 +244,10 @@ export class McpClientService { await client.close(); - const transport = this.createTransport(this.getPreferredServerIdentifier(serverPubkey)); + const transport = this.createTransport( + this.getPreferredServerIdentifier(serverPubkey), + this.connectionModes.get(serverPubkey) + ); await client.connect(transport); @@ -255,11 +284,10 @@ export class McpClientService { error: null }); - const transport = this.createTransport(serverIdentifier); + const transport = this.createTransport(serverIdentifier, this.connectionModes.get(serverKey)); const client = new Client(McpClientService.clientConfig); await client.connect(transport); - // CEP-8 `payment_required` is captured by the payments middleware handler. this.registerPaymentNotificationHandlers(client, serverIdentifier); this.clients.set(serverKey, client); @@ -283,15 +311,21 @@ export class McpClientService { } // Disconnect from a server - async disconnect(serverIdentifier: string): Promise { + async disconnect(serverIdentifier: string, options: { keepMode?: boolean } = {}): Promise { const serverKey = this.getServerKey(serverIdentifier); const client = this.clients.get(serverKey); + if (!client && !options.keepMode) { + this.connectionModes.delete(serverKey); + } if (client) { await client.close(); this.clients.delete(serverKey); this.clientTransports.delete(serverKey); this.clearServerState(serverIdentifier); this.serverIdentifiers.delete(serverKey); + if (!options.keepMode) { + this.connectionModes.delete(serverKey); + } this.setConnectionState(serverKey, { ...McpClientService.defaultConnectionState }); } } @@ -397,6 +431,7 @@ export class McpClientService { this.clientTransports.clear(); this.connectionStates.clear(); this.serverIdentifiers.clear(); + this.connectionModes.clear(); } } From cb32e4a7a318bb90de98b7626c467cbb4f9edfa1 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 03:17:05 +0530 Subject: [PATCH 03/11] Add payment error card --- .../components/chat/PaymentErrorCard.svelte | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/lib/components/chat/PaymentErrorCard.svelte diff --git a/src/lib/components/chat/PaymentErrorCard.svelte b/src/lib/components/chat/PaymentErrorCard.svelte new file mode 100644 index 0000000..6c48e76 --- /dev/null +++ b/src/lib/components/chat/PaymentErrorCard.svelte @@ -0,0 +1,109 @@ + + +
+
+ + {title} + + + {error.code === PAYMENT_REQUIRED_ERROR_CODE + ? PAYMENT_REQUIRED_ERROR_CODE + : PAYMENT_PENDING_ERROR_CODE} + + {#if retryAfter} + + Retry after {retryAfter}s + + {/if} +
+ + {#if instructions} +

{instructions}

+ {:else if error.message} +

{error.message}

+ {/if} + + {#if firstOption} +
+
+ {#if amount !== undefined} + Amount + {amount} + {/if} + {#if pmi} + PMI + {pmi} + {/if} +
+ + {#if payReq} + + + Payment Request + QR + + + +
+
{payReq}
+ +
+
+ + +
+ +
+
+
+ {/if} +
+ {/if} +
From 31aac0942ae780ad4c0a0d21aa5715237404b162 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 03:25:39 +0530 Subject: [PATCH 04/11] Agent orchestrator and chat UI integration --- src/lib/components/chat/ToolCallCard.svelte | 26 +++++++++++++++------ src/lib/services/agent-orchestrator.ts | 26 ++++++++++++++++++--- src/lib/types/chat-types.ts | 12 +++++++++- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/lib/components/chat/ToolCallCard.svelte b/src/lib/components/chat/ToolCallCard.svelte index 9f5aebc..2991447 100644 --- a/src/lib/components/chat/ToolCallCard.svelte +++ b/src/lib/components/chat/ToolCallCard.svelte @@ -1,5 +1,6 @@ @@ -295,7 +326,29 @@ {/if} -
+
+
+
+

Payment mode

+

+ {paymentModeCaption} +

+
+
+ + {explicitGatingEnabled ? 'Explicit gating' : 'Transparent'} + + +
+
{#if connectionState.connected}
Date: Fri, 3 Jul 2026 13:08:05 +0530 Subject: [PATCH 06/11] Render all payment options in PaymentErrorCard --- .../components/chat/PaymentErrorCard.svelte | 86 ++++++++++--------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/lib/components/chat/PaymentErrorCard.svelte b/src/lib/components/chat/PaymentErrorCard.svelte index 6c48e76..f2b33d4 100644 --- a/src/lib/components/chat/PaymentErrorCard.svelte +++ b/src/lib/components/chat/PaymentErrorCard.svelte @@ -21,10 +21,6 @@ (option): option is Record => typeof option === 'object' && option !== null ); }); - const firstOption = $derived(paymentOptions[0] ?? null); - const payReq = $derived(typeof firstOption?.pay_req === 'string' ? firstOption.pay_req : null); - const amount = $derived(firstOption?.amount); - const pmi = $derived(typeof firstOption?.pmi === 'string' ? firstOption.pmi : null); const instructions = $derived( typeof error.data.instructions === 'string' ? error.data.instructions : null ); @@ -63,47 +59,55 @@

{error.message}

{/if} - {#if firstOption} + {#if paymentOptions.length > 0}
-
- {#if amount !== undefined} - Amount - {amount} - {/if} - {#if pmi} - PMI - {pmi} - {/if} -
+ {#each paymentOptions as option, i (i)} + {@const amount = option.amount} + {@const pmi = typeof option.pmi === 'string' ? option.pmi : null} + {@const payReq = typeof option.pay_req === 'string' ? option.pay_req : null} - {#if payReq} - - - Payment Request - QR - +
0 ? 'mt-3 border-t border-border/50 pt-3' : ''}> +
+ {#if amount !== undefined} + Amount + {amount} + {/if} + {#if pmi} + PMI + {pmi} + {/if} +
- -
-
{payReq}
- -
-
+ {#if payReq} + + + Payment Request + QR + - -
- -
-
-
- {/if} + +
+
{payReq}
+ +
+
+ + +
+ +
+
+ + {/if} +
+ {/each}
{/if}
From 9d69d1aaac39c831521a73db9a3cee6ef524c4e8 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 13:08:05 +0530 Subject: [PATCH 07/11] Warn when server rejects explicit_gating --- src/routes/s/[pubkey]/+page.svelte | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/s/[pubkey]/+page.svelte b/src/routes/s/[pubkey]/+page.svelte index 6b5e66d..5429c92 100644 --- a/src/routes/s/[pubkey]/+page.svelte +++ b/src/routes/s/[pubkey]/+page.svelte @@ -176,6 +176,11 @@ : requestedPaymentMode ); const explicitGatingEnabled = $derived(requestedPaymentMode === 'explicit_gating'); + const modeMismatch = $derived( + connectionState.connected && + requestedPaymentMode === 'explicit_gating' && + effectivePaymentMode !== 'explicit_gating' + ); let paymentModeChanging = $state(false); // Server capabilities data @@ -335,6 +340,11 @@

{paymentModeCaption}

+ {#if modeMismatch} +

+ Server does not support explicit gating — fell back to transparent. +

+ {/if}
From 419fe94f1eab3b60ae55669199457871e4f6bf13 Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 13:08:05 +0530 Subject: [PATCH 08/11] Add orchestrator payment error unit tests --- src/lib/services/agent-orchestrator.test.ts | 236 ++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 src/lib/services/agent-orchestrator.test.ts diff --git a/src/lib/services/agent-orchestrator.test.ts b/src/lib/services/agent-orchestrator.test.ts new file mode 100644 index 0000000..331b090 --- /dev/null +++ b/src/lib/services/agent-orchestrator.test.ts @@ -0,0 +1,236 @@ +import { describe, expect, it, mock } from 'bun:test'; +import { AgentOrchestrator } from './agent-orchestrator'; +import { + PAYMENT_PENDING_ERROR_CODE, + PAYMENT_REQUIRED_ERROR_CODE +} from './payments/payment-errors'; +import type { LLMService, SendMessageResult } from './llm'; +import type { McpClientService } from './mcpClient.svelte'; +import type { ChatMessage } from '$lib/types/chat-types'; + +describe('AgentOrchestrator explicit gating', () => { + it('handles -32042 payment_required by setting status and serializing losslessly', async () => { + const mockData = { + instructions: 'Pay to continue', + payment_options: [{ pmi: 'bitcoin-lightning-bolt11', amount: 21, pay_req: 'lnbc...' }] + }; + const mockError = Object.assign(new Error('Payment Required'), { + code: PAYMENT_REQUIRED_ERROR_CODE, + data: mockData + }); + + const mcpClientService = { + clients: new Map([['test_server', { + getServerVersion: () => ({ name: 'test', version: '1' }), + listTools: async () => ({ tools: [{ name: 'test_tool', inputSchema: { type: 'object', properties: {} } }] }) + }]]), + listTools: mock(async () => ({ + tools: [ + { + name: 'test_tool', + inputSchema: { type: 'object', properties: {} } + } + ] + })), + callTool: mock(async () => { + throw mockError; + }) + } as unknown as McpClientService; + + const llmService: LLMService = { + sendMessage: mock(async (messages: ChatMessage[]) => { + // If we have a tool result, return stop + const hasToolResult = messages.some((m) => m.role === 'tool'); + if (hasToolResult) { + return { content: 'Acknowledged', finishReason: 'stop' } as SendMessageResult; + } + + return { + content: '', + finishReason: 'tool_calls', + model: 'test', + toolCalls: [ + { + id: 'call_1', + functionName: 'test_test_tool', + arguments: '{}' + } + ] + } as SendMessageResult; + }) + } as unknown as LLMService; + + const orchestrator = new AgentOrchestrator({ llmService, mcpClientService }); + + const initialMessages: ChatMessage[] = [{ id: 'msg1', role: 'user', content: 'Do it', timestamp: new Date() }]; + + const runPromise = orchestrator.run({ + messages: initialMessages, + signal: new AbortController().signal + }); + + await new Promise(resolve => setTimeout(resolve, 10)); + orchestrator.approveToolCall('call_1'); + + await runPromise; + + // We should have: User -> Assistant (tool_calls) -> Tool (payment_required result) -> Assistant (Acknowledged) + expect(initialMessages.length).toBe(4); + + const assistantMessage = initialMessages[1]; + expect(assistantMessage.role).toBe('assistant'); + expect(assistantMessage.toolCalls).toBeDefined(); + + const toolCall = assistantMessage.toolCalls![0]; + expect(toolCall.status).toBe('payment_required'); + expect(toolCall.paymentError).toEqual({ + code: PAYMENT_REQUIRED_ERROR_CODE, + message: 'Payment Required', + data: mockData + }); + + const toolMessage = initialMessages[2]; + expect(toolMessage.role).toBe('tool'); + expect(toolMessage.content).toBe( + JSON.stringify({ + code: PAYMENT_REQUIRED_ERROR_CODE, + message: 'Payment Required', + data: mockData + }) + ); + }); + + it('handles -32043 payment_pending by setting status and serializing losslessly', async () => { + const mockData = { retry_after: 2 }; + const mockError = Object.assign(new Error('Payment Pending'), { + code: PAYMENT_PENDING_ERROR_CODE, + data: mockData + }); + + const mcpClientService = { + clients: new Map([['test_server', { + getServerVersion: () => ({ name: 'test', version: '1' }), + listTools: async () => ({ tools: [{ name: 'test_tool', inputSchema: { type: 'object', properties: {} } }] }) + }]]), + listTools: mock(async () => ({ + tools: [ + { + name: 'test_tool', + inputSchema: { type: 'object', properties: {} } + } + ] + })), + callTool: mock(async () => { + throw mockError; + }) + } as unknown as McpClientService; + + const llmService: LLMService = { + sendMessage: mock(async (messages: ChatMessage[]) => { + const hasToolResult = messages.some((m) => m.role === 'tool'); + if (hasToolResult) { + return { content: 'Acknowledged', finishReason: 'stop' } as SendMessageResult; + } + + return { + content: '', + finishReason: 'tool_calls', + model: 'test', + toolCalls: [ + { + id: 'call_1', + functionName: 'test_test_tool', + arguments: '{}' + } + ] + } as SendMessageResult; + }) + } as unknown as LLMService; + + const orchestrator = new AgentOrchestrator({ llmService, mcpClientService }); + const initialMessages: ChatMessage[] = [{ id: 'msg1', role: 'user', content: 'Do it', timestamp: new Date() }]; + + const runPromise = orchestrator.run({ + messages: initialMessages, + signal: new AbortController().signal + }); + + await new Promise(resolve => setTimeout(resolve, 10)); + orchestrator.approveToolCall('call_1'); + + await runPromise; + + const assistantMessage = initialMessages[1]; + const toolCall = assistantMessage.toolCalls![0]; + + expect(toolCall.status).toBe('payment_required'); + expect(toolCall.paymentError).toEqual({ + code: PAYMENT_PENDING_ERROR_CODE, + message: 'Payment Pending', + data: mockData + }); + }); + + it('leaves status as error for non-payment errors', async () => { + const mockError = new Error('Network timeout'); + + const mcpClientService = { + clients: new Map([['test_server', { + getServerVersion: () => ({ name: 'test', version: '1' }), + listTools: async () => ({ tools: [{ name: 'test_tool', inputSchema: { type: 'object', properties: {} } }] }) + }]]), + listTools: mock(async () => ({ + tools: [ + { + name: 'test_tool', + inputSchema: { type: 'object', properties: {} } + } + ] + })), + callTool: mock(async () => { + throw mockError; + }) + } as unknown as McpClientService; + + const llmService: LLMService = { + sendMessage: mock(async (messages: ChatMessage[]) => { + const hasToolResult = messages.some((m) => m.role === 'tool'); + if (hasToolResult) { + return { content: 'Acknowledged', finishReason: 'stop' } as SendMessageResult; + } + + return { + content: '', + finishReason: 'tool_calls', + model: 'test', + toolCalls: [ + { + id: 'call_1', + functionName: 'test_test_tool', + arguments: '{}' + } + ] + } as SendMessageResult; + }) + } as unknown as LLMService; + + const orchestrator = new AgentOrchestrator({ llmService, mcpClientService }); + const initialMessages: ChatMessage[] = [{ id: 'msg1', role: 'user', content: 'Do it', timestamp: new Date() }]; + + const runPromise = orchestrator.run({ + messages: initialMessages, + signal: new AbortController().signal + }); + + await new Promise(resolve => setTimeout(resolve, 10)); + orchestrator.approveToolCall('call_1'); + + await runPromise; + + const assistantMessage = initialMessages[1]; + const toolCall = assistantMessage.toolCalls![0]; + + expect(toolCall.status).toBe('error'); + expect(toolCall.paymentError).toBeUndefined(); + }); +}); From 2411295c1c6575af92f2ad81d413b8fc7b4f536e Mon Sep 17 00:00:00 2001 From: Khushvendra Date: Fri, 3 Jul 2026 13:31:45 +0530 Subject: [PATCH 09/11] chore: address copilot PR review feedback - mcpClient: Add transparent-mode payment_required notification handler - PaymentErrorCard: Fix retryAfter truthiness check and literal tab IDs - agent-orchestrator.test: Replace hardcoded delays with polling --- .../components/chat/PaymentErrorCard.svelte | 12 ++++----- src/lib/services/agent-orchestrator.test.ts | 15 ++++++++--- src/lib/services/mcpClient.svelte.ts | 27 +++++++++++++++++-- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/lib/components/chat/PaymentErrorCard.svelte b/src/lib/components/chat/PaymentErrorCard.svelte index f2b33d4..845c1a7 100644 --- a/src/lib/components/chat/PaymentErrorCard.svelte +++ b/src/lib/components/chat/PaymentErrorCard.svelte @@ -46,7 +46,7 @@ ? PAYMENT_REQUIRED_ERROR_CODE : PAYMENT_PENDING_ERROR_CODE} - {#if retryAfter} + {#if retryAfter !== null} Retry after {retryAfter}s @@ -79,13 +79,13 @@
{#if payReq} - + - Payment Request - QR + Payment Request + QR - +
{payReq}