@agent-native/core 0.0.0-beta-20260820224008 → 0.0.0-beta-20260820233158

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 (49) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/brain/actions/list-sources.ts +17 -7
  3. package/corpus/templates/brain/app/components/brain/Surface.tsx +4 -2
  4. package/corpus/templates/brain/app/lib/brain.ts +13 -3
  5. package/corpus/templates/brain/server/lib/brain-health.ts +5 -8
  6. package/corpus/templates/brain/server/lib/brain.ts +1 -0
  7. package/corpus/templates/brain/server/lib/capture-sanitization.ts +5 -0
  8. package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +3 -2
  9. package/corpus/templates/clips/desktop/src/app.tsx +11 -10
  10. package/corpus/templates/clips/desktop/src/lib/desktop-oauth-window.ts +23 -0
  11. package/dist/client/AboutAgentNativeDialog.d.ts +5 -0
  12. package/dist/client/AboutAgentNativeDialog.js +56 -0
  13. package/dist/client/CommandMenu.d.ts +6 -1
  14. package/dist/client/CommandMenu.js +46 -3
  15. package/dist/client/EnvironmentBadge.js +4 -1
  16. package/dist/client/agent-native-version.d.ts +12 -0
  17. package/dist/client/agent-native-version.js +36 -0
  18. package/dist/collab/awareness.d.ts +2 -2
  19. package/dist/collab/struct-routes.d.ts +1 -1
  20. package/dist/config.d.ts +3 -1
  21. package/dist/config.js +14 -9
  22. package/dist/localization/core-messages/ar-SA.js +6 -0
  23. package/dist/localization/core-messages/de-DE.js +6 -0
  24. package/dist/localization/core-messages/en-US.d.ts +6 -0
  25. package/dist/localization/core-messages/en-US.js +6 -0
  26. package/dist/localization/core-messages/es-ES.js +6 -0
  27. package/dist/localization/core-messages/fr-FR.js +6 -0
  28. package/dist/localization/core-messages/hi-IN.js +6 -0
  29. package/dist/localization/core-messages/ja-JP.js +6 -0
  30. package/dist/localization/core-messages/ko-KR.js +6 -0
  31. package/dist/localization/core-messages/pt-BR.js +6 -0
  32. package/dist/localization/core-messages/zh-CN.js +6 -0
  33. package/dist/localization/core-messages/zh-TW.js +6 -0
  34. package/dist/localization/core-messages.d.ts +6 -0
  35. package/dist/notifications/routes.d.ts +3 -3
  36. package/dist/observability/routes.d.ts +5 -5
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  39. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  40. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  41. package/dist/server/realtime-token.d.ts +1 -1
  42. package/dist/server/transcribe-voice.d.ts +1 -1
  43. package/dist/vite/agent-native-config-loader.js +1 -1
  44. package/dist/vite/client.js +9 -8
  45. package/dist/vite/package-versions.d.ts +1 -0
  46. package/dist/vite/package-versions.js +97 -0
  47. package/docs/content/agent-native-config.mdx +55 -31
  48. package/docs/content/environment-variables.mdx +51 -14
  49. 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: 8490
34
+ - template files: 8491
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  import { getDb, schema } from "../server/db/index.js";
7
7
  import { nextBrainSourceSyncAt } from "../server/jobs/sync-sources.js";
8
8
  import { listAccessibleAudienceIds } from "../server/lib/audiences.js";
9
+ import { sourceHealthState } from "../server/lib/brain-health.js";
9
10
  import { parseJson, serializeSource } from "../server/lib/brain.js";
10
11
  import { sourceProviderSchema } from "./_schemas.js";
11
12
 
@@ -71,14 +72,23 @@ export default defineAction({
71
72
  .from(schema.brainSources)
72
73
  .where(and(...clauses))
73
74
  .orderBy(desc(schema.brainSources.updatedAt));
75
+ const nowMs = Date.now();
74
76
  const sources = await Promise.all(
75
- rows.map(async (row) => ({
76
- ...serializeSource(row),
77
- recordCount: await sourceRecordCount(row.id),
78
- latestRun: await latestRun(row.id),
79
- nextSyncAt: nextBrainSourceSyncAt(row),
80
- })),
77
+ rows.map(async (row) => {
78
+ const latest = await latestRun(row.id);
79
+ const nextSyncAt = nextBrainSourceSyncAt(row);
80
+ return {
81
+ ...serializeSource(row),
82
+ recordCount: await sourceRecordCount(row.id),
83
+ latestRun: latest,
84
+ nextSyncAt,
85
+ health: sourceHealthState(row, latest, nextSyncAt, nowMs),
86
+ };
87
+ }),
81
88
  );
82
- return { count: rows.length, sources };
89
+ return {
90
+ count: rows.length,
91
+ sources,
92
+ };
83
93
  },
84
94
  });
