@agent-native/core 0.76.8 → 0.76.10

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 (55) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +48 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/scripts/generate-og-font-data.mjs +9 -5
  5. package/corpus/core/src/agent/run-store.ts +21 -0
  6. package/corpus/core/src/client/AgentPanel.tsx +70 -1
  7. package/corpus/core/src/client/AssistantChat.tsx +1 -3
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +0 -62
  9. package/corpus/core/src/client/composer/TiptapComposer.tsx +6 -13
  10. package/corpus/core/src/server/agent-chat-plugin.ts +26 -0
  11. package/corpus/core/src/server/og-fonts-data.ts +7 -4
  12. package/corpus/core/src/server/og-fonts.ts +10 -3
  13. package/corpus/core/src/server/social-og-image.ts +45 -6
  14. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +199 -19
  15. package/corpus/templates/analytics/app/pages/adhoc/AdhocRouter.tsx +1 -21
  16. package/corpus/templates/analytics/app/pages/adhoc/DashboardSkeleton.tsx +35 -0
  17. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -23
  18. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +3 -26
  19. package/corpus/templates/analytics/changelog/2026-06-24-daily-dashboard-charts-now-use-local-day-buckets-and-show-th.md +6 -0
  20. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-loading-placeholders-now-span-the-available-page-w.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-06-24-first-party-dashboard-dates-now-align-to-pacific-time-for-da.md +6 -0
  22. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +19 -7
  23. package/dist/agent/run-store.d.ts +11 -0
  24. package/dist/agent/run-store.d.ts.map +1 -1
  25. package/dist/agent/run-store.js +20 -0
  26. package/dist/agent/run-store.js.map +1 -1
  27. package/dist/client/AgentPanel.d.ts +5 -1
  28. package/dist/client/AgentPanel.d.ts.map +1 -1
  29. package/dist/client/AgentPanel.js +23 -4
  30. package/dist/client/AgentPanel.js.map +1 -1
  31. package/dist/client/AssistantChat.d.ts +1 -3
  32. package/dist/client/AssistantChat.d.ts.map +1 -1
  33. package/dist/client/AssistantChat.js.map +1 -1
  34. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  35. package/dist/client/MultiTabAssistantChat.js +1 -17
  36. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  37. package/dist/client/composer/TiptapComposer.d.ts +4 -0
  38. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  39. package/dist/client/composer/TiptapComposer.js +5 -9
  40. package/dist/client/composer/TiptapComposer.js.map +1 -1
  41. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  42. package/dist/server/agent-chat-plugin.js +26 -0
  43. package/dist/server/agent-chat-plugin.js.map +1 -1
  44. package/dist/server/og-fonts-data.d.ts +1 -0
  45. package/dist/server/og-fonts-data.d.ts.map +1 -1
  46. package/dist/server/og-fonts-data.js +5 -4
  47. package/dist/server/og-fonts-data.js.map +1 -1
  48. package/dist/server/og-fonts.d.ts +1 -0
  49. package/dist/server/og-fonts.d.ts.map +1 -1
  50. package/dist/server/og-fonts.js +10 -4
  51. package/dist/server/og-fonts.js.map +1 -1
  52. package/dist/server/social-og-image.d.ts.map +1 -1
  53. package/dist/server/social-og-image.js +33 -6
  54. package/dist/server/social-og-image.js.map +1 -1
  55. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 1928
