@agent-native/core 0.174.0 → 0.174.1-nightly-20260825183012

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.
@@ -9,6 +9,7 @@ import {
9
9
  listProviderApiCatalog,
10
10
  } from "@agent-native/core/provider-api";
11
11
  import {
12
+ CredentialStoreUnavailableError,
12
13
  hasWorkspaceProviderOAuthCredentials,
13
14
  isGoogleWorkspaceOAuthProvider,
14
15
  } from "@agent-native/core/server";
@@ -50,7 +51,7 @@ type GrantSummary = {
50
51
  lastUsedAt?: string | null;
51
52
  };
52
53
 
53
- type GoogleOAuthAvailability = "configured" | "unconfigured";
54
+ type GoogleOAuthAvailability = "configured" | "unconfigured" | "unavailable";
54
55
 
55
56
  function unique(values: string[]) {
56
57
  return Array.from(
@@ -129,10 +130,17 @@ export default defineAction({
129
130
  | WorkspaceConnectionTemplateUse
130
131
  | undefined,
131
132
  });
132
- const googleOAuthAvailability: GoogleOAuthAvailability =
133
- (await hasWorkspaceProviderOAuthCredentials("gmail"))
133
+ let googleOAuthAvailability: GoogleOAuthAvailability;
134
+ try {
135
+ googleOAuthAvailability = (await hasWorkspaceProviderOAuthCredentials(
136
+ "gmail",
137
+ ))
134
138
  ? "configured"
135
139
  : "unconfigured";
140
+ } catch (error) {
141
+ if (!(error instanceof CredentialStoreUnavailableError)) throw error;
142
+ googleOAuthAvailability = "unavailable";
143
+ }
136
144
  const googleOAuthConfigured = googleOAuthAvailability === "configured";
137
145
  const providers = catalogProviders.filter(
138
146
  (provider) =>
@@ -274,7 +282,7 @@ export default defineAction({
274
282
  availability: {
275
283
  googleOAuth: {
276
284
  status: googleOAuthAvailability,
277
- retryable: false,
285
+ retryable: googleOAuthAvailability === "unavailable",
278
286
  },
279
287
  },
280
288
  providers: providersWithReadiness,
@@ -221,6 +221,12 @@ interface WorkspaceConnectionGrantSummary {
221
221
  }
222
222
 
223
223
  interface WorkspaceConnectionsResponse {
224
+ availability?: {
225
+ googleOAuth: {
226
+ status: "configured" | "unconfigured" | "unavailable";
227
+ retryable: boolean;
228
+ };
229
+ };
224
230
  providers: WorkspaceConnectionProvider[];
225
231
  connections: WorkspaceConnection[];
226
232
  grants: WorkspaceConnectionGrant[];
@@ -2719,6 +2725,9 @@ export default function WorkspaceIntegrationsRoute() {
2719
2725
  EMPTY_RESPONSE) as WorkspaceConnectionsResponse;
2720
2726
  const providers = data.providers;
2721
2727
  const connections = data.connections;
2728
+ const googleOAuthUnavailable =
2729
+ data.availability?.googleOAuth.status === "unavailable" &&
2730
+ data.availability.googleOAuth.retryable;
2722
2731
  const apps = (appsQuery.data ?? []) as WorkspaceAppSummary[];
2723
2732
  const groups = (groupsQuery.data ?? []) as WorkspaceUserGroup[];
2724
2733
  const providersById = useMemo(
@@ -3152,6 +3161,9 @@ export default function WorkspaceIntegrationsRoute() {
3152
3161
  }}
3153
3162
  />
3154
3163
  ) : null}
3164
+ {googleOAuthUnavailable ? (
3165
+ <ActionQueryError onRetry={() => void connectionsQuery.refetch()} />
3166
+ ) : null}
3155
3167
  {!connectionsQuery.isError && !appsQuery.isError ? (
3156
3168
  <>
3157
3169
  {connectionsQuery.isLoading ? (
@@ -80,8 +80,17 @@ function writeStoredModelSelection(key, selection) {
80
80
  catch { }
81
81
  }
82
82
  function resolveModelSelection(selection, groups) {
83
- if (!selection?.model)
84
- return undefined;
83
+ if (!selection?.model) {
84
+ const group = groups.find((candidate) => candidate.configured);
85
+ const model = group?.models[0];
86
+ return group && model
87
+ ? {
88
+ model,
89
+ engine: group.engine,
90
+ effort: resolveReasoningEffortSelection(model, undefined),
91
+ }
92
+ : undefined;
93
+ }
85
94
  // Engine precedence turns on whether the catalog OFFERS the supplied engine,
86
95
  // not on whether that engine advertises this model:
87
96
  // offered → honor it. A gateway's advertised list is its built-in
@@ -1974,6 +1983,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
1974
1983
  .filter((tabId) => tabId === activeThreadId || mountedTabsRef.current.has(tabId))
1975
1984
  .map((tabId) => {
1976
1985
  const modelSelection = resolveThreadModelSelection(tabId);
1986
+ const modelSelectionPending = !hostManagedModels && modelListLoading && !modelSelection;
1977
1987
  const tabDynamicSuggestions = tabId === activeThreadId && !contentHidden
1978
1988
  ? props.dynamicSuggestions
1979
1989
  : false;
@@ -2002,8 +2012,10 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
2002
2012
  // sub-agent tab would start a fresh run on that thread and kill
2003
2013
  // the in-flight team chunk. Disable the composer and show a
2004
2014
  // hint so users know to send via the orchestrator chat instead.
2005
- composerDisabled: Boolean(parentMap[tabId]), composerDisabledPlaceholder: parentMap[tabId]
2015
+ composerDisabled: Boolean(parentMap[tabId]) || modelSelectionPending, composerDisabledPlaceholder: parentMap[tabId]
2006
2016
  ? translate("agentChat.composer.subAgentReadOnly")
2007
- : undefined })] }, tabId));
2017
+ : modelSelectionPending
2018
+ ? translate("agentChat.composer.loadingModels")
2019
+ : undefined })] }, tabId));
2008
2020
  })] })] }));
