@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.
Files changed (194) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +39 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/index.ts +2 -0
  5. package/corpus/core/src/application-state/script-helpers.ts +12 -0
  6. package/corpus/core/src/application-state/store.ts +38 -0
  7. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +5 -1
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +8 -0
  10. package/corpus/core/src/client/composer/TiptapComposer.tsx +38 -1
  11. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +107 -32
  12. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +33 -0
  13. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  14. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
  15. package/corpus/core/src/client/use-chat-models.ts +10 -2
  16. package/corpus/core/src/connections/catalog.ts +107 -6
  17. package/corpus/core/src/localization/default-messages.ts +2 -0
  18. package/corpus/core/src/mcp/actions/call-mcp-tool.ts +18 -0
  19. package/corpus/core/src/mcp/actions/list-mcp-tools.ts +18 -0
  20. package/corpus/core/src/mcp-client/app-api.ts +136 -0
  21. package/corpus/core/src/mcp-client/index.ts +21 -0
  22. package/corpus/core/src/mcp-client/oauth-client.ts +462 -0
  23. package/corpus/core/src/mcp-client/oauth-routes.ts +307 -0
  24. package/corpus/core/src/mcp-client/remote-store.ts +93 -118
  25. package/corpus/core/src/mcp-client/remote-url.ts +97 -0
  26. package/corpus/core/src/mcp-client/routes.ts +11 -0
  27. package/corpus/core/src/oauth-tokens/store.ts +10 -4
  28. package/corpus/core/src/provider-api/index.ts +690 -55
  29. package/corpus/core/src/secrets/register-framework-secrets.ts +30 -0
  30. package/corpus/core/src/server/action-discovery.ts +2 -0
  31. package/corpus/core/src/server/core-routes-plugin.ts +9 -1
  32. package/corpus/core/src/server/workspace-provider-oauth.ts +448 -61
  33. package/corpus/core/src/templates/default/AGENTS.md +9 -7
  34. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  35. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -0
  36. package/corpus/templates/analytics/app/pages/DataSources.tsx +61 -0
  37. package/corpus/templates/analytics/server/handlers/jira.ts +7 -0
  38. package/corpus/templates/analytics/server/lib/jira.ts +14 -42
  39. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +11 -0
  40. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
  41. package/corpus/templates/design/AGENTS.md +16 -0
  42. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
  43. package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
  44. package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
  45. package/corpus/templates/design/actions/view-screen.ts +98 -2
  46. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
  47. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
  48. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
  49. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
  50. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
  51. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
  52. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  53. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
  54. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
  55. package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
  56. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
  57. package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
  58. package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
  59. package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
  60. package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
  61. package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
  62. package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
  63. package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
  64. package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
  65. package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
  66. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  67. package/corpus/templates/design/app/i18n-data.ts +32 -0
  68. package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
  69. package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
  70. package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
  71. package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
  72. package/corpus/templates/design/shared/code-layer.ts +15 -0
  73. package/corpus/templates/design/shared/node-rewrite.ts +213 -0
  74. package/corpus/templates/design/shared/review-anchor.ts +26 -3
  75. package/corpus/templates/dispatch/app/routes/integrations.tsx +57 -4
  76. package/dist/application-state/index.d.ts +2 -2
  77. package/dist/application-state/index.d.ts.map +1 -1
  78. package/dist/application-state/index.js +2 -2
  79. package/dist/application-state/index.js.map +1 -1
  80. package/dist/application-state/script-helpers.d.ts +1 -0
  81. package/dist/application-state/script-helpers.d.ts.map +1 -1
  82. package/dist/application-state/script-helpers.js +7 -1
  83. package/dist/application-state/script-helpers.js.map +1 -1
  84. package/dist/application-state/store.d.ts +1 -0
  85. package/dist/application-state/store.d.ts.map +1 -1
  86. package/dist/application-state/store.js +28 -0
  87. package/dist/application-state/store.js.map +1 -1
  88. package/dist/client/AssistantChat.d.ts +2 -0
  89. package/dist/client/AssistantChat.d.ts.map +1 -1
  90. package/dist/client/AssistantChat.js +2 -2
  91. package/dist/client/AssistantChat.js.map +1 -1
  92. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  93. package/dist/client/MultiTabAssistantChat.js +5 -2
  94. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  95. package/dist/client/composer/PromptComposer.d.ts +2 -0
  96. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  97. package/dist/client/composer/PromptComposer.js +5 -2
  98. package/dist/client/composer/PromptComposer.js.map +1 -1
  99. package/dist/client/composer/TiptapComposer.d.ts +4 -1
  100. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  101. package/dist/client/composer/TiptapComposer.js +13 -5
  102. package/dist/client/composer/TiptapComposer.js.map +1 -1
  103. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  104. package/dist/client/resources/McpIntegrationDialog.js +58 -14
  105. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  106. package/dist/client/resources/mcp-integration-catalog.d.ts +9 -0
  107. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  108. package/dist/client/resources/mcp-integration-catalog.js +13 -0
  109. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  110. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  111. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  112. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  113. package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
  114. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  115. package/dist/client/review/ReviewCommentComposer.js +3 -3
  116. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  117. package/dist/client/use-chat-models.d.ts +1 -0
  118. package/dist/client/use-chat-models.d.ts.map +1 -1
  119. package/dist/client/use-chat-models.js +8 -2
  120. package/dist/client/use-chat-models.js.map +1 -1
  121. package/dist/collab/struct-routes.d.ts +1 -1
  122. package/dist/connections/catalog.d.ts +78 -7
  123. package/dist/connections/catalog.d.ts.map +1 -1
  124. package/dist/connections/catalog.js +97 -6
  125. package/dist/connections/catalog.js.map +1 -1
  126. package/dist/localization/default-messages.d.ts +2 -0
  127. package/dist/localization/default-messages.d.ts.map +1 -1
  128. package/dist/localization/default-messages.js +2 -0
  129. package/dist/localization/default-messages.js.map +1 -1
  130. package/dist/mcp/actions/call-mcp-tool.d.ts +7 -0
  131. package/dist/mcp/actions/call-mcp-tool.d.ts.map +1 -0
  132. package/dist/mcp/actions/call-mcp-tool.js +15 -0
  133. package/dist/mcp/actions/call-mcp-tool.js.map +1 -0
  134. package/dist/mcp/actions/list-mcp-tools.d.ts +5 -0
  135. package/dist/mcp/actions/list-mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp/actions/list-mcp-tools.js +16 -0
  137. package/dist/mcp/actions/list-mcp-tools.js.map +1 -0
  138. package/dist/mcp-client/app-api.d.ts +34 -0
  139. package/dist/mcp-client/app-api.d.ts.map +1 -0
  140. package/dist/mcp-client/app-api.js +94 -0
  141. package/dist/mcp-client/app-api.js.map +1 -0
  142. package/dist/mcp-client/index.d.ts +3 -1
  143. package/dist/mcp-client/index.d.ts.map +1 -1
  144. package/dist/mcp-client/index.js +3 -1
  145. package/dist/mcp-client/index.js.map +1 -1
  146. package/dist/mcp-client/oauth-client.d.ts +113 -0
  147. package/dist/mcp-client/oauth-client.d.ts.map +1 -0
  148. package/dist/mcp-client/oauth-client.js +316 -0
  149. package/dist/mcp-client/oauth-client.js.map +1 -0
  150. package/dist/mcp-client/oauth-routes.d.ts +25 -0
  151. package/dist/mcp-client/oauth-routes.d.ts.map +1 -0
  152. package/dist/mcp-client/oauth-routes.js +218 -0
  153. package/dist/mcp-client/oauth-routes.js.map +1 -0
  154. package/dist/mcp-client/remote-store.d.ts +21 -6
  155. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  156. package/dist/mcp-client/remote-store.js +72 -124
  157. package/dist/mcp-client/remote-store.js.map +1 -1
  158. package/dist/mcp-client/remote-url.d.ts +8 -0
  159. package/dist/mcp-client/remote-url.d.ts.map +1 -0
  160. package/dist/mcp-client/remote-url.js +103 -0
  161. package/dist/mcp-client/remote-url.js.map +1 -0
  162. package/dist/mcp-client/routes.d.ts +1 -0
  163. package/dist/mcp-client/routes.d.ts.map +1 -1
  164. package/dist/mcp-client/routes.js +9 -0
  165. package/dist/mcp-client/routes.js.map +1 -1
  166. package/dist/notifications/routes.d.ts +2 -2
  167. package/dist/oauth-tokens/store.d.ts +2 -2
  168. package/dist/oauth-tokens/store.d.ts.map +1 -1
  169. package/dist/oauth-tokens/store.js +10 -6
  170. package/dist/oauth-tokens/store.js.map +1 -1
  171. package/dist/observability/routes.d.ts +5 -5
  172. package/dist/provider-api/index.d.ts +14 -0
  173. package/dist/provider-api/index.d.ts.map +1 -1
  174. package/dist/provider-api/index.js +499 -61
  175. package/dist/provider-api/index.js.map +1 -1
  176. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  177. package/dist/secrets/register-framework-secrets.js +28 -0
  178. package/dist/secrets/register-framework-secrets.js.map +1 -1
  179. package/dist/secrets/routes.d.ts +9 -9
  180. package/dist/server/action-discovery.d.ts.map +1 -1
  181. package/dist/server/action-discovery.js +2 -0
  182. package/dist/server/action-discovery.js.map +1 -1
  183. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  184. package/dist/server/core-routes-plugin.js +9 -1
  185. package/dist/server/core-routes-plugin.js.map +1 -1
  186. package/dist/server/workspace-provider-oauth.d.ts +10 -3
  187. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  188. package/dist/server/workspace-provider-oauth.js +325 -43
  189. package/dist/server/workspace-provider-oauth.js.map +1 -1
  190. package/dist/templates/default/AGENTS.md +9 -7
  191. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
  192. package/package.json +2 -2
  193. package/src/templates/default/AGENTS.md +9 -7
  194. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +46 -0