@@ -117,7 +117,8 @@ export function StatusBadge({ status }: { status: string }) {
117
117
  ? IconCircleCheck
118
118
  : normalized.includes("review") ||
119
119
  normalized.includes("degraded") ||
120
- normalized.includes("stale")
120
+ normalized.includes("stale") ||
121
+ normalized.includes("needs")
121
122
  ? IconClock
122
123
  : normalized.includes("error")
123
124
  ? IconAlertTriangle
@@ -132,7 +133,8 @@ export function StatusBadge({ status }: { status: string }) {
132
133
  "border-border bg-secondary text-secondary-foreground",
133
134
  (normalized.includes("review") ||
134
135
  normalized.includes("degraded") ||
135
- normalized.includes("stale")) &&
136
+ normalized.includes("stale") ||
137
+ normalized.includes("needs")) &&
136
138
  "border-amber-500/35 bg-amber-500/10 text-amber-700 dark:text-amber-300",
137
139
  normalized.includes("error") &&
138
140
  "border-destructive/35 bg-destructive/10 text-destructive",
@@ -20,7 +20,14 @@ export type BrainView =
20
20
  | "settings";
21
21
 
22
22
  export type KnowledgeStatus = "approved" | "needs_review" | "draft" | "stale";
23
- export type SourceHealth = "healthy" | "degraded" | "paused" | "error";
23
+ export type SourceHealth =
24
+ | "healthy"
25
+ | "needs_setup"
26
+ | "needs_sync"
27
+ | "stale"
28
+ | "degraded"
29
+ | "paused"
30
+ | "error";
24
31
  export type ReviewPriority = "high" | "medium" | "low";
25
32
 
26
33
  export interface Citation {
@@ -1144,8 +1151,11 @@ export function sourceDescription(source: BrainSource) {
1144
1151
  export function sourceHealth(source: BrainSource): SourceHealth {
1145
1152
  if (source.health) return source.health;
1146
1153
  if (sourceRetryAfter(source)) return "degraded";
1147
- if (source.status === "active")
1148
- return source.lastError ? "degraded" : "healthy";
1154
+ if (source.status === "active") {
1155
+ if (source.lastError) return "degraded";
1156
+ if (sourceAutoSync(source) && !source.lastSyncedAt) return "needs_sync";
1157
+ return "healthy";
1158
+ }
1149
1159
  if (source.status === "error") return "error";
1150
1160
  if (source.status === "paused" || source.status === "archived")
1151
1161
  return "paused";
@@ -189,9 +189,9 @@ function latestIso(values: Array<string | null | undefined>) {
189
189
  return latest;
190
190
  }
191
191
 
192
- function sourceHealthState(
192
+ export function sourceHealthState(
193
193
  source: SourceRow,
194
- latestRun: SyncRunRow | null,
194
+ latestRun: Pick<SyncRunRow, "status"> | null,
195
195
  nextSyncAt: string | null,
196
196
  nowMs: number,
197
197
  ): BrainSourceHealthState {
@@ -530,12 +530,9 @@ export async function readBrainHealth() {
530
530
  };
531
531
  });
532
532
 
533
- const sourceHealthCounts = countStatuses<
534
- BrainSourceHealthState | "active" | "paused" | "error"
535
- >([
536
- ...sourceSummaries.map((source) => ({ status: source.health })),
537
- ...sourceRows.map((source) => ({ status: source.status })),
538
- ]);
533
+ const sourceHealthCounts = countStatuses<BrainSourceHealthState>(
534
+ sourceSummaries.map((source) => ({ status: source.health })),
535
+ );
539
536
  const captureCounts = countStatuses(captureRows);
540
537
  const captureDispositionCounts = countStatuses(
541
538
  captureRows.map((capture) => ({
@@ -755,6 +755,7 @@ export async function createCapture(values: {
755
755
  title: source.title,
756
756
  provider: source.provider as BrainSourceProvider,
757
757
  ownerEmail: source.ownerEmail,
758
+ orgId: source.orgId,
758
759
  },
759
760
  sourceConfig: parseJson<Record<string, unknown>>(source.configJson, {}),
760
761
  settings,
@@ -71,6 +71,7 @@ export interface CaptureSanitizationInput {
71
71
  title: string;
72
72
  provider: BrainSourceProvider;
73
73
  ownerEmail: string;
74
+ orgId?: string | null;
74
75
  };
75
76
  sourceConfig?: Record<string, unknown>;
76
77
  settings: BrainSettings;
@@ -440,6 +441,10 @@ async function classifyWithApprovedModel(
440
441
  apiKeyEnvVar: userApiKey.apiKeyEnvVar,
441
442
  appId: "brain",
442
443
  engineOption: engineName,
444
+ credentialIdentity: {
445
+ userEmail: input.source.ownerEmail,
446
+ orgId: input.source.orgId ?? undefined,
447
+ },
443
448
  });
444
449
 
445
450
  const maxChars = Math.min(
@@ -10,7 +10,8 @@ declare global {
10
10
  var __AGENT_NATIVE_BRAIN_SCHEDULED_RUNTIME__: boolean | undefined;
11
11
  }
12
12
 
13
- export const BRAIN_QUEUE_SWEEP_LIMIT = 10;
13
+ export const BRAIN_QUEUE_SWEEP_LIMIT = 25;
14
+ export const BRAIN_MAX_DISTILLATIONS_PER_SWEEP = 2;
14
15
 
15
16
  function productionLike() {
16
17
  return (
@@ -53,7 +54,7 @@ export default defineEventHandler(async (event) => {
53
54
  processBrainIngestQueueOnce({
54
55
  limit: BRAIN_QUEUE_SWEEP_LIMIT,
55
56
  runDistillation: true,
56
- maxDistillations: 1,
57
+ maxDistillations: BRAIN_MAX_DISTILLATIONS_PER_SWEEP,
57
58
  }),
58
59
  expireSensitivityQuarantines(),
59
60
  ]);
@@ -67,6 +67,7 @@ import {
67
67
  startBubbleWebrtc,
68
68
  type BubbleWebrtcHandle,
69
69
  } from "./lib/bubble-webrtc";
70
+ import { openBoundOAuthWindow } from "./lib/desktop-oauth-window";
70
71
  import {
71
72
  getCameraStreamWithFallback,
72
73
  isMediaConstraintFailure,
@@ -2126,15 +2127,17 @@ export function App() {
2126
2127
  void tick();
2127
2128
  }
2128
2129
 
2129
- // Google verification stays in the bound Tauri WebView so the callback sees
2130
- // the same browser-binding cookie. Magic-link verification still completes
2131
- // in the system browser through its own exchange flow.
2130
+ // Google verification stays in a child of the bound Tauri WebView so the
2131
+ // callback sees the same browser-binding cookie. Magic-link verification
2132
+ // still completes in the system browser through its own exchange flow.
2132
2133
  async function signInExternal() {
2133
2134
  if (signInInflightRef.current) return;
2134
2135
  signInInflightRef.current = true;
2136
+ let oauthWindow: ReturnType<typeof openBoundOAuthWindow> | null = null;
2135
2137
 
2136
2138
  try {
2137
2139
  setSignInError(null);
2140
+ oauthWindow = openBoundOAuthWindow();
2138
2141
  const flowId = crypto.randomUUID?.() ?? null;
2139
2142
  const verifier = (() => {
2140
2143
  const randomUuid = crypto.randomUUID;
@@ -2163,7 +2166,6 @@ export function App() {
2163
2166
  const authParams = new URLSearchParams({
2164
2167
  desktop: "1",
2165
2168
  flow_id: flowId,
2166
- webview: "1",
2167
2169
  });
2168
2170
  const authResponse = await fetch(
2169
2171
  `${base}/_agent-native/google/auth-url?${authParams.toString()}`,
@@ -2194,13 +2196,12 @@ export function App() {
2194
2196
  : "Could not start Google sign-in.";
2195
2197
  throw new Error(message);
2196
2198
  }
2199
+ oauthWindow.location.href = authPayload.url;
2197
2200
  setSignInPending("google");
2198
- // Stay in this exact WebView: the auth bootstrap set the HttpOnly
2199
- // browser-binding cookie here, and the callback returns this page with
2200
- // the staged session cookie after Google completes.
2201
- window.location.href = authPayload.url;
2201
+ startDesktopAuthExchange(flowId, "google", verifier);
2202
2202
  } catch (err) {
2203
2203
  console.error("[clips-tray] signInExternal failed:", err);
2204
+ oauthWindow?.close();
2204
2205
  signInInflightRef.current = false;
2205
2206
  setSignInPending(null);
2206
2207
  setSignInError(
@@ -3962,8 +3963,8 @@ export function App() {
3962
3963
  // (not a separate Tauri window). This avoids Tauri 2's separate-WebKit-
3963
3964
  // data-store-per-WebviewWindow cookie-jar issue — the cookie is set in
3964
3965
  // the same webview that reads it on the next /auth/session poll.
3965
- // Google verification uses the bound WebView, while magic-link verification
3966
- // uses the system browser and password stays inline here.
3966
+ // Google verification uses a popup in the bound WebView, while magic-link
3967
+ // verification uses the system browser and password stays inline here.
3967
3968
  if (authStatus === "anon") {
3968
3969
  return (
3969
3970
  <div className="app" ref={appRef}>
@@ -0,0 +1,23 @@
1
+ type OAuthWindow = Pick<Window, "close"> & {
2
+ location: Pick<Location, "href">;
3
+ };
4
+
5
+ type OpenOAuthWindow = (url: string, target: string) => OAuthWindow | null;
6
+
7
+ /**
8
+ * Open a same-WebView OAuth child before awaiting the auth URL.
9
+ *
10
+ * The parent keeps polling the exchange while the child completes Google
11
+ * OAuth. Using window.open keeps both pages in the Tauri WebView cookie
12
+ * partition; the system-browser shell opener would lose the binding cookie.
13
+ */
14
+ export function openBoundOAuthWindow(
15
+ openWindow: OpenOAuthWindow = (url, target) =>
16
+ window.open(url, target) as OAuthWindow | null,
17
+ ): OAuthWindow {
18
+ const oauthWindow = openWindow("about:blank", "_blank");
19
+ if (!oauthWindow) {
20
+ throw new Error("Could not open the Google sign-in window.");
21
+ }
22
+ return oauthWindow;
23
+ }
@@ -0,0 +1,5 @@
1
+ export interface AboutAgentNativeDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ }
5
+ export declare function AboutAgentNativeDialog({ open, onOpenChange, }: AboutAgentNativeDialogProps): import("react").JSX.Element;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@agent-native/toolkit/ui/button";
3
+ import { IconCheck, IconCode, IconCopy, IconX } from "@tabler/icons-react";
4
+ import { useEffect, useMemo, useRef, useState } from "react";
5
+ import { formatAgentNativeDiagnostics, getAgentNativeDiagnostics, getAgentNativePackageVersions, } from "./agent-native-version.js";
6
+ import { writeClipboardText } from "./clipboard.js";
7
+ import { Dialog, DialogContent, DialogTitle } from "./components/ui/dialog.js";
8
+ import { useT } from "./i18n.js";
9
+ export function AboutAgentNativeDialog({ open, onOpenChange, }) {
10
+ const t = useT();
11
+ const [copyState, setCopyState] = useState("idle");
12
+ const copyResetTimer = useRef(null);
13
+ const packageVersions = getAgentNativePackageVersions();
14
+ const diagnostics = useMemo(() => formatAgentNativeDiagnostics(), []);
15
+ const { buildId, environment } = getAgentNativeDiagnostics();
16
+ useEffect(() => {
17
+ if (!open) {
18
+ setCopyState("idle");
19
+ }
20
+ }, [open]);
21
+ useEffect(() => () => {
22
+ if (copyResetTimer.current !== null) {
23
+ window.clearTimeout(copyResetTimer.current);
24
+ }
25
+ }, []);
26
+ const handleCopy = async () => {
27
+ const didCopy = await writeClipboardText(diagnostics);
28
+ setCopyState(didCopy ? "copied" : "failed");
29
+ if (copyResetTimer.current !== null) {
30
+ window.clearTimeout(copyResetTimer.current);
31
+ }
32
+ copyResetTimer.current = window.setTimeout(() => {
33
+ setCopyState("idle");
34
+ copyResetTimer.current = null;
35
+ }, 1600);
36
+ };
37
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { "aria-describedby": undefined, className: "max-w-md gap-5", children: [_jsxs(DialogTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(IconCode, { className: "size-4 text-muted-foreground" }), t("agentChat.aboutAgentNative.title", {
38
+ defaultValue: "About Agent Native",
39
+ })] }), _jsx("div", { className: "overflow-hidden rounded-md border border-border", children: packageVersions.length > 0 ? (packageVersions.map(({ name, version }) => (_jsxs("div", { className: "flex items-center justify-between gap-4 border-b border-border px-3 py-2 last:border-b-0", children: [_jsx("span", { className: "min-w-0 truncate font-mono text-xs text-foreground", children: name }), _jsxs("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: [_jsx("span", { className: "sr-only", children: t("agentChat.aboutAgentNative.version", {
40
+ defaultValue: "Version",
41
+ }) }), version] })] }, name)))) : (_jsxs("div", { className: "px-3 py-2 font-mono text-xs text-muted-foreground", children: ["@agent-native/core -", " ", t("agentChat.aboutAgentNative.unknown", {
42
+ defaultValue: "Unknown",
43
+ })] })) }), _jsxs("dl", { className: "grid gap-2 text-xs", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("dt", { className: "text-muted-foreground", children: t("agentChat.aboutAgentNative.environment", {
44
+ defaultValue: "Environment",
45
+ }) }), _jsx("dd", { className: "font-mono text-foreground", children: environment })] }), _jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("dt", { className: "text-muted-foreground", children: t("agentChat.aboutAgentNative.build", {
46
+ defaultValue: "Build",
47
+ }) }), _jsx("dd", { className: "max-w-[16rem] truncate font-mono text-foreground", children: buildId })] })] }), _jsxs(Button, { className: "w-full", onClick: () => void handleCopy(), type: "button", variant: "outline", children: [copyState === "copied" ? (_jsx(IconCheck, {})) : copyState === "failed" ? (_jsx(IconX, {})) : (_jsx(IconCopy, {})), copyState === "copied"
48
+ ? t("agentChat.common.copied", { defaultValue: "Copied" })
49
+ : copyState === "failed"
50
+ ? t("agentChat.recovery.copyFailed", {
51
+ defaultValue: "Copy failed",
52
+ })
53
+ : t("agentChat.aboutAgentNative.copyDiagnostics", {
54
+ defaultValue: "Copy diagnostics",
55
+ })] })] }) }));
56
+ }
@@ -89,8 +89,13 @@ export interface CommandMenuProps {
89
89
  * multiple apps share an origin.
90
90
  */
91
91
  changelogKey?: string;
92
+ /**
93
+ * Whether to show the built-in "About Agent Native" entry. Defaults to true
94
+ * for app-shell menus that provide a changelog; set false for local menus.
95
+ */
96
+ showAbout?: boolean;
92
97
  }
93
- export declare function CommandMenu({ open, onOpenChange, children, renderResults, placeholder, emptyText: _emptyText, showAgentFallback, className, changelog, changelogLabel, changelogKey, }: CommandMenuProps): React.JSX.Element;
98
+ export declare function CommandMenu({ open, onOpenChange, children, renderResults, placeholder, emptyText: _emptyText, showAgentFallback, className, changelog, changelogLabel, changelogKey, showAbout: showAboutProp, }: CommandMenuProps): React.JSX.Element;
94
99
  export declare namespace CommandMenu {
95
100
  export { CommandGroup as Group };
96
101
  export { CommandItem as Item };
@@ -16,12 +16,14 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
16
16
  * </CommandMenu>
17
17
  */
18
18
  import { Command as CommandPrimitive, CommandGroup as CommandGroupPrimitive, CommandInput as CommandInputPrimitive, CommandItem as CommandItemPrimitive, CommandList as CommandListPrimitive, CommandSeparator as CommandSeparatorPrimitive, CommandShortcut as CommandShortcutPrimitive, } from "@agent-native/toolkit/ui/command";
19
- import { IconBook2, IconExternalLink, IconMessage, IconHistory, } from "@tabler/icons-react";
19
+ import { IconBook2, IconExternalLink, IconInfoCircle, IconMessage, IconHistory, } from "@tabler/icons-react";
20
20
  import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
21
21
  import { parseChangelog } from "../changelog/parse.js";
22
+ import { AboutAgentNativeDialog } from "./AboutAgentNativeDialog.js";
22
23
  import { sendToAgentChat } from "./agent-chat.js";
23
24
  import { ChangelogDialog, useChangelogSeen } from "./changelog/Changelog.js";
24
25
  import { Dialog, DialogContent, DialogTitle } from "./components/ui/dialog.js";
26
+ import { useT } from "./i18n.js";
25
27
  import { cn } from "./utils.js";
26
28
  const CommandMenuContext = createContext(null);
27
29
  function useCommandMenuContext() {
@@ -122,15 +124,18 @@ function CommandDocsGroup({ docs, heading = "Docs" }) {
122
124
  return null;
123
125
  return (_jsx(CommandGroup, { heading: heading, children: docs.map((doc) => (_jsxs(CommandItem, { onSelect: () => openDocsHref(doc.href), keywords: [doc.title, doc.description ?? "", ...(doc.keywords ?? [])], deferSelect: false, className: "items-start py-2", children: [_jsx(IconBook2, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: doc.title }), doc.description ? (_jsx("span", { className: "mt-0.5 block line-clamp-2 text-xs leading-snug text-muted-foreground", children: doc.description })) : null] }), _jsx(IconExternalLink, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" })] }, doc.href))) }));
124
126
  }
125
- export function CommandMenu({ open, onOpenChange, children, renderResults, placeholder = "Type a command or ask AI...", emptyText: _emptyText = "No commands found.", showAgentFallback = true, className, changelog, changelogLabel = "What's new", changelogKey, }) {
127
+ export function CommandMenu({ open, onOpenChange, children, renderResults, placeholder = "Type a command or ask AI...", emptyText: _emptyText = "No commands found.", showAgentFallback = true, className, changelog, changelogLabel = "What's new", changelogKey, showAbout: showAboutProp, }) {
126
128
  const [search, setSearch] = useState("");
127
129
  const inputRef = useRef(null);
128
130
  const containerRef = useRef(null);
131
+ const t = useT();
129
132
  // Built-in "What's new" changelog surface (only active when `changelog` is
130
133
  // passed). The dialog is rendered alongside the menu so it survives the menu
131
134
  // closing; the unseen dot persists per browser via localStorage.
132
135
  const [changelogOpen, setChangelogOpen] = useState(false);
136
+ const [aboutOpen, setAboutOpen] = useState(false);
133
137
  const hasChangelog = typeof changelog === "string" && changelog.trim().length > 0;
138
+ const showAbout = showAboutProp ?? hasChangelog;
134
139
  const changelogEntries = useMemo(() => (hasChangelog ? parseChangelog(changelog) : []), [hasChangelog, changelog]);
135
140
  const latestChangelogId = changelogEntries[0]?.id;
136
141
  const { unseen: changelogUnseen, markSeen: markChangelogSeen } = useChangelogSeen(changelogKey ?? "app", latestChangelogId);
@@ -140,6 +145,11 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
140
145
  // Let the menu close before the dialog opens (avoids overlay flicker).
141
146
  setTimeout(() => setChangelogOpen(true), 50);
142
147
  }, [onOpenChange, markChangelogSeen]);
148
+ const openAbout = useCallback(() => {
149
+ onOpenChange(false);
150
+ // Let the menu close before the dialog opens (avoids overlay flicker).
151
+ setTimeout(() => setAboutOpen(true), 50);
152
+ }, [onOpenChange]);
143
153
  // Focus input when opening; clear search while closed so reopen never renders
144
154
  // dynamic results for the previous query.
145
155
  useEffect(() => {
@@ -178,6 +188,36 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
178
188
  .toLowerCase()
179
189
  .includes(search.toLowerCase());
180
190
  const showChangelogRow = hasChangelog && changelogRowMatches;
191
+ const aboutLabel = t("agentChat.aboutAgentNative.title", {
192
+ defaultValue: "About Agent Native",
193
+ });
194
+ const aboutVersionLabel = t("agentChat.aboutAgentNative.version", {
195
+ defaultValue: "Version",
196
+ });
197
+ const aboutEnvironmentLabel = t("agentChat.aboutAgentNative.environment", {
198
+ defaultValue: "Environment",
199
+ });
200
+ const aboutBuildLabel = t("agentChat.aboutAgentNative.build", {
201
+ defaultValue: "Build",
202
+ });
203
+ const aboutDiagnosticsLabel = t("agentChat.aboutAgentNative.copyDiagnostics", {
204
+ defaultValue: "Copy diagnostics",
205
+ });
206
+ const aboutRowMatches = !search ||
207
+ [
208
+ aboutLabel,
209
+ "agent native",
210
+ aboutVersionLabel,
211
+ "versions",
212
+ "package",
213
+ aboutEnvironmentLabel,
214
+ aboutBuildLabel,
215
+ aboutDiagnosticsLabel,
216
+ ]
217
+ .join(" ")
218
+ .toLowerCase()
219
+ .includes(search.toLowerCase());
220
+ const showAboutRow = showAbout && aboutRowMatches;
181
221
  // Filter children based on search
182
222
  const filterChildren = (nodes) => {
183
223
  return React.Children.map(nodes, (child) => {
@@ -242,7 +282,10 @@ export function CommandMenu({ open, onOpenChange, children, renderResults, place
242
282
  }, className: cn("fixed left-1/2 top-[15vh] !z-50 !max-h-none -translate-x-1/2 !translate-y-0 !gap-0 w-full max-w-lg", "rounded-lg border border-border bg-popover p-0 text-popover-foreground shadow-lg", className), style: {
243
283
  animation: "none",
244
284
  transition: "none",
245
- }, children: [_jsx(DialogTitle, { className: "sr-only", children: placeholder }), _jsx(CommandPrimitive, { loop: true, shouldFilter: false, className: "h-auto rounded-lg", children: _jsxs(CommandMenuContext.Provider, { value: { search, onOpenChange }, children: [_jsx(CommandInputPrimitive, { ref: inputRef, value: search, onValueChange: setSearch, placeholder: placeholder }), _jsxs(CommandListPrimitive, { children: [dynamicResults, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })), showAgentFallback && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow || hasDynamicResults) && (_jsx(CommandSeparator, {})), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: handleSubmitToAgent, children: [_jsx(IconMessage, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: search.trim() ? (_jsxs(_Fragment, { children: ["Ask AI:", " ", _jsxs("span", { className: "text-muted-foreground", children: ["\"", search, "\""] })] })) : (_jsx("span", { className: "text-muted-foreground", children: "Ask AI anything..." })) }), search.trim() && (_jsx("span", { className: "ms-auto text-xs text-muted-foreground", children: "\u21B5" }))] }) })] }))] })] }) })] }) }), hasChangelog && (_jsx(ChangelogDialog, { open: changelogOpen, onOpenChange: setChangelogOpen, markdown: changelog, title: changelogLabel }))] }));
285
+ }, children: [_jsx(DialogTitle, { className: "sr-only", children: placeholder }), _jsx(CommandPrimitive, { loop: true, shouldFilter: false, className: "h-auto rounded-lg", children: _jsxs(CommandMenuContext.Provider, { value: { search, onOpenChange }, children: [_jsx(CommandInputPrimitive, { ref: inputRef, value: search, onValueChange: setSearch, placeholder: placeholder }), _jsxs(CommandListPrimitive, { children: [dynamicResults, hasResults && filteredChildren, showChangelogRow && (_jsxs(_Fragment, { children: [hasResults && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openChangelog, children: [_jsx(IconHistory, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: changelogLabel }), changelogUnseen && (_jsx("span", { className: "ms-auto h-2 w-2 rounded-full bg-primary", "aria-label": "New updates available" }))] }) })] })), showAboutRow && (_jsxs(_Fragment, { children: [(hasResults || showChangelogRow) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: openAbout, children: [_jsx(IconInfoCircle, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: aboutLabel })] }) })] })), showAgentFallback && (_jsxs(_Fragment, { children: [(hasResults ||
286
+ showChangelogRow ||
287
+ showAboutRow ||
288
+ hasDynamicResults) && _jsx(CommandSeparator, {}), _jsx("div", { className: "p-1", children: _jsxs(CommandItemPrimitive, { className: "cursor-pointer gap-2 py-2", onSelect: handleSubmitToAgent, children: [_jsx(IconMessage, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: search.trim() ? (_jsxs(_Fragment, { children: ["Ask AI:", " ", _jsxs("span", { className: "text-muted-foreground", children: ["\"", search, "\""] })] })) : (_jsx("span", { className: "text-muted-foreground", children: "Ask AI anything..." })) }), search.trim() && (_jsx("span", { className: "ms-auto text-xs text-muted-foreground", children: "\u21B5" }))] }) })] }))] })] }) })] }) }), hasChangelog && (_jsx(ChangelogDialog, { open: changelogOpen, onOpenChange: setChangelogOpen, markdown: changelog, title: changelogLabel })), showAbout && (_jsx(AboutAgentNativeDialog, { open: aboutOpen, onOpenChange: setAboutOpen }))] }));
246
289
  }
