@agent-native/core 0.105.0 → 0.106.1
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +39 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/index.ts +2 -0
- package/corpus/core/src/application-state/script-helpers.ts +12 -0
- package/corpus/core/src/application-state/store.ts +38 -0
- package/corpus/core/src/client/AssistantChat.tsx +4 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
- package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
- package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
- package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
- package/corpus/core/src/client/use-chat-models.ts +10 -2
- package/corpus/core/src/connections/catalog.ts +107 -6
- package/corpus/core/src/localization/default-messages.ts +2 -0
- package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
- package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
- package/corpus/core/src/mcp-client/app-api.ts +136 -0
- package/corpus/core/src/mcp-client/index.ts +21 -0
- package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
- package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
- package/corpus/core/src/mcp-client/remote-store.ts +93 -118
- package/corpus/core/src/mcp-client/remote-url.ts +97 -0
- package/corpus/core/src/mcp-client/routes.ts +11 -0
- package/corpus/core/src/oauth-tokens/store.ts +10 -4
- package/corpus/core/src/provider-api/index.ts +690 -55
- package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
- package/corpus/core/src/server/action-discovery.ts +2 -0
- package/corpus/core/src/server/core-routes-plugin.ts +9 -1
- package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
- package/corpus/core/src/templates/default/AGENTS.md +9 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
- package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
- package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
- package/corpus/templates/analytics/server/lib/jira.ts +14 -42
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +16 -0
- package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
- package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
- package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
- package/corpus/templates/design/actions/view-screen.ts +98 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
- package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
- package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
- package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
- package/corpus/templates/design/app/i18n-data.ts +32 -0
- package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
- package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +15 -0
- package/corpus/templates/design/shared/node-rewrite.ts +213 -0
- package/corpus/templates/design/shared/review-anchor.ts +26 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/script-helpers.d.ts +1 -0
- package/dist/application-state/script-helpers.d.ts.map +1 -1
- package/dist/application-state/script-helpers.js +7 -1
- package/dist/application-state/script-helpers.js.map +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +2 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +5 -2
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +2 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +13 -5
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +58 -14
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +1 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
- package/dist/client/resources/use-mcp-servers.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/use-chat-models.d.ts +1 -0
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +8 -2
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +78 -7
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +97 -6
- package/dist/connections/catalog.js.map +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +2 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
- package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
- package/dist/mcp/actions/call-mcp-tool.js +15 -0
- package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
- package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
- package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
- package/dist/mcp/actions/list-mcp-tools.js +16 -0
- package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
- package/dist/mcp-client/app-api.d.ts +34 -0
- package/dist/mcp-client/app-api.d.ts.map +1 -0
- package/dist/mcp-client/app-api.js +94 -0
- package/dist/mcp-client/app-api.js.map +1 -0
- package/dist/mcp-client/index.d.ts +3 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +3 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/oauth-client.d.ts +113 -0
- package/dist/mcp-client/oauth-client.d.ts.map +1 -0
- package/dist/mcp-client/oauth-client.js +316 -0
- package/dist/mcp-client/oauth-client.js.map +1 -0
- package/dist/mcp-client/oauth-routes.d.ts +25 -0
- package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
- package/dist/mcp-client/oauth-routes.js +218 -0
- package/dist/mcp-client/oauth-routes.js.map +1 -0
- package/dist/mcp-client/remote-store.d.ts +21 -6
- package/dist/mcp-client/remote-store.d.ts.map +1 -1
- package/dist/mcp-client/remote-store.js +72 -124
- package/dist/mcp-client/remote-store.js.map +1 -1
- package/dist/mcp-client/remote-url.d.ts +8 -0
- package/dist/mcp-client/remote-url.d.ts.map +1 -0
- package/dist/mcp-client/remote-url.js +103 -0
- package/dist/mcp-client/remote-url.js.map +1 -0
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +9 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/oauth-tokens/store.d.ts +2 -2
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +10 -6
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/index.d.ts +14 -0
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +499 -61
- package/dist/provider-api/index.js.map +1 -1
- package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
- package/dist/secrets/register-framework-secrets.js +28 -0
- package/dist/secrets/register-framework-secrets.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +9 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +10 -3
- package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
- package/dist/server/workspace-provider-oauth.js +325 -43
- package/dist/server/workspace-provider-oauth.js.map +1 -1
- package/dist/templates/default/AGENTS.md +9 -7
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
- package/package.json +2 -2
- package/src/templates/default/AGENTS.md +9 -7
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
|
@@ -68,6 +68,52 @@ the real app focused on exactly what was produced. It reuses the existing
|
|
|
68
68
|
`navigate` / `application_state` contract the UI already drains every 2s (see
|
|
69
69
|
**context-awareness**) — we never invent a second navigation mechanism.
|
|
70
70
|
|
|
71
|
+
## App-facing MCP connectors
|
|
72
|
+
|
|
73
|
+
Apps can also consume connected remote MCP servers as a server-side API. The
|
|
74
|
+
framework stores each connector's OAuth client registration, discovery metadata,
|
|
75
|
+
access token, and refresh token in the encrypted `oauth_tokens` store, scoped to
|
|
76
|
+
the user or organization. The browser and sandboxed MCP App iframes never
|
|
77
|
+
receive those credentials.
|
|
78
|
+
|
|
79
|
+
Use the hidden core actions `list-mcp-tools` and `call-mcp-tool` when an app UI
|
|
80
|
+
needs a request-scoped API surface. Server-side app actions can import
|
|
81
|
+
`listVisibleMcpTools` and `callMcpTool` from `@agent-native/core/mcp-client`.
|
|
82
|
+
Both paths require an authenticated request context and enforce user/org server
|
|
83
|
+
visibility plus MCP App visibility metadata. Pass the configured server id and
|
|
84
|
+
the server's original tool name; the framework resolves the internal prefixed
|
|
85
|
+
tool name and injects the connector's bearer token.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { callMcpTool } from "@agent-native/core/mcp-client";
|
|
89
|
+
|
|
90
|
+
const result = await callMcpTool("org_acme_linear", "list_issues", {
|
|
91
|
+
project: "<PROJECT_ID>",
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use this for bounded product workflows (for example, syncing issues or
|
|
96
|
+
refreshing analytics). Do not expose a raw token, arbitrary server URL, or
|
|
97
|
+
unbounded proxy from an app action. Keep provider-specific schemas and access
|
|
98
|
+
checks in the app action, and use the connector catalog/OAuth button for setup.
|
|
99
|
+
|
|
100
|
+
## Provider API connectors
|
|
101
|
+
|
|
102
|
+
The shared workspace connection catalog is also the setup surface for provider
|
|
103
|
+
APIs. When a provider advertises OAuth, show that connection as the primary
|
|
104
|
+
setup action and retain its API key or token as an explicit fallback. Current
|
|
105
|
+
shared OAuth providers include Figma, GitHub, Google Drive, HubSpot, Jira Cloud, Notion, and Sentry. Slack
|
|
106
|
+
uses its managed messaging-install OAuth flow. Gmail,
|
|
107
|
+
Calendar, and Slides may use template-owned Google OAuth flows with broader
|
|
108
|
+
scopes; do not replace those flows with the narrower Drive connection.
|
|
109
|
+
|
|
110
|
+
Provider API actions resolve credentials server-side. Pass a granted
|
|
111
|
+
`connectionId` when a request must use a particular OAuth account; never copy
|
|
112
|
+
the access token into a browser, extension, MCP App, prompt, or stored action
|
|
113
|
+
payload. Key-only providers should remain in the normal scoped secrets flow
|
|
114
|
+
until their upstream OAuth app, scopes, refresh behavior, and identity mapping
|
|
115
|
+
have been implemented and configured.
|
|
116
|
+
|
|
71
117
|
## How
|
|
72
118
|
|
|
73
119
|
### 1. Connect to hosted apps
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
IconCopy,
|
|
27
27
|
IconDotsVertical,
|
|
28
28
|
IconBrandGithub,
|
|
29
|
+
IconPlugConnected,
|
|
29
30
|
} from "@tabler/icons-react";
|
|
30
31
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
31
32
|
import { useEffect, useState } from "react";
|
|
@@ -427,6 +428,55 @@ function GitHubOAuthView({
|
|
|
427
428
|
);
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
function WorkspaceOAuthView({
|
|
432
|
+
provider,
|
|
433
|
+
label,
|
|
434
|
+
connected,
|
|
435
|
+
}: {
|
|
436
|
+
provider: string;
|
|
437
|
+
label: string;
|
|
438
|
+
connected: boolean;
|
|
439
|
+
}) {
|
|
440
|
+
const t = useT();
|
|
441
|
+
const connect = () => {
|
|
442
|
+
const params = new URLSearchParams({
|
|
443
|
+
appId: "analytics",
|
|
444
|
+
return: "/data-sources",
|
|
445
|
+
});
|
|
446
|
+
window.location.assign(
|
|
447
|
+
agentNativePath(
|
|
448
|
+
`/_agent-native/connections/oauth/${provider}/start?${params.toString()}`,
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<div className="space-y-3 rounded-md border border-border/50 bg-muted/20 p-3">
|
|
455
|
+
<div className="flex items-start justify-between gap-3">
|
|
456
|
+
<div className="flex items-start gap-3">
|
|
457
|
+
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-background text-muted-foreground">
|
|
458
|
+
<IconPlugConnected className="h-4 w-4" />
|
|
459
|
+
</div>
|
|
460
|
+
<div className="min-w-0 space-y-1">
|
|
461
|
+
<p className="text-xs font-medium text-foreground">{label}</p>
|
|
462
|
+
<p className="text-xs text-muted-foreground">
|
|
463
|
+
{t("dataSources.sharedIntegration")}
|
|
464
|
+
</p>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
<Button
|
|
468
|
+
size="sm"
|
|
469
|
+
variant={connected ? "outline" : "default"}
|
|
470
|
+
onClick={connect}
|
|
471
|
+
className="shrink-0 text-xs"
|
|
472
|
+
>
|
|
473
|
+
{connected ? t("dataSources.reconnect") : t("dataSources.connect")}
|
|
474
|
+
</Button>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
430
480
|
function SharedConnectionBadge({ status }: { status: SharedConnectionStatus }) {
|
|
431
481
|
const tone =
|
|
432
482
|
status.kind === "ready"
|
|
@@ -1101,6 +1151,17 @@ function DataSourceCard({
|
|
|
1101
1151
|
/>
|
|
1102
1152
|
</div>
|
|
1103
1153
|
)}
|
|
1154
|
+
{(["notion", "hubspot", "jira", "sentry"] as const).includes(
|
|
1155
|
+
source.id as "notion" | "hubspot" | "jira" | "sentry",
|
|
1156
|
+
) && (
|
|
1157
|
+
<div className="mb-4">
|
|
1158
|
+
<WorkspaceOAuthView
|
|
1159
|
+
provider={source.id}
|
|
1160
|
+
label={source.name}
|
|
1161
|
+
connected={readyViaWorkspace}
|
|
1162
|
+
/>
|
|
1163
|
+
</div>
|
|
1164
|
+
)}
|
|
1104
1165
|
{sharedConnectionStatus && (
|
|
1105
1166
|
<SharedConnectionStatusRow status={sharedConnectionStatus} />
|
|
1106
1167
|
)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveWorkspaceConnectionForApp } from "@agent-native/core/workspace-connections";
|
|
1
2
|
import { defineEventHandler, getQuery, setResponseStatus } from "h3";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -17,6 +18,12 @@ import {
|
|
|
17
18
|
async function requireJiraCredentials(
|
|
18
19
|
event: Parameters<typeof requireCredential>[0],
|
|
19
20
|
) {
|
|
21
|
+
const workspaceConnection = await resolveWorkspaceConnectionForApp({
|
|
22
|
+
appId: "analytics",
|
|
23
|
+
provider: "jira",
|
|
24
|
+
requireConnected: true,
|
|
25
|
+
});
|
|
26
|
+
if (workspaceConnection.available) return null;
|
|
20
27
|
return (
|
|
21
28
|
(await requireCredential(event, "JIRA_BASE_URL", "Jira")) ||
|
|
22
29
|
(await requireCredential(event, "JIRA_USER_EMAIL", "Jira")) ||
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
// Jira Cloud REST API client
|
|
2
|
-
// Uses Basic auth (email + API token)
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
requireRequestCredentialContext,
|
|
7
|
-
scopedCredentialCacheKey,
|
|
8
|
-
} from "./credentials-context";
|
|
3
|
+
import { scopedCredentialCacheKey } from "./credentials-context";
|
|
4
|
+
import { executeProviderApiRequest } from "./provider-api";
|
|
9
5
|
|
|
10
6
|
const API_V3 = "/rest/api/3";
|
|
11
7
|
const API_AGILE = "/rest/agile/1.0";
|
|
@@ -15,29 +11,6 @@ const cache = new Map<string, { data: unknown; ts: number }>();
|
|
|
15
11
|
const CACHE_TTL_MS = 10 * 60 * 1000;
|
|
16
12
|
const MAX_CACHE = 100;
|
|
17
13
|
|
|
18
|
-
async function getAuth(): Promise<{
|
|
19
|
-
baseUrl: string;
|
|
20
|
-
headers: Record<string, string>;
|
|
21
|
-
}> {
|
|
22
|
-
const ctx = requireRequestCredentialContext("JIRA_BASE_URL");
|
|
23
|
-
const baseUrl = await resolveCredential("JIRA_BASE_URL", ctx);
|
|
24
|
-
const email = await resolveCredential("JIRA_USER_EMAIL", ctx);
|
|
25
|
-
const token = await resolveCredential("JIRA_API_TOKEN", ctx);
|
|
26
|
-
if (!baseUrl || !email || !token) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
"JIRA_BASE_URL, JIRA_USER_EMAIL, and JIRA_API_TOKEN are not configured",
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
const encoded = Buffer.from(`${email}:${token}`).toString("base64");
|
|
32
|
-
return {
|
|
33
|
-
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
34
|
-
headers: {
|
|
35
|
-
Authorization: `Basic ${encoded}`,
|
|
36
|
-
Accept: "application/json",
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
14
|
async function jiraGet<T>(
|
|
42
15
|
path: string,
|
|
43
16
|
params?: Record<string, string>,
|
|
@@ -53,21 +26,20 @@ async function jiraGet<T>(
|
|
|
53
26
|
return cached.data as T;
|
|
54
27
|
}
|
|
55
28
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
throw new Error(`Jira API error ${res.status}: ${text}`);
|
|
29
|
+
const result = (await executeProviderApiRequest({
|
|
30
|
+
provider: "jira",
|
|
31
|
+
path,
|
|
32
|
+
query: params,
|
|
33
|
+
})) as {
|
|
34
|
+
response: { ok: boolean; status: number; json?: unknown; text?: string };
|
|
35
|
+
};
|
|
36
|
+
if (!result.response.ok) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Jira API error ${result.response.status}: ${String(result.response.text ?? "")}`,
|
|
39
|
+
);
|
|
68
40
|
}
|
|
69
41
|
|
|
70
|
-
const data =
|
|
42
|
+
const data = result.response.json;
|
|
71
43
|
if (cache.size >= MAX_CACHE) {
|
|
72
44
|
const oldest = cache.keys().next().value;
|
|
73
45
|
if (oldest) cache.delete(oldest);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBody } from "@agent-native/core/server";
|
|
2
|
+
import { resolveWorkspaceConnectionForApp } from "@agent-native/core/workspace-connections";
|
|
2
3
|
import { defineEventHandler } from "h3";
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -141,6 +142,16 @@ export default defineEventHandler(async (event) => {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
case "jira": {
|
|
145
|
+
const workspaceConnection = await resolveWorkspaceConnectionForApp({
|
|
146
|
+
appId: "analytics",
|
|
147
|
+
provider: "jira",
|
|
148
|
+
requireConnected: true,
|
|
149
|
+
});
|
|
150
|
+
if (workspaceConnection.available) {
|
|
151
|
+
const { getProjects } = await import("../../lib/jira");
|
|
152
|
+
await getProjects();
|
|
153
|
+
return { ok: true };
|
|
154
|
+
}
|
|
144
155
|
const baseUrl = await resolveCredential("JIRA_BASE_URL", ctx);
|
|
145
156
|
const email = await resolveCredential("JIRA_USER_EMAIL", ctx);
|
|
146
157
|
const token = await resolveCredential("JIRA_API_TOKEN", ctx);
|
|
@@ -47,6 +47,28 @@ iframe-backed screens on the infinite canvas.
|
|
|
47
47
|
and create one screen per URL/path. Shorthand like
|
|
48
48
|
`localhost:1234/onboarding/1` means `http://localhost:1234/onboarding/1`.
|
|
49
49
|
|
|
50
|
+
## Select And Reprompt
|
|
51
|
+
|
|
52
|
+
When a chat message begins with `[Reprompt selection]`, the selected subtree is
|
|
53
|
+
a hard write boundary. The only mutation path is `propose-node-rewrite` with
|
|
54
|
+
the exact `repromptId`, target, and `baseVersionHash` captured in
|
|
55
|
+
`design-reprompt-pending:<designId>:<fileId>`. Never use `apply-visual-edit`,
|
|
56
|
+
`apply-source-edit`, `write-source`, `write-local-file`, `edit-design`, or any
|
|
57
|
+
other content-writing action for that request. Clarifying questions are allowed,
|
|
58
|
+
but a requested change must remain a proposal.
|
|
59
|
+
|
|
60
|
+
Produce one variant by default. Produce two or three only when the instruction
|
|
61
|
+
asks for options. A retry includes `priorProposalId`; keep the same target and
|
|
62
|
+
base version, incorporate the feedback, and call `propose-node-rewrite` again.
|
|
63
|
+
The UI previews the returned subtree without persisting it.
|
|
64
|
+
|
|
65
|
+
Use `resolve-node-rewrite` for the accept/reject lifecycle. Accept applies the
|
|
66
|
+
chosen variant as one version-checked inline/Yjs content transaction so one
|
|
67
|
+
undo restores the prior structure; reject clears the proposal without changing
|
|
68
|
+
content. For conversational resolution such as “apply the second one,” call
|
|
69
|
+
`view-screen`, read the active `design.reprompt.proposal`, and pass its
|
|
70
|
+
`proposalId` plus the zero-based `variantIndex` to `resolve-node-rewrite`.
|
|
71
|
+
|
|
50
72
|
## Review Quality
|
|
51
73
|
|
|
52
74
|
- Treat the running app as the truth. Preserve its component language, tokens,
|
|
@@ -16,6 +16,13 @@ patterns live in `.agents/skills/`.
|
|
|
16
16
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
17
17
|
- Use the app actions for designs, files, versions, design systems, variants,
|
|
18
18
|
export, and sharing. Do not write design rows directly with SQL.
|
|
19
|
+
- A message beginning with `[Reprompt selection]` is preview-only. Call
|
|
20
|
+
`propose-node-rewrite` with its exact `repromptId`, target, and base hash;
|
|
21
|
+
never call `edit-design`, `update-design`, `update-file`, `generate-design`,
|
|
22
|
+
`apply-visual-edit`, or another content writer. Only the frontend-only
|
|
23
|
+
`resolve-node-rewrite` action may persist an explicitly accepted proposal.
|
|
24
|
+
- A message beginning with `[Selection question]` is read-only. Answer about
|
|
25
|
+
the captured element and subtree without calling content-writing actions.
|
|
19
26
|
- When a user wants an established public system as a starting point, call
|
|
20
27
|
`create-design-system` with `templateId: material-3`, `carbon-white`, or
|
|
21
28
|
`primer-light`. These are source-linked, versioned token snapshots with
|
|
@@ -268,6 +275,15 @@ patterns live in `.agents/skills/`.
|
|
|
268
275
|
generation planning state created by `generate-screens` (canvas region
|
|
269
276
|
assignments and per-frame instructions consumed by `generate-design` and
|
|
270
277
|
`view-screen`; not rendered as canvas overlays).
|
|
278
|
+
- `design-reprompt-pending:<designId>:<fileId>` is the client-captured source
|
|
279
|
+
selection, instruction, base hash, and authoritative current request id for
|
|
280
|
+
a scoped regenerate request.
|
|
281
|
+
- `design-reprompt-proposal:<designId>:<fileId>:<repromptId>` is one
|
|
282
|
+
request-specific preview-only subtree proposal. Candidate payloads have a
|
|
283
|
+
256 KiB aggregate serialized limit. Resolution and cancellation use atomic
|
|
284
|
+
compare-and-set cleanup so an older request cannot erase a newer one.
|
|
285
|
+
`view-screen` lists only proposals paired to the current pending request as
|
|
286
|
+
`pendingCandidateReviews`.
|
|
271
287
|
- `show-design-questions` opens focused pre-generation questions in the main
|
|
272
288
|
design canvas (`show-questions` application state).
|
|
273
289
|
- `guided-questions` may contain a one-click chat choice for the current
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
compareAndSetAppState,
|
|
4
|
+
readAppState,
|
|
5
|
+
} from "@agent-native/core/application-state";
|
|
6
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
designRepromptPendingStateKey,
|
|
11
|
+
designRepromptProposalStateKey,
|
|
12
|
+
isNodeRewriteProposal,
|
|
13
|
+
isPendingDesignReprompt,
|
|
14
|
+
} from "../shared/node-rewrite.js";
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
agentTool: false,
|
|
18
|
+
description:
|
|
19
|
+
"Cancel one pending node rewrite only when it is still the current request.",
|
|
20
|
+
schema: z.object({
|
|
21
|
+
designId: z.string().min(1),
|
|
22
|
+
fileId: z.string().min(1),
|
|
23
|
+
repromptId: z.string().min(1),
|
|
24
|
+
}),
|
|
25
|
+
run: async ({ designId, fileId, repromptId }) => {
|
|
26
|
+
await assertAccess("design", designId, "editor");
|
|
27
|
+
const pendingKey = designRepromptPendingStateKey(designId, fileId);
|
|
28
|
+
const pending = await readAppState(pendingKey);
|
|
29
|
+
if (
|
|
30
|
+
!isPendingDesignReprompt(pending) ||
|
|
31
|
+
pending.repromptId !== repromptId
|
|
32
|
+
) {
|
|
33
|
+
return { cancelled: false, superseded: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const proposalKey = designRepromptProposalStateKey(
|
|
37
|
+
designId,
|
|
38
|
+
fileId,
|
|
39
|
+
repromptId,
|
|
40
|
+
);
|
|
41
|
+
const proposal = await readAppState(proposalKey);
|
|
42
|
+
const [pendingCancelled, proposalCancelled] = await Promise.all([
|
|
43
|
+
compareAndSetAppState(pendingKey, pending, null),
|
|
44
|
+
isNodeRewriteProposal(proposal)
|
|
45
|
+
? compareAndSetAppState(proposalKey, proposal, null)
|
|
46
|
+
: Promise.resolve(false),
|
|
47
|
+
]);
|
|
48
|
+
return {
|
|
49
|
+
cancelled: pendingCancelled,
|
|
50
|
+
proposalCancelled,
|
|
51
|
+
superseded: !pendingCancelled,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
compareAndSetAppState,
|
|
4
|
+
readAppState,
|
|
5
|
+
writeAppState,
|
|
6
|
+
} from "@agent-native/core/application-state";
|
|
7
|
+
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
8
|
+
import { and, eq } from "drizzle-orm";
|
|
9
|
+
import { nanoid } from "nanoid";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
13
|
+
import {
|
|
14
|
+
readLiveSourceFile,
|
|
15
|
+
type SourceWorkspaceFile,
|
|
16
|
+
} from "../server/source-workspace.js";
|
|
17
|
+
import type { CodeLayerSource } from "../shared/code-layer.js";
|
|
18
|
+
import {
|
|
19
|
+
designRepromptPendingStateKey,
|
|
20
|
+
designRepromptProposalStateKey,
|
|
21
|
+
MAX_NODE_REWRITE_PROPOSAL_BYTES,
|
|
22
|
+
resolveNodeRewriteTarget,
|
|
23
|
+
validateNodeRewriteVariant,
|
|
24
|
+
type NodeHtmlPreviewBridgeMessage,
|
|
25
|
+
type NodeRewriteProposal,
|
|
26
|
+
type NodeRewriteTarget,
|
|
27
|
+
} from "../shared/node-rewrite.js";
|
|
28
|
+
import { designSourceTypeFromData } from "../shared/source-mode.js";
|
|
29
|
+
|
|
30
|
+
const sourceSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
designId: z.string().min(1).optional(),
|
|
33
|
+
fileId: z.string().min(1).optional(),
|
|
34
|
+
filename: z.string().min(1).optional(),
|
|
35
|
+
})
|
|
36
|
+
.superRefine((source, ctx) => {
|
|
37
|
+
if (!source.designId && !source.fileId) {
|
|
38
|
+
ctx.addIssue({
|
|
39
|
+
code: z.ZodIssueCode.custom,
|
|
40
|
+
path: ["designId"],
|
|
41
|
+
message: "source.designId or source.fileId is required",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const targetSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
nodeId: z.string().min(1).optional(),
|
|
49
|
+
selector: z.string().min(1).optional(),
|
|
50
|
+
})
|
|
51
|
+
.superRefine((target, ctx) => {
|
|
52
|
+
if (!target.nodeId && !target.selector) {
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: z.ZodIssueCode.custom,
|
|
55
|
+
path: ["nodeId"],
|
|
56
|
+
message: "target.nodeId or target.selector is required",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const variantSchema = z.object({
|
|
62
|
+
html: z
|
|
63
|
+
.string()
|
|
64
|
+
.min(1)
|
|
65
|
+
.max(250_000)
|
|
66
|
+
.describe("Replacement outerHTML for the selected subtree"),
|
|
67
|
+
summary: z.string().trim().min(1).max(240),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const pendingRepromptSchema = z.object({
|
|
71
|
+
repromptId: z.string().min(1),
|
|
72
|
+
designId: z.string().min(1),
|
|
73
|
+
fileId: z.string().min(1),
|
|
74
|
+
target: targetSchema,
|
|
75
|
+
baseVersionHash: z.string().min(1),
|
|
76
|
+
instruction: z.string(),
|
|
77
|
+
createdAt: z.string(),
|
|
78
|
+
priorProposalId: z.string().min(1).optional(),
|
|
79
|
+
priorRepromptId: z.string().min(1).optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
interface EditableDesignFile extends SourceWorkspaceFile {
|
|
83
|
+
designData: string | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function resolveEditableDesignFile(source: {
|
|
87
|
+
designId?: string;
|
|
88
|
+
fileId?: string;
|
|
89
|
+
filename?: string;
|
|
90
|
+
}): Promise<EditableDesignFile> {
|
|
91
|
+
const db = getDb();
|
|
92
|
+
const conditions = [
|
|
93
|
+
accessFilter(schema.designs, schema.designShares),
|
|
94
|
+
source.fileId
|
|
95
|
+
? eq(schema.designFiles.id, source.fileId)
|
|
96
|
+
: eq(schema.designFiles.designId, source.designId ?? ""),
|
|
97
|
+
];
|
|
98
|
+
if (!source.fileId) {
|
|
99
|
+
conditions.push(
|
|
100
|
+
eq(schema.designFiles.filename, source.filename ?? "index.html"),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const [file] = await db
|
|
105
|
+
.select({
|
|
106
|
+
id: schema.designFiles.id,
|
|
107
|
+
designId: schema.designFiles.designId,
|
|
108
|
+
filename: schema.designFiles.filename,
|
|
109
|
+
fileType: schema.designFiles.fileType,
|
|
110
|
+
content: schema.designFiles.content,
|
|
111
|
+
createdAt: schema.designFiles.createdAt,
|
|
112
|
+
updatedAt: schema.designFiles.updatedAt,
|
|
113
|
+
designData: schema.designs.data,
|
|
114
|
+
})
|
|
115
|
+
.from(schema.designFiles)
|
|
116
|
+
.innerJoin(
|
|
117
|
+
schema.designs,
|
|
118
|
+
eq(schema.designFiles.designId, schema.designs.id),
|
|
119
|
+
)
|
|
120
|
+
.where(and(...conditions))
|
|
121
|
+
.limit(1);
|
|
122
|
+
|
|
123
|
+
if (!file) throw new Error("Design HTML file not found.");
|
|
124
|
+
if (source.designId && source.designId !== file.designId) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`source.designId "${source.designId}" does not match file "${file.id}"`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (source.filename && source.filename !== file.filename) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`source.filename "${source.filename}" does not match file "${file.id}"`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (file.fileType !== "html") {
|
|
135
|
+
throw new Error("Node rewrites only support HTML design files.");
|
|
136
|
+
}
|
|
137
|
+
if (designSourceTypeFromData(file.designData) !== "inline") {
|
|
138
|
+
throw new Error("Node rewrites only support inline design screens.");
|
|
139
|
+
}
|
|
140
|
+
await assertAccess("design", file.designId, "editor");
|
|
141
|
+
return file;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function targetsMatch(a: NodeRewriteTarget, b: NodeRewriteTarget): boolean {
|
|
145
|
+
const nodeIdsConflict = a.nodeId && b.nodeId && a.nodeId !== b.nodeId;
|
|
146
|
+
const selectorsConflict =
|
|
147
|
+
a.selector && b.selector && a.selector !== b.selector;
|
|
148
|
+
if (nodeIdsConflict || selectorsConflict) return false;
|
|
149
|
+
return Boolean(
|
|
150
|
+
(a.nodeId && b.nodeId && a.nodeId === b.nodeId) ||
|
|
151
|
+
(a.selector && b.selector && a.selector === b.selector),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export default defineAction({
|
|
156
|
+
description:
|
|
157
|
+
"Propose one to three scoped HTML rewrites for a pending Design selection. " +
|
|
158
|
+
"This previews only: it stores proposal state and never changes the design file. " +
|
|
159
|
+
"The target must identify, and the reprompt id and base hash must exactly match, the pending client selection.",
|
|
160
|
+
schema: z.object({
|
|
161
|
+
source: sourceSchema,
|
|
162
|
+
target: targetSchema,
|
|
163
|
+
baseVersionHash: z.string().min(1),
|
|
164
|
+
variants: z.array(variantSchema).min(1).max(3),
|
|
165
|
+
repromptId: z.string().min(1),
|
|
166
|
+
}),
|
|
167
|
+
run: async ({ source, target, baseVersionHash, variants, repromptId }) => {
|
|
168
|
+
const file = await resolveEditableDesignFile(source);
|
|
169
|
+
const sourceDescriptor: CodeLayerSource = {
|
|
170
|
+
kind: "design-file",
|
|
171
|
+
sourceType: "inline",
|
|
172
|
+
designId: file.designId,
|
|
173
|
+
fileId: file.id,
|
|
174
|
+
filename: file.filename,
|
|
175
|
+
};
|
|
176
|
+
const live = await readLiveSourceFile(file);
|
|
177
|
+
if (live.versionHash !== baseVersionHash) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Screen changed since the selection was captured — regenerate the proposal.",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const pendingKey = designRepromptPendingStateKey(file.designId, file.id);
|
|
184
|
+
const pending = pendingRepromptSchema.parse(await readAppState(pendingKey));
|
|
185
|
+
if (pending.repromptId !== repromptId) {
|
|
186
|
+
throw new Error("Pending reprompt does not match this request.");
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
pending.designId !== file.designId ||
|
|
190
|
+
pending.fileId !== file.id ||
|
|
191
|
+
!targetsMatch(pending.target, target)
|
|
192
|
+
) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"The proposed target does not match the pending selected subtree.",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (pending.baseVersionHash !== baseVersionHash) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"The proposal base hash does not match the pending selection.",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const authoritativeTarget = pending.target;
|
|
204
|
+
const node = resolveNodeRewriteTarget(
|
|
205
|
+
live.content,
|
|
206
|
+
authoritativeTarget,
|
|
207
|
+
sourceDescriptor,
|
|
208
|
+
);
|
|
209
|
+
const validatedVariants = variants.map((variant) =>
|
|
210
|
+
validateNodeRewriteVariant(variant, sourceDescriptor),
|
|
211
|
+
);
|
|
212
|
+
const proposalId = `node-rewrite-${nanoid()}`;
|
|
213
|
+
const resolvedTarget = {
|
|
214
|
+
nodeId: node.id,
|
|
215
|
+
selector: node.selector,
|
|
216
|
+
};
|
|
217
|
+
const proposal: NodeRewriteProposal = {
|
|
218
|
+
proposalId,
|
|
219
|
+
repromptId,
|
|
220
|
+
designId: file.designId,
|
|
221
|
+
fileId: file.id,
|
|
222
|
+
filename: file.filename,
|
|
223
|
+
target: authoritativeTarget,
|
|
224
|
+
resolvedTarget,
|
|
225
|
+
baseVersionHash,
|
|
226
|
+
variants: validatedVariants,
|
|
227
|
+
chosenIndex: 0,
|
|
228
|
+
createdAt: new Date().toISOString(),
|
|
229
|
+
};
|
|
230
|
+
const serializedProposal = JSON.stringify(proposal);
|
|
231
|
+
if (
|
|
232
|
+
Buffer.byteLength(serializedProposal, "utf8") >
|
|
233
|
+
MAX_NODE_REWRITE_PROPOSAL_BYTES
|
|
234
|
+
) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
"Generated candidates are too large to preview safely. Use fewer variants or regenerate a smaller selected subtree.",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const proposalKey = designRepromptProposalStateKey(
|
|
240
|
+
file.designId,
|
|
241
|
+
file.id,
|
|
242
|
+
repromptId,
|
|
243
|
+
);
|
|
244
|
+
await writeAppState(
|
|
245
|
+
proposalKey,
|
|
246
|
+
proposal as unknown as Record<string, unknown>,
|
|
247
|
+
);
|
|
248
|
+
const currentPending = await readAppState(pendingKey);
|
|
249
|
+
if (currentPending?.repromptId !== repromptId) {
|
|
250
|
+
await compareAndSetAppState(
|
|
251
|
+
proposalKey,
|
|
252
|
+
proposal as unknown as Record<string, unknown>,
|
|
253
|
+
null,
|
|
254
|
+
);
|
|
255
|
+
throw new Error(
|
|
256
|
+
"This regeneration was superseded by a newer request before its candidates were published.",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
if (pending.priorProposalId && pending.priorRepromptId) {
|
|
260
|
+
const priorProposalKey = designRepromptProposalStateKey(
|
|
261
|
+
file.designId,
|
|
262
|
+
file.id,
|
|
263
|
+
pending.priorRepromptId,
|
|
264
|
+
);
|
|
265
|
+
const priorProposal = await readAppState(priorProposalKey);
|
|
266
|
+
if (priorProposal?.proposalId === pending.priorProposalId) {
|
|
267
|
+
await compareAndSetAppState(priorProposalKey, priorProposal, null);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const bridgeMessages: NodeHtmlPreviewBridgeMessage[] = [
|
|
272
|
+
{
|
|
273
|
+
type: "node-html-preview",
|
|
274
|
+
proposalId,
|
|
275
|
+
target: resolvedTarget,
|
|
276
|
+
html: validatedVariants[0]!.html,
|
|
277
|
+
operation: "preview",
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
return {
|
|
281
|
+
proposalId,
|
|
282
|
+
repromptId,
|
|
283
|
+
designId: file.designId,
|
|
284
|
+
fileId: file.id,
|
|
285
|
+
target: resolvedTarget,
|
|
286
|
+
variants: validatedVariants,
|
|
287
|
+
bridgeMessages,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
});
|