@agent-native/core 0.84.23 → 0.84.24

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 (51) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +45 -12
  5. package/corpus/core/src/client/active-run-state.ts +22 -0
  6. package/corpus/core/src/client/chat/message-components.tsx +2 -1
  7. package/corpus/core/src/client/chat/tool-call-display.tsx +18 -2
  8. package/corpus/core/src/client/sse-event-processor.ts +157 -40
  9. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +15 -13
  10. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  11. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  12. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  13. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  14. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  15. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  16. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  17. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  18. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  19. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  20. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  21. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  22. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +31 -3
  23. package/corpus/templates/clips/changelog/2026-07-01-clip-embeds-now-render-as-a-clean-video-only-player-without-.md +6 -0
  24. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  25. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +2 -2
  26. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +41 -31
  27. package/dist/client/AssistantChat.d.ts +2 -1
  28. package/dist/client/AssistantChat.d.ts.map +1 -1
  29. package/dist/client/AssistantChat.js +37 -15
  30. package/dist/client/AssistantChat.js.map +1 -1
  31. package/dist/client/active-run-state.d.ts +2 -0
  32. package/dist/client/active-run-state.d.ts.map +1 -1
  33. package/dist/client/active-run-state.js +17 -0
  34. package/dist/client/active-run-state.js.map +1 -1
  35. package/dist/client/chat/message-components.d.ts.map +1 -1
  36. package/dist/client/chat/message-components.js +3 -1
  37. package/dist/client/chat/message-components.js.map +1 -1
  38. package/dist/client/chat/tool-call-display.d.ts +3 -1
  39. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  40. package/dist/client/chat/tool-call-display.js +8 -8
  41. package/dist/client/chat/tool-call-display.js.map +1 -1
  42. package/dist/client/sse-event-processor.d.ts +1 -0
  43. package/dist/client/sse-event-processor.d.ts.map +1 -1
  44. package/dist/client/sse-event-processor.js +136 -35
  45. package/dist/client/sse-event-processor.js.map +1 -1
  46. package/dist/collab/awareness.d.ts +2 -2
  47. package/dist/collab/awareness.d.ts.map +1 -1
  48. package/dist/notifications/routes.d.ts +3 -3
  49. package/dist/observability/routes.d.ts +5 -5
  50. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  51. 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
@@ -184,6 +185,10 @@ function isPreparingActionActivity(ev: SSEEvent): boolean {
184
185
  return label.startsWith("preparing ") && label.includes(" action");
185
186
  }
186
187
 
188
+ function isProgressEvent(ev: SSEEvent): boolean {
189
+ return ev.type !== "stream_keepalive";
190
+ }
191
+
187
192
  function baseActivityLabel(ev: SSEEvent, tool?: string): string {
188
193
  return humanizeToolLabelText(ev.label ?? "Working", tool);
189
194
  }
@@ -460,6 +465,81 @@ function pendingToolNames(content: ContentPart[]): {
460
465
  return { activity: [...activity], running: [...running] };
461
466
  }
462
467
 