247
290
  // Helper to extract text content from React children
248
291
  function getTextContent(children) {
@@ -6,6 +6,7 @@ import { BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOpt
6
6
  import { trackEvent } from "./analytics.js";
7
7
  import { injectedAgentNativeConfig } from "./app-config.js";
8
8
  import { useSession } from "./use-session.js";
9
+ import { cn } from "./utils.js";
9
10
  export { BETA_OPT_OUT_DURATION_MS, BETA_OPT_OUT_QUERY_PARAM, BETA_OPT_OUT_STORAGE_KEY, buildEnvironmentOptOutUrl, buildEnvironmentUrl, resolveEnvironmentTargets, } from "../shared/environment-lanes.js";
10
11
  export function isBuilderIoEmployee(email) {
11
12
  return email?.trim().toLowerCase().endsWith("@builder.io") ?? false;
@@ -88,7 +89,9 @@ function EnvironmentBadgeContent({ environment, targets, }) {
88
89
  const title = environment === "beta"
89
90
  ? "You're on Agent Native Beta"
90
91
  : "You're on Agent Native Production";
91
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { "aria-label": `Open ${title.toLowerCase()} switcher`, className: "fixed bottom-3 right-3 z-[100] h-6 min-w-0 rounded-xl border-border/80 bg-background/95 px-2 text-[11px] font-semibold uppercase tracking-[0.5px] shadow-sm backdrop-blur-sm", size: "sm", variant: environment === "beta" ? "default" : "outline", children: label }) }), _jsxs(PopoverContent, { align: "center", className: "w-[280px] p-5", side: "top", sideOffset: 8, children: [_jsx("div", { className: "mb-1 text-sm font-semibold leading-5", children: title }), _jsx("div", { className: "mb-4 text-sm text-muted-foreground", children: "Choose where you want to continue." }), _jsx("div", { className: "grid gap-2", children: environment === "beta" ? (_jsx(EnvironmentLink, { href: productionHref, label: "Switch to production" })) : (_jsx(EnvironmentLink, { href: betaHref, label: "Go to beta" })) })] })] }));
92
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { "aria-label": `Open ${title.toLowerCase()} switcher`, className: cn("fixed bottom-3 right-3 z-[100] h-6 min-w-0 rounded-xl px-2 text-[11px] font-semibold uppercase tracking-[0.5px] shadow-sm backdrop-blur-sm", environment === "beta"
93
+ ? "border-primary/80"
94
+ : "border-border/80 bg-background/95 text-foreground"), size: "sm", variant: environment === "beta" ? "default" : "outline", children: label }) }), _jsxs(PopoverContent, { align: "center", className: "w-[280px] p-5", side: "top", sideOffset: 8, children: [_jsx("div", { className: "mb-1 text-sm font-semibold leading-5", children: title }), _jsx("div", { className: "mb-4 text-sm text-muted-foreground", children: "Choose where you want to continue." }), _jsx("div", { className: "grid gap-2", children: environment === "beta" ? (_jsx(EnvironmentLink, { href: productionHref, label: "Switch to production" })) : (_jsx(EnvironmentLink, { href: betaHref, label: "Go to beta" })) })] })] }));
92
95
  }