31
- - template files: 4243
31
+ - template files: 4247
@@ -1,5 +1,53 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.76.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 2be975e: Animate the standard agent sidebar drawer when it opens and closes.
8
+ - 2be975e: Durable background diagnostics: preserve the background-function worker's last
9
+ `diag_stage` (`route_entered` / `auth_failed` / etc., or `none` if it never
10
+ reached the route) in the foreground circuit-breaker's
11
+ `foreground_inline_recovery` detail instead of overwriting it. This makes a
12
+ silent worker death diagnosable from `/runs/active` without reading the
13
+ unreadable Netlify background-function logs. `readBackgroundRunClaim` now also
14
+ returns `diagStage`.
15
+ - 2be975e: Durable background agent runs: add a foreground **circuit-breaker** so a dead
16
+ background worker can no longer break chat. A Netlify async background function
17
+ returns `202` the instant it enqueues the invocation, but the worker may never
18
+ execute — e.g. the generated function wrapper fails to import `./main.mjs` or
19
+ hand off to the Nitro `_process-run` route, so it never reaches
20
+ `claimBackgroundRun` and the run is reaped as "worker never claimed the run".
21
+ After a successful dispatch the foreground now polls briefly for the worker to
22
+ actually claim the run; if it doesn't within the grace window, the turn is
23
+ recovered **inline** (the same safe atomic-claim path used for a fast dispatch
24
+ failure), so a dead worker degrades to a working synchronous turn instead of a
25
+ reaped failure. Also harden the generated background-function wrapper to pass
26
+ Netlify's `context` through to the Nitro handler and wrap the handoff in
27
+ try/catch so a pre-route failure is logged loudly instead of silently swallowed
28
+ behind the async 202.
29
+ - 2be975e: Bundle an Arabic-capable OG image font so localized Arabic docs previews render real text instead of missing-glyph boxes.
30
+ - 2be975e: Show a New chat button on full-page Ask chat surfaces with visible conversation tabs after a conversation starts.
31
+ - 2be975e: Add an `agentNative()` Vite plugin preset so app `vite.config.ts` files can use
32
+ Vite's native `defineConfig` while keeping Agent-Native framework defaults.
33
+ - 2be975e: Stop showing previous scoped chats in the empty chat state.
34
+ - 2be975e: Let the agent composer model picker shrink to its content instead of forcing a tall popover.
35
+
36
+ ## 0.76.9
37
+
38
+ ### Patch Changes
39
+
40
+ - bbbd01a: Durable background agent-chat: actually run the background worker. The
41
+ self-dispatch into the Netlify background function is cookieless (HMAC-only), so
42
+ the worker had no session and `resolveOwnerContext` threw 401 "Unauthenticated"
43
+ before it could even claim the run — every durable run died at the route
44
+ boundary (`route_threw`) and only completed via the foreground circuit-breaker's
45
+ inline recovery, never using the 15-minute background budget. The `_process-run`
46
+ route now resolves the owner securely from the run's chat thread
47
+ (`getRunOwnerEmail(runId)` — DB-derived from the HMAC-signed run row, not the
48
+ forgeable request body) and pre-seeds the owner context, so the background
49
+ worker runs with the correct authenticated owner and the full background budget.
50
+
3
51
  ## 0.76.8
4
52
 
5
53
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.76.8",
3
+ "version": "0.76.10",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // Regenerates src/server/og-fonts-data.ts by base64-encoding the bundled
3
- // Liberation Sans fonts. The fonts are embedded as base64 (rather than read
3
+ // OG image fonts. The fonts are embedded as base64 (rather than read
4
4
  // from disk at runtime) so that OG image text renders consistently regardless
5
5
  // of the host's system fonts or how the serverless bundle is traced — the
6
6
  // font bytes always travel with the compiled JS. Run after updating the .ttf
@@ -17,13 +17,17 @@ const fonts = [
17
17
  constName: "LIBERATION_SANS_BOLD_BASE64",
18
18
  file: "../src/assets/fonts/LiberationSans-Bold.ttf",
19
19
  },
20
+ {
21
+ constName: "NOTO_NASKH_ARABIC_BASE64",
22
+ file: "../src/assets/fonts/NotoNaskhArabic-Variable.ttf",
23
+ },
20
24
  ];
21
25
 
22
26
  const banner = `// AUTO-GENERATED by scripts/generate-og-font-data.mjs — do not edit by hand.
23
- // Base64-encoded Liberation Sans (a metric-compatible libre replacement for
24
- // Arial/Helvetica), embedded so the OG image renderer always has a font to use,
25
- // independent of host system fonts or serverless asset tracing. See
26
- // src/assets/fonts/LICENSE_LIBERATION for licensing. Regenerate with:
27
+ // Base64-encoded OG image fonts, embedded so the OG image renderer always has
28
+ // script coverage independent of host system fonts or serverless asset tracing.
29
+ // See src/assets/fonts/LICENSE_LIBERATION and
30
+ // src/assets/fonts/LICENSE_NOTO_NASKH_ARABIC for licensing. Regenerate with:
27
31
  // node scripts/generate-og-font-data.mjs
28
32
  `;
29
33
 
@@ -414,6 +414,27 @@ export async function readBackgroundRunClaim(runId: string): Promise<{
414
414
  };
415
415
  }
416
416
 
