@agent-native/core 0.189.0-nightly-20260925200527 → 0.189.0

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 (34) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/content/CHANGELOG.md +6 -0
  3. package/corpus/templates/content/vite.config.ts +2 -38
  4. package/corpus/templates/design/CHANGELOG.md +4 -0
  5. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +36 -11
  6. package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +82 -0
  7. package/corpus/templates/mail/app/pages/InboxPage.tsx +18 -19
  8. package/corpus/templates/mail/changelog/2026-09-25-keep-inbox-tab-counts-consistent-when-switching-tabs.md +6 -0
  9. package/corpus/templates/slides/CHANGELOG.md +4 -0
  10. package/dist/cli/index.js +1 -2
  11. package/dist/collab/awareness.d.ts +2 -2
  12. package/dist/deploy/build.d.ts +1 -1
  13. package/dist/deploy/build.js +9 -348
  14. package/dist/deploy/workspace-deploy.d.ts +2 -2
  15. package/dist/deploy/workspace-deploy.js +9 -130
  16. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  17. package/dist/notifications/routes.d.ts +3 -3
  18. package/dist/observability/routes.d.ts +6 -6
  19. package/dist/org/handlers.d.ts +1 -1
  20. package/dist/progress/routes.d.ts +1 -1
  21. package/dist/resources/handlers.d.ts +1 -1
  22. package/dist/secrets/routes.d.ts +3 -3
  23. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  24. package/dist/vite/client.d.ts +1 -1
  25. package/docs/content/a2a-protocol.mdx +2 -2
  26. package/docs/content/cloudflare.mdx +2 -10
  27. package/docs/content/deploy-an-app.mdx +0 -1
  28. package/docs/content/deployment.mdx +2 -8
  29. package/docs/content/environment-variables.mdx +1 -1
  30. package/docs/content/multi-app-workspace.mdx +1 -1
  31. package/docs/content/netlify.mdx +1 -1
  32. package/docs/content/real-time-collaboration.mdx +6 -6
  33. package/docs/content/workspace-deployment.mdx +4 -15
  34. package/package.json +4 -4
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: 9962
34
+ - template files: 9963
@@ -3,6 +3,12 @@
3
3
  All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-25
7
+
8
+ ### Added
9
+
10
+ - Chat messages can be reverted to their autosaved version, including a shortcut to restore the start of a chat.
11
+
6
12
  ## 2026-09-23
7
13
 
8
14
  ### Added
@@ -389,41 +389,6 @@ const dynamicLocalComponentDirs = (() => {
389
389
  }
390
390
  })();
391
391
 
