@agent-native/core 0.146.5 → 0.146.7
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/analytics/server/jobs/analytics-bigquery-backfill.ts +13 -9
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +69 -33
- package/corpus/templates/calendar/app/root.tsx +0 -1
- package/corpus/templates/clips/app/root.tsx +0 -1
- package/corpus/templates/content/app/root.tsx +0 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +21 -0
- package/dist/deploy/build.js +185 -10
- package/dist/jobs/background-automation-runner.d.ts +1 -1
- package/dist/jobs/background-automation-runner.js +1 -1
- package/dist/jobs/scheduler-health.d.ts +4 -0
- package/dist/jobs/scheduler-health.js +33 -0
- package/dist/localization/server.d.ts +0 -1
- package/dist/localization/server.js +3 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/routes.d.ts +11 -0
- package/dist/mcp-client/routes.js +26 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/mcp-glue.js +12 -3
- package/dist/server/agent-chat-plugin.js +73 -48
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +7 -0
- package/dist/server/http-response-telemetry.js +112 -5
- package/dist/server/release-migrations.js +2 -0
- package/dist/server/ssr-handler.js +8 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +54 -3
- package/dist/vite/client.js +5 -1
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +54 -3
|
@@ -201,21 +201,13 @@ export async function queueFirstPartyAnalyticsBigQueryBackfill(
|
|
|
201
201
|
const id = jobId(scope.orgId ?? "");
|
|
202
202
|
if (!scope.orgId)
|
|
203
203
|
throw new Error("BigQuery backfill requires an organization");
|
|
204
|
-
const conflictClause =
|
|
205
|
-
requestedBatchSize === undefined
|
|
206
|
-
? "ON CONFLICT (id) DO NOTHING"
|
|
207
|
-
: `ON CONFLICT (id) DO UPDATE SET batch_size = CASE
|
|
208
|
-
WHEN batch_size < excluded.batch_size
|
|
209
|
-
THEN excluded.batch_size
|
|
210
|
-
ELSE batch_size
|
|
211
|
-
END`;
|
|
212
204
|
await executor().execute({
|
|
213
205
|
sql: `INSERT INTO ${JOB_TABLE} (
|
|
214
206
|
id, org_id, owner_email, table_ref, batch_size, backfill_cursor,
|
|
215
207
|
status, copied_count, lease_token, lease_expires_at, next_run_at,
|
|
216
208
|
last_error, completed_at, updated_at
|
|
217
209
|
) VALUES (?, ?, ?, ?, ?, ?, 'pending', 0, NULL, NULL, ?, NULL, NULL, ?)
|
|
218
|
-
|
|
210
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
219
211
|
args: [
|
|
220
212
|
id,
|
|
221
213
|
scope.orgId,
|
|
@@ -229,6 +221,18 @@ export async function queueFirstPartyAnalyticsBigQueryBackfill(
|
|
|
229
221
|
timeoutMs: 5_000,
|
|
230
222
|
maxAttempts: 1,
|
|
231
223
|
});
|
|
224
|
+
if (requestedBatchSize !== undefined) {
|
|
225
|
+
const batchSize = boundedBatchSize(requestedBatchSize);
|
|
226
|
+
await executor().execute({
|
|
227
|
+
sql: `UPDATE ${JOB_TABLE}
|
|
228
|
+
SET batch_size = ?
|
|
229
|
+
WHERE id = ?
|
|
230
|
+
AND batch_size < ?`,
|
|
231
|
+
args: [batchSize, id, batchSize],
|
|
232
|
+
timeoutMs: 5_000,
|
|
233
|
+
maxAttempts: 1,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
232
236
|
const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
|
|
233
237
|
if (!job) throw new Error("BigQuery backfill job was not persisted");
|
|
234
238
|
if (job.table !== table) {
|
|
@@ -1031,7 +1031,7 @@ function backfillBranchSql(
|
|
|
1031
1031
|
? [cursor.receivedAt, cursor.receivedAt, cursor.id]
|
|
1032
1032
|
: [];
|
|
1033
1033
|
return {
|
|
1034
|
-
sql: `SELECT
|
|
1034
|
+
sql: `SELECT id, received_at
|
|
1035
1035
|
FROM analytics_events
|
|
1036
1036
|
WHERE ${predicate}${cursorSql ? ` AND ${cursorSql}` : ""}
|
|
1037
1037
|
ORDER BY received_at ASC, id ASC LIMIT ?`,
|
|
@@ -1039,32 +1039,20 @@ function backfillBranchSql(
|
|
|
1039
1039
|
};
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
|
-
function
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
): { sql: string; args: Array<string | number> } {
|
|
1047
|
-
const branchQueries = branches.map((branch) =>
|
|
1048
|
-
backfillBranchSql(branch.predicate, branch.args, cursor),
|
|
1049
|
-
);
|
|
1050
|
-
const columns = FIRST_PARTY_ANALYTICS_BACKFILL_COLUMNS.join(", ");
|
|
1042
|
+
function backfillRowsByIdsSql(ids: string[]): {
|
|
1043
|
+
sql: string;
|
|
1044
|
+
args: string[];
|
|
1045
|
+
} {
|
|
1051
1046
|
return {
|
|
1052
|
-
sql: `
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
)
|
|
1057
|
-
.join(", ")}
|
|
1058
|
-
SELECT ${columns}
|
|
1059
|
-
FROM (${branchQueries.map((_branch, index) => `SELECT ${columns} FROM backfill_branch_${index}`).join(" UNION ALL ")}) AS selected_events
|
|
1060
|
-
ORDER BY received_at ASC, id ASC LIMIT ?`,
|
|
1061
|
-
args: [
|
|
1062
|
-
...branchQueries.flatMap((branch) => [...branch.args, limit]),
|
|
1063
|
-
limit,
|
|
1064
|
-
],
|
|
1047
|
+
sql: `SELECT ${FIRST_PARTY_ANALYTICS_BACKFILL_COLUMNS.join(", ")}
|
|
1048
|
+
FROM analytics_events
|
|
1049
|
+
WHERE id IN (${ids.map(() => "?").join(", ")})`,
|
|
1050
|
+
args: ids,
|
|
1065
1051
|
};
|
|
1066
1052
|
}
|
|
1067
1053
|
|
|
1054
|
+
const MAX_SQLITE_BIND_VARIABLES = 900;
|
|
1055
|
+
|
|
1068
1056
|
function backfillRowCursor(
|
|
1069
1057
|
row: Record<string, unknown>,
|
|
1070
1058
|
): FirstPartyAnalyticsBackfillCursor {
|
|
@@ -1081,6 +1069,18 @@ function backfillRowCursor(
|
|
|
1081
1069
|
return { receivedAt, id };
|
|
1082
1070
|
}
|
|
1083
1071
|
|
|
1072
|
+
function compareBackfillRows(
|
|
1073
|
+
left: Record<string, unknown>,
|
|
1074
|
+
right: Record<string, unknown>,
|
|
1075
|
+
): number {
|
|
1076
|
+
const leftCursor = backfillRowCursor(left);
|
|
1077
|
+
const rightCursor = backfillRowCursor(right);
|
|
1078
|
+
return (
|
|
1079
|
+
leftCursor.receivedAt.localeCompare(rightCursor.receivedAt) ||
|
|
1080
|
+
leftCursor.id.localeCompare(rightCursor.id)
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
1084
|
export async function backfillFirstPartyAnalyticsBatch(
|
|
1085
1085
|
scope: FirstPartyAnalyticsScope,
|
|
1086
1086
|
cursor: string | null,
|
|
@@ -1108,15 +1108,22 @@ export async function backfillFirstPartyAnalyticsBatch(
|
|
|
1108
1108
|
},
|
|
1109
1109
|
];
|
|
1110
1110
|
const rows: Record<string, unknown>[] = [];
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1111
|
+
for (const branch of branches) {
|
|
1112
|
+
const scoped = backfillBranchSql(
|
|
1113
|
+
branch.predicate,
|
|
1114
|
+
branch.args,
|
|
1115
|
+
parsedCursor,
|
|
1116
|
+
);
|
|
1117
|
+
const result = await db.execute({
|
|
1118
|
+
sql: scoped.sql,
|
|
1119
|
+
args: [...scoped.args, boundedLimit],
|
|
1120
|
+
timeoutMs: 20_000,
|
|
1121
|
+
maxAttempts: 1,
|
|
1122
|
+
});
|
|
1123
|
+
rows.push(...(result.rows as Record<string, unknown>[]));
|
|
1124
|
+
}
|
|
1125
|
+
rows.sort(compareBackfillRows);
|
|
1126
|
+
const selectedRows = rows.slice(0, boundedLimit);
|
|
1120
1127
|
if (!selectedRows.length) {
|
|
1121
1128
|
return {
|
|
1122
1129
|
nextCursor: serializeBackfillCursor(parsedCursor),
|
|
@@ -1125,7 +1132,36 @@ export async function backfillFirstPartyAnalyticsBatch(
|
|
|
1125
1132
|
};
|
|
1126
1133
|
}
|
|
1127
1134
|
|
|
1128
|
-
const
|
|
1135
|
+
const selectedIds = selectedRows.map((row) => backfillRowCursor(row).id);
|
|
1136
|
+
const hydratedRows: Record<string, unknown>[] = [];
|
|
1137
|
+
for (
|
|
1138
|
+
let offset = 0;
|
|
1139
|
+
offset < selectedIds.length;
|
|
1140
|
+
offset += MAX_SQLITE_BIND_VARIABLES
|
|
1141
|
+
) {
|
|
1142
|
+
const hydratedQuery = backfillRowsByIdsSql(
|
|
1143
|
+
selectedIds.slice(offset, offset + MAX_SQLITE_BIND_VARIABLES),
|
|
1144
|
+
);
|
|
1145
|
+
const hydratedResult = await db.execute({
|
|
1146
|
+
sql: hydratedQuery.sql,
|
|
1147
|
+
args: hydratedQuery.args,
|
|
1148
|
+
timeoutMs: 20_000,
|
|
1149
|
+
maxAttempts: 1,
|
|
1150
|
+
});
|
|
1151
|
+
hydratedRows.push(...(hydratedResult.rows as Record<string, unknown>[]));
|
|
1152
|
+
}
|
|
1153
|
+
const hydratedById = new Map(
|
|
1154
|
+
hydratedRows.map((row) => [backfillRowCursor(row).id, row]),
|
|
1155
|
+
);
|
|
1156
|
+
const selectedEvents = selectedIds.map((id) => {
|
|
1157
|
+
const row = hydratedById.get(id);
|
|
1158
|
+
if (!row) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`First-party analytics backfill row ${id} disappeared before hydration`,
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return row;
|
|
1164
|
+
});
|
|
1129
1165
|
await insertFirstPartyAnalyticsRows(selectedEvents, configuredTable);
|
|
1130
1166
|
const lastCursor = backfillRowCursor(selectedRows[selectedRows.length - 1]!);
|
|
1131
1167
|
return {
|
|
@@ -130,7 +130,6 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|
|
130
130
|
const localeInitScript = getHydrationStableLocaleInitScript({
|
|
131
131
|
locale: loaderData.locale,
|
|
132
132
|
preference: loaderData.preference,
|
|
133
|
-
messages: loaderData.messages,
|
|
134
133
|
});
|
|
135
134
|
|
|
136
135
|
return (
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -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;
|
package/dist/deploy/build.d.ts
CHANGED
|
@@ -74,6 +74,14 @@ export { copyDir };
|
|
|
74
74
|
export declare function isServerlessNativePlatformPackage(packageName: string): boolean;
|
|
75
75
|
type ServerlessFfmpegStaticArch = "arm64" | "x64";
|
|
76
76
|
export declare function shouldBundleFfmpegStaticForServerless(hostPlatform?: NodeJS.Platform, hostArch?: NodeJS.Architecture, targetArch?: ServerlessFfmpegStaticArch | null): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Which dependency gives this app a path to the serverless browser runtime, or
|
|
79
|
+
* null when it has none. Resolution cannot answer this: findInstalledPackageRoot
|
|
80
|
+
* falls back to an ancestor + .pnpm store walk, so in a workspace every app
|
|
81
|
+
* "resolves" the sibling package that actually declares Chromium and ships its
|
|
82
|
+
* ~80MB into every emitted function.
|
|
83
|
+
*/
|
|
84
|
+
export declare function findServerlessBrowserRuntimeConsumer(projectCwd?: string): string | null;
|
|
77
85
|
export declare function findInstalledPackageRoot(packageName: string, nodeModulesRoots: string[], fromPackageDir?: string): string | null;
|
|
78
86
|
export declare function copyInstalledBrowserRuntimePackages(serverDir: string | undefined, projectCwd?: string): number;
|
|
79
87
|
export declare function findInstalledFfmpegStaticPackage(nodeModulesRoots: string[]): string | null;
|
|
@@ -238,6 +246,19 @@ export declare function writeSingleTemplateNetlifyRedirects(projectCwd: string):
|
|
|
238
246
|
* though the server bundle never imports Electron at runtime.
|
|
239
247
|
*/
|
|
240
248
|
export declare function sanitizeServerlessFunctionPackageManifest(functionDir: string | undefined): void;
|
|
249
|
+
/**
|
|
250
|
+
* Nitro's dependency tracer is not subject to the copy-time filters above, so
|
|
251
|
+
* it ships every prebuild of a multi-platform native package plus whatever the
|
|
252
|
+
* local dev server left in `data/`. Prune both from the function dir before it
|
|
253
|
+
* is cloned for the extra functions, since each clone is zipped and uploaded in
|
|
254
|
+
* full.
|
|
255
|
+
*
|
|
256
|
+
* Only prunes inside a directory that still holds a runnable prebuild under the
|
|
257
|
+
* `isServerlessNativePlatformPackage` naming (`@libsql`, `@resvg`). Packages
|
|
258
|
+
* that name prebuilds differently — `@img/sharp-linux-x64` has no gnu/musl
|
|
259
|
+
* suffix — read as entirely dead and must be left alone.
|
|
260
|
+
*/
|
|
261
|
+
export declare function pruneServerlessFunctionDeadWeight(functionDir: string | undefined): number;
|
|
241
262
|
/**
|
|
242
263
|
* Build for any non-Cloudflare preset using Nitro's programmatic build API.
|
|
243
264
|
* Handles netlify, vercel, deno_deploy, aws-lambda, and all other targets.
|
package/dist/deploy/build.js
CHANGED
|
@@ -1925,6 +1925,13 @@ const SERVERLESS_BROWSER_RUNTIME_PACKAGES = [
|
|
|
1925
1925
|
"@sparticuz/chromium",
|
|
1926
1926
|
"playwright-core",
|
|
1927
1927
|
];
|
|
1928
|
+
const SERVERLESS_BROWSER_RUNTIME_CONSUMER = "@agent-native/creative-context";
|
|
1929
|
+
const PACKAGE_DEPENDENCY_FIELDS = [
|
|
1930
|
+
"dependencies",
|
|
1931
|
+
"optionalDependencies",
|
|
1932
|
+
"devDependencies",
|
|
1933
|
+
"peerDependencies",
|
|
1934
|
+
];
|
|
1928
1935
|
// Serverless functions only ever run on 64-bit Linux. The darwin/win32/android
|
|
1929
1936
|
// and 32-bit-arm prebuilds of these native packages are ~100MB that can never
|
|
1930
1937
|
// execute there, and Netlify copies the whole server dir again for every extra
|
|
@@ -1941,6 +1948,11 @@ const SERVERLESS_NATIVE_PACKAGE_SUFFIXES = [
|
|
|
1941
1948
|
export function isServerlessNativePlatformPackage(packageName) {
|
|
1942
1949
|
return SERVERLESS_NATIVE_PACKAGE_SUFFIXES.some((suffix) => packageName.endsWith(suffix));
|
|
1943
1950
|
}
|
|
1951
|
+
// Names a prebuild package by the platform it targets: `darwin-arm64`,
|
|
1952
|
+
// `resvg-js-win32-ia32-msvc`, `linux-x64-gnu`. Matching only says "this is a
|
|
1953
|
+
// per-platform prebuild"; isServerlessNativePlatformPackage decides whether it
|
|
1954
|
+
// can run in the function.
|
|
1955
|
+
const SERVERLESS_PLATFORM_PACKAGE_NAME = /(?:^|-)(?:darwin|win32|linux|android|freebsd)-[a-z0-9]+(?:-[a-z0-9]+)?$/;
|
|
1944
1956
|
const FFMPEG_STATIC_BINARY_NAMES = process.platform === "win32" ? ["ffmpeg.exe", "ffmpeg"] : ["ffmpeg"];
|
|
1945
1957
|
const SERVERLESS_FFMPEG_STATIC_PLATFORM = "linux";
|
|
1946
1958
|
const SERVERLESS_FFMPEG_STATIC_ARCHES = new Set([
|
|
@@ -1993,6 +2005,37 @@ function readPackageManifest(packageDir) {
|
|
|
1993
2005
|
}
|
|
1994
2006
|
return manifest;
|
|
1995
2007
|
}
|
|
2008
|
+
function manifestDeclaresDependency(manifest, packageName) {
|
|
2009
|
+
if (!manifest)
|
|
2010
|
+
return false;
|
|
2011
|
+
return PACKAGE_DEPENDENCY_FIELDS.some((field) => {
|
|
2012
|
+
const deps = manifest[field];
|
|
2013
|
+
if (!deps || typeof deps !== "object" || Array.isArray(deps))
|
|
2014
|
+
return false;
|
|
2015
|
+
return Object.prototype.hasOwnProperty.call(deps, packageName);
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Which dependency gives this app a path to the serverless browser runtime, or
|
|
2020
|
+
* null when it has none. Resolution cannot answer this: findInstalledPackageRoot
|
|
2021
|
+
* falls back to an ancestor + .pnpm store walk, so in a workspace every app
|
|
2022
|
+
* "resolves" the sibling package that actually declares Chromium and ships its
|
|
2023
|
+
* ~80MB into every emitted function.
|
|
2024
|
+
*/
|
|
2025
|
+
export function findServerlessBrowserRuntimeConsumer(projectCwd = cwd) {
|
|
2026
|
+
const manifest = readPackageManifest(projectCwd);
|
|
2027
|
+
for (const packageName of [
|
|
2028
|
+
...SERVERLESS_BROWSER_RUNTIME_PACKAGES,
|
|
2029
|
+
SERVERLESS_BROWSER_RUNTIME_CONSUMER,
|
|
2030
|
+
]) {
|
|
2031
|
+
if (manifestDeclaresDependency(manifest, packageName))
|
|
2032
|
+
return packageName;
|
|
2033
|
+
const linked = path.join(projectCwd, "node_modules", ...packageName.split("/"));
|
|
2034
|
+
if (fs.existsSync(linked))
|
|
2035
|
+
return packageName;
|
|
2036
|
+
}
|
|
2037
|
+
return null;
|
|
2038
|
+
}
|
|
1996
2039
|
function packageRootFromResolvedPath(packageName, resolvedPath) {
|
|
1997
2040
|
let current = path.dirname(resolvedPath);
|
|
1998
2041
|
while (true) {
|
|
@@ -2062,6 +2105,16 @@ export function copyInstalledBrowserRuntimePackages(serverDir, projectCwd = cwd)
|
|
|
2062
2105
|
if (!serverDir || !fs.existsSync(serverDir))
|
|
2063
2106
|
return 0;
|
|
2064
2107
|
const nodeModulesRoots = nodeModulesAncestors(projectCwd);
|
|
2108
|
+
const consumer = findServerlessBrowserRuntimeConsumer(projectCwd);
|
|
2109
|
+
if (!consumer) {
|
|
2110
|
+
const skippedBytes = SERVERLESS_BROWSER_RUNTIME_PACKAGES.reduce((total, packageName) => {
|
|
2111
|
+
const packageDir = findInstalledPackageRoot(packageName, nodeModulesRoots);
|
|
2112
|
+
return packageDir ? total + getDirSize(packageDir) : total;
|
|
2113
|
+
}, 0);
|
|
2114
|
+
console.log(`[deploy] Skipped the serverless browser runtime (${SERVERLESS_BROWSER_RUNTIME_PACKAGES.join(", ")}): this app depends on neither ${SERVERLESS_BROWSER_RUNTIME_CONSUMER} ` +
|
|
2115
|
+
`nor a browser package. Kept ${(skippedBytes / 1024 / 1024).toFixed(1)}MB out of every emitted function.`);
|
|
2116
|
+
return 0;
|
|
2117
|
+
}
|
|
2065
2118
|
const copiedPackages = new Set();
|
|
2066
2119
|
let copiedCount = 0;
|
|
2067
2120
|
for (const packageName of SERVERLESS_BROWSER_RUNTIME_PACKAGES) {
|
|
@@ -2070,9 +2123,15 @@ export function copyInstalledBrowserRuntimePackages(serverDir, projectCwd = cwd)
|
|
|
2070
2123
|
continue;
|
|
2071
2124
|
copiedCount += copyRuntimePackageTree(packageName, packageDir, serverDir, nodeModulesRoots, copiedPackages);
|
|
2072
2125
|
}
|
|
2073
|
-
if (copiedCount
|
|
2074
|
-
|
|
2126
|
+
if (copiedCount === 0) {
|
|
2127
|
+
// Not the same as the skip above: the app asked for the browser runtime and
|
|
2128
|
+
// the build could not find it, so anything that opens a browser will fail
|
|
2129
|
+
// at runtime instead of at build time.
|
|
2130
|
+
console.warn(`[deploy] ${consumer} needs the serverless browser runtime but none of ` +
|
|
2131
|
+
`${SERVERLESS_BROWSER_RUNTIME_PACKAGES.join(", ")} is installed; the function ships without it.`);
|
|
2132
|
+
return 0;
|
|
2075
2133
|
}
|
|
2134
|
+
console.log(`[deploy] Copied ${copiedCount} serverless browser runtime package(s) into the server bundle (required by ${consumer}).`);
|
|
2076
2135
|
return copiedCount;
|
|
2077
2136
|
}
|
|
2078
2137
|
function findInstalledLibsqlNativePackage(nodeModulesRoots, packageName) {
|
|
@@ -2847,6 +2906,60 @@ export function bundleYjsRuntimeForServerlessOutput(serverDir, projectCwd) {
|
|
|
2847
2906
|
});
|
|
2848
2907
|
return bareImports;
|
|
2849
2908
|
}
|
|
2909
|
+
// Netlify's hard limit is 250MB unzipped per function; a bundle this far under
|
|
2910
|
+
// it still leaves room for growth, and today's server functions land near 70MB
|
|
2911
|
+
// once the dead weight is out. Apps that legitimately ship the browser runtime
|
|
2912
|
+
// get its ~85MB on top, so the budget stays a regression signal rather than a
|
|
2913
|
+
// tax on Chromium-backed templates.
|
|
2914
|
+
const NETLIFY_FUNCTION_SIZE_BUDGET_BYTES = 120 * 1024 * 1024;
|
|
2915
|
+
const NETLIFY_BROWSER_RUNTIME_SIZE_ALLOWANCE_BYTES = 100 * 1024 * 1024;
|
|
2916
|
+
function reportNetlifyFunctionSizes(projectCwd, internalDir, failures) {
|
|
2917
|
+
if (!fs.existsSync(internalDir))
|
|
2918
|
+
return;
|
|
2919
|
+
const functions = fs
|
|
2920
|
+
.readdirSync(internalDir, { withFileTypes: true })
|
|
2921
|
+
.filter((entry) => entry.isDirectory())
|
|
2922
|
+
.map((entry) => ({
|
|
2923
|
+
name: entry.name,
|
|
2924
|
+
dir: path.join(internalDir, entry.name),
|
|
2925
|
+
size: getDirSize(path.join(internalDir, entry.name)),
|
|
2926
|
+
}))
|
|
2927
|
+
.sort((a, b) => b.size - a.size);
|
|
2928
|
+
if (functions.length === 0)
|
|
2929
|
+
return;
|
|
2930
|
+
const toMb = (bytes) => (bytes / 1024 / 1024).toFixed(1);
|
|
2931
|
+
const total = functions.reduce((sum, fn) => sum + fn.size, 0);
|
|
2932
|
+
// Every extra function is a full second copy of the bundle: the clone is
|
|
2933
|
+
// hard-linked on disk, but zip-it-and-ship-it sees regular files and uploads
|
|
2934
|
+
// each one whole, so the total is what the deploy actually pays.
|
|
2935
|
+
console.log(`[deploy] Netlify functions: ${functions.length} (${functions
|
|
2936
|
+
.map((fn) => `${fn.name} ${toMb(fn.size)}MB`)
|
|
2937
|
+
.join(", ")}) — ${toMb(total)}MB uploaded in total.`);
|
|
2938
|
+
const budget = NETLIFY_FUNCTION_SIZE_BUDGET_BYTES +
|
|
2939
|
+
(findServerlessBrowserRuntimeConsumer(projectCwd)
|
|
2940
|
+
? NETLIFY_BROWSER_RUNTIME_SIZE_ALLOWANCE_BYTES
|
|
2941
|
+
: 0);
|
|
2942
|
+
for (const fn of functions) {
|
|
2943
|
+
if (fn.size <= budget)
|
|
2944
|
+
continue;
|
|
2945
|
+
// node_modules is always the biggest child and names nothing useful; the
|
|
2946
|
+
// packages inside it are what a regression actually adds.
|
|
2947
|
+
const nodeModulesDir = path.join(fn.dir, "node_modules");
|
|
2948
|
+
const inspectDir = fs.existsSync(nodeModulesDir) ? nodeModulesDir : fn.dir;
|
|
2949
|
+
const largest = fs
|
|
2950
|
+
.readdirSync(inspectDir, { withFileTypes: true })
|
|
2951
|
+
.filter((entry) => entry.isDirectory())
|
|
2952
|
+
.map((entry) => ({
|
|
2953
|
+
name: entry.name,
|
|
2954
|
+
size: getDirSize(path.join(inspectDir, entry.name)),
|
|
2955
|
+
}))
|
|
2956
|
+
.sort((a, b) => b.size - a.size)
|
|
2957
|
+
.slice(0, 3)
|
|
2958
|
+
.map((child) => `${child.name} ${toMb(child.size)}MB`)
|
|
2959
|
+
.join(", ");
|
|
2960
|
+
failures.push(`function ${fn.name} is ${toMb(fn.size)}MB, over the ${toMb(budget)}MB budget — largest: ${largest}`);
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2850
2963
|
export function assertSingleTemplateNetlifyBuildOutput(projectCwd) {
|
|
2851
2964
|
const failures = [];
|
|
2852
2965
|
const publishDir = path.join(projectCwd, "dist");
|
|
@@ -2988,9 +3101,10 @@ export function assertSingleTemplateNetlifyBuildOutput(projectCwd) {
|
|
|
2988
3101
|
}
|
|
2989
3102
|
}
|
|
2990
3103
|
}
|
|
3104
|
+
reportNetlifyFunctionSizes(projectCwd, internalDir, failures);
|
|
2991
3105
|
if (failures.length > 0) {
|
|
2992
3106
|
throw new Error("[deploy] Netlify deploy guard failed; refusing to publish an output " +
|
|
2993
|
-
"that would likely serve Netlify 404s:\n" +
|
|
3107
|
+
"that would likely serve Netlify 404s or blow the function size limit:\n" +
|
|
2994
3108
|
failures.map((failure) => `- ${failure}`).join("\n"));
|
|
2995
3109
|
}
|
|
2996
3110
|
console.log("[deploy] Netlify deploy guard passed: publish dir and catch-all server function are present.");
|
|
@@ -3111,13 +3225,7 @@ export function sanitizeServerlessFunctionPackageManifest(functionDir) {
|
|
|
3111
3225
|
return;
|
|
3112
3226
|
}
|
|
3113
3227
|
let removed = 0;
|
|
3114
|
-
const
|
|
3115
|
-
"dependencies",
|
|
3116
|
-
"optionalDependencies",
|
|
3117
|
-
"devDependencies",
|
|
3118
|
-
"peerDependencies",
|
|
3119
|
-
];
|
|
3120
|
-
for (const field of depFields) {
|
|
3228
|
+
for (const field of PACKAGE_DEPENDENCY_FIELDS) {
|
|
3121
3229
|
const deps = packageJson[field];
|
|
3122
3230
|
if (!deps || typeof deps !== "object" || Array.isArray(deps))
|
|
3123
3231
|
continue;
|
|
@@ -3145,6 +3253,70 @@ export function sanitizeServerlessFunctionPackageManifest(functionDir) {
|
|
|
3145
3253
|
console.log(`[deploy] Removed ${removed} desktop-only package reference(s) from ${path.relative(cwd, functionDir)}.`);
|
|
3146
3254
|
}
|
|
3147
3255
|
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Nitro's dependency tracer is not subject to the copy-time filters above, so
|
|
3258
|
+
* it ships every prebuild of a multi-platform native package plus whatever the
|
|
3259
|
+
* local dev server left in `data/`. Prune both from the function dir before it
|
|
3260
|
+
* is cloned for the extra functions, since each clone is zipped and uploaded in
|
|
3261
|
+
* full.
|
|
3262
|
+
*
|
|
3263
|
+
* Only prunes inside a directory that still holds a runnable prebuild under the
|
|
3264
|
+
* `isServerlessNativePlatformPackage` naming (`@libsql`, `@resvg`). Packages
|
|
3265
|
+
* that name prebuilds differently — `@img/sharp-linux-x64` has no gnu/musl
|
|
3266
|
+
* suffix — read as entirely dead and must be left alone.
|
|
3267
|
+
*/
|
|
3268
|
+
export function pruneServerlessFunctionDeadWeight(functionDir) {
|
|
3269
|
+
if (!functionDir || !fs.existsSync(functionDir))
|
|
3270
|
+
return 0;
|
|
3271
|
+
let removedBytes = 0;
|
|
3272
|
+
const removedNames = [];
|
|
3273
|
+
const nodeModulesDir = path.join(functionDir, "node_modules");
|
|
3274
|
+
const packageDirs = [];
|
|
3275
|
+
if (fs.existsSync(nodeModulesDir)) {
|
|
3276
|
+
packageDirs.push(nodeModulesDir);
|
|
3277
|
+
for (const entry of fs.readdirSync(nodeModulesDir, {
|
|
3278
|
+
withFileTypes: true,
|
|
3279
|
+
})) {
|
|
3280
|
+
if (entry.isDirectory() && entry.name.startsWith("@")) {
|
|
3281
|
+
packageDirs.push(path.join(nodeModulesDir, entry.name));
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
for (const packageDir of packageDirs) {
|
|
3286
|
+
const prebuilds = fs
|
|
3287
|
+
.readdirSync(packageDir, { withFileTypes: true })
|
|
3288
|
+
.filter((entry) => entry.isDirectory() &&
|
|
3289
|
+
SERVERLESS_PLATFORM_PACKAGE_NAME.test(entry.name))
|
|
3290
|
+
.map((entry) => entry.name);
|
|
3291
|
+
const runnable = prebuilds.filter(isServerlessNativePlatformPackage);
|
|
3292
|
+
if (runnable.length === 0)
|
|
3293
|
+
continue;
|
|
3294
|
+
for (const name of prebuilds) {
|
|
3295
|
+
if (isServerlessNativePlatformPackage(name))
|
|
3296
|
+
continue;
|
|
3297
|
+
const deadDir = path.join(packageDir, name);
|
|
3298
|
+
removedBytes += getDirSize(deadDir);
|
|
3299
|
+
removedNames.push(path.relative(functionDir, deadDir));
|
|
3300
|
+
fs.rmSync(deadDir, { recursive: true, force: true });
|
|
3301
|
+
}
|
|
3302
|
+
const survivors = runnable.filter((name) => fs.existsSync(path.join(packageDir, name)));
|
|
3303
|
+
if (survivors.length === 0) {
|
|
3304
|
+
throw new Error(`[deploy] Pruning dead-platform prebuilds from ${path.relative(cwd, packageDir)} removed every runnable Linux prebuild (had: ${runnable.join(", ")}).`);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
const dataDir = path.join(functionDir, "data");
|
|
3308
|
+
if (fs.existsSync(dataDir)) {
|
|
3309
|
+
removedBytes += getDirSize(dataDir);
|
|
3310
|
+
removedNames.push("data");
|
|
3311
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
3312
|
+
}
|
|
3313
|
+
if (removedNames.length > 0) {
|
|
3314
|
+
console.log(`[deploy] Pruned ${removedNames.length} unrunnable path(s) (${(removedBytes /
|
|
3315
|
+
1024 /
|
|
3316
|
+
1024).toFixed(1)}MB) from ${path.relative(cwd, functionDir)}: ${removedNames.join(", ")}.`);
|
|
3317
|
+
}
|
|
3318
|
+
return removedBytes;
|
|
3319
|
+
}
|
|
3148
3320
|
/**
|
|
3149
3321
|
* Create stub directories for dangling platform-specific optional dependency
|
|
3150
3322
|
* symlinks in the pnpm store.
|
|
@@ -3612,6 +3784,9 @@ export default bundle;
|
|
|
3612
3784
|
copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
|
|
3613
3785
|
copyInstalledBrowserRuntimePackages(nitro.options.output.serverDir);
|
|
3614
3786
|
sanitizeServerlessFunctionPackageManifest(nitro.options.output.serverDir);
|
|
3787
|
+
// Before the Netlify block below clones this dir into the extra functions,
|
|
3788
|
+
// so they inherit the pruned bundle instead of a second full copy.
|
|
3789
|
+
pruneServerlessFunctionDeadWeight(nitro.options.output.serverDir);
|
|
3615
3790
|
bundleYjsRuntimeForServerlessOutput(nitro.options.output.serverDir, cwd);
|
|
3616
3791
|
}
|
|
3617
3792
|
if (isCloudflareModulePreset(preset)) {
|
|
@@ -12,7 +12,7 @@ export interface BackgroundAutomationContext {
|
|
|
12
12
|
resource: Resource;
|
|
13
13
|
}
|
|
14
14
|
export interface BackgroundAutomationDeps {
|
|
15
|
-
getActions: (automation?: BackgroundAutomationContext) => Record<string, ActionEntry
|
|
15
|
+
getActions: (automation?: BackgroundAutomationContext) => Record<string, ActionEntry> | Promise<Record<string, ActionEntry>>;
|
|
16
16
|
getSystemPrompt: (owner: string) => Promise<string>;
|
|
17
17
|
getInitialToolNames?: (automation?: BackgroundAutomationContext) => string[] | undefined;
|
|
18
18
|
engine?: AgentEngine;
|
|
@@ -221,7 +221,7 @@ async function executeBackgroundAutomation(options, deps, historyId) {
|
|
|
221
221
|
userEmail: ownerEmail,
|
|
222
222
|
orgId,
|
|
223
223
|
}, async () => {
|
|
224
|
-
const baseActions = deps.getActions(automation);
|
|
224
|
+
const baseActions = await deps.getActions(automation);
|
|
225
225
|
assertRequestedMcpToolsAvailable(automation, baseActions);
|
|
226
226
|
const configuredInitialTools = deps.getInitialToolNames?.(automation);
|
|
227
227
|
const initialToolNames = configuredInitialTools
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { type MigrationEntry } from "../db/migrations.js";
|
|
2
|
+
/** Authoritative release-time schema for recurring scheduler health. */
|
|
3
|
+
export declare const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS: MigrationEntry[];
|
|
4
|
+
export declare function runAutomationSchedulerHealthMigrations(nitroApp: unknown): Promise<void>;
|
|
1
5
|
export interface AutomationSchedulerHealth {
|
|
2
6
|
appId: string;
|
|
3
7
|
orgId: string | null;
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
2
2
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
|
|
3
|
+
import { runMigrations } from "../db/migrations.js";
|
|
3
4
|
const TABLE = "automation_scheduler_health";
|
|
4
5
|
const DEFAULT_APP_ID = "default";
|
|
5
6
|
const MAX_ERROR_LENGTH = 500;
|
|
7
|
+
/** Authoritative release-time schema for recurring scheduler health. */
|
|
8
|
+
export const AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS = [
|
|
9
|
+
{
|
|
10
|
+
version: 1,
|
|
11
|
+
name: "automation-scheduler-health-table",
|
|
12
|
+
sql: `
|
|
13
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
app_id TEXT NOT NULL DEFAULT 'default',
|
|
16
|
+
org_id TEXT,
|
|
17
|
+
last_checked_at INTEGER,
|
|
18
|
+
last_dispatched_at INTEGER,
|
|
19
|
+
last_error TEXT,
|
|
20
|
+
runtime TEXT,
|
|
21
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
22
|
+
);
|
|
23
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS app_id TEXT NOT NULL DEFAULT 'default';
|
|
24
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS org_id TEXT;
|
|
25
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_checked_at INTEGER;
|
|
26
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_dispatched_at INTEGER;
|
|
27
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS last_error TEXT;
|
|
28
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS runtime TEXT;
|
|
29
|
+
ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS updated_at INTEGER NOT NULL DEFAULT 0;
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_${TABLE}_updated ON ${TABLE} (updated_at)
|
|
31
|
+
`,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
export async function runAutomationSchedulerHealthMigrations(nitroApp) {
|
|
35
|
+
await runMigrations(AUTOMATION_SCHEDULER_HEALTH_MIGRATIONS, {
|
|
36
|
+
table: "_automation_scheduler_health_migrations",
|
|
37
|
+
})(nitroApp);
|
|
38
|
+
}
|
|
6
39
|
function normalizeAppId(appId) {
|
|
7
40
|
const normalized = appId?.trim();
|
|
8
41
|
return normalized || DEFAULT_APP_ID;
|
|
@@ -16,7 +16,6 @@ export interface ResolveLocaleFromRequestOptions {
|
|
|
16
16
|
export interface LocaleInitScriptOptions {
|
|
17
17
|
locale?: LocaleCode;
|
|
18
18
|
preference?: LocalizationPreference | LocalePreference;
|
|
19
|
-
messages?: Record<string, unknown> | null;
|
|
20
19
|
}
|
|
21
20
|
export declare function parseAcceptLanguage(header: string | null | undefined): string[];
|
|
22
21
|
export declare function resolveLocaleFromRequest(options?: ResolveLocaleFromRequestOptions): ResolvedRequestLocale;
|
|
@@ -58,11 +58,13 @@ export function getLocaleInitScript(options = {}) {
|
|
|
58
58
|
const safeLocale = normalizeLocaleCode(options.locale) ?? DEFAULT_LOCALE;
|
|
59
59
|
const shouldStorePreference = options.preference !== undefined;
|
|
60
60
|
const safePreference = normalizeLocalizationPreference(options.preference ?? { locale: "system" });
|
|
61
|
+
// Translations are deliberately absent here: this script is render-blocking in
|
|
62
|
+
// <head>, and the provider already receives the same catalog through loader
|
|
63
|
+
// data as `initialMessages`. Adding them back doubles every localized payload.
|
|
61
64
|
const payload = {
|
|
62
65
|
locale: safeLocale,
|
|
63
66
|
preference: safePreference,
|
|
64
67
|
dir: localeDirection(safeLocale),
|
|
65
|
-
messages: options.messages ?? undefined,
|
|
66
68
|
};
|
|
67
69
|
return `(function(){try{var supported=${JSON.stringify(SUPPORTED_LOCALES)};var payload=${JSON.stringify(payload)};var shouldStorePreference=${JSON.stringify(shouldStorePreference)};function valid(x){return supported.indexOf(x)>=0}function canon(x){if(typeof x!=='string'||!x)return null;try{var c=Intl.getCanonicalLocales(x)[0];if(valid(c))return c;var lang=c&&c.split('-')[0].toLowerCase();for(var i=0;i<supported.length;i++){if(supported[i].split('-')[0].toLowerCase()===lang)return supported[i]}}catch(e){}return null}function storageGet(k){try{return window.localStorage.getItem(k)}catch(e){return null}}function storageSet(k,v){try{window.localStorage.setItem(k,v)}catch(e){}}var stored=storageGet(${JSON.stringify(LOCALE_STORAGE_KEY)});var pref=payload.preference&&payload.preference.locale;var locale=payload.locale;if(!valid(locale)){locale=null}if(!locale&&stored&&stored!=='system'){locale=canon(stored)}if(!locale&&pref&&pref!=='system'){locale=canon(pref)}if(!locale){var langs=navigator.languages&&navigator.languages.length?navigator.languages:[navigator.language];for(var j=0;j<langs.length&&!locale;j++){locale=canon(langs[j])}}if(!locale)locale=${JSON.stringify(DEFAULT_LOCALE)};var root=document.documentElement;root.setAttribute('lang',locale);root.setAttribute('dir',locale==='ar-SA'?'rtl':'ltr');root.setAttribute('data-locale',locale);payload.locale=locale;payload.dir=locale==='ar-SA'?'rtl':'ltr';window[${JSON.stringify(LOCALE_HYDRATION_GLOBAL)}]=payload;if(shouldStorePreference&&pref){storageSet(${JSON.stringify(LOCALE_STORAGE_KEY)},pref)}}catch(e){}})();`;
|
|
68
70
|
}
|
|
@@ -10,7 +10,7 @@ export { listRemoteServers, addRemoteServer, addOAuthRemoteServer, addFirstParty
|
|
|
10
10
|
export { finishMcpOAuthAuthorization, getMcpOAuthAccessToken, readMcpOAuthCredentials, saveMcpOAuthCredentials, startMcpOAuthAuthorization, type McpOAuthCallbackResult, type McpOAuthCredentialBundle, type McpOAuthDiscoveryState, type McpOAuthProviderOptions, type McpOAuthStartResult, } from "./oauth-client.js";
|
|
11
11
|
export { areBuiltinMcpCapabilitiesSupported, BUILTIN_MCP_CAPABILITIES, getBuiltinMcpCapability, isBuiltinMcpCapabilityAvailable, listSupportedBuiltinMcpCapabilities, normalizeBuiltinMcpCapabilityIds, toBuiltinMcpServerConfig, type BuiltinMcpCapability, type BuiltinMcpCapabilityId, } from "./builtin-capabilities.js";
|
|
12
12
|
export { builtinMcpCapabilitiesSettingsKey, listEnabledBuiltinMcpCapabilities, setEnabledBuiltinMcpCapabilities, setBuiltinMcpCapabilityEnabled, type StoredBuiltinMcpCapabilities, } from "./builtin-store.js";
|
|
13
|
-
export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, type ClientBuiltinCapability, } from "./routes.js";
|
|
13
|
+
export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, McpConfigUnreadableError, type ClientBuiltinCapability, } from "./routes.js";
|
|
14
14
|
export { mountMcpHubRoutes, listHubServers, getHubStatus, isHubServeEnabled, isHubConsumeEnabled, type HubServerRecord, type HubServersResponse, } from "./hub-routes.js";
|
|
15
15
|
export { fetchHubServers } from "./hub-client.js";
|
|
16
16
|
export { isMcpToolAllowedForRequest } from "./visibility.js";
|
package/dist/mcp-client/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { listRemoteServers, addRemoteServer, addOAuthRemoteServer, addFirstParty
|
|
|
10
10
|
export { finishMcpOAuthAuthorization, getMcpOAuthAccessToken, readMcpOAuthCredentials, saveMcpOAuthCredentials, startMcpOAuthAuthorization, } from "./oauth-client.js";
|
|
11
11
|
export { areBuiltinMcpCapabilitiesSupported, BUILTIN_MCP_CAPABILITIES, getBuiltinMcpCapability, isBuiltinMcpCapabilityAvailable, listSupportedBuiltinMcpCapabilities, normalizeBuiltinMcpCapabilityIds, toBuiltinMcpServerConfig, } from "./builtin-capabilities.js";
|
|
12
12
|
export { builtinMcpCapabilitiesSettingsKey, listEnabledBuiltinMcpCapabilities, setEnabledBuiltinMcpCapabilities, setBuiltinMcpCapabilityEnabled, } from "./builtin-store.js";
|
|
13
|
-
export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, } from "./routes.js";
|
|
13
|
+
export { mountMcpServersRoutes, buildMergedConfig, builtinMergedConfigKey, startMcpConfigRefresh, McpConfigUnreadableError, } from "./routes.js";
|
|
14
14
|
export { mountMcpHubRoutes, listHubServers, getHubStatus, isHubServeEnabled, isHubConsumeEnabled, } from "./hub-routes.js";
|
|
15
15
|
export { fetchHubServers } from "./hub-client.js";
|
|
16
16
|
export { isMcpToolAllowedForRequest } from "./visibility.js";
|