@agent-native/core 0.145.2 → 0.145.4
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/README.md +1 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +10 -8
- package/corpus/templates/assets/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +22 -1
- package/corpus/templates/mail/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/server/lib/plan-framework-tools.ts +11 -0
- package/corpus/templates/plan/server/plugins/00-mcp.ts +25 -68
- package/corpus/templates/plan/server/plugins/agent-chat.ts +8 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/framework-tools.d.ts +33 -1
- package/dist/framework-tools.js +88 -4
- package/dist/mcp/build-server.d.ts +15 -0
- package/dist/mcp/build-server.js +94 -30
- package/dist/mcp/oauth-client-metadata.js +6 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/server/action-discovery.d.ts +3 -18
- package/dist/server/action-discovery.js +9 -65
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -2
- package/dist/server/agent-chat/action-filters-a2a.js +1 -1
- package/dist/server/agent-chat/mcp-options.d.ts +106 -0
- package/dist/server/agent-chat/mcp-options.js +50 -0
- package/dist/server/agent-chat/plugin-options.d.ts +17 -26
- package/dist/server/agent-chat-plugin.js +21 -11
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +7 -0
- package/dist/server/poll.d.ts +11 -0
- package/dist/server/poll.js +7 -4
- package/dist/server/realtime-token.js +8 -0
- package/dist/server/short-lived-token.d.ts +8 -0
- package/dist/server/short-lived-token.js +12 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import type { FrameworkToolsConfig } from "../../framework-tools.js";
|
|
|
5
5
|
import type { ExternalAgentPolicy } from "../../mcp/external-agent-policy.js";
|
|
6
6
|
import type { DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
|
|
7
7
|
import type { PromptExamples } from "../prompts/index.js";
|
|
8
|
+
import type { AgentChatMcpIcon, AgentChatMcpOptions } from "./mcp-options.js";
|
|
8
9
|
/** Shape of a Nitro plugin function: receives the Nitro app instance at
|
|
9
10
|
* startup and may register routes/hooks synchronously or asynchronously. */
|
|
10
11
|
export type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
|
|
@@ -51,21 +52,23 @@ export interface AgentChatPluginOptions {
|
|
|
51
52
|
mentionProviders?: Record<string, MentionProvider> | (() => Record<string, MentionProvider> | Promise<Record<string, MentionProvider>>);
|
|
52
53
|
/** App ID used to exclude self from agent discovery (e.g., "mail", "calendar") */
|
|
53
54
|
appId?: string;
|
|
54
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Everything about this app's MCP mount — whether it is mounted, which tools
|
|
57
|
+
* external callers see, and the branding sent during the `initialize`
|
|
58
|
+
* handshake. See `AgentChatMcpOptions`.
|
|
59
|
+
*
|
|
60
|
+
* Replaces the top-level `disableMcp`, `mcpServerInfo`, `connectorCatalog`,
|
|
61
|
+
* and `externalAgents`, which stay accepted for one minor. Setting both
|
|
62
|
+
* forms to disagreeing values throws at plugin init rather than silently
|
|
63
|
+
* picking one.
|
|
64
|
+
*/
|
|
65
|
+
mcp?: AgentChatMcpOptions;
|
|
66
|
+
/** @deprecated Use `mcp.title` / `mcp.description` / `mcp.websiteUrl` / `mcp.icons`. */
|
|
55
67
|
mcpServerInfo?: {
|
|
56
|
-
/** Human-facing title. Defaults to the capitalized app id/name. */
|
|
57
68
|
title?: string;
|
|
58
|
-
/** Host-facing description. Defaults to "Agent-native <app> agent". */
|
|
59
69
|
description?: string;
|
|
60
|
-
/** Canonical app URL. Relative URLs are resolved against the request origin. */
|
|
61
70
|
websiteUrl?: string;
|
|
62
|
-
|
|
63
|
-
icons?: Array<{
|
|
64
|
-
src: string;
|
|
65
|
-
mimeType?: string;
|
|
66
|
-
sizes?: string[];
|
|
67
|
-
theme?: "light" | "dark";
|
|
68
|
-
}>;
|
|
71
|
+
icons?: AgentChatMcpIcon[];
|
|
69
72
|
};
|
|
70
73
|
/**
|
|
71
74
|
* Optional callback to resolve the org ID for the current request.
|
|
@@ -330,15 +333,10 @@ export interface AgentChatPluginOptions {
|
|
|
330
333
|
* claim in their connect-minted JWT) or the deployment-wide
|
|
331
334
|
* `AGENT_NATIVE_MCP_FULL_CATALOG=1` env override.
|
|
332
335
|
*
|
|
333
|
-
*
|
|
336
|
+
* @deprecated Use `mcp.connectorCatalog`.
|
|
334
337
|
*/
|
|
335
338
|
connectorCatalog?: string[];
|
|
336
|
-
/**
|
|
337
|
-
* Default authenticated external-agent policy. In `auto` read mode, every
|
|
338
|
-
* action explicitly marked as GET + readOnly + publicAgent.requiresAuth is
|
|
339
|
-
* added to the connector surface automatically. Writes remain ask_app-only
|
|
340
|
-
* unless `writes: "allowlisted"` is explicitly selected.
|
|
341
|
-
*/
|
|
339
|
+
/** @deprecated Use `mcp.externalAgents`. */
|
|
342
340
|
externalAgents?: ExternalAgentPolicy;
|
|
343
341
|
/**
|
|
344
342
|
* Allow this app's A2A agent to delegate to a different app through the
|
|
@@ -359,14 +357,7 @@ export interface AgentChatPluginOptions {
|
|
|
359
357
|
maxRunInputTokens?: number;
|
|
360
358
|
maxToolResultChars?: number;
|
|
361
359
|
};
|
|
362
|
-
/**
|
|
363
|
-
* Skip mounting the remote MCP protocol route.
|
|
364
|
-
*
|
|
365
|
-
* Most apps should leave this off so agent chat, A2A, and MCP share one
|
|
366
|
-
* runtime. Hosted apps with a dedicated early MCP plugin can set this to
|
|
367
|
-
* true so their external connector does not depend on the heavier chat
|
|
368
|
-
* plugin initialization path.
|
|
369
|
-
*/
|
|
360
|
+
/** @deprecated Use `mcp: { enabled: false }`. */
|
|
370
361
|
disableMcp?: boolean;
|
|
371
362
|
/**
|
|
372
363
|
* Code-execution capability for the production agent.
|
|
@@ -110,6 +110,7 @@ import { createA2AEngineToolSurface, DEFAULT_DELEGATED_MAX_ITERATIONS, DEFAULT_D
|
|
|
110
110
|
import { createBuilderBrowserTool, createTeamTools, } from "./agent-chat/browser-team-tools.js";
|
|
111
111
|
import { createDataWidgetActionEntries, createFrameworkContextEntry, createRefreshScreenEntry, createUrlTools, } from "./agent-chat/context-tools.js";
|
|
112
112
|
import { _agentChatPromptSectionsForTests, buildFrameworkPrompts, buildSchemaBlock, collectFiles, corpusToolNamesTaughtByPrompt, generateActionsPrompt, generateCorpusToolsPrompt, } from "./agent-chat/framework-prompts.js";
|
|
113
|
+
import { resolveAgentChatMcpOptions } from "./agent-chat/mcp-options.js";
|
|
113
114
|
import { resolveA2AAgentDelegationEnabled, } from "./agent-chat/plugin-options.js";
|
|
114
115
|
import { finalizeClaimedAgentChatProcessRunFailure } from "./agent-chat/process-run-failure.js";
|
|
115
116
|
import { loadResourcesForPrompt, promptResourceManifestSections, resourceScopeForOwner, } from "./agent-chat/prompt-resources.js";
|
|
@@ -286,6 +287,11 @@ export function createAgentChatPlugin(options) {
|
|
|
286
287
|
// here rather than booting with a surface nobody chose.
|
|
287
288
|
const frameworkTools = resolveFrameworkTools(options);
|
|
288
289
|
const disabledFrameworkGroups = frameworkTools.disabledGroups;
|
|
290
|
+
// Same treatment for the MCP mount: one resolved shape, folding the
|
|
291
|
+
// deprecated `disableMcp` / `mcpServerInfo` / `connectorCatalog` /
|
|
292
|
+
// `externalAgents` into `mcp`. A2A reads the same object, so the
|
|
293
|
+
// connector policy cannot diverge between the two external surfaces.
|
|
294
|
+
const mcpOptions = resolveAgentChatMcpOptions(options);
|
|
289
295
|
// Build the four assembled system prompt strings. These are static for the
|
|
290
296
|
// lifetime of this plugin instance — examples come from options once at
|
|
291
297
|
// startup, not per-request.
|
|
@@ -1055,12 +1061,12 @@ export function createAgentChatPlugin(options) {
|
|
|
1055
1061
|
: "Agent",
|
|
1056
1062
|
description: `Agent-native ${options?.appId ?? "app"} agent`,
|
|
1057
1063
|
skills: buildPublicAgentA2ASkills(allScripts),
|
|
1058
|
-
authenticatedSkills: buildAuthenticatedAgentA2ASkills(mcpFullActions ?? allScripts,
|
|
1064
|
+
authenticatedSkills: buildAuthenticatedAgentA2ASkills(mcpFullActions ?? allScripts, mcpOptions),
|
|
1059
1065
|
publicSkillsOnly: true,
|
|
1060
1066
|
streaming: true,
|
|
1061
1067
|
durableBackgroundRuns: options?.durableBackgroundRuns,
|
|
1062
1068
|
executeReadOnlyAction: async ({ action, input, invocationId }) => {
|
|
1063
|
-
const actions = filterDirectA2AActions(mcpFullActions ?? allScripts,
|
|
1069
|
+
const actions = filterDirectA2AActions(mcpFullActions ?? allScripts, mcpOptions);
|
|
1064
1070
|
const entry = actions[action];
|
|
1065
1071
|
if (!entry) {
|
|
1066
1072
|
return {
|
|
@@ -1667,26 +1673,30 @@ export function createAgentChatPlugin(options) {
|
|
|
1667
1673
|
: DEV_FRAMEWORK_PROMPT) + devActionsPrompt;
|
|
1668
1674
|
// Keep legacy names for the composition below
|
|
1669
1675
|
const basePrompt = prodPrompt;
|
|
1670
|
-
if (
|
|
1676
|
+
if (mcpOptions.enabled) {
|
|
1671
1677
|
// Mount MCP remote server — same action registry as A2A + agent chat
|
|
1672
1678
|
const { mountMCP } = await import("../mcp/server.js");
|
|
1673
1679
|
mountMCP(nitroApp, {
|
|
1674
1680
|
name: options?.appId
|
|
1675
1681
|
? options.appId.charAt(0).toUpperCase() + options.appId.slice(1)
|
|
1676
1682
|
: "Agent",
|
|
1677
|
-
title:
|
|
1683
|
+
title: mcpOptions.title,
|
|
1678
1684
|
appId: options?.appId,
|
|
1679
|
-
description:
|
|
1685
|
+
description: mcpOptions.description ??
|
|
1680
1686
|
`Agent-native ${options?.appId ?? "app"} agent`,
|
|
1681
|
-
websiteUrl:
|
|
1682
|
-
icons:
|
|
1687
|
+
websiteUrl: mcpOptions.websiteUrl,
|
|
1688
|
+
icons: mcpOptions.icons,
|
|
1683
1689
|
actions: allScripts,
|
|
1684
1690
|
productionActions: mcpFullActions,
|
|
1685
|
-
...(
|
|
1686
|
-
|
|
1691
|
+
...(mcpOptions.catalog ? { catalogMode: mcpOptions.catalog } : {}),
|
|
1692
|
+
...(mcpOptions.builtinCrossAppTools !== undefined
|
|
1693
|
+
? { builtinCrossAppTools: mcpOptions.builtinCrossAppTools }
|
|
1687
1694
|
: {}),
|
|
1688
|
-
...(
|
|
1689
|
-
? {
|
|
1695
|
+
...(mcpOptions.connectorCatalog
|
|
1696
|
+
? { connectorCatalog: mcpOptions.connectorCatalog }
|
|
1697
|
+
: {}),
|
|
1698
|
+
...(mcpOptions.externalAgents
|
|
1699
|
+
? { externalAgents: mcpOptions.externalAgents }
|
|
1690
1700
|
: {}),
|
|
1691
1701
|
askAgent: async (message) => {
|
|
1692
1702
|
const ownerEmail = getRequestUserEmail();
|
package/dist/server/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export { createObservationalMemoryPlugin, defaultObservationalMemoryPlugin, } fr
|
|
|
32
32
|
export { createGoogleAuthPlugin, type GoogleAuthPluginOptions, } from "./google-auth-plugin.js";
|
|
33
33
|
export type { GoogleAuthMode } from "./google-auth-mode.js";
|
|
34
34
|
export { createAgentChatPlugin, defaultAgentChatPlugin, refreshGlobalMcpManager, type AgentChatPluginOptions, } from "./agent-chat-plugin.js";
|
|
35
|
+
export type { AgentChatMcpIcon, AgentChatMcpOptions, } from "./agent-chat/mcp-options.js";
|
|
35
36
|
export { configureAgentNativeEmbeddedEnvironment, createAgentNativeEmbeddedAuthOptions, createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, normalizeAgentNativeEmbeddedSession, type AgentNativeEmbeddedAuthOptions, type AgentNativeEmbeddedGetSession, type AgentNativeEmbeddedHostSession, type AgentNativeEmbeddedPluginOptions, } from "./embedded.js";
|
|
36
37
|
export { createThread, getThread, listThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, type ChatThread, type ChatThreadScope, type ChatThreadSummary, type ListThreadsOptions, } from "../chat-threads/store.js";
|
|
37
38
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
@@ -52,6 +53,8 @@ export { getH3App, awaitBootstrap, markDefaultPluginProvided, type H3AppShim, }
|
|
|
52
53
|
export { fireInternalDispatch, resolveSelfDispatchBaseUrl, type FireInternalDispatchOptions, } from "./self-dispatch.js";
|
|
53
54
|
export { extractBearerToken as extractInternalBearerToken, verifyInternalToken, } from "../integrations/internal-token.js";
|
|
54
55
|
export { autoDiscoverActions, autoDiscoverScripts, loadActionsFromStaticRegistry, mergeCoreSharingActions, registerPackageActions, } from "./action-discovery.js";
|
|
56
|
+
export { attachToolSearch, createToolSearchEntry, searchToolRegistry, TOOL_SEARCH_ACTION_NAME, } from "../agent/tool-search.js";
|
|
57
|
+
export { filterFrameworkToolGroups, frameworkGroupEnabled, resolveFrameworkTools, FRAMEWORK_TOOL_GROUPS, type FrameworkToolGroup, type FrameworkToolsConfig, type FrameworkToolsOption, type ResolvedFrameworkTools, } from "../framework-tools.js";
|
|
55
58
|
export { registerPromptContextProvider, type PromptContextProvider, type PromptContextProviderContext, type PromptContextProviderContribution, } from "./agent-chat/prompt-resources.js";
|
|
56
59
|
export { mountActionRoutes, type MountActionRoutesOptions, type ActionRouteAuthAdapter, type ActionRouteResolvedCaller, } from "./action-routes.js";
|
|
57
60
|
export { AGENT_RUN_OWNER_CONTEXT_KEY, seedAgentRunOwnerContext, type AgentRunOwnerContext, } from "./agent-run-context.js";
|
package/dist/server/index.js
CHANGED
|
@@ -53,6 +53,13 @@ export { getH3App, awaitBootstrap, markDefaultPluginProvided, } from "./framewor
|
|
|
53
53
|
export { fireInternalDispatch, resolveSelfDispatchBaseUrl, } from "./self-dispatch.js";
|
|
54
54
|
export { extractBearerToken as extractInternalBearerToken, verifyInternalToken, } from "../integrations/internal-token.js";
|
|
55
55
|
export { autoDiscoverActions, autoDiscoverScripts, loadActionsFromStaticRegistry, mergeCoreSharingActions, registerPackageActions, } from "./action-discovery.js";
|
|
56
|
+
// A standalone `mountMCP` plugin has to compose the same action surface the
|
|
57
|
+
// agent-chat plugin does. Without these, the only way to build one was to
|
|
58
|
+
// hand-roll a copy — which is how a template ends up with a `tool-search` that
|
|
59
|
+
// drifts from the framework's, and an MCP mount that silently ignores
|
|
60
|
+
// `frameworkTools`.
|
|
61
|
+
export { attachToolSearch, createToolSearchEntry, searchToolRegistry, TOOL_SEARCH_ACTION_NAME, } from "../agent/tool-search.js";
|
|
62
|
+
export { filterFrameworkToolGroups, frameworkGroupEnabled, resolveFrameworkTools, FRAMEWORK_TOOL_GROUPS, } from "../framework-tools.js";
|
|
56
63
|
export { registerPromptContextProvider, } from "./agent-chat/prompt-resources.js";
|
|
57
64
|
export { mountActionRoutes, } from "./action-routes.js";
|
|
58
65
|
export { AGENT_RUN_OWNER_CONTEXT_KEY, seedAgentRunOwnerContext, } from "./agent-run-context.js";
|
package/dist/server/poll.d.ts
CHANGED
|
@@ -114,6 +114,15 @@ export interface AppSyncStateOptions {
|
|
|
114
114
|
* Postgres only; ignored on SQLite.
|
|
115
115
|
*/
|
|
116
116
|
dbAssignedVersions?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* TTL for a cached ALLOW decision. Defaults to 30s.
|
|
119
|
+
*
|
|
120
|
+
* `invalidateCollabAccessCache` only reaches the in-process default instance,
|
|
121
|
+
* so a gateway holding per-app instances cannot be told a share was revoked
|
|
122
|
+
* and serves its cached ALLOW until this lapses. Shorten it to bound that
|
|
123
|
+
* window, at the cost of more `can-see` round-trips per shared resource.
|
|
124
|
+
*/
|
|
125
|
+
accessAllowTtlMs?: number;
|
|
117
126
|
}
|
|
118
127
|
/**
|
|
119
128
|
* Per-app change-tracking state and read path. One instance per deployed app.
|
|
@@ -180,7 +189,9 @@ export declare class AppSyncState {
|
|
|
180
189
|
private readonly accessInFlight;
|
|
181
190
|
/** Per-resource generation bumped when shares/visibility change. */
|
|
182
191
|
private readonly accessInvalidationEpoch;
|
|
192
|
+
private readonly accessAllowTtlMs;
|
|
183
193
|
constructor(options?: AppSyncStateOptions);
|
|
194
|
+
private accessCacheTtl;
|
|
184
195
|
/**
|
|
185
196
|
* Durable-event id. Deterministic (hash of logical identity + `dedupeKey`,
|
|
186
197
|
* excluding the per-instance `version`) when the instance opts in AND a
|
package/dist/server/poll.js
CHANGED
|
@@ -153,9 +153,6 @@ function accessCacheKey(userEmail, orgId, resourceType, resourceId) {
|
|
|
153
153
|
function accessResourceKey(resourceType, resourceId) {
|
|
154
154
|
return `${resourceType}|${resourceId}`;
|
|
155
155
|
}
|
|
156
|
-
function accessCacheTtl(allowed) {
|
|
157
|
-
return allowed ? ACCESS_CACHE_TTL_MS : ACCESS_CACHE_DENY_TTL_MS;
|
|
158
|
-
}
|
|
159
156
|
function extensionTargetKey(target) {
|
|
160
157
|
if (target.owner)
|
|
161
158
|
return `owner:${target.owner}`;
|
|
@@ -291,14 +288,19 @@ export class AppSyncState {
|
|
|
291
288
|
accessInFlight = new Set();
|
|
292
289
|
/** Per-resource generation bumped when shares/visibility change. */
|
|
293
290
|
accessInvalidationEpoch = new Map();
|
|
291
|
+
accessAllowTtlMs;
|
|
294
292
|
constructor(options = {}) {
|
|
295
293
|
this.getDb = options.getDb ?? getDbExec;
|
|
296
294
|
this.isPg = options.isPostgres ?? isPostgres;
|
|
297
295
|
this.resolveAccessFn = options.resolveAccess ?? defaultResolveAccess;
|
|
298
296
|
this.deterministicEventIds = options.deterministicEventIds ?? false;
|
|
299
297
|
this.dbAssignedVersions = options.dbAssignedVersions ?? false;
|
|
298
|
+
this.accessAllowTtlMs = options.accessAllowTtlMs ?? ACCESS_CACHE_TTL_MS;
|
|
300
299
|
this.pollEmitter.setMaxListeners(0);
|
|
301
300
|
}
|
|
301
|
+
accessCacheTtl(allowed) {
|
|
302
|
+
return allowed ? this.accessAllowTtlMs : ACCESS_CACHE_DENY_TTL_MS;
|
|
303
|
+
}
|
|
302
304
|
/**
|
|
303
305
|
* Durable-event id. Deterministic (hash of logical identity + `dedupeKey`,
|
|
304
306
|
* excluding the per-instance `version`) when the instance opts in AND a
|
|
@@ -675,7 +677,8 @@ export class AppSyncState {
|
|
|
675
677
|
const key = accessCacheKey(userEmail, orgId, event.resourceType, event.resourceId);
|
|
676
678
|
const cached = this.accessCache.get(key);
|
|
677
679
|
const now = Date.now();
|
|
678
|
-
if (cached &&
|
|
680
|
+
if (cached &&
|
|
681
|
+
now - cached.checkedAt < this.accessCacheTtl(cached.allowed)) {
|
|
679
682
|
// Fresh, non-expired cache hit → trust the cached decision.
|
|
680
683
|
return cached.allowed ? "visible" : "hidden";
|
|
681
684
|
}
|
|
@@ -28,6 +28,13 @@ import { signRealtimeSubscribeToken } from "./short-lived-token.js";
|
|
|
28
28
|
export const REALTIME_HMAC_SECRET_ENV = "AGENT_NATIVE_REALTIME_HMAC_SECRET";
|
|
29
29
|
/** Short TTL — validated at connect; the gateway rotates over the stream. */
|
|
30
30
|
const REALTIME_TOKEN_TTL_SECONDS = 600;
|
|
31
|
+
/**
|
|
32
|
+
* Ceiling on how long one mint can be extended by gateway rotation before the
|
|
33
|
+
* client must come back through this session-gated endpoint. Without it a single
|
|
34
|
+
* mint streams forever and logout, session expiry, user deletion and org removal
|
|
35
|
+
* never reach an open stream.
|
|
36
|
+
*/
|
|
37
|
+
const REALTIME_SESSION_MAX_SECONDS = 15 * 60;
|
|
31
38
|
export function getRealtimeSigningSecret() {
|
|
32
39
|
return process.env[REALTIME_HMAC_SECRET_ENV]?.trim() || undefined;
|
|
33
40
|
}
|
|
@@ -76,6 +83,7 @@ export function createRealtimeTokenHandler() {
|
|
|
76
83
|
owner: session.email,
|
|
77
84
|
orgId: requestContext.orgId,
|
|
78
85
|
ttlSeconds: REALTIME_TOKEN_TTL_SECONDS,
|
|
86
|
+
absExp: Math.floor(Date.now() / 1000) + REALTIME_SESSION_MAX_SECONDS,
|
|
79
87
|
}, secret);
|
|
80
88
|
const expiresAt = new Date(Date.now() + REALTIME_TOKEN_TTL_SECONDS * 1000).toISOString();
|
|
81
89
|
return { token, expiresAt, ttlSeconds: REALTIME_TOKEN_TTL_SECONDS };
|
|
@@ -82,6 +82,13 @@ export interface RealtimeSubscribeClaims {
|
|
|
82
82
|
orgId?: string;
|
|
83
83
|
/** Override default TTL (seconds). */
|
|
84
84
|
ttlSeconds?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Absolute unix-seconds ceiling, independent of `exp`. The gateway re-signs a
|
|
87
|
+
* stream's token every few minutes without consulting the app, so `exp` alone
|
|
88
|
+
* lets one mint be extended forever and a revoked session keeps streaming.
|
|
89
|
+
* Rotation copies this verbatim and never extends it.
|
|
90
|
+
*/
|
|
91
|
+
absExp?: number;
|
|
85
92
|
}
|
|
86
93
|
/**
|
|
87
94
|
* Result of {@link verifyRealtimeSubscribeToken}. On success it returns the
|
|
@@ -93,6 +100,7 @@ export type RealtimeVerifyResult = {
|
|
|
93
100
|
owner?: string;
|
|
94
101
|
orgId?: string;
|
|
95
102
|
exp: number;
|
|
103
|
+
absExp?: number;
|
|
96
104
|
} | {
|
|
97
105
|
ok: false;
|
|
98
106
|
reason: string;
|
|
@@ -182,6 +182,8 @@ export function signRealtimeSubscribeToken(claims, key) {
|
|
|
182
182
|
payload.owner = claims.owner;
|
|
183
183
|
if (claims.orgId)
|
|
184
184
|
payload.orgId = claims.orgId;
|
|
185
|
+
if (claims.absExp)
|
|
186
|
+
payload.absExp = claims.absExp;
|
|
185
187
|
const payloadStr = base64UrlEncode(JSON.stringify(payload));
|
|
186
188
|
return `${payloadStr}.${hmacB64(payloadStr, key)}`;
|
|
187
189
|
}
|
|
@@ -218,6 +220,15 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
218
220
|
if (claims.exp * 1000 < Date.now()) {
|
|
219
221
|
return { ok: false, reason: "expired" };
|
|
220
222
|
}
|
|
223
|
+
if (claims.absExp !== undefined) {
|
|
224
|
+
if (typeof claims.absExp !== "number") {
|
|
225
|
+
return { ok: false, reason: "bad_payload" };
|
|
226
|
+
}
|
|
227
|
+
// `<=`, not `<`: a ceiling is not valid at the instant it is reached.
|
|
228
|
+
if (claims.absExp * 1000 <= Date.now()) {
|
|
229
|
+
return { ok: false, reason: "session_expired" };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
221
232
|
if (claims.projectId !== expected.projectId) {
|
|
222
233
|
return { ok: false, reason: "wrong_project" };
|
|
223
234
|
}
|
|
@@ -227,6 +238,7 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
227
238
|
owner: claims.owner,
|
|
228
239
|
orgId: claims.orgId,
|
|
229
240
|
exp: claims.exp,
|
|
241
|
+
absExp: claims.absExp,
|
|
230
242
|
};
|
|
231
243
|
}
|
|
232
244
|
// ── Gateway access-check tokens ──────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.4",
|
|
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": {
|