93
96
  function ProductionEnvironmentBadge({ targets, }) {
94
97
  const { session, status } = useSession();
@@ -0,0 +1,12 @@
1
+ export interface AgentNativePackageVersion {
2
+ name: string;
3
+ version: string;
4
+ }
5
+ export interface AgentNativeDiagnostics {
6
+ buildId: string;
7
+ environment: string;
8
+ packages: Record<string, string>;
9
+ }
10
+ export declare function getAgentNativePackageVersions(): AgentNativePackageVersion[];
11
+ export declare function getAgentNativeDiagnostics(): AgentNativeDiagnostics;
12
+ export declare function formatAgentNativeDiagnostics(): string;
@@ -0,0 +1,36 @@
1
+ import { injectedAgentNativeConfig } from "./app-config.js";
2
+ import { clientBuildId } from "./build-compatibility.js";
3
+ function readPackageVersions(value) {
4
+ if (!value || typeof value !== "object") {
5
+ return {};
6
+ }
7
+ return Object.fromEntries(Object.entries(value)
8
+ .filter(([name, version]) => name.startsWith("@agent-native/") &&
9
+ typeof version === "string" &&
10
+ version.trim().length > 0)
11
+ .map(([name, version]) => [name, version.trim()]));
12
+ }
13
+ function injectedPackageVersions() {
14
+ if (typeof __AGENT_NATIVE_PACKAGE_VERSIONS__ !== "undefined") {
15
+ return readPackageVersions(__AGENT_NATIVE_PACKAGE_VERSIONS__);
16
+ }
17
+ return readPackageVersions(globalThis.__AGENT_NATIVE_PACKAGE_VERSIONS__);
18
+ }
19
+ export function getAgentNativePackageVersions() {
20
+ return Object.entries(injectedPackageVersions())
21
+ .sort(([left], [right]) => left.localeCompare(right))
22
+ .map(([name, version]) => ({ name, version }));
23
+ }
24
+ export function getAgentNativeDiagnostics() {
25
+ return {
26
+ buildId: clientBuildId() || "development",
27
+ environment: injectedAgentNativeConfig().deployment?.environment ?? "unknown",
28
+ packages: Object.fromEntries(getAgentNativePackageVersions().map(({ name, version }) => [
29
+ name,
30
+ version,
31
+ ])),
32
+ };
33
+ }
34
+ export function formatAgentNativeDiagnostics() {
35
+ return JSON.stringify(getAgentNativeDiagnostics(), null, 2);
36
+ }
@@ -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
  }>>;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