@@ -0,0 +1,107 @@
1
+ import { getThread, type ActionEntry } from "@agent-native/core/server";
2
+
3
+ const REPROMPT_MUTATION_ALLOWLIST = new Set(["propose-node-rewrite"]);
4
+
5
+ export function isRepromptSelectionMessage(value: string): boolean {
6
+ return /(?:^|\n)\[Reprompt selection\](?:\n|$)/.test(value);
7
+ }
8
+
9
+ export function isSelectionQuestionMessage(value: string): boolean {
10
+ return /(?:^|\n)\[Selection question\](?:\n|$)/.test(value);
11
+ }
12
+
13
+ function latestUserMessageText(threadData: string): string {
14
+ try {
15
+ const parsed = JSON.parse(threadData) as {
16
+ messages?: Array<{
17
+ message?: {
18
+ role?: unknown;
19
+ content?: unknown;
20
+ };
21
+ role?: unknown;
22
+ content?: unknown;
23
+ }>;
24
+ };
25
+ const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
26
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
27
+ const entry = messages[index]!;
28
+ const message = entry.message ?? entry;
29
+ if (message.role !== "user") continue;
30
+ if (typeof message.content === "string") return message.content;
31
+ if (!Array.isArray(message.content)) return "";
32
+ return message.content
33
+ .filter((part): part is { type: "text"; text: string } =>
34
+ Boolean(
35
+ part &&
36
+ typeof part === "object" &&
37
+ (part as { type?: unknown }).type === "text" &&
38
+ typeof (part as { text?: unknown }).text === "string",
39
+ ),
40
+ )
41
+ .map((part) => part.text)
42
+ .join("\n");
43
+ }
44
+ } catch {}
45
+ return "";
46
+ }
47
+
48
+ function threadSelectionIntent(thread: {
49
+ preview: string;
50
+ threadData: string;
51
+ }): "reprompt" | "question" | null {
52
+ const latestMessage = latestUserMessageText(thread.threadData);
53
+ if (
54
+ isRepromptSelectionMessage(thread.preview) ||
55
+ isRepromptSelectionMessage(latestMessage)
56
+ ) {
57
+ return "reprompt";
58
+ }
59
+ if (
60
+ isSelectionQuestionMessage(thread.preview) ||
61
+ isSelectionQuestionMessage(latestMessage)
62
+ ) {
63
+ return "question";
64
+ }
65
+ return null;
66
+ }
67
+
68
+ export function guardRepromptActionRegistry(
69
+ actions: Record<string, ActionEntry>,
70
+ ): Record<string, ActionEntry> {
71
+ return Object.fromEntries(
72
+ Object.entries(actions).map(([name, entry]) => {
73
+ if (entry.readOnly === true) return [name, entry];
74
+ return [
75
+ name,
76
+ {
77
+ ...entry,
78
+ run: async (args, context) => {
79
+ if (context?.caller === "tool" && context.threadId) {
80
+ const thread = await getThread(context.threadId);
81
+ if (!thread) {
82
+ throw new Error(
83
+ `Cannot verify the agent thread for mutating action ${name}.`,
84
+ );
85
+ }
86
+ const intent = threadSelectionIntent(thread);
87
+ if (
88
+ intent === "reprompt" &&
89
+ !REPROMPT_MUTATION_ALLOWLIST.has(name)
90
+ ) {
91
+ throw new Error(
92
+ `The current [Reprompt selection] turn is preview-only. Do not call ${name}; call propose-node-rewrite with the captured repromptId, target, and baseVersionHash instead.`,
93
+ );
94
+ }
95
+ if (intent === "question") {
96
+ throw new Error(
97
+ `The current [Selection question] turn is read-only. Answer the user's question without calling ${name} or changing the design.`,
98
+ );
99
+ }
100
+ }
101
+ return entry.run(args, context);
102
+ },
103
+ },
104
+ ];
105
+ }),
106
+ );
107
+ }
@@ -5,6 +5,7 @@ import {
5
5
  } from "@agent-native/core/server";