468
+ function contentSnapshot(content: ContentPart[]): ContentPart[] {
469
+ return content.map((part) => {
470
+ if (part.type === "text") return { ...part };
471
+ return {
472
+ ...part,
473
+ args: { ...part.args },
474
+ ...(part.mcpApp ? { mcpApp: { ...part.mcpApp } } : {}),
475
+ ...(part.chatUI ? { chatUI: { ...part.chatUI } } : {}),
476
+ ...(part.approval ? { approval: { ...part.approval } } : {}),
477
+ ...(part.structuredMeta
478
+ ? { structuredMeta: { ...part.structuredMeta } }
479
+ : {}),
480
+ };
481
+ });
482
+ }
483
+
484
+ function repeatSignatureValue(value: unknown): string {
485
+ try {
486
+ return JSON.stringify(value);
487
+ } catch {
488
+ return String(value ?? "");
489
+ }
490
+ }
491
+
492
+ function completedToolRepeatSignature(
493
+ part: Extract<ContentPart, { type: "tool-call" }>,
494
+ ): string | null {
495
+ if (
496
+ part.result === undefined ||
497
+ part.activity === true ||
498
+ part.approval ||
499
+ part.mcpApp ||
500
+ part.chatUI ||
501
+ part.structuredMeta
502
+ ) {
503
+ return null;
504
+ }
505
+ return [
506
+ part.toolName,
507
+ part.argsText,
508
+ repeatSignatureValue(part.args),
509
+ part.result,
510
+ part.isError === true ? "error" : "",
511
+ part.completedSideEffect === true ? "side-effect" : "",
512
+ ].join("\u0000");
513
+ }
514
+
515
+ function coalesceCompletedToolRepeat(
516
+ content: ContentPart[],
517
+ completedIndex: number,
518
+ ): void {
519
+ const current = content[completedIndex];
520
+ const previous = content[completedIndex - 1];
521
+ if (
522
+ !current ||
523
+ !previous ||
524
+ current.type !== "tool-call" ||
525
+ previous.type !== "tool-call"
526
+ ) {
527
+ return;
528
+ }
529
+
530
+ const currentSignature = completedToolRepeatSignature(current);
531
+ if (
532
+ !currentSignature ||
533
+ currentSignature !== completedToolRepeatSignature(previous)
534
+ ) {
535
+ return;
536
+ }
537
+
538
+ previous.repeatCount =
539
+ (previous.repeatCount ?? 1) + (current.repeatCount ?? 1);
540
+ content.splice(completedIndex, 1);
541
+ }
542
+
463
543
  function formatToolNames(tools: string[]): string {
464
544
  const names = tools.map(humanizeToolName);
465
545
  if (names.length === 0) return "the promised action";
@@ -578,7 +658,7 @@ export function processEvent(
578
658
  }
579
659
  return {
580
660
  action: "yield",
581
- result: { content: [...content] } as ChatModelRunResult,
661
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
582
662
  };
583
663
  }
584
664
 
@@ -615,7 +695,7 @@ export function processEvent(
615
695
  }
616
696
  return {
617
697
  action: "yield",
618
- result: { content: [...content] } as ChatModelRunResult,
698
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
619
699
  };
620
700
  }
621
701
 
@@ -672,7 +752,7 @@ export function processEvent(
672
752
  }
673
753
  return {
674
754
  action: "yield",
675
- result: { content: [...content] } as ChatModelRunResult,
755
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
676
756
  };
677
757
  }
678
758
 
@@ -694,7 +774,7 @@ export function processEvent(
694
774
  }
695
775
  return {
696
776
  action: "yield",
697
- result: { content: [...content] } as ChatModelRunResult,
777
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
698
778
  };
699
779
  }
700
780
 
@@ -730,11 +810,12 @@ export function processEvent(
730
810
  if (part.activity !== true && part.isError !== true) {
731
811
  markCompletedToolAfterAssistantText(state, part.toolName);
732
812
  }
813
+ coalesceCompletedToolRepeat(content, doneIdx);
733
814
  }
734
815
  }
735
816
  return {
736
817
  action: "yield",
737
- result: { content: [...content] } as ChatModelRunResult,
818
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
738
819
  };
739
820
  }
740
821
 
@@ -764,7 +845,7 @@ export function processEvent(
764
845
  }
765
846
  return {
766
847
  action: "yield",
767
- result: { content: [...content] } as ChatModelRunResult,
848
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
768
849
  };
769
850
  }
770
851
 
@@ -784,7 +865,7 @@ export function processEvent(
784
865
  }
785
866
  return {
786
867
  action: "yield",
787
- result: { content: [...content] } as ChatModelRunResult,
868
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
788
869
  };
789
870
  }
790
871
 
