@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.
- package/corpus/templates/dispatch/actions/list-workspace-connections.ts +12 -4
- package/corpus/templates/dispatch/app/routes/integrations.tsx +12 -0
- package/dist/client/MultiTabAssistantChat.js +16 -4
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp-client/oauth-routes.d.ts +11 -1
- package/dist/mcp-client/oauth-routes.js +52 -25
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/server/credential-provider.d.ts +5 -0
- package/dist/server/credential-provider.js +84 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
133
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
|
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 {
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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) &&
|
|
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.
|
|
252
|
+
const client = MANAGED_MCP_OAUTH_CLIENTS.find((candidate) => candidate.serverOrigins.includes(serverUrl.origin));
|
|
224
253
|
if (!client)
|
|
225
254
|
return undefined;
|
|
226
|
-
for (
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
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.
|
|
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": {
|