@agent-native/core 0.0.0-beta-20260820210146 → 0.0.0-beta-20260820214042

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.
@@ -253,7 +253,11 @@ function applyOrder<T extends { id: string }>(
253
253
  return ordered;
254
254
  }
255
255
 
256
- function matchesVisibilityFilter(
256
+ function isVisibility(value: unknown): value is Visibility {
257
+ return value === "private" || value === "org" || value === "public";
258
+ }
259
+
260
+ export function matchesVisibilityFilter(
257
261
  item: { visibility?: Visibility },
258
262
  filter: SidebarVisibilityFilter,
259
263
  ): boolean {
@@ -264,6 +268,23 @@ function matchesVisibilityFilter(
264
268
  return item.visibility === "org" || item.visibility === "public";
265
269
  }
266
270
 
271
+ export function threadMatchesVisibilityFilter(
272
+ thread: ChatThreadSummary,
273
+ filter: SidebarVisibilityFilter,
274
+ ): boolean {
275
+ const runtimeThread = thread as ChatThreadSummary & {
276
+ visibility?: unknown;
277
+ };
278
+ return matchesVisibilityFilter(
279
+ {
280
+ visibility: isVisibility(runtimeThread.visibility)
281
+ ? runtimeThread.visibility
282
+ : "private",
283
+ },
284
+ filter,
285
+ );
286
+ }
287
+
267
288
  function SidebarSectionSettingsPopover({
268
289
  label,
269
290
  sortMode,
@@ -274,8 +295,8 @@ function SidebarSectionSettingsPopover({
274
295
  onShowHiddenChange,
275
296
  }: {
276
297
  label: string;
277
- sortMode: SidebarSortMode;
278
- onSortModeChange: (value: SidebarSortMode) => void;
298
+ sortMode?: SidebarSortMode;
299
+ onSortModeChange?: (value: SidebarSortMode) => void;
279
300
  visibilityFilter: SidebarVisibilityFilter;
280
301
  onVisibilityFilterChange: (value: SidebarVisibilityFilter) => void;
281
302
  showHidden?: boolean;
@@ -350,54 +371,56 @@ function SidebarSectionSettingsPopover({
350
371
  </Tooltip>
351
372
  </ToggleGroup>
352
373
  </div>
353
- <div className="grid gap-1.5">
354
- <p className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
355
- {t("sidebar.sortBy")}
356
- </p>
357
- <ToggleGroup
358
- type="single"
359
- value={sortMode}
360
- onValueChange={(next) => {
361
- if (
362
- next === "most-used" ||
363
- next === "alphabetical" ||
364
- next === "manual"
365
- ) {
366
- onSortModeChange(next);
367
- }
368
- }}
369
- className="grid grid-cols-3 gap-1 rounded-lg border border-border/60 bg-background/50 p-1"
370
- >
371
- <Tooltip>
372
- <TooltipTrigger asChild>
373
- <ToggleGroupItem
374
- value="most-used"
375
- aria-label={t("sidebar.sortMostUsedPersonal")}
376
- className={segmentedItemClass}
377
- >
378
- {t("sidebar.used")}
379
- </ToggleGroupItem>
380
- </TooltipTrigger>
381
- <TooltipContent side="bottom">
382
- {t("sidebar.usedExplainer")}
383
- </TooltipContent>
384
- </Tooltip>
385
- <ToggleGroupItem
386
- value="alphabetical"
387
- aria-label={t("sidebar.sortAlphabetically")}
388
- className={segmentedItemClass}
389
- >
390
- {t("sidebar.alphabetical")}
391
- </ToggleGroupItem>
392
- <ToggleGroupItem
393
- value="manual"
394
- aria-label={t("sidebar.sortManually")}
395
- className={segmentedItemClass}
374
+ {sortMode && onSortModeChange ? (
375
+ <div className="grid gap-1.5">
376
+ <p className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
377
+ {t("sidebar.sortBy")}
378
+ </p>
379
+ <ToggleGroup
380
+ type="single"
381
+ value={sortMode}
382
+ onValueChange={(next) => {
383
+ if (
384
+ next === "most-used" ||
385
+ next === "alphabetical" ||
386
+ next === "manual"
387
+ ) {
388
+ onSortModeChange(next);
389
+ }
390
+ }}
391
+ className="grid grid-cols-3 gap-1 rounded-lg border border-border/60 bg-background/50 p-1"
396
392
  >
397
- {t("sidebar.manual")}
398
- </ToggleGroupItem>
399
- </ToggleGroup>
400
- </div>
393
+ <Tooltip>
394
+ <TooltipTrigger asChild>
395
+ <ToggleGroupItem
396
+ value="most-used"
397
+ aria-label={t("sidebar.sortMostUsedPersonal")}
398
+ className={segmentedItemClass}
399
+ >
400
+ {t("sidebar.used")}
401
+ </ToggleGroupItem>
402
+ </TooltipTrigger>
403
+ <TooltipContent side="bottom">
404
+ {t("sidebar.usedExplainer")}
405
+ </TooltipContent>
406
+ </Tooltip>
407
+ <ToggleGroupItem
408
+ value="alphabetical"
409
+ aria-label={t("sidebar.sortAlphabetically")}
410
+ className={segmentedItemClass}
411
+ >
412
+ {t("sidebar.alphabetical")}
413
+ </ToggleGroupItem>
414
+ <ToggleGroupItem
415
+ value="manual"
416
+ aria-label={t("sidebar.sortManually")}
417
+ className={segmentedItemClass}
418
+ >
419
+ {t("sidebar.manual")}
420
+ </ToggleGroupItem>
421
+ </ToggleGroup>
422
+ </div>
423
+ ) : null}
401
424
  <div className="grid gap-1">
402
425
  {onShowHiddenChange && showHidden !== undefined && (
403
426
  <label
@@ -1285,9 +1308,11 @@ function persistedAnalyticsThreadId() {
1285
1308
  function AnalyticsChatsSection({
1286
1309
  isAskRoute,
1287
1310
  open,
1311
+ visibilityFilter,
1288
1312
  }: {
1289
1313
  isAskRoute: boolean;
1290
1314
  open: boolean;
1315
+ visibilityFilter: SidebarVisibilityFilter;
1291
1316
  }) {
1292
1317
  const navigate = useNavigate();
1293
1318
  const t = useT();
@@ -1309,10 +1334,15 @@ function AnalyticsChatsSection({
1309
1334
  const visibleThreads = useMemo(
1310
1335
  () =>
1311
1336
  threads
1312
- .filter((thread) => thread.messageCount > 0 && !thread.archivedAt)
1337
+ .filter(
1338
+ (thread) =>
1339
+ thread.messageCount > 0 &&
1340
+ !thread.archivedAt &&
1341
+ threadMatchesVisibilityFilter(thread, visibilityFilter),
1342
+ )
1313
1343
  .sort(compareThreads)
1314
1344
  .slice(0, 15),
1315
- [threads],
1345
+ [threads, visibilityFilter],
1316
1346
  );
1317
1347
  const chatItems = useMemo<ChatHistoryItem[]>(
1318
1348
  () =>
@@ -1488,6 +1518,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
1488
1518
  const [askOpen, setAskOpen] = useState(
1489
1519
  () => getStoredBooleanPreference(ASK_OPEN_KEY) ?? isAskRoute,
1490
1520
  );
1521
+ const [askFilter, setAskFilter] = useState<SidebarVisibilityFilter>("all");
1491
1522
  const [dashOpen, setDashOpen] = useState(
1492
1523
  () =>
1493
1524
  getStoredBooleanPreference(DASHBOARDS_OPEN_KEY) ??
@@ -2295,6 +2326,11 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2295
2326
  {t("navigation.ask")}
2296
2327
  </span>
2297
2328
  </Link>
2329
+ <SidebarSectionSettingsPopover
2330
+ label={t("navigation.ask")}
2331
+ visibilityFilter={askFilter}
2332
+ onVisibilityFilterChange={setAskFilter}
2333
+ />
2298
2334
  <button
2299
2335
  type="button"
2300
2336
  onClick={toggleAskOpen}
@@ -2314,7 +2350,11 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2314
2350
  />
2315
2351
  </button>
2316
2352
  </div>
2317
- <AnalyticsChatsSection isAskRoute={isAskRoute} open={askOpen} />
2353
+ <AnalyticsChatsSection
2354
+ isAskRoute={isAskRoute}
2355
+ open={askOpen}
2356
+ visibilityFilter={askFilter}
2357
+ />
2318
2358
  </div>
2319
2359
 
2320
2360
  {/* Sessions link */}
@@ -113,10 +113,10 @@ const enUS = {
113
113
  "Used, sort, and manual order are personal to this browser.",
114
114
  usedExplainer: "Counts how often you've opened each item in this browser.",
115
115
  visibilityAll: "All",
116
- visibilityPrivateOnly: "Private",
116
+ visibilityPrivateOnly: "Mine",
117
117
  visibilitySharedOnly: "Shared",
118
118
  visibilityAllDescription: "Show all items",
119
- visibilityPrivateOnlyDescription: "Show private items only",
119
+ visibilityPrivateOnlyDescription: "Show items visible only to you",
120
120
  visibilitySharedOnlyDescription: "Show org-shared and public items",
121
121
  hiddenAnalyses: "Hidden analyses",
122
122
  shareWithOrg: "Share with org",
@@ -5048,10 +5048,10 @@ export const messagesByLocale = {
5048
5048
  personalControlsNote: "排序、手动顺序和“常用”仅适用于此浏览器。",
5049
5049
  usedExplainer: "统计你在此浏览器中打开每个项目的次数。",
5050
5050
  visibilityAll: "全部",
5051
- visibilityPrivateOnly: "私有",
5051
+ visibilityPrivateOnly: "我的",
5052
5052
  visibilitySharedOnly: "共享",
5053
5053
  visibilityAllDescription: "显示所有项目",
5054
- visibilityPrivateOnlyDescription: "仅显示私有项目",
5054
+ visibilityPrivateOnlyDescription: "仅显示你可见的项目",
5055
5055
  visibilitySharedOnlyDescription: "显示组织共享和公开项目",
5056
5056
  hiddenAnalyses: "隐藏的分析",
5057
5057
  shareWithOrg: "与组织共享",
@@ -5258,10 +5258,11 @@ export const messagesByLocale = {
5258
5258
  usedExplainer:
5259
5259
  "Cuenta cuántas veces abriste cada elemento en este navegador.",
5260
5260
  visibilityAll: "Todo",
5261
- visibilityPrivateOnly: "Privado",
5261
+ visibilityPrivateOnly: "Míos",
5262
5262
  visibilitySharedOnly: "Compartido",
5263
5263
  visibilityAllDescription: "Mostrar todos los elementos",
5264
- visibilityPrivateOnlyDescription: "Mostrar solo elementos privados",
5264
+ visibilityPrivateOnlyDescription:
5265
+ "Mostrar solo elementos visibles para ti",
5265
5266
  visibilitySharedOnlyDescription:
5266
5267
  "Mostrar elementos compartidos con la organización y públicos",
5267
5268
  hiddenAnalyses: "Análisis ocultos",
@@ -5478,11 +5479,11 @@ export const messagesByLocale = {
5478
5479
  usedExplainer:
5479
5480
  "Compte le nombre de fois où vous avez ouvert chaque élément dans ce navigateur.",
5480
5481
  visibilityAll: "Tous",
5481
- visibilityPrivateOnly: "Privé",
5482
+ visibilityPrivateOnly: "À moi",
5482
5483
  visibilitySharedOnly: "Partagé",
5483
5484
  visibilityAllDescription: "Afficher tous les éléments",
5484
5485
  visibilityPrivateOnlyDescription:
5485
- "Afficher uniquement les éléments privés",
5486
+ "Afficher uniquement les éléments visibles pour vous",
5486
5487
  visibilitySharedOnlyDescription:
5487
5488
  "Afficher les éléments partagés avec l’organisation et publics",
5488
5489
  hiddenAnalyses: "Analyses masquées",
@@ -5705,10 +5706,11 @@ export const messagesByLocale = {
5705
5706
  usedExplainer:
5706
5707
  "Zählt, wie oft du jedes Element in diesem Browser geöffnet hast.",
5707
5708
  visibilityAll: "Alle",
5708
- visibilityPrivateOnly: "Privat",
5709
+ visibilityPrivateOnly: "Meine",
5709
5710
  visibilitySharedOnly: "Geteilt",
5710
5711
  visibilityAllDescription: "Alle Elemente anzeigen",
5711
- visibilityPrivateOnlyDescription: "Nur private Elemente anzeigen",
5712
+ visibilityPrivateOnlyDescription:
5713
+ "Nur für dich sichtbare Elemente anzeigen",
5712
5714
  visibilitySharedOnlyDescription:
5713
5715
  "Mit Organisation geteilte und öffentliche Elemente anzeigen",
5714
5716
  hiddenAnalyses: "Ausgeblendete Analysen",
@@ -5931,10 +5933,10 @@ export const messagesByLocale = {
5931
5933
  "並べ替え、手動順序、「使用」はこのブラウザーだけに保存されます。",
5932
5934
  usedExplainer: "このブラウザーで各項目を開いた回数を数えます。",
5933
5935
  visibilityAll: "すべて",
5934
- visibilityPrivateOnly: "非公開",
5936
+ visibilityPrivateOnly: "自分の",
5935
5937
  visibilitySharedOnly: "共有",
5936
5938
  visibilityAllDescription: "すべての項目を表示",
5937
- visibilityPrivateOnlyDescription: "非公開の項目のみ表示",
5939
+ visibilityPrivateOnlyDescription: "自分だけに表示される項目のみ表示",
5938
5940
  visibilitySharedOnlyDescription: "組織共有と公開の項目を表示",
5939
5941
  hiddenAnalyses: "非表示の分析",
5940
5942
  shareWithOrg: "組織と共有",
@@ -6149,10 +6151,10 @@ export const messagesByLocale = {
6149
6151
  "정렬, 수동 순서, 사용됨은 이 브라우저에만 적용됩니다.",
6150
6152
  usedExplainer: "이 브라우저에서 각 항목을 연 횟수를 계산합니다.",
6151
6153
  visibilityAll: "전체",
6152
- visibilityPrivateOnly: "비공개",
6154
+ visibilityPrivateOnly: "내 항목",
6153
6155
  visibilitySharedOnly: "공유됨",
6154
6156
  visibilityAllDescription: "모든 항목 표시",
6155
- visibilityPrivateOnlyDescription: "비공개 항목만 표시",
6157
+ visibilityPrivateOnlyDescription: "나에게만 표시되는 항목만 표시",
6156
6158
  visibilitySharedOnlyDescription: "조직 공유 및 공개 항목 표시",
6157
6159
  hiddenAnalyses: "숨긴 분석",
6158
6160
  shareWithOrg: "조직과 공유",
@@ -6368,10 +6370,11 @@ export const messagesByLocale = {
6368
6370
  usedExplainer:
6369
6371
  "Conta quantas vezes você abriu cada item neste navegador.",
6370
6372
  visibilityAll: "Tudo",
6371
- visibilityPrivateOnly: "Privado",
6373
+ visibilityPrivateOnly: "Meus",
6372
6374
  visibilitySharedOnly: "Compartilhado",
6373
6375
  visibilityAllDescription: "Mostrar todos os itens",
6374
- visibilityPrivateOnlyDescription: "Mostrar apenas itens privados",
6376
+ visibilityPrivateOnlyDescription:
6377
+ "Mostrar apenas itens visíveis para você",
6375
6378
  visibilitySharedOnlyDescription:
6376
6379
  "Mostrar itens compartilhados com a organização e públicos",
6377
6380
  hiddenAnalyses: "Análises ocultas",
@@ -6592,10 +6595,10 @@ export const messagesByLocale = {
6592
6595
  personalControlsNote: "क्रम, मैन्युअल क्रम और उपयोग इस ब्राउज़र के लिए निजी हैं।",
6593
6596
  usedExplainer: "गिनता है कि आपने इस ब्राउज़र में हर आइटम कितनी बार खोला है।",
6594
6597
  visibilityAll: "सभी",
6595
- visibilityPrivateOnly: "निजी",
6598
+ visibilityPrivateOnly: "मेरे",
6596
6599
  visibilitySharedOnly: "साझा",
6597
6600
  visibilityAllDescription: "सभी आइटम दिखाएं",
6598
- visibilityPrivateOnlyDescription: "केवल निजी आइटम दिखाएं",
6601
+ visibilityPrivateOnlyDescription: "केवल आपको दिखाई देने वाले आइटम दिखाएं",
6599
6602
  visibilitySharedOnlyDescription: "संगठन से साझा और सार्वजनिक आइटम दिखाएं",
6600
6603
  hiddenAnalyses: "छिपे हुए विश्लेषण",
6601
6604
  shareWithOrg: "संगठन से साझा करें",
@@ -6807,10 +6810,10 @@ export const messagesByLocale = {
6807
6810
  "الفرز والترتيب اليدوي وخيار مستخدم شخصية لهذا المتصفح.",
6808
6811
  usedExplainer: "يحسب عدد مرات فتحك لكل عنصر في هذا المتصفح.",
6809
6812
  visibilityAll: "الكل",
6810
- visibilityPrivateOnly: "خاص",
6813
+ visibilityPrivateOnly: "لي",
6811
6814
  visibilitySharedOnly: "مشترك",
6812
6815
  visibilityAllDescription: "عرض كل العناصر",
6813
- visibilityPrivateOnlyDescription: "عرض العناصر الخاصة فقط",
6816
+ visibilityPrivateOnlyDescription: "عرض العناصر الظاهرة لك فقط",
6814
6817
  visibilitySharedOnlyDescription: "عرض عناصر المؤسسة المشتركة والعامة",
6815
6818
  hiddenAnalyses: "التحليلات المخفية",
6816
6819
  shareWithOrg: "مشاركة مع المؤسسة",
@@ -613,11 +613,12 @@ export function SqlChartCard({
613
613
  {panel.title}
614
614
  </CardTitle>
615
615
  <div className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100">
616
- {editable && onSaveSql ? (
616
+ {!editable || onSaveSql ? (
617
617
  <ViewSqlPopover
618
618
  panel={panel}
619
619
  resolvedSql={resolvedSql}
620
620
  onSaveSql={onSaveSql}
621
+ editable={editable}
621
622
  >
622
623
  <button
623
624
  className="p-1 rounded text-muted-foreground hover:text-foreground"
@@ -7,6 +7,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
7
7
 
8
8
  ### Improved
9
9
 
10
+ - Recording preparation now uses a centered full-screen overlay that transitions smoothly into the 3-2-1 countdown.
10
11
  - Clip viewers now make Share the primary action and keep the overflow control compact and vertical.
11
12
  - Recording controls now follow you across browser tabs and page navigations.
12
13
 
@@ -237,6 +237,213 @@ function normalizedChangelogText(value) {
237
237
  .replace(/\s+/g, " ")
238
238
  .trim();
239
239
  }
240
+ function stripHtmlComments(line, inComment, initialInlineCodeMarker) {
241
+ let cursor = 0;
242
+ let visibleLine = "";
243
+ const comments = [];
244
+ let inlineCodeMarker = initialInlineCodeMarker;
245
+ while (cursor < line.length) {
246
+ if (inComment) {
247
+ const commentEnd = line.indexOf("-->", cursor);
248
+ if (commentEnd === -1) {
249
+ return {
250
+ line: visibleLine,
251
+ inComment: true,
252
+ inlineCodeMarker,
253
+ comments,
254
+ };
255
+ }
256
+ cursor = commentEnd + 3;
257
+ inComment = false;
258
+ continue;
259
+ }
260
+ if (inlineCodeMarker) {
261
+ const codeEnd = line.indexOf(inlineCodeMarker, cursor);
262
+ if (codeEnd === -1) {
263
+ visibleLine += line.slice(cursor);
264
+ break;
265
+ }
266
+ visibleLine += line.slice(cursor, codeEnd + inlineCodeMarker.length);
267
+ cursor = codeEnd + inlineCodeMarker.length;
268
+ inlineCodeMarker = undefined;
269
+ continue;
270
+ }
271
+ const commentStart = line.indexOf("<!--", cursor);
272
+ const codeStartIndex = line.indexOf("`", cursor);
273
+ if (codeStartIndex !== -1 &&
274
+ (commentStart === -1 || codeStartIndex < commentStart)) {
275
+ visibleLine += line.slice(cursor, codeStartIndex);
276
+ const codeStart = /^`+/.exec(line.slice(codeStartIndex))?.[0] ?? "`";
277
+ visibleLine += codeStart;
278
+ cursor = codeStartIndex + codeStart.length;
279
+ inlineCodeMarker = codeStart;
280
+ continue;
281
+ }
282
+ if (commentStart === -1) {
283
+ visibleLine += line.slice(cursor);
284
+ break;
285
+ }
286
+ visibleLine += line.slice(cursor, commentStart);
287
+ const commentEnd = line.indexOf("-->", commentStart + 4);
288
+ if (commentEnd !== -1) {
289
+ comments.push(line.slice(commentStart, commentEnd + 3));
290
+ }
291
+ cursor = commentStart + 4;
292
+ inComment = true;
293
+ }
294
+ return { line: visibleLine, inComment, inlineCodeMarker, comments };
295
+ }
296
+ function splitChangelogBodyGroups(body) {
297
+ const matches = [];
298
+ let offset = 0;
299
+ let fenceMarker;
300
+ let htmlComment = false;
301
+ let inlineCodeMarker;
302
+ for (const line of body.split(/\r?\n/)) {
303
+ const lineEnd = offset + line.length;
304
+ if (fenceMarker) {
305
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(line)?.[1];
306
+ const closingFence = /^\s*(`{3,}|~{3,})\s*$/.exec(line)?.[1];
307
+ if (fence &&
308
+ closingFence &&
309
+ fenceMarker.character === fence[0] &&
310
+ closingFence.length >= fenceMarker.length) {
311
+ fenceMarker = undefined;
312
+ }
313
+ }
314
+ else {
315
+ let visibleLine;
316
+ let headingComments = [];
317
+ if (!htmlComment && !inlineCodeMarker) {
318
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(line)?.[1];
319
+ if (fence) {
320
+ fenceMarker = { character: fence[0], length: fence.length };
321
+ }
322
+ else {
323
+ const commentResult = stripHtmlComments(line, false, inlineCodeMarker);
324
+ htmlComment = commentResult.inComment;
325
+ inlineCodeMarker = commentResult.inlineCodeMarker;
326
+ visibleLine = commentResult.line;
327
+ headingComments = commentResult.comments;
328
+ }
329
+ }
330
+ else {
331
+ const commentResult = stripHtmlComments(line, htmlComment, inlineCodeMarker);
332
+ htmlComment = commentResult.inComment;
333
+ inlineCodeMarker = commentResult.inlineCodeMarker;
334
+ visibleLine = commentResult.line;
335
+ headingComments = commentResult.comments;
336
+ }
337
+ if (visibleLine !== undefined && !fenceMarker) {
338
+ const fence = /^\s*(`{3,}|~{3,})(.*)$/.exec(visibleLine)?.[1];
339
+ if (fence) {
340
+ fenceMarker = { character: fence[0], length: fence.length };
341
+ }
342
+ else {
343
+ const heading = /^ {0,3}###\s+(.+?)(?:\s+#+)?\s*$/.exec(visibleLine);
344
+ if (heading &&
345
+ CHANGELOG_GROUP_ORDER.some((type) => GROUP_LABELS[type].toLowerCase() ===
346
+ heading[1].trim().toLowerCase())) {
347
+ matches.push({
348
+ title: heading[1].trim(),
349
+ headingComments,
350
+ start: offset,
351
+ headingEnd: lineEnd,
352
+ });
353
+ }
354
+ }
355
+ }
356
+ }
357
+ const newlineLength = body.startsWith("\r\n", lineEnd)
358
+ ? 2
359
+ : lineEnd < body.length
360
+ ? 1
361
+ : 0;
362
+ offset = lineEnd + newlineLength;
363
+ }
364
+ if (matches.length === 0)
365
+ return { prefix: body.trim(), groups: [] };
366
+ const prefix = body.slice(0, matches[0].start).trim();
367
+ const groups = matches.map((match, index) => {
368
+ const end = matches[index + 1]?.start ?? body.length;
369
+ return {
370
+ title: match.title,
371
+ headingComments: match.headingComments,
372
+ body: body.slice(match.headingEnd, end).trim(),
373
+ index,
374
+ };
375
+ });
376
+ return { prefix, groups };
377
+ }
378
+ function changelogBodyGroupOrder(title) {
379
+ const normalizedTitle = title.toLowerCase();
380
+ const index = CHANGELOG_GROUP_ORDER.findIndex((type) => GROUP_LABELS[type].toLowerCase() === normalizedTitle);
381
+ return index === -1 ? CHANGELOG_GROUP_ORDER.length : index;
382
+ }
383
+ function normalizeChangelogBody(body) {
384
+ const { prefix, groups } = splitChangelogBodyGroups(body);
385
+ if (groups.length < 2)
386
+ return body.trim();
387
+ const uniqueGroups = new Map();
388
+ for (const group of groups) {
389
+ const key = group.title.toLowerCase();
390
+ const current = uniqueGroups.get(key);
391
+ const headingComments = current
392
+ ? [
393
+ ...current.headingComments,
394
+ ...group.headingComments.filter((comment) => !current.headingComments.includes(comment)),
395
+ ]
396
+ : group.headingComments;
397
+ uniqueGroups.set(key, {
398
+ title: current?.title ?? group.title,
399
+ headingComments,
400
+ body: [current?.body, group.body].filter(Boolean).join("\n"),
401
+ index: current?.index ?? group.index,
402
+ });
403
+ }
404
+ const renderedGroups = [...uniqueGroups.values()]
405
+ .sort((a, b) => changelogBodyGroupOrder(a.title) - changelogBodyGroupOrder(b.title) ||
406
+ a.index - b.index)
407
+ .map((group) => `### ${group.title}${group.headingComments.length ? ` ${group.headingComments.join(" ")}` : ""}\n\n${group.body}`);
408
+ return [prefix, ...renderedGroups].filter(Boolean).join("\n\n");
409
+ }
410
+ function mergeChangelogBodies(pendingBody, existingBody) {
411
+ const pending = splitChangelogBodyGroups(pendingBody);
412
+ const existing = splitChangelogBodyGroups(existingBody);
413
+ if (pending.groups.length === 0) {
414
+ return normalizeChangelogBody(existingBody);
415
+ }
416
+ if (existing.groups.length === 0) {
417
+ return [pendingBody, existingBody]
418
+ .map((body) => body.trim())
419
+ .filter(Boolean)
420
+ .join("\n\n");
421
+ }
422
+ const groups = new Map();
423
+ for (const group of [...pending.groups, ...existing.groups]) {
424
+ const key = group.title.toLowerCase();
425
+ const current = groups.get(key);
426
+ const headingComments = current
427
+ ? [
428
+ ...current.headingComments,
429
+ ...group.headingComments.filter((comment) => !current.headingComments.includes(comment)),
430
+ ]
431
+ : group.headingComments;
432
+ groups.set(key, {
433
+ title: current?.title ?? group.title,
434
+ headingComments,
435
+ body: [current?.body, group.body].filter(Boolean).join("\n"),
436
+ index: current?.index ?? group.index,
437
+ });
438
+ }
439
+ const prefix = [pending.prefix, existing.prefix].filter(Boolean).join("\n\n");
440
+ return normalizeChangelogBody([
441
+ prefix,
442
+ ...[...groups.values()].map((group) => `### ${group.title}${group.headingComments.length ? ` ${group.headingComments.join(" ")}` : ""}\n\n${group.body}`),
443
+ ]
444
+ .filter(Boolean)
445
+ .join("\n\n"));
446
+ }
240
447
  /**
241
448
  * Render an app-facing changelog that includes both released CHANGELOG.md
242
449
  * sections and adjacent folder-backed `changelog/*.md` entries. This is pure
@@ -246,10 +453,19 @@ function normalizedChangelogText(value) {
246
453
  export function mergePendingChangelog(existing, pending) {
247
454
  const cleanExisting = stripChangelogArchiveNotes(existing);
248
455
  const existingEntries = parseChangelog(cleanExisting);
456
+ const pendingSeen = new Set();
249
457
  const pendingWithText = pending.filter((entry) => {
250
458
  const text = entry.text.trim();
251
459
  if (!text)
252
460
  return false;
461
+ const pendingKey = [
462
+ entry.date ?? "",
463
+ entry.type,
464
+ normalizedChangelogText(text),
465
+ ].join("\u0000");
466
+ if (pendingSeen.has(pendingKey))
467
+ return false;
468
+ pendingSeen.add(pendingKey);
253
469
  return !existingEntries.some((existingEntry) => {
254
470
  if (entry.date &&
255
471
  existingEntry.date &&
@@ -261,7 +477,7 @@ export function mergePendingChangelog(existing, pending) {
261
477
  });
262
478
  if (pendingWithText.length === 0) {
263
479
  const sections = existingEntries
264
- .map((entry) => `## ${entry.title}\n\n${cleanChangelogBody(entry.body)}`)
480
+ .map((entry) => `## ${entry.title}\n\n${normalizeChangelogBody(cleanChangelogBody(entry.body))}`)
265
481
  .join("\n\n");
266
482
  return `${changelogHeader(cleanExisting)}${sections ? `\n\n${sections}` : ""}\n\n${CHANGELOG_ARCHIVE_NOTE}\n`;
267
483
  }
@@ -273,7 +489,7 @@ export function mergePendingChangelog(existing, pending) {
273
489
  const sections = existingEntries.map((entry) => ({
274
490
  title: entry.title,
275
491
  date: entry.date,
276
- body: cleanChangelogBody(entry.body),
492
+ body: normalizeChangelogBody(cleanChangelogBody(entry.body)),
277
493
  }));
278
494
  for (const title of [...pendingByTitle.keys()].sort(pendingSectionSort)) {
279
495
  const body = renderReleaseBody(pendingByTitle.get(title) ?? []);
@@ -283,12 +499,7 @@ export function mergePendingChangelog(existing, pending) {
283
499
  return entry.date === title || entry.title === title;
284
500
  });
285
501
  if (existingIndex !== -1) {
286
- sections[existingIndex].body = [
287
- body,
288
- cleanChangelogBody(sections[existingIndex].body),
289
- ]
290
- .filter(Boolean)
291
- .join("\n\n");
502
+ sections[existingIndex].body = mergeChangelogBodies(body, cleanChangelogBody(sections[existingIndex].body));
292
503
  continue;
293
504
  }
294
505
  const newSection = { title, date: title.match(ISO_DATE)?.[1], body };
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- ok?: undefined;
30
29
  error: string;
30
+ ok?: undefined;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
44
45
  summary: import("./types.js").TraceSummary;
45
46
  spans: import("./types.js").TraceSpan[];
46
47
  id?: undefined;
47
- error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
+ error?: undefined;
50
51
  summary?: undefined;
51
52
  spans?: undefined;
52
53
  id: string;
53
- error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
+ error?: undefined;
62
63
  summary?: undefined;
63
64
  spans?: undefined;
64
65
  id?: undefined;
65
- error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
51
51
  error: string;
52
52
  ok?: undefined;
53
53
  } | {
54
- error?: undefined;
55
54
  ok: boolean;
55
+ error?: undefined;
56
56
  }>;
57
57
  /** POST /_agent-native/resources/upload — upload a file as a resource */
58
58
  export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
73
73
  runId: string | null;
74
74
  expiresAt: number | null;
75
75
  metadata: string | null;
76
- error?: undefined;
77
76
  url: string;
78
77
  provider: string;
79
78
  storageSetupRequired?: undefined;
79
+ error?: undefined;
80
80
  } | {
81
81
  error: string;
82
82
  storageSetupRequired: boolean;
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
+ error?: undefined;
30
31
  ok: boolean;
31
32
  key: string;
32
33
  baseUrlKey?: string;
33
34
  scope: AgentEngineApiKeyScope;
34
- error?: undefined;
35
35
  }>>;
36
36
  export {};
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- text: string;
24
23
  error?: undefined;
24
+ text: string;
25
25
  }>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.0.0-beta-20260820210146",
3
+ "version": "0.0.0-beta-20260820214042",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -427,8 +427,8 @@
427
427
  "y-protocols": "^1.0.7",
428
428
  "yjs": "^13.6.32",
429
429
  "zod": "^4.3.6",
430
- "@agent-native/recap-cli": "0.5.6",
431
- "@agent-native/toolkit": "^0.16.9"
430
+ "@agent-native/toolkit": "^0.16.9",
431
+ "@agent-native/recap-cli": "0.5.6"
432
432
  },
433
433
  "devDependencies": {
434
434
  "@ai-sdk/anthropic": "^3.0.71",