@@ -826,7 +907,7 @@ export function processEvent(
826
907
  return {
827
908
  action: "missing_api_key",
828
909
  result: {
829
- content: [...content],
910
+ content: contentSnapshot(content),
830
911
  status: { type: "incomplete" as const, reason: "error" as const },
831
912
  metadata: { custom: { runError } },
832
913
  } as ChatModelRunResult,
@@ -938,7 +1019,7 @@ export function processEvent(
938
1019
  return {
939
1020
  action: "error",
940
1021
  result: {
941
- content: [...content],
1022
+ content: contentSnapshot(content),
942
1023
  status: { type: "incomplete" as const, reason: "error" as const },
943
1024
  metadata: { custom: { runError } },
944
1025
  } as ChatModelRunResult,
@@ -974,7 +1055,7 @@ export function processEvent(
974
1055
  return {
975
1056
  action: "error",
976
1057
  result: {
977
- content: [...content],
1058
+ content: contentSnapshot(content),
978
1059
  status: { type: "incomplete" as const, reason: "error" as const },
979
1060
  metadata: { custom: { runError } },
980
1061
  } as ChatModelRunResult,
@@ -993,7 +1074,7 @@ export function processEvent(
993
1074
  return {
994
1075
  action: "done",
995
1076
  result: {
996
- content: [...content],
1077
+ content: contentSnapshot(content),
997
1078
  status: { type: "complete" as const, reason: "stop" as const },
998
1079
  metadata: {
999
1080
  custom: {
@@ -1009,7 +1090,7 @@ export function processEvent(
1009
1090
  }
1010
1091
  return {
1011
1092
  action: "done",
1012
- result: { content: [...content] } as ChatModelRunResult,
1093
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
1013
1094
  };
1014
1095
  }
1015
1096
 
@@ -1075,16 +1156,30 @@ export async function* readSSEStream(
1075
1156
 
1076
1157
  try {
1077
1158
  while (true) {
1078
- const { done, value } = await readChunkWithProgressTimeout(
1079
- reader,
1080
- lastMeaningfulEventAt,
1081
- );
1159
+ let readResult: ReadableStreamReadResult<Uint8Array>;
1160
+ try {
1161
+ readResult = await readChunkWithProgressTimeout(
1162
+ reader,
1163
+ lastMeaningfulEventAt,
1164
+ );
1165
+ } catch (err) {
1166
+ if (err instanceof AgentAutoContinueSignal) {
1167
+ throw new AgentAutoContinueSignal({
1168
+ reason: err.reason,
1169
+ maxIterations: err.maxIterations,
1170
+ activityTrail: [...activityTrail],
1171
+ errorInfo: err.errorInfo,
1172
+ });
1173
+ }
1174
+ throw err;
1175
+ }
1176
+ const { done, value } = readResult;
1082
1177
  if (done) break;
1083
1178
 
1084
1179
  buf += decoder.decode(value, { stream: true });
1085
1180
  const lines = buf.split("\n");
1086
1181
  buf = lines.pop() ?? "";
1087
- let sawDataEvent = false;
1182
+ let sawProgressEvent = false;
1088
1183
 
1089
1184
  for (const line of lines) {
1090
1185
  if (!line.startsWith("data: ")) continue;
@@ -1097,9 +1192,11 @@ export async function* readSSEStream(
1097
1192
  } catch {
1098
1193
  continue;
1099
1194
  }
1100
- sawDataEvent = true;
1101
1195
  const now = Date.now();
1102
- lastMeaningfulEventAt = now;
1196
+ if (isProgressEvent(ev)) {
1197
+ sawProgressEvent = true;
1198
+ lastMeaningfulEventAt = now;
1199
+ }
1103
1200
  updatePreparingActionState(preparingActionState, ev, now);
1104
1201
 
1105
1202
  // Track sequence number for reconnection
@@ -1162,10 +1259,13 @@ export async function* readSSEStream(
1162
1259
  }
1163
1260
 
1164
1261
  if (
1165
- !sawDataEvent &&
1262
+ !sawProgressEvent &&
1166
1263
  Date.now() - lastMeaningfulEventAt >= SSE_NO_PROGRESS_TIMEOUT_MS
1167
1264
  ) {
1168
- throw new AgentAutoContinueSignal({ reason: "no_progress" });
1265
+ throw new AgentAutoContinueSignal({
1266
+ reason: "no_progress",
1267
+ activityTrail: [...activityTrail],
1268
+ });
1169
1269
  }
1170
1270
  }
1171
1271
  } finally {
@@ -1218,18 +1318,31 @@ export async function readSSEStreamRaw(
1218
1318
 
1219
1319
  try {
1220
1320
  while (true) {
1221
- const { done, value } = await readChunkWithProgressTimeout(
1222
- reader,
1223
- lastMeaningfulEventAt,
1224
- );
1321
+ let readResult: ReadableStreamReadResult<Uint8Array>;
1322
+ try {
1323
+ readResult = await readChunkWithProgressTimeout(
1324
+ reader,
1325
+ lastMeaningfulEventAt,
1326
+ );
1327
+ } catch (err) {
1328
+ if (err instanceof AgentAutoContinueSignal) {
1329
+ throw new AgentAutoContinueSignal({
1330
+ reason: err.reason,
1331
+ maxIterations: err.maxIterations,
1332
+ activityTrail: [...activityTrail],
1333
+ errorInfo: err.errorInfo,
1334
+ });
1335
+ }
1336
+ throw err;
1337
+ }
1338
+ const { done, value } = readResult;
1225
1339
  if (done) break;
1226
1340
 
1227
1341
  buf += decoder.decode(value, { stream: true });
1228
1342
  const lines = buf.split("\n");
1229
1343
  buf = lines.pop() ?? "";
1230
1344
 
1231
- let updated = false;
1232
- let sawDataEvent = false;
1345
+ let sawProgressEvent = false;
1233
1346
  for (const line of lines) {
1234
1347
  if (!line.startsWith("data: ")) continue;
1235
1348
  const raw = line.slice(6).trim();
@@ -1241,9 +1354,11 @@ export async function readSSEStreamRaw(
1241
1354
  } catch {
1242
1355
  continue;
1243
1356
  }
1244
- sawDataEvent = true;
1245
1357
  const now = Date.now();
1246
- lastMeaningfulEventAt = now;
1358
+ if (isProgressEvent(ev)) {
1359
+ sawProgressEvent = true;
1360
+ lastMeaningfulEventAt = now;
1361
+ }
1247
1362
  updatePreparingActionState(preparingActionState, ev, now);
1248
1363
 
1249
1364
  if (ev.seq !== undefined && onSeq) {
@@ -1287,10 +1402,12 @@ export async function readSSEStreamRaw(
1287
1402
  action === "error" ||
1288
1403
  action === "missing_api_key"
1289
1404
  ) {
1290
- updated = true;
1405
+ onUpdate(contentSnapshot(content));
1406
+ emittedLatestContent = true;
1291
1407
  }
1292
1408
  if (action === "auto_continue") {
1293
- onUpdate([...content]);
1409
+ onUpdate(contentSnapshot(content));
1410
+ emittedLatestContent = true;
1294
1411
  throw new AgentAutoContinueSignal(
1295
1412
  autoContinue
1296
1413
  ? { ...autoContinue, activityTrail: [...activityTrail] }
@@ -1298,7 +1415,7 @@ export async function readSSEStreamRaw(
1298
1415
  );
1299
1416
  }
1300
1417
  if (hasStalledPreparingAction(preparingActionState, Date.now())) {
1301
- onUpdate([...content]);
1418
+ onUpdate(contentSnapshot(content));
1302
1419
  throw new AgentAutoContinueSignal({
1303
1420
  reason: "no_progress",
1304
1421
  activityTrail: [...activityTrail],
@@ -1309,20 +1426,18 @@ export async function readSSEStreamRaw(
1309
1426
  action === "error" ||
1310
1427
  action === "missing_api_key"
1311
1428
  ) {
1312
- onUpdate([...content]);
1313
1429
  return;
1314
1430
  }
1315
1431
  }
1316
1432
 
1317
- if (updated) {
1318
- onUpdate([...content]);
1319
- emittedLatestContent = true;
1320
- }
1321
1433
  if (
1322
- !sawDataEvent &&
1434
+ !sawProgressEvent &&
1323
1435
  Date.now() - lastMeaningfulEventAt >= SSE_NO_PROGRESS_TIMEOUT_MS
1324
1436
  ) {
1325
- throw new AgentAutoContinueSignal({ reason: "no_progress" });
1437
+ throw new AgentAutoContinueSignal({
1438
+ reason: "no_progress",
1439
+ activityTrail: [...activityTrail],
1440
+ });
1326
1441
  }
1327
1442
  }
1328
1443
  } finally {
@@ -1332,6 +1447,8 @@ export async function readSSEStreamRaw(
1332
1447
  // See readSSEStream: cancellation may race lock release in browsers.
1333
1448
  }
1334
1449
  }
1335
- if (content.length > 0 && !emittedLatestContent) onUpdate([...content]);
1450
+ if (content.length > 0 && !emittedLatestContent) {
1451
+ onUpdate(contentSnapshot(content));
1452
+ }
1336
1453
  throw new AgentAutoContinueSignal({ reason: "stream_ended" });
1337
1454
  }
@@ -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" />
@@ -452,8 +452,8 @@ function ClipsEmbedConfigurator({
452
452
 
453
453
  const code =
454
454
  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>`;
455
+ ? `<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>`
456
+ : `<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
457
 
458
458
  return (
459
459
  <div className="space-y-3">
@@ -428,6 +428,7 @@ const messages = {
428
428
  askOwnerPublic: "اطلب من المالك نشره.",
429
429
  responsive: "مستجيب (16:9)",
430
430
  fixedSize: "حجم ثابت",
431
+ embedIframeTitle: "فيديو Clips",
431
432
  width: "عرض",
432
433
  height: "ارتفاع",
433
434
  autoplay: "التشغيل التلقائي",
@@ -445,6 +445,7 @@ const messages = {
445
445
  askOwnerPublic: "Bitten Sie den Eigentümer, es öffentlich zu machen.",
446
446
  responsive: "Responsiv (16:9)",
447
447
  fixedSize: "Feste Größe",
448
+ embedIframeTitle: "Clips-Video",
448
449
  width: "Breite",
449
450
  height: "Höhe",
450
451
  autoplay: "Übersetzt: Autoplay",
@@ -428,6 +428,7 @@ const messages = {
428
428
  askOwnerPublic: "Ask the owner to make it public.",
429
429
  responsive: "Responsive (16:9)",
430
430
  fixedSize: "Fixed size",
431
+ embedIframeTitle: "Clips video",
431
432
  width: "Width",
432
433
  height: "Height",
433
434
  autoplay: "Autoplay",
@@ -440,6 +440,7 @@ const messages = {
440
440
  askOwnerPublic: "Pídele al propietario que lo haga público.",
441
441
  responsive: "Responsivo (16:9)",
442
442
  fixedSize: "Tamaño fijo",
443
+ embedIframeTitle: "Video de Clips",
443
444
  width: "Ancho",
444
445
  height: "Altura",
445
446
  autoplay: "Reproducción automática",
@@ -440,6 +440,7 @@ const messages = {
440
440
  askOwnerPublic: "Demandez au propriétaire de le rendre public.",
441
441
  responsive: "Réactif (16:9)",
442
442
  fixedSize: "Taille fixe",
443
+ embedIframeTitle: "Video Clips",
443
444
  width: "Largeur",
444
445
  height: "Hauteur",
445
446
  autoplay: "Lecture automatique",
@@ -425,6 +425,7 @@ const messages = {
425
425
  askOwnerPublic: "मालिक से इसे सार्वजनिक करने के लिए कहें.",
426
426
  responsive: "प्रतिक्रियाशील (16:9)",
427
427
  fixedSize: "निश्चित आकार",
428
+ embedIframeTitle: "Clips वीडियो",
428
429
  width: "चौड़ाई",
429
430
  height: "ऊंचाई",
430
431
  autoplay: "स्वत: प्ले",
@@ -437,6 +437,7 @@ const messages = {
437
437
  askOwnerPublic: "所有者に公開するよう依頼してください。",
438
438
  responsive: "レスポンシブ (16:9)",
439
439
  fixedSize: "固定サイズ",
440
+ embedIframeTitle: "Clips動画",
440
441
  width: "幅",
441
442
  height: "身長",
442
443
  autoplay: "自動再生",
@@ -430,6 +430,7 @@ const messages = {
430
430
  askOwnerPublic: "소유자에게 공개하도록 요청하세요.",
431
431
  responsive: "반응형(16:9)",
432
432
  fixedSize: "고정 크기",
433
+ embedIframeTitle: "Clips 동영상",
433
434
  width: "너비",
434
435
  height: "키",
435
436
  autoplay: "자동재생",
@@ -438,6 +438,7 @@ const messages = {
438
438
  askOwnerPublic: "Peça ao proprietário para torná-lo público.",
439
439
  responsive: "Responsivo (16:9)",
440
440
  fixedSize: "Tamanho fixo",
441
+ embedIframeTitle: "Video do Clips",
441
442
  width: "Largura",
442
443
  height: "Altura",
443
444
  autoplay: "Reprodução automática",
@@ -412,6 +412,7 @@ const messages = {
412
412
  askOwnerPublic: "请楼主公开一下。",
413
413
  responsive: "反应灵敏 (16:9)",
414
414
  fixedSize: "固定尺寸",
415
+ embedIframeTitle: "Clips 视频",
415
416
  width: "宽度",
416
417
  height: "高度",
417
418
  autoplay: "自动播放",
@@ -412,6 +412,7 @@ const messages = {
412
412
  askOwnerPublic: "請樓主公開一下。",
413
413
  responsive: "反應靈敏 (16:9)",
414
414
  fixedSize: "固定尺寸",
415
+ embedIframeTitle: "Clips 影片",
415
416
  width: "寬度",
416
417
  height: "高度",
417
418
  autoplay: "自動播放",
@@ -78,6 +78,34 @@ export default function EmbedRoute() {
78
78
  });
79
79
  const [pwError, setPwError] = useState<string | null>(null);
80
80
 
81
+ useEffect(() => {
82
+ if (typeof document === "undefined") return;
83
+
84
+ const html = document.documentElement;
85
+ const body = document.body;
86
+ const previous = {
87
+ htmlOverflow: html.style.overflow,
88
+ htmlHeight: html.style.height,
89
+ bodyOverflow: body.style.overflow,
90
+ bodyHeight: body.style.height,
91
+ bodyBackground: body.style.background,
92
+ };
93
+
94
+ html.style.overflow = "hidden";
95
+ html.style.height = "100%";
96
+ body.style.overflow = "hidden";
97
+ body.style.height = "100%";
98
+ body.style.background = "#000";
99
+
100
+ return () => {
101
+ html.style.overflow = previous.htmlOverflow;
102
+ html.style.height = previous.htmlHeight;
103
+ body.style.overflow = previous.bodyOverflow;
104
+ body.style.height = previous.bodyHeight;
105
+ body.style.background = previous.bodyBackground;
106
+ };
107
+ }, []);
108
+
81
109
  const dataQ = useQuery({
82
110
  queryKey: ["public-recording", shareId, password],
83
111
  queryFn: async () => {
@@ -135,7 +163,7 @@ export default function EmbedRoute() {
135
163
 
136
164
  if (dataQ.isLoading) {
137
165
  return (
138
- <div className="flex items-center justify-center h-screen w-full bg-black">
166
+ <div className="fixed inset-0 flex h-dvh w-dvw items-center justify-center overflow-hidden bg-black">
139
167
  <Spinner className="h-8 w-8 text-white/70" />
140
168
  </div>
141
169
  );
@@ -153,14 +181,14 @@ export default function EmbedRoute() {
153
181
 
154
182
  if (!recording) {
155
183
  return (
156
- <div className="flex items-center justify-center h-screen w-full bg-black text-white">
184
+ <div className="fixed inset-0 flex h-dvh w-dvw items-center justify-center overflow-hidden bg-black text-white">
157
185
  <p className="text-sm">{t("embedRoute.unavailable")}</p>
158
186
  </div>
159
187
  );
160
188
  }
161
189
 
162
190
  return (
163
- <div className="h-screen w-screen bg-black overflow-hidden">
191
+ <div className="fixed inset-0 h-dvh w-dvw overflow-hidden bg-black">
164
192
  <VideoPlayer
165
193
  ref={playerRef}
166
194
  recordingId={recording.id}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Clip embeds now render as a clean video-only player without extra page chrome or scrollbars.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "Agent-Native Clips",
4
4
  "short_name": "Clips",
5
- "version": "0.1.11",
5
+ "version": "0.1.12",
6
6
  "description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub.",
7
7
  "minimum_chrome_version": "116",
8
8
  "action": {
@@ -168,10 +168,10 @@
168
168
  Object.assign(frame.style, {
169
169
  display: "block",
170
170
  width: "100%",
171
- height: "430px",
171
+ height: "405px",
172
172
  border: "0",
173
173
  borderRadius: "8px",
174
- background: "transparent",
174
+ background: "#000",
175
175
  overflow: "hidden",
176
176
  });
177
177