2009
2021
  }
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
137
137
  inputSchema: {
138
138
  type: string;
139
139
  properties: {
140
+ count?: undefined;
140
141
  query?: undefined;
141
142
  minutes?: undefined;
142
143
  limit?: undefined;
143
144
  clientHint?: undefined;
144
145
  timestamp?: undefined;
145
146
  chapterId?: undefined;
146
- count?: undefined;
147
147
  startAt?: undefined;
148
148
  endAt?: undefined;
149
149
  reason?: undefined;
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
159
159
  inputSchema: {
160
160
  type: string;
161
161
  properties: {
162
+ count?: undefined;
162
163
  query: {
163
164
  type: string;
164
165
  description: string;
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
174
175
  clientHint?: undefined;
175
176
  timestamp?: undefined;
176
177
  chapterId?: undefined;
177
- count?: undefined;
178
178
  startAt?: undefined;
179
179
  endAt?: undefined;
180
180
  reason?: undefined;
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
190
190
  inputSchema: {
191
191
  type: string;
192
192
  properties: {
193
+ count?: undefined;
193
194
  minutes: {
194
195
  type: string;
195
196
  description: string;
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
199
200
  clientHint?: undefined;
200
201
  timestamp?: undefined;
201
202
  chapterId?: undefined;
202
- count?: undefined;
203
203
  startAt?: undefined;
204
204
  endAt?: undefined;
205
205
  reason?: undefined;
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
216
216
  type: string;
217
217
  required: string[];
218
218
  properties: {
219
+ count?: undefined;
219
220
  query: {
220
221
  type: string;
221
222
  description: string;
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
234
235
  };
235
236
  timestamp?: undefined;
236
237
  chapterId?: undefined;
237
- count?: undefined;
238
238
  startAt?: undefined;
239
239
  endAt?: undefined;
240
240
  reason?: undefined;
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
250
250
  type: string;
251
251
  required: string[];
252
252
  properties: {
253
+ count?: undefined;
253
254
  query?: undefined;
254
255
  minutes?: undefined;
255
256
  limit?: undefined;
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
263
264
  description: string;
264
265
  };
265
266
  chapterId?: undefined;
266
- count?: undefined;
267
267
  startAt?: undefined;
268
268
  endAt?: undefined;
269
269
  includeMicrophone?: undefined;
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
314
314
  type: string;
315
315
  required: string[];
316
316
  properties: {
317
+ count?: undefined;
317
318
  query?: undefined;
318
319
  minutes?: undefined;
319
320
  limit?: undefined;
320
321
  clientHint?: undefined;
321
322
  timestamp?: undefined;
322
323
  chapterId?: undefined;
323
- count?: undefined;
324
324
  startAt: {
325
325
  type: string;
326
326
  description: string;
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
349
349
  type: string;
350
350
  required: string[];
351
351
  properties: {
352
+ count?: undefined;
352
353
  query?: undefined;
353
354
  minutes?: undefined;
354
355
  limit?: undefined;
355
356
  clientHint?: undefined;
356
357
  timestamp?: undefined;
357
358
  chapterId?: undefined;
358
- count?: undefined;
359
359
  startAt?: undefined;
360
360
  endAt?: undefined;
361
361
  reason?: undefined;
@@ -24,7 +24,17 @@ export interface McpOAuthRoutesOptions {
24
24
  }
25
25
  export declare function redirectWithStagedCookies(event: H3Event, location: string): Response;
26
26
  export declare function mountMcpOAuthRoutes(nitroApp: any, options: McpOAuthRoutesOptions): void;
27
- export declare function resolveMcpOAuthScope(serverUrl: URL, requestedScope: unknown): RemoteMcpScope | null;
27
+ export declare function resolveMcpOAuthStartError(error: unknown): {
28
+ status: 400 | 503;
29
+ body: {
30
+ error: string;
31
+ errorCode?: string;
32
+ retryable?: boolean;
33
+ };
34
+ };
35
+ export declare function resolveMcpOAuthScope(serverUrl: URL, requestedScope: unknown, options?: {
36
+ allowManagedOrgReconnect?: boolean;
37
+ }): RemoteMcpScope | null;
28
38
  export declare function stripMcpOAuthAppBasePath(path: string, basePath: string): string;
29
39
  export declare function resolveManagedMcpOAuthClient(serverUrl: URL): Promise<StoredOAuthClientInformation | undefined>;
30
40
  export declare function setMcpOAuthFlowCookie(event: H3Event, flow: McpOAuthFlow, secure: boolean): void;
@@ -3,7 +3,7 @@ import { deleteCookie, defineEventHandler, getChunkedCookie, getCookie, getMetho
3
3
  import { getOrgContext } from "../org/context.js";
4
4
  import { decryptSecretValue, encryptSecretValue } from "../secrets/crypto.js";
5
5
  import { getSession, safeReturnPath } from "../server/auth.js";
6
- import { resolveSecret } from "../server/credential-provider.js";
6
+ import { CredentialStoreUnavailableError, resolveSecretPair, } from "../server/credential-provider.js";
7
7
  import { getH3App } from "../server/framework-request-handler.js";
8
8
  import { getAppBasePath, getAppUrl, resolveOAuthRedirectUri, } from "../server/google-oauth.js";
9
9
  import { runWithRequestContext } from "../server/request-context.js";
@@ -16,17 +16,25 @@ const FLOW_COOKIE_MAX_CHUNKS = 8;
16
16
  const CHUNKED_COOKIE_PREFIX = "__chunked__";
17
17
  const MANAGED_MCP_OAUTH_CLIENTS = [
18
18
  {
19
- serverOrigin: "https://mcp.hubspot.com",
20
- clientIdKeys: [
21
- "HUBSPOT_MCP_CLIENT_ID",
22
- "HUBSPOT_INTEGRATION_CLIENT_ID",
23
- "HUBSPOT_CLIENT_ID",
19
+ serverOrigins: ["https://mcp.hubspot.com"],
20
+ credentialPairs: [
21
+ ["HUBSPOT_MCP_CLIENT_ID", "HUBSPOT_MCP_CLIENT_SECRET"],
22
+ ["HUBSPOT_INTEGRATION_CLIENT_ID", "HUBSPOT_INTEGRATION_CLIENT_SECRET"],
23
+ ["HUBSPOT_CLIENT_ID", "HUBSPOT_CLIENT_SECRET"],
24
24
  ],
25
- clientSecretKeys: [
26
- "HUBSPOT_MCP_CLIENT_SECRET",
27
- "HUBSPOT_INTEGRATION_CLIENT_SECRET",
28
- "HUBSPOT_CLIENT_SECRET",
25
+ },
26
+ {
27
+ serverOrigins: [
28
+ "https://gmailmcp.googleapis.com",
29
+ "https://drivemcp.googleapis.com",
30
+ "https://docsmcp.googleapis.com",
31
+ "https://sheetsmcp.googleapis.com",
32
+ "https://slidesmcp.googleapis.com",
33
+ "https://calendarmcp.googleapis.com",
34
+ "https://chatmcp.googleapis.com",
35
+ "https://people.googleapis.com",
29
36
  ],
37
+ credentialPairs: [["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]],
30
38
  },
31
39
  ];
32
40
  export function redirectWithStagedCookies(event, location) {
@@ -111,7 +119,9 @@ async function handleMcpOAuthStart(event) {
111
119
  setResponseStatus(event, 400);
112
120
  return { error: "MCP server name is invalid." };
113
121
  }
114
- const requestedScope = resolveMcpOAuthScope(urlCheck.url, query.scope);
122
+ const requestedScope = resolveMcpOAuthScope(urlCheck.url, query.scope, {
123
+ allowManagedOrgReconnect: reconnectScope === "org" && Boolean(reconnectServer),
124
+ });
115
125
  if (!requestedScope) {
116
126
  setResponseStatus(event, 400);
117
127
  return {
@@ -165,7 +175,7 @@ async function handleMcpOAuthStart(event) {
165
175
  if (!started) {
166
176
  setResponseStatus(event, 400);
167
177
  return {
168
- error: "HubSpot personal MCP connect is not configured for this workspace. A workspace owner must register the HubSpot MCP Auth App once; after that, any workspace member can connect a personal account.",
178
+ error: "Managed MCP OAuth is not configured for this workspace. A workspace owner must register the OAuth client once; after that, any workspace member can connect a personal account.",
169
179
  };
170
180
  }
171
181
  const flow = {
@@ -190,18 +200,37 @@ async function handleMcpOAuthStart(event) {
190
200
  setMcpOAuthFlowCookie(event, flow, redirectUri.startsWith("https://"));
191
201
  return redirectWithStagedCookies(event, started.authorizationUrl.href);
192
202
  }
193
- catch {
194
- setResponseStatus(event, 400);
203
+ catch (error) {
204
+ const failure = resolveMcpOAuthStartError(error);
205
+ setResponseStatus(event, failure.status);
206
+ return failure.body;
207
+ }
208
+ }
209
+ export function resolveMcpOAuthStartError(error) {
210
+ if (error instanceof CredentialStoreUnavailableError) {
195
211
  return {
196
- error: "This MCP server could not start OAuth. It may not support standard MCP OAuth discovery or dynamic client registration.",
212
+ status: 503,
213
+ body: {
214
+ error: error.message,
215
+ errorCode: error.errorCode,
216
+ retryable: error.retryable,
217
+ },
197
218
  };
198
219
  }
220
+ return {
221
+ status: 400,
222
+ body: {
223
+ error: "This MCP server could not start OAuth. It may not support standard MCP OAuth discovery or dynamic client registration.",
224
+ },
225
+ };
199
226
  }
200
227
  function isManagedMcpOAuthServer(serverUrl) {
201
- return MANAGED_MCP_OAUTH_CLIENTS.some((client) => client.serverOrigin === serverUrl.origin);
228
+ return MANAGED_MCP_OAUTH_CLIENTS.some((client) => client.serverOrigins.includes(serverUrl.origin));
202
229
  }
203
- export function resolveMcpOAuthScope(serverUrl, requestedScope) {
204
- if (isManagedMcpOAuthServer(serverUrl) && requestedScope === "org") {
230
+ export function resolveMcpOAuthScope(serverUrl, requestedScope, options) {
231
+ if (isManagedMcpOAuthServer(serverUrl) &&
232
+ requestedScope === "org" &&
233
+ !options?.allowManagedOrgReconnect) {
205
234
  return null;
206
235
  }
207
236
  return requestedScope === "org" ? "org" : "user";
@@ -220,15 +249,13 @@ export function stripMcpOAuthAppBasePath(path, basePath) {
220
249
  : path;
221
250
  }
222
251
  export async function resolveManagedMcpOAuthClient(serverUrl) {
223
- const client = MANAGED_MCP_OAUTH_CLIENTS.find((candidate) => candidate.serverOrigin === serverUrl.origin);
252
+ const client = MANAGED_MCP_OAUTH_CLIENTS.find((candidate) => candidate.serverOrigins.includes(serverUrl.origin));
224
253
  if (!client)
225
254
  return undefined;
226
- for (let index = 0; index < client.clientIdKeys.length; index += 1) {
227
- const [clientId, clientSecret] = await Promise.all([
228
- resolveSecret(client.clientIdKeys[index]),
229
- resolveSecret(client.clientSecretKeys[index]),
230
- ]);
231
- if (clientId && clientSecret) {
255
+ for (const [clientIdKey, clientSecretKey] of client.credentialPairs) {
256
+ const credentials = await resolveSecretPair([clientIdKey, clientSecretKey]);
257
+ if (credentials) {
258
+ const [clientId, clientSecret] = credentials;
232
259
  return {
233
260
  client_id: clientId,
234
261
  client_secret: clientSecret,
@@ -16,18 +16,18 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ count?: undefined;
19
20
  updated: number;
20
21
  error?: undefined;
21
22
  ok?: undefined;
22
- count?: undefined;
23
23
  } | {
24
+ count?: undefined;
24
25
  updated?: undefined;
25
26
  error: string;
26
27
  ok?: undefined;
27
- count?: undefined;
28
28
  } | {
29
+ count?: undefined;
29
30
  updated?: undefined;
30
31
  error?: undefined;
31
32
  ok: boolean;
32
- count?: undefined;
33
33
  }>>;
@@ -401,6 +401,11 @@ export declare function prefetchSecrets(keys: readonly string[]): Promise<void>;
401
401
  * Resolving several keys in one request? Call `prefetchSecrets` first.
402
402
  */
403
403
  export declare function resolveSecret(key: string): Promise<string | null>;
404
+ /**
405
+ * Resolve two related secrets from one scope/source. A complete lower-
406
+ * precedence pair wins over mixing a partial override with another source.
407
+ */
408
+ export declare function resolveSecretPair(keys: readonly [string, string]): Promise<[string, string] | null>;
404
409
  /**
405
410
  * `resolveSecret` without the throw: reports whether the miss is definitive
406
411
  * (`lookupFailed: false` — no such row anywhere the caller can reach) or just
@@ -1235,6 +1235,90 @@ export async function resolveSecret(key) {
1235
1235
  assertCredentialStoreReadable(resolved);
1236
1236
  return null;
1237
1237
  }
1238
+ /**
1239
+ * Resolve two related secrets from one scope/source. A complete lower-
1240
+ * precedence pair wins over mixing a partial override with another source.
1241
+ */
1242
+ export async function resolveSecretPair(keys) {
1243
+ const [firstKey, secondKey] = keys;
1244
+ const readPair = async (scope, scopeId) => {
1245
+ const { readAppSecrets } = await import("../secrets/storage.js");
1246
+ const secrets = await readAppSecrets({ keys, scope, scopeId });
1247
+ const first = secrets.get(firstKey)?.value;
1248
+ const second = secrets.get(secondKey)?.value;
1249
+ return first && second ? [first, second] : null;
1250
+ };
1251
+ const readEnvironmentPair = () => {
1252
+ if (!canUseDeployCredentialFallbackForRequest(firstKey) ||
1253
+ !canUseDeployCredentialFallbackForRequest(secondKey)) {
1254
+ return null;
1255
+ }
1256
+ const first = process.env[firstKey];
1257
+ const second = process.env[secondKey];
1258
+ return first && second ? [first, second] : null;
1259
+ };
1260
+ const email = getRequestUserEmail();
1261
+ if (!email)
1262
+ return readEnvironmentPair();
1263
+ let lookupFailed = false;
1264
+ let cause;
1265
+ try {
1266
+ let pair = await readPair("user", email);
1267
+ if (pair)
1268
+ return pair;
1269
+ let orgId = getRequestOrgId();
1270
+ if (!orgId) {
1271
+ const resolved = await resolveOrgIdForRequestEmail(email);
1272
+ cause = resolved.cause;
1273
+ lookupFailed = cause !== undefined;
1274
+ orgId = resolved.orgId;
1275
+ }
1276
+ if (lookupFailed) {
1277
+ const environmentPair = readEnvironmentPair();
1278
+ if (environmentPair)
1279
+ return environmentPair;
1280
+ assertCredentialStoreReadable({ lookupFailed, cause });
1281
+ return null;
1282
+ }
1283
+ if (orgId) {
1284
+ pair = await readPair("org", orgId);
1285
+ if (pair)
1286
+ return pair;
1287
+ pair = await readPair("workspace", orgId);
1288
+ if (pair)
1289
+ return pair;
1290
+ }
1291
+ pair = await readPair("workspace", `solo:${email}`);
1292
+ if (pair)
1293
+ return pair;
1294
+ const vaultOrgId = process.env.AGENT_VAULT_ORG_ID?.trim();
1295
+ if (vaultOrgId && vaultOrgId !== orgId) {
1296
+ const access = await Promise.all(keys.map((key) => canReadDesignatedVaultFallback(vaultOrgId, key)));
1297
+ const unavailable = access.find((result) => result.status === "unavailable");
1298
+ if (unavailable?.status === "unavailable") {
1299
+ lookupFailed = true;
1300
+ cause = unavailable.cause;
1301
+ }
1302
+ else if (access.every((result) => result.status === "allowed")) {
1303
+ pair = await readPair("org", vaultOrgId);
1304
+ if (pair)
1305
+ return pair;
1306
+ pair = await readPair("workspace", vaultOrgId);
1307
+ if (pair)
1308
+ return pair;
1309
+ }
1310
+ }
1311
+ }
1312
+ catch (error) {
1313
+ lookupFailed = true;
1314
+ cause = error;
1315
+ }
1316
+ const environmentPair = readEnvironmentPair();
1317
+ if (environmentPair)
1318
+ return environmentPair;
1319
+ assertCredentialStoreReadable({ lookupFailed, cause });
1320
+ return null;
1321
+ }
1238
1322
  /**
1239
1323
  * `resolveSecret` without the throw: reports whether the miss is definitive
1240
1324
  * (`lookupFailed: false` — no such row anywhere the caller can reach) or just
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.174.0",
3
+ "version": "0.174.1-nightly-20260825183012",
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": {