@8wave/ai-elements 0.115.0-beta.23 → 0.115.0-beta.30
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.
- package/dist/_chunks/{PkStreamingMarkdown-BKxA6Ntl.js → PkStreamingMarkdown-B7SRIqnV.js} +2 -2
- package/dist/_chunks/{PkStreamingMarkdown-BKxA6Ntl.js.map → PkStreamingMarkdown-B7SRIqnV.js.map} +1 -1
- package/dist/_chunks/{PkToolRequestOAuthConnection-CgQePKKb.js → PkToolRequestOAuthConnection-CbZn1f14.js} +31 -25
- package/dist/_chunks/PkToolRequestOAuthConnection-CbZn1f14.js.map +1 -0
- package/dist/_chunks/{PkToolShowArtifact-CHxeuKEU.js → PkToolShowArtifact-CRQySuGC.js} +2 -2
- package/dist/_chunks/{PkToolShowArtifact-CHxeuKEU.js.map → PkToolShowArtifact-CRQySuGC.js.map} +1 -1
- package/dist/_chunks/{PkToolShowCalendarEvent-D0jVbqWt.js → PkToolShowCalendarEvent-Cj7yRj-0.js} +2 -2
- package/dist/_chunks/{PkToolShowCalendarEvent-D0jVbqWt.js.map → PkToolShowCalendarEvent-Cj7yRj-0.js.map} +1 -1
- package/dist/_chunks/{PkToolShowContactForm-DqVLVwqJ.js → PkToolShowContactForm-DGmzPa3d.js} +2 -2
- package/dist/_chunks/{PkToolShowContactForm-DqVLVwqJ.js.map → PkToolShowContactForm-DGmzPa3d.js.map} +1 -1
- package/dist/_chunks/{PkToolShowForm-YJQPYfEe.js → PkToolShowForm-76LQdZHH.js} +2 -2
- package/dist/_chunks/{PkToolShowForm-YJQPYfEe.js.map → PkToolShowForm-76LQdZHH.js.map} +1 -1
- package/dist/_chunks/{PkToolShowImageGallery-1mq-_cnp.js → PkToolShowImageGallery-BmpcrdIL.js} +2 -2
- package/dist/_chunks/{PkToolShowImageGallery-1mq-_cnp.js.map → PkToolShowImageGallery-BmpcrdIL.js.map} +1 -1
- package/dist/_chunks/{PkToolShowSources-ClqmBUMB.js → PkToolShowSources-13IBQve7.js} +3 -3
- package/dist/_chunks/{PkToolShowSources-ClqmBUMB.js.map → PkToolShowSources-13IBQve7.js.map} +1 -1
- package/dist/_chunks/{PkToolShowWebPages-BgXlRdmH.js → PkToolShowWebPages-CCSRAlz5.js} +2 -2
- package/dist/_chunks/{PkToolShowWebPages-BgXlRdmH.js.map → PkToolShowWebPages-CCSRAlz5.js.map} +1 -1
- package/dist/_chunks/{markdown-BAq2EAVh.js → markdown-tS_2A0k9.js} +5 -2
- package/dist/_chunks/{markdown-BAq2EAVh.js.map → markdown-tS_2A0k9.js.map} +1 -1
- package/dist/_chunks/{url-validation-uLMZ5-AU.js → url-validation-9DstIfq2.js} +9 -2
- package/dist/_chunks/{url-validation-uLMZ5-AU.js.map → url-validation-9DstIfq2.js.map} +1 -1
- package/dist/ai-elements.es.js +23 -13
- package/dist/ai-elements.es.js.map +1 -1
- package/dist-vue/PkChatbot.js +1 -1
- package/dist-vue/PkChatbotEmptyState.js +1 -1
- package/dist-vue/PkChatbotInput.js +1 -1
- package/dist-vue/PkChatbotMessages.js +1 -1
- package/dist-vue/PkChatbotSteps.js +1 -1
- package/dist-vue/PkChatbotViewChat.js +1 -1
- package/dist-vue/PkChatbotViewConversations.js +1 -1
- package/dist-vue/PkChatbotViewFiles.js +1 -1
- package/dist-vue/PkChatbotViewProfile.js +1 -1
- package/dist-vue/_chunks/{Document-CRiJBpnk.js → Document-BmfGStvM.js} +5 -2
- package/dist-vue/_chunks/{Document-CRiJBpnk.js.map → Document-BmfGStvM.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbot-_7hJo-g0.js → PkChatbot-DKqKEsUn.js} +7 -7
- package/dist-vue/_chunks/{PkChatbot-_7hJo-g0.js.map → PkChatbot-DKqKEsUn.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotEmptyState-Dicjr_Fv.js → PkChatbotEmptyState-BPPP0-0U.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotEmptyState-Dicjr_Fv.js.map → PkChatbotEmptyState-BPPP0-0U.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotInput-DcLmOqA1.js → PkChatbotInput-BHcxLnWU.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotInput-DcLmOqA1.js.map → PkChatbotInput-BHcxLnWU.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotMessages-ClPdP7sw.js → PkChatbotMessages-CaHFzdIC.js} +16 -16
- package/dist-vue/_chunks/{PkChatbotMessages-ClPdP7sw.js.map → PkChatbotMessages-CaHFzdIC.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotSteps-CPPjwTJN.js → PkChatbotSteps-CXiFF3Z-.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotSteps-CPPjwTJN.js.map → PkChatbotSteps-CXiFF3Z-.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewChat-BBYfIQBR.js → PkChatbotViewChat-D5HtjK3u.js} +8 -6
- package/dist-vue/_chunks/{PkChatbotViewChat-BBYfIQBR.js.map → PkChatbotViewChat-D5HtjK3u.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewConversations-DvmRjVND.js → PkChatbotViewConversations-C5O-IocA.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewConversations-DvmRjVND.js.map → PkChatbotViewConversations-C5O-IocA.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewFiles-D7KcAEiB.js → PkChatbotViewFiles-W8DDmtTi.js} +3 -3
- package/dist-vue/_chunks/{PkChatbotViewFiles-D7KcAEiB.js.map → PkChatbotViewFiles-W8DDmtTi.js.map} +1 -1
- package/dist-vue/_chunks/{PkChatbotViewProfile-C1eLHU7x.js → PkChatbotViewProfile--upofqcF.js} +2 -2
- package/dist-vue/_chunks/{PkChatbotViewProfile-C1eLHU7x.js.map → PkChatbotViewProfile--upofqcF.js.map} +1 -1
- package/dist-vue/_chunks/{PkStreamingMarkdown-DYy79C1P.js → PkStreamingMarkdown-it3X7T75.js} +2 -2
- package/dist-vue/_chunks/{PkStreamingMarkdown-DYy79C1P.js.map → PkStreamingMarkdown-it3X7T75.js.map} +1 -1
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-BiaSF6R_.js +124 -0
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-BiaSF6R_.js.map +1 -0
- package/dist-vue/_chunks/{PkToolShowArtifact-DUOnFdfa.js → PkToolShowArtifact-BFyFYjHI.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowArtifact-DUOnFdfa.js.map → PkToolShowArtifact-BFyFYjHI.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowCalendarEvent-BsBdsbV0.js → PkToolShowCalendarEvent-yttlTHiX.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowCalendarEvent-BsBdsbV0.js.map → PkToolShowCalendarEvent-yttlTHiX.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowContactForm-1TFKvG_s.js → PkToolShowContactForm-YpaC_31s.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowContactForm-1TFKvG_s.js.map → PkToolShowContactForm-YpaC_31s.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowDiagram-Co3GRItt.js → PkToolShowDiagram-Bdxjd0BJ.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowDiagram-Co3GRItt.js.map → PkToolShowDiagram-Bdxjd0BJ.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowEmail-BDsBoETQ.js → PkToolShowEmail-CNkFP7qC.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowEmail-BDsBoETQ.js.map → PkToolShowEmail-CNkFP7qC.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowForm-DwFKxMJd.js → PkToolShowForm-FKJNKXPq.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowForm-DwFKxMJd.js.map → PkToolShowForm-FKJNKXPq.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowImageGallery-DudQzBx4.js → PkToolShowImageGallery-DmhZLnBk.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowImageGallery-DudQzBx4.js.map → PkToolShowImageGallery-DmhZLnBk.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowMessage-Dau3agUy.js → PkToolShowMessage-C9xFAi1s.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowMessage-Dau3agUy.js.map → PkToolShowMessage-C9xFAi1s.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowProductList-CsupMsuy.js → PkToolShowProductList-k1ckf1Ko.js} +2 -2
- package/dist-vue/_chunks/{PkToolShowProductList-CsupMsuy.js.map → PkToolShowProductList-k1ckf1Ko.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowSources-CIccPA8Z.js → PkToolShowSources-DE0RO8z9.js} +4 -4
- package/dist-vue/_chunks/{PkToolShowSources-CIccPA8Z.js.map → PkToolShowSources-DE0RO8z9.js.map} +1 -1
- package/dist-vue/_chunks/{PkToolShowWebPages-D4E3Xzg1.js → PkToolShowWebPages-v-6DGCNM.js} +3 -3
- package/dist-vue/_chunks/{PkToolShowWebPages-D4E3Xzg1.js.map → PkToolShowWebPages-v-6DGCNM.js.map} +1 -1
- package/dist-vue/_chunks/{PkUrl-Cu3qKl4G.js → PkUrl-C5UXwNxy.js} +2 -2
- package/dist-vue/_chunks/{PkUrl-Cu3qKl4G.js.map → PkUrl-C5UXwNxy.js.map} +1 -1
- package/dist-vue/_chunks/{createChatbotApiClient-BJz36z7v.js → createChatbotApiClient-L7wpQUUA.js} +2 -2
- package/dist-vue/_chunks/{createChatbotApiClient-BJz36z7v.js.map → createChatbotApiClient-L7wpQUUA.js.map} +1 -1
- package/dist-vue/_chunks/mcpOAuthReport-DG84H11Y.js +9 -0
- package/dist-vue/_chunks/mcpOAuthReport-DG84H11Y.js.map +1 -0
- package/dist-vue/_chunks/{url-validation-uLMZ5-AU.js → url-validation-9DstIfq2.js} +9 -2
- package/dist-vue/_chunks/{url-validation-uLMZ5-AU.js.map → url-validation-9DstIfq2.js.map} +1 -1
- package/dist-vue/_chunks/{useChatbotStore-D3Q1ZgQg.js → useChatbotStore-Ctr5gDIr.js} +2 -2
- package/dist-vue/_chunks/{useChatbotStore-D3Q1ZgQg.js.map → useChatbotStore-Ctr5gDIr.js.map} +1 -1
- package/dist-vue/api.js +1 -1
- package/dist-vue/composables.js +10 -2
- package/dist-vue/composables.js.map +1 -1
- package/dist-vue/index.js +2973 -2972
- package/dist-vue/index.js.map +1 -1
- package/dist-vue/packages/auth/src/social-redirect-plugin.d.ts +13 -1
- package/dist-vue/packages/components/src/chat/PkToolRequestOAuthConnection.d.ts +5 -0
- package/dist-vue/packages/components/src/chat/index.d.ts +1 -0
- package/dist-vue/packages/components/src/chat/mcpOAuthReport.d.ts +11 -0
- package/dist-vue/packages/models/src/schema/Document.d.ts +1 -0
- package/dist-vue/packages/models/src/schema/McpServer.d.ts +0 -3
- package/dist-vue/packages/models/src/schema/Organization.d.ts +23 -6
- package/package.json +1 -1
- package/dist/_chunks/PkToolRequestOAuthConnection-CgQePKKb.js.map +0 -1
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-a2LegFoE.js +0 -122
- package/dist-vue/_chunks/PkToolRequestOAuthConnection-a2LegFoE.js.map +0 -1
|
@@ -7,6 +7,17 @@ import { BetterAuthPlugin } from 'better-auth';
|
|
|
7
7
|
* whose host it refuses cannot run anyway.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isListedOrigin(value: string, trusted: string[]): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The provider's authorization URL with its account chooser asked for.
|
|
12
|
+
*
|
|
13
|
+
* The widget's popup hands the session it creates to the page that opened it,
|
|
14
|
+
* a page listed by an agent and so chosen by a customer. Without a prompt, a
|
|
15
|
+
* visitor already signed in at the provider who had consented once went
|
|
16
|
+
* through without a single screen: one click anywhere on such a page was
|
|
17
|
+
* enough for it to receive their session. The chooser makes signing in
|
|
18
|
+
* something the visitor does. An existing prompt is kept, except `none`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withAccountChooser(location: string): string;
|
|
10
21
|
/**
|
|
11
22
|
* Backs the social sign-in that runs in a browser window on the backend origin.
|
|
12
23
|
*
|
|
@@ -23,6 +34,7 @@ export declare function isListedOrigin(value: string, trusted: string[]): boolea
|
|
|
23
34
|
* callback hands back the token of the session it just created, and only to the
|
|
24
35
|
* origin that started it.
|
|
25
36
|
*
|
|
26
|
-
* The before hook narrows the origin `oauthPopup` accepts to the listed ones
|
|
37
|
+
* The before hook narrows the origin `oauthPopup` accepts to the listed ones,
|
|
38
|
+
* the after hook makes the provider show its account chooser.
|
|
27
39
|
*/
|
|
28
40
|
export declare function socialRedirect(): BetterAuthPlugin;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { DefineComponent, ComponentOptionsMixin, PublicProps, ComponentProvideOptions } from 'vue';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
part: unknown;
|
|
4
|
+
/**
|
|
5
|
+
* The backend the chat talks to, where the sign-in's callback page
|
|
6
|
+
* runs. Without it the card hears no report and relies on its poll.
|
|
7
|
+
*/
|
|
8
|
+
backendUrl?: string;
|
|
4
9
|
resolveConnection?: (serverName: string) => Promise<{
|
|
5
10
|
mcpServerId: string;
|
|
6
11
|
authorizeUrl: string;
|
|
@@ -53,4 +53,5 @@ import { default as PkVoiceSession } from './PkVoiceSession';
|
|
|
53
53
|
import { default as PkVoiceOrb } from './PkVoiceOrb';
|
|
54
54
|
import { default as PkChatButton } from './PkChatButton';
|
|
55
55
|
export { MODEL_CATALOG_KEY, provideModelCatalog, useModelCatalogEntry, } from './modelCatalog';
|
|
56
|
+
export { isMcpOAuthReport } from './mcpOAuthReport';
|
|
56
57
|
export { PkAgentSelectorInline, PkAgentSettingsPanel, PkAuroraCanvas, PkAvatar, PkChatSidebar, PkChatSidebarConversationItem, PkChatbot, PkChatbotEmptyState, PkChatbotError, PkChatbotFeedbackForm, PkChatbotFilePreview, PkChatbotInput, PkChatbotMessages, PkChatbotSteps, PkChatbotStepToolDetail, PkChatbotStepPayload, PkChatbotViewChat, PkChunkPreview, PkChatbotViewConversations, PkChatbotViewProfile, PkCode, PkCopyBlock, PkDocumentHeader, PkLanguageModel, PkLanguageModelCombobox, PkModelOption, PkMessageParts, PkMessageDetail, PkStreamingMarkdown, PkStreamingMarkdownAutoscroll, PkToolShowForm, PkToolShowArtifact, PkToolApprovalBadge, PkToolShowEmail, PkToolShowMessage, PkToolShowWebPages, PkToolShowProductList, PkToolShowContactForm, PkToolShowCalendarEvent, PkToolShowComparison, PkToolShowLocation, PkToolShowQrCode, PkToolShowImageGallery, PkToolShowSuggestedReply, PkToolShowSources, PkRetryCountdown, PkReasoningChat, PkReasoningSettings, PkWebSearchSettings, PkWelcomePopup, PkChatbotAuth, PkVoiceSession, PkVoiceOrb, PkChatButton, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The `type` the MCP sign-in callback page posts (mcpOAuth.controller.ts). */
|
|
2
|
+
export declare const MCP_OAUTH_REPORT_TYPE = "mcp-oauth-complete";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a `message` event is the report of the MCP sign-in a page opened:
|
|
5
|
+
* posted by that window while it shows the callback page, which runs on the
|
|
6
|
+
* backend's origin. Any other window holding the page could post the same
|
|
7
|
+
* message, success included, and so could the provider's own pages, which run
|
|
8
|
+
* in that same window before the callback. Without the window or the backend
|
|
9
|
+
* URL nothing is a report, and the caller relies on reading the connection.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isMcpOAuthReport(event: MessageEvent, signInWindow: Window | null | undefined, backendUrl: string | undefined): boolean;
|
|
@@ -1748,6 +1748,7 @@ export declare const DocumentRestoreResultSchema: z.ZodObject<{
|
|
|
1748
1748
|
}, z.core.$strip>;
|
|
1749
1749
|
export declare const DocumentTrashSummarySchema: z.ZodObject<{
|
|
1750
1750
|
count: z.ZodNumber;
|
|
1751
|
+
retentionDays: z.ZodNumber;
|
|
1751
1752
|
}, z.core.$strip>;
|
|
1752
1753
|
export declare const DocumentEmptyTrashDtoSchema: z.ZodObject<{
|
|
1753
1754
|
agentId: z.ZodUUID;
|
|
@@ -88,9 +88,6 @@ export declare const McpServerCreateSchema: z.ZodObject<{
|
|
|
88
88
|
organizationId: z.ZodString;
|
|
89
89
|
}, z.core.$strip>;
|
|
90
90
|
export declare const McpServerUpdateSchema: z.ZodObject<{
|
|
91
|
-
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
92
|
-
deletedAt: z.ZodOptional<z.ZodNullable<z.ZodCoercedDate<unknown>>>;
|
|
93
|
-
deletedBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
94
91
|
authType: z.ZodDefault<z.ZodEnum<{
|
|
95
92
|
headers: "headers";
|
|
96
93
|
oauth: "oauth";
|
|
@@ -209,6 +209,13 @@ export declare const OrganizationDtoSchema: z.ZodObject<{
|
|
|
209
209
|
metadata: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
210
210
|
}, z.core.$strip>;
|
|
211
211
|
export type OrganizationDto = z.infer<typeof OrganizationDtoSchema>;
|
|
212
|
+
/**
|
|
213
|
+
* The longest trash retention, a hundred years. Past it a date computed from
|
|
214
|
+
* the window stops being one Postgres accepts, and nobody means it anyway.
|
|
215
|
+
* The other two windows have no such bound: values they already store keep
|
|
216
|
+
* meaning what they meant.
|
|
217
|
+
*/
|
|
218
|
+
export declare const TRASH_RETENTION_MAX_DAYS = 36500;
|
|
212
219
|
/**
|
|
213
220
|
* Per-organization GDPR retention policy, persisted (as JSON) inside the
|
|
214
221
|
* `organization.metadata` text column. Each field overrides the matching
|
|
@@ -218,8 +225,9 @@ export type OrganizationDto = z.infer<typeof OrganizationDtoSchema>;
|
|
|
218
225
|
* - `> 0` → number of days before the pipeline acts.
|
|
219
226
|
*/
|
|
220
227
|
export declare const OrganizationRetentionPolicySchema: z.ZodObject<{
|
|
221
|
-
chatRetentionDays: z.ZodOptional<z.ZodNullable<z.ZodNumber
|
|
222
|
-
anonymousRetentionDays: z.ZodOptional<z.ZodNullable<z.ZodNumber
|
|
228
|
+
chatRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
229
|
+
anonymousRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
230
|
+
trashRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
223
231
|
}, z.core.$strip>;
|
|
224
232
|
export type OrganizationRetentionPolicy = z.infer<typeof OrganizationRetentionPolicySchema>;
|
|
225
233
|
/**
|
|
@@ -359,8 +367,9 @@ export declare enum ModelDefaultIssue {
|
|
|
359
367
|
}
|
|
360
368
|
export declare const OrganizationMetadataSchema: z.ZodObject<{
|
|
361
369
|
retention: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
362
|
-
chatRetentionDays: z.ZodOptional<z.ZodNullable<z.ZodNumber
|
|
363
|
-
anonymousRetentionDays: z.ZodOptional<z.ZodNullable<z.ZodNumber
|
|
370
|
+
chatRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
371
|
+
anonymousRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
372
|
+
trashRetentionDays: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
|
|
364
373
|
}, z.core.$strip>>>;
|
|
365
374
|
features: z.ZodCatch<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
366
375
|
models: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
@@ -375,12 +384,20 @@ export declare const OrganizationMetadataSchema: z.ZodObject<{
|
|
|
375
384
|
export type OrganizationMetadata = z.infer<typeof OrganizationMetadataSchema>;
|
|
376
385
|
/**
|
|
377
386
|
* Canonical fallback retention windows (in days) applied when an organization
|
|
378
|
-
* does not override them in `metadata.retention`. The retention job lets
|
|
379
|
-
* be overridden per environment via
|
|
387
|
+
* does not override them in `metadata.retention`. The retention job lets the
|
|
388
|
+
* chat and anonymous ones be overridden per environment via
|
|
389
|
+
* `GDPR_*_RETENTION_DAYS` (the trash one has no such variable), but absent any
|
|
380
390
|
* override these are the effective system default — and what the admin UI shows
|
|
381
391
|
* as the inherited value. `0` means the automation is disabled by default.
|
|
382
392
|
*/
|
|
383
393
|
export declare const GDPR_RETENTION_DEFAULTS: Record<keyof OrganizationRetentionPolicy, number>;
|
|
394
|
+
/**
|
|
395
|
+
* How many days a deleted document of the organization stays in the trash
|
|
396
|
+
* before it is destroyed for good, `0` when nothing destroys it: its own
|
|
397
|
+
* setting, else `GDPR_RETENTION_DEFAULTS`. One rule for the timer that
|
|
398
|
+
* destroys and for the trash that shows how long is left.
|
|
399
|
+
*/
|
|
400
|
+
export declare function trashRetentionDaysOf(metadata: string | Record<string, unknown> | null | undefined): number;
|
|
384
401
|
/**
|
|
385
402
|
* Safely parse the raw `organization.metadata` value (a JSON string from our
|
|
386
403
|
* own drizzle reads, or an already-parsed object from better-auth) into a
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolRequestOAuthConnection-CgQePKKb.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;6BAsBU,IAAmB,KASnB,IAAkB;;;;;;;;;EA1BxB,IAAM,IAAQ,GAUR,IAAO,GAIP,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAc1C,IAAW,QACN,EAAM,IAMhB,GAEK,IAAQ,QAAe,EAAS,MAAM,SAAS,SAAS,GACxD,IAAa,QAAe,EAAS,MAAM,OAAO,UAAU,GAU5D,IAAc,QAAe,EAAiB,EAAS,MAAM,MAAM,CAAC,GAGpE,IAAsB,QAEnB,EAAS,MAAM,QACV,cAAc,EAC5B,GAEM,IAAuB,QACnB,EAAM,UAAU,qBAAqB,EAAY,KAC3D,GAEM,IAAc,EAAmB,IAAI,GAWrC,IAAe,EAAI,EAAK,GAExB,IAAc,EAAI,EAAK,GACvB,IAAa,EAAmB,IAAI,GAEpC,IAAS,QACL,EAAa,SAAS,EAAoB,KACpD,GACM,IAAa,QACT,EAAQ,EAAW,SAAU,EAAQ,EAAM,iBACrD;EAMA,eAAe,IAAmC;GAC9C,IAAI,CAAC,EAAW,SAAS,CAAC,EAAM,iBAC5B,OAAO;GAEX,IAAI;IACA,IAAM,IAAS,MAAM,EAAM,gBAAgB,EAAW,KAAK;IAE3D,OADA,EAAY,QAAQ,EAAO,aACpB,EAAO;GAClB,QAAQ;IAGJ,OAAO;GACX;EACJ;EAEA,IAAI,GAOA,IAAgB,GAEhB,IAA8B;EAElC,SAAS,IAAe;GAOpB,AANI,MAAc,KAAA,MACd,aAAa,CAAS,GACtB,IAAY,KAAA,IAEhB,KAAiB,GACjB,IAAe,MACf,EAAY,QAAQ;EACxB;EAEA,SAAS,EAAgB,GAA2B;GAEhD,AADA,EAAa,GACT,GAAa,UAGjB,EAAa,QAAQ,IACrB,EAAY,QAAQ,GACpB,EAAW,QAAQ,MACnB,EAAK,aAAa,CAAiB;EACvC;EAaA,SAAS,GAAmB,GAAe;GACvC,EAAa;GACb,IAAM,IAAU;GAEhB,AADA,IAAe,GACf,EAAY,QAAQ;GACpB,IAAM,IAAW,KAAK,IAAI,IAAI,GACxB,IAAO,YAAY;IACrB,IAAI,MAAY,KAAiB,KAAK,IAAI,IAAI,GAAU;KACpD,AAAI,MAAY,KACZ,EAAa;KAEjB;IACJ;IACA,IAAM,IAAY,MAAM,EAAe;IACvC,IAAI,MAAY,GAGhB;SAAI,KAAa,EAAY,OAAO;MAChC,EAAgB,EAAY,KAAK;MACjC;KACJ;KACA,IAAY,WAAW,GAAM,CAAgB;IAD7C;GAEJ;GACA,IAAY,WAAW,GAAM,CAAgB;EACjD;EAWA,SAAS,EAAU,GAAqB;GACpC,IACI,EAAM,MAAM,SAAS,wBACrB,CAAC,EAAY,SACb,EAAM,WAAW,GAEjB;GAEJ,IAAM,IAAmB,EAAM,KAAK;GACpC,IACI,KACA,EAAY,SACZ,MAAqB,EAAY,OAEjC;GAEJ,IAAI,CAAC,EAAM,KAAK,SAAS;IAGrB,AADA,EAAa,GACb,EAAW,QAAQ,EAAM,KAAK,SAAS;IACvC;GACJ;GACA,IAAM,IAAoB,KAAoB,EAAY;GAC1D,AAAI,KACA,EAAgB,CAAiB;EAEzC;EAWA,eAAe,IAAiB;GAC5B,IACI,CAAC,EAAW,SACZ,CAAC,EAAM,qBACP,EAAY,OAEZ;GAEJ,EAAW,QAAQ;GACnB,IAAM,IAAQ,OAAO,KACjB,IACA,aACA,4CACJ;GACA,IAAI,CAAC,GAAO;IACR,EAAW,QAAQ,EAAG,mBAAmB;IACzC;GACJ;GACA,GAAmB,CAAK;GACxB,IAAI;IACA,IAAM,IAAa,MAAM,EAAM,kBAAkB,EAAW,KAAK;IAEjE,AADA,EAAY,QAAQ,EAAW,aAC/B,EAAM,SAAS,OAAO,EAAW;GACrC,SAAS,GAAK;IAGV,AAFA,EAAa,GACb,EAAM,MAAM,GACZ,EAAW,QACP,aAAe,QAAQ,EAAI,UAAU;GAC7C;EACJ;SAKA,QAAgB,OAAO,iBAAiB,WAAW,CAAS,CAAC,GAC7D,QAAkB;GAEd,AADA,OAAO,oBAAoB,WAAW,CAAS,GAC/C,EAAa;EACjB,CAAC;;GAMD,OAAA,EAAA,GAAA,EAyFM,OAzFN,GAyFM,CAvFF,EAIM,OAJN,GAIM,CAFF,EAAkD,GAAA;IAA1C,MAAK;IAAU,OAAM;GAC7B,CAAA,GAAA,EAAkD,UAAlD,GAAkD,EAAtB,EAAA,KAAU,GAAA,CAAA,CAAA,CAAA,GAE1C,EAiFM,OAjFN,IAiFM,CA/Ec,EAAA,SACZ,EAAA,GAAA,EASM,OATN,GASM,CARF,EAA4D,GAAA;IAApD,MAAK;IAAgB,OAAM;GACnC,CAAA,GAAA,EAMS,UANT,GAMS,EAJD,EAAA,CAAA,CAAE,CAAA,6CAAA,EAAsF,MAAA,EAAA,MAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,KASnF,EAAA,SACjB,EAAA,GAAA,EAKM,OALN,GAKM,CAJF,EAA8B,GAAA,EAAtB,MAAK,eAAc,CAAA,GAC3B,EAES,QAFT,GAES,EADL,EAAA,CAAA,CAAE,CAAA,oCAAA,CAAA,GAAA,CAAA,CAAA,CAAA,KAMO,EAAA,SACjB,EAAA,GAAA,EAiBM,OAjBN,IAiBM,CAhBF,EAKM,OALN,IAKM,CAJF,EAAmD,GAAA;IAA3C,MAAK;IAAgB,OAAM;GACnC,CAAA,GAAA,EAES,UAFT,IAES,EADF,EAAA,KAAU,GAAA,CAAA,CAAA,CAAA,GAIX,EAAA,SADV,EAAA,GAAA,EAS8B,GAAA;;IAP1B,WAAU;IACV,MAAK;IACJ,OAAoC,EAAA,CAAA,CAAE,CAAA,4CAAA,EAAqF,MAAA,EAAA,MAAA,CAAA;IAK3H,SAAO;GAKC,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,KAAA,EAAA,SAAwB,EAAA,SAA7C,EAAA,GAAA,EAmBW,GAAA,EAAA,KAAA,EAAA,GAAA,CAlBP,EAQM,OARN,GAQM,CAPF,EAMO,QAAA,MAAA,EAJC,EAAA,CAAA,CAAE,CAAA,4CAAA,EAAqF,MAAA,EAAA,MAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,GAMnG,EAQ8B,GAAA;IAP1B,WAAU;IACV,MAAK;IACJ,OAAgC,EAAA,CAAA,CAAE,CAAA,4CAAA,EAAiF,MAAA,EAAA,MAAA,CAAA;IAKnH,SAAO;GAIZ,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,EASM,OATN,GASM,CARF,EAAyB,GAAA,EAAjB,MAAK,UAAS,CAAA,GACtB,EAMO,QANP,GAMO,EAJC,EAAA,CAAA,CAAE,CAAA,0CAAA,EAAmF,MAAA,EAAA,MAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { O as e } from "./Agent-tbKw8Mdt.js";
|
|
2
|
-
import { VvButton as t, VvIcon as n } from "@volverjs/ui-vue/components";
|
|
3
|
-
import { Fragment as r, computed as i, createBlock as a, createCommentVNode as o, createElementBlock as s, createElementVNode as c, createVNode as l, defineComponent as u, onMounted as d, onUnmounted as f, openBlock as p, ref as m, toDisplayString as h, unref as g } from "vue";
|
|
4
|
-
import { useI18n as _ } from "vue-i18n";
|
|
5
|
-
//#region ../../packages/components/src/chat/PkToolRequestOAuthConnection.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
-
var v = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12" }, y = { class: "px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8" }, b = { class: "truncate" }, x = { class: "p-sm" }, S = {
|
|
7
|
-
key: 0,
|
|
8
|
-
class: "flex items-center gap-8"
|
|
9
|
-
}, ee = { class: "font-medium text-word-1" }, te = {
|
|
10
|
-
key: 1,
|
|
11
|
-
class: "flex items-center gap-8 text-word-3"
|
|
12
|
-
}, ne = { class: "text-10" }, re = {
|
|
13
|
-
key: 2,
|
|
14
|
-
class: "flex flex-col gap-xs"
|
|
15
|
-
}, C = { class: "flex items-center gap-8 text-word-3" }, w = { class: "text-10" }, T = { class: "flex items-center gap-6 text-word-3 mb-xs" }, E = {
|
|
16
|
-
key: 4,
|
|
17
|
-
class: "flex items-center gap-8 text-word-3"
|
|
18
|
-
}, D = { class: "text-10" }, O = 2e3, k = 3e5, A = /* @__PURE__ */ u({
|
|
19
|
-
__name: "PkToolRequestOAuthConnection",
|
|
20
|
-
props: {
|
|
21
|
-
part: {},
|
|
22
|
-
resolveConnection: { type: Function },
|
|
23
|
-
checkConnection: { type: Function }
|
|
24
|
-
},
|
|
25
|
-
emits: ["connected"],
|
|
26
|
-
setup(u, { emit: A }) {
|
|
27
|
-
let j = u, M = A, { t: N } = _({ useScope: "global" }), P = i(() => j.part), F = i(() => P.value.state ?? "unknown"), I = i(() => P.value.input?.serverName), L = i(() => e(P.value.output)), R = i(() => P.value.output?.connected === !0), z = i(() => F.value === "input-available" || L.value), B = m(null), V = m(!1), H = m(!1), U = m(null), W = i(() => V.value || R.value), G = i(() => !!I.value && !!j.resolveConnection);
|
|
28
|
-
async function K() {
|
|
29
|
-
if (!I.value || !j.checkConnection) return !1;
|
|
30
|
-
try {
|
|
31
|
-
let e = await j.checkConnection(I.value);
|
|
32
|
-
return B.value = e.mcpServerId, e.connected;
|
|
33
|
-
} catch {
|
|
34
|
-
return !1;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
let q, J = 0, Y = null;
|
|
38
|
-
function X() {
|
|
39
|
-
q !== void 0 && (clearTimeout(q), q = void 0), J += 1, Y = null, H.value = !1;
|
|
40
|
-
}
|
|
41
|
-
function Z(e) {
|
|
42
|
-
X(), !V.value && (V.value = !0, B.value = e, U.value = null, M("connected", e));
|
|
43
|
-
}
|
|
44
|
-
function ie(e) {
|
|
45
|
-
X();
|
|
46
|
-
let t = J;
|
|
47
|
-
Y = e, H.value = !0;
|
|
48
|
-
let n = Date.now() + k, r = async () => {
|
|
49
|
-
if (t !== J || Date.now() > n) {
|
|
50
|
-
t === J && X();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
let e = await K();
|
|
54
|
-
if (t === J) {
|
|
55
|
-
if (e && B.value) {
|
|
56
|
-
Z(B.value);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
q = setTimeout(r, O);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
q = setTimeout(r, O);
|
|
63
|
-
}
|
|
64
|
-
function Q(e) {
|
|
65
|
-
if (e.data?.type !== "mcp-oauth-complete" || !H.value || e.source !== Y) return;
|
|
66
|
-
let t = e.data.mcpServerId;
|
|
67
|
-
if (t && B.value && t !== B.value) return;
|
|
68
|
-
if (!e.data.success) {
|
|
69
|
-
X(), U.value = e.data.error ?? "Unknown error";
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
let n = t ?? B.value;
|
|
73
|
-
n && Z(n);
|
|
74
|
-
}
|
|
75
|
-
async function $() {
|
|
76
|
-
if (!I.value || !j.resolveConnection || H.value) return;
|
|
77
|
-
U.value = null;
|
|
78
|
-
let e = window.open("", "mcp_oauth", "width=600,height=700,menubar=no,toolbar=no");
|
|
79
|
-
if (!e) {
|
|
80
|
-
U.value = N("auth.popupBlocked");
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
ie(e);
|
|
84
|
-
try {
|
|
85
|
-
let t = await j.resolveConnection(I.value);
|
|
86
|
-
B.value = t.mcpServerId, e.location.href = t.authorizeUrl;
|
|
87
|
-
} catch (t) {
|
|
88
|
-
X(), e.close(), U.value = t instanceof Error ? t.message : "Failed to resolve OAuth";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return d(() => window.addEventListener("message", Q)), f(() => {
|
|
92
|
-
window.removeEventListener("message", Q), X();
|
|
93
|
-
}), (e, i) => {
|
|
94
|
-
let u = n, d = t;
|
|
95
|
-
return p(), s("div", v, [c("div", y, [l(u, {
|
|
96
|
-
name: "ri:link",
|
|
97
|
-
class: "text-16 shrink-0"
|
|
98
|
-
}), c("strong", b, h(I.value), 1)]), c("div", x, [W.value ? (p(), s("div", S, [l(u, {
|
|
99
|
-
name: "ri:check-line",
|
|
100
|
-
class: "text-16 text-success"
|
|
101
|
-
}), c("strong", ee, h(g(N)("messagePart.requestOAuthConnectionSuccess", { name: I.value })), 1)])) : H.value ? (p(), s("div", te, [l(u, { name: "loading-loop" }), c("span", ne, h(g(N)("messagePart.requestOAuthConnection")), 1)])) : U.value ? (p(), s("div", re, [c("div", C, [l(u, {
|
|
102
|
-
name: "ri:close-line",
|
|
103
|
-
class: "text-danger"
|
|
104
|
-
}), c("strong", w, h(U.value), 1)]), G.value ? (p(), a(d, {
|
|
105
|
-
key: 0,
|
|
106
|
-
modifiers: "primary-small",
|
|
107
|
-
icon: "ri:external-link-line",
|
|
108
|
-
label: g(N)("messagePart.requestOAuthConnectionButton", { name: I.value }),
|
|
109
|
-
onClick: $
|
|
110
|
-
}, null, 8, ["label"])) : o("", !0)])) : z.value && G.value ? (p(), s(r, { key: 3 }, [c("div", T, [c("span", null, h(g(N)("messagePart.requestOAuthConnectionPrompt", { name: I.value })), 1)]), l(d, {
|
|
111
|
-
modifiers: "primary-small",
|
|
112
|
-
icon: "ri:external-link-line",
|
|
113
|
-
label: g(N)("messagePart.requestOAuthConnectionButton", { name: I.value }),
|
|
114
|
-
onClick: $
|
|
115
|
-
}, null, 8, ["label"])], 64)) : (p(), s("div", E, [l(u, { name: "ri:link" }), c("span", D, h(g(N)("messagePart.requestOAuthConnectionInfo", { name: I.value })), 1)]))])]);
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
//#endregion
|
|
120
|
-
export { A as default };
|
|
121
|
-
|
|
122
|
-
//# sourceMappingURL=PkToolRequestOAuthConnection-a2LegFoE.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PkToolRequestOAuthConnection-a2LegFoE.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|