417
+ /**
418
+ * Resolve the authenticated owner email for a run by joining it to its chat
419
+ * thread. The durable background worker's self-dispatch is cookieless
420
+ * (HMAC-only — see `AGENT_CHAT_PROCESS_RUN_PATH`), so it has no session for the
421
+ * normal owner resolution and would otherwise be treated as unauthenticated.
422
+ * The thread's `owner_email` was written by the authenticated foreground when it
423
+ * created the thread, so it is a trusted, non-forgeable owner source: only the
424
+ * HMAC-signed `runId` selects the row, and the caller cannot influence which
425
+ * owner that row maps to. Returns null when the run (or its thread) is missing.
426
+ */
427
+ export async function getRunOwnerEmail(runId: string): Promise<string | null> {
428
+ await ensureRunTables();
429
+ const client = getDbExec();
430
+ const { rows } = await client.execute({
431
+ sql: `SELECT t.owner_email AS owner_email FROM agent_runs r JOIN chat_threads t ON r.thread_id = t.id WHERE r.id = ? LIMIT 1`,
432
+ args: [runId],
433
+ });
434
+ const row = rows?.[0] as { owner_email?: string | null } | undefined;
435
+ return row?.owner_email ?? null;
436
+ }
437
+
417
438
  /**
418
439
  * Atomically acquire a run lease for a thread. Succeeds (returns true) only
419
440
  * when no other run for the same thread is currently status='running' with a
@@ -374,6 +374,18 @@ export function shouldShowAgentPanelChatTabBar(
374
374
  return mainTabs.length > 1 || hasSubTabs;
375
375
  }
376
376
 
377
+ export function shouldShowAgentPanelPageNewChatButton(
378
+ tabs: MultiTabAssistantChatHeaderProps["tabs"],
379
+ activeTabId: string,
380
+ activeTabMessageCount: number,
381
+ ) {
382
+ const activeTab = tabs.find((tab) => tab.id === activeTabId);
383
+ return (
384
+ Boolean(activeTabId) &&
385
+ (activeTabMessageCount > 0 || activeTab?.status === "running")
386
+ );
387
+ }
388
+
377
389
  export function shouldShowAgentPanelCliTabBar(cliTabs: string[]) {
378
390
  return cliTabs.length > 1;
379
391
  }
@@ -532,6 +544,8 @@ export interface AgentPanelProps extends Omit<
532
544
  chatNotice?: React.ReactNode;
533
545
  /** Show the chat thread tab row when the panel header is hidden. Default: true. */
534
546
  showTabBar?: boolean;
547
+ /** Show a compact New chat action in page chat when the main header is hidden. */
548
+ showPageNewChatButton?: boolean;
535
549
  /** Capability gate for source edits and CLI access. */
536
550
  codeAccess?: AgentPanelCodeAccess;
537
551
  }