package/dist/config.d.ts CHANGED
@@ -113,7 +113,9 @@ export declare function agentNativeConfigEnvName(path: readonly string[]): strin
113
113
  */
114
114
  export declare function readAgentNativeConfigEnv(env: Record<string, string | undefined>): AgentNativeConfig;
115
115
  export declare function normalizeAgentNativeConfig(input: unknown, source?: string): AgentNativeConfig;
116
- export declare function mergeAgentNativeConfigs(base: AgentNativeConfig, override: AgentNativeConfig): AgentNativeConfig;
116
+ export declare function mergeAgentNativeConfigs(base: AgentNativeConfig, override: AgentNativeConfig, options?: {
117
+ arrayStrategy?: "merge" | "replace";
118
+ }): AgentNativeConfig;
117
119
  export declare function resolveAgentNativeConfig(input: AgentNativeConfigInput | undefined, context: AgentNativeConfigContext): AgentNativeConfig;
118
120
  export declare function resolveFirstRunOnboardingMode(setting: AgentNativeFirstRunOnboardingSetting, context: AgentNativeConfigContext): AgentNativeFirstRunOnboardingMode;
119
121
  export declare function isAgentNativeDeploymentEnvironment(value: unknown): value is AgentNativeDeploymentEnvironment;
package/dist/config.js CHANGED
@@ -315,7 +315,8 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
315
315
  }
