@agent-native/core 0.212.1 → 0.214.0

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.
Files changed (107) hide show
  1. package/dist/a2a/anthropic-managed-agents.js +1 -0
  2. package/dist/a2a/caller-auth.js +7 -2
  3. package/dist/a2a/client.d.ts +1 -0
  4. package/dist/a2a/client.js +27 -0
  5. package/dist/a2a/handlers.js +15 -3
  6. package/dist/a2a/organization-identity.d.ts +4 -1
  7. package/dist/a2a/organization-identity.js +3 -1
  8. package/dist/a2a/server.d.ts +6 -5
  9. package/dist/a2a/server.js +79 -6
  10. package/dist/a2a-claims.d.ts +1 -0
  11. package/dist/a2a-claims.js +1 -0
  12. package/dist/agent/actions/manage-provider-key-policy.js +4 -2
  13. package/dist/agent/app-model-defaults.js +2 -10
  14. package/dist/agent/loop-settings.js +2 -10
  15. package/dist/appearance/actions/get-theme-family.d.ts +8 -0
  16. package/dist/appearance/actions/get-theme-family.js +38 -0
  17. package/dist/appearance/actions/set-theme-family.d.ts +7 -0
  18. package/dist/appearance/actions/set-theme-family.js +33 -0
  19. package/dist/appearance/theme-family.d.ts +13 -0
  20. package/dist/appearance/theme-family.js +38 -0
  21. package/dist/audit/actions/export-audit-events.js +1 -0
  22. package/dist/audit/attribution.d.ts +5 -4
  23. package/dist/audit/attribution.js +2 -1
  24. package/dist/audit/index.d.ts +1 -1
  25. package/dist/audit/ocsf.js +1 -0
  26. package/dist/audit/read-scope.js +2 -12
  27. package/dist/audit/record.js +1 -0
  28. package/dist/audit/store.js +12 -3
  29. package/dist/audit/types.d.ts +4 -0
  30. package/dist/automation/index.d.ts +1 -1
  31. package/dist/automations/service.js +3 -10
  32. package/dist/client/org/hooks.d.ts +3 -0
  33. package/dist/client/org/hooks.js +12 -0
  34. package/dist/client/theme-family.d.ts +35 -0
  35. package/dist/client/theme-family.js +223 -0
  36. package/dist/client/theme.d.ts +1 -1
  37. package/dist/client/theme.js +5 -2
  38. package/dist/client/ui/index.d.ts +1 -0
  39. package/dist/client/ui/index.js +1 -0
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/routes.d.ts +2 -2
  42. package/dist/collab/struct-routes.d.ts +1 -1
  43. package/dist/connections/catalog.d.ts +1 -1
  44. package/dist/connections/catalog.js +7 -1
  45. package/dist/credentials/index.js +18 -2
  46. package/dist/data-widgets/index.d.ts +339 -4
  47. package/dist/data-widgets/index.js +150 -7
  48. package/dist/db/schema-snapshot.generated.js +5 -2
  49. package/dist/feature-flags/a2a-action-route.js +1 -0
  50. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  51. package/dist/file-upload/storage-settings.js +2 -9
  52. package/dist/framework-tools.js +2 -0
  53. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  54. package/dist/jobs/tools.js +2 -12
  55. package/dist/mcp/actions/service-token-access.js +15 -11
  56. package/dist/mcp/credential-issuance.js +9 -2
  57. package/dist/notifications/routes.d.ts +3 -3
  58. package/dist/observability/routes.d.ts +1 -1
  59. package/dist/org/actions.d.ts +1 -1
  60. package/dist/org/actions.js +3 -7
  61. package/dist/org/app-roles.js +10 -1
  62. package/dist/org/context.d.ts +1 -0
  63. package/dist/org/context.js +122 -24
  64. package/dist/org/federation.d.ts +1 -1
  65. package/dist/org/federation.js +2 -2
  66. package/dist/org/handlers.d.ts +4 -0
  67. package/dist/org/handlers.js +140 -44
  68. package/dist/org/index.d.ts +2 -1
  69. package/dist/org/membership.d.ts +6 -4
  70. package/dist/org/membership.js +7 -5
  71. package/dist/org/migrations.js +6 -0
  72. package/dist/org/plugin.d.ts +1 -0
  73. package/dist/org/plugin.js +9 -1
  74. package/dist/org/private-icon-handlers.js +4 -10
  75. package/dist/org/types.d.ts +2 -0
  76. package/dist/org/workspace-app-access.js +62 -14
  77. package/dist/progress/routes.d.ts +1 -1
  78. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  79. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  80. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  81. package/dist/secrets/routes.d.ts +6 -6
  82. package/dist/server/action-discovery.js +8 -0
  83. package/dist/server/action-routes.d.ts +1 -0
  84. package/dist/server/action-routes.js +13 -1
  85. package/dist/server/agent-chat/context-tools.js +43 -1
  86. package/dist/server/agent-chat/prompt-resources.js +1 -1
  87. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  88. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  89. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  90. package/dist/server/index.d.ts +1 -0
  91. package/dist/server/index.js +1 -0
  92. package/dist/server/org-admin.js +3 -2
  93. package/dist/server/personal-provider-key-holders.d.ts +1 -1
  94. package/dist/server/personal-provider-key-holders.js +7 -2
  95. package/dist/server/personal-provider-key-policy.d.ts +2 -0
  96. package/dist/server/personal-provider-key-policy.js +21 -5
  97. package/dist/server/request-context.d.ts +2 -0
  98. package/dist/server/request-context.js +7 -0
  99. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
  100. package/dist/triggers/actions/manage-automation.d.ts +4 -4
  101. package/dist/triggers/routes.js +2 -9
  102. package/dist/usage/alerts-store.js +2 -9
  103. package/dist/usage/metrics-store.js +2 -9
  104. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
  105. package/dist/workspace-connections/groups.js +2 -11
  106. package/package.json +3 -3
  107. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