392
- const cloudflareSsrStubs =
393
- process.env.NITRO_PRESET === "cloudflare_pages"
394
- ? [
395
- "@assistant-ui/react",
396
- "@tiptap/core",
397
- "@tiptap/extension-blockquote",
398
- "@tiptap/extension-code-block-lowlight",
399
- "@tiptap/extension-collaboration",
400
- "@tiptap/extension-collaboration-caret",
401
- "@tiptap/extension-image",
402
- "@tiptap/extension-link",
403
- "@tiptap/extension-placeholder",
404
- "@tiptap/extension-table",
405
- "@tiptap/extension-table-cell",
406
- "@tiptap/extension-table-header",
407
- "@tiptap/extension-table-row",
408
- "@tiptap/extension-task-item",
409
- "@tiptap/extension-task-list",
410
- "@tiptap/pm",
411
- "@tiptap/react",
412
- "@tiptap/starter-kit",
413
- "@xterm/addon-fit",
414
- "@xterm/addon-web-links",
415
- "@xterm/xterm",
416
- "lowlight",
417
- "prettier",
418
- "react-markdown",
419
- "remark-gfm",
420
- "remark-mdx",
421
- "tiptap-markdown",
422
- "yjs",
423
- "y-protocols",
424
- ]
425
- : [];
426
-
427
392
  export default defineConfig({
428
393
  plugins: [
429
394
  contentLocalComponentsPlugin(),
@@ -434,9 +399,8 @@ export default defineConfig({
434
399
  ...(localWorkspaceRoot ? [localWorkspaceRoot] : []),
435
400
  ...dynamicLocalComponentDirs,
436
401
  ],
437
- // shiki only runs in AssistantChat's useEffect — keep it out of the
438
- // CF Pages Functions bundle (25 MiB limit).
439
- ssrStubs: ["shiki", ...cloudflareSsrStubs],
402
+ // shiki only runs in AssistantChat's useEffect — keep it out of SSR bundles.
403
+ ssrStubs: ["shiki"],
440
404
  }),
441
405
  ],
442
406
  optimizeDeps: {
@@ -5,6 +5,10 @@ from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
6
  ## 2026-09-25
7
7
 
8
+ ### Added
9
+
10
+ - Chat messages can be reverted to their autosaved version, including a shortcut to restore the start of a chat.
11
+
8
12
  ### Fixed
9
13
 
10
14
  - Cross-screen moves preserve Flex and Grid sizing through undo and redo.
@@ -101,7 +101,9 @@ import {
101
101
  import {
102
102
  INBOX_PAGE_SIZE,
103
103
  invalidateInboxThreads,
104
+ mergeOptimisticInboxTabCounts,
104
105
  resolveInboxTabId,
106
+ useInboxOverview,
105
107
  useInboxThreads,
106
108
  } from "@/hooks/use-inbox-threads";
107
109
  import {
@@ -563,18 +565,43 @@ function AppLayoutInner({ children }: AppLayoutProps) {
563
565
  const resolvedInboxTab = resolveInboxTabId(searchParams);
564
566
  const inboxAccountEmails =
565
567
  activeAccounts.size > 0 ? [...activeAccounts] : undefined;
566
- const inboxThreads = useInboxThreads({
568
+ const inboxThreadInput = {
567
569
  tab: resolvedInboxTab,
568
570
  accountEmails: inboxAccountEmails,
569
571
  limit: INBOX_PAGE_SIZE,
570
572
  offset: 0,
571
- });
573
+ };
574
+ const inboxThreads = useInboxThreads(inboxThreadInput);
575
+ const inboxRawPage = queryClient.getQueryData<
576
+ NonNullable<typeof inboxThreads.data>
577
+ >(["action", "list-inbox-threads", inboxThreadInput]);
578
+ const inboxOverview = useInboxOverview(inboxAccountEmails);
579
+ const inboxMetadata =
580
+ inboxOverview.data ??
581
+ (inboxThreads.isPlaceholderData ? undefined : inboxThreads.data);
582
+ const inboxTabs = useMemo(() => {
583
+ const tabs = inboxMetadata?.tabs ?? [];
584
+ if (!inboxOverview.data || inboxThreads.isPlaceholderData) {
585
+ return tabs;
586
+ }
587
+ return mergeOptimisticInboxTabCounts(
588
+ inboxOverview.data,
589
+ inboxRawPage,
590
+ inboxThreads.data,
591
+ );
592
+ }, [
593
+ inboxMetadata?.tabs,
594
+ inboxOverview.data,
595
+ inboxThreads.data,
596
+ inboxThreads.isPlaceholderData,
597
+ inboxRawPage,
598
+ ]);
572
599
  const activeInboxTabId = inboxThreads.isPlaceholderData
573
600
  ? (resolvedInboxTab ?? inboxThreads.data?.tabs[0]?.id)
574
601
  : (inboxThreads.data?.activeTabId ?? resolvedInboxTab);
575
602
  const inboxIsFetching = inboxThreads.isFetching;
576
- const inboxSyncing = inboxThreads.data?.syncing === true;
577
- const needsReauthAccount = inboxThreads.data?.accounts.find(
603
+ const inboxSyncing = inboxMetadata?.syncing === true;
604
+ const needsReauthAccount = inboxMetadata?.accounts.find(
578
605
  (account) => account.state === "needs_reauth",
579
606
  );
580
607
 
@@ -721,12 +748,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
721
748
  }, [combineInbox, pinnedLabels, view, t]);
722
749
 
723
750
  // The inbox split (Important / pinned labels / saved filters / Other) with
724
- // its counts comes straight from the server — see the useInboxThreads call
725
- // above. A tab's badge can never disagree with what it lists because both
726
- // are read off the same row.
751
+ // its counts comes from one account-scoped snapshot, shared across each
752
+ // tab's separately cached row page.
727
753
  const dataTabs = useMemo<RenderedTab[]>(() => {
728
- const tabs = inboxThreads.data?.tabs ?? [];
729
- return tabs.map((tab) => {
754
+ return inboxTabs.map((tab) => {
730
755
  const label = labels.find((l) => l.id === tab.id);
731
756
  return {
732
757
  id: tab.id,
@@ -743,7 +768,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
743
768
  isSystemView: false,
744
769
  };
745
770
  });
746
- }, [inboxThreads.data?.tabs, activeInboxTabId, labels, t, view]);
771
+ }, [inboxTabs, activeInboxTabId, labels, t, view]);
747
772
 
748
773
  const topBarTabs = useMemo<RenderedTab[]>(
749
774
  () => [...systemViewTabs, ...dataTabs],
@@ -1265,7 +1290,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
1265
1290
  // unread count from the same sync-cache label list the tab bar uses —
1266
1291
  // summing the (possibly overlapping) split tabs would double-count threads
1267
1292
  // that match more than one label/filter tab.
1268
- const inboxSidebarUnreadCount = inboxThreads.data?.labels.find(
1293
+ const inboxSidebarUnreadCount = inboxMetadata?.labels.find(
1269
1294
  (label) => label.id === "inbox",
1270
1295
  )?.unreadCount;
1271
1296
  const railNavItems = [
@@ -7,6 +7,7 @@ import type {
7
7
  } from "@shared/inbox-threads";
8
8
  import {
9
9
  keepPreviousData,
10
+ skipToken,
10
11
  useQuery,
11
12
  useQueries,
12
13
  useQueryClient,
@@ -22,6 +23,79 @@ export const INBOX_THREADS_QUERY_KEY = ["action", "list-inbox-threads"];
22
23
 
23
24
  const SYNCING_POLL_MS = 3_000;
24
25
  const IDLE_POLL_MS = 20_000;
26
+ const INBOX_THREADS_STALE_TIME_MS = IDLE_POLL_MS;
27
+
28
+ export type InboxOverview = Pick<
29
+ ListInboxThreadsResult,
30
+ "tabs" | "syncing" | "accounts" | "labels"
31
+ > & { clientSnapshotId: number };
32
+
33
+ type InboxPageCountSnapshot = Pick<
34
+ ListInboxThreadsResult,
35
+ "activeTabId" | "tabs"
36
+ > & { clientSnapshotId: number };
37
+
38
+ export function mergeOptimisticInboxTabCounts(
39
+ overview: Pick<InboxOverview, "tabs" | "clientSnapshotId">,
40
+ base: InboxPageCountSnapshot | undefined,
41
+ projected: InboxPageCountSnapshot | undefined,
42
+ ) {
43
+ if (
44
+ !base ||
45
+ !projected ||
46
+ base.clientSnapshotId !== overview.clientSnapshotId ||
47
+ projected.clientSnapshotId !== overview.clientSnapshotId ||
48
+ base.activeTabId !== projected.activeTabId
49
+ ) {
50
+ return overview.tabs;
51
+ }
52
+ const baseTab = base.tabs.find((tab) => tab.id === base.activeTabId);
53
+ const projectedTab = projected.tabs.find(
54
+ (tab) => tab.id === projected.activeTabId,
55
+ );
56
+ if (!baseTab || !projectedTab) return overview.tabs;
57
+
58
+ const totalDelta = projectedTab.total - baseTab.total;
59
+ const unreadDelta = projectedTab.unread - baseTab.unread;
60
+ if (totalDelta === 0 && unreadDelta === 0) return overview.tabs;
61
+
62
+ return overview.tabs.map((tab) =>
63
+ tab.id === projectedTab.id
64
+ ? {
65
+ ...tab,
66
+ total: Math.max(0, tab.total + totalDelta),
67
+ unread: Math.max(0, tab.unread + unreadDelta),
68
+ }
69
+ : tab,
70
+ );
71
+ }
72
+
73
+ export function inboxOverviewQueryKey(accountEmails?: readonly string[]) {
74
+ const accounts = accountEmails
75
+ ? [...accountEmails].map((email) => email.toLowerCase()).sort()
76
+ : undefined;
77
+ return ["mail-inbox-overview", accounts] as const;
78
+ }
79
+
80
+ export function publishInboxOverview(
81
+ qc: QueryClient,
82
+ accountEmails: readonly string[] | undefined,
83
+ incoming: InboxOverview,
84
+ ) {
85
+ const queryKey = inboxOverviewQueryKey(accountEmails);
86
+ const current = qc.getQueryData<InboxOverview>(queryKey);
87
+ if (current && current.clientSnapshotId >= incoming.clientSnapshotId) return;
88
+ qc.setQueryData(queryKey, incoming);
89
+ }
90
+
91
+ /** Shared metadata is populated by the active tab query and never fetches separately. */
92
+ export function useInboxOverview(accountEmails?: readonly string[]) {
93
+ return useQuery<InboxOverview>({
94
+ queryKey: inboxOverviewQueryKey(accountEmails),
95
+ queryFn: skipToken,
96
+ staleTime: Infinity,
97
+ });
98
+ }
25
99
 
26
100
  // Not yet re-exported for template use from
27
101
  // packages/core/src/client/create-query-client.ts's isTerminalAuthFailure.
@@ -124,6 +198,13 @@ function fetchInboxThreads(
124
198
  { method: "GET", signal },
125
199
  ).then((data) => {
126
200
  const incoming = { ...data, clientSnapshotId };
201
+ publishInboxOverview(qc, input.accountEmails, {
202
+ tabs: incoming.tabs,
203
+ syncing: incoming.syncing,
204
+ accounts: incoming.accounts,
205
+ labels: incoming.labels,
206
+ clientSnapshotId,
207
+ });
127
208
  const current = qc.getQueryData<InboxQueryResult>(queryKey);
128
209
  return keepLatestInboxSnapshot(current, incoming);
129
210
  });
@@ -151,6 +232,7 @@ export function useInboxThreads(
151
232
  // window-focus refetch fans out across every mounted instance (bar +
152
233
  // list) and isn't worth the added request-storm risk.
153
234
  refetchInterval: inboxThreadsRefetchInterval,
235
+ staleTime: INBOX_THREADS_STALE_TIME_MS,
154
236
  // Tab switches must never blank the list while the new tab's page loads.
155
237
  placeholderData: keepPreviousData,
156
238
  select: (data) => applyInboxMutationOverlay(qc, data) as InboxQueryResult,
@@ -35,6 +35,7 @@ import {
35
35
  inboxThreadsHasNextPage,
36
36
  mergeInboxThreadPages,
37
37
  resolveInboxTabId,
38
+ useInboxOverview,
38
39
  useInboxThreads,
39
40
  useInboxThreadsPages,
40
41
  } from "@/hooks/use-inbox-threads";
@@ -450,13 +451,11 @@ export function InboxPage() {
450
451
  const searchQuery =
451
452
  activeSavedFilter?.query ?? searchParams.get("q") ?? undefined;
452
453
 
453
- // The inbox view is split server-side (tabs, counts, and the rendered
454
- // list all come from one `list-inbox-threads` call keyed by the resolved
455
- // tab id) — see shared/inbox-threads.ts. Every other view still fetches
456
- // through `useEmails` below, unchanged. A `q` search on /inbox is not a
457
- // tab partition the store computes, so it falls through to the same
458
- // `useEmails` search path non-inbox views use — the store path is only
459
- // for a plain /inbox with no `q`.
454
+ // Inbox rows are tab-scoped; each response publishes its account-scoped
455
+ // counts and sync metadata to the shared overview cache. Other views still
456
+ // fetch through `useEmails` below, unchanged. A `q` search on /inbox is not
457
+ // a tab partition the store computes, so it uses the same `useEmails`
458
+ // search path as non-inbox views.
460
459
  const isInboxView = view === "inbox" && !searchParams.get("q");
461
460
  useEffect(() => {
462
461
  if (!jevAvailability.isSuccess) return;
@@ -473,10 +472,10 @@ export function InboxPage() {
473
472
  const resolvedInboxTab = resolveInboxTabId(searchParams);
474
473
  const inboxAccountEmails =
475
474
  activeAccounts.size > 0 ? [...activeAccounts] : undefined;
476
- // Page 0 drives tabs/counts/syncing/accounts/labels and is the only page
477
- // that polls. "Load more" grows `inboxExtraPageCount`, fetching one more
478
- // unpolled page per step (see useInboxThreadsPages's doc for why this
479
- // isn't one useInfiniteQuery).
475
+ // Page 0 drives rows and pagination totals and is the only page that polls.
476
+ // Its account-scoped metadata snapshot is shared across tabs. "Load more"
477
+ // grows `inboxExtraPageCount`, fetching one unpolled page per step (see
478
+ // useInboxThreadsPages's doc for why this isn't one useInfiniteQuery).
480
479
  const inboxThreads = useInboxThreads(
481
480
  {
482
481
  tab: resolvedInboxTab,
@@ -488,6 +487,10 @@ export function InboxPage() {
488
487
  // it stays keyed on the route alone, not `isInboxView`.
489
488
  { enabled: view === "inbox" },
490
489
  );
490
+ const inboxOverview = useInboxOverview(inboxAccountEmails);
491
+ const inboxMetadata =
492
+ inboxOverview.data ??
493
+ (inboxThreads.isPlaceholderData ? undefined : inboxThreads.data);
491
494
  const [inboxExtraPageCount, setInboxExtraPageCount] = useState(0);
492
495
  useEffect(() => {
493
496
  const priorityExtraPages = Math.max(
@@ -559,7 +562,7 @@ export function InboxPage() {
559
562
  // coverage the same as a sync error (the reconnect-specific banner in
560
563
  // AppLayout is unaffected — this only feeds the generic notice + the
561
564
  // Inbox Zero suppression below).
562
- const errored = inboxThreads.data?.accounts.filter(
565
+ const errored = inboxMetadata?.accounts.filter(
563
566
  (account) =>
564
567
  account.state === "error" || account.state === "needs_reauth",
565
568
  );
@@ -579,7 +582,7 @@ export function InboxPage() {
579
582
  const combined = [...inboxErrors, ...labelErrors];
580
583
  return combined.length ? combined : undefined;
581
584
  }, [
582
- inboxThreads.data?.accounts,
585
+ inboxMetadata?.accounts,
583
586
  inboxThreads.isPlaceholderData,
584
587
  labelAccountErrors,
585
588
  ]);
@@ -687,9 +690,7 @@ export function InboxPage() {
687
690
  // produced any rows yet, so the list shows skeleton rows (not Inbox Zero)
688
691
  // until there is something real to show either way.
689
692
  const inboxStillSyncingEmpty =
690
- isInboxView &&
691
- inboxThreads.data?.syncing === true &&
692
- inboxItems.length === 0;
693
+ isInboxView && inboxMetadata?.syncing === true && inboxItems.length === 0;
693
694
  const isLoading = isInboxView
694
695
  ? inboxThreads.isLoading ||
695
696
  inboxThreads.isPlaceholderData ||
@@ -1152,9 +1153,7 @@ export function InboxPage() {
1152
1153
  emailsError={emailsError}
1153
1154
  accountErrors={accountErrors}
1154
1155
  labels={
1155
- isInboxView
1156
- ? (inboxThreads.data?.labels ?? EMPTY_LABELS)
1157
- : undefined
1156
+ isInboxView ? (inboxMetadata?.labels ?? EMPTY_LABELS) : undefined
1158
1157
  }
1159
1158
  refetchEmails={refetchEmails}
1160
1159
  hasNextPage={hasNextPage}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-25
4
+ ---
5
+
6
+ Keep inbox tab counts consistent when switching tabs
@@ -5,6 +5,10 @@ time from the command menu (Cmd+K → "What's new").
5
5
 
6
6
  ## 2026-09-25
7
7
 
8
+ ### Added
9
+
10
+ - Chat messages can be reverted to their autosaved version, including a shortcut to restore the start of a chat.
11
+
8
12
  ### Fixed
9
13
 
10
14
  - Word document imports now complete successfully.
package/dist/cli/index.js CHANGED
@@ -1233,8 +1233,7 @@ Options:
1233
1233
  --standalone Scaffold a single standalone app (no workspace)
1234
1234
  --emit [dir] With migrate, emit an own-agent dossier
1235
1235
  --describe <text> With migrate, describe URL/prose-only sources
1236
- --preset <name> Workspace deploy preset:
1237
- cloudflare_pages (default), netlify, or vercel
1236
+ --preset <name> Workspace deploy preset: netlify (default) or vercel
1238
1237
  --build-only Build workspace deploy artifacts without publishing
1239
1238
  --eager With workspace dev, start every app immediately
1240
1239
  --prewarm With workspace dev, warm non-default apps in the background
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -8,7 +8,6 @@
8
8
  * 3. Bundles everything with esbuild into the target format
9
9
  *
10
10
  * Supported presets:
11
- * - cloudflare_pages: Outputs dist/ with _worker.js for Cloudflare Pages
12
11
  * - cloudflare_module: Outputs a native Cloudflare Worker under .output/server
13
12
  * - aws_amplify: Uses Nitro's .amplify-hosting deployment specification
14
13
  *
@@ -438,3 +437,4 @@ export declare function resolveNitroBundledYjsEntry(): string;
438
437
  */
439
438
  export declare function nitroNoExternalsForPreset(targetPreset: string): true | readonly string[];
440
439
  export declare function resolveNitroBuildReplacements(env?: NodeJS.ProcessEnv, deploymentEnvironment?: string, projectCwd?: string, firstRunOnboardingMode?: AgentNativeFirstRunOnboardingMode | "", harnessMode?: string): Record<string, string>;
440
+ export declare function assertCloudflarePagesPresetRemoved(targetPreset: string): void;