@agent-native/core 0.118.1 → 0.119.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 (184) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
  4. package/corpus/core/docs/content/template-chat.mdx +1 -1
  5. package/corpus/core/docs/content/template-plan.mdx +3 -3
  6. package/corpus/core/package.json +1 -1
  7. package/corpus/core/src/a2a/activity.ts +371 -0
  8. package/corpus/core/src/a2a/client.ts +3 -1
  9. package/corpus/core/src/a2a/index.ts +21 -0
  10. package/corpus/core/src/a2a/types.ts +8 -0
  11. package/corpus/core/src/agent/model-config.ts +19 -23
  12. package/corpus/core/src/agent/production-agent.ts +167 -100
  13. package/corpus/core/src/agent/types.ts +16 -1
  14. package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
  15. package/corpus/core/src/client/chat-model-groups.ts +67 -27
  16. package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
  17. package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
  18. package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
  19. package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
  20. package/corpus/core/src/client/sse-event-processor.ts +85 -1
  21. package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
  22. package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
  23. package/corpus/core/src/ingestion/index.ts +20 -0
  24. package/corpus/core/src/localization/default-messages.ts +6 -0
  25. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  26. package/corpus/core/src/scripts/call-agent.ts +49 -6
  27. package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  29. package/corpus/templates/assets/app/routes/library.tsx +17 -9
  30. package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
  31. package/corpus/templates/clips/server/plugins/db.ts +9 -1
  32. package/corpus/templates/design/AGENTS.md +18 -5
  33. package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
  34. package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
  35. package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
  36. package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
  37. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
  38. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
  39. package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
  40. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
  41. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
  42. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
  43. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  45. package/corpus/templates/design/app/i18n-data.ts +150 -2
  46. package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
  47. package/corpus/templates/design/app/lib/design-import.ts +19 -1
  48. package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
  49. package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
  50. package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
  51. package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
  52. package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
  53. package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
  54. package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
  55. package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
  56. package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
  57. package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
  58. package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
  59. package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
  60. package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
  61. package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
  62. package/corpus/templates/design/server/register-secrets.ts +14 -2
  63. package/dist/a2a/activity.d.ts +50 -0
  64. package/dist/a2a/activity.d.ts.map +1 -0
  65. package/dist/a2a/activity.js +236 -0
  66. package/dist/a2a/activity.js.map +1 -0
  67. package/dist/a2a/client.d.ts +2 -0
  68. package/dist/a2a/client.d.ts.map +1 -1
  69. package/dist/a2a/client.js +1 -1
  70. package/dist/a2a/client.js.map +1 -1
  71. package/dist/a2a/index.d.ts +2 -1
  72. package/dist/a2a/index.d.ts.map +1 -1
  73. package/dist/a2a/index.js +1 -0
  74. package/dist/a2a/index.js.map +1 -1
  75. package/dist/a2a/types.d.ts +1 -0
  76. package/dist/a2a/types.d.ts.map +1 -1
  77. package/dist/a2a/types.js.map +1 -1
  78. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  79. package/dist/agent/engine/builder-engine.d.ts +2 -2
  80. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  81. package/dist/agent/model-config.d.ts +16 -16
  82. package/dist/agent/model-config.d.ts.map +1 -1
  83. package/dist/agent/model-config.js +19 -23
  84. package/dist/agent/model-config.js.map +1 -1
  85. package/dist/agent/production-agent.d.ts +26 -0
  86. package/dist/agent/production-agent.d.ts.map +1 -1
  87. package/dist/agent/production-agent.js +137 -80
  88. package/dist/agent/production-agent.js.map +1 -1
  89. package/dist/agent/types.d.ts +10 -0
  90. package/dist/agent/types.d.ts.map +1 -1
  91. package/dist/agent/types.js.map +1 -1
  92. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  93. package/dist/client/chat/tool-call-display.js +63 -14
  94. package/dist/client/chat/tool-call-display.js.map +1 -1
  95. package/dist/client/chat-model-groups.d.ts.map +1 -1
  96. package/dist/client/chat-model-groups.js +44 -21
  97. package/dist/client/chat-model-groups.js.map +1 -1
  98. package/dist/client/sharing/ShareButton.d.ts +1 -1
  99. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  100. package/dist/client/sharing/ShareButton.js +3 -4
  101. package/dist/client/sharing/ShareButton.js.map +1 -1
  102. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  103. package/dist/client/sharing/ShareDialog.js +1 -2
  104. package/dist/client/sharing/ShareDialog.js.map +1 -1
  105. package/dist/client/sharing/useShareButtonController.d.ts +0 -1
  106. package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
  107. package/dist/client/sharing/useShareButtonController.js +0 -6
  108. package/dist/client/sharing/useShareButtonController.js.map +1 -1
  109. package/dist/client/sharing/useShareDialogController.d.ts +0 -1
  110. package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
  111. package/dist/client/sharing/useShareDialogController.js +0 -1
  112. package/dist/client/sharing/useShareDialogController.js.map +1 -1
  113. package/dist/client/sse-event-processor.d.ts +12 -0
  114. package/dist/client/sse-event-processor.d.ts.map +1 -1
  115. package/dist/client/sse-event-processor.js +63 -1
  116. package/dist/client/sse-event-processor.js.map +1 -1
  117. package/dist/collab/routes.d.ts +1 -1
  118. package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
  119. package/dist/ingestion/figma-node-to-html.js +14 -88
  120. package/dist/ingestion/figma-node-to-html.js.map +1 -1
  121. package/dist/ingestion/figma-paint-math.d.ts +157 -0
  122. package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
  123. package/dist/ingestion/figma-paint-math.js +274 -0
  124. package/dist/ingestion/figma-paint-math.js.map +1 -0
  125. package/dist/ingestion/index.d.ts +1 -0
  126. package/dist/ingestion/index.d.ts.map +1 -1
  127. package/dist/ingestion/index.js +1 -0
  128. package/dist/ingestion/index.js.map +1 -1
  129. package/dist/localization/default-messages.d.ts +6 -0
  130. package/dist/localization/default-messages.d.ts.map +1 -1
  131. package/dist/localization/default-messages.js +6 -0
  132. package/dist/localization/default-messages.js.map +1 -1
  133. package/dist/notifications/routes.d.ts +2 -2
  134. package/dist/observability/routes.d.ts +5 -5
  135. package/dist/progress/routes.d.ts +1 -1
  136. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  137. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  138. package/dist/resources/handlers.d.ts +1 -1
  139. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  140. package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
  141. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  142. package/dist/scripts/call-agent.d.ts.map +1 -1
  143. package/dist/scripts/call-agent.js +49 -6
  144. package/dist/scripts/call-agent.js.map +1 -1
  145. package/dist/secrets/routes.d.ts +3 -3
  146. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  147. package/dist/server/agent-chat-plugin.js +38 -10
  148. package/dist/server/agent-chat-plugin.js.map +1 -1
  149. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  150. package/dist/server/realtime-token.d.ts +1 -1
  151. package/dist/server/transcribe-voice.d.ts +1 -1
  152. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  153. package/docs/content/a2a-protocol.mdx +15 -0
  154. package/docs/content/template-chat.mdx +1 -1
  155. package/docs/content/template-plan.mdx +3 -3
  156. package/package.json +1 -1
  157. package/src/a2a/activity.ts +371 -0
  158. package/src/a2a/client.ts +3 -1
  159. package/src/a2a/index.ts +21 -0
  160. package/src/a2a/types.ts +8 -0
  161. package/src/agent/model-config.ts +19 -23
  162. package/src/agent/production-agent.ts +167 -100
  163. package/src/agent/types.ts +16 -1
  164. package/src/client/chat/tool-call-display.tsx +190 -35
  165. package/src/client/chat-model-groups.ts +67 -27
  166. package/src/client/sharing/ShareButton.tsx +1 -27
  167. package/src/client/sharing/ShareDialog.tsx +0 -16
  168. package/src/client/sharing/useShareButtonController.ts +0 -7
  169. package/src/client/sharing/useShareDialogController.ts +0 -2
  170. package/src/client/sse-event-processor.ts +85 -1
  171. package/src/ingestion/figma-node-to-html.ts +20 -99
  172. package/src/ingestion/figma-paint-math.ts +371 -0
  173. package/src/ingestion/index.ts +20 -0
  174. package/src/localization/default-messages.ts +6 -0
  175. package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  176. package/src/scripts/call-agent.ts +49 -6
  177. package/src/server/agent-chat-plugin.ts +50 -10
  178. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  179. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  180. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  181. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  182. package/dist/observability/hosted-model-experiment.js +0 -90
  183. package/dist/observability/hosted-model-experiment.js.map +0 -1
  184. package/src/observability/hosted-model-experiment.ts +0 -118
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Pure, synchronous math helpers for Figma gradient geometry and blend-mode
3
+ * normalisation. No DOM, no CSS, no network.
4
+ *
5
+ * Supports two gradient-transform sources:
6
+ * - REST API `gradientHandlePositions` (3-element Vec2 array, already in
7
+ * normalized 0..1 node-space).
8
+ * - 2×3 affine transform in **either** the Kiwi/fig-file object form
9
+ * `{m00..m12}` or the REST/Plugin API array form `[[a,b,tx],[c,d,ty]]`.
10
+ * Both encode the **node-to-gradient** mapping (same convention as Figma's
11
+ * own `gradientTransform` field); invert to obtain handle positions.
12
+ */
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export interface Vec2 {
19
+ x: number;
20
+ y: number;
21
+ }
22
+
23
+ export interface GradientHandles {
24
+ start: Vec2;
25
+ end: Vec2;
26
+ width: Vec2;
27
+ }
28
+
29
+ /**
30
+ * 2×3 affine transform in the row-major object form used by Kiwi/fig-file
31
+ * decoded paint nodes:
32
+ * [m00, m01, m02]
33
+ * [m10, m11, m12]
34
+ */
35
+ export interface Mat2x3Object {
36
+ m00: number;
37
+ m01: number;
38
+ m02: number;
39
+ m10: number;
40
+ m11: number;
41
+ m12: number;
42
+ }
43
+
44
+ /**
45
+ * 2×3 affine transform in the nested-array form used by the Figma REST API
46
+ * and Plugin API `gradientTransform` field:
47
+ * [[a, b, tx], [c, d, ty]]
48
+ */
49
+ export type Mat2x3Array = [[number, number, number], [number, number, number]];
50
+
51
+ export type BlendVerdict = "exact" | "approximated";
52
+
53
+ export interface BlendModeResult {
54
+ cssMode: string;
55
+ verdict: BlendVerdict;
56
+ }
57
+
58
+ export type GradientKind = "LINEAR" | "RADIAL" | "ANGULAR" | "DIAMOND";
59
+
60
+ export interface GradientGeometry {
61
+ kind: GradientKind;
62
+ handles: GradientHandles;
63
+ start: Vec2;
64
+ end: Vec2;
65
+ center: Vec2;
66
+ rx: number;
67
+ ry: number;
68
+ rotationDeg: number;
69
+ fromDeg: number;
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Matrix helpers
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /** Convert the REST/Plugin nested-array form to the object form. */
77
+ export function mat2x3FromArray(m: Mat2x3Array): Mat2x3Object {
78
+ return {
79
+ m00: m[0][0],
80
+ m01: m[0][1],
81
+ m02: m[0][2],
82
+ m10: m[1][0],
83
+ m11: m[1][1],
84
+ m12: m[1][2],
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Invert a 2×3 affine transform. Returns null when the matrix is singular
90
+ * (determinant near zero, i.e. the gradient has collapsed to a line or point).
91
+ *
92
+ * The 2×2 rotation/scale sub-matrix is [[m00,m01],[m10,m11]]; the
93
+ * standard 2×2 inverse is applied and the translation is back-solved:
94
+ * inv_tx = (-m11*m02 + m01*m12) / det
95
+ * inv_ty = ( m10*m02 - m00*m12) / det
96
+ */
97
+ export function invert2x3(m: Mat2x3Object): Mat2x3Object | null {
98
+ const det = m.m00 * m.m11 - m.m01 * m.m10;
99
+ if (Math.abs(det) < 1e-8) return null;
100
+ const inv00 = m.m11 / det;
101
+ const inv01 = -m.m01 / det;
102
+ const inv10 = -m.m10 / det;
103
+ const inv11 = m.m00 / det;
104
+ return {
105
+ m00: inv00,
106
+ m01: inv01,
107
+ m02: (-m.m11 * m.m02 + m.m01 * m.m12) / det,
108
+ m10: inv10,
109
+ m11: inv11,
110
+ m12: (m.m10 * m.m02 - m.m00 * m.m12) / det,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Apply a 2×3 transform to a 2-D point.
116
+ * The point is treated as a homogeneous [x, y, 1]^T column vector.
117
+ */
118
+ function applyMat2x3(m: Mat2x3Object, v: Vec2): Vec2 {
119
+ return {
120
+ x: m.m00 * v.x + m.m01 * v.y + m.m02,
121
+ y: m.m10 * v.x + m.m11 * v.y + m.m12,
122
+ };
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Handle positions from transforms
127
+ // ---------------------------------------------------------------------------
128
+
129
+ /**
130
+ * Derive REST-style `GradientHandles` from a 2×3 **node-to-gradient**
131
+ * transform in Kiwi/fig-file object form.
132
+ *
133
+ * Figma's gradient transform encodes where the gradient's natural coordinate
134
+ * system sits inside the node's normalized [0,1]² box. The three canonical
135
+ * gradient-space points (start, end, width) map back to node-space by
136
+ * inverting the transform:
137
+ * handles = inv(M) * {(0,0), (1,0), (0,1)}
138
+ */
139
+ export function handlePositionsFromObjectTransform(
140
+ t: Mat2x3Object,
141
+ ): GradientHandles | null {
142
+ const inv = invert2x3(t);
143
+ if (!inv) return null;
144
+ return {
145
+ start: applyMat2x3(inv, { x: 0, y: 0 }),
146
+ end: applyMat2x3(inv, { x: 1, y: 0 }),
147
+ width: applyMat2x3(inv, { x: 0, y: 1 }),
148
+ };
149
+ }
150
+
151
+ export function gradientGeometryFromTransform(
152
+ kind: GradientKind,
153
+ transform: Mat2x3Object,
154
+ box: { width: number; height: number },
155
+ ): GradientGeometry | null {
156
+ const inverse = invert2x3(transform);
157
+ if (!inverse) return null;
158
+ const toPixels = (point: Vec2): Vec2 => ({
159
+ x: point.x * box.width,
160
+ y: point.y * box.height,
161
+ });
162
+ const startNormalized = applyMat2x3(inverse, { x: 0, y: 0.5 });
163
+ const endNormalized = applyMat2x3(inverse, { x: 1, y: 0.5 });
164
+ const widthNormalized = applyMat2x3(inverse, { x: 1, y: 0 });
165
+ const center = toPixels(applyMat2x3(inverse, { x: 0.5, y: 0.5 }));
166
+ const vertex = toPixels(applyMat2x3(inverse, { x: 1, y: 0.5 }));
167
+ const covertex = toPixels(applyMat2x3(inverse, { x: 0.5, y: 1 }));
168
+ const vertexDx = vertex.x - center.x;
169
+ const vertexDy = vertex.y - center.y;
170
+ return {
171
+ kind,
172
+ handles: {
173
+ start: startNormalized,
174
+ end: endNormalized,
175
+ width: widthNormalized,
176
+ },
177
+ start: toPixels(startNormalized),
178
+ end: toPixels(endNormalized),
179
+ center,
180
+ rx: Math.hypot(vertexDx, vertexDy),
181
+ ry: Math.hypot(covertex.x - center.x, covertex.y - center.y),
182
+ rotationDeg: (Math.atan2(vertexDy, vertexDx) * 180) / Math.PI,
183
+ fromDeg: (Math.atan2(-transform.m10, transform.m00) * 180) / Math.PI,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Derive REST-style `GradientHandles` from a 2×3 **node-to-gradient**
189
+ * transform in REST/Plugin API nested-array form.
190
+ */
191
+ export function handlePositionsFromArrayTransform(
192
+ t: Mat2x3Array,
193
+ ): GradientHandles | null {
194
+ return handlePositionsFromObjectTransform(mat2x3FromArray(t));
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Gradient geometry from REST handle positions
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * Resolve `GradientHandles` from a raw `gradientHandlePositions` array as
203
+ * returned by the Figma REST API. Returns null when the array is too short.
204
+ */
205
+ export function resolveGradientHandles(
206
+ gradientHandlePositions: Array<Vec2> | undefined,
207
+ ): GradientHandles | null {
208
+ if (!gradientHandlePositions || gradientHandlePositions.length < 3)
209
+ return null;
210
+ return {
211
+ start: gradientHandlePositions[0]!,
212
+ end: gradientHandlePositions[1]!,
213
+ width: gradientHandlePositions[2]!,
214
+ };
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Gradient angle
219
+ // ---------------------------------------------------------------------------
220
+
221
+ /**
222
+ * Derive a CSS `linear-gradient()` angle (degrees) from Figma's normalized
223
+ * `gradientHandlePositions`. Handle positions are normalized independently in
224
+ * x and y (0..1 relative to the node's bounding box), so the angle must be
225
+ * computed in actual pixel space using the node's real width/height —
226
+ * otherwise a non-square box silently distorts the angle.
227
+ *
228
+ * Identity: left-to-right handles (start=(0,0.5), end=(1,0.5)) → 90 deg.
229
+ * Top-to-bottom handles (start=(0.5,0), end=(0.5,1)) → 180 deg.
230
+ *
231
+ * This is the same function previously inlined in figma-node-to-html.ts and
232
+ * is preserved here verbatim for public API compatibility.
233
+ */
234
+ export function gradientAngleDegrees(
235
+ paint: { gradientHandlePositions?: Array<Vec2> },
236
+ box: { width: number; height: number },
237
+ ): number | null {
238
+ const handles = resolveGradientHandles(paint.gradientHandlePositions);
239
+ if (!handles) return null;
240
+ return gradientAngleDegreesFromHandles(handles, box);
241
+ }
242
+
243
+ /**
244
+ * Same calculation as `gradientAngleDegrees` but accepts already-resolved
245
+ * `GradientHandles` — useful when handles come from a transform inversion.
246
+ */
247
+ export function gradientAngleDegreesFromHandles(
248
+ handles: GradientHandles,
249
+ box: { width: number; height: number },
250
+ ): number {
251
+ const dx = (handles.end.x - handles.start.x) * box.width;
252
+ const dy = (handles.end.y - handles.start.y) * box.height;
253
+ const angleRad = Math.atan2(dy, dx);
254
+ const angleDeg = (angleRad * 180) / Math.PI + 90;
255
+ return ((angleDeg % 360) + 360) % 360;
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Linear stop position remapping
260
+ // ---------------------------------------------------------------------------
261
+
262
+ /**
263
+ * CSS `linear-gradient(angle, ...)` always stretches its 0%/100% stops across
264
+ * the box's full diagonal at that angle (the CSS "gradient line" always spans
265
+ * corner-to-corner). Figma's stop positions are fractions of the literal
266
+ * handle-to-handle distance, which only coincides with the CSS span when the
267
+ * handles are dragged exactly corner-to-corner.
268
+ *
269
+ * This function returns a remap closure that projects each Figma stop's real
270
+ * pixel position onto the CSS gradient line and re-expresses it as a
271
+ * percentage of the CSS line's length, so a partial/offset gradient renders at
272
+ * the same pixel positions Figma draws it at.
273
+ */
274
+ export function remapLinearStopPosition(
275
+ handles: GradientHandles,
276
+ box: { width: number; height: number },
277
+ angleDeg: number,
278
+ ): (position: number) => number {
279
+ const angleRad = (angleDeg * Math.PI) / 180;
280
+ const ux = Math.sin(angleRad);
281
+ const uy = -Math.cos(angleRad);
282
+ const lineLength = box.width * Math.abs(ux) + box.height * Math.abs(uy);
283
+ if (lineLength < 1e-6) return (position) => position;
284
+ const startPx = {
285
+ x: handles.start.x * box.width,
286
+ y: handles.start.y * box.height,
287
+ };
288
+ const endPx = { x: handles.end.x * box.width, y: handles.end.y * box.height };
289
+ const cx = box.width / 2;
290
+ const cy = box.height / 2;
291
+ return (position: number): number => {
292
+ const px = startPx.x + position * (endPx.x - startPx.x);
293
+ const py = startPx.y + position * (endPx.y - startPx.y);
294
+ const projected = (px - cx) * ux + (py - cy) * uy;
295
+ return (projected + lineLength / 2) / lineLength;
296
+ };
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Vector length (pixel-space)
301
+ // ---------------------------------------------------------------------------
302
+
303
+ /**
304
+ * Euclidean distance between two normalized-coordinate points after scaling
305
+ * into actual pixel space.
306
+ */
307
+ export function vectorLength(
308
+ from: Vec2,
309
+ to: Vec2,
310
+ box: { width: number; height: number },
311
+ ): number {
312
+ const dx = (to.x - from.x) * box.width;
313
+ const dy = (to.y - from.y) * box.height;
314
+ return Math.sqrt(dx * dx + dy * dy);
315
+ }
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // CSS blend mode mapping
319
+ // ---------------------------------------------------------------------------
320
+
321
+ const CSS_BLEND_MODES = new Set([
322
+ "multiply",
323
+ "screen",
324
+ "overlay",
325
+ "darken",
326
+ "lighten",
327
+ "color-dodge",
328
+ "color-burn",
329
+ "hard-light",
330
+ "soft-light",
331
+ "difference",
332
+ "exclusion",
333
+ "hue",
334
+ "saturation",
335
+ "color",
336
+ "luminosity",
337
+ ]);
338
+
339
+ /**
340
+ * Figma-only blend modes that have no exact CSS equivalent. The value is the
341
+ * closest CSS mode (approximation) and callers should record the verdict.
342
+ */
343
+ const FIGMA_ONLY_BLEND_MODE_FALLBACK: Record<string, string> = {
344
+ LINEAR_BURN: "plus-darker",
345
+ LINEAR_DODGE: "plus-lighter",
346
+ LIGHTER: "plus-lighter",
347
+ DARKER: "darken",
348
+ };
349
+
350
+ /**
351
+ * Map a Figma blend mode string to a CSS `mix-blend-mode` value with an
352
+ * explicit fidelity verdict:
353
+ * - `"exact"` — CSS supports the mode natively.
354
+ * - `"approximated"` — mapped to the closest CSS equivalent.
355
+ *
356
+ * Returns `null` for `PASS_THROUGH`, `NORMAL`, and unrecognised modes (caller
357
+ * should omit the CSS property entirely in those cases).
358
+ */
359
+ export function cssBlendMode(figmaBlendMode: string): BlendModeResult | null {
360
+ if (
361
+ !figmaBlendMode ||
362
+ figmaBlendMode === "PASS_THROUGH" ||
363
+ figmaBlendMode === "NORMAL"
364
+ )
365
+ return null;
366
+ const cssMode = figmaBlendMode.toLowerCase().replace(/_/g, "-");
367
+ if (CSS_BLEND_MODES.has(cssMode)) return { cssMode, verdict: "exact" };
368
+ const fallback = FIGMA_ONLY_BLEND_MODE_FALLBACK[figmaBlendMode];
369
+ if (fallback) return { cssMode: fallback, verdict: "approximated" };
370
+ return null;
371
+ }
@@ -53,6 +53,26 @@ export {
53
53
  type FigmaRgba,
54
54
  type SummarizeFigmaNodeResult,
55
55
  } from "./figma.js";
56
+ export {
57
+ cssBlendMode,
58
+ gradientAngleDegreesFromHandles,
59
+ gradientGeometryFromTransform,
60
+ handlePositionsFromArrayTransform,
61
+ handlePositionsFromObjectTransform,
62
+ invert2x3,
63
+ mat2x3FromArray,
64
+ remapLinearStopPosition,
65
+ resolveGradientHandles,
66
+ vectorLength,
67
+ type BlendModeResult,
68
+ type BlendVerdict,
69
+ type GradientGeometry,
70
+ type GradientHandles,
71
+ type GradientKind,
72
+ type Mat2x3Array,
73
+ type Mat2x3Object,
74
+ type Vec2,
75
+ } from "./figma-paint-math.js";
56
76
  export {
57
77
  assertFigmaNodeTreeComplexity,
58
78
  collectFallbackNodeIds,
@@ -337,6 +337,12 @@ const messages = {
337
337
  configureProviderKeys: "Configure Anthropic, OpenAI, or another provider",
338
338
  checkingAiConnection: "Checking AI connection...",
339
339
  connectionUnavailable: "Unable to check AI connection. Click to retry.",
340
+ delegatedAgent: {
341
+ asking: "Asking {{name}}...",
342
+ asked: "Asked {{name}}",
343
+ error: "Error asking {{name}}",
344
+ elapsed: "{{duration}} elapsed",
345
+ },
340
346
  voiceMode: {
341
347
  entryButtonLabel: "Use microphone",
342
348
  promptTitle: "Use your voice",
@@ -15,8 +15,6 @@ import {
15
15
  normalizeModelForEngine,
16
16
  } from "../../agent/engine/index.js";
17
17
  import type { ActionTool } from "../../agent/types.js";
18
- import { resolveHostedDefaultModelExperiment } from "../../observability/hosted-model-experiment.js";
19
- import { getRequestUserEmail } from "../../server/request-context.js";
20
18
  import { getSetting } from "../../settings/index.js";
21
19
 
22
20
  export const tool: ActionTool = {
@@ -91,15 +89,6 @@ export async function run(args: Record<string, string> = {}): Promise<string> {
91
89
  currentModelCandidate ?? currentEntry.defaultModel,
92
90
  )
93
91
  : (currentModelCandidate ?? DEFAULT_MODEL);
94
- const hostedExperiment = currentEntry
95
- ? resolveHostedDefaultModelExperiment({
96
- userId: getRequestUserEmail(),
97
- engineName: currentEntry.name,
98
- isDefaultModelSelection: currentModelCandidate == null,
99
- supportedModels: currentEntry.supportedModels,
100
- })
101
- : null;
102
- const effectiveCurrentModel = hostedExperiment?.model ?? currentModel;
103
92
  const result = {
104
93
  engines: engines.map((e) => ({
105
94
  name: e.name,
@@ -116,13 +105,7 @@ export async function run(args: Record<string, string> = {}): Promise<string> {
116
105
  ? null
117
106
  : {
118
107
  engine: currentEngineName,
119
- model: effectiveCurrentModel,
120
- ...(hostedExperiment
121
- ? {
122
- modelSelectionSource: "experiment",
123
- experimentAssignments: [hostedExperiment.assignment],
124
- }
125
- : {}),
108
+ model: currentModel,
126
109
  },
127
110
  };
128
111
 
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
 
3
+ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
3
4
  import {
4
5
  A2ATaskTimeoutError,
5
6
  callAgent,
@@ -271,8 +272,15 @@ export async function run(
271
272
  : undefined;
272
273
 
273
274
  if (action) {
275
+ const agentCallId = randomUUID();
276
+ const startedAt = Date.now();
274
277
  if (context?.send) {
275
- context.send({ type: "agent_call", agent: agent.name, status: "start" });
278
+ context.send({
279
+ type: "agent_call",
280
+ agent: agent.name,
281
+ status: "start",
282
+ agentCallId,
283
+ });
276
284
  }
277
285
  try {
278
286
  const output = await invokeReadOnlyAppAction(
@@ -281,10 +289,24 @@ export async function run(
281
289
  input as Record<string, unknown>,
282
290
  buildDelegationCorrelation(context, selfAppId, randomUUID()),
283
291
  );
292
+ if (context?.send && output) {
293
+ context.send({
294
+ type: "agent_call_text",
295
+ agent: agent.name,
296
+ text: output,
297
+ agentCallId,
298
+ });
299
+ }
284
300
  return output;
285
301
  } finally {
286
302
  if (context?.send) {
287
- context.send({ type: "agent_call", agent: agent.name, status: "done" });
303
+ context.send({
304
+ type: "agent_call",
305
+ agent: agent.name,
306
+ status: "done",
307
+ agentCallId,
308
+ durationMs: Date.now() - startedAt,
309
+ });
288
310
  }
289
311
  }
290
312
  }
@@ -363,6 +385,7 @@ export async function run(
363
385
 
364
386
  let responseText = "";
365
387
  let lastSentLength = 0;
388
+ const agentCallId = randomUUID();
366
389
  const existingContinuationText = taskId
367
390
  ? null
368
391
  : await formatExistingIntegrationContinuationIfRetry(agent, message);
@@ -372,6 +395,7 @@ export async function run(
372
395
  type: "agent_call",
373
396
  agent: agent.name,
374
397
  status: "start",
398
+ agentCallId,
375
399
  });
376
400
 
377
401
  const emitNewText = (newText: string) => {
@@ -380,6 +404,7 @@ export async function run(
380
404
  type: "agent_call_text",
381
405
  agent: agent.name,
382
406
  text: newText.slice(lastSentLength),
407
+ agentCallId,
383
408
  });
384
409
  lastSentLength = newText.length;
385
410
  }
@@ -395,9 +420,10 @@ export async function run(
395
420
  // time we get to the sync fallback, Lambda is dead and the second fetch
396
421
  // errors out as "fetch failed". Async+poll has its own short fetches
397
422
  // with their own budgets, so it works reliably across hosts. The
398
- // trade-off is we lose progressive in-UI text streaming for cross-app
399
- // A2A calls, but the receiving agent's full response still surfaces via
400
- // the tool_result event below.
423
+ // trade-off is that cross-app activity arrives at the poll cadence rather
424
+ // than token-by-token. Agent Native peers attach their current reasoning,
425
+ // tool status, and response preview to each task checkpoint, and the
426
+ // receiver's full response still surfaces below.
401
427
  //
402
428
  // That trade-off has a second-order cost: callAgent()'s poll (see
403
429
  // A2AClient.sendAndWait in a2a/client.ts) can legitimately run for
@@ -450,8 +476,22 @@ export async function run(
450
476
  ]);
451
477
  const callStartedAt = Date.now();
452
478
  let lastProgressEmitAt = callStartedAt;
479
+ let lastActivitySequence = -1;
453
480
  const onRemotePollUpdate = (task: Task) => {
454
481
  const state = task?.status?.state;
482
+ const parts = task.status?.message?.parts;
483
+ const snapshot = Array.isArray(parts)
484
+ ? parts.map(parseA2AAgentActivityPart).find((value) => value !== null)
485
+ : undefined;
486
+ if (snapshot && snapshot.sequence > lastActivitySequence) {
487
+ lastActivitySequence = snapshot.sequence;
488
+ context.send!({
489
+ type: "agent_call_activity",
490
+ agent: agent.name,
491
+ agentCallId,
492
+ snapshot,
493
+ });
494
+ }
455
495
  if (!state || !ACTIVELY_WORKING_STATES.has(state)) return;
456
496
  const now = Date.now();
457
497
  if (now - lastProgressEmitAt < PROGRESS_MIN_INTERVAL_MS) return;
@@ -462,6 +502,7 @@ export async function run(
462
502
  agent: agent.name,
463
503
  state,
464
504
  elapsedSeconds: Math.round((now - callStartedAt) / 1000),
505
+ agentCallId,
465
506
  ...(detail ? { detail } : {}),
466
507
  });
467
508
  };
@@ -549,6 +590,8 @@ export async function run(
549
590
  type: "agent_call",
550
591
  agent: agent.name,
551
592
  status: "done",
593
+ agentCallId,
594
+ durationMs: Date.now() - callStartedAt,
552
595
  });
553
596
 
554
597
  return responseText || "(empty response)";
@@ -718,7 +761,7 @@ async function enqueueIntegrationContinuationIfPossible(
718
761
  const MAX_PROGRESS_DETAIL_CHARS = 200;
719
762
  function extractRemoteProgressDetail(task: Task): string | undefined {
720
763
  const parts = task.status?.message?.parts;
721
- if (!parts) return undefined;
764
+ if (!Array.isArray(parts)) return undefined;
722
765
  const text = parts
723
766
  .filter((p): p is { type: "text"; text: string } => p.type === "text")
724
767
  .map((p) => p.text)