6
6
 
7
7
  import actionsRegistry from "../../.generated/actions-registry.js";
8
+ import { guardRepromptActionRegistry } from "../lib/reprompt-action-guard.js";
8
9
  import "../register-secrets.js";
9
10
 
10
11
  const DESIGN_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
@@ -33,6 +34,7 @@ const INITIAL_TOOL_NAMES = [
33
34
  "edit-design",
34
35
  "generate-design",
35
36
  "present-design-variants",
37
+ "propose-node-rewrite",
36
38
  "insert-asset",
37
39
  "connect-assets-mcp",
38
40
  "apply-tweaks",
@@ -48,7 +50,9 @@ const INITIAL_TOOL_NAMES = [
48
50
 
49
51
  export default createAgentChatPlugin({
50
52
  appId: "design",
51
- actions: loadActionsFromStaticRegistry(actionsRegistry),
53
+ actions: guardRepromptActionRegistry(
54
+ loadActionsFromStaticRegistry(actionsRegistry),
55
+ ),
52
56
  initialToolNames: INITIAL_TOOL_NAMES,
53
57
  // Enable sandboxed JavaScript execution so Design agents can fetch,
54
58
  // paginate, and reduce provider data through providerFetch() without us
@@ -60,6 +64,10 @@ export default createAgentChatPlugin({
60
64
  resolveOrgId: async (event) => (await getOrgContext(event)).orgId,
61
65
  systemPrompt: `You are an AI prototyping assistant. You create and edit designs, files, design systems, variants, exports, sharing, and connected repository context through actions and shared application state.
62
66
 
67
+ When a user message begins with [Reprompt selection], the design must remain unchanged until the user accepts a preview. Call propose-node-rewrite with the exact repromptId, target, and baseVersionHash from the message. Never call edit-design, update-design, update-file, generate-design, apply-visual-edit, or any other content-writing action for that turn. The proposal action stores preview state only; the frontend-only resolve-node-rewrite action persists a chosen variant after the user presses Accept.
68
+
69
+ When a user message begins with [Selection question], answer about the captured element and selected subtree without changing the design. You may use read-only actions when more context is needed, but do not call any content-writing action. If the user actually intended an edit, explain that they can choose Preview change from the composer mode menu and resend.
70
+
63
71
  When the user asks for a new design and the current navigation view is list, settings, design-systems, or otherwise has no designId, create a new design first. Do not reuse, delete screens from, or edit a previous design unless the user explicitly names that design or the current navigation state is an editor/present view with that designId.
64
72
 
65
73
  Every web design must be responsive. Use mobile-first CSS, a viewport meta tag, and responsive layout changes for narrow widths; never ship a fixed-width desktop shell. Desktop is the default primary artboard: use a 1440×1024 canvas frame (or primaryViewport "desktop") unless the user explicitly asks for a mobile- or tablet-primary design. After generation, inspect desktop and mobile screenshots and correct overflow or broken reflow before reporting completion.
@@ -2709,6 +2709,21 @@ function resolveTarget(
2709
2709
  };
2710
2710
  }
2711
2711
 
2712
+ export function resolveCodeLayerTarget(
2713
+ html: string,
2714
+ target: EditIntentTarget,
2715
+ options: { source?: CodeLayerSource } = {},
2716
+ ): { projection: CodeLayerProjection; resolution: EditIntentResolution } {
2717
+ const build = buildProjection(
2718
+ html,
2719
+ options.source ?? { kind: "inline-html" },
2720
+ );
2721
+ return {
2722
+ projection: build.projection,
2723
+ resolution: resolveTarget(build, target),
2724
+ };
2725
+ }
2726
+
2712
2727
  function summarizeNode(node: CodeLayerNode): PatchNodeSummary {
2713
2728
  return {
2714
2729
  nodeId: node.id,
@@ -0,0 +1,213 @@
1
+ import type {
2
+ CodeLayerNode,
3
+ CodeLayerSource,
4
+ EditIntentTarget,
5
+ } from "./code-layer.js";
6
+ import { resolveCodeLayerTarget } from "./code-layer.js";
7
+ import { assertDesignHtmlEditIntegrity } from "./html-integrity.js";
8
+ import { annotateScreenHtmlForPersist } from "./screen-annotation.js";
9
+
10
+ export const DESIGN_REPROMPT_PENDING_STATE_PREFIX = "design-reprompt-pending:";
11
+ export const DESIGN_REPROMPT_PROPOSAL_STATE_PREFIX =
12
+ "design-reprompt-proposal:";
13
+ export const MAX_NODE_REWRITE_PROPOSAL_BYTES = 256 * 1024;
14
+
15
+ export type NodeRewriteTarget = EditIntentTarget;
16
+
17
+ export interface PendingDesignReprompt {
18
+ repromptId: string;
19
+ designId: string;
20
+ fileId: string;
21
+ target: NodeRewriteTarget;
22
+ baseVersionHash: string;
23
+ instruction: string;
24
+ createdAt: string;
25
+ priorProposalId?: string;
26
+ priorRepromptId?: string;
27
+ }
28
+
29
+ export interface NodeRewriteVariant {
30
+ html: string;
31
+ summary: string;
32
+ }
33
+
34
+ export interface NodeRewriteProposal {
35
+ proposalId: string;
36
+ repromptId: string;
37
+ designId: string;
38
+ fileId: string;
39
+ filename: string;
40
+ target: NodeRewriteTarget;
41
+ resolvedTarget: Required<NodeRewriteTarget>;
42
+ baseVersionHash: string;
43
+ variants: NodeRewriteVariant[];
44
+ chosenIndex: number;
45
+ createdAt: string;
46
+ }
47
+
48
+ export function isNodeRewriteProposal(
49
+ value: unknown,
50
+ ): value is NodeRewriteProposal {
51
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
52
+ const proposal = value as Partial<NodeRewriteProposal>;
53
+ return (
54
+ typeof proposal.proposalId === "string" &&
55
+ typeof proposal.repromptId === "string" &&
56
+ typeof proposal.designId === "string" &&
57
+ typeof proposal.fileId === "string" &&
58
+ typeof proposal.baseVersionHash === "string" &&
59
+ Boolean(proposal.target) &&
60
+ Boolean(proposal.resolvedTarget) &&
61
+ Array.isArray(proposal.variants) &&
62
+ proposal.variants.length > 0
63
+ );
64
+ }
65
+
66
+ export function isPendingDesignReprompt(
67
+ value: unknown,
68
+ ): value is PendingDesignReprompt {
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
70
+ const pending = value as Partial<PendingDesignReprompt>;
71
+ return (
72
+ typeof pending.repromptId === "string" &&
73
+ typeof pending.designId === "string" &&
74
+ typeof pending.fileId === "string" &&
75
+ typeof pending.baseVersionHash === "string" &&
76
+ typeof pending.instruction === "string" &&
77
+ typeof pending.createdAt === "string" &&
78
+ Boolean(pending.target)
79
+ );
80
+ }
81
+
82
+ export interface NodeHtmlPreviewBridgeMessage {
83
+ type: "node-html-preview";
84
+ proposalId: string;
85
+ target: NodeRewriteTarget;
86
+ operation: "preview" | "restore";
87
+ html?: string;
88
+ }
89
+
90
+ export function designRepromptPendingStateKey(
91
+ designId: string,
92
+ fileId: string,
93
+ ): string {
94
+ return `${DESIGN_REPROMPT_PENDING_STATE_PREFIX}${designId}:${fileId}`;
95
+ }
96
+
97
+ export function designRepromptProposalStateKey(
98
+ designId: string,
99
+ fileId: string,
100
+ repromptId: string,
101
+ ): string {
102
+ return `${DESIGN_REPROMPT_PROPOSAL_STATE_PREFIX}${designId}:${fileId}:${repromptId}`;
103
+ }
104
+
105
+ export function designRepromptProposalStatePrefix(
106
+ designId: string,
107
+ fileId?: string,
108
+ ): string {
109
+ return `${DESIGN_REPROMPT_PROPOSAL_STATE_PREFIX}${designId}:${fileId ? `${fileId}:` : ""}`;
110
+ }
111
+
112
+ function assertSingleElementFragment(
113
+ html: string,
114
+ source: CodeLayerSource,
115
+ ): void {
116
+ if (!html.trim()) throw new Error("Variant HTML must not be empty.");
117
+ if (/<\/?(?:html|head|body)\b|<!doctype\b/i.test(html)) {
118
+ throw new Error("Variant HTML must be one subtree, not a full document.");
119
+ }
120
+
121
+ const { projection } = resolveCodeLayerTarget(
122
+ html,
123
+ { selector: "__node_rewrite_validation_target__" },
124
+ { source },
125
+ );
126
+ if (projection.rootNodeIds.length !== 1) {
127
+ throw new Error("Variant HTML must contain exactly one root element.");
128
+ }
129
+ const root = projection.nodes.find(
130
+ (node) => node.id === projection.rootNodeIds[0],
131
+ );
132
+ if (!root?.source) {
133
+ throw new Error("Variant HTML could not be parsed as an element subtree.");
134
+ }
135
+ if (
136
+ html.slice(0, root.source.start).trim() ||
137
+ html.slice(root.source.end).trim()
138
+ ) {
139
+ throw new Error("Variant HTML must contain exactly one root element.");
140
+ }
141
+
142
+ const voidElements = new Set([
143
+ "area",
144
+ "base",
145
+ "br",
146
+ "col",
147
+ "embed",
148
+ "hr",
149
+ "img",
150
+ "input",
151
+ "link",
152
+ "meta",
153
+ "param",
154
+ "source",
155
+ "track",
156
+ "wbr",
157
+ ]);
158
+ const unclosed = projection.nodes.find(
159
+ (node) =>
160
+ node.source &&
161
+ !voidElements.has(node.tag) &&
162
+ node.source.closeEnd === undefined,
163
+ );
164
+ if (unclosed) {
165
+ throw new Error(`Variant HTML has an unclosed <${unclosed.tag}> element.`);
166
+ }
167
+ }
168
+
169
+ export function validateNodeRewriteVariant(
170
+ variant: NodeRewriteVariant,
171
+ source: CodeLayerSource,
172
+ ): NodeRewriteVariant {
173
+ const html = variant.html.trim();
174
+ assertSingleElementFragment(html, source);
175
+ return { html, summary: variant.summary.trim() };
176
+ }
177
+
178
+ export function resolveNodeRewriteTarget(
179
+ content: string,
180
+ target: NodeRewriteTarget,
181
+ source: CodeLayerSource,
182
+ ): CodeLayerNode {
183
+ const { resolution } = resolveCodeLayerTarget(content, target, { source });
184
+ if (resolution.status !== "resolved" || !resolution.node?.source) {
185
+ throw new Error(
186
+ `Target missing — re-anchor the selection. ${resolution.message ?? "The selected node no longer exists."}`,
187
+ );
188
+ }
189
+ return resolution.node;
190
+ }
191
+
192
+ export function spliceNodeRewriteVariant(args: {
193
+ content: string;
194
+ target: NodeRewriteTarget;
195
+ variant: NodeRewriteVariant;
196
+ source: CodeLayerSource;
197
+ fileType: string;
198
+ }): { content: string; node: CodeLayerNode; variant: NodeRewriteVariant } {
199
+ const node = resolveNodeRewriteTarget(args.content, args.target, args.source);
200
+ const variant = validateNodeRewriteVariant(args.variant, args.source);
201
+ const replacement = annotateScreenHtmlForPersist(variant.html, args.fileType);
202
+ const nextContent = `${args.content.slice(0, node.source!.start)}${replacement}${args.content.slice(node.source!.end)}`;
203
+ assertDesignHtmlEditIntegrity({
204
+ previousContent: args.content,
205
+ nextContent,
206
+ fileType: args.fileType,
207
+ });
208
+ return {
209
+ content: nextContent,
210
+ node,
211
+ variant: { ...variant, html: replacement },
212
+ };
213
+ }
@@ -5,22 +5,25 @@ export interface ReviewAnchorPoint {
5
5
 
6
6
  export interface DesignReviewAnchor {
7
7
  nodeId?: string;
8
+ selector?: string;
8
9
  point: ReviewAnchorPoint;
9
10
  }
10
11
 
11
12
  export interface ResolvedReviewAnchor {
12
13
  anchor: DesignReviewAnchor;
13
14
  point: ReviewAnchorPoint;
14
- source: "node" | "point";
15
+ source: "node" | "selector" | "point";
15
16
  }
16
17
 
17
18
  export function createElementReviewAnchor(input: {
18
19
  nodeId?: string | null;
20
+ selector?: string | null;
19
21
  rect?: { x: number; y: number; width: number; height: number } | null;
20
22
  viewportWidth?: number | null;
21
23
  viewportHeight?: number | null;
22
24
  }): DesignReviewAnchor | null {
23
25
  const nodeId = input.nodeId?.trim() ?? "";
26
+ const selector = input.selector?.trim() ?? "";
24
27
  const rect = input.rect;
25
28
  const hasRect = Boolean(
26
29
  rect &&
@@ -37,7 +40,7 @@ export function createElementReviewAnchor(input: {
37
40
  Number.isFinite(input.viewportHeight) &&
38
41
  input.viewportHeight > 0,
39
42
  );
40
- if (!nodeId && !hasRect) return null;
43
+ if (!nodeId && !selector && !hasRect) return null;
41
44
 
42
45
  const point = hasRect
43
46
  ? {
@@ -58,6 +61,7 @@ export function createElementReviewAnchor(input: {
58
61
 
59
62
  return {
60
63
  ...(nodeId ? { nodeId } : {}),
64
+ ...(selector ? { selector } : {}),
61
65
  point,
62
66
  };
63
67
  }
@@ -100,16 +104,21 @@ export function parseReviewAnchor(value: unknown): DesignReviewAnchor | null {
100
104
  if (xPct === null || yPct === null) return null;
101
105
 
102
106
  const nodeId = typeof record.nodeId === "string" ? record.nodeId.trim() : "";
107
+ const selector =
108
+ typeof record.selector === "string" ? record.selector.trim() : "";
103
109
  return {
104
110
  ...(nodeId ? { nodeId } : {}),
111
+ ...(selector ? { selector } : {}),
105
112
  point: { xPct, yPct },
106
113
  };
107
114
  }
108
115
 
109
- /** Resolve a node-id position first, then degrade to the stored click point. */
116
+ /** Resolve a node or selector position first, then degrade to the click point. */
110
117
  export function resolveReviewAnchor(
111
118
  value: unknown,
112
119
  resolveNodePoint: (nodeId: string) => ReviewAnchorPoint | null,
120
+ resolveSelectorPoint: (selector: string) => ReviewAnchorPoint | null = () =>
121
+ null,
113
122
  ): ResolvedReviewAnchor | null {
114
123
  const anchor = parseReviewAnchor(value);
115
124
  if (!anchor) return null;
@@ -127,5 +136,19 @@ export function resolveReviewAnchor(
127
136
  }
128
137
  }
129
138
  }
139
+ if (anchor.selector) {
140
+ const selectorPoint = resolveSelectorPoint(anchor.selector);
141
+ if (selectorPoint) {
142
+ const xPct = inBoundsPercentage(selectorPoint.xPct);
143
+ const yPct = inBoundsPercentage(selectorPoint.yPct);
144
+ if (xPct !== null && yPct !== null) {
145
+ return {
146
+ anchor,
147
+ point: { xPct, yPct },
148
+ source: "selector",
149
+ };
150
+ }
151
+ }
152
+ }
130
153
  return { anchor, point: anchor.point, source: "point" };
131
154
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ agentNativePath,
2
3
  useActionMutation,
3
4
  useActionQuery,
4
5
  useT,
@@ -127,6 +128,13 @@ interface WorkspaceConnectionProvider {
127
128
  credentialKeys: WorkspaceConnectionCredentialKey[];
128
129
  capabilities: string[];
129
130
  recommendedTemplateUses: string[];
131
+ oauth?: {
132
+ provider: string;
133
+ authorizationUrl: string;
134
+ tokenUrl: string;
135
+ refreshUrl?: string;
136
+ scopes: string[];
137
+ };
130
138
  readiness?: WorkspaceConnectionProviderReadiness;
131
139
  }
132
140
 
@@ -718,6 +726,32 @@ function summarizeAppList(
718
726
  : t("integrations.noApps");
719
727
  }
720
728
 
729
+ function startWorkspaceProviderOAuth(provider: WorkspaceConnectionProvider) {
730
+ const returnPath = `${window.location.pathname}${window.location.search}`;
731
+ if (provider.id === "slack") {
732
+ const params = new URLSearchParams({ return: returnPath });
733
+ window.location.assign(
734
+ agentNativePath(
735
+ `/_agent-native/integrations/slack/oauth/install?${params.toString()}`,
736
+ ),
737
+ );
738
+ return;
739
+ }
740
+ const params = new URLSearchParams({
741
+ appId: "dispatch",
742
+ return: returnPath,
743
+ });
744
+ window.location.assign(
745
+ agentNativePath(
746
+ `/_agent-native/connections/oauth/${provider.id}/start?${params.toString()}`,
747
+ ),
748
+ );
749
+ }
750
+
751
+ function providerHasOAuth(provider: WorkspaceConnectionProvider): boolean {
752
+ return Boolean(provider.oauth) || provider.id === "slack";
753
+ }
754
+
721
755
  function ProviderCard({
722
756
  provider,
723
757
  connections,
@@ -788,10 +822,29 @@ function ProviderCard({
788
822
  count: provider.credentialKeys.length,
789
823
  })}
790
824
  </span>
791
- <Button type="button" variant="outline" size="sm" onClick={onCreate}>
792
- <IconPlus size={14} />
793
- {t("integrations.connect")}
794
- </Button>
825
+ <div className="flex items-center gap-2">
826
+ {providerHasOAuth(provider) ? (
827
+ <Button
828
+ type="button"
829
+ size="sm"
830
+ onClick={() => startWorkspaceProviderOAuth(provider)}
831
+ >
832
+ <IconShieldCheck size={14} />
833
+ {t("integrations.connect")}
834
+ </Button>
835
+ ) : null}
836
+ <Button
837
+ type="button"
838
+ variant={providerHasOAuth(provider) ? "ghost" : "outline"}
839
+ size="sm"
840
+ onClick={onCreate}
841
+ >
842
+ <IconKey size={14} />
843
+ {providerHasOAuth(provider)
844
+ ? t("integrations.connectionFallback")
845
+ : t("integrations.connect")}
846
+ </Button>
847
+ </div>
795
848
  </div>
796
849
  </article>
797
850
  );
@@ -1,5 +1,5 @@
1
- export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
1
+ export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateCompareAndSet, appStateList, appStateDeleteByPrefix, } from "./store.js";
2
2
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, type AppStateEvent, } from "./emitter.js";
3
3
  export { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
4
- export { readAppState, writeAppState, deleteAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
4
+ export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC"}
@@ -1,9 +1,9 @@
1
1
  // Store
2
- export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
2
+ export { appStateGet, appStateGetMany, appStatePut, appStateDelete, appStateCompareAndSet, appStateList, appStateDeleteByPrefix, } from "./store.js";
3
3
  // Emitter (for SSE wiring)
4
4
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, } from "./emitter.js";
5
5
  // H3 route handlers (for templates)
6
6
  export { getState, putState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
7
7
  // Script helpers
8
- export { readAppState, writeAppState, deleteAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
8
+ export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
9
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AAAA,QAAQ;AACR,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,2BAA2B;AAC3B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,cAAc,CAAC;AAEtB,oCAAoC;AACpC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC","sourcesContent":["// Store\nexport {\n appStateGet,\n appStateGetMany,\n appStatePut,\n appStateDelete,\n appStateList,\n appStateDeleteByPrefix,\n} from \"./store.js\";\n\n// Emitter (for SSE wiring)\nexport {\n getAppStateEmitter,\n emitAppStateChange,\n emitAppStateDelete,\n type AppStateEvent,\n} from \"./emitter.js\";\n\n// H3 route handlers (for templates)\nexport {\n getState,\n putState,\n deleteState,\n listComposeDrafts,\n getComposeDraft,\n putComposeDraft,\n deleteComposeDraft,\n deleteAllComposeDrafts,\n} from \"./handlers.js\";\n\n// Script helpers\nexport {\n readAppState,\n writeAppState,\n deleteAppState,\n listAppState,\n deleteAppStateByPrefix,\n readAppStateForCurrentTab,\n writeAppStateForCurrentTab,\n appStateKeyForBrowserTab,\n getCurrentRequestBrowserTabId,\n} from \"./script-helpers.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/application-state/index.ts"],"names":[],"mappings":"AAAA,QAAQ;AACR,OAAO,EACL,WAAW,EACX,eAAe,EACf,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,2BAA2B;AAC3B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,cAAc,CAAC;AAEtB,oCAAoC;AACpC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,iBAAiB;AACjB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,6BAA6B,GAC9B,MAAM,qBAAqB,CAAC","sourcesContent":["// Store\nexport {\n appStateGet,\n appStateGetMany,\n appStatePut,\n appStateDelete,\n appStateCompareAndSet,\n appStateList,\n appStateDeleteByPrefix,\n} from \"./store.js\";\n\n// Emitter (for SSE wiring)\nexport {\n getAppStateEmitter,\n emitAppStateChange,\n emitAppStateDelete,\n type AppStateEvent,\n} from \"./emitter.js\";\n\n// H3 route handlers (for templates)\nexport {\n getState,\n putState,\n deleteState,\n listComposeDrafts,\n getComposeDraft,\n putComposeDraft,\n deleteComposeDraft,\n deleteAllComposeDrafts,\n} from \"./handlers.js\";\n\n// Script helpers\nexport {\n readAppState,\n writeAppState,\n deleteAppState,\n compareAndSetAppState,\n listAppState,\n deleteAppStateByPrefix,\n readAppStateForCurrentTab,\n writeAppStateForCurrentTab,\n appStateKeyForBrowserTab,\n getCurrentRequestBrowserTabId,\n} from \"./script-helpers.js\";\n"]}
@@ -14,6 +14,7 @@
14
14
  export declare function readAppState(key: string): Promise<Record<string, unknown> | null>;
15
15
  export declare function writeAppState(key: string, value: Record<string, unknown>): Promise<void>;
16
16
  export declare function deleteAppState(key: string): Promise<boolean>;
17
+ export declare function compareAndSetAppState(key: string, expectedValue: Record<string, unknown>, nextValue: Record<string, unknown> | null): Promise<boolean>;
17
18
  export declare function listAppState(prefix: string): Promise<Array<{
18
19
  key: string;
19
20
  value: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"script-helpers.d.ts","sourceRoot":"","sources":["../../src/application-state/script-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqCH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAGzC;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKlE;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAGjE;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAUD;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,CAM7D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,OAAO,GACpB,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAQzC;AAED,oEAAoE;AACpE,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAGf"}
1
+ {"version":3,"file":"script-helpers.d.ts","sourceRoot":"","sources":["../../src/application-state/script-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsCH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAGzC;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAKlE;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACxC,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAGjE;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAUD;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,CAM7D;AAED,uEAAuE;AACvE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,OAAO,GACpB,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GACvC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAQzC;AAED,oEAAoE;AACpE,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -12,7 +12,7 @@
12
12
  * context) should fall through to the env var.
13
13
  */
14
14
  import { getRequestRunContext } from "../server/request-context.js";
15
- import { appStateGet, appStatePut, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
15
+ import { appStateGet, appStatePut, appStateDelete, appStateCompareAndSet, appStateList, appStateDeleteByPrefix, } from "./store.js";
16
16
  /**
17
17
  * Resolve session ID for the current caller.
18
18
  *
@@ -52,6 +52,12 @@ export async function deleteAppState(key) {
52
52
  requestSource: "agent",
53
53
  });
54
54
  }
55
+ export async function compareAndSetAppState(key, expectedValue, nextValue) {
56
+ const sessionId = await resolveSessionId();
57
+ return appStateCompareAndSet(sessionId, key, expectedValue, nextValue, {
58
+ requestSource: "agent",
59
+ });
60
+ }
55
61
  export async function listAppState(prefix) {
56
62
  const sessionId = await resolveSessionId();
57
63
  return appStateList(sessionId, prefix);