@agent-native/core 0.168.2 → 0.168.3

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.
@@ -15,11 +15,11 @@
15
15
  import { isPostgres } from "@agent-native/core/db";
16
16
  import { recordChange } from "@agent-native/core/server";
17
17
  import {
18
- getAllSettings,
19
18
  getOrgSetting,
20
19
  getUserSetting,
21
20
  deleteOrgSetting,
22
21
  deleteUserSetting,
22
+ listSettingsByPrefix,
23
23
  } from "@agent-native/core/settings";
24
24
  import {
25
25
  accessFilter,
@@ -167,6 +167,33 @@ interface AccessCtx {
167
167
  orgId: string | null;
168
168
  }
169
169
 
170
+ /**
171
+ * Legacy KV rows are only ever reachable under `o:<orgId>:` or `u:<email>:`,
172
+ * so a caller can match nothing else. Reading them with `getAllSettings()`
173
+ * pulled and JSON-parsed every tenant's settings row into the Lambda to
174
+ * string-match those two prefixes, putting the whole deployment's settings
175
+ * table on the critical path of every dashboard and analysis list read.
176
+ */
177
+ async function getScopedLegacySettings(
178
+ ctx: Pick<AccessCtx, "email" | "orgId">,
179
+ ): Promise<Record<string, Record<string, unknown>>> {
180
+ // User scope first, then org: callers append these to the SQL rows in
181
+ // iteration order and never re-sort, so the order is user-visible. The
182
+ // previous full-table read inherited whatever order the settings table
183
+ // returned, which no query pinned.
184
+ const prefixes: string[] = [];
185
+ if (ctx.email) prefixes.push(`u:${ctx.email}:`);
186
+ if (ctx.orgId) prefixes.push(`o:${ctx.orgId}:`);
187
+ if (prefixes.length === 0) return {};
188
+ const scoped: Record<string, Record<string, unknown>> = {};
189
+ for (const entries of await Promise.all(
190
+ prefixes.map((prefix) => listSettingsByPrefix(prefix)),
191
+ )) {
192
+ for (const { key, value } of entries) scoped[key] = value;
193
+ }
194
+ return scoped;
195
+ }
196
+
170
197
  const SQL_PREFIX = "sql-dashboard-";
171
198
  const EXPLORER_PREFIX = "dashboard-";
172
199
 
@@ -666,7 +693,7 @@ export async function listDashboards(
666
693
  // entirely when the caller wants archived-only or hidden-only records.
667
694
  if (archived === "archived" || hidden === "hidden") return out;
668
695
  try {
669
- const all = await getAllSettings();
696
+ const all = await getScopedLegacySettings(ctx);
670
697
  for (const [key, value] of Object.entries(all)) {
671
698
  let id: string | null = null;
672
699
  let kind: DashboardKind | null = null;
@@ -814,7 +841,7 @@ export async function listDashboardSummaries(
814
841
 
815
842
  if (archived === "archived" || hidden === "hidden") return out;
816
843
  try {
817
- const all = await getAllSettings();
844
+ const all = await getScopedLegacySettings(ctx);
818
845
  for (const [key, value] of Object.entries(all)) {
819
846
  let id: string | null = null;
820
847
  let kind: DashboardKind | null = null;
@@ -962,7 +989,7 @@ export async function searchDashboardReferences(
962
989
  const seen = new Set(
963
990
  ranked.map(({ record }) => `${record.kind}:${record.id}`),
964
991
  );
965
- const allSettings = await getAllSettings();
992
+ const allSettings = await getScopedLegacySettings(ctx);
966
993
  for (const [key, value] of Object.entries(allSettings)) {
967
994
  const scope = legacyDashboardReferenceScope(key, ctx);
968
995
  if (
@@ -2040,7 +2067,7 @@ export async function listAnalyses(
2040
2067
  // the legacy scan entirely.
2041
2068
  if (hidden === "hidden") return out;
2042
2069
  try {
2043
- const all = await getAllSettings();
2070
+ const all = await getScopedLegacySettings(ctx);
2044
2071
  for (const [key, value] of Object.entries(all)) {
2045
2072
  let id: string | null = null;
2046
2073
  let ownerEmail = ctx.email;
@@ -11,6 +11,7 @@ import {
11
11
  getOrgSetting,
12
12
  getUserSetting,
13
13
  listOrgSettings,
14
+ listSettingsByPrefix,
14
15
  putOrgSetting,
15
16
  putUserSetting,
16
17
  } from "@agent-native/core/settings";
@@ -35,11 +36,9 @@ async function listUserSettings(
35
36
  email: string,
36
37
  prefix: string,
37
38
  ): Promise<Record<string, Record<string, unknown>>> {
38
- const all = await getAllSettings();
39
39
  const scopedPrefix = `${userPrefix(email)}${prefix}`;
40
40
  const out: Record<string, Record<string, unknown>> = {};
41
- for (const [key, value] of Object.entries(all)) {
42
- if (!key.startsWith(scopedPrefix)) continue;
41
+ for (const { key, value } of await listSettingsByPrefix(scopedPrefix)) {
43
42
  out[key.slice(userPrefix(email).length)] = value;
44
43
  }
45
44
  return out;
@@ -5,6 +5,7 @@ import {
5
5
  createAgentNativeQueryClient,
6
6
  useDbSync,
7
7
  } from "@agent-native/core/client/hooks";
8
+ import { isEmbedAuthActive } from "@agent-native/core/client/host";
8
9
  import {
9
10
  getLocaleInitScript,
10
11
  type LocaleCode,
@@ -278,6 +279,7 @@ export default function Root() {
278
279
  <AppProviders
279
280
  queryClient={queryClient}
280
281
  isPublicPath={isPublicPath}
282
+ sessionBypass={isEmbedAuthActive()}
281
283
  clientOnlyFallback={<DefaultSpinner />}
282
284
  toaster={<Toaster richColors position="bottom-center" />}
283
285
  i18n={{
@@ -67,7 +67,6 @@ import {
67
67
  startBubbleWebrtc,
68
68
  type BubbleWebrtcHandle,
69
69
  } from "./lib/bubble-webrtc";
70
- import { openBoundOAuthWindow } from "./lib/desktop-oauth-window";
71
70
  import {
72
71
  getCameraStreamWithFallback,
73
72
  isMediaConstraintFailure,
@@ -2127,17 +2126,15 @@ export function App() {
2127
2126
  void tick();
2128
2127
  }
2129
2128
 
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.
2129
+ // Google verification opens in the system browser so the user's Google
2130
+ // cookies are available. The client-held verifier still gates the exchange
2131
+ // back into this Tauri app.
2133
2132
  async function signInExternal() {
2134
2133
  if (signInInflightRef.current) return;
2135
2134
  signInInflightRef.current = true;
2136
- let oauthWindow: ReturnType<typeof openBoundOAuthWindow> | null = null;
2137
2135
 
2138
2136
  try {
2139
2137
  setSignInError(null);
2140
- oauthWindow = openBoundOAuthWindow();
2141
2138
  const flowId = crypto.randomUUID?.() ?? null;
2142
2139
  const verifier = (() => {
2143
2140
  const randomUuid = crypto.randomUUID;
@@ -2196,12 +2193,11 @@ export function App() {
2196
2193
  : "Could not start Google sign-in.";
2197
2194
  throw new Error(message);
2198
2195
  }
2199
- oauthWindow.location.href = authPayload.url;
2196
+ await openExternal(authPayload.url);
2200
2197
  setSignInPending("google");
2201
2198
  startDesktopAuthExchange(flowId, "google", verifier);
2202
2199
  } catch (err) {
2203
2200
  console.error("[clips-tray] signInExternal failed:", err);
2204
- oauthWindow?.close();
2205
2201
  signInInflightRef.current = false;
2206
2202
  setSignInPending(null);
2207
2203
  setSignInError(
@@ -64,6 +64,7 @@ export default defineEventHandler(async (event: H3Event) => {
64
64
  }
65
65
  const calendarConnect =
66
66
  q.calendar === "1" || q.calendar === "true" || q.product === "calendar";
67
+ const desktopWebview = desktop && q.webview === "1" && !calendarConnect;
67
68
  let desktopVerifierHash: string | undefined;
68
69
  let desktopBrowserBindingHash: string | undefined;
69
70
  if (flowId && !calendarConnect) {
@@ -77,12 +78,20 @@ export default defineEventHandler(async (event: H3Event) => {
77
78
  return { error: "Invalid desktop exchange challenge." };
78
79
  }
79
80
  try {
80
- desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
81
- desktopVerifierHash = await registerDesktopExchange(
82
- flowId,
83
- verifier,
84
- desktopBrowserBindingHash,
85
- );
81
+ // The system-browser flow deliberately uses the user's existing
82
+ // Google cookies. The client-held verifier still gates the exchange
83
+ // token returned to the initiating Tauri app. Only an in-app WebView
84
+ // needs the additional browser-partition binding.
85
+ if (desktopWebview) {
86
+ desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
87
+ desktopVerifierHash = await registerDesktopExchange(
88
+ flowId,
89
+ verifier,
90
+ desktopBrowserBindingHash,
91
+ );
92
+ } else {
93
+ desktopVerifierHash = await registerDesktopExchange(flowId, verifier);
94
+ }
86
95
  } catch {
87
96
  setResponseStatus(event, 400);
88
97
  return { error: "Invalid desktop exchange challenge." };
@@ -116,13 +125,10 @@ export default defineEventHandler(async (event: H3Event) => {
116
125
  redirectUri,
117
126
  owner,
118
127
  desktop,
119
- desktopWebview: desktop && q.webview === "1" && !calendarConnect,
128
+ desktopWebview,
120
129
  addAccount: calendarConnect,
121
130
  app: CLIPS_GOOGLE_OAUTH_APP_ID,
122
- returnUrl:
123
- desktop && q.webview === "1" && !calendarConnect
124
- ? "/?desktop_auth=complete"
125
- : returnUrl,
131
+ returnUrl: desktopWebview ? "/?desktop_auth=complete" : returnUrl,
126
132
  flowId: calendarConnect ? undefined : flowId,
127
133
  desktopVerifierHash,
128
134
  desktopBrowserBindingHash,
@@ -35,11 +35,12 @@ async function handleGoogleSignInCallback(
35
35
  if (
36
36
  flowId &&
37
37
  (!state.desktopVerifierHash ||
38
- !state.desktopBrowserBindingHash ||
39
- !matchesDesktopOAuthBrowserBinding(
40
- event,
41
- state.desktopBrowserBindingHash,
42
- ))
38
+ (state.desktopWebview &&
39
+ (!state.desktopBrowserBindingHash ||
40
+ !matchesDesktopOAuthBrowserBinding(
41
+ event,
42
+ state.desktopBrowserBindingHash,
43
+ ))))
43
44
  ) {
44
45
  return oauthErrorPage("Desktop OAuth browser binding is invalid.");
45
46
  }
@@ -7,7 +7,10 @@ import {
7
7
  getBrowserTabId,
8
8
  useSession,
9
9
  } from "@agent-native/core/client/hooks";
10
- import { setAgentNativeApiDisabled } from "@agent-native/core/client/host";
10
+ import {
11
+ isEmbedAuthActive,
12
+ setAgentNativeApiDisabled,
13
+ } from "@agent-native/core/client/host";
11
14
  import { getLocaleInitScript, useT } from "@agent-native/core/client/i18n";
12
15
  import {
13
16
  CommandMenu,
@@ -230,6 +233,7 @@ export default function Root() {
230
233
  <AppProviders
231
234
  queryClient={queryClient}
232
235
  isPublicPath={isPublicPath}
236
+ sessionBypass={isEmbedAuthActive()}
233
237
  i18n={{ catalog: i18nCatalog, persistPreference: !isPublicPath }}
234
238
  toaster={<DesignToaster />}
235
239
  >
@@ -8,6 +8,14 @@ import { type AppConfig, type AppConfigInput } from "./schema.js";
8
8
  */
9
9
  declare const LAYER_ORDER: readonly ["env", "legacy", "app"];
10
10
  export type AppConfigLayer = (typeof LAYER_ORDER)[number];
11
+ /**
12
+ * Nitro embeds build-only deployment markers into direct env reads. Netlify's
13
+ * prebuilt beta lane has those markers while building, but does not copy them
14
+ * into the deployed Function environment. Keep the embedded values as the
15
+ * fallback for this one config boundary so a later dynamic `env[key]` read
16
+ * cannot erase the build decision from the server bundle.
17
+ */
18
+ export declare function readConfigEnvironment(embedded?: Record<string, string | undefined>): Record<string, string | undefined>;
11
19
  /**
12
20
  * Writes one layer of the ladder. Framework-internal: the deprecated setters
13
21
  * use it to keep working without reintroducing a second namespace.
@@ -48,6 +48,24 @@ function resolve(envLayer) {
48
48
  }
49
49
  return appConfigSchema.parse(merged);
50
50
  }
51
+ /**
52
+ * Nitro embeds build-only deployment markers into direct env reads. Netlify's
53
+ * prebuilt beta lane has those markers while building, but does not copy them
54
+ * into the deployed Function environment. Keep the embedded values as the
55
+ * fallback for this one config boundary so a later dynamic `env[key]` read
56
+ * cannot erase the build decision from the server bundle.
57
+ */
58
+ export function readConfigEnvironment(embedded = {
59
+ AGENT_NATIVE_RELEASE_MIGRATIONS: process.env.AGENT_NATIVE_RELEASE_MIGRATIONS,
60
+ AGENT_NATIVE_BETA_SCHEMA_OWNER: process.env.AGENT_NATIVE_BETA_SCHEMA_OWNER,
61
+ }) {
62
+ const env = { ...process.env };
63
+ for (const [key, value] of Object.entries(embedded)) {
64
+ if (!env[key] && value)
65
+ env[key] = value;
66
+ }
67
+ return env;
68
+ }
51
69
  /**
52
70
  * Writes one layer of the ladder. Framework-internal: the deprecated setters
53
71
  * use it to keep working without reintroducing a second namespace.
@@ -84,7 +102,7 @@ export function defineAppConfig(config) {
84
102
  * remove.
85
103
  */
86
104
  export function getAppConfig() {
87
- const envLayer = readEnvConfigLayer(appConfigSchema, process.env);
105
+ const envLayer = readEnvConfigLayer(appConfigSchema, readConfigEnvironment());
88
106
  const signature = JSON.stringify(envLayer);
89
107
  if (state.resolved && state.envSignature === signature)
90
108
  return state.resolved;
@@ -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;
@@ -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
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  configured?: undefined;
21
22
  connectPath?: undefined;
22
23
  url: string;
23
24
  id: string;
24
25
  provider: string;
25
- error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- ok: boolean;
32
31
  error?: undefined;
32
+ ok: boolean;
33
33
  }>>;
@@ -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 {};
@@ -1,4 +1,4 @@
1
- export { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingIfValue, getAllSettings, getSettingsEmitter, type StoreWriteOptions, } from "./store.js";
1
+ export { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingIfValue, getAllSettings, listSettingsByPrefix, getSettingsEmitter, type StoreWriteOptions, } from "./store.js";
2
2
  export { getSettingHandler, putSettingHandler, deleteSettingHandler, } from "./handlers.js";
3
3
  export { readSetting, writeSetting, removeSetting } from "./script-helpers.js";
4
4
  export { getUserSetting, putUserSetting, deleteUserSetting, } from "./user-settings.js";
@@ -1,5 +1,5 @@
1
1
  // Store
2
- export { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingIfValue, getAllSettings, getSettingsEmitter, } from "./store.js";
2
+ export { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingIfValue, getAllSettings, listSettingsByPrefix, getSettingsEmitter, } from "./store.js";
3
3
  // H3 route handlers
4
4
  export { getSettingHandler, putSettingHandler, deleteSettingHandler, } from "./handlers.js";
5
5
  // Script helpers
@@ -7,7 +7,7 @@
7
7
  * No global fallback — each org starts with a clean slate. This
8
8
  * prevents one org's data from leaking to another.
9
9
  */
10
- import { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, getAllSettings, } from "./store.js";
10
+ import { getSetting, mutateSetting, putSetting, deleteSetting, deleteSettingsByPrefix, listSettingsByPrefix, } from "./store.js";
11
11
  function orgKey(orgId, key) {
12
12
  return `o:${orgId}:${key}`;
13
13
  }
@@ -37,9 +37,13 @@ export async function deleteAllOrgSettings(orgId, options) {
37
37
  * Returns a map of `<key>` (without the org prefix) to value.
38
38
  */
39
39
  export async function listOrgSettings(orgId, subPrefix) {
40
- const all = await getAllSettings();
40
+ // Narrow in SQL, then apply the same checks as before. Reading this with
41
+ // `getAllSettings()` pulled and JSON-parsed every org's rows into the caller
42
+ // to keep one org's, putting the whole deployment's settings table on the
43
+ // critical path of any org-scoped list read.
44
+ const scoped = await listSettingsByPrefix(`o:${orgId}:${subPrefix ?? ""}`);
41
45
  const out = {};
42
- for (const [fullKey, value] of Object.entries(all)) {
46
+ for (const { key: fullKey, value } of scoped) {
43
47
  const m = ORG_PREFIX_RE.exec(fullKey);
44
48
  if (!m || m[1] !== orgId)
45
49
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.168.2",
3
+ "version": "0.168.3",
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": {