@agent-native/core 0.194.0 → 0.194.1-nightly-20260927113046

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 (41) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/brain/actions/check-builder-embeddings.ts +71 -0
  3. package/corpus/templates/brain/app/i18n-data.ts +90 -0
  4. package/corpus/templates/brain/app/routes/sources.tsx +95 -0
  5. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +3 -1
  6. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +6 -1
  7. package/corpus/templates/design/changelog/2026-09-27-drops-into-plain-frames-now-stack-above-existing-content-whi.md +6 -0
  8. package/dist/client/observability/ObservabilityDashboard.js +113 -12
  9. package/dist/client/observability/ObservabilityReviewSummaryButton.d.ts +1 -1
  10. package/dist/client/observability/ObservabilityReviewSummaryButton.js +13 -8
  11. package/dist/collab/awareness.d.ts +2 -2
  12. package/dist/collab/routes.d.ts +1 -1
  13. package/dist/collab/struct-routes.d.ts +1 -1
  14. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  15. package/dist/localization/core-messages/ar-SA.js +8 -0
  16. package/dist/localization/core-messages/de-DE.js +8 -0
  17. package/dist/localization/core-messages/en-US.d.ts +8 -0
  18. package/dist/localization/core-messages/en-US.js +8 -0
  19. package/dist/localization/core-messages/es-ES.js +8 -0
  20. package/dist/localization/core-messages/fr-FR.js +8 -0
  21. package/dist/localization/core-messages/hi-IN.js +8 -0
  22. package/dist/localization/core-messages/ja-JP.js +8 -0
  23. package/dist/localization/core-messages/ko-KR.js +8 -0
  24. package/dist/localization/core-messages/pt-BR.js +8 -0
  25. package/dist/localization/core-messages/zh-CN.js +8 -0
  26. package/dist/localization/core-messages/zh-TW.js +8 -0
  27. package/dist/localization/core-messages.d.ts +8 -0
  28. package/dist/localization/core-messages.js +14 -0
  29. package/dist/localization/default-messages.d.ts +4 -1
  30. package/dist/localization/default-messages.js +6 -3
  31. package/dist/notifications/routes.d.ts +3 -3
  32. package/dist/observability/reviews.js +26 -14
  33. package/dist/observability/routes.d.ts +8 -8
  34. package/dist/observability/store.d.ts +4 -1
  35. package/dist/observability/store.js +27 -6
  36. package/dist/observability/types.d.ts +2 -0
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  39. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  40. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  41. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 10319
34
+ - template files: 10321
@@ -0,0 +1,71 @@
1
+ import { defineAction } from "@agent-native/core/action";
2
+ import { readEmbeddingFamilyAvailability } from "@agent-native/core/embeddings";
3
+ import { runWithRequestContext } from "@agent-native/core/server/request-context";
4
+ import { assertAccess } from "@agent-native/core/sharing";
5
+ import { eq } from "drizzle-orm";
6
+ import { z } from "zod";
7
+
8
+ import { getDb, schema } from "../server/db/index.js";
9
+
10
+ export const checkBuilderEmbeddingsSchema = z.object({
11
+ sourceId: z.string().min(1),
12
+ confirmProviderCost: z.literal(true),
13
+ });
14
+
15
+ export default defineAction({
16
+ description:
17
+ "Check Builder embeddings for one Brain source with a single synthetic query; does not index or store data.",
18
+ schema: checkBuilderEmbeddingsSchema,
19
+ toolCallable: false,
20
+ needsApproval: true,
21
+ run: async ({ sourceId }) => {
22
+ await assertAccess("brain-source", sourceId, "admin");
23
+ const [source] = await getDb()
24
+ .select({
25
+ ownerEmail: schema.brainSources.ownerEmail,
26
+ orgId: schema.brainSources.orgId,
27
+ })
28
+ .from(schema.brainSources)
29
+ .where(eq(schema.brainSources.id, sourceId))
30
+ .limit(1);
31
+ if (!source) throw new Error("Brain source was not found.");
32
+
33
+ return runWithRequestContext(
34
+ {
35
+ userEmail: source.ownerEmail,
36
+ orgId: source.orgId ?? undefined,
37
+ },
38
+ async () => {
39
+ const availability = await readEmbeddingFamilyAvailability();
40
+ if (availability.unavailableProviders.includes("builder")) {
41
+ throw new Error(
42
+ "Builder embedding credential lookup is unavailable.",
43
+ );
44
+ }
45
+ const family = availability.families.find(
46
+ (candidate) => candidate.provider === "builder",
47
+ );
48
+ if (!family) throw new Error("Builder embeddings are not configured.");
49
+ const vectors = await family.embed(
50
+ [{ text: "Builder embedding connectivity check." }],
51
+ "query",
52
+ );
53
+ const [vector] = vectors;
54
+ if (
55
+ vectors.length !== 1 ||
56
+ !vector ||
57
+ vector.length !== family.dimensions ||
58
+ !vector.every(Number.isFinite)
59
+ ) {
60
+ throw new Error("Builder returned an invalid embedding.");
61
+ }
62
+ return {
63
+ provider: family.provider,
64
+ model: family.model,
65
+ dimensions: family.dimensions,
66
+ success: true,
67
+ };
68
+ },
69
+ );
70
+ },
71
+ });
@@ -493,6 +493,15 @@ const enUS = {
493
493
  reviewRequired: "Review required",
494
494
  reviewRequiredDescription: "Queue extracted knowledge before approval",
495
495
  cancel: "Cancel",
496
+ close: "Close",
497
+ checkBuilder: "Check Builder embeddings",
498
+ checkBuilderDescription:
499
+ "Verify Builder with one synthetic request. No source content is sent or indexed.",
500
+ checkBuilderConfirm:
501
+ "Run one synthetic Builder embedding request for {{source}}? This may incur provider usage. No source content is sent, and the vector is not stored.",
502
+ runBuilderCheck: "Run one check",
503
+ checkBuilderSuccess:
504
+ "Builder check passed: {{provider}} / {{model}}, {{dimensions}} dimensions. No vector was stored.",
496
505
  saveSource: "Save source",
497
506
  createSource: "Create source",
498
507
  retryAfter: "Retry after {{date}}",
@@ -1006,6 +1015,15 @@ const baseMessagesByLocale = {
1006
1015
  provider: "提供方",
1007
1016
  notRequired: "不需要",
1008
1017
  cancel: "取消",
1018
+ close: "关闭",
1019
+ checkBuilder: "检查 Builder 嵌入服务",
1020
+ checkBuilderDescription:
1021
+ "发送一次模拟请求以验证 Builder。不会发送或索引来源内容。",
1022
+ checkBuilderConfirm:
1023
+ "要为 {{source}} 发送一次模拟的 Builder 嵌入请求吗?这可能产生服务费用。不会发送来源内容,也不会保存向量。",
1024
+ runBuilderCheck: "运行一次检查",
1025
+ checkBuilderSuccess:
1026
+ "Builder 检查通过:{{provider}} / {{model}},{{dimensions}} 维。未保存向量。",
1009
1027
  saveSource: "保存来源",
1010
1028
  createSource: "创建来源",
1011
1029
  retryAfter: "{{date}} 后重试",
@@ -1212,6 +1230,15 @@ const baseMessagesByLocale = {
1212
1230
  provider: "Proveedor",
1213
1231
  notRequired: "No requerido",
1214
1232
  cancel: "Cancelar",
1233
+ close: "Cerrar",
1234
+ checkBuilder: "Comprobar embeddings de Builder",
1235
+ checkBuilderDescription:
1236
+ "Verifica Builder con una solicitud de prueba. No se envía ni indexa contenido de la fuente.",
1237
+ checkBuilderConfirm:
1238
+ "¿Enviar una solicitud de prueba de embeddings de Builder para {{source}}? Puede generar costes del proveedor. No se envía contenido de la fuente ni se guarda el vector.",
1239
+ runBuilderCheck: "Realizar una comprobación",
1240
+ checkBuilderSuccess:
1241
+ "Comprobación de Builder correcta: {{provider}} / {{model}}, {{dimensions}} dimensiones. No se guardó ningún vector.",
1215
1242
  saveSource: "Guardar fuente",
1216
1243
  createSource: "Crear fuente",
1217
1244
  retryAfter: "Reintentar después de {{date}}",
@@ -1428,6 +1455,15 @@ const baseMessagesByLocale = {
1428
1455
  provider: "Fournisseur",
1429
1456
  notRequired: "Non requis",
1430
1457
  cancel: "Annuler",
1458
+ close: "Fermer",
1459
+ checkBuilder: "Vérifier les embeddings Builder",
1460
+ checkBuilderDescription:
1461
+ "Vérifiez Builder avec une seule requête de test. Aucun contenu de la source n'est envoyé ni indexé.",
1462
+ checkBuilderConfirm:
1463
+ "Envoyer une requête de test d'embedding Builder pour {{source}} ? Cela peut entraîner des frais du fournisseur. Aucun contenu de la source n'est envoyé et le vecteur n'est pas stocké.",
1464
+ runBuilderCheck: "Lancer une vérification",
1465
+ checkBuilderSuccess:
1466
+ "Vérification Builder réussie : {{provider}} / {{model}}, {{dimensions}} dimensions. Aucun vecteur n'a été stocké.",
1431
1467
  saveSource: "Enregistrer la source",
1432
1468
  createSource: "Créer une source",
1433
1469
  retryAfter: "Réessayer après {{date}}",
@@ -1643,6 +1679,15 @@ const baseMessagesByLocale = {
1643
1679
  provider: "Anbieter",
1644
1680
  notRequired: "Nicht erforderlich",
1645
1681
  cancel: "Abbrechen",
1682
+ close: "Schließen",
1683
+ checkBuilder: "Builder-Embeddings prüfen",
1684
+ checkBuilderDescription:
1685
+ "Builder mit einer Testanfrage prüfen. Quellinhalte werden weder gesendet noch indexiert.",
1686
+ checkBuilderConfirm:
1687
+ "Eine Testanfrage für Builder-Embeddings zu {{source}} senden? Dabei können Anbietergebühren anfallen. Quellinhalte werden nicht gesendet und der Vektor wird nicht gespeichert.",
1688
+ runBuilderCheck: "Einmal prüfen",
1689
+ checkBuilderSuccess:
1690
+ "Builder-Prüfung erfolgreich: {{provider}} / {{model}}, {{dimensions}} Dimensionen. Kein Vektor wurde gespeichert.",
1646
1691
  saveSource: "Quelle speichern",
1647
1692
  createSource: "Quelle erstellen",
1648
1693
  retryAfter: "Erneut versuchen nach {{date}}",
@@ -1853,6 +1898,15 @@ const baseMessagesByLocale = {
1853
1898
  provider: "プロバイダー",
1854
1899
  notRequired: "不要",
1855
1900
  cancel: "キャンセル",
1901
+ close: "閉じる",
1902
+ checkBuilder: "Builder の埋め込みを確認",
1903
+ checkBuilderDescription:
1904
+ "テスト用のリクエストを 1 回送信して Builder を確認します。ソースの内容は送信もインデックス登録もされません。",
1905
+ checkBuilderConfirm:
1906
+ "{{source}} に対して Builder の埋め込みテストリクエストを 1 回送信しますか?プロバイダーの利用料金が発生する場合があります。ソースの内容は送信されず、ベクトルも保存されません。",
1907
+ runBuilderCheck: "1 回確認する",
1908
+ checkBuilderSuccess:
1909
+ "Builder の確認に成功しました: {{provider}} / {{model}}、{{dimensions}} 次元。ベクトルは保存されていません。",
1856
1910
  saveSource: "ソースを保存",
1857
1911
  createSource: "ソースを作成",
1858
1912
  retryAfter: "{{date}} 後に再試行",
@@ -2063,6 +2117,15 @@ const baseMessagesByLocale = {
2063
2117
  provider: "제공자",
2064
2118
  notRequired: "필요 없음",
2065
2119
  cancel: "취소",
2120
+ close: "닫기",
2121
+ checkBuilder: "Builder 임베딩 확인",
2122
+ checkBuilderDescription:
2123
+ "테스트 요청 한 번으로 Builder를 확인합니다. 소스 내용은 전송하거나 인덱싱하지 않습니다.",
2124
+ checkBuilderConfirm:
2125
+ "{{source}}에 대해 Builder 임베딩 테스트 요청을 한 번 보내시겠습니까? 제공업체 사용 요금이 발생할 수 있습니다. 소스 내용은 전송하지 않으며 벡터도 저장하지 않습니다.",
2126
+ runBuilderCheck: "한 번 확인하기",
2127
+ checkBuilderSuccess:
2128
+ "Builder 확인 성공: {{provider}} / {{model}}, {{dimensions}}차원. 벡터는 저장되지 않았습니다.",
2066
2129
  saveSource: "소스 저장",
2067
2130
  createSource: "소스 만들기",
2068
2131
  retryAfter: "{{date}} 후 다시 시도",
@@ -2273,6 +2336,15 @@ const baseMessagesByLocale = {
2273
2336
  provider: "Provedor",
2274
2337
  notRequired: "Não obrigatório",
2275
2338
  cancel: "Cancelar",
2339
+ close: "Fechar",
2340
+ checkBuilder: "Verificar embeddings do Builder",
2341
+ checkBuilderDescription:
2342
+ "Verifique o Builder com uma solicitação de teste. O conteúdo da fonte não é enviado nem indexado.",
2343
+ checkBuilderConfirm:
2344
+ "Enviar uma solicitação de teste de embeddings do Builder para {{source}}? Isso pode gerar custos do provedor. O conteúdo da fonte não é enviado e o vetor não é armazenado.",
2345
+ runBuilderCheck: "Executar uma verificação",
2346
+ checkBuilderSuccess:
2347
+ "Verificação do Builder concluída: {{provider}} / {{model}}, {{dimensions}} dimensões. Nenhum vetor foi armazenado.",
2276
2348
  saveSource: "Salvar fonte",
2277
2349
  createSource: "Criar fonte",
2278
2350
  retryAfter: "Tentar novamente após {{date}}",
@@ -2487,6 +2559,15 @@ const baseMessagesByLocale = {
2487
2559
  provider: "प्रदाता",
2488
2560
  notRequired: "आवश्यक नहीं",
2489
2561
  cancel: "रद्द करें",
2562
+ close: "बंद करें",
2563
+ checkBuilder: "Builder एम्बेडिंग जाँचें",
2564
+ checkBuilderDescription:
2565
+ "एक परीक्षण अनुरोध से Builder की जाँच करें। स्रोत की सामग्री भेजी या इंडेक्स नहीं की जाती।",
2566
+ checkBuilderConfirm:
2567
+ "{{source}} के लिए Builder एम्बेडिंग का एक परीक्षण अनुरोध भेजें? प्रदाता का शुल्क लग सकता है। स्रोत की सामग्री नहीं भेजी जाएगी और वेक्टर सहेजा नहीं जाएगा।",
2568
+ runBuilderCheck: "एक बार जाँचें",
2569
+ checkBuilderSuccess:
2570
+ "Builder जाँच सफल: {{provider}} / {{model}}, {{dimensions}} आयाम। कोई वेक्टर सहेजा नहीं गया।",
2490
2571
  saveSource: "स्रोत सहेजें",
2491
2572
  createSource: "स्रोत बनाएं",
2492
2573
  retryAfter: "{{date}} के बाद फिर कोशिश करें",
@@ -2695,6 +2776,15 @@ const baseMessagesByLocale = {
2695
2776
  provider: "المزوّد",
2696
2777
  notRequired: "غير مطلوب",
2697
2778
  cancel: "إلغاء",
2779
+ close: "إغلاق",
2780
+ checkBuilder: "التحقق من تضمينات Builder",
2781
+ checkBuilderDescription:
2782
+ "تحقّق من Builder بطلب اختباري واحد. لن يُرسل محتوى المصدر أو يُفهرس.",
2783
+ checkBuilderConfirm:
2784
+ "هل تريد إرسال طلب اختباري واحد لتضمينات Builder من أجل {{source}}؟ قد تترتب رسوم من المزوّد. لن يُرسل محتوى المصدر ولن يُحفظ المتجه.",
2785
+ runBuilderCheck: "إجراء تحقق واحد",
2786
+ checkBuilderSuccess:
2787
+ "نجح التحقق من Builder: {{provider}} / {{model}}، {{dimensions}} بُعدًا. لم يُحفظ أي متجه.",
2698
2788
  saveSource: "حفظ المصدر",
2699
2789
  createSource: "إنشاء مصدر",
2700
2790
  retryAfter: "إعادة المحاولة بعد {{date}}",
@@ -10,6 +10,7 @@ import {
10
10
  IconArchive,
11
11
  IconBrandGithub,
12
12
  IconBrandSlack,
13
+ IconBolt,
13
14
  IconChecks,
14
15
  IconChevronDown,
15
16
  IconCircleCheck,
@@ -1803,6 +1804,21 @@ function SourceListItem({
1803
1804
  }) {
1804
1805
  const t = useT();
1805
1806
  const [expanded, setExpanded] = useState(false);
1807
+ const [builderCheckOpen, setBuilderCheckOpen] = useState(false);
1808
+ const sourceAccess = useActionQuery<{ accessRole?: string }>(
1809
+ "get-source" as any,
1810
+ { id: source.id } as any,
1811
+ { enabled: expanded, retry: false },
1812
+ );
1813
+ const checkBuilder = useActionMutation<
1814
+ { provider: string; model: string; dimensions: number; success: boolean },
1815
+ { sourceId: string; confirmProviderCost: true }
1816
+ >("check-builder-embeddings" as any, {
1817
+ skipActionQueryInvalidation: true,
1818
+ });
1819
+ const canCheckBuilder =
1820
+ sourceAccess.data?.accessRole === "admin" ||
1821
+ sourceAccess.data?.accessRole === "owner";
1806
1822
  const Icon = sourceProviderIcon(source.provider);
1807
1823
  const retry = sourceRetryAfter(source);
1808
1824
  const hasSyncNotice = Boolean(
@@ -1979,6 +1995,25 @@ function SourceListItem({
1979
1995
  </Button>
1980
1996
  </div>
1981
1997
  ) : null}
1998
+ {canCheckBuilder ? (
1999
+ <div className="mt-3 flex flex-col gap-3 rounded-md border border-border bg-muted/25 p-3 sm:flex-row sm:items-center sm:justify-between">
2000
+ <p className="text-sm leading-6 text-muted-foreground">
2001
+ {t("sources.checkBuilderDescription")}
2002
+ </p>
2003
+ <Button
2004
+ size="sm"
2005
+ variant="outline"
2006
+ className="shrink-0"
2007
+ onClick={() => {
2008
+ checkBuilder.reset();
2009
+ setBuilderCheckOpen(true);
2010
+ }}
2011
+ >
2012
+ <IconBolt className="size-4" />
2013
+ {t("sources.checkBuilder")}
2014
+ </Button>
2015
+ </div>
2016
+ ) : null}
1982
2017
  {hasSyncNotice ? (
1983
2018
  <div className="mt-3 flex gap-2 rounded-md border border-destructive/20 bg-destructive/5 px-3 py-2 text-sm">
1984
2019
  <IconAlertTriangle className="mt-0.5 size-4 shrink-0 text-destructive" />
@@ -2017,6 +2052,66 @@ function SourceListItem({
2017
2052
  ) : null}
2018
2053
  </div>
2019
2054
  ) : null}
2055
+
2056
+ <Dialog
2057
+ open={builderCheckOpen}
2058
+ onOpenChange={(open) => {
2059
+ if (!open && checkBuilder.isPending) return;
2060
+ setBuilderCheckOpen(open);
2061
+ }}
2062
+ >
2063
+ <DialogContent className="max-w-md gap-5">
2064
+ <DialogHeader>
2065
+ <DialogTitle>{t("sources.checkBuilder")}</DialogTitle>
2066
+ <DialogDescription>
2067
+ {t("sources.checkBuilderConfirm", { source: sourceName(source) })}
2068
+ </DialogDescription>
2069
+ </DialogHeader>
2070
+ {checkBuilder.data ? (
2071
+ <p role="status" className="text-sm text-foreground">
2072
+ {t("sources.checkBuilderSuccess", {
2073
+ provider: checkBuilder.data.provider,
2074
+ model: checkBuilder.data.model,
2075
+ dimensions: checkBuilder.data.dimensions.toLocaleString(),
2076
+ })}
2077
+ </p>
2078
+ ) : null}
2079
+ {checkBuilder.isError ? (
2080
+ <p role="alert" className="text-sm text-destructive">
2081
+ {checkBuilder.error.message}
2082
+ </p>
2083
+ ) : null}
2084
+ <DialogFooter>
2085
+ <Button
2086
+ type="button"
2087
+ variant="outline"
2088
+ disabled={checkBuilder.isPending}
2089
+ onClick={() => setBuilderCheckOpen(false)}
2090
+ >
2091
+ {checkBuilder.data ? t("sources.close") : t("sources.cancel")}
2092
+ </Button>
2093
+ {!checkBuilder.data ? (
2094
+ <Button
2095
+ type="button"
2096
+ disabled={checkBuilder.isPending}
2097
+ onClick={() =>
2098
+ checkBuilder.mutate({
2099
+ sourceId: source.id,
2100
+ confirmProviderCost: true,
2101
+ })
2102
+ }
2103
+ >
2104
+ {checkBuilder.isPending ? (
2105
+ <IconLoader2 className="size-4 animate-spin" />
2106
+ ) : (
2107
+ <IconBolt className="size-4" />
2108
+ )}
2109
+ {t("sources.runBuilderCheck")}
2110
+ </Button>
2111
+ ) : null}
2112
+ </DialogFooter>
2113
+ </DialogContent>
2114
+ </Dialog>
2020
2115
  </div>
2021
2116
  );
2022
2117
  }
@@ -12451,7 +12451,9 @@ export const editorChromeBridgeScript: string = `"use strict";
12451
12451
  clientY,
12452
12452
  dragged
12453
12453
  );
12454
- if (betweenChildren) return betweenChildren;
12454
+ if (betweenChildren && (hit === el.parentElement || isAutoLayoutElement(hit))) {
12455
+ return betweenChildren;
12456
+ }
12455
12457
  return {
12456
12458
  anchor: hit,
12457
12459
  placement: "inside",
@@ -15648,7 +15648,12 @@ declare var __INITIAL_SOURCE_HEAD__: string;
15648
15648
  clientY,
15649
15649
  dragged,
15650
15650
  );
15651
- if (betweenChildren) return betweenChildren;
15651
+ if (
15652
+ betweenChildren &&
15653
+ (hit === el.parentElement || isAutoLayoutElement(hit))
15654
+ ) {
15655
+ return betweenChildren;
15656
+ }
15652
15657
  return {
15653
15658
  anchor: hit,
15654
15659
  placement: "inside",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-27
4
+ ---
5
+
6
+ Drops into plain frames now stack above existing content, while auto-layout keeps the chosen insertion position.
@@ -333,6 +333,9 @@ function ReviewTab({ days, renderArtifactPreview, }) {
333
333
  const [summaryStatus, setSummaryStatus] = useState(null);
334
334
  const [summaryRequests, setSummaryRequests] = useState({});
335
335
  const summaryRetryTimers = useRef(new Map());
336
+ const summaryBaselineRef = useRef(new Map());
337
+ const summaryBatchRunIds = useRef([]);
338
+ const summaryBatchRetryTimer = useRef(null);
336
339
  const summaryRequestMounted = useRef(false);
337
340
  const [openPopover, setOpenPopover] = useState(null);
338
341
  const pendingInstructionRunId = useRef(null);
@@ -346,6 +349,9 @@ function ReviewTab({ days, renderArtifactPreview, }) {
346
349
  window.clearTimeout(timer);
347
350
  }
348
351
  summaryRetryTimers.current.clear();
352
+ if (summaryBatchRetryTimer.current !== null) {
353
+ window.clearTimeout(summaryBatchRetryTimer.current);
354
+ }
349
355
  };
350
356
  }, []);
351
357
  const updateSummaryRequests = (runIds, status) => {
@@ -353,20 +359,30 @@ function ReviewTab({ days, renderArtifactPreview, }) {
353
359
  return;
354
360
  const uniqueRunIds = [...new Set(runIds)];
355
361
  for (const runId of uniqueRunIds) {
362
+ if (status === "sending") {
363
+ const review = reviews?.find((candidate) => candidate.runId === runId ||
364
+ candidate.runs?.some((run) => run.runId === runId));
365
+ const runSummaryUpdatedAt = review?.runs?.find((run) => run.runId === runId)?.summaryUpdatedAt;
366
+ summaryBaselineRef.current.set(runId, runSummaryUpdatedAt ??
367
+ (review?.runId === runId
368
+ ? (review.summaryUpdatedAt ?? null)
369
+ : null));
370
+ }
371
+ else if (status === null || status === "failed") {
372
+ summaryBaselineRef.current.delete(runId);
373
+ }
356
374
  const timer = summaryRetryTimers.current.get(runId);
357
375
  if (timer !== undefined)
358
376
  window.clearTimeout(timer);
359
377
  summaryRetryTimers.current.delete(runId);
360
- if (status === "sent") {
378
+ if (status === "queued") {
361
379
  const retryTimer = window.setTimeout(() => {
362
380
  if (!summaryRequestMounted.current)
363
381
  return;
364
382
  setSummaryRequests((current) => {
365
- if (current[runId] !== "sent")
383
+ if (current[runId] !== "queued")
366
384
  return current;
367
- const next = { ...current };
368
- delete next[runId];
369
- return next;
385
+ return { ...current, [runId]: "expired" };
370
386
  });
371
387
  summaryRetryTimers.current.delete(runId);
372
388
  }, SUMMARY_RETRY_AFTER_MS);
@@ -387,6 +403,68 @@ function ReviewTab({ days, renderArtifactPreview, }) {
387
403
  const getSummaryStatus = (runId) => summaryRequests[runId] ?? null;
388
404
  const reviewRows = reviews?.filter((review) => Boolean(review.threadId?.trim()) &&
389
405
  Boolean(review.summary || review.threadTitle.trim())) ?? [];
406
+ useEffect(() => {
407
+ if (!reviews)
408
+ return;
409
+ const summarizedRunIds = new Map();
410
+ for (const review of reviews) {
411
+ if (typeof review.summaryUpdatedAt === "number")
412
+ summarizedRunIds.set(review.runId, review.summaryUpdatedAt);
413
+ for (const run of review.runs ?? []) {
414
+ if (typeof run.summaryUpdatedAt === "number") {
415
+ summarizedRunIds.set(run.runId, run.summaryUpdatedAt);
416
+ }
417
+ }
418
+ }
419
+ const completedRunIds = Object.entries(summaryRequests)
420
+ .filter(([runId, status]) => {
421
+ const updatedAt = summarizedRunIds.get(runId);
422
+ if ((status !== "queued" && status !== "expired") ||
423
+ updatedAt === undefined) {
424
+ return false;
425
+ }
426
+ const baseline = summaryBaselineRef.current.get(runId);
427
+ return baseline == null || updatedAt > baseline;
428
+ })
429
+ .map(([runId]) => runId);
430
+ if (completedRunIds.length > 0) {
431
+ for (const runId of completedRunIds) {
432
+ const timer = summaryRetryTimers.current.get(runId);
433
+ if (timer !== undefined)
434
+ window.clearTimeout(timer);
435
+ summaryRetryTimers.current.delete(runId);
436
+ summaryBaselineRef.current.delete(runId);
437
+ }
438
+ setSummaryRequests((current) => {
439
+ let changed = false;
440
+ const next = { ...current };
441
+ for (const runId of completedRunIds) {
442
+ if (next[runId] !== "queued" && next[runId] !== "expired")
443
+ continue;
444
+ delete next[runId];
445
+ changed = true;
446
+ }
447
+ return changed ? next : current;
448
+ });
449
+ }
450
+ const batchRunIds = summaryBatchRunIds.current;
451
+ if ((summaryStatus === "queued" || summaryStatus === "expired") &&
452
+ batchRunIds.length > 0 &&
453
+ batchRunIds.every((runId) => {
454
+ const updatedAt = summarizedRunIds.get(runId);
455
+ if (updatedAt === undefined)
456
+ return false;
457
+ const baseline = summaryBaselineRef.current.get(runId);
458
+ return baseline == null || updatedAt > baseline;
459
+ })) {
460
+ if (summaryBatchRetryTimer.current !== null) {
461
+ window.clearTimeout(summaryBatchRetryTimer.current);
462
+ summaryBatchRetryTimer.current = null;
463
+ }
464
+ summaryBatchRunIds.current = [];
465
+ setSummaryStatus(null);
466
+ }
467
+ }, [reviews, summaryRequests, summaryStatus]);
390
468
  useEffect(() => {
391
469
  if (!reviews)
392
470
  return;
@@ -421,8 +499,12 @@ function ReviewTab({ days, renderArtifactPreview, }) {
421
499
  review.summary?.outcome,
422
500
  review.threadTitle,
423
501
  review.authorName,
502
+ review.authorEmail,
424
503
  review.model,
425
504
  ...review.artifacts.map((artifact) => artifact.title),
505
+ ...review.feedback
506
+ .filter((entry) => entry.feedbackType === "text")
507
+ .map((entry) => entry.value),
426
508
  ]
427
509
  .filter(Boolean)
428
510
  .join(" ")
@@ -615,7 +697,7 @@ function ReviewTab({ days, renderArtifactPreview, }) {
615
697
  return (!review.summary &&
616
698
  !review.readOnly &&
617
699
  summaryRequests[review.runId] !== "sending" &&
618
- summaryRequests[review.runId] !== "sent");
700
+ summaryRequests[review.runId] !== "queued");
619
701
  }) ?? [];
620
702
  const feedbackToImprove = (visibleReviews ?? []).flatMap((review) => {
621
703
  if (review.readOnly)
@@ -648,11 +730,16 @@ function ReviewTab({ days, renderArtifactPreview, }) {
648
730
  const summarizeVisible = () => {
649
731
  if (summaryStatus === "sending" || unsummarizedReviews.length === 0)
650
732
  return;
733
+ if (summaryBatchRetryTimer.current !== null) {
734
+ window.clearTimeout(summaryBatchRetryTimer.current);
735
+ summaryBatchRetryTimer.current = null;
736
+ }
651
737
  const batches = [];
652
738
  for (let offset = 0; offset < unsummarizedReviews.length; offset += 25) {
653
739
  batches.push(unsummarizedReviews.slice(offset, offset + 25));
654
740
  }
655
741
  const batchRunIds = batches.map((batch) => batch.map((review) => review.runId));
742
+ summaryBatchRunIds.current = batchRunIds.flat();
656
743
  setSummaryStatus("sending");
657
744
  updateSummaryRequests(batchRunIds.flat(), "sending");
658
745
  const requests = batches.map(async (batch, index) => {
@@ -677,7 +764,7 @@ function ReviewTab({ days, renderArtifactPreview, }) {
677
764
  chatTarget: "local",
678
765
  usageLabel: "observability:human-review-summary",
679
766
  });
680
- status = result.delivered ? "sent" : "failed";
767
+ status = result.delivered ? "queued" : "failed";
681
768
  }
682
769
  catch {
683
770
  status = "failed";
@@ -688,7 +775,19 @@ function ReviewTab({ days, renderArtifactPreview, }) {
688
775
  void Promise.all(requests).then((results) => {
689
776
  if (!summaryRequestMounted.current)
690
777
  return;
691
- setSummaryStatus(results.every((result) => result.status === "sent") ? "sent" : "failed");
778
+ if (results.every((result) => result.status === "queued")) {
779
+ setSummaryStatus("queued");
780
+ summaryBatchRetryTimer.current = window.setTimeout(() => {
781
+ summaryBatchRetryTimer.current = null;
782
+ if (!summaryRequestMounted.current)
783
+ return;
784
+ setSummaryStatus((current) => current === "queued" ? "expired" : current);
785
+ }, SUMMARY_RETRY_AFTER_MS);
786
+ }
787
+ else {
788
+ summaryBatchRunIds.current = [];
789
+ setSummaryStatus("failed");
790
+ }
692
791
  });
693
792
  };
694
793
  const toggleReview = (runId) => {
@@ -770,9 +869,11 @@ function ReviewTab({ days, renderArtifactPreview, }) {
770
869
  ? "bg-background text-foreground shadow-sm"
771
870
  : "text-muted-foreground hover:text-foreground"), children: label }, value))) }), unsummarizedReviews.length > 0 && (_jsxs("button", { type: "button", "data-review-bulk-summary": true, onClick: summarizeVisible, title: t("observability.summarizeWithAgent"), disabled: summaryStatus === "sending", className: "rounded-md px-2.5 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [t("observability.summarizeWithAgent"), " \u00B7", " ", unsummarizedReviews.length] })), feedbackToImprove.length > 0 && (_jsx("button", { type: "button", onClick: improveFromFeedback, title: t("observability.updateInstructions"), className: "rounded-md px-2.5 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: t("observability.updateInstructions") })), summaryStatus && (_jsx("span", { role: "status", "aria-live": "polite", className: "w-full text-xs text-muted-foreground", children: t(summaryStatus === "sending"
772
871
  ? "observability.summarySending"
773
- : summaryStatus === "sent"
774
- ? "observability.summarySent"
775
- : "observability.summaryFailed") }))] }), _jsx("div", { className: "space-y-2", "data-review-list": true, children: visibleReviews.length === 0 ? (_jsx(EmptyState, { message: t("observability.noData") })) : (visibleReviews.map((review) => {
872
+ : summaryStatus === "queued"
873
+ ? "observability.summaryQueued"
874
+ : summaryStatus === "expired"
875
+ ? "observability.summaryExpired"
876
+ : "observability.summaryFailed") }))] }), _jsx("div", { className: "space-y-2", "data-review-list": true, children: visibleReviews.length === 0 ? (_jsx(EmptyState, { message: t("observability.noData") })) : (visibleReviews.map((review) => {
776
877
  const expanded = selectedRunId === review.runId;
777
878
  const artifact = latestRenderableReviewArtifact(review.artifacts, Boolean(renderArtifactPreview));
778
879
  const artifactHref = artifact
@@ -864,7 +965,7 @@ function ReviewTab({ days, renderArtifactPreview, }) {
864
965
  return (_jsxs("div", { className: cn("max-w-[92%] break-words rounded-xl px-3 py-2 text-sm", message.role === "user"
865
966
  ? "ml-auto whitespace-pre-wrap bg-primary/10 text-foreground"
866
967
  : "mr-auto bg-muted text-foreground"), children: [message.role === "assistant" && (_jsx("span", { className: "mb-1 block text-[10px] font-medium text-muted-foreground", children: t("agentChat.common.agent") })), isLongAssistant ? (_jsxs("details", { children: [_jsxs("summary", { className: "cursor-pointer list-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx("span", { className: "line-clamp-3 whitespace-pre-wrap", children: message.text }), _jsx("span", { className: "mt-1 block text-xs text-muted-foreground", children: t("agentChat.common.expand") })] }), _jsx("p", { className: "mt-2 whitespace-pre-wrap", children: message.text })] })) : (_jsx("p", { className: "whitespace-pre-wrap", children: message.text })), message.toolCalls?.map((toolCall, toolIndex) => (_jsxs("span", { className: "mt-2 inline-flex max-w-full items-center gap-1 rounded-full bg-background/70 px-2 py-1 text-[10px] text-muted-foreground", title: toolCall, children: [_jsx(IconTool, { size: 11 }), _jsx("span", { className: "truncate", children: toolCall })] }, `${toolCall}-${toolIndex}`)))] }, `${message.role}-${index}`));
867
- })) })] })] }), _jsxs("div", { className: "col-span-full flex shrink-0 items-center gap-1 border-t border-border px-3 py-2 sm:px-4", children: [selectedReview.readOnly ? (_jsx("span", { role: "note", title: t("observability.readOnlyTenant"), className: "text-xs text-muted-foreground", children: t("observability.readOnlyTenant") })) : (_jsxs(_Fragment, { children: [_jsx(ObservabilityReviewSummaryButton, { runId: activeRunId ?? selectedReview.runId, orgId: selectedReview.orgId, status: getSummaryStatus(activeRunId ?? selectedReview.runId), onStatusChange: (status) => updateSummaryRequests([activeRunId ?? selectedReview.runId], status), compact: true, refresh: Boolean(selectedSummary) }), _jsxs("div", { role: "group", "aria-label": t("observability.reviewFeedback"), className: "flex items-center gap-1", children: [activeRunId && pendingVotes[activeRunId] && (_jsxs("span", { role: "status", "aria-live": "polite", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(IconLoader2, { size: 13, className: "animate-spin" }), t("agentChat.common.saving")] })), activeRunId && voteErrors[activeRunId] && (_jsx("span", { role: "status", "aria-live": "polite", className: "text-xs text-destructive", children: t("agentChat.common.saveFailed") })), _jsx("button", { type: "button", "aria-label": t("observability.thumbsUp"), "aria-pressed": selectedVoteType === "thumbs_up", title: t("observability.thumbsUp"), disabled: Boolean(activeRunId && pendingVotes[activeRunId]), onClick: () => saveFeedback(activeRunId ?? selectedReview.runId, "thumbs_up"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVoteType === "thumbs_up" &&
968
+ })) })] })] }), _jsxs("div", { className: "col-span-full flex shrink-0 items-center gap-1 border-t border-border px-3 py-2 sm:px-4", children: [selectedReview.readOnly ? (_jsx("span", { role: "note", title: t("observability.readOnlyTenant"), className: "text-xs text-muted-foreground", children: t("observability.readOnlyTenant") })) : (_jsxs(_Fragment, { children: [_jsx(ObservabilityReviewSummaryButton, { runId: activeRunId ?? selectedReview.runId, orgId: selectedReview.orgId, status: getSummaryStatus(activeRunId ?? selectedReview.runId), onStatusChange: (status) => updateSummaryRequests([activeRunId ?? selectedReview.runId], status), refresh: Boolean(selectedSummary) }), _jsxs("div", { role: "group", "aria-label": t("observability.reviewFeedback"), className: "flex items-center gap-1", children: [activeRunId && pendingVotes[activeRunId] && (_jsxs("span", { role: "status", "aria-live": "polite", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(IconLoader2, { size: 13, className: "animate-spin" }), t("agentChat.common.saving")] })), activeRunId && voteErrors[activeRunId] && (_jsx("span", { role: "status", "aria-live": "polite", className: "text-xs text-destructive", children: t("agentChat.common.saveFailed") })), _jsx("button", { type: "button", "aria-label": t("observability.thumbsUp"), "aria-pressed": selectedVoteType === "thumbs_up", title: t("observability.thumbsUp"), disabled: Boolean(activeRunId && pendingVotes[activeRunId]), onClick: () => saveFeedback(activeRunId ?? selectedReview.runId, "thumbs_up"), className: cn("rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50", selectedVoteType === "thumbs_up" &&
868
969
  "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"), children: _jsx(IconThumbUp, { size: 16 }) }), _jsx("button", { type: "button", "aria-label": t("observability.thumbsDown"), "aria-pressed": selectedVoteType === "thumbs_down", title: t("observability.thumbsDown"), disabled: Boolean(activeRunId && pendingVotes[activeRunId]), onClick: () => {
869
970
  saveFeedback(activeRunId ?? selectedReview.runId, "thumbs_down");
870
971
  setFeedbackNote((current) => current?.runId ===
@@ -7,5 +7,5 @@ export interface ObservabilityReviewSummaryButtonProps {
7
7
  background?: boolean;
8
8
  refresh?: boolean;
9
9
  }
10
- export type ObservabilityReviewSummaryStatus = "sending" | "sent" | "failed";
10
+ export type ObservabilityReviewSummaryStatus = "sending" | "queued" | "failed" | "expired";
11
11
  export declare function ObservabilityReviewSummaryButton({ runId, orgId, status, onStatusChange, compact, background, refresh, }: ObservabilityReviewSummaryButtonProps): import("react").JSX.Element;