@agent-native/core 0.84.23 → 0.84.25

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 (66) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +8 -6
  5. package/corpus/core/src/agent/run-manager.ts +57 -6
  6. package/corpus/core/src/client/AssistantChat.tsx +82 -15
  7. package/corpus/core/src/client/active-run-state.ts +22 -0
  8. package/corpus/core/src/client/chat/message-components.tsx +2 -1
  9. package/corpus/core/src/client/chat/tool-call-display.tsx +18 -2
  10. package/corpus/core/src/client/sse-event-processor.ts +196 -51
  11. package/corpus/core/src/client/tool-display.ts +1 -1
  12. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +15 -13
  13. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +4 -2
  14. package/corpus/templates/clips/app/components/player/share-dialog.tsx +11 -2
  15. package/corpus/templates/clips/app/i18n/ar-SA.ts +4 -0
  16. package/corpus/templates/clips/app/i18n/de-DE.ts +4 -0
  17. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  18. package/corpus/templates/clips/app/i18n/es-ES.ts +4 -0
  19. package/corpus/templates/clips/app/i18n/fr-FR.ts +4 -0
  20. package/corpus/templates/clips/app/i18n/hi-IN.ts +4 -0
  21. package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -0
  22. package/corpus/templates/clips/app/i18n/ko-KR.ts +4 -0
  23. package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -0
  24. package/corpus/templates/clips/app/i18n/zh-CN.ts +4 -0
  25. package/corpus/templates/clips/app/i18n/zh-TW.ts +4 -0
  26. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +31 -3
  27. package/corpus/templates/clips/changelog/2026-07-01-clip-embeds-now-render-as-a-clean-video-only-player-without-.md +6 -0
  28. package/corpus/templates/clips/changelog/2026-07-01-shared-clips-can-now-copy-a-ready-to-send-prompt-that-tells-.md +6 -0
  29. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  30. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +2 -2
  31. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +41 -31
  32. package/dist/agent/production-agent.d.ts.map +1 -1
  33. package/dist/agent/production-agent.js +8 -6
  34. package/dist/agent/production-agent.js.map +1 -1
  35. package/dist/agent/run-manager.d.ts.map +1 -1
  36. package/dist/agent/run-manager.js +56 -4
  37. package/dist/agent/run-manager.js.map +1 -1
  38. package/dist/client/AssistantChat.d.ts +2 -1
  39. package/dist/client/AssistantChat.d.ts.map +1 -1
  40. package/dist/client/AssistantChat.js +74 -18
  41. package/dist/client/AssistantChat.js.map +1 -1
  42. package/dist/client/active-run-state.d.ts +2 -0
  43. package/dist/client/active-run-state.d.ts.map +1 -1
  44. package/dist/client/active-run-state.js +17 -0
  45. package/dist/client/active-run-state.js.map +1 -1
  46. package/dist/client/chat/message-components.d.ts.map +1 -1
  47. package/dist/client/chat/message-components.js +3 -1
  48. package/dist/client/chat/message-components.js.map +1 -1
  49. package/dist/client/chat/tool-call-display.d.ts +3 -1
  50. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  51. package/dist/client/chat/tool-call-display.js +8 -8
  52. package/dist/client/chat/tool-call-display.js.map +1 -1
  53. package/dist/client/sse-event-processor.d.ts +1 -0
  54. package/dist/client/sse-event-processor.d.ts.map +1 -1
  55. package/dist/client/sse-event-processor.js +163 -45
  56. package/dist/client/sse-event-processor.js.map +1 -1
  57. package/dist/client/tool-display.js +1 -1
  58. package/dist/client/tool-display.js.map +1 -1
  59. package/dist/collab/awareness.d.ts +2 -2
  60. package/dist/collab/awareness.d.ts.map +1 -1
  61. package/dist/collab/routes.d.ts +1 -1
  62. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  63. package/dist/notifications/routes.d.ts +3 -3
  64. package/dist/observability/routes.d.ts +6 -6
  65. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  66. package/package.json +1 -1
@@ -27,6 +27,7 @@ export type ContentPart =
27
27
  mcpApp?: AgentMcpAppPayload;
