@agent-native/core 0.106.0 → 0.106.2

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 (104) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/artifact-response.ts +84 -0
  5. package/corpus/core/src/agent/thread-data-builder.ts +1 -1
  6. package/corpus/core/src/application-state/index.ts +2 -0
  7. package/corpus/core/src/application-state/script-helpers.ts +12 -0
  8. package/corpus/core/src/application-state/store.ts +38 -0
  9. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +10 -3
  10. package/corpus/core/src/integrations/webhook-handler.ts +41 -5
  11. package/corpus/core/src/server/agent-chat-plugin.ts +7 -2
  12. package/corpus/templates/clips/app/components/capture-install-options.tsx +60 -5
  13. package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -1
  14. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +4 -4
  15. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  16. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  17. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  18. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  19. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  20. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  21. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  22. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  23. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  24. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  25. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  26. package/corpus/templates/clips/app/lib/capture-install-options.ts +89 -4
  27. package/corpus/templates/clips/app/routes/_app.dictate.tsx +10 -1
  28. package/corpus/templates/clips/app/routes/record.tsx +1 -0
  29. package/corpus/templates/clips/app/routes/share.$shareId.tsx +7 -0
  30. package/corpus/templates/clips/changelog/2026-07-16-the-desktop-app-button-now-shows-open-desktop-app-and-launch.md +6 -0
  31. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +5 -0
  32. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
  33. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +41 -6
  34. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +5 -0
  35. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  36. package/corpus/templates/content/changelog/2026-07-16-slack-corrections-now-preserve-newer-content-values-unless-y.md +6 -0
  37. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +22 -0
  38. package/corpus/templates/design/AGENTS.md +16 -0
  39. package/corpus/templates/design/actions/cancel-node-rewrite-request.ts +54 -0
  40. package/corpus/templates/design/actions/propose-node-rewrite.ts +290 -0
  41. package/corpus/templates/design/actions/resolve-node-rewrite.ts +257 -0
  42. package/corpus/templates/design/actions/view-screen.ts +98 -2
  43. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +71 -1
  44. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -0
  45. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +33 -0
  46. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +194 -0
  47. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +21 -4
  48. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +18 -0
  49. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
  50. package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +638 -0
  51. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +535 -54
  52. package/corpus/templates/design/app/components/visual-editor/index.ts +5 -1
  53. package/corpus/templates/design/app/components/visual-editor/review-canvas-state.ts +21 -3
  54. package/corpus/templates/design/app/i18n/ar-SA.ts +35 -0
  55. package/corpus/templates/design/app/i18n/de-DE.ts +35 -0
  56. package/corpus/templates/design/app/i18n/es-ES.ts +35 -0
  57. package/corpus/templates/design/app/i18n/fr-FR.ts +35 -0
  58. package/corpus/templates/design/app/i18n/hi-IN.ts +35 -0
  59. package/corpus/templates/design/app/i18n/ja-JP.ts +35 -0
  60. package/corpus/templates/design/app/i18n/ko-KR.ts +35 -0
  61. package/corpus/templates/design/app/i18n/pt-BR.ts +35 -0
  62. package/corpus/templates/design/app/i18n/zh-CN.ts +35 -0
  63. package/corpus/templates/design/app/i18n/zh-TW.ts +32 -0
  64. package/corpus/templates/design/app/i18n-data.ts +32 -0
  65. package/corpus/templates/design/app/lib/node-reprompt.ts +110 -0
  66. package/corpus/templates/design/changelog/2026-07-16-design-editing-and-review-are-now-more-predictable.md +6 -0
  67. package/corpus/templates/design/server/lib/reprompt-action-guard.ts +107 -0
  68. package/corpus/templates/design/server/plugins/agent-chat.ts +9 -1
  69. package/corpus/templates/design/shared/code-layer.ts +15 -0
  70. package/corpus/templates/design/shared/node-rewrite.ts +213 -0
  71. package/corpus/templates/design/shared/review-anchor.ts +26 -3
  72. package/dist/a2a/artifact-response.d.ts +9 -0
  73. package/dist/a2a/artifact-response.d.ts.map +1 -1
  74. package/dist/a2a/artifact-response.js +68 -0
  75. package/dist/a2a/artifact-response.js.map +1 -1
  76. package/dist/agent/thread-data-builder.js +1 -1
  77. package/dist/agent/thread-data-builder.js.map +1 -1
  78. package/dist/application-state/index.d.ts +2 -2
  79. package/dist/application-state/index.d.ts.map +1 -1
  80. package/dist/application-state/index.js +2 -2
  81. package/dist/application-state/index.js.map +1 -1
  82. package/dist/application-state/script-helpers.d.ts +1 -0
  83. package/dist/application-state/script-helpers.d.ts.map +1 -1
  84. package/dist/application-state/script-helpers.js +7 -1
  85. package/dist/application-state/script-helpers.js.map +1 -1
  86. package/dist/application-state/store.d.ts +1 -0
  87. package/dist/application-state/store.d.ts.map +1 -1
  88. package/dist/application-state/store.js +28 -0
  89. package/dist/application-state/store.js.map +1 -1
  90. package/dist/client/review/ReviewCommentComposer.d.ts +4 -1
  91. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  92. package/dist/client/review/ReviewCommentComposer.js +3 -3
  93. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  94. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  95. package/dist/integrations/webhook-handler.js +24 -5
  96. package/dist/integrations/webhook-handler.js.map +1 -1
  97. package/dist/notifications/routes.d.ts +2 -2
  98. package/dist/resources/handlers.d.ts +1 -1
  99. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  100. package/dist/server/agent-chat-plugin.js +3 -1
  101. package/dist/server/agent-chat-plugin.js.map +1 -1
  102. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  103. package/dist/server/transcribe-voice.d.ts +1 -1
  104. package/package.json +1 -1
