@agent-native/core 0.154.0 → 0.154.2
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/changelog/2026-08-12-feature-flags-can-be-managed-across-apps-whose-workspace-rec.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-12-kept-feature-flag-details-readable-by-stacking-rollout-contr.md +6 -0
- package/corpus/templates/analytics/scripts/preview-dashboard-report.ts +16 -5
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +98 -31
- package/corpus/templates/content/actions/resolve-content-landing.ts +162 -32
- package/corpus/templates/content/shared/content-landing.ts +5 -0
- package/corpus/templates/dispatch/changelog/2026-08-12-dispatch-now-appears-in-authorized-workspace-app-directories.md +6 -0
- package/corpus/templates/dispatch/server/lib/org-apps-directory.ts +3 -4
- package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +15 -3
- package/corpus/templates/slides/actions/patch-deck.ts +12 -3
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +73 -1
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +6 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +15 -0
- package/corpus/templates/slides/app/context/DeckContext.tsx +19 -31
- package/corpus/templates/slides/app/hooks/use-navigation-state.ts +15 -16
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +9 -0
- package/corpus/templates/slides/changelog/2026-08-13-clicking-a-slide-thumbnail-now-focuses-it-so-the-slide-shortc.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-duplicating-or-restoring-a-slide-now-keeps-its-transition-ani.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-opening-presentation-mode-from-the-agent-now-starts-on-the-re.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-13-slide-transitions-can-be-set-from-a-visible-control-in-the-sl.md +6 -0
- package/dist/a2a/audience.d.ts +2 -0
- package/dist/a2a/audience.js +23 -0
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.js +32 -14
- package/dist/a2a/server.d.ts +9 -4
- package/dist/a2a/server.js +52 -25
- package/dist/a2a/types.d.ts +4 -4
- package/dist/a2a-claims.d.ts +1 -0
- package/dist/a2a-claims.js +3 -1
- package/dist/client/feature-flags/FeatureFlagsPanel.js +2 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/feature-flags/a2a-action-route.js +10 -1
- package/dist/feature-flags/actions/set-feature-flag.d.ts +2 -1
- package/dist/feature-flags/actions/set-feature-flag.js +9 -2
- package/dist/mcp/org-directory.d.ts +3 -0
- package/dist/mcp/org-directory.js +15 -5
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +6 -6
- package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-routes.js +10 -2
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -13
- package/dist/server/agent-chat/action-filters-a2a.js +59 -23
- package/dist/server/agent-discovery.d.ts +6 -2
- package/dist/server/agent-discovery.js +16 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/identity-sso-store.d.ts +5 -4
- package/dist/server/identity-sso-store.js +5 -4
- package/package.json +1 -1
package/dist/a2a/types.d.ts
CHANGED
|
@@ -189,10 +189,10 @@ export interface A2AConfig {
|
|
|
189
189
|
version?: string;
|
|
190
190
|
skills: AgentSkill[];
|
|
191
191
|
/**
|
|
192
|
-
* Skills advertised only to a caller with a verified A2A identity
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
192
|
+
* Skills advertised only to a caller with a verified A2A identity. Read-only
|
|
193
|
+
* skills may also be available through `actions/invoke`; mutating skills are
|
|
194
|
+
* message-only capabilities for delegation. Anonymous card fetches never see
|
|
195
|
+
* either set.
|
|
196
196
|
*/
|
|
197
197
|
authenticatedSkills?: AgentSkill[];
|
|
198
198
|
/** If true, public agent-card discovery includes only explicit public-safe skills. */
|
package/dist/a2a-claims.d.ts
CHANGED
package/dist/a2a-claims.js
CHANGED
|
@@ -24,10 +24,12 @@ export async function verifyA2ATokenWithClaims(token, event) {
|
|
|
24
24
|
? raw.scope.split(/\s+/).filter(Boolean)
|
|
25
25
|
: [];
|
|
26
26
|
const issuer = typeof raw.iss === "string" ? raw.iss.trim() : "";
|
|
27
|
-
|
|
27
|
+
const orgDomain = identity.orgDomain?.trim().toLowerCase() ?? "";
|
|
28
|
+
return orgId && orgDomain && jti
|
|
28
29
|
? {
|
|
29
30
|
email: identity.email,
|
|
30
31
|
orgId,
|
|
32
|
+
orgDomain,
|
|
31
33
|
jti,
|
|
32
34
|
...(issuer ? { issuer } : {}),
|
|
33
35
|
scope: scopes,
|
|
@@ -117,9 +117,9 @@ function FeatureFlagRow({ flag, onMutate, isPending, error, }) {
|
|
|
117
117
|
const actor = formatActor(flag.rules.updatedBy);
|
|
118
118
|
const when = formatWhen(flag.rules.updatedAt);
|
|
119
119
|
const metadata = [actor, when].filter(Boolean).join(" · ");
|
|
120
|
-
return (_jsxs("article", { id: `feature-flag-${flag.key}`, className: "grid gap-4 scroll-mt-24 border-b border-border py-5 last:border-b-0
|
|
120
|
+
return (_jsxs("article", { id: `feature-flag-${flag.key}`, className: "grid gap-4 scroll-mt-24 border-b border-border py-5 last:border-b-0 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("h3", { className: "truncate text-sm font-medium text-foreground", children: flag.displayName ?? flag.key }), flag.displayName && flag.displayName !== flag.key ? (_jsx("code", { className: "truncate text-xs text-muted-foreground", children: flag.key })) : null, _jsx(Badge, { variant: flag.rules.mode === "on" ? "secondary" : "outline", className: "ms-auto shrink-0", children: modeLabel(flag.rules, t) })] }), flag.description ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: flag.description })) : null, _jsxs("div", { className: "mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { children: [t("featureFlags.enabledFor"), ": ", audienceLabel(flag.rules, t)] }), metadata ? (_jsx("span", { children: t("featureFlags.lastChanged", { metadata }) })) : null] }), _jsx("div", { className: "mt-2 flex items-center gap-1.5 text-xs font-medium text-foreground", children: flag.enabledForCurrentUser === true ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "size-4 text-emerald-500" }), t("featureFlags.youHaveAccess")] })) : flag.enabledForCurrentUser === false ? (_jsx(_Fragment, { children: t("featureFlags.youDoNotHaveAccess") })) : (_jsx(_Fragment, { children: t("featureFlags.currentUserUnknown") })) }), error ? (_jsx("p", { className: "mt-3 text-sm text-destructive", children: t("featureFlags.mutationUnverified", {
|
|
121
121
|
name: flag.displayName ?? flag.key,
|
|
122
|
-
}) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2
|
|
122
|
+
}) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 lg:justify-end", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "text-xs", disabled: isPending || flag.enabledForCurrentUser === true, onClick: () => onMutate({
|
|
123
123
|
key: flag.key,
|
|
124
124
|
operation: "enable-for-current-user",
|
|
125
125
|
}), children: [flag.enabledForCurrentUser === true ? (_jsx(IconCheck, {})) : (_jsx(IconUserCheck, {})), flag.enabledForCurrentUser === true
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
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
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getHeader } from "h3";
|
|
2
2
|
import * as jose from "jose";
|
|
3
3
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
4
|
+
import { resolveOrgByDomain } from "../org/context.js";
|
|
5
|
+
import { consumeOneTimeJti } from "../server/identity-sso-store.js";
|
|
4
6
|
const FLAG_ACTION_SCOPES = {
|
|
5
7
|
"list-feature-flags": "flags:read",
|
|
6
8
|
"set-feature-flag": "flags:write",
|
|
@@ -37,9 +39,16 @@ export function createFeatureFlagA2AActionRouteAuth(actionName) {
|
|
|
37
39
|
if (!claims || !claims.scope.includes(FLAG_ACTION_SCOPES[actionName])) {
|
|
38
40
|
throw new Error("Invalid feature flag delegation");
|
|
39
41
|
}
|
|
42
|
+
const localOrg = await resolveOrgByDomain(claims.orgDomain);
|
|
43
|
+
if (!localOrg)
|
|
44
|
+
throw new Error("Invalid feature flag delegation");
|
|
45
|
+
if (actionName === "set-feature-flag" &&
|
|
46
|
+
(await consumeOneTimeJti(claims.jti))) {
|
|
47
|
+
throw new Error("Invalid feature flag delegation");
|
|
48
|
+
}
|
|
40
49
|
return {
|
|
41
50
|
owner: claims.email,
|
|
42
|
-
orgId:
|
|
51
|
+
orgId: localOrg.orgId,
|
|
43
52
|
anonymous: false,
|
|
44
53
|
delegationJti: claims.jti,
|
|
45
54
|
delegationIssuer: claims.issuer,
|
|
@@ -14,12 +14,13 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
14
14
|
percentage?: number;
|
|
15
15
|
};
|
|
16
16
|
}, {
|
|
17
|
-
contractVersion:
|
|
17
|
+
contractVersion: 2;
|
|
18
18
|
status: "ready";
|
|
19
19
|
key: string;
|
|
20
20
|
rules: import("../store.js").FeatureFlagRules;
|
|
21
21
|
scope: {
|
|
22
22
|
orgId: string;
|
|
23
|
+
orgDomain: string;
|
|
23
24
|
};
|
|
24
25
|
}>;
|
|
25
26
|
export default _default;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { defineAction } from "../../action.js";
|
|
3
|
+
import { getOrgDomain } from "../../org/context.js";
|
|
3
4
|
import { requireFeatureFlagManager } from "../permissions.js";
|
|
4
5
|
import { getFeatureFlagDefinition } from "../registry.js";
|
|
5
6
|
import { defaultFeatureFlagRules, mutateFeatureFlagRules, normalizeFeatureFlagRules, } from "../store.js";
|
|
@@ -47,6 +48,12 @@ export default defineAction({
|
|
|
47
48
|
if (!getFeatureFlagDefinition(args.key)) {
|
|
48
49
|
throw new Error(`Unknown feature flag: ${args.key}`);
|
|
49
50
|
}
|
|
51
|
+
const orgDomain = manager.orgId
|
|
52
|
+
? (await getOrgDomain(manager.orgId))?.trim().toLowerCase() || null
|
|
53
|
+
: null;
|
|
54
|
+
if (ctx?.caller === "a2a" && manager.orgId && !orgDomain) {
|
|
55
|
+
throw new Error("Feature flag organization domain is unavailable.");
|
|
56
|
+
}
|
|
50
57
|
const persistedRules = await mutateFeatureFlagRules(args.key, manager, (current) => {
|
|
51
58
|
let rules;
|
|
52
59
|
if (args.operation === "off") {
|
|
@@ -67,11 +74,11 @@ export default defineAction({
|
|
|
67
74
|
return { ...rules, updatedAt: Date.now(), updatedBy: manager.email };
|
|
68
75
|
});
|
|
69
76
|
return {
|
|
70
|
-
contractVersion:
|
|
77
|
+
contractVersion: 2,
|
|
71
78
|
status: "ready",
|
|
72
79
|
key: args.key,
|
|
73
80
|
rules: persistedRules,
|
|
74
|
-
scope: { orgId: manager.orgId },
|
|
81
|
+
scope: { orgId: manager.orgId, orgDomain },
|
|
75
82
|
};
|
|
76
83
|
},
|
|
77
84
|
});
|
|
@@ -71,11 +71,14 @@ export declare function resolveOrgDirectoryOrigin(env?: NodeJS.ProcessEnv): stri
|
|
|
71
71
|
*
|
|
72
72
|
* @param opts.selfId Current app id (so it's stripped from the result).
|
|
73
73
|
* @param opts.selfOrigin Current app origin (so it's stripped by origin too).
|
|
74
|
+
* @param opts.includeDirectoryApp Request the directory authority itself. The
|
|
75
|
+
* server keeps legacy self-filtering unless this explicit signal is present.
|
|
74
76
|
* @param opts.env Injectable env (tests). Defaults to `process.env`.
|
|
75
77
|
*/
|
|
76
78
|
export declare function fetchOrgApps(opts?: {
|
|
77
79
|
selfId?: string;
|
|
78
80
|
selfOrigin?: string;
|
|
81
|
+
includeDirectoryApp?: boolean;
|
|
79
82
|
serviceOrgId?: string;
|
|
80
83
|
env?: NodeJS.ProcessEnv;
|
|
81
84
|
}): Promise<OrgApp[]>;
|
|
@@ -109,18 +109,23 @@ function sameOrigin(a, b) {
|
|
|
109
109
|
return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
function scopedCacheKey(origin, auth) {
|
|
112
|
+
function scopedCacheKey(origin, auth, includeDirectoryApp = false) {
|
|
113
113
|
return [
|
|
114
114
|
origin,
|
|
115
115
|
`user:${auth.userEmail ?? ""}`,
|
|
116
116
|
`org:${auth.orgId ?? auth.orgDomain ?? ""}`,
|
|
117
|
+
`include-directory:${includeDirectoryApp ? "1" : "0"}`,
|
|
117
118
|
].join("|");
|
|
118
119
|
}
|
|
119
120
|
function authTokenAttempts(auth) {
|
|
120
121
|
return [auth.apiKey, ...(auth.apiKeyFallbacks ?? [])].filter((token) => typeof token === "string" && token.length > 0);
|
|
121
122
|
}
|
|
122
|
-
function serviceScopedCacheKey(origin, orgId) {
|
|
123
|
-
return [
|
|
123
|
+
function serviceScopedCacheKey(origin, orgId, includeDirectoryApp = false) {
|
|
124
|
+
return [
|
|
125
|
+
origin,
|
|
126
|
+
`service-org:${orgId}`,
|
|
127
|
+
`include-directory:${includeDirectoryApp ? "1" : "0"}`,
|
|
128
|
+
].join("|");
|
|
124
129
|
}
|
|
125
130
|
/**
|
|
126
131
|
* Fetch the org's first-party sibling apps from the org directory.
|
|
@@ -133,6 +138,8 @@ function serviceScopedCacheKey(origin, orgId) {
|
|
|
133
138
|
*
|
|
134
139
|
* @param opts.selfId Current app id (so it's stripped from the result).
|
|
135
140
|
* @param opts.selfOrigin Current app origin (so it's stripped by origin too).
|
|
141
|
+
* @param opts.includeDirectoryApp Request the directory authority itself. The
|
|
142
|
+
* server keeps legacy self-filtering unless this explicit signal is present.
|
|
136
143
|
* @param opts.env Injectable env (tests). Defaults to `process.env`.
|
|
137
144
|
*/
|
|
138
145
|
export async function fetchOrgApps(opts) {
|
|
@@ -155,7 +162,7 @@ export async function fetchOrgApps(opts) {
|
|
|
155
162
|
let ttl = EMPTY_TTL_MS;
|
|
156
163
|
const serviceOrgId = opts?.serviceOrgId?.trim();
|
|
157
164
|
if (serviceOrgId) {
|
|
158
|
-
const serviceCacheKey = serviceScopedCacheKey(origin, serviceOrgId);
|
|
165
|
+
const serviceCacheKey = serviceScopedCacheKey(origin, serviceOrgId, opts?.includeDirectoryApp);
|
|
159
166
|
const cached = cache.get(serviceCacheKey);
|
|
160
167
|
if (cached && cached.expiresAt > Date.now()) {
|
|
161
168
|
return stripSelf(cached.apps);
|
|
@@ -174,7 +181,7 @@ export async function fetchOrgApps(opts) {
|
|
|
174
181
|
}
|
|
175
182
|
if (!cacheKey) {
|
|
176
183
|
const now = Date.now();
|
|
177
|
-
cacheKey = scopedCacheKey(origin, auth);
|
|
184
|
+
cacheKey = scopedCacheKey(origin, auth, opts?.includeDirectoryApp);
|
|
178
185
|
const cached = cache.get(cacheKey);
|
|
179
186
|
if (cached && cached.expiresAt > now) {
|
|
180
187
|
return stripSelf(cached.apps);
|
|
@@ -186,6 +193,9 @@ export async function fetchOrgApps(opts) {
|
|
|
186
193
|
headers: {
|
|
187
194
|
Authorization: `Bearer ${attempts[i]}`,
|
|
188
195
|
Accept: "application/json",
|
|
196
|
+
...(opts?.includeDirectoryApp
|
|
197
|
+
? { "X-Agent-Native-Include-Directory-App": "1" }
|
|
198
|
+
: {}),
|
|
189
199
|
},
|
|
190
200
|
signal: AbortSignal.timeout(4000),
|
|
191
201
|
});
|
|
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
|
-
error?: undefined;
|
|
46
44
|
summary: import("./types.js").TraceSummary;
|
|
47
45
|
spans: import("./types.js").TraceSpan[];
|
|
48
46
|
id?: undefined;
|
|
49
|
-
} | {
|
|
50
|
-
ok?: undefined;
|
|
51
47
|
error?: undefined;
|
|
48
|
+
ok?: undefined;
|
|
49
|
+
} | {
|
|
52
50
|
summary?: undefined;
|
|
53
51
|
spans?: undefined;
|
|
54
52
|
id: string;
|
|
55
|
-
|
|
53
|
+
error?: undefined;
|
|
56
54
|
ok?: undefined;
|
|
55
|
+
} | {
|
|
57
56
|
summary?: undefined;
|
|
58
57
|
spans?: undefined;
|
|
59
58
|
id?: undefined;
|
|
60
59
|
error: any;
|
|
60
|
+
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
66
65
|
ok: boolean;
|
|
66
|
+
error?: undefined;
|
|
67
67
|
}>>;
|
|
@@ -75,8 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
id?: undefined;
|
|
79
78
|
deleted?: undefined;
|
|
79
|
+
id?: undefined;
|
|
80
80
|
provider?: undefined;
|
|
81
81
|
providers: {
|
|
82
82
|
id: string;
|
|
@@ -93,8 +93,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
93
93
|
label?: undefined;
|
|
94
94
|
message?: undefined;
|
|
95
95
|
} | {
|
|
96
|
-
id?: undefined;
|
|
97
96
|
deleted?: undefined;
|
|
97
|
+
id?: undefined;
|
|
98
98
|
providers?: undefined;
|
|
99
99
|
count?: undefined;
|
|
100
100
|
found: boolean;
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
error: string;
|
|
52
|
+
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,16 +34,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
ok?: undefined;
|
|
38
37
|
status?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
39
39
|
} | {
|
|
40
40
|
ok: boolean;
|
|
41
41
|
status: string;
|
|
42
42
|
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
error: string;
|
|
46
45
|
removed?: undefined;
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
48
|
ok: boolean;
|
|
49
49
|
removed: boolean;
|
|
@@ -54,9 +54,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
-
ok?: undefined;
|
|
58
57
|
error: string;
|
|
59
58
|
note?: undefined;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
ok: boolean;
|
|
62
62
|
note?: undefined;
|
|
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, setResponseStatus, setResponseHeader,
|
|
|
2
2
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
3
3
|
import { isAgentActionStopError } from "../action.js";
|
|
4
4
|
import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
|
|
5
|
-
import { resolveOrgIdForEmail } from "../org/context.js";
|
|
5
|
+
import { resolveOrgByDomain, resolveOrgIdForEmail } from "../org/context.js";
|
|
6
6
|
import { readBody } from "../server/h3-helpers.js";
|
|
7
7
|
import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
|
|
8
8
|
import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
@@ -12,6 +12,7 @@ import { captureError } from "./capture-error.js";
|
|
|
12
12
|
import { getAllowedCorsOrigin as resolveAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
13
13
|
import { resolveEmbedSessionFromRequest, resolvedEmbedCapabilityScope, } from "./embed-session.js";
|
|
14
14
|
import { getHttpRequestTelemetryId } from "./http-response-telemetry.js";
|
|
15
|
+
import { consumeOneTimeJti } from "./identity-sso-store.js";
|
|
15
16
|
function requiredClientCompatibilityVersion() {
|
|
16
17
|
const configured = typeof __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__ === "string"
|
|
17
18
|
? __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__
|
|
@@ -52,9 +53,16 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
|
|
|
52
53
|
const claims = await verifyA2ATokenWithClaims(token, event);
|
|
53
54
|
if (!claims || !claims.scope.includes(required))
|
|
54
55
|
throw new Error("Invalid feature flag delegation");
|
|
56
|
+
const localOrg = await resolveOrgByDomain(claims.orgDomain);
|
|
57
|
+
if (!localOrg)
|
|
58
|
+
throw new Error("Invalid feature flag delegation");
|
|
59
|
+
if (actionName === "set-feature-flag" &&
|
|
60
|
+
(await consumeOneTimeJti(claims.jti))) {
|
|
61
|
+
throw new Error("Invalid feature flag delegation");
|
|
62
|
+
}
|
|
55
63
|
return {
|
|
56
64
|
owner: claims.email,
|
|
57
|
-
orgId:
|
|
65
|
+
orgId: localOrg.orgId,
|
|
58
66
|
anonymous: false,
|
|
59
67
|
delegationJti: claims.jti,
|
|
60
68
|
delegationIssuer: claims.issuer,
|
|
Binary file
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type A2AArtifactResponseOptions, type A2AToolResultSummary } from "../../a2a/artifact-response.js";
|
|
2
|
+
import type { AgentSkill } from "../../a2a/types.js";
|
|
2
3
|
import type { EngineTool } from "../../agent/engine/types.js";
|
|
3
4
|
import { type ActionEntry, type AgentLoopOutcome } from "../../agent/production-agent.js";
|
|
4
5
|
import { runAgentLoopDirectWithSoftTimeout } from "../../agent/run-loop-with-resume.js";
|
|
@@ -44,6 +45,13 @@ export interface A2AExternalAgentSurface {
|
|
|
44
45
|
* query the caller wrote.
|
|
45
46
|
*/
|
|
46
47
|
export declare function filterDirectA2AActions(actions: Record<string, ActionEntry>, options: A2AExternalAgentSurface): Record<string, ActionEntry>;
|
|
48
|
+
/**
|
|
49
|
+
* Authenticated write capabilities that a sibling may delegate to this app's
|
|
50
|
+
* agent, but never invoke directly. `publicAgent` is the action-owned opt-in;
|
|
51
|
+
* keeping this projection independent of `connectorCatalog` avoids making apps
|
|
52
|
+
* maintain a second capability registry. The deny list remains a final veto.
|
|
53
|
+
*/
|
|
54
|
+
export declare function filterDelegatedA2ACapabilityActions(actions: Record<string, ActionEntry>, options: A2AExternalAgentSurface): Record<string, ActionEntry>;
|
|
47
55
|
export declare function buildPublicAgentA2ASkills(actions: Record<string, ActionEntry>): Array<{
|
|
48
56
|
id: string;
|
|
49
57
|
name: string;
|
|
@@ -52,20 +60,13 @@ export declare function buildPublicAgentA2ASkills(actions: Record<string, Action
|
|
|
52
60
|
inputSchema?: Record<string, unknown>;
|
|
53
61
|
}>;
|
|
54
62
|
/**
|
|
55
|
-
* Skills for a caller with a verified A2A identity
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* an app has nothing callable when it does.
|
|
63
|
+
* Skills for a caller with a verified A2A identity. Exact bounded reads carry
|
|
64
|
+
* schemas and may be invoked directly; explicitly exposed writes carry no
|
|
65
|
+
* schema and are discoverable only as natural-language delegation targets.
|
|
66
|
+
* Kept separate from `buildPublicAgentA2ASkills` because neither authenticated
|
|
67
|
+
* surface may be disclosed by the anonymous card.
|
|
61
68
|
*/
|
|
62
|
-
export declare function buildAuthenticatedAgentA2ASkills(actions: Record<string, ActionEntry>, options: A2AExternalAgentSurface):
|
|
63
|
-
id: string;
|
|
64
|
-
name: string;
|
|
65
|
-
description: string;
|
|
66
|
-
publicAgent: ActionEntry["publicAgent"];
|
|
67
|
-
inputSchema?: Record<string, unknown>;
|
|
68
|
-
}>;
|
|
69
|
+
export declare function buildAuthenticatedAgentA2ASkills(actions: Record<string, ActionEntry>, options: A2AExternalAgentSurface): AgentSkill[];
|
|
69
70
|
export declare function resolveArtifactBaseUrl(event: any | undefined): string | undefined;
|
|
70
71
|
export declare function assembleA2AFinalResponse(events: readonly AgentChatEvent[], toolResults: readonly A2AToolResultSummary[], options?: A2AArtifactResponseOptions & {
|
|
71
72
|
event?: any;
|
|
@@ -96,6 +96,24 @@ export function filterDirectA2AActions(actions, options) {
|
|
|
96
96
|
exposure.isConsequential !== true);
|
|
97
97
|
}));
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Authenticated write capabilities that a sibling may delegate to this app's
|
|
101
|
+
* agent, but never invoke directly. `publicAgent` is the action-owned opt-in;
|
|
102
|
+
* keeping this projection independent of `connectorCatalog` avoids making apps
|
|
103
|
+
* maintain a second capability registry. The deny list remains a final veto.
|
|
104
|
+
*/
|
|
105
|
+
export function filterDelegatedA2ACapabilityActions(actions, options) {
|
|
106
|
+
const denied = new Set(options.externalAgents?.denyActions ?? []);
|
|
107
|
+
return Object.fromEntries(Object.entries(actions).filter(([name, entry]) => {
|
|
108
|
+
const exposure = entry.publicAgent;
|
|
109
|
+
return (!denied.has(name) &&
|
|
110
|
+
entry.agentTool !== false &&
|
|
111
|
+
entry.readOnly !== true &&
|
|
112
|
+
exposure?.expose === true &&
|
|
113
|
+
exposure.readOnly === false &&
|
|
114
|
+
exposure.requiresAuth === true);
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
99
117
|
export function buildPublicAgentA2ASkills(actions) {
|
|
100
118
|
return Object.entries(filterPublicAgentActions(actions)).map(([name, entry]) => ({
|
|
101
119
|
id: name,
|
|
@@ -110,31 +128,49 @@ export function buildPublicAgentA2ASkills(actions) {
|
|
|
110
128
|
}));
|
|
111
129
|
}
|
|
112
130
|
/**
|
|
113
|
-
* Skills for a caller with a verified A2A identity
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* an app has nothing callable when it does.
|
|
131
|
+
* Skills for a caller with a verified A2A identity. Exact bounded reads carry
|
|
132
|
+
* schemas and may be invoked directly; explicitly exposed writes carry no
|
|
133
|
+
* schema and are discoverable only as natural-language delegation targets.
|
|
134
|
+
* Kept separate from `buildPublicAgentA2ASkills` because neither authenticated
|
|
135
|
+
* surface may be disclosed by the anonymous card.
|
|
119
136
|
*/
|
|
120
137
|
export function buildAuthenticatedAgentA2ASkills(actions, options) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
const directActions = filterDirectA2AActions(actions, options);
|
|
139
|
+
const delegatedActions = filterDelegatedA2ACapabilityActions(actions, options);
|
|
140
|
+
return Object.entries(actions).flatMap(([name, entry]) => {
|
|
141
|
+
const directEntry = directActions[name];
|
|
142
|
+
const delegatedEntry = delegatedActions[name];
|
|
143
|
+
if (!directEntry && !delegatedEntry)
|
|
144
|
+
return [];
|
|
145
|
+
if (directEntry) {
|
|
146
|
+
return {
|
|
147
|
+
id: name,
|
|
148
|
+
name,
|
|
149
|
+
description: entry.tool.description,
|
|
150
|
+
publicAgent: entry.publicAgent,
|
|
151
|
+
// Every action in this set is read-only by construction. Omitting the
|
|
152
|
+
// flag made discovery label all of them "(mutating)", which pushes a
|
|
153
|
+
// caller away from invoking them and back into open-ended delegation.
|
|
154
|
+
readOnly: true,
|
|
155
|
+
// Naming a directly callable action without its parameters is what
|
|
156
|
+
// makes a caller invoke it with `{}` and fail on a required field.
|
|
157
|
+
...(entry.tool.parameters
|
|
158
|
+
? {
|
|
159
|
+
inputSchema: entry.tool.parameters,
|
|
160
|
+
}
|
|
161
|
+
: {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
id: name,
|
|
166
|
+
name,
|
|
167
|
+
description: entry.tool.description,
|
|
168
|
+
publicAgent: entry.publicAgent,
|
|
169
|
+
// No input schema: the sibling sends an objective to the receiving app,
|
|
170
|
+
// whose agent chooses and validates its own local actions.
|
|
171
|
+
readOnly: false,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
138
174
|
}
|
|
139
175
|
export function resolveArtifactBaseUrl(event) {
|
|
140
176
|
const fromEnv = process.env.APP_URL ||
|
|
@@ -70,12 +70,16 @@ export declare function shouldIncludeRemoteAgentManifest(manifest: {
|
|
|
70
70
|
/**
|
|
71
71
|
* Get built-in agents (static, no DB). Used as fallback and for seeding.
|
|
72
72
|
*/
|
|
73
|
-
export declare function getBuiltinAgents(selfAppId?: string
|
|
73
|
+
export declare function getBuiltinAgents(selfAppId?: string, options?: {
|
|
74
|
+
preferLocalUrls?: boolean;
|
|
75
|
+
}): DiscoveredAgent[];
|
|
74
76
|
/**
|
|
75
77
|
* Discover all agents: built-in + custom agents stored as resources.
|
|
76
78
|
* Custom agents override built-in agents with the same ID.
|
|
77
79
|
*/
|
|
78
|
-
export declare function discoverAgents(selfAppId?: string
|
|
80
|
+
export declare function discoverAgents(selfAppId?: string, options?: {
|
|
81
|
+
preferLocalUrls?: boolean;
|
|
82
|
+
}): Promise<DiscoveredAgent[]>;
|
|
79
83
|
/**
|
|
80
84
|
* Look up a single agent by ID or name (case-insensitive).
|
|
81
85
|
*/
|
|
@@ -191,13 +191,13 @@ export function shouldIncludeRemoteAgentManifest(manifest, selfAppId) {
|
|
|
191
191
|
/**
|
|
192
192
|
* Get built-in agents (static, no DB). Used as fallback and for seeding.
|
|
193
193
|
*/
|
|
194
|
-
export function getBuiltinAgents(selfAppId) {
|
|
194
|
+
export function getBuiltinAgents(selfAppId, options) {
|
|
195
195
|
const normalizedSelfAppId = selfAppId ? normalizeAgentId(selfAppId) : "";
|
|
196
196
|
return BUILTIN_AGENTS.filter((app) => app.id !== normalizedSelfAppId && app.url).map((app) => ({
|
|
197
197
|
id: app.id,
|
|
198
198
|
name: app.name,
|
|
199
199
|
description: app.description,
|
|
200
|
-
url: resolveAgentUrl(app),
|
|
200
|
+
url: resolveAgentUrl(app, options?.preferLocalUrls),
|
|
201
201
|
color: app.color,
|
|
202
202
|
}));
|
|
203
203
|
}
|
|
@@ -205,8 +205,8 @@ export function getBuiltinAgents(selfAppId) {
|
|
|
205
205
|
* Discover all agents: built-in + custom agents stored as resources.
|
|
206
206
|
* Custom agents override built-in agents with the same ID.
|
|
207
207
|
*/
|
|
208
|
-
export async function discoverAgents(selfAppId) {
|
|
209
|
-
const builtins = getBuiltinAgents(selfAppId);
|
|
208
|
+
export async function discoverAgents(selfAppId, options) {
|
|
209
|
+
const builtins = getBuiltinAgents(selfAppId, options);
|
|
210
210
|
const agentsById = new Map();
|
|
211
211
|
// Start with built-ins
|
|
212
212
|
for (const agent of builtins) {
|
|
@@ -249,6 +249,11 @@ export async function discoverAgents(selfAppId) {
|
|
|
249
249
|
url = builtin.url;
|
|
250
250
|
}
|
|
251
251
|
const builtin = agentsById.get(manifestId);
|
|
252
|
+
if (options?.preferLocalUrls && builtin) {
|
|
253
|
+
const isBuiltinAgent = BUILTIN_AGENTS.some((candidate) => candidate.id === manifestId);
|
|
254
|
+
if (isBuiltinAgent)
|
|
255
|
+
url = builtin.url;
|
|
256
|
+
}
|
|
252
257
|
const isLegacyAssetsManifest = manifest.id.trim().toLowerCase() !== manifestId;
|
|
253
258
|
if (isLegacyAssetsManifest && builtin?.url) {
|
|
254
259
|
try {
|
|
@@ -280,7 +285,7 @@ export async function discoverAgents(selfAppId) {
|
|
|
280
285
|
}
|
|
281
286
|
// Overlay sibling workspace apps last so same-origin workspaces prefer the
|
|
282
287
|
// app mounted in this workspace over the public template with the same id.
|
|
283
|
-
for (const agent of await discoverWorkspaceAgents(selfAppId)) {
|
|
288
|
+
for (const agent of await discoverWorkspaceAgents(selfAppId, options)) {
|
|
284
289
|
agentsById.set(agent.id, agent);
|
|
285
290
|
}
|
|
286
291
|
return Array.from(agentsById.values());
|
|
@@ -347,8 +352,8 @@ function isHostedRuntime() {
|
|
|
347
352
|
function shouldUseLocalAgentUrls() {
|
|
348
353
|
return !isHostedRuntime();
|
|
349
354
|
}
|
|
350
|
-
function resolveAgentUrl(app) {
|
|
351
|
-
if (shouldUseLocalAgentUrls()) {
|
|
355
|
+
function resolveAgentUrl(app, preferLocalUrls = false) {
|
|
356
|
+
if (preferLocalUrls || shouldUseLocalAgentUrls()) {
|
|
352
357
|
return app.devUrl || `http://localhost:${app.devPort}`;
|
|
353
358
|
}
|
|
354
359
|
return app.url;
|
|
@@ -527,7 +532,7 @@ function workspaceAppUrl(app, hostedLoopbackFallbackUrl) {
|
|
|
527
532
|
return null;
|
|
528
533
|
}
|
|
529
534
|
}
|
|
530
|
-
async function discoverWorkspaceAgents(selfAppId) {
|
|
535
|
+
async function discoverWorkspaceAgents(selfAppId, options) {
|
|
531
536
|
const workspaceApps = loadWorkspaceAppsManifest();
|
|
532
537
|
if (!workspaceApps)
|
|
533
538
|
return [];
|
|
@@ -537,7 +542,9 @@ async function discoverWorkspaceAgents(selfAppId) {
|
|
|
537
542
|
.map((app) => {
|
|
538
543
|
const withOverride = applyWorkspaceAppMetadataOverride(app, metadataSettings);
|
|
539
544
|
const builtin = BUILTIN_AGENTS.find((agent) => agent.id === withOverride.id);
|
|
540
|
-
const url =
|
|
545
|
+
const url = options?.preferLocalUrls && builtin
|
|
546
|
+
? resolveAgentUrl(builtin, true)
|
|
547
|
+
: workspaceAppUrl(withOverride, builtin?.url);
|
|
541
548
|
if (!url)
|
|
542
549
|
return null;
|
|
543
550
|
return {
|