@@ -62,6 +62,7 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
62
62
  workspaceAppDefaultVisibility: WorkspaceAppDefaultVisibility;
63
63
  signInMethods: import("./sign-in-methods.js").OrgSignInMethods;
64
64
  a2aSecretSet: boolean;
65
+ a2aMemberDelegationEnabled: boolean;
65
66
  soloDeploymentAdmin: boolean;
66
67
  }>>;
67
68
  export declare const retryPendingFederatedRemovalHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
@@ -175,6 +176,9 @@ export declare const setRequiredAuthProviderHandler: import("h3").EventHandlerWi
175
176
  revokedLegacySessions: number;
176
177
  provider: RequiredAuthProvider;
177
178
  }>>;
179
+ export declare const setA2AMemberDelegationHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
180
+ enabled: any;
181
+ }>>;
178
182
  export declare const revealA2ASecretHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
179
183
  a2aSecret: string;
180
184
  }>>;
@@ -35,6 +35,8 @@ import { getAppProductionUrl, resolveAppRuntimeUrl, } from "../server/app-url.js
35
35
  import { isTrustedSelfHostedRuntime, resolveVercelDeploymentProtectionHeaders, } from "../server/credential-provider.js";
36
36
  import { sendEmail, isEmailConfigured } from "../server/email.js";
37
37
  import { readBody } from "../server/h3-helpers.js";
38
+ import { effectiveOrgMemberRole } from "../server/personal-provider-key-policy.js";
39
+ import { getRequestContext } from "../server/request-context.js";
38
40
  import { resolveDeploymentSignInMethods } from "../server/social-sign-in-providers.js";
39
41
  import { getOrgSetting, putOrgSetting } from "../settings/org-settings.js";
40
42
  import { isEmailDerivedName } from "../user-profile/shared.js";
@@ -140,37 +142,82 @@ async function getSessionForEvent(event) {
140
142
  }
