@agent-native/core 0.84.24 → 0.84.27
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 +1 -1
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +12 -8
- package/corpus/core/src/agent/run-manager.ts +58 -6
- package/corpus/core/src/agent/types.ts +9 -2
- package/corpus/core/src/client/AssistantChat.tsx +51 -3
- package/corpus/core/src/client/FeedbackButton.tsx +2 -2
- package/corpus/core/src/client/components/PresenceBar.tsx +7 -2
- package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +23 -9
- package/corpus/core/src/client/sharing/ShareButton.tsx +22 -3
- package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -1
- package/corpus/core/src/client/sse-event-processor.ts +83 -26
- package/corpus/core/src/client/tool-display.ts +1 -1
- package/corpus/core/src/sharing/actions/set-resource-visibility.ts +3 -3
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +4 -2
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +9 -0
- package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +3 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +3 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +3 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +3 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -0
- package/corpus/templates/clips/changelog/2026-07-01-shared-clips-can-now-copy-a-ready-to-send-prompt-that-tells-.md +6 -0
- package/corpus/templates/design/actions/insert-asset.ts +14 -2
- package/corpus/templates/design/actions/insert-design-native-asset.ts +16 -4
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +14 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +14 -0
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +54 -10
- package/corpus/templates/design/app/components/design/EditPanel.tsx +23 -16
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -2
- package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +9 -5
- package/corpus/templates/design/app/components/layout/Header.tsx +44 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +267 -105
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +15 -1
- package/corpus/templates/design/app/pages/Index.tsx +20 -9
- package/corpus/templates/design/app/routes/settings.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-01-design-editor-polish-makes-zoom-assets-sharing-and-agent-handoffs-clearer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-01-design-qa-fixes-make-exports-permissions-and-tools-clearer.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +12 -9
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +56 -4
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +3 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +52 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.js +2 -2
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/components/PresenceBar.d.ts.map +1 -1
- package/dist/client/components/PresenceBar.js +6 -2
- package/dist/client/components/PresenceBar.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +15 -7
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +15 -2
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.js +16 -1
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +64 -21
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/tool-display.js +1 -1
- package/dist/client/tool-display.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +3 -3
- package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
- package/package.json +1 -1
|
@@ -150,9 +150,10 @@ export const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90_000;
|
|
|
150
150
|
|
|
151
151
|
type ActivityTrailEntry = AgentActivityTrailEntry;
|
|
152
152
|
|
|
153
|
-
type
|
|
154
|
-
tool
|
|
153
|
+
type PreparingActionEntry = {
|
|
154
|
+
tool: string;
|
|
155
155
|
startedAt?: number;
|
|
156
|
+
lastProgressBytes?: number;
|
|
156
157
|
/**
|
|
157
158
|
* Timestamp of the last real streaming progress for the in-preparation tool
|
|
158
159
|
* input. The server emits a throttled `activity` heartbeat per
|
|
@@ -165,6 +166,10 @@ type PreparingActionState = {
|
|
|
165
166
|
lastProgressAt?: number;
|
|
166
167
|
};
|
|
167
168
|
|
|
169
|
+
type PreparingActionState = {
|
|
170
|
+
entries?: Map<string, PreparingActionEntry>;
|
|
171
|
+
};
|
|
172
|
+
|
|
168
173
|
function formatProgressBytes(bytes: number): string {
|
|
169
174
|
if (bytes < 1024) return `${bytes} B`;
|
|
170
175
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -185,8 +190,15 @@ function isPreparingActionActivity(ev: SSEEvent): boolean {
|
|
|
185
190
|
return label.startsWith("preparing ") && label.includes(" action");
|
|
186
191
|
}
|
|
187
192
|
|
|
188
|
-
function
|
|
189
|
-
|
|
193
|
+
function isMeaningfulProgressEvent(
|
|
194
|
+
ev: SSEEvent,
|
|
195
|
+
actionPreparationProgress?: boolean,
|
|
196
|
+
): boolean {
|
|
197
|
+
if (ev.type === "stream_keepalive") return false;
|
|
198
|
+
if (ev.type === "activity" && isPreparingActionActivity(ev)) {
|
|
199
|
+
return actionPreparationProgress === true;
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
190
202
|
}
|
|
191
203
|
|
|
192
204
|
function baseActivityLabel(ev: SSEEvent, tool?: string): string {
|
|
@@ -258,19 +270,42 @@ function updatePreparingActionState(
|
|
|
258
270
|
state: PreparingActionState,
|
|
259
271
|
ev: SSEEvent,
|
|
260
272
|
now: number,
|
|
261
|
-
) {
|
|
273
|
+
): boolean | undefined {
|
|
262
274
|
if (ev.type === "activity" && isPreparingActionActivity(ev)) {
|
|
263
275
|
const tool = ev.tool?.trim() || undefined;
|
|
264
|
-
if (!tool) return;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
if (!tool) return false;
|
|
277
|
+
const key = ev.id?.trim() || tool;
|
|
278
|
+
const entries = state.entries ?? new Map<string, PreparingActionEntry>();
|
|
279
|
+
state.entries = entries;
|
|
280
|
+
let entry = entries.get(key);
|
|
281
|
+
if (!entry) {
|
|
282
|
+
entry = {
|
|
283
|
+
tool,
|
|
284
|
+
startedAt: now,
|
|
285
|
+
lastProgressAt: undefined,
|
|
286
|
+
lastProgressBytes: undefined,
|
|
287
|
+
};
|
|
288
|
+
entries.set(key, entry);
|
|
268
289
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
290
|
+
const progressBytes = activityProgressBytes(ev);
|
|
291
|
+
const previousBytes = entry.lastProgressBytes ?? 0;
|
|
292
|
+
if (progressBytes !== undefined) {
|
|
293
|
+
entry.lastProgressBytes = Math.max(previousBytes, progressBytes);
|
|
294
|
+
}
|
|
295
|
+
if (!ev.id?.trim()) {
|
|
296
|
+
if (progressBytes !== undefined && progressBytes > 0) {
|
|
297
|
+
entry.lastProgressAt = now;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (progressBytes !== undefined && progressBytes > previousBytes) {
|
|
303
|
+
// A byte increase is proof the model is still streaming this action's
|
|
304
|
+
// argument. Repeated zero-byte prep activity is only a heartbeat.
|
|
305
|
+
entry.lastProgressAt = now;
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
274
309
|
}
|
|
275
310
|
|
|
276
311
|
if (
|
|
@@ -282,10 +317,19 @@ function updatePreparingActionState(
|
|
|
282
317
|
ev.type === "error" ||
|
|
283
318
|
ev.type === "missing_api_key"
|
|
284
319
|
) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
320
|
+
if (ev.type === "tool_start" || ev.type === "tool_done") {
|
|
321
|
+
const tool = ev.tool?.trim();
|
|
322
|
+
const id = ev.id?.trim();
|
|
323
|
+
for (const [key, entry] of state.entries ?? []) {
|
|
324
|
+
if ((id && key === id) || (!id && entry.tool === tool)) {
|
|
325
|
+
state.entries?.delete(key);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
state.entries?.clear();
|
|
330
|
+
}
|
|
288
331
|
}
|
|
332
|
+
return undefined;
|
|
289
333
|
}
|
|
290
334
|
|
|
291
335
|
function hasStalledPreparingAction(state: PreparingActionState, now: number) {
|
|
@@ -294,11 +338,16 @@ function hasStalledPreparingAction(state: PreparingActionState, now: number) {
|
|
|
294
338
|
// streaming for a long time. `lastProgressAt` advances on every delta
|
|
295
339
|
// heartbeat, so an actively-streaming large output keeps resetting this and
|
|
296
340
|
// survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
341
|
+
for (const entry of state.entries?.values() ?? []) {
|
|
342
|
+
if (
|
|
343
|
+
entry.startedAt !== undefined &&
|
|
344
|
+
now - (entry.lastProgressAt ?? entry.startedAt) >=
|
|
345
|
+
SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
|
|
346
|
+
) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
302
351
|
}
|
|
303
352
|
|
|
304
353
|
async function readChunkWithProgressTimeout(
|
|
@@ -1193,11 +1242,15 @@ export async function* readSSEStream(
|
|
|
1193
1242
|
continue;
|
|
1194
1243
|
}
|
|
1195
1244
|
const now = Date.now();
|
|
1196
|
-
|
|
1245
|
+
const actionPreparationProgress = updatePreparingActionState(
|
|
1246
|
+
preparingActionState,
|
|
1247
|
+
ev,
|
|
1248
|
+
now,
|
|
1249
|
+
);
|
|
1250
|
+
if (isMeaningfulProgressEvent(ev, actionPreparationProgress)) {
|
|
1197
1251
|
sawProgressEvent = true;
|
|
1198
1252
|
lastMeaningfulEventAt = now;
|
|
1199
1253
|
}
|
|
1200
|
-
updatePreparingActionState(preparingActionState, ev, now);
|
|
1201
1254
|
|
|
1202
1255
|
// Track sequence number for reconnection
|
|
1203
1256
|
if (ev.seq !== undefined && onSeq) {
|
|
@@ -1355,11 +1408,15 @@ export async function readSSEStreamRaw(
|
|
|
1355
1408
|
continue;
|
|
1356
1409
|
}
|
|
1357
1410
|
const now = Date.now();
|
|
1358
|
-
|
|
1411
|
+
const actionPreparationProgress = updatePreparingActionState(
|
|
1412
|
+
preparingActionState,
|
|
1413
|
+
ev,
|
|
1414
|
+
now,
|
|
1415
|
+
);
|
|
1416
|
+
if (isMeaningfulProgressEvent(ev, actionPreparationProgress)) {
|
|
1359
1417
|
sawProgressEvent = true;
|
|
1360
1418
|
lastMeaningfulEventAt = now;
|
|
1361
1419
|
}
|
|
1362
|
-
updatePreparingActionState(preparingActionState, ev, now);
|
|
1363
1420
|
|
|
1364
1421
|
if (ev.seq !== undefined && onSeq) {
|
|
1365
1422
|
onSeq(ev.seq);
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
|
|
17
17
|
export default defineAction({
|
|
18
18
|
description:
|
|
19
|
-
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link.
|
|
20
|
-
// (audit H5) Visibility changes
|
|
21
|
-
//
|
|
19
|
+
"Change the coarse visibility of a shareable resource: 'private' keeps it owner-only, 'org' shares it with all members of the owner's organization, 'public' makes it accessible to anyone with the link. Visibility changes require owner or admin role.",
|
|
20
|
+
// (audit H5) Visibility changes can flip a private resource org-wide or
|
|
21
|
+
// public. Refuse from the tools iframe bridge.
|
|
22
22
|
toolCallable: false,
|
|
23
23
|
mcpApp: {
|
|
24
24
|
compactCatalog: true,
|
|
@@ -184,8 +184,10 @@ These endpoints follow the same access model as `/api/public-recording`:
|
|
|
184
184
|
raw provider URLs.
|
|
185
185
|
|
|
186
186
|
The share popover's "Share with agents" field should copy the agent context URL,
|
|
187
|
-
not raw transcript text.
|
|
188
|
-
|
|
187
|
+
not raw transcript text. Its "Copy agent prompt" field may wrap that URL with
|
|
188
|
+
instructions to fetch transcripts, frames, and browser diagnostics, but it should
|
|
189
|
+
still point agents at the context response so they can fetch only the visual
|
|
190
|
+
context they need.
|
|
189
191
|
|
|
190
192
|
## View counting
|
|
191
193
|
|
|
@@ -324,6 +324,9 @@ function LinkTab({
|
|
|
324
324
|
? tokenizedAgentContextUrl
|
|
325
325
|
: publicAgentContextUrl;
|
|
326
326
|
const agentShareDisabled = isPending || !isPublic || !agentContextUrl;
|
|
327
|
+
const agentPrompt = agentContextUrl
|
|
328
|
+
? t("shareDialog.agentPrompt", { agentContextUrl })
|
|
329
|
+
: "";
|
|
327
330
|
|
|
328
331
|
return (
|
|
329
332
|
<div className="space-y-4">
|
|
@@ -351,6 +354,12 @@ function LinkTab({
|
|
|
351
354
|
disabled={agentShareDisabled}
|
|
352
355
|
/>
|
|
353
356
|
|
|
357
|
+
<CopyField
|
|
358
|
+
label={t("shareDialog.copyAgentPrompt")}
|
|
359
|
+
value={agentPrompt}
|
|
360
|
+
disabled={agentShareDisabled}
|
|
361
|
+
/>
|
|
362
|
+
|
|
354
363
|
{isPublic && hasPassword ? (
|
|
355
364
|
<p className="text-xs text-muted-foreground">
|
|
356
365
|
{t("shareDialog.agentTokenDescription")}
|
|
@@ -415,6 +415,9 @@ const messages = {
|
|
|
415
415
|
embed: "تضمين",
|
|
416
416
|
shareLink: "رابط المشاركة",
|
|
417
417
|
shareWithAgents: "شارك مع الوكلاء",
|
|
418
|
+
copyAgentPrompt: "نسخ مطالبة الوكيل",
|
|
419
|
+
agentPrompt:
|
|
420
|
+
"اجلب عنوان URL لسياق وكيل Clips هذا: {{agentContextUrl}}. استخدم transcript.segments للسياق المنطوق، واجلب recommendedFrames أو عناوين URL الخاصة بواجهة API للإطارات لرؤية الشاشة، وتحقق من browserDiagnostics إن وجدت لسجلات وحدة التحكم المنقحة وبيانات طلبات fetch/XHR الوصفية.",
|
|
418
421
|
agentTokenDescription:
|
|
419
422
|
"يستخدم هذا الوكيل URL رمزًا مميزًا قصير العمر، بحيث يمكن للعملاء قراءة المقطع دون الكشف عن كلمة المرور.",
|
|
420
423
|
gifPreview: "معاينة GIF",
|
|
@@ -432,6 +432,9 @@ const messages = {
|
|
|
432
432
|
embed: "Einbetten",
|
|
433
433
|
shareLink: "Teillink",
|
|
434
434
|
shareWithAgents: "Mit Agenten teilen",
|
|
435
|
+
copyAgentPrompt: "Agent-Prompt kopieren",
|
|
436
|
+
agentPrompt:
|
|
437
|
+
"Rufe diese Clips-Agent-Kontext-URL ab: {{agentContextUrl}}. Verwende transcript.segments fuer den gesprochenen Kontext, rufe recommendedFrames oder die Frame-API-URLs ab, um den Bildschirm zu sehen, und pruefe browserDiagnostics, falls vorhanden, fuer redigierte Konsolenprotokolle und fetch/XHR-Anfragemetadaten.",
|
|
435
438
|
agentTokenDescription:
|
|
436
439
|
"Dieser Agent URL verwendet ein kurzlebiges Token, sodass Agenten den Clip lesen können, ohne das Passwort preiszugeben.",
|
|
437
440
|
gifPreview: "GIF-Vorschau",
|
|
@@ -415,6 +415,9 @@ const messages = {
|
|
|
415
415
|
embed: "Embed",
|
|
416
416
|
shareLink: "Share link",
|
|
417
417
|
shareWithAgents: "Share with agents",
|
|
418
|
+
copyAgentPrompt: "Copy agent prompt",
|
|
419
|
+
agentPrompt:
|
|
420
|
+
"Fetch this Clips agent context URL: {{agentContextUrl}}. Use transcript.segments for spoken context, fetch recommendedFrames or the frame API URLs to see the screen, and check browserDiagnostics if present for redacted console logs and fetch/XHR request metadata.",
|
|
418
421
|
agentTokenDescription:
|
|
419
422
|
"This agent URL uses a short-lived token, so agents can read the clip without exposing the password.",
|
|
420
423
|
gifPreview: "GIF preview",
|
|
@@ -427,6 +427,9 @@ const messages = {
|
|
|
427
427
|
embed: "Insertar",
|
|
428
428
|
shareLink: "Enlace para compartir",
|
|
429
429
|
shareWithAgents: "Compartir con agentes",
|
|
430
|
+
copyAgentPrompt: "Copiar indicación para agente",
|
|
431
|
+
agentPrompt:
|
|
432
|
+
"Obtén esta URL de contexto para agentes de Clips: {{agentContextUrl}}. Usa transcript.segments para el contexto hablado, obtén recommendedFrames o las URLs de la API de fotogramas para ver la pantalla y revisa browserDiagnostics si está presente para ver registros de consola redactados y metadatos de solicitudes fetch/XHR.",
|
|
430
433
|
agentTokenDescription:
|
|
431
434
|
"Este agente URL utiliza un token de corta duración, por lo que los agentes pueden leer el clip sin exponer la contraseña.",
|
|
432
435
|
gifPreview: "vista previa de GIF",
|
|
@@ -427,6 +427,9 @@ const messages = {
|
|
|
427
427
|
embed: "Intégrer",
|
|
428
428
|
shareLink: "Lien de partage",
|
|
429
429
|
shareWithAgents: "Partager avec les agents",
|
|
430
|
+
copyAgentPrompt: "Copier le prompt pour agent",
|
|
431
|
+
agentPrompt:
|
|
432
|
+
"Récupère cette URL de contexte Clips pour agent : {{agentContextUrl}}. Utilise transcript.segments pour le contexte parlé, récupère recommendedFrames ou les URLs de l'API d'images pour voir l'écran, et consulte browserDiagnostics s'il est présent pour les journaux de console expurgés et les métadonnées de requêtes fetch/XHR.",
|
|
430
433
|
agentTokenDescription:
|
|
431
434
|
"Cet agent URL utilise un jeton de courte durée, afin que les agents puissent lire le clip sans exposer le mot de passe.",
|
|
432
435
|
gifPreview: "aperçu de GIF",
|
|
@@ -412,6 +412,9 @@ const messages = {
|
|
|
412
412
|
embed: "एम्बेड",
|
|
413
413
|
shareLink: "शेयर लिंक",
|
|
414
414
|
shareWithAgents: "एजेंटों के साथ साझा करें",
|
|
415
|
+
copyAgentPrompt: "एजेंट प्रॉम्प्ट कॉपी करें",
|
|
416
|
+
agentPrompt:
|
|
417
|
+
"यह Clips एजेंट संदर्भ URL प्राप्त करें: {{agentContextUrl}}। बोले गए संदर्भ के लिए transcript.segments का उपयोग करें, स्क्रीन देखने के लिए recommendedFrames या फ्रेम API URL प्राप्त करें, और यदि browserDiagnostics मौजूद हो तो संशोधित कंसोल लॉग और fetch/XHR अनुरोध मेटाडेटा जांचें।",
|
|
415
418
|
agentTokenDescription:
|
|
416
419
|
"यह एजेंट URL एक अल्पकालिक टोकन का उपयोग करता है, ताकि एजेंट पासवर्ड उजागर किए बिना क्लिप पढ़ सकें।",
|
|
417
420
|
gifPreview: "GIF पूर्वावलोकन",
|
|
@@ -424,6 +424,9 @@ const messages = {
|
|
|
424
424
|
embed: "埋め込み",
|
|
425
425
|
shareLink: "共有リンク",
|
|
426
426
|
shareWithAgents: "エージェントと共有する",
|
|
427
|
+
copyAgentPrompt: "エージェント用プロンプトをコピー",
|
|
428
|
+
agentPrompt:
|
|
429
|
+
"この Clips エージェントコンテキスト URL を取得してください: {{agentContextUrl}}。音声の文脈には transcript.segments を使い、画面を見るために recommendedFrames またはフレーム API URL を取得し、browserDiagnostics がある場合は、編集済みのコンソールログと fetch/XHR リクエストのメタデータを確認してください。",
|
|
427
430
|
agentTokenDescription:
|
|
428
431
|
"このエージェント URL は有効期間の短いトークンを使用するため、エージェントはパスワードを公開せずにクリップを読み取ることができます。",
|
|
429
432
|
gifPreview: "GIF プレビュー",
|
|
@@ -417,6 +417,9 @@ const messages = {
|
|
|
417
417
|
embed: "임베드",
|
|
418
418
|
shareLink: "공유 링크",
|
|
419
419
|
shareWithAgents: "상담원과 공유",
|
|
420
|
+
copyAgentPrompt: "에이전트 프롬프트 복사",
|
|
421
|
+
agentPrompt:
|
|
422
|
+
"이 Clips 에이전트 컨텍스트 URL을 가져오세요: {{agentContextUrl}}. 말한 내용의 맥락은 transcript.segments를 사용하고, 화면을 보기 위해 recommendedFrames 또는 프레임 API URL을 가져오며, browserDiagnostics가 있으면 수정된 콘솔 로그와 fetch/XHR 요청 메타데이터를 확인하세요.",
|
|
420
423
|
agentTokenDescription:
|
|
421
424
|
"이 에이전트 URL는 수명이 짧은 토큰을 사용하므로 에이전트는 비밀번호를 노출하지 않고도 클립을 읽을 수 있습니다.",
|
|
422
425
|
gifPreview: "GIF 미리보기",
|
|
@@ -425,6 +425,9 @@ const messages = {
|
|
|
425
425
|
embed: "Incorporar",
|
|
426
426
|
shareLink: "Link de compartilhamento",
|
|
427
427
|
shareWithAgents: "Compartilhe com agentes",
|
|
428
|
+
copyAgentPrompt: "Copiar prompt para agente",
|
|
429
|
+
agentPrompt:
|
|
430
|
+
"Busque esta URL de contexto para agentes do Clips: {{agentContextUrl}}. Use transcript.segments para o contexto falado, busque recommendedFrames ou as URLs da API de quadros para ver a tela e confira browserDiagnostics, se presente, para logs de console redigidos e metadados de solicitações fetch/XHR.",
|
|
428
431
|
agentTokenDescription:
|
|
429
432
|
"Este agente URL usa um token de curta duração, para que os agentes possam ler o clipe sem expor a senha.",
|
|
430
433
|
gifPreview: "visualização de GIF",
|
|
@@ -399,6 +399,9 @@ const messages = {
|
|
|
399
399
|
embed: "嵌入",
|
|
400
400
|
shareLink: "分享链接",
|
|
401
401
|
shareWithAgents: "与代理商分享",
|
|
402
|
+
copyAgentPrompt: "复制代理提示",
|
|
403
|
+
agentPrompt:
|
|
404
|
+
"获取这个 Clips 代理上下文 URL:{{agentContextUrl}}。使用 transcript.segments 读取语音上下文,获取 recommendedFrames 或帧 API URL 来查看屏幕,并在 browserDiagnostics 存在时检查经过脱敏的控制台日志和 fetch/XHR 请求元数据。",
|
|
402
405
|
agentTokenDescription:
|
|
403
406
|
"该代理 URL 使用短期令牌,因此代理可以在不暴露密码的情况下读取剪辑。",
|
|
404
407
|
gifPreview: "GIF 预览",
|
|
@@ -399,6 +399,9 @@ const messages = {
|
|
|
399
399
|
embed: "嵌入",
|
|
400
400
|
shareLink: "分享連結",
|
|
401
401
|
shareWithAgents: "與 Agent 分享",
|
|
402
|
+
copyAgentPrompt: "複製 Agent 提示",
|
|
403
|
+
agentPrompt:
|
|
404
|
+
"取得這個 Clips Agent 脈絡 URL:{{agentContextUrl}}。使用 transcript.segments 讀取語音脈絡,取得 recommendedFrames 或影格 API URL 來查看螢幕,並在 browserDiagnostics 存在時檢查已遮蔽的主控台記錄和 fetch/XHR 請求中繼資料。",
|
|
402
405
|
agentTokenDescription:
|
|
403
406
|
"此 Agent URL 使用短期權杖,因此 Agent 可以在不暴露密碼的情況下讀取剪輯。",
|
|
404
407
|
gifPreview: "GIF 預覽",
|
|
@@ -57,6 +57,14 @@ function escapeHtml(value: string): string {
|
|
|
57
57
|
.replace(/"/g, """);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function createInsertedNodeId(prefix: string): string {
|
|
61
|
+
const random =
|
|
62
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
63
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
64
|
+
: Math.random().toString(36).slice(2, 14);
|
|
65
|
+
return `inserted-${prefix}-${random}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
function insertBeforeClosingTag(
|
|
61
69
|
html: string,
|
|
62
70
|
closingTag: "main" | "body",
|
|
@@ -70,6 +78,7 @@ function insertBeforeClosingTag(
|
|
|
70
78
|
function appendAssetMarkup(
|
|
71
79
|
html: string,
|
|
72
80
|
args: z.infer<typeof schemaInput>,
|
|
81
|
+
nodeId: string,
|
|
73
82
|
): string {
|
|
74
83
|
const label = args.title?.trim() || args.altText?.trim() || "Generated asset";
|
|
75
84
|
const escapedUrl = escapeHtml(args.assetUrl);
|
|
@@ -82,7 +91,7 @@ function appendAssetMarkup(
|
|
|
82
91
|
? `<video src="${escapedUrl}" controls class="w-full rounded-xl object-cover"></video>`
|
|
83
92
|
: `<img src="${escapedUrl}" alt="${escapeHtml(args.altText?.trim() || label)}" class="w-full rounded-xl object-cover" />`;
|
|
84
93
|
const snippet = `
|
|
85
|
-
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset${assetIdAttr}>
|
|
94
|
+
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapedLabel}"${assetIdAttr}>
|
|
86
95
|
<figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
87
96
|
${media}
|
|
88
97
|
<figcaption class="px-4 py-3 text-sm text-slate-600">${escapedLabel}</figcaption>
|
|
@@ -191,7 +200,8 @@ export default defineAction({
|
|
|
191
200
|
// Collab read is best-effort; fall back to stored content.
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
const
|
|
203
|
+
const insertedNodeId = createInsertedNodeId("asset");
|
|
204
|
+
const content = appendAssetMarkup(base, args, insertedNodeId);
|
|
195
205
|
const now = new Date().toISOString();
|
|
196
206
|
await db
|
|
197
207
|
.update(schema.designFiles)
|
|
@@ -213,6 +223,8 @@ export default defineAction({
|
|
|
213
223
|
fileId: file.id,
|
|
214
224
|
filename: file.filename,
|
|
215
225
|
inserted: true,
|
|
226
|
+
insertedNodeId,
|
|
227
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
216
228
|
assetId: args.assetId ?? null,
|
|
217
229
|
assetUrl: args.assetUrl,
|
|
218
230
|
};
|
|
@@ -50,9 +50,17 @@ function insertBeforeClosingTag(
|
|
|
50
50
|
return html.replace(pattern, `${snippet}\n</${closingTag}>`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function createInsertedNodeId(prefix: string): string {
|
|
54
|
+
const random =
|
|
55
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
56
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
57
|
+
: Math.random().toString(36).slice(2, 14);
|
|
58
|
+
return `inserted-${prefix}-${random}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nativeSnippet(kind: DesignNativeAssetKind, nodeId: string): string {
|
|
54
62
|
const attrs = (componentName: string) =>
|
|
55
|
-
`data-agent-native-native-asset data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
63
|
+
`data-agent-native-native-asset data-agent-native-node-id="${nodeId}" data-agent-native-component="${componentName}" data-agent-native-layer-name="${componentName}"`;
|
|
56
64
|
switch (kind) {
|
|
57
65
|
case "section-frame":
|
|
58
66
|
return `
|
|
@@ -137,8 +145,9 @@ function nativeSnippet(kind: DesignNativeAssetKind): string {
|
|
|
137
145
|
function appendNativeAssetMarkup(
|
|
138
146
|
html: string,
|
|
139
147
|
kind: DesignNativeAssetKind,
|
|
148
|
+
nodeId: string,
|
|
140
149
|
): string {
|
|
141
|
-
const snippet = nativeSnippet(kind);
|
|
150
|
+
const snippet = nativeSnippet(kind, nodeId);
|
|
142
151
|
return (
|
|
143
152
|
insertBeforeClosingTag(html, "main", snippet) ??
|
|
144
153
|
insertBeforeClosingTag(html, "body", snippet) ??
|
|
@@ -239,7 +248,8 @@ export default defineAction({
|
|
|
239
248
|
// Collab read is best-effort; fall back to stored content.
|
|
240
249
|
}
|
|
241
250
|
|
|
242
|
-
const
|
|
251
|
+
const insertedNodeId = createInsertedNodeId("native");
|
|
252
|
+
const content = appendNativeAssetMarkup(base, args.kind, insertedNodeId);
|
|
243
253
|
const now = new Date().toISOString();
|
|
244
254
|
await db
|
|
245
255
|
.update(schema.designFiles)
|
|
@@ -261,6 +271,8 @@ export default defineAction({
|
|
|
261
271
|
fileId: file.id,
|
|
262
272
|
filename: file.filename,
|
|
263
273
|
inserted: true,
|
|
274
|
+
insertedNodeId,
|
|
275
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
264
276
|
source: "design-native",
|
|
265
277
|
kind: args.kind,
|
|
266
278
|
};
|
|
@@ -66,6 +66,14 @@ function escapeHtml(value: string): string {
|
|
|
66
66
|
.replace(/"/g, """);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function createInsertedNodeId(prefix: string): string {
|
|
70
|
+
const random =
|
|
71
|
+
typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
72
|
+
? crypto.randomUUID().replace(/-/g, "").slice(0, 12)
|
|
73
|
+
: Math.random().toString(36).slice(2, 14);
|
|
74
|
+
return `inserted-${prefix}-${random}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
69
77
|
function insertBeforeClosingTag(
|
|
70
78
|
html: string,
|
|
71
79
|
closingTag: "main" | "body",
|
|
@@ -83,6 +91,7 @@ function optionalDataAttribute(name: string, value: string | undefined) {
|
|
|
83
91
|
function appendFigmaAssetMarkup(
|
|
84
92
|
html: string,
|
|
85
93
|
args: z.infer<typeof schemaInput>,
|
|
94
|
+
nodeId: string,
|
|
86
95
|
): string {
|
|
87
96
|
const label = args.name?.trim() || "Figma library asset";
|
|
88
97
|
const description = args.description?.trim();
|
|
@@ -90,7 +99,7 @@ function appendFigmaAssetMarkup(
|
|
|
90
99
|
? `<a href="${escapeHtml(args.sourceUrl)}" target="_blank" rel="noreferrer" class="text-slate-500 underline decoration-slate-300 underline-offset-2 hover:text-slate-800">Open in Figma</a>`
|
|
91
100
|
: "";
|
|
92
101
|
const snippet = `
|
|
93
|
-
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
|
|
102
|
+
<section class="mx-auto my-8 max-w-5xl px-4" data-agent-native-asset-source="figma" data-agent-native-figma-asset data-agent-native-node-id="${escapeHtml(nodeId)}" data-agent-native-layer-name="${escapeHtml(label)}" data-figma-file-key="${escapeHtml(args.fileKey)}"${optionalDataAttribute("data-figma-node-id", args.nodeId)}${optionalDataAttribute("data-figma-component-key", args.componentKey)} data-figma-asset-kind="${escapeHtml(args.kind)}">
|
|
94
103
|
<figure class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
95
104
|
<img src="${escapeHtml(args.renderUrl)}" alt="${escapeHtml(label)}" class="w-full rounded-t-2xl object-contain" />
|
|
96
105
|
<figcaption class="flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-3 text-sm text-slate-600">
|
|
@@ -201,7 +210,8 @@ export default defineAction({
|
|
|
201
210
|
// Collab read is best-effort; fall back to stored content.
|
|
202
211
|
}
|
|
203
212
|
|
|
204
|
-
const
|
|
213
|
+
const insertedNodeId = createInsertedNodeId("figma");
|
|
214
|
+
const content = appendFigmaAssetMarkup(base, args, insertedNodeId);
|
|
205
215
|
const now = new Date().toISOString();
|
|
206
216
|
await db
|
|
207
217
|
.update(schema.designFiles)
|
|
@@ -223,6 +233,8 @@ export default defineAction({
|
|
|
223
233
|
fileId: file.id,
|
|
224
234
|
filename: file.filename,
|
|
225
235
|
inserted: true,
|
|
236
|
+
insertedNodeId,
|
|
237
|
+
insertedSelector: `[data-agent-native-node-id="${insertedNodeId}"]`,
|
|
226
238
|
source: "figma",
|
|
227
239
|
fileKey: args.fileKey,
|
|
228
240
|
nodeId: args.nodeId ?? null,
|
|
@@ -1775,6 +1775,11 @@ export function DesignCanvas({
|
|
|
1775
1775
|
// explicit viewport width (e.g. 390 / 768 / 1280 side-by-side breakpoints).
|
|
1776
1776
|
const resolvedWidth =
|
|
1777
1777
|
previewWidthPx != null ? `${previewWidthPx}px` : iframeWidth;
|
|
1778
|
+
const focusScrollSurface = useCallback(() => {
|
|
1779
|
+
const surface = scrollContainerRef.current;
|
|
1780
|
+
if (!surface || document.activeElement === surface) return;
|
|
1781
|
+
surface.focus({ preventScroll: true });
|
|
1782
|
+
}, []);
|
|
1778
1783
|
|
|
1779
1784
|
// Wrap the iframe in a positioned container so DrawOverlay /
|
|
1780
1785
|
// CanvasCommentPins can absolutely-position themselves on top of the
|
|
@@ -1901,6 +1906,9 @@ export function DesignCanvas({
|
|
|
1901
1906
|
return (
|
|
1902
1907
|
<div
|
|
1903
1908
|
ref={scrollContainerRef}
|
|
1909
|
+
tabIndex={-1}
|
|
1910
|
+
onPointerEnter={focusScrollSurface}
|
|
1911
|
+
onMouseEnter={focusScrollSurface}
|
|
1904
1912
|
className="relative h-full w-full overflow-hidden"
|
|
1905
1913
|
>
|
|
1906
1914
|
{iframeElement}
|
|
@@ -1915,6 +1923,9 @@ export function DesignCanvas({
|
|
|
1915
1923
|
return (
|
|
1916
1924
|
<div
|
|
1917
1925
|
ref={scrollContainerRef}
|
|
1926
|
+
tabIndex={-1}
|
|
1927
|
+
onPointerEnter={focusScrollSurface}
|
|
1928
|
+
onMouseEnter={focusScrollSurface}
|
|
1918
1929
|
className="relative h-full w-full overflow-hidden"
|
|
1919
1930
|
style={{
|
|
1920
1931
|
width: embeddedFrame.displayWidth,
|
|
@@ -1945,6 +1956,9 @@ export function DesignCanvas({
|
|
|
1945
1956
|
return (
|
|
1946
1957
|
<div
|
|
1947
1958
|
ref={scrollContainerRef}
|
|
1959
|
+
tabIndex={-1}
|
|
1960
|
+
onPointerEnter={focusScrollSurface}
|
|
1961
|
+
onMouseEnter={focusScrollSurface}
|
|
1948
1962
|
className="relative flex-1 h-full overflow-auto"
|
|
1949
1963
|
>
|
|
1950
1964
|
{/* Canvas area. "none" mode fills the canvas (responsive preview);
|