@agent-native/core 0.146.4 → 0.146.6
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/app/components/layout/CommandPalette.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/analytics/app/pages/Settings.tsx +5 -1
- package/corpus/templates/analytics/app/routes/agent.tsx +7 -5
- 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/assets/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/assets/app/routes/agent.tsx +7 -5
- package/corpus/templates/assets/app/routes/settings.tsx +7 -2
- package/corpus/templates/assets/server/plugins/creative-context.ts +1 -1
- package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/brain/app/lib/brain.ts +4 -2
- package/corpus/templates/brain/app/root.tsx +1 -1
- package/corpus/templates/brain/app/routes/agent.tsx +10 -2
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/calendar/app/root.tsx +1 -2
- package/corpus/templates/calendar/app/routes/_app.agent.tsx +8 -16
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +5 -2
- package/corpus/templates/chat/app/routes/agent.tsx +7 -31
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/root.tsx +1 -2
- package/corpus/templates/clips/app/routes/_app.agent.tsx +9 -7
- package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/content/app/root.tsx +1 -2
- package/corpus/templates/content/app/routes/_app.agent.tsx +9 -8
- package/corpus/templates/content/app/routes/_app.settings.tsx +7 -2
- package/corpus/templates/content/server/plugins/creative-context.ts +1 -1
- package/corpus/templates/crm/app/components/crm/RecordActions.tsx +4 -1
- package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +1 -1
- package/corpus/templates/crm/app/routes/agent.tsx +10 -2
- package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/design/app/root.tsx +1 -1
- package/corpus/templates/design/app/routes/agent.tsx +9 -7
- package/corpus/templates/design/app/routes/settings.tsx +7 -2
- package/corpus/templates/design/server/plugins/creative-context.ts +1 -1
- package/corpus/templates/dispatch/app/root.tsx +5 -2
- package/corpus/templates/dispatch/app/routes/agent.tsx +7 -7
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +5 -2
- package/corpus/templates/factory/app/root.tsx +1 -1
- package/corpus/templates/factory/app/routes/agent.tsx +9 -14
- package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/forms/app/root.tsx +1 -1
- package/corpus/templates/forms/app/routes/_app.agent.tsx +9 -7
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +5 -3
- package/corpus/templates/macros/app/root.tsx +1 -1
- package/corpus/templates/macros/app/routes/agent.tsx +9 -6
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +8 -2
- package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +1 -1
- package/corpus/templates/mail/app/routes/agent.tsx +9 -6
- package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/plan/app/root.tsx +1 -1
- package/corpus/templates/plan/app/routes/agent.tsx +9 -6
- package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +8 -12
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +39 -2
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +22 -5
- package/corpus/templates/slides/app/pages/Index.tsx +20 -5
- package/corpus/templates/slides/app/root.tsx +1 -1
- package/corpus/templates/slides/app/routes/agent.tsx +6 -9
- package/corpus/templates/slides/app/routes/settings.tsx +7 -2
- package/corpus/templates/slides/server/plugins/creative-context.ts +1 -1
- package/corpus/templates/tasks/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/tasks/app/routes/agent.tsx +10 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/client/navigation/index.d.ts +1 -1
- package/dist/client/navigation/index.js +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +8 -0
- package/dist/client/settings/SettingsPanel.js +16 -1
- package/dist/client/settings/index.d.ts +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/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/navigation/index.d.ts +1 -0
- package/dist/navigation/index.js +34 -0
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- 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/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/mcp-glue.js +12 -3
- package/dist/server/agent-chat-plugin.js +73 -48
- package/dist/server/http-response-telemetry.js +112 -5
- package/dist/server/realtime-token.d.ts +1 -1
- 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/chat/app/hooks/use-navigation-state.ts +5 -2
- package/dist/templates/chat/app/routes/agent.tsx +7 -31
- package/dist/templates/default/app/routes/_index.tsx +1 -1
- package/dist/templates/default/app/routes/agent.tsx +7 -25
- 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/chat/app/hooks/use-navigation-state.ts +5 -2
- package/src/templates/chat/app/routes/agent.tsx +7 -31
- package/src/templates/default/app/routes/_index.tsx +1 -1
- package/src/templates/default/app/routes/agent.tsx +7 -25
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +54 -3
|
@@ -73,6 +73,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
73
73
|
offset?: unknown;
|
|
74
74
|
limit?: unknown;
|
|
75
75
|
}, {
|
|
76
|
+
deleted?: undefined;
|
|
76
77
|
jobs: {
|
|
77
78
|
id: string;
|
|
78
79
|
name: string;
|
|
@@ -83,7 +84,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
83
84
|
updatedAt: string;
|
|
84
85
|
}[];
|
|
85
86
|
total: number;
|
|
86
|
-
deleted?: undefined;
|
|
87
87
|
jobId?: undefined;
|
|
88
88
|
} | {
|
|
89
89
|
jobs?: undefined;
|
|
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
|
|
|
91
91
|
deleted: boolean;
|
|
92
92
|
jobId: string;
|
|
93
93
|
} | {
|
|
94
|
+
deleted?: undefined;
|
|
94
95
|
jobs?: undefined;
|
|
95
96
|
total?: undefined;
|
|
96
|
-
deleted?: undefined;
|
|
97
97
|
jobId?: undefined;
|
|
98
98
|
hits: Record<string, unknown>[];
|
|
99
99
|
offset: number;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
37
37
|
ok?: undefined;
|
|
38
38
|
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
44
|
ok?: undefined;
|
|
45
45
|
error: string;
|
|
46
46
|
removed?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
|
|
|
58
58
|
error: string;
|
|
59
59
|
note?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineEventHandler, getMethod, setResponseHeader, setResponseStatus, } from "h3";
|
|
2
|
-
import { buildMergedConfig, getHubStatus, } from "../../mcp-client/index.js";
|
|
2
|
+
import { buildMergedConfig, getHubStatus, McpConfigUnreadableError, } from "../../mcp-client/index.js";
|
|
3
3
|
import { getH3App } from "../framework-request-handler.js";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// MCP client glue — a shared manager reference + a /_agent-native/mcp/status
|
|
@@ -18,8 +18,17 @@ export async function refreshGlobalMcpManager() {
|
|
|
18
18
|
const manager = getGlobalMcpManager();
|
|
19
19
|
if (!manager)
|
|
20
20
|
return false;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
try {
|
|
22
|
+
await manager.reconfigure(await buildMergedConfig());
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err instanceof McpConfigUnreadableError) {
|
|
27
|
+
console.warn(`[mcp-client] global refresh skipped: ${err.message}`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
export function mountMcpHubStatusRoute(nitroApp) {
|
|
25
34
|
const mountedApps = (globalThis.__agentNativeMcpHubStatusMountedApps ??= new WeakSet());
|
|
@@ -24,11 +24,11 @@ import { runChatThreadDataMigrations } from "../chat-threads/migrations.js";
|
|
|
24
24
|
import { adoptThreadScopeIfUnscoped, createThread, forkThread, getThread, registerChatThreadsShareable, resolveRunThreadScope, resolveThreadAccess, listThreads, searchThreads, renameThread, createThreadShareLink, getThreadByShareToken, getThreadShareState, revokeThreadShareLink, setThreadArchived, setThreadPinned, setThreadScope, updateThreadData, withThreadDataLock, deleteThread, setThreadQueuedMessages, setThreadSourceIfMissing, } from "../chat-threads/store.js";
|
|
25
25
|
import { isCheckpointRestorePath } from "../checkpoints/route-match.js";
|
|
26
26
|
import { createDbAdminAgentTools } from "../db-admin/agent-tools.js";
|
|
27
|
-
import { isTransientDatabaseError } from "../db/client.js";
|
|
27
|
+
import { isProductionServerlessFunctionRuntime, isTransientDatabaseError, } from "../db/client.js";
|
|
28
28
|
import { filterFrameworkToolGroups, resolveFrameworkTools, } from "../framework-tools.js";
|
|
29
29
|
import { verifyInternalToken, extractBearerToken, } from "../integrations/internal-token.js";
|
|
30
30
|
import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
|
|
31
|
-
import { McpClientManager,
|
|
31
|
+
import { McpClientManager, mcpToolsToActionEntries, syncMcpActionEntries, mountMcpServersRoutes, mountMcpHubRoutes, buildMergedConfig, startMcpConfigRefresh, getHubStatus, isHubServeEnabled, } from "../mcp-client/index.js";
|
|
32
32
|
import { setProgressPreListHook } from "../progress/store.js";
|
|
33
33
|
import { getSkillNameFromPath } from "../resources/metadata.js";
|
|
34
34
|
import { resourceList, resourceListAccessible, resourceGet, ensurePersonalDefaults, SHARED_OWNER, WORKSPACE_OWNER, } from "../resources/store.js";
|
|
@@ -309,36 +309,43 @@ export function createAgentChatPlugin(options) {
|
|
|
309
309
|
const mcpActionEntries = {};
|
|
310
310
|
let mcpInitializationPromise = null;
|
|
311
311
|
const initializeMcpManager = async () => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return null;
|
|
315
|
-
});
|
|
316
|
-
if (!mcpConfig) {
|
|
317
|
-
mcpConfig = loadMcpConfig() ?? autoDetectMcpConfig();
|
|
318
|
-
if (mcpConfig?.source) {
|
|
319
|
-
console.log(`[mcp-client] loaded config from ${mcpConfig.source} (${Object.keys(mcpConfig.servers).length} server(s))`);
|
|
320
|
-
}
|
|
321
|
-
else if (process.env.DEBUG) {
|
|
322
|
-
console.log("[mcp-client] no configured MCP servers — skipping MCP tools");
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
else if (mcpConfig.source) {
|
|
312
|
+
const mcpConfig = await buildMergedConfig();
|
|
313
|
+
if (mcpConfig?.source) {
|
|
326
314
|
console.log(`[mcp-client] merged config (${Object.keys(mcpConfig.servers).length} server(s), source: ${mcpConfig.source})`);
|
|
327
315
|
}
|
|
328
|
-
|
|
329
|
-
|
|
316
|
+
else if (process.env.DEBUG) {
|
|
317
|
+
console.log("[mcp-client] no configured MCP servers — skipping MCP tools");
|
|
330
318
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
319
|
+
await mcpManager.reconfigure(mcpConfig);
|
|
320
|
+
startMcpConfigRefresh(mcpManager);
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* Start MCP initialization at most once, and return the run in flight.
|
|
324
|
+
*
|
|
325
|
+
* On a serverless function nothing kicks this off at boot (see the
|
|
326
|
+
* `isProductionServerlessFunctionRuntime` gate below), so every surface
|
|
327
|
+
* that actually consumes MCP tools must await this or the manager stays
|
|
328
|
+
* empty for the life of the container.
|
|
329
|
+
*/
|
|
330
|
+
const ensureMcpInitialized = () => {
|
|
331
|
+
if (!mcpInitializationPromise) {
|
|
332
|
+
mcpInitializationPromise = initializeMcpManager().catch((err) => {
|
|
333
|
+
// Do not cache a settings outage as a successful empty manager;
|
|
334
|
+
// the next MCP-consuming request must retry the configuration read.
|
|
335
|
+
mcpInitializationPromise = null;
|
|
336
|
+
throw err;
|
|
337
|
+
});
|
|
336
338
|
}
|
|
339
|
+
return mcpInitializationPromise;
|
|
337
340
|
};
|
|
338
|
-
const getJobMcpActionEntries = (job) => {
|
|
341
|
+
const getJobMcpActionEntries = async (job) => {
|
|
339
342
|
const requested = job?.meta.mcpTools ?? [];
|
|
340
343
|
if (requested.length === 0)
|
|
341
344
|
return {};
|
|
345
|
+
// Background action suppliers may be async so event-triggered and
|
|
346
|
+
// scheduled runs can await lazy MCP hydration on serverless cold
|
|
347
|
+
// starts. Runs without requested MCP tools still skip this work.
|
|
348
|
+
await ensureMcpInitialized();
|
|
342
349
|
const entries = mcpToolsToActionEntries(mcpManager, {
|
|
343
350
|
toolNames: requested,
|
|
344
351
|
});
|
|
@@ -354,7 +361,7 @@ export function createAgentChatPlugin(options) {
|
|
|
354
361
|
mountMcpServersRoutes(nitroApp, mcpManager, {
|
|
355
362
|
// Serialize an unusually early settings mutation behind the initial
|
|
356
363
|
// config snapshot so stale startup data cannot overwrite the write.
|
|
357
|
-
waitUntilReady:
|
|
364
|
+
waitUntilReady: ensureMcpInitialized,
|
|
358
365
|
});
|
|
359
366
|
// Hub-serve: expose org-scope servers to other agent-native apps in the
|
|
360
367
|
// workspace when `AGENT_NATIVE_MCP_HUB_TOKEN` is set (dispatch, by
|
|
@@ -825,25 +832,28 @@ export function createAgentChatPlugin(options) {
|
|
|
825
832
|
// background surface. Interactive-only tools stay out of it, while
|
|
826
833
|
// shared capabilities such as call-agent and core-send-email cannot
|
|
827
834
|
// drift independently between the two background entry points.
|
|
828
|
-
const getBackgroundActionEntries = (automation) =>
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
835
|
+
const getBackgroundActionEntries = async (automation) => {
|
|
836
|
+
const mcpActions = await getJobMcpActionEntries(automation);
|
|
837
|
+
return {
|
|
838
|
+
...templateScripts,
|
|
839
|
+
...resourceScripts,
|
|
840
|
+
...docsScripts,
|
|
841
|
+
...(lazyContext ? frameworkContextTool : {}),
|
|
842
|
+
...urlTools,
|
|
843
|
+
...chatScripts,
|
|
844
|
+
...callAgentScript,
|
|
845
|
+
...jobTools,
|
|
846
|
+
...automationTools,
|
|
847
|
+
...notificationTools,
|
|
848
|
+
...progressTools,
|
|
849
|
+
...fetchTool,
|
|
850
|
+
...webSearchTool,
|
|
851
|
+
...toolActions,
|
|
852
|
+
...backgroundCoreEmailTools,
|
|
853
|
+
...coreAttachmentTools,
|
|
854
|
+
...mcpActions,
|
|
855
|
+
};
|
|
856
|
+
};
|
|
847
857
|
// -----------------------------------------------------------------------
|
|
848
858
|
// Production code-execution mode resolution.
|
|
849
859
|
//
|
|
@@ -4514,6 +4524,10 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
4514
4524
|
// the background worker), so both go through identical context + handler
|
|
4515
4525
|
// selection.
|
|
4516
4526
|
const invokeAgentChatHandler = async (event) => {
|
|
4527
|
+
// Chat is the main MCP consumer, and on serverless nothing hydrated the
|
|
4528
|
+
// manager at boot. Awaiting here trades first-chat latency for tools
|
|
4529
|
+
// that are actually present instead of a silently MCP-less run.
|
|
4530
|
+
await ensureMcpInitialized();
|
|
4517
4531
|
// Resolve per-request auth context.
|
|
4518
4532
|
const ownerContext = await resolveOwnerContext(event);
|
|
4519
4533
|
return runWithAgentRunContext({
|
|
@@ -4851,6 +4865,10 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
4851
4865
|
};
|
|
4852
4866
|
}
|
|
4853
4867
|
try {
|
|
4868
|
+
// Jobs may request MCP tools, and `getActions` is synchronous —
|
|
4869
|
+
// hydrate before the sweep so a serverless container that never
|
|
4870
|
+
// eagerly initialized still resolves them.
|
|
4871
|
+
await ensureMcpInitialized();
|
|
4854
4872
|
await processRecurringJobs(schedulerDeps);
|
|
4855
4873
|
return { ok: true };
|
|
4856
4874
|
}
|
|
@@ -4913,10 +4931,17 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
4913
4931
|
setInterval(() => void sweep(), 30_000);
|
|
4914
4932
|
})();
|
|
4915
4933
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4934
|
+
// Long-lived runtimes hydrate MCP once at boot. Serverless functions must
|
|
4935
|
+
// not: nothing awaits this, so a settings scan plus third-party MCP
|
|
4936
|
+
// handshakes run on EVERY cold start of every app — including requests
|
|
4937
|
+
// that never touch MCP — and outlive the response on a runtime that
|
|
4938
|
+
// freezes after responding. There, `ensureMcpInitialized()` is driven by
|
|
4939
|
+
// the surfaces that consume MCP tools instead.
|
|
4940
|
+
if (!isProductionServerlessFunctionRuntime()) {
|
|
4941
|
+
void ensureMcpInitialized().catch((err) => {
|
|
4942
|
+
console.warn(`[mcp-client] eager initialization failed: ${err?.message ?? err}`);
|
|
4943
|
+
});
|
|
4944
|
+
}
|
|
4920
4945
|
// ─── Agent Teams orphan sweep ─────────────────────────────────────
|
|
4921
4946
|
// Re-fires stuck/queued dispatches when the browser is closed and the
|
|
4922
4947
|
// RunsTray's per-user reconciliation never triggers. Runs every 2 minutes
|
|
@@ -13,10 +13,21 @@ const TRACKING_INGEST_PATHS = new Set([
|
|
|
13
13
|
"/api/events/track",
|
|
14
14
|
"/_agent-native/track",
|
|
15
15
|
]);
|
|
16
|
+
const SLOW_REQUEST_MS = 1_000;
|
|
17
|
+
const SLOW_REQUEST_LOG_EVENT = "agent-native.slow_request";
|
|
16
18
|
const PROCESS_STATE_KEY = Symbol.for("@agent-native/core/http-response-telemetry.process-state");
|
|
17
19
|
const globalRef = globalThis;
|
|
20
|
+
// Process start → this module being evaluated. On a serverless cold start that
|
|
21
|
+
// span is the platform's container boot plus server-bundle evaluation, which
|
|
22
|
+
// happens entirely before any request handler runs and is therefore invisible
|
|
23
|
+
// to every in-handler measurement. Recorded here because module scope is the
|
|
24
|
+
// earliest point our own code can observe. Stored on globalThis so the
|
|
25
|
+
// earliest-evaluated copy wins if the bundle loads this module twice.
|
|
18
26
|
const processState = globalRef[PROCESS_STATE_KEY] ??
|
|
19
|
-
(globalRef[PROCESS_STATE_KEY] = {
|
|
27
|
+
(globalRef[PROCESS_STATE_KEY] = {
|
|
28
|
+
requestSequence: 0,
|
|
29
|
+
moduleEvalUptimeMs: Math.max(0, Math.round(process.uptime() * 1_000)),
|
|
30
|
+
});
|
|
20
31
|
const REQUEST_TELEMETRY_KEY = Symbol.for("@agent-native/core/http-response-telemetry.request");
|
|
21
32
|
const installedApps = new WeakSet();
|
|
22
33
|
function envValue(key) {
|
|
@@ -118,7 +129,7 @@ function shouldTrack(pathname, statusCode, state) {
|
|
|
118
129
|
return true;
|
|
119
130
|
if (state.startupDb)
|
|
120
131
|
return true;
|
|
121
|
-
if (Date.now() - state.startedAt >=
|
|
132
|
+
if (Date.now() - state.startedAt >= SLOW_REQUEST_MS)
|
|
122
133
|
return true;
|
|
123
134
|
if (state.db.errorCount > 0 || state.db.timeoutCount > 0) {
|
|
124
135
|
return true;
|
|
@@ -151,6 +162,10 @@ function runtimeProvider() {
|
|
|
151
162
|
return "cloudflare-pages";
|
|
152
163
|
return "node";
|
|
153
164
|
}
|
|
165
|
+
/** Module evaluation → this request starting, i.e. idle boot the app paid for. */
|
|
166
|
+
function moduleToRequestMs(state) {
|
|
167
|
+
return Math.max(0, state.processAgeAtStartMs - processState.moduleEvalUptimeMs);
|
|
168
|
+
}
|
|
154
169
|
function emitTelemetry(event, state, response) {
|
|
155
170
|
const statusCode = responseStatusCode(event, response);
|
|
156
171
|
const pathname = requestPath(event);
|
|
@@ -175,6 +190,8 @@ function emitTelemetry(event, state, response) {
|
|
|
175
190
|
cold_start: state.requestSequence === 1,
|
|
176
191
|
request_sequence: state.requestSequence,
|
|
177
192
|
process_age_ms: state.processAgeAtStartMs,
|
|
193
|
+
boot_to_module_ms: processState.moduleEvalUptimeMs,
|
|
194
|
+
module_to_request_ms: moduleToRequestMs(state),
|
|
178
195
|
framework_ready_wait_ms: state.frameworkReadyWaitMs,
|
|
179
196
|
runtime_provider: runtimeProvider(),
|
|
180
197
|
function_name: envValue("AWS_LAMBDA_FUNCTION_NAME"),
|
|
@@ -238,20 +255,99 @@ function requestTelemetryState(event) {
|
|
|
238
255
|
export function getHttpRequestTelemetryId(event) {
|
|
239
256
|
return requestTelemetryState(event)?.requestId;
|
|
240
257
|
}
|
|
241
|
-
function appendServerTiming(response, event, name, durationMs) {
|
|
258
|
+
function appendServerTiming(response, event, name, durationMs, desc) {
|
|
242
259
|
const duration = Math.max(0, Math.round(durationMs));
|
|
260
|
+
const suffix = desc ? `;desc=${JSON.stringify(desc)}` : "";
|
|
243
261
|
try {
|
|
244
|
-
response.headers.append("server-timing", `${name};dur=${duration}`);
|
|
262
|
+
response.headers.append("server-timing", `${name};dur=${duration}${suffix}`);
|
|
245
263
|
}
|
|
246
264
|
catch {
|
|
247
265
|
try {
|
|
248
|
-
setServerTiming(event, name, { dur: duration });
|
|
266
|
+
setServerTiming(event, name, desc ? { dur: duration, desc } : { dur: duration });
|
|
249
267
|
}
|
|
250
268
|
catch {
|
|
251
269
|
// Some adapters finalize headers eagerly. Tracking still runs.
|
|
252
270
|
}
|
|
253
271
|
}
|
|
254
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Does this response get stored in a shared (CDN) cache and replayed?
|
|
275
|
+
*
|
|
276
|
+
* SSR HTML and React Router `.data` are one impersonal shell hard-cached for
|
|
277
|
+
* every visitor, so their headers are written ONCE by the origin render and
|
|
278
|
+
* then handed unchanged to everyone who hits the cache afterwards.
|
|
279
|
+
*/
|
|
280
|
+
function isSharedCacheable(response) {
|
|
281
|
+
const cacheControl = response.headers.get("cache-control")?.toLowerCase() ?? "";
|
|
282
|
+
if (!cacheControl)
|
|
283
|
+
return false;
|
|
284
|
+
if (/\b(?:no-store|no-cache|private)\b/.test(cacheControl))
|
|
285
|
+
return false;
|
|
286
|
+
return (/\bpublic\b/.test(cacheControl) || /\bs-maxage=[1-9]/.test(cacheControl));
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Per-phase `server-timing` entries do NOT belong on a shared-cacheable
|
|
290
|
+
* response. `app;dur=2159` on a cached shell describes one origin render from
|
|
291
|
+
* an arbitrary point in the past, yet every later visitor reads it as the cost
|
|
292
|
+
* of their own request — a stale number wearing a live number's name.
|
|
293
|
+
*
|
|
294
|
+
* So a cacheable response gets exactly one entry, `origin`, whose description
|
|
295
|
+
* leads with the absolute wall-clock time of the render that produced it. Two
|
|
296
|
+
* visitors comparing notes see the identical timestamp, which is what a replay
|
|
297
|
+
* is. The live per-invocation breakdown goes to the slow-request log line and
|
|
298
|
+
* to tracking instead; neither is ever cached.
|
|
299
|
+
*/
|
|
300
|
+
function originSnapshotDesc(state) {
|
|
301
|
+
const parts = [new Date(state.startedAt).toISOString()];
|
|
302
|
+
if (state.requestSequence === 1) {
|
|
303
|
+
parts.push("cold", `boot=${processState.moduleEvalUptimeMs}`, `init=${moduleToRequestMs(state)}`);
|
|
304
|
+
}
|
|
305
|
+
if (state.frameworkReadyWaitMs > 0) {
|
|
306
|
+
parts.push(`startup=${Math.round(state.frameworkReadyWaitMs)}`);
|
|
307
|
+
}
|
|
308
|
+
if (state.db.operationCount > 0) {
|
|
309
|
+
parts.push(`db=${Math.round(state.db.operationWallMs)}`, `dbops=${state.db.operationCount}`);
|
|
310
|
+
}
|
|
311
|
+
return parts.join(" ");
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* One structured line per cold or slow request, straight to stdout.
|
|
315
|
+
*
|
|
316
|
+
* Function logs are the only timing surface readable without a deploy, and
|
|
317
|
+
* `track()` silently no-ops when no tracking provider is registered — which is
|
|
318
|
+
* the common case. Deliberately NOT wrapped in a catch: a swallowed emit would
|
|
319
|
+
* leave a slow request indistinguishable from a fast one.
|
|
320
|
+
*/
|
|
321
|
+
function logSlowRequest(event, state, response, durationMs, pathname) {
|
|
322
|
+
const coldStart = state.requestSequence === 1;
|
|
323
|
+
if (!coldStart && durationMs < SLOW_REQUEST_MS)
|
|
324
|
+
return;
|
|
325
|
+
console.log(JSON.stringify({
|
|
326
|
+
event: SLOW_REQUEST_LOG_EVENT,
|
|
327
|
+
app: getAppName(),
|
|
328
|
+
method: getMethod(event),
|
|
329
|
+
path: normalizeHttpTelemetryPath(pathname),
|
|
330
|
+
status: responseStatusCode(event, response),
|
|
331
|
+
duration_ms: Math.round(durationMs),
|
|
332
|
+
cold_start: coldStart,
|
|
333
|
+
request_sequence: state.requestSequence,
|
|
334
|
+
boot_to_module_ms: processState.moduleEvalUptimeMs,
|
|
335
|
+
module_to_request_ms: moduleToRequestMs(state),
|
|
336
|
+
process_age_ms: state.processAgeAtStartMs,
|
|
337
|
+
framework_ready_wait_ms: Math.round(state.frameworkReadyWaitMs),
|
|
338
|
+
db_ms: Math.round(state.db.operationWallMs),
|
|
339
|
+
db_connect_ms: Math.round(state.db.connectTotalMs),
|
|
340
|
+
db_operation_count: state.db.operationCount,
|
|
341
|
+
db_error_count: state.db.errorCount,
|
|
342
|
+
db_timeout_count: state.db.timeoutCount,
|
|
343
|
+
startup_db_ms: state.startupDb
|
|
344
|
+
? Math.round(state.startupDb.operationWallMs)
|
|
345
|
+
: undefined,
|
|
346
|
+
shared_cacheable: response ? isSharedCacheable(response) : undefined,
|
|
347
|
+
runtime_provider: runtimeProvider(),
|
|
348
|
+
request_id: state.requestId,
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
255
351
|
export function recordFrameworkReadyWait(event, durationMs) {
|
|
256
352
|
const state = requestTelemetryState(event);
|
|
257
353
|
if (state) {
|
|
@@ -295,7 +391,17 @@ export function installHttpResponseTelemetryHooks(nitroApp) {
|
|
|
295
391
|
// Some adapters finalize headers eagerly. Tracking still has the id.
|
|
296
392
|
}
|
|
297
393
|
}
|
|
394
|
+
if (isSharedCacheable(response)) {
|
|
395
|
+
appendServerTiming(response, event, "origin", durationMs, originSnapshotDesc(state));
|
|
396
|
+
logSlowRequest(event, state, response, durationMs, requestPath(event));
|
|
397
|
+
emitTelemetry(event, state, response);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
298
400
|
appendServerTiming(response, event, "app", durationMs);
|
|
401
|
+
if (state.requestSequence === 1) {
|
|
402
|
+
appendServerTiming(response, event, "boot", processState.moduleEvalUptimeMs);
|
|
403
|
+
appendServerTiming(response, event, "init", moduleToRequestMs(state));
|
|
404
|
+
}
|
|
299
405
|
if (state.frameworkReadyWaitMs > 0) {
|
|
300
406
|
appendServerTiming(response, event, "startup", state.frameworkReadyWaitMs);
|
|
301
407
|
}
|
|
@@ -309,6 +415,7 @@ export function installHttpResponseTelemetryHooks(nitroApp) {
|
|
|
309
415
|
appendServerTiming(response, event, "startup-db", state.startupDb.operationWallMs);
|
|
310
416
|
appendServerTiming(response, event, "startup-db-connect", state.startupDb.connectTotalMs);
|
|
311
417
|
}
|
|
418
|
+
logSlowRequest(event, state, response, durationMs, requestPath(event));
|
|
312
419
|
emitTelemetry(event, state, response);
|
|
313
420
|
});
|
|
314
421
|
}
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -2,6 +2,7 @@ import { CONTEXT_XRAY_MIGRATIONS } from "../agent/context-xray/migrations.js";
|
|
|
2
2
|
import { OBSERVATIONAL_MEMORY_MIGRATIONS } from "../agent/observational-memory/migrations.js";
|
|
3
3
|
import { runMigrations } from "../db/migrations.js";
|
|
4
4
|
import { runAutomationRunMigrations } from "../jobs/run-history.js";
|
|
5
|
+
import { runAutomationSchedulerHealthMigrations } from "../jobs/scheduler-health.js";
|
|
5
6
|
import { ORG_MIGRATIONS } from "../org/migrations.js";
|
|
6
7
|
import { runBetterAuthMigrations } from "./better-auth-migrations.js";
|
|
7
8
|
/**
|
|
@@ -21,4 +22,5 @@ export async function runFrameworkReleaseMigrations(nitroApp) {
|
|
|
21
22
|
table: "_observational_memory_migrations",
|
|
22
23
|
})(nitroApp);
|
|
23
24
|
await runAutomationRunMigrations(nitroApp);
|
|
25
|
+
await runAutomationSchedulerHealthMigrations(nitroApp);
|
|
24
26
|
}
|
|
@@ -193,6 +193,14 @@ function isSsrHtmlOrDataResponse(headers, status, pathname) {
|
|
|
193
193
|
* │ hatch — because that is what poisons a shared CDN cache key. A value fixed │
|
|
194
194
|
* │ for the whole deployment cannot. │
|
|
195
195
|
* └──────────────────────────────────────────────────────────────────────────┘
|
|
196
|
+
*
|
|
197
|
+
* The same sharing rule governs any DIAGNOSTIC header on this response. A
|
|
198
|
+
* per-request timing written here is stored once by the origin render and
|
|
199
|
+
* replayed unchanged to every later visitor, so it must not wear a live-looking
|
|
200
|
+
* phase name. `installHttpResponseTelemetryHooks` detects the shared-cacheable
|
|
201
|
+
* policy stamped below and collapses `server-timing` to one `origin` entry
|
|
202
|
+
* carrying the render's wall-clock time; do not add a per-request header here
|
|
203
|
+
* that contradicts that.
|
|
196
204
|
*/
|
|
197
205
|
function applyDefaultSsrCacheHeader(headers, status, pathname) {
|
|
198
206
|
if (!isSsrHtmlOrDataResponse(headers, status, pathname))
|
|
@@ -42,7 +42,10 @@ function viewForPath(pathname: string): string {
|
|
|
42
42
|
if (pathname.startsWith("/database")) return "database";
|
|
43
43
|
if (pathname.startsWith("/extensions")) return "extensions";
|
|
44
44
|
if (pathname.startsWith("/observability")) return "observability";
|
|
45
|
-
if (pathname.startsWith("/agent")
|
|
45
|
+
if (pathname.startsWith("/settings/agent") || pathname.startsWith("/agent")) {
|
|
46
|
+
return "agent";
|
|
47
|
+
}
|
|
48
|
+
if (pathname.startsWith("/settings")) return "settings";
|
|
46
49
|
if (pathname.startsWith("/team")) return "settings";
|
|
47
50
|
return "chat";
|
|
48
51
|
}
|
|
@@ -60,7 +63,7 @@ function pathForView(view?: string): string {
|
|
|
60
63
|
case "observability":
|
|
61
64
|
return "/observability";
|
|
62
65
|
case "agent":
|
|
63
|
-
return "/agent";
|
|
66
|
+
return "/settings/agent";
|
|
64
67
|
case "settings":
|
|
65
68
|
return "/settings";
|
|
66
69
|
case "team":
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
|
|
1
|
+
import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
|
|
3
2
|
import { Navigate, useLocation } from "react-router";
|
|
4
3
|
|
|
5
4
|
export function meta() {
|
|
@@ -7,34 +6,11 @@ export function meta() {
|
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
export default function AgentRoute() {
|
|
10
|
-
const t = useT();
|
|
11
9
|
const location = useLocation();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"agents",
|
|
19
|
-
"memory",
|
|
20
|
-
"skills",
|
|
21
|
-
"learnings",
|
|
22
|
-
]);
|
|
23
|
-
const destination = resourceTabs.has(legacyTab)
|
|
24
|
-
? `/settings/agent/resources/${legacyTab}`
|
|
25
|
-
: legacyTab === "remote-agents"
|
|
26
|
-
? "/settings/agent/agents"
|
|
27
|
-
: legacyTab === "connections"
|
|
28
|
-
? "/settings/integrations"
|
|
29
|
-
: legacyTab === "jobs"
|
|
30
|
-
? "/settings/agent/automations"
|
|
31
|
-
: legacyTab === "access"
|
|
32
|
-
? "/settings/agent"
|
|
33
|
-
: ["llm", "app-models", "limits", "voice", "background"].includes(
|
|
34
|
-
legacyTab,
|
|
35
|
-
)
|
|
36
|
-
? `/settings/agent/${legacyTab}`
|
|
37
|
-
: "/settings/agent";
|
|
38
|
-
|
|
39
|
-
return <Navigate to={destination} replace />;
|
|
10
|
+
return (
|
|
11
|
+
<Navigate
|
|
12
|
+
to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
|
|
13
|
+
replace
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
40
16
|
}
|
|
@@ -76,7 +76,7 @@ export default function IndexPage() {
|
|
|
76
76
|
</p>
|
|
77
77
|
</Link>
|
|
78
78
|
<Link
|
|
79
|
-
to="/agent"
|
|
79
|
+
to="/settings/agent"
|
|
80
80
|
className="group rounded-lg border border-border/50 px-4 py-3 hover:bg-accent/50 transition-colors"
|
|
81
81
|
>
|
|
82
82
|
<p className="text-[13px] font-medium text-foreground">
|
|
@@ -1,34 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Link } from "react-router";
|
|
1
|
+
import { buildLegacyAgentSettingsRoute } from "@agent-native/core/client/navigation";
|
|
2
|
+
import { Navigate, useLocation } from "react-router";
|
|
4
3
|
|
|
5
4
|
export function meta() {
|
|
6
5
|
return [{ title: "Agent - {{APP_TITLE}}" }];
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
export default function AgentPage() {
|
|
10
|
-
const
|
|
11
|
-
|
|
9
|
+
const location = useLocation();
|
|
12
10
|
return (
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
to="/settings"
|
|
18
|
-
className="text-[13px] text-muted-foreground hover:text-foreground"
|
|
19
|
-
>
|
|
20
|
-
{t("settings.title")}
|
|
21
|
-
</Link>
|
|
22
|
-
<h1 className="text-2xl font-semibold tracking-tight text-foreground">
|
|
23
|
-
{t("settings.agentTitle")}
|
|
24
|
-
</h1>
|
|
25
|
-
<p className="text-[14px] leading-relaxed text-muted-foreground">
|
|
26
|
-
{t("settings.agentDescription")}
|
|
27
|
-
</p>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<AgentTabsPage className="min-h-[640px] rounded-lg border border-border/50" />
|
|
31
|
-
</div>
|
|
32
|
-
</main>
|
|
11
|
+
<Navigate
|
|
12
|
+
to={buildLegacyAgentSettingsRoute(location.hash, location.search)}
|
|
13
|
+
replace
|
|
14
|
+
/>
|
|
33
15
|
);
|
|
34
16
|
}
|