@@ -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,6 +1,8 @@
1
1
  export interface A2AToolResultSummary {
2
2
  tool: string;
3
3
  result: string;
4
+ isError?: boolean;
5
+ completedSideEffect?: boolean;
4
6
  }
5
7
  export interface A2AArtifactResponseOptions {
6
8
  baseUrl?: string;
@@ -23,4 +25,11 @@ export declare function stripA2APersistedArtifactMarkers(text: string): string;
23
25
  export declare function extractA2AArtifactIdentities(results: A2AToolResultSummary[]): A2AArtifactIdentity[];
24
26
  export declare function appendA2AArtifactLinks(responseText: string, toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string;
25
27
  export declare function buildA2ARecoverableArtifactMessage(toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string | null;
28
+ /**
29
+ * Build a bounded participant-facing receipt from authenticated artifact writes.
30
+ * Unlike generic artifact recovery, this only trusts identities extracted from
31
+ * successful write actions (or a signed downstream write ledger), so a read or
32
+ * an unverified URL cannot be rounded up to a successful mutation.
33
+ */
34
+ export declare function buildA2AVerifiedMutationReceipt(toolResults: A2AToolResultSummary[], options?: A2AArtifactResponseOptions): string | null;
26
35
  //# sourceMappingURL=artifact-response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-response.d.ts","sourceRoot":"","sources":["../../src/a2a/artifact-response.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EACR,UAAU,GACV,MAAM,GACN,WAAW,GACX,UAAU,GACV,OAAO,GACP,QAAQ,GACR,SAAS,GACT,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAgGD,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErE;AA8tBD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,oBAAoB,EAAE,GAC9B,mBAAmB,EAAE,CAmGvB;AA2YD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,oBAAoB,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,MAAM,CA8IR;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,oBAAoB,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,MAAM,GAAG,IAAI,CA8Bf"}
1
+ {"version":3,"file":"artifact-response.d.ts","sourceRoot":"","sources":["../../src/a2a/artifact-response.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EACR,UAAU,GACV,MAAM,GACN,WAAW,GACX,UAAU,GACV,OAAO,GACP,QAAQ,GACR,SAAS,GACT,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAiGD,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErE;AA4uBD;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,oBAAoB,EAAE,GAC9B,mBAAmB,EAAE,CAqGvB;AA2YD,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,oBAAoB,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,MAAM,CA8IR;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,oBAAoB,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,MAAM,GAAG,IAAI,CA8Bf;AAmCD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,oBAAoB,EAAE,EACnC,OAAO,GAAE,0BAA+B,GACvC,MAAM,GAAG,IAAI,CAqBf"}
@@ -6,6 +6,7 @@ const ARTIFACT_IDENTITY_WRITE_TOOLS = new Set([
6
6
  "add-database-item",
7
7
  "create-document",
8
8
  "update-document",
9
+ "set-document-property",
9
10
  "create-deck",
10
11
  "duplicate-deck",
11
12
  "add-slide",
@@ -367,6 +368,8 @@ function collectArtifacts(results) {
367
368
  const monitors = new Map();
368
369
  const forms = new Map();
369
370
  for (const toolResult of results) {
371
+ if (toolResult.isError === true || toolResult.completedSideEffect === false)
372
+ continue;
370
373
  if (toolResult.tool === "call-agent") {
371
374
  for (const artifact of parseDownstreamArtifactBlock(toolResult.result)) {
372
375
  if (artifact.kind === "deck") {
@@ -452,6 +455,8 @@ function collectArtifacts(results) {
452
455
  }
453
456
  if (toolResult.tool === "create-document" ||
454
457
  toolResult.tool === "update-document") {
458
+ if (parsed.conflict === true)
459
+ continue;
455
460
  const id = stringValue(parsed.id);
456
461
  if (id) {
457
462
  documents.set(id, {
@@ -462,6 +467,16 @@ function collectArtifacts(results) {
462
467
  }
463
468
  continue;
464
469
  }
470
+ if (toolResult.tool === "set-document-property") {
471
+ const id = stringValue(parsed.documentId);
472
+ if (id) {
473
+ documents.set(id, {
474
+ id,
475
+ url: stringValue(parsed.url) ?? stringValue(parsed.urlPath),
476
+ });
477
+ }
478
+ continue;
479
+ }
465
480
  if (toolResult.tool === "get-document" ||
466
481
  toolResult.tool === "get-content-document") {
467
482
  const document = asRecord(parsed.document);
@@ -658,6 +673,8 @@ export function extractA2AArtifactIdentities(results) {
658
673
  identities.set(`${identity.resourceType}:${identity.id}`, identity);
659
674
  };
660
675
  for (const result of results) {
676
+ if (result.isError === true || result.completedSideEffect === false)
677
+ continue;
661
678
  if (result.tool === "call-agent") {
662
679
  for (const identity of persistedArtifactIdentitiesFromMarker(result.result)) {
663
680
  remember({ ...identity, sourceAction: "call-agent" });
@@ -1153,4 +1170,55 @@ export function buildA2ARecoverableArtifactMessage(toolResults, options = {}) {
1153
1170
  ...lines,
1154
1171
  ].join("\n");
1155
1172
  }
1173
+ function mutationReceiptUrl(identity, baseUrl) {
1174
+ if (identity.sourceAction === "call-agent" &&
1175
+ (!identity.url || identity.url.startsWith("/"))) {
1176
+ return undefined;
1177
+ }
1178
+ if (identity.url) {
1179
+ return identity.url.startsWith("/")
1180
+ ? artifactUrl(baseUrl, identity.url)
1181
+ : identity.url;
1182
+ }
1183
+ const path = identity.resourceType === "document"
1184
+ ? `/page/${identity.id}`
1185
+ : identity.resourceType === "deck"
1186
+ ? `/deck/${identity.id}`
1187
+ : identity.resourceType === "dashboard"
1188
+ ? `/adhoc/${identity.id}`
1189
+ : identity.resourceType === "analysis"
1190
+ ? `/analyses/${identity.id}`
1191
+ : identity.resourceType === "image"
1192
+ ? `/image/${identity.id}`
1193
+ : identity.resourceType === "design"
1194
+ ? `/design/${identity.id}`
1195
+ : undefined;
1196
+ return path ? artifactUrl(baseUrl, path) : undefined;
1197
+ }
1198
+ /**
1199
+ * Build a bounded participant-facing receipt from authenticated artifact writes.
1200
+ * Unlike generic artifact recovery, this only trusts identities extracted from
1201
+ * successful write actions (or a signed downstream write ledger), so a read or
1202
+ * an unverified URL cannot be rounded up to a successful mutation.
1203
+ */
1204
+ export function buildA2AVerifiedMutationReceipt(toolResults, options = {}) {
1205
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
1206
+ const identities = extractA2AArtifactIdentities(toolResults);
1207
+ if (identities.length === 0)
1208
+ return null;
1209
+ const lines = identities.map((identity) => {
1210
+ const label = identity.resourceType.charAt(0).toUpperCase() +
1211
+ identity.resourceType.slice(1);
1212
+ const url = mutationReceiptUrl(identity, baseUrl);
1213
+ return url
1214
+ ? `- ${label}: ${url} (ID: ${identity.id})`
1215
+ : `- ${label} ID: ${identity.id}`;
1216
+ });
1217
+ return [
1218
+ "A verified change was saved, but I couldn't generate the detailed summary.",
1219
+ "",
1220
+ "Saved artifacts:",
1221
+ ...lines,
1222
+ ].join("\n");
1223
+ }
1156
1224
  //# sourceMappingURL=artifact-response.js.map