141
143
  export const getMyOrgHandler = defineEventHandler(async (event) => {
142
144
  const ctx = await getOrgContext(event);
145
+ const requestContext = getRequestContext();
146
+ const delegatedOrgMember = requestContext?.a2aIdentityAssurance === "org-member";
147
+ const delegatedEmail = requestContext?.userEmail?.trim().toLowerCase();
148
+ const delegatedOrgId = delegatedOrgMember
149
+ ? requestContext.orgId?.trim()
150
+ : undefined;
151
+ const scopedDelegatedOrgId = delegatedEmail &&
152
+ delegatedEmail === ctx.email.trim().toLowerCase() &&
153
+ delegatedOrgId &&
154
+ ctx.orgId === delegatedOrgId
155
+ ? delegatedOrgId
156
+ : null;
157
+ const visibleOrgId = delegatedOrgMember ? scopedDelegatedOrgId : ctx.orgId;
158
+ const visibleOrgName = visibleOrgId ? ctx.orgName : null;
159
+ const visibleRole = delegatedOrgMember
160
+ ? visibleOrgId
161
+ ? "member"
162
+ : null
163
+ : ctx.role;
143
164
  const e = await exec();
144
- const allOrgsRes = await e.execute({
145
- sql: `SELECT m.org_id AS "orgId", m.role AS role, o.name AS "orgName",
146
- o.icon_json AS "iconJson", o.icon_revision AS "iconRevision"
147
- FROM org_members m
148
- INNER JOIN organizations o ON m.org_id = o.id
149
- WHERE LOWER(m.email) = ?
150
- AND m.federation_removal_pending_at IS NULL`,
151
- args: [ctx.email.toLowerCase()],
152
- });
153
- const orgs = allOrgsRes.rows.map((r) => ({
154
- orgId: String(r.orgId ?? r.org_id),
155
- role: String(r.role),
156
- orgName: String(r.orgName ?? r.org_name),
157
- icon: parseOrganizationIconJson(r.iconJson ?? r.icon_json),
158
- iconRevision: Number(r.iconRevision ?? r.icon_revision ?? 0),
159
- }));
160
- const pendingRemovalRes = await e.execute({
161
- sql: `SELECT m.org_id AS "orgId", o.name AS "orgName"
162
- FROM org_members m
163
- INNER JOIN organizations o ON m.org_id = o.id
164
- WHERE LOWER(m.email) = ?
165
- AND m.federation_removal_pending_at IS NOT NULL`,
166
- args: [ctx.email.toLowerCase()],
165
+ const allOrgsRes = delegatedOrgMember
166
+ ? scopedDelegatedOrgId
167
+ ? await e.execute({
168
+ sql: `SELECT m.org_id AS "orgId", m.role AS role, o.name AS "orgName",
169
+ o.icon_json AS "iconJson", o.icon_revision AS "iconRevision"
170
+ FROM org_members m
171
+ INNER JOIN organizations o ON m.org_id = o.id
172
+ WHERE LOWER(m.email) = ? AND m.org_id = ?
173
+ AND m.federation_removal_pending_at IS NULL`,
174
+ args: [ctx.email.toLowerCase(), scopedDelegatedOrgId],
175
+ })
176
+ : { rows: [] }
177
+ : await e.execute({
178
+ sql: `SELECT m.org_id AS "orgId", m.role AS role, o.name AS "orgName",
179
+ o.icon_json AS "iconJson", o.icon_revision AS "iconRevision"
180
+ FROM org_members m
181
+ INNER JOIN organizations o ON m.org_id = o.id
182
+ WHERE LOWER(m.email) = ?
183
+ AND m.federation_removal_pending_at IS NULL`,
184
+ args: [ctx.email.toLowerCase()],
185
+ });
186
+ const orgs = allOrgsRes.rows.flatMap((r) => {
187
+ const orgId = String(r.orgId ?? r.org_id);
188
+ const role = delegatedOrgMember
189
+ ? effectiveOrgMemberRole(r.role, orgId, ctx.email)
190
+ : String(r.role);
191
+ return role
192
+ ? [
193
+ {
194
+ orgId,
195
+ role,
196
+ orgName: String(r.orgName ?? r.org_name),
197
+ icon: parseOrganizationIconJson(r.iconJson ?? r.icon_json),
198
+ iconRevision: Number(r.iconRevision ?? r.icon_revision ?? 0),
199
+ },
200
+ ]
201
+ : [];
167
202
  });
203
+ const pendingRemovalRes = delegatedOrgMember
204
+ ? { rows: [] }
205
+ : await e.execute({
206
+ sql: `SELECT m.org_id AS "orgId", o.name AS "orgName"
207
+ FROM org_members m
208
+ INNER JOIN organizations o ON m.org_id = o.id
209
+ WHERE LOWER(m.email) = ?
210
+ AND m.federation_removal_pending_at IS NOT NULL`,
211
+ args: [ctx.email.toLowerCase()],
212
+ });
168
213
  const pendingRemovals = pendingRemovalRes.rows.map((r) => ({
169
214
  orgId: String(r.orgId ?? r.org_id),
170
215
  orgName: String(r.orgName ?? r.org_name),
171
216
  }));
172
217
  let domainMatches = [];
173
- const domain = ctx.email.split("@")[1]?.toLowerCase();
218
+ const domain = delegatedOrgMember
219
+ ? undefined
220
+ : ctx.email.split("@")[1]?.toLowerCase();
174
221
  if (domain) {
175
222
  try {
176
223
  const dmRes = await e.execute({
@@ -199,14 +246,16 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
199
246
  let workspaceUrl = null;
200
247
  let requiredAuthProvider = null;
201
248
  let a2aSecretSet = false;
249
+ let a2aMemberDelegationEnabled = false;
202
250
  let icon = null;
203
251
  let iconRevision = 0;
204
- if (ctx.orgId) {
252
+ if (visibleOrgId) {
205
253
  const adRes = await e.execute({
206
- sql: `SELECT allowed_domain, a2a_secret, workspace_url, required_auth_provider,
254
+ sql: `SELECT allowed_domain, a2a_secret, a2a_member_delegation_enabled,
255
+ workspace_url, required_auth_provider,
207
256
  icon_json, icon_revision
208
257
  FROM organizations WHERE id = ? LIMIT 1`,
209
- args: [ctx.orgId],
258
+ args: [visibleOrgId],
210
259
  });
211
260
  if (adRes.rows[0]) {
212
261
  allowedDomain =
@@ -214,35 +263,49 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
214
263
  workspaceUrl = String(adRes.rows[0].workspace_url ?? "") || null;
215
264
  requiredAuthProvider = parseRequiredAuthProvider(adRes.rows[0].required_auth_provider ?? null);
216
265
  a2aSecretSet = Boolean(String(adRes.rows[0].a2a_secret ?? "").trim());
266
+ a2aMemberDelegationEnabled =
267
+ adRes.rows[0].a2a_member_delegation_enabled === true;
217
268
  icon = parseOrganizationIconJson(adRes.rows[0].icon_json);
218
269
  iconRevision = Number(adRes.rows[0].icon_revision ?? 0);
219
270
  }
220
271
  }
221
- const isOwnerOrAdmin = ctx.role === "owner" || ctx.role === "admin";
272
+ const isOwnerOrAdmin = visibleRole === "owner" || visibleRole === "admin";
222
273
  let workspaceAppDefaultVisibility = "org";
223
- if (ctx.orgId) {
224
- const setting = await getOrgSetting(ctx.orgId, WORKSPACE_APP_DEFAULT_VISIBILITY_KEY);
274
+ if (visibleOrgId) {
275
+ const setting = await getOrgSetting(visibleOrgId, WORKSPACE_APP_DEFAULT_VISIBILITY_KEY);
225
276
  workspaceAppDefaultVisibility = normalizeWorkspaceAppDefaultVisibility(setting?.visibility);
226
277
  }
227
- const invitesRes = await e.execute({
228
- sql: `SELECT i.id AS id, i.org_id AS "orgId", o.name AS "orgName", i.invited_by AS "invitedBy"
229
- FROM org_invitations i
230
- INNER JOIN organizations o ON i.org_id = o.id
231
- WHERE LOWER(i.email) = ? AND i.status = 'pending'`,
232
- args: [ctx.email.toLowerCase()],
233
- });
278
+ const invitesRes = delegatedOrgMember
279
+ ? scopedDelegatedOrgId
280
+ ? await e.execute({
281
+ sql: `SELECT i.id AS id, i.org_id AS "orgId", o.name AS "orgName", i.invited_by AS "invitedBy"
282
+ FROM org_invitations i
283
+ INNER JOIN organizations o ON i.org_id = o.id
284
+ WHERE LOWER(i.email) = ? AND i.org_id = ? AND i.status = 'pending'`,
285
+ args: [ctx.email.toLowerCase(), scopedDelegatedOrgId],
286
+ })
287
+ : { rows: [] }
288
+ : await e.execute({
289
+ sql: `SELECT i.id AS id, i.org_id AS "orgId", o.name AS "orgName", i.invited_by AS "invitedBy"
290
+ FROM org_invitations i
291
+ INNER JOIN organizations o ON i.org_id = o.id
292
+ WHERE LOWER(i.email) = ? AND i.status = 'pending'`,
293
+ args: [ctx.email.toLowerCase()],
294
+ });
234
295
  const pendingInvitations = invitesRes.rows.map((r) => ({
235
296
  id: String(r.id),
236
297
  orgId: String(r.orgId ?? r.org_id),
237
298
  orgName: String(r.orgName ?? r.org_name),
238
299
  invitedBy: String(r.invitedBy ?? r.invited_by),
239
300
  }));
240
- scheduleFederatedOrgSync(event, { ...ctx, icon, iconRevision });
301
+ if (!delegatedOrgMember) {
302
+ scheduleFederatedOrgSync(event, { ...ctx, icon, iconRevision });
303
+ }
241
304
  return {
242
- email: ctx.email,
243
- orgId: ctx.orgId,
244
- orgName: ctx.orgName,
245
- role: ctx.role,
305
+ email: delegatedOrgMember ? (delegatedEmail ?? "") : ctx.email,
306
+ orgId: visibleOrgId,
307
+ orgName: visibleOrgName,
308
+ role: visibleRole,
246
309
  icon,
247
310
  iconRevision,
248
311
  emailConfigured: await isEmailConfigured(),
@@ -268,8 +331,13 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
268
331
  // so the value would sit in JSON any script on the page can read, and it
269
332
  // signs the JWTs peers accept as first-party callers. Reveal is an explicit
270
333
  // owner GET on /_agent-native/org/a2a-secret.
271
- a2aSecretSet: canManageOrgA2ASecret(ctx.role) ? a2aSecretSet : undefined,
272
- soloDeploymentAdmin: ctx.orgId ? undefined : isTrustedSelfHostedRuntime(),
334
+ a2aSecretSet: canManageOrgA2ASecret(visibleRole) ? a2aSecretSet : undefined,
335
+ a2aMemberDelegationEnabled: visibleRole === "owner" ? a2aMemberDelegationEnabled : undefined,
336
+ soloDeploymentAdmin: delegatedOrgMember
337
+ ? undefined
338
+ : visibleOrgId
339
+ ? undefined
340
+ : isTrustedSelfHostedRuntime(),
273
341
  };
274
342
  });
275
343
  export const retryPendingFederatedRemovalHandler = defineEventHandler(async (event) => {
@@ -1558,6 +1626,33 @@ export const setRequiredAuthProviderHandler = defineEventHandler(async (event) =
1558
1626
  const result = await setRequiredAuthProvider(ctx.orgId, provider);
1559
1627
  return { provider, ...result };
1560
1628
  });
1629
+ export const setA2AMemberDelegationHandler = defineEventHandler(async (event) => {
1630
+ const ctx = await getOrgContext(event);
1631
+ if (!ctx.orgId) {
1632
+ throw createError({ statusCode: 400, message: "No active organization" });
1633
+ }
1634
+ if (ctx.role !== "owner") {
1635
+ throw createError({
1636
+ statusCode: 403,
1637
+ message: "Only the organization owner can manage member delegation",
1638
+ });
1639
+ }
1640
+ const body = await readBody(event);
1641
+ if (typeof body?.enabled !== "boolean") {
1642
+ throw createError({
1643
+ statusCode: 400,
1644
+ message: "enabled must be a boolean",
1645
+ });
1646
+ }
1647
+ const e = await exec();
1648
+ await e.execute({
1649
+ sql: `UPDATE organizations
1650
+ SET a2a_member_delegation_enabled = ?
1651
+ WHERE id = ?`,
1652
+ args: [body.enabled, ctx.orgId],
1653
+ });
1654
+ return { enabled: body.enabled };
1655
+ });
1561
1656
  export const revealA2ASecretHandler = defineEventHandler(async (event) => {
1562
1657
  const ctx = await getOrgContext(event);
1563
1658
  if (!ctx.orgId) {
@@ -1886,6 +1981,7 @@ export const receiveA2ASecretHandler = defineEventHandler(async (event) => {
1886
1981
  verified = null;
1887
1982
  }
1888
1983
  if (!verified ||
1984
+ verified.identityAssurance !== "organization" ||
1889
1985
  verified.orgId !== localOrgId ||
1890
1986
  verified.orgDomain?.trim().toLowerCase() !== orgDomain) {
1891
1987
  throw createError({
@@ -159,13 +159,14 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
159
159
  workspaceAppDefaultVisibility: import("./types.js").WorkspaceAppDefaultVisibility;
160
160
  signInMethods: import("./sign-in-methods.js").OrgSignInMethods;
161
161
  a2aSecretSet: boolean;
162
+ a2aMemberDelegationEnabled: boolean;
162
163
  soloDeploymentAdmin: boolean;
163
164
  }>>;
164
165
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
165
166
  success: boolean;
167
+ id?: undefined;
166
168
  name?: undefined;
167
169
  role?: undefined;
168
- id?: undefined;
169
170
  } | {
170
171
  success?: undefined;
171
172
  id: string;
@@ -1,10 +1,12 @@
1
+ import type { H3Event } from "h3";
1
2
  export declare function isMissingOrganizationTableError(error: unknown): boolean;
2
3
  export declare function isOrgMember(orgId: string, email: string, options?: {
3
4
  requireOrganizationMetadata?: boolean;
5
+ requestEvent?: H3Event;
4
6
  }): Promise<boolean>;
5
7
  /**
6
- * Checks membership for an A2A identity authenticated by an organization
7
- * secret. Unlike legacy callers, this requires readable organization metadata
8
- * as well as the membership row before the token subject can become an owner.
8
+ * Checks the live roster for an opted-in org-member A2A assertion. This proves
9
+ * membership in the asserted org; authorization still caps the identity at
10
+ * member regardless of the stored owner/admin role.
9
11
  */
10
- export declare function isOrgMemberForA2A(orgId: string, email: string): Promise<boolean>;
12
+ export declare function isOrgMemberForA2A(orgId: string, email: string, requestEvent?: H3Event): Promise<boolean>;
@@ -65,23 +65,25 @@ async function isOrgMemberWithPolicy(orgId, email, options) {
65
65
  const validation = await validateFederatedOrganizationMembershipForCurrentRequest({
66
66
  orgId,
67
67
  email: normalized,
68
- });
68
+ }, options.requestEvent);
69
69
  return validation.active;
70
70
  }
71
71
  export function isOrgMember(orgId, email, options = {}) {
72
72
  return isOrgMemberWithPolicy(orgId, email, {
73
73
  allowMissingOrganizationTable: !options.requireOrganizationMetadata,
74
74
  requireOrganizationRecord: !!options.requireOrganizationMetadata,
75
+ requestEvent: options.requestEvent,
75
76
  });
76
77
  }
77
78
  /**
78
- * Checks membership for an A2A identity authenticated by an organization
79
- * secret. Unlike legacy callers, this requires readable organization metadata
80
- * as well as the membership row before the token subject can become an owner.
79
+ * Checks the live roster for an opted-in org-member A2A assertion. This proves
80
+ * membership in the asserted org; authorization still caps the identity at
81
+ * member regardless of the stored owner/admin role.
81
82
  */
82
- export function isOrgMemberForA2A(orgId, email) {
83
+ export function isOrgMemberForA2A(orgId, email, requestEvent) {
83
84
  return isOrgMemberWithPolicy(orgId, email, {
84
85
  allowMissingOrganizationTable: false,
85
86
  requireOrganizationRecord: true,
87
+ requestEvent,
86
88
  });
87
89
  }
@@ -284,4 +284,10 @@ export const ORG_MIGRATIONS = [
284
284
  ON private_icon_assets (owner_email, org_id, created_at);
285
285
  `,
286
286
  },
287
+ {
288
+ version: 1035,
289
+ name: "organization-a2a-member-delegation",
290
+ sql: `ALTER TABLE organizations
291
+ ADD COLUMN IF NOT EXISTS a2a_member_delegation_enabled BOOLEAN NOT NULL DEFAULT FALSE`,
292
+ },
287
293
  ];
@@ -24,6 +24,7 @@ type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
24
24
  * PUT /_agent-native/org/domain — set/clear allowed email domain (owner/admin)
25
25
  * PUT /_agent-native/org/workspace-url — set/clear the org's workspace origin (owner/admin)
26
26
  * PUT /_agent-native/org/auth-provider — require/clear Google sign-in (owner/admin)
27
+ * PUT /_agent-native/org/a2a-member-delegation — allow org-member A2A assertions (owner only)
27
28
  * PUT /_agent-native/org/visual-identity — set/clear workspace icon (owner/admin)
28
29
  * GET /_agent-native/org/a2a-secret — reveal A2A secret on demand (owner/admin)
29
30
  * PUT /_agent-native/org/a2a-secret — regenerate or set A2A secret (owner/admin)
@@ -4,7 +4,7 @@ import { awaitBootstrap, getH3App, FRAMEWORK_PREFIX, markDefaultPluginProvided,
4
4
  import { listAppRolesHandler, setAppRoleHandler, getAppPermissionsHandler, setAppPermissionsHandler, resetAppPermissionHandler, } from "./app-roles-handlers.js";
5
5
  import { listSSOProvidersHandler, createSSOProviderHandler, verifySSOProviderHandler, deleteSSOProviderHandler, getSCIMHandler, createSCIMHandler, deleteSCIMHandler, } from "./enterprise-auth-handlers.js";
6
6
  import { CROSS_APP_ORG_FEDERATION_FLAG } from "./feature-flags.js";
7
- import { getMyOrgHandler, createOrgHandler, updateOrgHandler, deleteOrgHandler, switchOrgHandler, listMembersHandler, removeMemberHandler, retryPendingFederatedRemovalHandler, changeMemberRoleHandler, listInvitationsHandler, createInvitationHandler, acceptInvitationHandler, joinByDomainHandler, setDomainHandler, setWorkspaceUrlHandler, setRequiredAuthProviderHandler, revealA2ASecretHandler, setA2ASecretHandler, syncA2ASecretHandler, receiveA2ASecretHandler, setWorkspaceAppDefaultVisibilityHandler, setOrgVisualIdentityHandler, } from "./handlers.js";
7
+ import { getMyOrgHandler, createOrgHandler, updateOrgHandler, deleteOrgHandler, switchOrgHandler, listMembersHandler, removeMemberHandler, retryPendingFederatedRemovalHandler, changeMemberRoleHandler, listInvitationsHandler, createInvitationHandler, acceptInvitationHandler, joinByDomainHandler, setDomainHandler, setWorkspaceUrlHandler, setRequiredAuthProviderHandler, setA2AMemberDelegationHandler, revealA2ASecretHandler, setA2ASecretHandler, syncA2ASecretHandler, receiveA2ASecretHandler, setWorkspaceAppDefaultVisibilityHandler, setOrgVisualIdentityHandler, } from "./handlers.js";
8
8
  import { readWorkspacePrivateIconHandler, uploadWorkspacePrivateIconHandler, } from "./private-icon-handlers.js";
9
9
  import { orgStore } from "./schema-store.js";
10
10
  const ORG_PREFIX = `${FRAMEWORK_PREFIX}/org`;
@@ -56,6 +56,7 @@ async function workspaceIconUploadResponse(event) {
56
56
  * PUT /_agent-native/org/domain — set/clear allowed email domain (owner/admin)
57
57
  * PUT /_agent-native/org/workspace-url — set/clear the org's workspace origin (owner/admin)
58
58
  * PUT /_agent-native/org/auth-provider — require/clear Google sign-in (owner/admin)
59
+ * PUT /_agent-native/org/a2a-member-delegation — allow org-member A2A assertions (owner only)
59
60
  * PUT /_agent-native/org/visual-identity — set/clear workspace icon (owner/admin)
60
61
  * GET /_agent-native/org/a2a-secret — reveal A2A secret on demand (owner/admin)
61
62
  * PUT /_agent-native/org/a2a-secret — regenerate or set A2A secret (owner/admin)
@@ -205,6 +206,13 @@ export function createOrgPlugin() {
205
206
  }
206
207
  return retryPendingFederatedRemovalHandler(event);
207
208
  }));
209
+ app.use(`${ORG_PREFIX}/a2a-member-delegation`, defineEventHandler(async (event) => {
210
+ if (getMethod(event) !== "PUT") {
211
+ setResponseStatus(event, 405);
212
+ return { error: "Method not allowed" };
213
+ }
214
+ return setA2AMemberDelegationHandler(event);
215
+ }));
208
216
  app.use(`${ORG_PREFIX}/workspace-app-default-visibility`, defineEventHandler(async (event) => {
209
217
  if (getMethod(event) !== "PUT") {
210
218
  setResponseStatus(event, 405);
@@ -3,6 +3,7 @@ import { getDbExec } from "../db/client.js";
3
3
  import { getIconAsset, IconUploadBodyError, putIconAsset, readIconUploadFormData, readIconAssetForAuthorizedReference, } from "../icon-assets/index.js";
4
4
  import { readFederatedWorkspaceIcon, uploadFederatedWorkspaceIcon, verifyFederatedWorkspaceIconOwner, } from "../icon-assets/workspace-transport.js";
5
5
  import { getSession } from "../server/auth.js";
6
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
6
7
  import { runWithRequestContext } from "../server/request-context.js";
7
8
  import { getOrgContext } from "./context.js";
8
9
  import { validateFederatedOrganizationMembership } from "./federation.js";
@@ -118,20 +119,13 @@ export const readWorkspacePrivateIconHandler = defineEventHandler(async (event)
118
119
  });
119
120
  }
120
121
  const [, library, orgId, assetId] = match;
121
- const membership = await getDbExec().execute({
122
- sql: `SELECT role FROM org_members
123
- WHERE org_id = ? AND LOWER(email) = ?
124
- AND federation_removal_pending_at IS NULL LIMIT 1`,
125
- args: [orgId, session.email.toLowerCase()],
126
- });
127
- if (!membership.rows.length)
122
+ const role = await readOrgMemberRole(orgId, session.email);
123
+ if (!role)
128
124
  throw createError({
129
125
  statusCode: 404,
130
126
  message: "Workspace icon not found",
131
127
  });
132
- if (library &&
133
- membership.rows[0]?.role !== "owner" &&
134
- membership.rows[0]?.role !== "admin") {
128
+ if (library && role !== "owner" && role !== "admin") {
135
129
  throw createError({
136
130
  statusCode: 404,
137
131
  message: "Workspace icon not found",
@@ -63,6 +63,8 @@ export interface OrgInfo {
63
63
  * fetches it on demand from `GET /_agent-native/org/a2a-secret`.
64
64
  */
65
65
  a2aSecretSet?: boolean;
66
+ /** Owners only; enables current-member assertions signed with the org secret. */
67
+ a2aMemberDelegationEnabled?: boolean;
66
68
  /**
67
69
  * Set only when the viewer has no active organization. True on a
68
70
  * single-tenant self-hosted deployment, where that viewer manages the
@@ -1,11 +1,13 @@
1
1
  import { createHmac, randomUUID } from "node:crypto";
2
2
  import { canonicalA2AAudience } from "../a2a/audience.js";
3
3
  import { isLoopbackAddress } from "../a2a/auth-policy.js";
4
- import { signA2AToken } from "../a2a/client.js";
4
+ import { signA2AOrgMemberDelegationToken, signA2AToken, } from "../a2a/client.js";
5
5
  import { getAppConfig } from "../app-config/index.js";
6
6
  import { getDbExec } from "../db/client.js";
7
7
  import { readDeployCredentialEnv } from "../server/credential-provider.js";
8
8
  import { isHostedWorkspaceRuntime, resolveVercelDeploymentProtectionHeaders, } from "../server/deployment-protection.js";
9
+ import { effectiveOrgMemberRole } from "../server/personal-provider-key-policy.js";
10
+ import { getRequestContext } from "../server/request-context.js";
9
11
  import { workspaceUserGroupsIncludeUser } from "../workspace-connections/groups.js";
10
12
  import { isMissingOrganizationTableError } from "./membership.js";
11
13
  import { getCachedWorkspaceAccess, invalidateMemberOrgCaches, rememberWorkspaceAccess, workspaceAccessGenerationNow, } from "./request-org-cache.js";
@@ -239,25 +241,54 @@ async function resolveHostedWorkspaceAppAuth(configuredDirectory, context, email
239
241
  const orgSecret = orgId
240
242
  ? (await cachedOrgA2ASecret(orgId))?.trim() || undefined
241
243
  : undefined;
242
- const signingSecret = orgSecret || readDeployCredentialEnv("A2A_SECRET")?.trim();
244
+ const deploymentSecret = readDeployCredentialEnv("A2A_SECRET")?.trim();
245
+ const signingSecret = orgSecret || deploymentSecret;
243
246
  if (!signingSecret) {
244
247
  return workspaceAppAccessUnavailable("a2a-secret-missing", { audience });
245
248
  }
246
249
  stage = "token-signing-failed";
247
- const token = await signA2AToken(email, normalizedOrgDomain, orgSecret, {
248
- expiresIn: "1m",
249
- preferGlobalSecret: false,
250
- audience,
251
- extraClaims: {
252
- jti: randomUUID(),
253
- },
254
- });
250
+ const requestContext = getRequestContext();
251
+ let token;
252
+ if (requestContext?.a2aIdentityAssurance === "org-member") {
253
+ if (!orgId ||
254
+ !normalizedOrgDomain ||
255
+ requestContext.orgId?.trim() !== orgId ||
256
+ requestContext.userEmail?.trim().toLowerCase() !== email) {
257
+ return workspaceAppAccessUnavailable("token-signing-failed", {
258
+ audience,
259
+ detail: "Delegated member identity is missing or out of scope.",
260
+ });
261
+ }
262
+ if (!orgSecret) {
263
+ return workspaceAppAccessUnavailable("a2a-secret-missing", {
264
+ audience,
265
+ });
266
+ }
267
+ if (orgSecret === deploymentSecret) {
268
+ return workspaceAppAccessUnavailable("token-signing-failed", {
269
+ audience,
270
+ detail: "Organization and deployment A2A secrets must differ for delegated identity.",
271
+ });
272
+ }
273
+ token = await signA2AOrgMemberDelegationToken(email, normalizedOrgDomain, orgSecret, audience);
274
+ }
275
+ else {
276
+ token = await signA2AToken(email, normalizedOrgDomain, orgSecret, {
277
+ expiresIn: "1m",
278
+ preferGlobalSecret: false,
279
+ audience,
280
+ extraClaims: {
281
+ jti: randomUUID(),
282
+ },
283
+ });
284
+ }
255
285
  const protectionHeaders = resolveVercelDeploymentProtectionHeaders(url.toString());
256
286
  const requestKey = createHmac("sha256", signingSecret)
257
287
  .update(JSON.stringify([
258
288
  configuredDirectory,
259
289
  email,
260
290
  orgId,
291
+ requestContext?.a2aIdentityAssurance ?? "user",
261
292
  normalizedOrgDomain ?? null,
262
293
  getAppConfig().app.url ?? "http://localhost:3000",
263
294
  protectionHeaders,
@@ -603,7 +634,13 @@ export async function claimWorkspaceAppForOrganization(appId, context) {
603
634
  if (!member || !(await isActiveWorkspaceOrgMember(member, orgId, email))) {
604
635
  return false;
605
636
  }
606
- return claimWorkspaceAppOrganization(db, normalizedAppId, orgId, member);
637
+ const role = effectiveOrgMemberRole(member.role, orgId, email);
638
+ return role
639
+ ? claimWorkspaceAppOrganization(db, normalizedAppId, orgId, {
640
+ ...member,
641
+ role,
642
+ })
643
+ : false;
607
644
  }
608
645
  catch (error) {
609
646
  console.error("[workspace-app-access] organization claim failed", error);
@@ -667,7 +704,13 @@ export async function checkWorkspaceAppAccess(appId, context) {
667
704
  !(await isActiveWorkspaceOrgMember(member, orgId, email))) {
668
705
  return false;
669
706
  }
670
- return claimWorkspaceAppOrganization(db, normalizedAppId, orgId, member);
707
+ const role = effectiveOrgMemberRole(member.role, orgId, email);
708
+ return role
709
+ ? claimWorkspaceAppOrganization(db, normalizedAppId, orgId, {
710
+ ...member,
711
+ role,
712
+ })
713
+ : false;
671
714
  }
672
715
  const ownerEmail = normalizedEmail(typeof app.owner_email === "string" ? app.owner_email : "");
673
716
  const resourceOrgId = (typeof app.org_id === "string" ? app.org_id : "").trim() || null;
@@ -688,9 +731,14 @@ export async function checkWorkspaceAppAccess(appId, context) {
688
731
  if (!member || !(await isActiveWorkspaceOrgMember(member, orgId, email))) {
689
732
  return false;
690
733
  }
691
- const memberRole = member.role;
734
+ const memberRole = effectiveOrgMemberRole(member.role, orgId, email);
735
+ if (!memberRole)
736
+ return false;
692
737
  if (canClaimCallerOrg) {
693
- if (!(await claimWorkspaceAppOrganization(db, normalizedAppId, orgId, member))) {
738
+ if (!(await claimWorkspaceAppOrganization(db, normalizedAppId, orgId, {
739
+ ...member,
740
+ role: memberRole,
741
+ }))) {
694
742
  return false;
695
743
  }
696
744
  }
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -75,7 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- provider?: undefined;
78
+ message?: undefined;
79
+ found?: undefined;
79
80
  providers: {
80
81
  id: string;
81
82
  label: string;
@@ -86,13 +87,13 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
86
87
  updatedAt: number;
87
88
  }[];
88
89
  count: number;
89
- found?: undefined;
90
+ provider?: undefined;
90
91
  deleted?: undefined;
91
92
  registered?: undefined;
92
93
  id?: undefined;
93
94
  label?: undefined;
94
- message?: undefined;
95
95
  } | {
96
+ message?: undefined;
96
97
  providers?: undefined;
97
98
  count?: undefined;
98
99
  found: boolean;
@@ -101,32 +102,31 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
101
102
  registered?: undefined;
102
103
  id?: undefined;
103
104
  label?: undefined;
104
- message?: undefined;
105
105
  } | {
106
- provider?: undefined;
106
+ message?: undefined;
107
107
  providers?: undefined;
108
108
  count?: undefined;
109
+ provider?: undefined;
109
110
  found: boolean;
110
111
  id: string;
111
112
  deleted?: undefined;
112
113
  registered?: undefined;
113
114
  label?: undefined;
114
- message?: undefined;
115
115
  } | {
116
- provider?: undefined;
116
+ message?: undefined;
117
+ found?: undefined;
117
118
  providers?: undefined;
118
119
  count?: undefined;
119
- found?: undefined;
120
+ provider?: undefined;
120
121
  deleted: boolean;
121
122
  id: string;
122
123
  registered?: undefined;
123
124
  label?: undefined;
124
- message?: undefined;
125
125
  } | {
126
- provider?: undefined;
126
+ found?: undefined;
127
127
  providers?: undefined;
128
128
  count?: undefined;
129
- found?: undefined;
129
+ provider?: undefined;
130
130
  deleted?: undefined;
131
131
  registered: boolean;
132
132
  id: string;