@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
@@ -183,6 +183,7 @@ async function verifyContinuationToken(token, context, options) {
183
183
  verified = await verifyA2AToken(token, context.event, {
184
184
  includeClaims: true,
185
185
  globalSecretOnly: true,
186
+ allowAnthropicManagedContinuation: true,
186
187
  });
187
188
  }
188
189
  catch (cause) {
@@ -1,8 +1,9 @@
1
- import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
- import { getGlobalA2ASecret, signA2AOrganizationToken, signA2AToken, } from "./client.js";
1
+ import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
2
+ import { getGlobalA2ASecret, signA2AOrgMemberDelegationToken, signA2AOrganizationToken, signA2AToken, } from "./client.js";
3
3
  const DEFAULT_A2A_CALLER_TOKEN_TTL = "5m";
4
4
  export async function resolveA2ACallerAuth(options) {
5
5
  const userEmail = getRequestUserEmail();
6
+ const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
6
7
  const globalSecret = getGlobalA2ASecret();
7
8
  const metadata = {};
8
9
  if (userEmail)
@@ -24,6 +25,7 @@ export async function resolveA2ACallerAuth(options) {
24
25
  apiKeyAttempts.push(token);
25
26
  };
26
27
  if (userEmail &&
28
+ !isOrgMemberDelegation &&
27
29
  options?.audience &&
28
30
  globalSecret &&
29
31
  (options?.userIdentityOnly || !orgId || orgDomain?.trim())) {
@@ -40,6 +42,9 @@ export async function resolveA2ACallerAuth(options) {
40
42
  orgDomain &&
41
43
  options?.audience &&
42
44
  (orgSecret || globalSecret)) {
45
+ if (userEmail && orgSecret) {
46
+ addApiKeyAttempt(await signA2AOrgMemberDelegationToken(userEmail, orgDomain, orgSecret, options.audience));
47
+ }
43
48
  addApiKeyAttempt(await signA2AOrganizationToken(orgDomain, orgSecret, undefined, {
44
49
  expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
45
50
  audience: options?.audience,
@@ -64,6 +64,7 @@ export declare function signA2AOrganizationToken(orgDomain: string | undefined,
64
64
  preferGlobalSecret?: boolean;
65
65
  extraClaims?: Record<string, unknown>;
66
66
  }): Promise<string>;
67
+ export declare function signA2AOrgMemberDelegationToken(email: string, orgDomain: string, orgSecret: string, audience: string | string[]): Promise<string>;
67
68
  export declare function shouldPreferGlobalA2ASecret(orgSecret?: string): boolean;
68
69
  export declare class A2AClient {
69
70
  private baseUrl;
@@ -8,6 +8,7 @@ import { getRequestContext } from "../server/request-context.js";
8
8
  import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
9
9
  import { canonicalA2AAudience } from "./audience.js";
10
10
  import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
11
+ import { A2A_ORG_MEMBER_DELEGATION_TYPE } from "./organization-identity.js";
11
12
  import { RemoteAgentCredentialRejectedError } from "./remote-agent-auth.js";
12
13
  import { workspacePrivateOrigins } from "./workspace-private-origins.js";
13
14
  const DEFAULT_A2A_POLL_REQUEST_TIMEOUT_MS = 15_000;
@@ -172,6 +173,27 @@ export async function signA2AOrganizationToken(orgDomain, orgSecret, orgId, opti
172
173
  jwt.setAudience(options.audience);
173
174
  return jwt.sign(new TextEncoder().encode(secret));
174
175
  }
176
+ export async function signA2AOrgMemberDelegationToken(email, orgDomain, orgSecret, audience) {
177
+ const normalizedEmail = email.trim().toLowerCase();
178
+ const normalizedDomain = orgDomain.trim().toLowerCase();
179
+ const secret = orgSecret.trim();
180
+ if (!normalizedEmail || !normalizedDomain || !secret || !audience) {
181
+ throw new Error("A member email, organization domain, organization secret, and audience are required.");
182
+ }
183
+ const appUrl = getAppConfig().app.url ?? "http://localhost:3000";
184
+ return new jose.SignJWT({
185
+ typ: A2A_ORG_MEMBER_DELEGATION_TYPE,
186
+ sub: normalizedEmail,
187
+ org_domain: normalizedDomain,
188
+ })
189
+ .setProtectedHeader({ alg: "HS256" })
190
+ .setIssuer(appUrl)
191
+ .setIssuedAt()
192
+ .setJti(randomUUID())
193
+ .setExpirationTime("5m")
194
+ .setAudience(audience)
195
+ .sign(new TextEncoder().encode(secret));
196
+ }
175
197
  export function shouldPreferGlobalA2ASecret(orgSecret) {
176
198
  return !!getGlobalA2ASecret() || !orgSecret;
177
199
  }
@@ -1517,7 +1539,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
1517
1539
  attempts.push(token);
1518
1540
  };
1519
1541
  const globalSecret = getGlobalA2ASecret();
1542
+ const isOrgMemberDelegation = getRequestContext()?.a2aIdentityAssurance === "org-member";
1520
1543
  if (opts?.userEmail &&
1544
+ !isOrgMemberDelegation &&
1521
1545
  globalSecret &&
1522
1546
  (!opts.orgId || opts.orgDomain?.trim())) {
1523
1547
  add(await signA2AToken(opts.userEmail, opts.orgDomain, undefined, {
@@ -1525,6 +1549,9 @@ async function buildA2AApiKeyAttempts(opts, audience) {
1525
1549
  audience,
1526
1550
  }));
1527
1551
  }
1552
+ if (opts?.userEmail && opts.orgDomain && opts.orgSecret && audience) {
1553
+ add(await signA2AOrgMemberDelegationToken(opts.userEmail, opts.orgDomain, opts.orgSecret, audience));
1554
+ }
1528
1555
  if (opts?.orgDomain && (opts.orgSecret || globalSecret)) {
1529
1556
  add(await signA2AOrganizationToken(opts.orgDomain, opts.orgSecret, undefined, { audience }));
1530
1557
  }
@@ -302,9 +302,11 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
302
302
  const verifiedEmail = processorMeta.verifiedEmail;
303
303
  const identityAssurance = processorMeta.identityAssurance === "organization"
304
304
  ? "organization"
305
- : processorMeta.identityAssurance === "user"
306
- ? "user"
307
- : undefined;
305
+ : processorMeta.identityAssurance === "org-member"
306
+ ? "org-member"
307
+ : processorMeta.identityAssurance === "user"
308
+ ? "user"
309
+ : undefined;
308
310
  const orgDomainHint = processorMeta.orgDomainHint;
309
311
  const verifiedOrgId = typeof processorMeta.verifiedOrgId === "string"
310
312
  ? processorMeta.verifiedOrgId.trim()
@@ -367,6 +369,9 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
367
369
  const { runWithRequestContext } = await import("../server/request-context.js");
368
370
  await runWithRequestContext({
369
371
  userEmail: verifiedEmail,
372
+ ...(identityAssurance
373
+ ? { a2aIdentityAssurance: identityAssurance }
374
+ : {}),
370
375
  ...(resolvedOrgId
371
376
  ? { orgId: resolvedOrgId }
372
377
  : verifiedEmail
@@ -547,12 +552,19 @@ async function withA2ARequestContext(metadata, event, fn) {
547
552
  const { runWithRequestContext } = await import("../server/request-context.js");
548
553
  const verifiedEmail = event?.context?.__a2aVerifiedEmail ?? undefined;
549
554
  const verifiedOrgId = event?.context?.__a2aVerifiedOrgId ?? undefined;
555
+ const rawIdentityAssurance = event?.context?.__a2aIdentityAssurance;
556
+ const identityAssurance = rawIdentityAssurance === "user" ||
557
+ rawIdentityAssurance === "organization" ||
558
+ rawIdentityAssurance === "org-member"
559
+ ? rawIdentityAssurance
560
+ : undefined;
550
561
  const requestOrigin = requestOriginForContext(metadata, event);
551
562
  if (event?.context && verifiedEmail && !verifiedOrgId) {
552
563
  markExplicitPersonalOrgScope(event);
553
564
  }
554
565
  return runWithRequestContext({
555
566
  userEmail: verifiedEmail,
567
+ ...(identityAssurance ? { a2aIdentityAssurance: identityAssurance } : {}),
556
568
  ...(verifiedOrgId
557
569
  ? { orgId: verifiedOrgId }
558
570
  : verifiedEmail
@@ -1,4 +1,5 @@
1
1
  import type { JWTPayload } from "jose";
2
+ export declare const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
2
3
  export interface VerifiedA2AOrganizationIdentity {
3
4
  orgId: string;
4
5
  orgDomain: string | null;
@@ -6,10 +7,12 @@ export interface VerifiedA2AOrganizationIdentity {
6
7
  export interface A2AOrganizationCredential extends VerifiedA2AOrganizationIdentity {
7
8
  orgDomain: string;
8
9
  secret: string;
10
+ a2aMemberDelegationEnabled?: boolean;
9
11
  }
10
12
  /**
11
13
  * An organization secret authenticates an organization principal. Its JWT
12
- * subject is caller-controlled data and must never become a user identity.
14
+ * subject is caller-controlled data unless it is an opted-in, audience-bound
15
+ * member delegation verified against the receiver's current roster.
13
16
  */
14
17
  export declare function organizationPrincipalClaims(payload: JWTPayload, organization: A2AOrganizationCredential): JWTPayload | null;
15
18
  /**
@@ -1,6 +1,8 @@
1
+ export const A2A_ORG_MEMBER_DELEGATION_TYPE = "agent-native-org-member-delegation";
1
2
  /**
2
3
  * An organization secret authenticates an organization principal. Its JWT
3
- * subject is caller-controlled data and must never become a user identity.
4
+ * subject is caller-controlled data unless it is an opted-in, audience-bound
5
+ * member delegation verified against the receiver's current roster.
4
6
  */
5
7
  export function organizationPrincipalClaims(payload, organization) {
6
8
  const orgDomain = typeof payload.org_domain === "string"
@@ -2,16 +2,15 @@ import * as jose from "jose";
2
2
  import "../authorization/check-action.js";
3
3
  import type { A2AConfig, AgentSkill } from "./types.js";
4
4
  /**
5
- * Result of verifying an inbound A2A JWT. `email` is present only when a
6
- * deployment-secret user assertion verifies; org-secret tokens carry an
7
- * organization principal and never supply a user identity. `orgDomain` and
8
- * `orgId` are bound to local organization metadata when present.
5
+ * Result of verifying an inbound A2A JWT. `email` is present for deployment
6
+ * user assertions or an explicitly opted-in org-member delegation. `orgDomain`
7
+ * and `orgId` are bound to local organization metadata when present.
9
8
  */
10
9
  export interface A2ATokenPayload {
11
10
  email: string | null;
12
11
  orgDomain: string | null;
13
12
  orgId?: string;
14
- identityAssurance?: "user" | "organization";
13
+ identityAssurance?: "user" | "organization" | "org-member";
15
14
  claims?: jose.JWTPayload;
16
15
  }
17
16
  export declare class A2AIdentityVerificationUnavailableError extends Error {
@@ -47,6 +46,8 @@ export declare function verifyA2AToken(token: string, event?: any, audienceOptio
47
46
  globalSecretOnly?: boolean;
48
47
  /** Only use this explicitly configured credential; never fall back. */
49
48
  verificationSecret?: string;
49
+ /** Dedicated verifier for the typed Anthropic continuation credential. */
50
+ allowAnthropicManagedContinuation?: boolean;
50
51
  requireAudience?: boolean;
51
52
  }): Promise<A2ATokenPayload>;
52
53
  export type A2ATokenFailureCause = "malformed" | "secret-missing" | "audience-unresolvable" | "audience" | "issuer" | "expired" | "signature" | "invalid-claims" | "mcp-credential" | "missing-subject" | "org-binding" | "org-lookup-failed" | "undetermined";
@@ -19,7 +19,7 @@ import { addAgentProfileExamples } from "./app-profile.js";
19
19
  import { canonicalA2AAudience } from "./audience.js";
20
20
  import { hasConfiguredA2ASecret, isA2AProductionRuntime, } from "./auth-policy.js";
21
21
  import { handleJsonRpcH3, processA2ATaskFromQueue } from "./handlers.js";
22
- import { organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
22
+ import { A2A_ORG_MEMBER_DELEGATION_TYPE, organizationPrincipalClaims, verifyA2AOrganizationIdentity, } from "./organization-identity.js";
23
23
  import { claimA2AApproval, a2aTaskLeaseThreadId, getA2AApprovalForOwner, settleA2AApproval, } from "./task-store.js";
24
24
  let _warnedUnauthA2A = false;
25
25
  function warnA2AUnauthOnce() {
@@ -59,7 +59,7 @@ export function isA2AIdentityVerificationUnavailableError(error) {
59
59
  (error instanceof Error &&
60
60
  error.name === "A2AIdentityVerificationUnavailableError"));
61
61
  }
62
- function identityPayload(payload, audienceOptions, verifiedOrganization) {
62
+ function identityPayload(payload, audienceOptions, verifiedOrganization, identityAssurance) {
63
63
  const email = typeof payload.sub === "string" && payload.sub.trim()
64
64
  ? payload.sub.trim()
65
65
  : null;
@@ -79,9 +79,34 @@ function identityPayload(payload, audienceOptions, verifiedOrganization) {
79
79
  ...(!email && verifiedOrganization
80
80
  ? { identityAssurance: "organization" }
81
81
  : {}),
82
+ ...(identityAssurance ? { identityAssurance } : {}),
82
83
  ...(audienceOptions?.includeClaims ? { claims } : {}),
83
84
  };
84
85
  }
86
+ function validOrgMemberDelegationClaims(payload) {
87
+ const email = typeof payload.sub === "string" ? payload.sub.trim() : "";
88
+ const jti = typeof payload.jti === "string" ? payload.jti.trim() : "";
89
+ const issuedAt = payload.iat;
90
+ const expiresAt = payload.exp;
91
+ const now = Math.floor(Date.now() / 1_000);
92
+ const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
93
+ return (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE &&
94
+ email.length <= 320 &&
95
+ email.includes("@") &&
96
+ !/[\u0000-\u001f\u007f]/.test(email) &&
97
+ jti.length > 0 &&
98
+ jti.length <= 128 &&
99
+ !/[\u0000-\u001f\u007f]/.test(jti) &&
100
+ typeof issuedAt === "number" &&
101
+ Number.isSafeInteger(issuedAt) &&
102
+ typeof expiresAt === "number" &&
103
+ Number.isSafeInteger(expiresAt) &&
104
+ issuedAt <= now + 60 &&
105
+ expiresAt > now &&
106
+ expiresAt > issuedAt &&
107
+ expiresAt - issuedAt <= 15 * 60 &&
108
+ audiences.some((audience) => typeof audience === "string" && audience.trim()));
109
+ }
85
110
  async function verifyJwtWithSecret(token, secret, options) {
86
111
  try {
87
112
  const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), options);
@@ -327,6 +352,17 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
327
352
  if (globalSecret) {
328
353
  const payload = await verifyJwtWithSecret(token, globalSecret, verifyOptions);
329
354
  if (payload) {
355
+ // Typed credentials have their own verifier. Only the managed-agent
356
+ // confirmation verifier may accept its continuation token here.
357
+ if (typeof payload.typ !== "undefined") {
358
+ const managedContinuation = payload.typ === "anthropic-managed-continuation" &&
359
+ audienceOptions?.globalSecretOnly === true &&
360
+ audienceOptions.allowAnthropicManagedContinuation === true &&
361
+ audienceOptions.includeClaims === true;
362
+ if (!managedContinuation) {
363
+ return { payload: { email: null, orgDomain: null } };
364
+ }
365
+ }
330
366
  if (typeof payload.org_domain === "string" && payload.org_domain.trim()) {
331
367
  let organization;
332
368
  try {
@@ -388,12 +424,49 @@ async function verifyA2ATokenInternal(token, event, audienceOptions) {
388
424
  }
389
425
  if (!organization)
390
426
  return { payload: { email: null, orgDomain: null } };
391
- const payload = await verifyJwtWithSecret(token, organization.secret, verifyOptions);
427
+ const orgVerifyOptions = unverifiedPayload?.typ === A2A_ORG_MEMBER_DELEGATION_TYPE
428
+ ? { ...verifyOptions, algorithms: ["HS256"] }
429
+ : verifyOptions;
430
+ const payload = await verifyJwtWithSecret(token, organization.secret, orgVerifyOptions);
392
431
  if (!payload)
393
432
  return { payload: { email: null, orgDomain: null } };
394
433
  const claims = organizationPrincipalClaims(payload, organization);
395
434
  if (!claims)
396
435
  return { payload: { email: null, orgDomain: null } };
436
+ if (payload.typ === A2A_ORG_MEMBER_DELEGATION_TYPE) {
437
+ if (!validOrgMemberDelegationClaims(payload)) {
438
+ return { payload: { email: null, orgDomain: null } };
439
+ }
440
+ if (organization.secret.trim() === globalSecret) {
441
+ return { payload: { email: null, orgDomain: null } };
442
+ }
443
+ let isCurrentMember = false;
444
+ if (organization.a2aMemberDelegationEnabled) {
445
+ try {
446
+ const { isOrgMemberForA2A } = await import("../org/membership.js");
447
+ isCurrentMember = await isOrgMemberForA2A(organization.orgId, String(payload.sub).trim(), event);
448
+ }
449
+ catch (cause) {
450
+ throw new A2AIdentityVerificationUnavailableError(cause);
451
+ }
452
+ }
453
+ if (isCurrentMember) {
454
+ return {
455
+ payload: identityPayload(payload, audienceOptions, organization, "org-member"),
456
+ verifiedOrgId: organization.orgId,
457
+ };
458
+ }
459
+ return {
460
+ payload: {
461
+ email: null,
462
+ orgDomain: organization.orgDomain,
463
+ orgId: organization.orgId,
464
+ identityAssurance: "organization",
465
+ ...(audienceOptions?.includeClaims ? { claims } : {}),
466
+ },
467
+ verifiedOrgId: organization.orgId,
468
+ };
469
+ }
397
470
  warnUserAssertionDemotedOnce(organization.orgId, payload, "user token verified only with org secret");
398
471
  return {
399
472
  payload: {
@@ -742,9 +815,9 @@ export function mountA2A(nitroApp, config, routePrefix = "/_agent-native") {
742
815
  }
743
816
  verifiedCallerEmail = tokenPayload.email;
744
817
  verifiedOrgDomain = tokenPayload.orgDomain;
745
- verifiedIdentityAssurance = tokenPayload.email
746
- ? "user"
747
- : tokenPayload.identityAssurance;
818
+ verifiedIdentityAssurance =
819
+ tokenPayload.identityAssurance ??
820
+ (tokenPayload.email ? "user" : undefined);
748
821
  bearerTokenVerified = verifiedIdentityAssurance !== undefined;
749
822
  verifiedAudienceBound =
750
823
  bearerTokenVerified && tokenHasAudienceClaim(bearerToken);
@@ -3,6 +3,7 @@ export interface VerifiedA2AClaims {
3
3
  orgId: string;
4
4
  orgDomain: string;
5
5
  jti: string;
6
+ identityAssurance?: "user" | "org-member";
6
7
  issuer?: string;
7
8
  scope: string[];
8
9
  }
@@ -36,6 +36,7 @@ export async function verifyA2ATokenWithClaims(token, event) {
36
36
  orgId,
37
37
  orgDomain,
38
38
  jti,
39
+ identityAssurance: identity.identityAssurance === "org-member" ? "org-member" : "user",
39
40
  ...(issuer ? { issuer } : {}),
40
41
  scope: scopes,
41
42
  }
@@ -92,7 +92,7 @@ export default defineAction({
92
92
  return {
93
93
  ...policy,
94
94
  canManage,
95
- affectedMembers: await listPersonalProviderKeyHolders(orgId),
95
+ affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
96
96
  changed,
97
97
  };
98
98
  }
@@ -101,7 +101,9 @@ export default defineAction({
101
101
  ...policy,
102
102
  canManage,
103
103
  ...(canManage
104
- ? { affectedMembers: await listPersonalProviderKeyHolders(orgId) }
104
+ ? {
105
+ affectedMembers: await listPersonalProviderKeyHolders(orgId, email),
106
+ }
105
107
  : {}),
106
108
  };
107
109
  },
@@ -1,4 +1,4 @@
1
- import { getDbExec } from "../db/client.js";
1
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
2
2
  import { getRequestContext, getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
3
3
  import { deleteOrgSetting, deleteUserSetting, getOrgSetting, getUserSetting, putOrgSetting, putUserSetting, } from "../settings/index.js";
4
4
  export const AGENT_APP_MODEL_DEFAULT_KEY_PREFIX = "agent-app-model-default";
@@ -116,15 +116,7 @@ export async function canUpdateAgentAppModelDefaultSettings(userEmail, orgId, or
116
116
  if (!orgId)
117
117
  return orgScope === "personal";
118
118
  try {
119
- const exec = getDbExec();
120
- const { rows } = await exec.execute({
121
- sql: `SELECT role FROM org_members
122
- WHERE org_id = ? AND LOWER(email) = ?
123
- AND federation_removal_pending_at IS NULL
124
- LIMIT 1`,
125
- args: [orgId, userEmail.toLowerCase()],
126
- });
127
- const role = String(rows[0]?.role ?? "");
119
+ const role = await readOrgMemberRole(orgId, userEmail);
128
120
  return role === "owner" || role === "admin";
129
121
  }
130
122
  catch {
@@ -1,4 +1,4 @@
1
- import { getDbExec } from "../db/client.js";
1
+ import { readOrgMemberRole } from "../server/personal-provider-key-policy.js";
2
2
  import { getOrgSetting, putOrgSetting, deleteOrgSetting, getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/index.js";
3
3
  export const AGENT_LOOP_SETTINGS_KEY = "agent-loop";
4
4
  export const DEFAULT_AGENT_MAX_ITERATIONS = 400;
@@ -169,15 +169,7 @@ export async function canUpdateAgentLoopSettings(userEmail, orgId) {
169
169
  if (!orgId)
170
170
  return true;
171
171
  try {
172
- const exec = getDbExec();
173
- const { rows } = await exec.execute({
174
- sql: `SELECT role FROM org_members
175
- WHERE org_id = ? AND LOWER(email) = ?
176
- AND federation_removal_pending_at IS NULL
177
- LIMIT 1`,
178
- args: [orgId, userEmail.toLowerCase()],
179
- });
180
- const role = String(rows[0]?.role ?? "");
172
+ const role = await readOrgMemberRole(orgId, userEmail);
181
173
  return role === "owner" || role === "admin";
182
174
  }
183
175
  catch {
@@ -0,0 +1,8 @@
1
+ import { type ThemeFamily } from "../theme-family.js";
2
+ export interface ThemeFamilyPreference {
3
+ family: ThemeFamily;
4
+ source: "default" | "user-setting";
5
+ appId: string | null;
6
+ }
7
+ declare const _default: import("../../action.js").ActionDefinition<Record<string, never>, ThemeFamilyPreference>;
8
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { defineAction, fail } from "../../action.js";
3
+ import { getUserSetting } from "../../settings/user-settings.js";
4
+ import { DEFAULT_THEME_FAMILY, THEME_FAMILIES, themeFamilySettingKey, } from "../theme-family.js";
5
+ export default defineAction({
6
+ description: "Read the current app's saved theme family, which is independent of light or dark color mode.",
7
+ schema: z.object({}),
8
+ http: { method: "GET" },
9
+ readOnly: true,
10
+ agentTool: false,
11
+ run: async (_args, ctx) => {
12
+ const email = ctx?.userEmail;
13
+ if (!email)
14
+ fail("Not authenticated.", { statusCode: 401 });
15
+ const stored = await getUserSetting(email, themeFamilySettingKey(ctx?.appId));
16
+ if (stored === null) {
17
+ return {
18
+ family: DEFAULT_THEME_FAMILY,
19
+ source: "default",
20
+ appId: ctx?.appId ?? null,
21
+ };
22
+ }
23
+ const parsed = z
24
+ .object({ family: z.enum(THEME_FAMILIES) })
25
+ .safeParse(stored);
26
+ if (!parsed.success) {
27
+ fail("The saved theme family setting is invalid and could not be read.", {
28
+ errorCode: "theme_family_setting_invalid",
29
+ statusCode: 500,
30
+ });
31
+ }
32
+ return {
33
+ family: parsed.data.family,
34
+ source: "user-setting",
35
+ appId: ctx?.appId ?? null,
36
+ };
37
+ },
38
+ });
@@ -0,0 +1,7 @@
1
+ declare const _default: import("../../action.js").ActionDefinition<{
2
+ family: "agent-native" | "original";
3
+ }, {
4
+ family: "agent-native" | "original";
5
+ appId: string;
6
+ }>;
7
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { defineAction, fail } from "../../action.js";
3
+ import { writeAppState } from "../../application-state/script-helpers.js";
4
+ import { putUserSetting } from "../../settings/user-settings.js";
5
+ import { THEME_FAMILIES, themeFamilyAppStateKey, themeFamilySettingKey, } from "../theme-family.js";
6
+ export default defineAction({
7
+ description: "Set the current app's theme family to agent-native or original. This is separate from light or dark color mode.",
8
+ schema: z.object({
9
+ family: z
10
+ .enum(THEME_FAMILIES)
11
+ .describe("Theme family: agent-native or original."),
12
+ }),
13
+ http: { method: "POST" },
14
+ mcpAnnotations: {
15
+ readOnlyHint: false,
16
+ destructiveHint: false,
17
+ openWorldHint: false,
18
+ },
19
+ run: async ({ family }, ctx) => {
20
+ const email = ctx?.userEmail;
21
+ if (!email)
22
+ fail("Not authenticated.", { statusCode: 401 });
23
+ const settingKey = themeFamilySettingKey(ctx?.appId);
24
+ await putUserSetting(email, settingKey, { family });
25
+ try {
26
+ await writeAppState(themeFamilyAppStateKey(ctx?.appId), { family });
27
+ }
28
+ catch {
29
+ fail("The theme family was saved, but the open app could not be synchronized.", { errorCode: "theme_family_sync_failed", statusCode: 503 });
30
+ }
31
+ return { family, appId: ctx?.appId ?? null };
32
+ },
33
+ });
@@ -0,0 +1,13 @@
1
+ export declare const THEME_FAMILIES: readonly ["agent-native", "original"];
2
+ export type ThemeFamily = (typeof THEME_FAMILIES)[number];
3
+ export declare const DEFAULT_THEME_FAMILY: ThemeFamily;
4
+ export declare const THEME_FAMILY_STORAGE_KEY = "theme-family";
5
+ export declare const THEME_FAMILY_ATTRIBUTE = "data-theme-family";
6
+ export declare const THEME_FAMILY_STATUS_ATTRIBUTE = "data-theme-family-status";
7
+ export declare const THEME_FAMILY_APP_STATE_KEY = "theme-family";
8
+ export declare const THEME_FAMILY_SETTING_PREFIX = "theme-family";
9
+ export declare function isThemeFamily(value: unknown): value is ThemeFamily;
10
+ export declare function themeFamilySettingKey(appId?: string): string;
11
+ export declare function themeFamilyStorageKey(appId?: string): string;
12
+ export declare function themeFamilyAppStateKey(appId?: string): string;
13
+ export declare function getThemeFamilyInitScript(appId?: string | null): string;
@@ -0,0 +1,38 @@
1
+ export const THEME_FAMILIES = ["agent-native", "original"];
2
+ export const DEFAULT_THEME_FAMILY = "original";
3
+ export const THEME_FAMILY_STORAGE_KEY = "theme-family";
4
+ export const THEME_FAMILY_ATTRIBUTE = "data-theme-family";
5
+ export const THEME_FAMILY_STATUS_ATTRIBUTE = "data-theme-family-status";
6
+ export const THEME_FAMILY_APP_STATE_KEY = "theme-family";
7
+ export const THEME_FAMILY_SETTING_PREFIX = "theme-family";
8
+ export function isThemeFamily(value) {
9
+ return value === "agent-native" || value === "original";
10
+ }
11
+ export function themeFamilySettingKey(appId) {
12
+ // Older or standalone action contexts can genuinely omit appId. Keep their
13
+ // setting isolated under one stable fallback rather than borrowing another app's choice.
14
+ if (appId === undefined)
15
+ return `${THEME_FAMILY_SETTING_PREFIX}:__no_app_id__`;
16
+ const normalizedAppId = appId.trim().toLowerCase();
17
+ if (!normalizedAppId) {
18
+ throw new TypeError("Theme family settings require a non-empty appId.");
19
+ }
20
+ return `${THEME_FAMILY_SETTING_PREFIX}:${encodeURIComponent(normalizedAppId)}`;
21
+ }
22
+ export function themeFamilyStorageKey(appId) {
23
+ return themeFamilySettingKey(appId);
24
+ }
25
+ export function themeFamilyAppStateKey(appId) {
26
+ if (appId === undefined)
27
+ return `${THEME_FAMILY_APP_STATE_KEY}:__no_app_id__`;
28
+ const normalizedAppId = appId.trim().toLowerCase();
29
+ if (!normalizedAppId) {
30
+ throw new TypeError("Theme family application state requires a non-empty appId.");
31
+ }
32
+ const encodedAppId = Array.from(normalizedAppId, (character) => character.codePointAt(0).toString(16)).join("_");
33
+ return `${THEME_FAMILY_APP_STATE_KEY}:${encodedAppId}`;
34
+ }
35
+ export function getThemeFamilyInitScript(appId) {
36
+ const storageKey = themeFamilyStorageKey(appId ?? undefined);
37
+ return `(function(){var root=document.documentElement;var fallback=${JSON.stringify(DEFAULT_THEME_FAMILY)};try{var value=window.localStorage.getItem(${JSON.stringify(storageKey)});if(value===null){root.setAttribute(${JSON.stringify(THEME_FAMILY_ATTRIBUTE)},fallback);root.setAttribute(${JSON.stringify(THEME_FAMILY_STATUS_ATTRIBUTE)},'awaiting-app-setting');return}if(value==='agent-native'||value==='original'){root.setAttribute(${JSON.stringify(THEME_FAMILY_ATTRIBUTE)},value);root.removeAttribute(${JSON.stringify(THEME_FAMILY_STATUS_ATTRIBUTE)});return}root.setAttribute(${JSON.stringify(THEME_FAMILY_ATTRIBUTE)},fallback);root.setAttribute(${JSON.stringify(THEME_FAMILY_STATUS_ATTRIBUTE)},'invalid')}catch(e){root.setAttribute(${JSON.stringify(THEME_FAMILY_ATTRIBUTE)},fallback);root.setAttribute(${JSON.stringify(THEME_FAMILY_STATUS_ATTRIBUTE)},'unreadable')}})();`;
38
+ }
@@ -26,6 +26,7 @@ const CSV_COLUMNS = [
26
26
  ["reportedClientVersion", "reported_client_version"],
27
27
  ["oauthClientId", "oauth_client_id"],
28
28
  ["approval", "approval"],
29
+ ["identityAssurance", "identity_assurance"],
29
30
  ];
30
31
  function csvField(value) {
31
32
  if (value === null || value === undefined)
@@ -1,4 +1,4 @@
1
- import type { AuditApproval } from "./types.js";
1
+ import type { AuditApproval, AuditIdentityAssurance } from "./types.js";
2
2
  declare const auditAttributionBrand: unique symbol;
3
3
  interface AuditAttributionFields {
4
4
  caller: string;
@@ -16,11 +16,12 @@ interface AuditAttributionFields {
16
16
  };
17
17
  mcpClientAccepted?: true;
18
18
  approval?: AuditApproval;
19
+ identityAssurance?: AuditIdentityAssurance;
19
20
  }
20
21
  /**
21
22
  * Where an audited call came from and what vouched for it: its source, the
22
- * MCP client it reported, the OAuth client of its credential, the approval
23
- * signal, and its run and network ids. `recordActionAudit` requires one and
23
+ * MCP client it reported, the OAuth client of its credential, verified A2A
24
+ * identity assurance, the approval signal, and its run and network ids. `recordActionAudit` requires one and
24
25
  * only {@link auditAttribution} makes one, so a recorder cannot build its own
25
26
  * context and store nulls for a field it forgot.
26
27
  */
@@ -38,7 +39,7 @@ export type AuditOverride = Partial<Pick<AuditAttributionFields, "threadId" | "t
38
39
  * The run context attribution reads from; an `ActionRunContext` is one.
39
40
  * Approval is not read from it: a context can be handed to another action.
40
41
  */
41
- export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted">>;
42
+ export type AuditAttributionSource = Partial<Omit<AuditAttributionFields, "approval" | "approvedToolCallKey" | "mcpClientAccepted" | "identityAssurance">>;
42
43
  /** A recorder was given no attribution, or one `auditAttribution` did not make. */
43
44
  export declare class AuditAttributionError extends Error {
44
45
  constructor(recorder: string);
@@ -1,5 +1,5 @@
1
1
  import { currentInvocationEvidence } from "../action-frame.js";
2
- import { getVerifiedMcpCredentialFromEvent } from "../server/request-context.js";
2
+ import { getRequestContext, getVerifiedMcpCredentialFromEvent, } from "../server/request-context.js";
3
3
  // Registered, so an attribution made by another loaded copy of this module
4
4
  // still passes the check.
5
5
  const auditAttributionBrand = Symbol.for("@agent-native/core/audit-attribution");
@@ -40,6 +40,7 @@ export function auditAttribution(ctx) {
40
40
  mcpClient: ctx?.mcpClient,
41
41
  mcpClientAccepted: evidence?.mcpClientAccepted,
42
42
  approval: override?.approval,
43
+ identityAssurance: getRequestContext()?.a2aIdentityAssurance,
43
44
  };
44
45
  return { ...fields, [auditAttributionBrand]: true };
45
46
  }
@@ -1,4 +1,4 @@
1
- export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
1
+ export type { ActionAuditConfig, AuditActorKind, AuditApproval, AuditCallMeta, AuditEvent, AuditIdentityAssurance, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
2
2
  export { AGENT_AUDIT_CALLERS, deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
3
3
  export { redactArgsToJson } from "./redact.js";
4
4
  export { ensureAuditTables, insertAuditEvent, queryAuditEvents, queryAuditEventPage, queryAuditApps, getAuditEventById, deleteOldAuditEvents, type AuditEventPage, type AuditReadScope, type AuditTrail, } from "./store.js";