28
28
  chatUI?: ActionChatUIConfig;
29
29
  activity?: boolean;
30
+ repeatCount?: number;
30
31
  /**
31
32
  * Set when the server emitted an `approval_required` event for this tool
32
33
  * call (opt-in `needsApproval` actions). The action did NOT run; the UI
@@ -152,6 +153,7 @@ type ActivityTrailEntry = AgentActivityTrailEntry;
152
153
  type PreparingActionState = {
153
154
  tool?: string;
154
155
  startedAt?: number;
156
+ lastProgressBytes?: number;
155
157
  /**
156
158
  * Timestamp of the last real streaming progress for the in-preparation tool
157
159
  * input. The server emits a throttled `activity` heartbeat per
@@ -184,6 +186,17 @@ function isPreparingActionActivity(ev: SSEEvent): boolean {
184
186
  return label.startsWith("preparing ") && label.includes(" action");
185
187
  }
186
188
 
189
+ function isMeaningfulProgressEvent(
190
+ ev: SSEEvent,
191
+ actionPreparationProgress?: boolean,
192
+ ): boolean {
193
+ if (ev.type === "stream_keepalive") return false;
194
+ if (ev.type === "activity" && isPreparingActionActivity(ev)) {
195
+ return actionPreparationProgress === true;
196
+ }
197
+ return true;
198
+ }
199
+
187
200
  function baseActivityLabel(ev: SSEEvent, tool?: string): string {
188
201
  return humanizeToolLabelText(ev.label ?? "Working", tool);
189
202
  }
@@ -253,19 +266,28 @@ function updatePreparingActionState(
253
266
  state: PreparingActionState,
254
267
  ev: SSEEvent,
255
268
  now: number,
256
- ) {
269
+ ): boolean | undefined {
257
270
  if (ev.type === "activity" && isPreparingActionActivity(ev)) {
258
271
  const tool = ev.tool?.trim() || undefined;
259
- if (!tool) return;
272
+ if (!tool) return false;
260
273
  if (state.tool !== tool || state.startedAt === undefined) {
261
274
  state.tool = tool;
262
275
  state.startedAt = now;
276
+ state.lastProgressAt = undefined;
277
+ state.lastProgressBytes = undefined;
263
278
  }
264
- // Every tool-input activity is a throttled proof the model is still
265
- // streaming this action's argument. Treat it as progress so large inputs
266
- // that take many seconds/minutes to stream are NOT flagged as stalled.
267
- state.lastProgressAt = now;
268
- return;
279
+ const progressBytes = activityProgressBytes(ev);
280
+ const previousBytes = state.lastProgressBytes ?? 0;
281
+ if (progressBytes !== undefined) {
282
+ state.lastProgressBytes = Math.max(previousBytes, progressBytes);
283
+ }
284
+ if (progressBytes !== undefined && progressBytes > previousBytes) {
285
+ // A byte increase is proof the model is still streaming this action's
286
+ // argument. Repeated zero-byte prep activity is only a heartbeat.
287
+ state.lastProgressAt = now;
288
+ return true;
289
+ }
290
+ return false;
269
291
  }
270
292
 
271
293
  if (
@@ -280,7 +302,9 @@ function updatePreparingActionState(
280
302
  state.tool = undefined;
281
303
  state.startedAt = undefined;
282
304
  state.lastProgressAt = undefined;
305
+ state.lastProgressBytes = undefined;
283
306
  }
307
+ return undefined;
284
308
  }
285
309
 
286
310
  function hasStalledPreparingAction(state: PreparingActionState, now: number) {
@@ -291,8 +315,9 @@ function hasStalledPreparingAction(state: PreparingActionState, now: number) {
291
315
  // survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
292
316
  return (
293
317
  state.tool !== undefined &&
294
- state.lastProgressAt !== undefined &&
295
- now - state.lastProgressAt >= SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
318
+ state.startedAt !== undefined &&
319
+ now - (state.lastProgressAt ?? state.startedAt) >=
320
+ SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
296
321
  );
297
322
  }
298
323
 
@@ -460,6 +485,81 @@ function pendingToolNames(content: ContentPart[]): {
460
485
  return { activity: [...activity], running: [...running] };
461
486
  }
462
487
 
488
+ function contentSnapshot(content: ContentPart[]): ContentPart[] {
489
+ return content.map((part) => {
490
+ if (part.type === "text") return { ...part };
491
+ return {
492
+ ...part,
493
+ args: { ...part.args },
494
+ ...(part.mcpApp ? { mcpApp: { ...part.mcpApp } } : {}),
495
+ ...(part.chatUI ? { chatUI: { ...part.chatUI } } : {}),
496
+ ...(part.approval ? { approval: { ...part.approval } } : {}),
497
+ ...(part.structuredMeta
498
+ ? { structuredMeta: { ...part.structuredMeta } }
499
+ : {}),
500
+ };
501
+ });
502
+ }
503
+
504
+ function repeatSignatureValue(value: unknown): string {
505
+ try {
506
+ return JSON.stringify(value);
507
+ } catch {
508
+ return String(value ?? "");
509
+ }
510
+ }
511
+
512
+ function completedToolRepeatSignature(
513
+ part: Extract<ContentPart, { type: "tool-call" }>,
514
+ ): string | null {
515
+ if (
516
+ part.result === undefined ||
517
+ part.activity === true ||
518
+ part.approval ||
519
+ part.mcpApp ||
520
+ part.chatUI ||
521
+ part.structuredMeta
522
+ ) {
523
+ return null;
524
+ }
525
+ return [
526
+ part.toolName,
527
+ part.argsText,
528
+ repeatSignatureValue(part.args),
529
+ part.result,
530
+ part.isError === true ? "error" : "",
531
+ part.completedSideEffect === true ? "side-effect" : "",
532
+ ].join("\u0000");
533
+ }
534
+
535
+ function coalesceCompletedToolRepeat(
536
+ content: ContentPart[],
537
+ completedIndex: number,
538
+ ): void {
539
+ const current = content[completedIndex];
540
+ const previous = content[completedIndex - 1];
541
+ if (
542
+ !current ||
543
+ !previous ||
544
+ current.type !== "tool-call" ||
545
+ previous.type !== "tool-call"
546
+ ) {
547
+ return;
548
+ }
549
+
550
+ const currentSignature = completedToolRepeatSignature(current);
551
+ if (
552
+ !currentSignature ||
553
+ currentSignature !== completedToolRepeatSignature(previous)
554
+ ) {
555
+ return;
556
+ }
557
+
558
+ previous.repeatCount =
559
+ (previous.repeatCount ?? 1) + (current.repeatCount ?? 1);
560
+ content.splice(completedIndex, 1);
561
+ }
562
+
463
563
  function formatToolNames(tools: string[]): string {
464
564
  const names = tools.map(humanizeToolName);
465
565
  if (names.length === 0) return "the promised action";
@@ -578,7 +678,7 @@ export function processEvent(
578
678
  }
579
679
  return {
580
680
  action: "yield",
581
- result: { content: [...content] } as ChatModelRunResult,
681
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
582
682
  };
583
683
  }
584
684
 
@@ -615,7 +715,7 @@ export function processEvent(
615
715
  }
616
716
  return {
617
717
  action: "yield",
618
- result: { content: [...content] } as ChatModelRunResult,
718
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
619
719
  };
620
720
  }
621
721
 
@@ -672,7 +772,7 @@ export function processEvent(
672
772
  }
673
773
  return {
674
774
  action: "yield",
675
- result: { content: [...content] } as ChatModelRunResult,
775
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
676
776
  };
677
777
  }
678
778
 
@@ -694,7 +794,7 @@ export function processEvent(
694
794
  }
695
795
  return {
696
796
  action: "yield",
697
- result: { content: [...content] } as ChatModelRunResult,
797
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
698
798
  };
699
799
  }
700
800
 
@@ -730,11 +830,12 @@ export function processEvent(
730
830
  if (part.activity !== true && part.isError !== true) {
731
831
  markCompletedToolAfterAssistantText(state, part.toolName);
732
832
  }
833
+ coalesceCompletedToolRepeat(content, doneIdx);
733
834
  }
734
835
  }
735
836
  return {
736
837
  action: "yield",
737
- result: { content: [...content] } as ChatModelRunResult,
838
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
738
839
  };
739
840
  }
740
841
 
@@ -764,7 +865,7 @@ export function processEvent(
764
865
  }
765
866
  return {
766
867
  action: "yield",
767
- result: { content: [...content] } as ChatModelRunResult,
868
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
768
869
  };
769
870
  }
770
871
 
@@ -784,7 +885,7 @@ export function processEvent(
784
885
  }
785
886
  return {
786
887
  action: "yield",
787
- result: { content: [...content] } as ChatModelRunResult,
888
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
788
889
  };
789
890
  }
790
891
 
@@ -826,7 +927,7 @@ export function processEvent(
826
927
  return {
827
928
  action: "missing_api_key",
828
929
  result: {
829
- content: [...content],
930
+ content: contentSnapshot(content),
830
931
  status: { type: "incomplete" as const, reason: "error" as const },
831
932
  metadata: { custom: { runError } },
832
933
  } as ChatModelRunResult,
@@ -938,7 +1039,7 @@ export function processEvent(
938
1039
  return {
939
1040
  action: "error",
940
1041
  result: {
941
- content: [...content],
1042
+ content: contentSnapshot(content),
942
1043
  status: { type: "incomplete" as const, reason: "error" as const },
943
1044
  metadata: { custom: { runError } },
944
1045
  } as ChatModelRunResult,
@@ -974,7 +1075,7 @@ export function processEvent(
974
1075
  return {
975
1076
  action: "error",
976
1077
  result: {
977
- content: [...content],
1078
+ content: contentSnapshot(content),
978
1079
  status: { type: "incomplete" as const, reason: "error" as const },
979
1080
  metadata: { custom: { runError } },
980
1081
  } as ChatModelRunResult,
@@ -993,7 +1094,7 @@ export function processEvent(
993
1094
  return {
994
1095
  action: "done",
995
1096
  result: {
996
- content: [...content],
1097
+ content: contentSnapshot(content),
997
1098
  status: { type: "complete" as const, reason: "stop" as const },
998
1099
  metadata: {
999
1100
  custom: {
@@ -1009,7 +1110,7 @@ export function processEvent(
1009
1110
  }
1010
1111
  return {
1011
1112
  action: "done",
1012
- result: { content: [...content] } as ChatModelRunResult,
1113
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
1013
1114
  };
1014
1115
  }
1015
1116
 
@@ -1075,16 +1176,30 @@ export async function* readSSEStream(
1075
1176
 
1076
1177
  try {
1077
1178
  while (true) {
1078
- const { done, value } = await readChunkWithProgressTimeout(
1079
- reader,
1080
- lastMeaningfulEventAt,
1081
- );
1179
+ let readResult: ReadableStreamReadResult<Uint8Array>;
1180
+ try {
1181
+ readResult = await readChunkWithProgressTimeout(
1182
+ reader,
1183
+ lastMeaningfulEventAt,
1184
+ );
1185
+ } catch (err) {
1186
+ if (err instanceof AgentAutoContinueSignal) {
1187
+ throw new AgentAutoContinueSignal({
1188
+ reason: err.reason,
1189
+ maxIterations: err.maxIterations,
1190
+ activityTrail: [...activityTrail],
1191
+ errorInfo: err.errorInfo,
1192
+ });
1193
+ }
1194
+ throw err;
1195
+ }
1196
+ const { done, value } = readResult;
1082
1197
  if (done) break;
1083
1198
 
1084
1199
  buf += decoder.decode(value, { stream: true });
1085
1200
  const lines = buf.split("\n");
1086
1201
  buf = lines.pop() ?? "";
1087
- let sawDataEvent = false;
1202
+ let sawProgressEvent = false;
1088
1203
 
1089
1204
  for (const line of lines) {
1090
1205
  if (!line.startsWith("data: ")) continue;
@@ -1097,10 +1212,16 @@ export async function* readSSEStream(
1097
1212
  } catch {
1098
1213
  continue;
1099
1214
  }
1100
- sawDataEvent = true;
1101
1215
  const now = Date.now();
1102
- lastMeaningfulEventAt = now;
1103
- updatePreparingActionState(preparingActionState, ev, now);
1216
+ const actionPreparationProgress = updatePreparingActionState(
1217
+ preparingActionState,
1218
+ ev,
1219
+ now,
1220
+ );
1221
+ if (isMeaningfulProgressEvent(ev, actionPreparationProgress)) {
1222
+ sawProgressEvent = true;
1223
+ lastMeaningfulEventAt = now;
1224
+ }
1104
1225
 
1105
1226
  // Track sequence number for reconnection
1106
1227
  if (ev.seq !== undefined && onSeq) {
@@ -1162,10 +1283,13 @@ export async function* readSSEStream(
1162
1283
  }
1163
1284
 
1164
1285
  if (
1165
- !sawDataEvent &&
1286
+ !sawProgressEvent &&
1166
1287
  Date.now() - lastMeaningfulEventAt >= SSE_NO_PROGRESS_TIMEOUT_MS
1167
1288
  ) {
1168
- throw new AgentAutoContinueSignal({ reason: "no_progress" });
1289
+ throw new AgentAutoContinueSignal({
1290
+ reason: "no_progress",
1291
+ activityTrail: [...activityTrail],
1292
+ });
1169
1293
  }
1170
1294
  }
1171
1295
  } finally {
@@ -1218,18 +1342,31 @@ export async function readSSEStreamRaw(
1218
1342
 
1219
1343
  try {
1220
1344
  while (true) {
1221
- const { done, value } = await readChunkWithProgressTimeout(
1222
- reader,
1223
- lastMeaningfulEventAt,
1224
- );
1345
+ let readResult: ReadableStreamReadResult<Uint8Array>;
1346
+ try {
1347
+ readResult = await readChunkWithProgressTimeout(
1348
+ reader,
1349
+ lastMeaningfulEventAt,
1350
+ );
1351
+ } catch (err) {
1352
+ if (err instanceof AgentAutoContinueSignal) {
1353
+ throw new AgentAutoContinueSignal({
1354
+ reason: err.reason,
1355
+ maxIterations: err.maxIterations,
1356
+ activityTrail: [...activityTrail],
1357
+ errorInfo: err.errorInfo,
1358
+ });
1359
+ }
1360
+ throw err;
1361
+ }
1362
+ const { done, value } = readResult;
1225
1363
  if (done) break;
1226
1364
 
1227
1365
  buf += decoder.decode(value, { stream: true });
1228
1366
  const lines = buf.split("\n");
1229
1367
  buf = lines.pop() ?? "";
1230
1368
 
1231
- let updated = false;
1232
- let sawDataEvent = false;
1369
+ let sawProgressEvent = false;
1233
1370
  for (const line of lines) {
1234
1371
  if (!line.startsWith("data: ")) continue;
1235
1372
  const raw = line.slice(6).trim();
@@ -1241,10 +1378,16 @@ export async function readSSEStreamRaw(
1241
1378
  } catch {
1242
1379
  continue;
1243
1380
  }
1244
- sawDataEvent = true;
1245
1381
  const now = Date.now();
1246
- lastMeaningfulEventAt = now;
1247
- updatePreparingActionState(preparingActionState, ev, now);
1382
+ const actionPreparationProgress = updatePreparingActionState(
1383
+ preparingActionState,
1384
+ ev,
1385
+ now,
1386
+ );
1387
+ if (isMeaningfulProgressEvent(ev, actionPreparationProgress)) {
1388
+ sawProgressEvent = true;
1389
+ lastMeaningfulEventAt = now;
1390
+ }
1248
1391
 
1249
1392
  if (ev.seq !== undefined && onSeq) {
1250
1393
  onSeq(ev.seq);
@@ -1287,10 +1430,12 @@ export async function readSSEStreamRaw(
1287
1430
  action === "error" ||
1288
1431
  action === "missing_api_key"
1289
1432
  ) {
1290
- updated = true;
1433
+ onUpdate(contentSnapshot(content));
1434
+ emittedLatestContent = true;
1291
1435
  }
1292
1436
  if (action === "auto_continue") {
1293
- onUpdate([...content]);
1437
+ onUpdate(contentSnapshot(content));
1438
+ emittedLatestContent = true;
1294
1439
  throw new AgentAutoContinueSignal(
1295
1440
  autoContinue
1296
1441
  ? { ...autoContinue, activityTrail: [...activityTrail] }
@@ -1298,7 +1443,7 @@ export async function readSSEStreamRaw(
1298
1443
  );
1299
1444
  }
1300
1445
  if (hasStalledPreparingAction(preparingActionState, Date.now())) {
1301
- onUpdate([...content]);
1446
+ onUpdate(contentSnapshot(content));
1302
1447
  throw new AgentAutoContinueSignal({
1303
1448
  reason: "no_progress",
1304
1449
  activityTrail: [...activityTrail],
@@ -1309,20 +1454,18 @@ export async function readSSEStreamRaw(
1309
1454
  action === "error" ||
1310
1455
  action === "missing_api_key"
1311
1456
  ) {
1312
- onUpdate([...content]);
1313
1457
  return;
1314
1458
  }
1315
1459
  }
1316
1460
 
1317
- if (updated) {
1318
- onUpdate([...content]);
1319
- emittedLatestContent = true;
1320
- }
1321
1461
  if (
1322
- !sawDataEvent &&
1462
+ !sawProgressEvent &&
1323
1463
  Date.now() - lastMeaningfulEventAt >= SSE_NO_PROGRESS_TIMEOUT_MS
1324
1464
  ) {
1325
- throw new AgentAutoContinueSignal({ reason: "no_progress" });
1465
+ throw new AgentAutoContinueSignal({
1466
+ reason: "no_progress",
1467
+ activityTrail: [...activityTrail],
1468
+ });
1326
1469
  }
1327
1470
  }
1328
1471
  } finally {
@@ -1332,6 +1475,8 @@ export async function readSSEStreamRaw(
1332
1475
  // See readSSEStream: cancellation may race lock release in browsers.
1333
1476
  }
1334
1477
  }
1335
- if (content.length > 0 && !emittedLatestContent) onUpdate([...content]);
1478
+ if (content.length > 0 && !emittedLatestContent) {
1479
+ onUpdate(contentSnapshot(content));
1480
+ }
1336
1481
  throw new AgentAutoContinueSignal({ reason: "stream_ended" });
1337
1482
  }
@@ -1,5 +1,5 @@
1
1
  const TOOL_DISPLAY_NAMES: Record<string, string> = {
2
- "delete-file": "delete screen",
2
+ "delete-file": "remove screen",
3
3
  "get-design-snapshot": "get screen snapshot",
4
4
  "edit-design": "edit screen",
5
5
  };
@@ -466,6 +466,21 @@ export function SqlChartCard({
466
466
  <TooltipContent>{t("sqlDashboard.refreshing")}</TooltipContent>
467
467
  </Tooltip>
468
468
  ) : null}
469
+ {editable && onSaveSql ? (
470
+ <ViewSqlPopover
471
+ panel={panel}
472
+ resolvedSql={resolvedSql}
473
+ onSaveSql={onSaveSql}
474
+ >
475
+ <button
476
+ className="p-1 rounded text-muted-foreground hover:text-foreground"
477
+ aria-label={t("sqlDashboard.viewSql")}
478
+ title={t("sqlDashboard.viewSql")}
479
+ >
480
+ <IconCode className="h-3.5 w-3.5" />
481
+ </button>
482
+ </ViewSqlPopover>
483
+ ) : null}
469
484
  {showPanelMenu ? (
470
485
  <DropdownMenu>
471
486
  <Tooltip>
@@ -503,19 +518,6 @@ export function SqlChartCard({
503
518
  {editable && panel.chartType === "table" ? (
504
519
  <DropdownMenuSeparator />
505
520
  ) : null}
506
- {editable && onSaveSql ? (
507
- <ViewSqlPopover
508
- panel={panel}
509
- resolvedSql={resolvedSql}
510
- onSaveSql={onSaveSql}
511
- >
512
- <DropdownMenuItem onSelect={(e) => e.preventDefault()}>
513
- <IconCode className="h-4 w-4 mr-2" />
514
- {t("sqlDashboard.viewSql")}
515
- </DropdownMenuItem>
516
- </ViewSqlPopover>
517
- ) : null}
518
- {editable ? <DropdownMenuSeparator /> : null}
519
521
  {editable && onEdit && (
520
522
  <DropdownMenuItem onSelect={() => onEdit()}>
521
523
  <IconPencil className="h-4 w-4 mr-2" />
@@ -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. The context response points agents at the transcript and
188
- frame APIs so they can fetch only the visual context they need.
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")}
@@ -452,8 +461,8 @@ function ClipsEmbedConfigurator({
452
461
 
453
462
  const code =
454
463
  mode === "responsive"
455
- ? `<div style="position:relative;padding-bottom:56.25%;height:0"><iframe src="${src}" frameborder="0" allowfullscreen allow="autoplay; picture-in-picture" style="position:absolute;inset:0;width:100%;height:100%"></iframe></div>`
456
- : `<iframe src="${src}" width="${width}" height="${height}" frameborder="0" allowfullscreen allow="autoplay; picture-in-picture"></iframe>`;
464
+ ? `<div style="position:relative;padding-bottom:56.25%;height:0;background:#000;overflow:hidden"><iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#000;overflow:hidden"></iframe></div>`
465
+ : `<iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" width="${width}" height="${height}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="display:block;max-width:100%;border:0;background:#000;overflow:hidden"></iframe>`;
457
466
 
458
467
  return (
459
468
  <div className="space-y-3">
@@ -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",
@@ -428,6 +431,7 @@ const messages = {
428
431
  askOwnerPublic: "اطلب من المالك نشره.",
429
432
  responsive: "مستجيب (16:9)",
430
433
  fixedSize: "حجم ثابت",
434
+ embedIframeTitle: "فيديو Clips",
431
435
  width: "عرض",
432
436
  height: "ارتفاع",
433
437
  autoplay: "التشغيل التلقائي",
@@ -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",
@@ -445,6 +448,7 @@ const messages = {
445
448
  askOwnerPublic: "Bitten Sie den Eigentümer, es öffentlich zu machen.",
446
449
  responsive: "Responsiv (16:9)",
447
450
  fixedSize: "Feste Größe",
451
+ embedIframeTitle: "Clips-Video",
448
452
  width: "Breite",
449
453
  height: "Höhe",
450
454
  autoplay: "Übersetzt: Autoplay",
@@ -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",
@@ -428,6 +431,7 @@ const messages = {
428
431
  askOwnerPublic: "Ask the owner to make it public.",
429
432
  responsive: "Responsive (16:9)",
430
433
  fixedSize: "Fixed size",
434
+ embedIframeTitle: "Clips video",
431
435
  width: "Width",
432
436
  height: "Height",
433
437
  autoplay: "Autoplay",
@@ -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",
@@ -440,6 +443,7 @@ const messages = {
440
443
  askOwnerPublic: "Pídele al propietario que lo haga público.",
441
444
  responsive: "Responsivo (16:9)",
442
445
  fixedSize: "Tamaño fijo",
446
+ embedIframeTitle: "Video de Clips",
443
447
  width: "Ancho",
444
448
  height: "Altura",
445
449
  autoplay: "Reproducción automática",
@@ -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",
@@ -440,6 +443,7 @@ const messages = {
440
443
  askOwnerPublic: "Demandez au propriétaire de le rendre public.",
441
444
  responsive: "Réactif (16:9)",
442
445
  fixedSize: "Taille fixe",
446
+ embedIframeTitle: "Video Clips",
443
447
  width: "Largeur",
444
448
  height: "Hauteur",
445
449
  autoplay: "Lecture automatique",
@@ -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 पूर्वावलोकन",
@@ -425,6 +428,7 @@ const messages = {
425
428
  askOwnerPublic: "मालिक से इसे सार्वजनिक करने के लिए कहें.",
426
429
  responsive: "प्रतिक्रियाशील (16:9)",
427
430
  fixedSize: "निश्चित आकार",
431
+ embedIframeTitle: "Clips वीडियो",
428
432
  width: "चौड़ाई",
429
433
  height: "ऊंचाई",
430
434
  autoplay: "स्वत: प्ले",