@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-plan.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +371 -0
- package/corpus/core/src/a2a/client.ts +3 -1
- package/corpus/core/src/a2a/index.ts +21 -0
- package/corpus/core/src/a2a/types.ts +8 -0
- package/corpus/core/src/agent/model-config.ts +19 -23
- package/corpus/core/src/agent/production-agent.ts +167 -100
- package/corpus/core/src/agent/types.ts +16 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
- package/corpus/core/src/client/chat-model-groups.ts +67 -27
- package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
- package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
- package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
- package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
- package/corpus/core/src/client/sse-event-processor.ts +85 -1
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/corpus/core/src/scripts/call-agent.ts +49 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +9 -1
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/dist/a2a/activity.d.ts +50 -0
- package/dist/a2a/activity.d.ts.map +1 -0
- package/dist/a2a/activity.js +236 -0
- package/dist/a2a/activity.js.map +1 -0
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +1 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/index.d.ts +2 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/types.d.ts +1 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +16 -16
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +19 -23
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +26 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +137 -80
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +63 -14
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +44 -21
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -4
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +1 -2
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sharing/useShareButtonController.d.ts +0 -1
- package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
- package/dist/client/sharing/useShareButtonController.js +0 -6
- package/dist/client/sharing/useShareButtonController.js.map +1 -1
- package/dist/client/sharing/useShareDialogController.d.ts +0 -1
- package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
- package/dist/client/sharing/useShareDialogController.js +0 -1
- package/dist/client/sharing/useShareDialogController.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +12 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +63 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +6 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +49 -6
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/docs/content/a2a-protocol.mdx +15 -0
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-plan.mdx +3 -3
- package/package.json +1 -1
- package/src/a2a/activity.ts +371 -0
- package/src/a2a/client.ts +3 -1
- package/src/a2a/index.ts +21 -0
- package/src/a2a/types.ts +8 -0
- package/src/agent/model-config.ts +19 -23
- package/src/agent/production-agent.ts +167 -100
- package/src/agent/types.ts +16 -1
- package/src/client/chat/tool-call-display.tsx +190 -35
- package/src/client/chat-model-groups.ts +67 -27
- package/src/client/sharing/ShareButton.tsx +1 -27
- package/src/client/sharing/ShareDialog.tsx +0 -16
- package/src/client/sharing/useShareButtonController.ts +0 -7
- package/src/client/sharing/useShareDialogController.ts +0 -2
- package/src/client/sse-event-processor.ts +85 -1
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
- package/src/localization/default-messages.ts +6 -0
- package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/src/scripts/call-agent.ts +49 -6
- package/src/server/agent-chat-plugin.ts +50 -10
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
- 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
|
+
}
|
package/src/ingestion/index.ts
CHANGED
|
@@ -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:
|
|
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({
|
|
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({
|
|
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
|
|
399
|
-
//
|
|
400
|
-
//
|
|
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)
|