@@ -649,6 +663,7 @@ function AgentPanelInner({
649
663
  threadUrlSync,
650
664
  chatNotice,
651
665
  showTabBar = true,
666
+ showPageNewChatButton = false,
652
667
  codeAccess,
653
668
  ...assistantChatProps
654
669
  }: AgentPanelProps) {
@@ -1246,6 +1261,43 @@ function AgentPanelInner({
1246
1261
  ],
1247
1262
  );
1248
1263
 
1264
+ const renderPageChatOverlay = useCallback(
1265
+ ({
1266
+ activeTabId,
1267
+ activeTabMessageCount,
1268
+ addTab,
1269
+ tabs,
1270
+ }: MultiTabAssistantChatHeaderProps) => {
1271
+ if (
1272
+ !shouldShowAgentPanelPageNewChatButton(
1273
+ tabs,
1274
+ activeTabId,
1275
+ activeTabMessageCount,
1276
+ )
1277
+ ) {
1278
+ return null;
1279
+ }
1280
+
1281
+ return (
1282
+ <div className="pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4">
1283
+ <button
1284
+ type="button"
1285
+ data-agent-page-new-chat=""
1286
+ aria-label={t("agentPanel.newChat")}
1287
+ onClick={() => {
1288
+ addTab();
1289
+ }}
1290
+ className="pointer-events-auto inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1291
+ >
1292
+ <IconPlus size={14} />
1293
+ <span>{t("agentPanel.newChat")}</span>
1294
+ </button>
1295
+ </div>
1296
+ );
1297
+ },
1298
+ [t],
1299
+ );
1300
+
1249
1301
  // Ref callback: scroll the active tab into view in the overflow container.
1250
1302
  // Uses getBoundingClientRect for reliable positioning regardless of offsetParent.
1251
1303
  const activeTabRefCb = useCallback((el: HTMLDivElement | null) => {
@@ -1591,7 +1643,11 @@ function AgentPanelInner({
1591
1643
  showHeader={false}
1592
1644
  renderHeader={showHeader ? renderChatHeader : undefined}
1593
1645
  showTabBar={showTabBar}
1594
- renderOverlay={undefined}
1646
+ renderOverlay={
1647
+ showPageNewChatButton && !showHeader
1648
+ ? renderPageChatOverlay
1649
+ : undefined
1650
+ }
1595
1651
  contentHidden={mode !== "chat"}
1596
1652
  emptyStateText={emptyStateText}
1597
1653
  emptyStateAddon={emptyStateAddon}
@@ -2181,6 +2237,13 @@ export interface AgentChatSurfaceProps extends AgentPanelProps {
2181
2237
  chatViewTransition?: boolean;
2182
2238
  }
2183
2239
 
2240
+ export function shouldDefaultAgentChatSurfacePageNewChatButton(
2241
+ mode: AgentChatSurfaceMode | undefined,
2242
+ showTabBar: boolean | undefined,
2243
+ ): boolean {
2244
+ return mode === "page" && showTabBar !== false;
2245
+ }
2246
+
2184
2247
  /**
2185
2248
  * Reusable chat surface backed by AgentPanel internals.
2186
2249
  *
@@ -2195,15 +2258,21 @@ export function AgentChatSurface({
2195
2258
  isFullscreen,
2196
2259
  style,
2197
2260
  chatViewTransition = false,
2261
+ showPageNewChatButton,
2198
2262
  ...props
2199
2263
  }: AgentChatSurfaceProps) {
2200
2264
  const pageMode = mode === "page";
2265
+ const defaultShowPageNewChatButton =
2266
+ shouldDefaultAgentChatSurfacePageNewChatButton(mode, props.showTabBar);
2201
2267
 
2202
2268
  return (
2203
2269
  <AgentPanel
2204
2270
  {...props}
2205
2271
  defaultMode={defaultMode}
2206
2272
  isFullscreen={isFullscreen ?? pageMode}
2273
+ showPageNewChatButton={
2274
+ showPageNewChatButton ?? defaultShowPageNewChatButton
2275
+ }
2207
2276
  className={cn(
2208
2277
  pageMode && "h-full min-h-0 w-full overflow-hidden bg-background",
2209
2278
  chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS,
@@ -637,9 +637,7 @@ export interface AssistantChatProps {
637
637
  suggestions?: string[];
638
638
  /** Context-aware suggestions merged with `suggestions`. Enabled by default. */
639
639
  dynamicSuggestions?: AgentDynamicSuggestionsOption;
640
- /** Optional content rendered in the empty state, above the suggestion buttons.
641
- * Used by MultiTabAssistantChat to surface "previous chats for this design"
642
- * when the current thread is empty but the scope has other threads. */
640
+ /** Optional content rendered in the empty state, above the suggestion buttons. */
643
641
  emptyStateAddon?: React.ReactNode;
644
642
  /** Whether to show the header bar. Default: true */
645
643
  showHeader?: boolean;
@@ -342,57 +342,6 @@ function ScopeBadge({
342
342
  );
343
343
  }
344
344
 
345
- /**
346
- * Empty-state addon shown when the user starts a fresh chat inside a
347
- * scoped surface that already has other threads. Surfaces those threads
348
- * inline so chats don't feel "lost" after the user navigates away and
349
- * back — the chip popover lists them too, but this nudge is visible
350
- * without any extra clicks.
351
- */
352
- function PreviousScopedChatsHint({
353
- scope,
354
- threads,
355
- onSelectThread,
356
- }: {
357
- scope: ChatThreadScope;
358
- threads: ChatThreadSummary[];
359
- onSelectThread: (id: string) => void;
360
- }) {
361
- const MAX_INLINE = 3;
362
- const shown = threads.slice(0, MAX_INLINE);
363
- const remaining = threads.length - shown.length;
364
- const scopeLabel = scope.label || `this ${formatScopeType(scope.type)}`;
365
- return (
366
- <div className="flex w-full max-w-[280px] flex-col gap-1.5">
367
- <div className="text-[10px] uppercase tracking-wider text-muted-foreground/70 text-center">
368
- Previous chats for {scopeLabel}
369
- </div>
370
- <div className="flex flex-col gap-1">
371
- {shown.map((thread) => (
372
- <button
373
- key={thread.id}
374
- type="button"
375
- onClick={() => onSelectThread(thread.id)}
376
- className="flex items-baseline justify-between gap-2 rounded-md border border-border px-2.5 py-1.5 text-start hover:bg-accent cursor-pointer"
377
- >
378
- <span className="truncate text-[12px] text-foreground">
379
- {thread.title || thread.preview || "Chat"}
380
- </span>
381
- <span className="shrink-0 text-[10px] text-muted-foreground">
382
- {formatThreadTime(thread.updatedAt)}
383
- </span>
384
- </button>
385
- ))}
386
- </div>
387
- {remaining > 0 && (
388
- <div className="text-[10px] text-muted-foreground/70 text-center">
389
- +{remaining} more
390
- </div>
391
- )}
392
- </div>
393
- );
394
- }
395
-
396
345
  /**
397
346
  * Thin confirmation banner shown briefly after detach. The chip itself
398
347
  * unmounts the moment scope clears on the active thread, so this banner
@@ -2777,17 +2726,6 @@ export function MultiTabAssistantChat({
2777
2726
  ? `Ask about ${tabScope.label}`
2778
2727
  : props.emptyStateText
2779
2728
  }
2780
- emptyStateAddon={
2781
- tabId === activeThreadId &&
2782
- tabScope &&
2783
- otherScopedThreads.length > 0 ? (
2784
- <PreviousScopedChatsHint
2785
- scope={tabScope}
2786
- threads={otherScopedThreads}
2787
- onSelectThread={openFromHistory}
2788
- />
2789
- ) : undefined
2790
- }
2791
2729
  ref={(handle) => {
2792
2730
  if (handle) {
2793
2731
  chatRefs.current.set(tabId, handle);
@@ -596,6 +596,11 @@ const FRIENDLY_MODEL_NAMES: Record<string, string> = {
596
596
  "deepseek-v3-1": "DeepSeek v3.1",
597
597
  };
598
598
 
599
+ export const MODEL_SELECTOR_POPOVER_STYLE = {
600
+ fontSize: 13,
601
+ maxHeight: "min(500px, var(--radix-popover-content-available-height, 500px))",
602
+ } satisfies React.CSSProperties;
603
+
599
604
  function friendlyModelName(model: string): string {
600
605
  if (FRIENDLY_MODEL_NAMES[model]) return FRIENDLY_MODEL_NAMES[model];
601
606
  // Claude: claude-{tier}-{major}-{minor}[-dateYYYYMMDD] → Tier Major.Minor
@@ -835,19 +840,7 @@ function ModelSelector({
835
840
  collisionPadding={8}
836
841
  data-agent-native-composer-popover="true"
837
842
  className="z-[260] box-border w-72 overflow-y-auto rounded-lg border-border p-0 py-1 shadow-lg"
838
- style={
839
- providerGroups.length > 0
840
- ? {
841
- fontSize: 13,
842
- height:
843
- "min(500px, var(--radix-popover-content-available-height, 500px))",
844
- }
845
- : {
846
- fontSize: 13,
847
- maxHeight:
848
- "min(500px, var(--radix-popover-content-available-height, 500px))",
849
- }
850
- }
843
+ style={MODEL_SELECTOR_POPOVER_STYLE}
851
844
  >
852
845
  {showBuilderCta && (
853
846
  <>
@@ -7877,6 +7877,32 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
7877
7877
  (event as any).context = (event as any).context ?? {};
7878
7878
  (event as any).context.__agentChatBackgroundBody = prepared.body;
7879
7879
 
7880
+ // DURABLE OWNER CONTEXT: this self-dispatch is cookieless (HMAC-only —
7881
+ // the foreground POST carried the user's session, but this background-
7882
+ // function invocation does not). Without a session, resolveOwnerContext()
7883
+ // falls through to its 401 "Unauthenticated" throw and the worker dies
7884
+ // before it can even claim the run (observed in prod as the `route_threw`
7885
+ // diag stage — every durable run only completed via the foreground
7886
+ // circuit-breaker's inline recovery). The run's chat thread already
7887
+ // records the authenticated owner (written by the foreground under the
7888
+ // signed-in user), so resolve the owner from the run row — NOT from the
7889
+ // request body, which the HMAC does not cover and a caller could forge —
7890
+ // and pre-seed the owner context the handler reads (resolveOwnerContext
7891
+ // returns it before attempting any session lookup).
7892
+ try {
7893
+ const { getRunOwnerEmail } = await import("../agent/run-store.js");
7894
+ const backgroundOwner = await getRunOwnerEmail(prepared.runId);
7895
+ if (backgroundOwner) {
7896
+ (event as any).context[OWNER_CONTEXT_KEY] = {
7897
+ owner: backgroundOwner,
7898
+ anonymous: false,
7899
+ };
7900
+ }
7901
+ } catch {
7902
+ // Best-effort: if the lookup fails, resolveOwnerContext runs as before
7903
+ // and throws its normal 401, surfaced via the route_threw diagnostic.
7904
+ }
7905
+
7880
7906
  try {
7881
7907
  return await invokeAgentChatHandler(event);
7882
7908
  } catch (err: any) {