316
316
  return normalized;
317
317
  }
318
- export function mergeAgentNativeConfigs(base, override) {
318
+ export function mergeAgentNativeConfigs(base, override, options = {}) {
319
+ const arrayStrategy = options.arrayStrategy ?? "merge";
319
320
  return {
320
321
  ...(base.version === undefined && override.version === undefined
321
322
  ? {}
@@ -348,7 +349,7 @@ export function mergeAgentNativeConfigs(base, override) {
348
349
  ? {
349
350
  ...base.runtime?.environment,
350
351
  ...override.runtime?.environment,
351
- required: mergeStringLists(base.runtime?.environment?.required, override.runtime?.environment?.required),
352
+ required: mergeStringLists(base.runtime?.environment?.required, override.runtime?.environment?.required, arrayStrategy),
352
353
  }
353
354
  : undefined,
354
355
  }
@@ -390,7 +391,7 @@ export function mergeAgentNativeConfigs(base, override) {
390
391
  ...override.changelog,
391
392
  }
392
393
  : undefined,
393
- harness: mergeHarnessSettings(base.harness, override.harness),
394
+ harness: mergeHarnessSettings(base.harness, override.harness, arrayStrategy),
394
395
  };
395
396
  }
396
397
  export function resolveAgentNativeConfig(input, context) {
@@ -551,7 +552,7 @@ function normalizeHarnessConfig(value, source) {
551
552
  : { runtimes: [...new Set(runtimes)] }),
552
553
  };
553
554
  }
554
- function mergeHarnessSettings(base, override) {
555
+ function mergeHarnessSettings(base, override, arrayStrategy = "merge") {
555
556
  if (override === undefined)
556
557
  return base;
557
558
  if (typeof override === "boolean")
@@ -563,10 +564,12 @@ function mergeHarnessSettings(base, override) {
563
564
  }
564
565
  return {
565
566
  runtimes: [
566
- ...new Set([
567
- ...(base.runtimes ?? []),
568
- ...(override.runtimes ?? []),
569
- ]),
567
+ ...(arrayStrategy === "replace"
568
+ ? (override.runtimes ?? base.runtimes ?? [])
569
+ : new Set([
570
+ ...(base.runtimes ?? []),
571
+ ...(override.runtimes ?? []),
572
+ ])),
570
573
  ],
571
574
  };
572
575
  }
@@ -592,9 +595,11 @@ function normalizeRequiredEnvKeys(value, source) {
592
595
  }
593
596
  return [...new Set(keys)];
594
597
  }
595
- function mergeStringLists(base, override) {
598
+ function mergeStringLists(base, override, arrayStrategy = "merge") {
596
599
  if (base === undefined && override === undefined)
597
600
  return undefined;
601
+ if (arrayStrategy === "replace")
602
+ return override ?? base;
598
603
  return [...new Set([...(base ?? []), ...(override ?? [])])];
599
604
  }
600
605
  function isFirstRunMode(value) {