@standardagents/builder 0.41.15 → 0.41.17

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.
@@ -1190,6 +1190,8 @@ interface AuthUser {
1190
1190
  * no explicit set — see {@link hasPermission} for the fallback rules.
1191
1191
  */
1192
1192
  permissions?: string[] | null;
1193
+ /** Application roles, independent of built-in instance permissions. */
1194
+ roles?: string[];
1193
1195
  platform_user_id?: string | null;
1194
1196
  email?: string | null;
1195
1197
  display_name?: string | null;
@@ -1609,6 +1611,7 @@ interface AuthenticationUser {
1609
1611
  password_hash: string;
1610
1612
  role: 'admin' | 'user' | 'api';
1611
1613
  permissions?: string | null;
1614
+ roles?: string[];
1612
1615
  platform_user_id?: string | null;
1613
1616
  email?: string | null;
1614
1617
  display_name?: string | null;
@@ -1864,6 +1867,17 @@ interface DurableAgentBuilderRpc {
1864
1867
  user_id: string;
1865
1868
  id: string;
1866
1869
  } | null>;
1870
+ createApiKey(params: {
1871
+ name: string;
1872
+ key_hash: string;
1873
+ key_prefix: string;
1874
+ last_five: string;
1875
+ user_id: string;
1876
+ }): Promise<string>;
1877
+ getUserByPlatformUserId(platformUserId: string): Promise<{
1878
+ id: string;
1879
+ username: string;
1880
+ } | null>;
1867
1881
  consumeAuthRateLimit(params: {
1868
1882
  key: string;
1869
1883
  limit: number;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { M as ModelDefinition, A as AgentDefinition } from './resolver-DE5HFRlb.js';
2
- export { m as AgentBuilderEnv, n as AgentBuilderEvent, ah as BroadcastOptions, C as CodeExecutionBridge, h as DurableAgentBuilder, D as DurableThread, l as DurableUserHub, af as FlowStateSdk, ai as FlowStateWithSdk, G as HandlerSide, H as HubClientInfo, E as HumanSide, aq as ImageContextConfig, ae as InjectMessageOptions, as as LLMProviderInterface, aL as NamespaceResolutionError, w as PromptContent, s as PromptDefinition, y as PromptIncludePart, x as PromptPart, F as PromptSide, t as PromptToolConfig, P as Provider, at as ProviderContentPart, aw as ProviderFilePart, av as ProviderImagePart, ar as ProviderRegistry, au as ProviderTextPart, B as SessionToolBinding, z as SessionToolConfig, I as SideConfig, J as SubagentRelationship, u as SubagentToolConfig, S as SubpromptConfig, o as ThreadEventPayload, p as ThreadRegistryEntry, T as ThreadWatchdog, j as ThreadWatchdogState, v as ToolConfig, U as UpdateThreadParams, q as User, k as WatchdogCheckin, W as WatchdogProbeResult, f as applyRuntimeCors, am as buildImageDescription, c as canAccessThread, a9 as cat, aJ as createNamespaceContext, Q as emitThreadEvent, ag as enhanceFlowState, a0 as exists, ad as find, R as forceTurn, ap as generateImageDescription, a7 as getFileStats, N as getMessages, al as getMessagesToSummarize, az as getShortName, a8 as getThumbnail, ak as getUnsummarizedImageAttachments, aG as getVisibleAgentNames, aI as getVisibleModelNames, aH as getVisiblePromptNames, aF as getVisibleToolNames, ac as grep, aj as hasImageAttachments, aa as head, L as injectMessage, d as isAdminOnlyApiRoute, i as isHostedInstance, b as isPublicApiRoute, aK as isQualifiedName, e as isSameOriginBrowserRequest, a as isTrustedLocalDevelopmentRequest, Z as linkFile, a2 as mkdir, a5 as move, ao as optimizeImageContext, ax as parseQualifiedName, ay as qualifyName, K as queueTool, _ as readFile, a3 as readdir, O as reloadHistory, a6 as rename, an as replaceImagesWithDescriptions, aD as resolveAgent, aE as resolveHook, aC as resolveModel, aB as resolvePrompt, aA as resolveTool, a4 as rmdir, r as runtimeCorsHeaders, g as runtimePreflightResponse, $ as stat, ab as tail, a1 as unlink, V as updateThread, X as writeFile, Y as writeImage } from './resolver-DE5HFRlb.js';
1
+ import { M as ModelDefinition, A as AgentDefinition } from './resolver-eygn_O1w.js';
2
+ export { m as AgentBuilderEnv, n as AgentBuilderEvent, ah as BroadcastOptions, C as CodeExecutionBridge, h as DurableAgentBuilder, D as DurableThread, l as DurableUserHub, af as FlowStateSdk, ai as FlowStateWithSdk, G as HandlerSide, H as HubClientInfo, E as HumanSide, aq as ImageContextConfig, ae as InjectMessageOptions, as as LLMProviderInterface, aL as NamespaceResolutionError, w as PromptContent, s as PromptDefinition, y as PromptIncludePart, x as PromptPart, F as PromptSide, t as PromptToolConfig, P as Provider, at as ProviderContentPart, aw as ProviderFilePart, av as ProviderImagePart, ar as ProviderRegistry, au as ProviderTextPart, B as SessionToolBinding, z as SessionToolConfig, I as SideConfig, J as SubagentRelationship, u as SubagentToolConfig, S as SubpromptConfig, o as ThreadEventPayload, p as ThreadRegistryEntry, T as ThreadWatchdog, j as ThreadWatchdogState, v as ToolConfig, U as UpdateThreadParams, q as User, k as WatchdogCheckin, W as WatchdogProbeResult, f as applyRuntimeCors, am as buildImageDescription, c as canAccessThread, a9 as cat, aJ as createNamespaceContext, Q as emitThreadEvent, ag as enhanceFlowState, a0 as exists, ad as find, R as forceTurn, ap as generateImageDescription, a7 as getFileStats, N as getMessages, al as getMessagesToSummarize, az as getShortName, a8 as getThumbnail, ak as getUnsummarizedImageAttachments, aG as getVisibleAgentNames, aI as getVisibleModelNames, aH as getVisiblePromptNames, aF as getVisibleToolNames, ac as grep, aj as hasImageAttachments, aa as head, L as injectMessage, d as isAdminOnlyApiRoute, i as isHostedInstance, b as isPublicApiRoute, aK as isQualifiedName, e as isSameOriginBrowserRequest, a as isTrustedLocalDevelopmentRequest, Z as linkFile, a2 as mkdir, a5 as move, ao as optimizeImageContext, ax as parseQualifiedName, ay as qualifyName, K as queueTool, _ as readFile, a3 as readdir, O as reloadHistory, a6 as rename, an as replaceImagesWithDescriptions, aD as resolveAgent, aE as resolveHook, aC as resolveModel, aB as resolvePrompt, aA as resolveTool, a4 as rmdir, r as runtimeCorsHeaders, g as runtimePreflightResponse, $ as stat, ab as tail, a1 as unlink, V as updateThread, X as writeFile, Y as writeImage } from './resolver-eygn_O1w.js';
3
3
  export { AgentPluginOptions, agentbuilder, builder } from './plugin.js';
4
- export { v as Agent, J as AttachmentRef, i as AuthContext, A as AuthUser, e as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, p as ClientStreamRequest, q as ClientStreamResponse, n as ClientToolRequest, o as ClientToolResponse, B as Controller, a as ControllerContext, G as Env, H as FileRecord, N as FileStats, D as FlowResult, F as FlowState, K as GrepResult, P as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, U as MessageContent, Q as MultimodalContent, R as RequestContext, S as StorageBackend, E as TelemetryEvent, O as TextContentPart, T as ThreadEndpointContext, b as ThreadEnv, w as ThreadInstance, x as ThreadMetadata, y as ToolCall, z as ToolResult, f as authenticate, j as callClient, k as callClientStream, c as createThreadEndpointHandler, m as decodeStreamChunk, d as defineController, l as encodeStreamChunk, h as endpointCallerFromAuth, g as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-ChHAVogf.js';
4
+ export { v as Agent, J as AttachmentRef, i as AuthContext, A as AuthUser, e as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, p as ClientStreamRequest, q as ClientStreamResponse, n as ClientToolRequest, o as ClientToolResponse, B as Controller, a as ControllerContext, G as Env, H as FileRecord, N as FileStats, D as FlowResult, F as FlowState, K as GrepResult, P as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, U as MessageContent, Q as MultimodalContent, R as RequestContext, S as StorageBackend, E as TelemetryEvent, O as TextContentPart, T as ThreadEndpointContext, b as ThreadEnv, w as ThreadInstance, x as ThreadMetadata, y as ToolCall, z as ToolResult, f as authenticate, j as callClient, k as callClientStream, c as createThreadEndpointHandler, m as decodeStreamChunk, d as defineController, l as encodeStreamChunk, h as endpointCallerFromAuth, g as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-C82dLGPq.js';
5
5
  export { AgentType, DefineHandlerOptions, DefinitionLoader, GlobalNamespaceContext, HANDLER_SYMBOL, HandlerDefinition, HookExecutionContext, HookExecutionSource, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, ParentHookRequirement, ParentHookStage, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderConfigSlot, ProviderConfigSlots, ProviderConfigValueSource, ProviderDefinition, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ProviderMethodOverrides, ModelCapabilities as ProviderModelCapabilities, ProviderOverride, ProviderOverrideContext, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSyncOverride, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, ThreadHookName, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHandler, defineHook, defineModel, definePrompt, defineProvider, defineTool, isHandler, isPacked, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue } from '@standardagents/spec';
6
6
  export { C as ConstituentItem, D as DiscoveredPackage, N as NamespacedRegistry, P as PackingAnalysis, a as PackingOptions, b as PackingResult, U as UnpackOptions, c as UnpackResult } from './types-Dhd0M369.js';
7
7
  export { D as DiscoveryConfig, P as PackageDiscoveryService, d as discoverPackages } from './discovery-D5pmuazY.js';
package/dist/index.js CHANGED
@@ -10329,6 +10329,24 @@ var init_platform_handoff_jtis = __esm({
10329
10329
  }
10330
10330
  });
10331
10331
 
10332
+ // src/durable-objects/agentbuilder-migrations/0022_application_roles.ts
10333
+ var migration60;
10334
+ var init_application_roles = __esm({
10335
+ "src/durable-objects/agentbuilder-migrations/0022_application_roles.ts"() {
10336
+ migration60 = {
10337
+ version: 22,
10338
+ async up(sql) {
10339
+ sql.exec(`CREATE TABLE platform_application_roles (
10340
+ platform_user_id TEXT PRIMARY KEY,
10341
+ roles TEXT NOT NULL DEFAULT '[]',
10342
+ roles_version INTEGER NOT NULL DEFAULT 0
10343
+ )`);
10344
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '22')`);
10345
+ }
10346
+ };
10347
+ }
10348
+ });
10349
+
10332
10350
  // src/durable-objects/agentbuilder-migrations/index.ts
10333
10351
  var migrations2, LATEST_SCHEMA_VERSION2;
10334
10352
  var init_agentbuilder_migrations = __esm({
@@ -10354,9 +10372,10 @@ var init_agentbuilder_migrations = __esm({
10354
10372
  init_scope_skills();
10355
10373
  init_websocket_tickets();
10356
10374
  init_platform_handoff_jtis();
10357
- migrations2 = [migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57, migration58, migration59];
10375
+ init_application_roles();
10376
+ migrations2 = [migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57, migration58, migration59, migration60];
10358
10377
  LATEST_SCHEMA_VERSION2 = migrations2.reduce(
10359
- (max, migration60) => Math.max(max, migration60.version),
10378
+ (max, migration61) => Math.max(max, migration61.version),
10360
10379
  0
10361
10380
  );
10362
10381
  }
@@ -11165,9 +11184,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
11165
11184
  }
11166
11185
  }
11167
11186
  async runMigrations(fromVersion) {
11168
- for (const migration60 of migrations2) {
11169
- if (migration60.version > fromVersion) {
11170
- await migration60.up(this.ctx.storage.sql);
11187
+ for (const migration61 of migrations2) {
11188
+ if (migration61.version > fromVersion) {
11189
+ await migration61.up(this.ctx.storage.sql);
11171
11190
  }
11172
11191
  }
11173
11192
  }
@@ -12722,6 +12741,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12722
12741
  // admin meaning.
12723
12742
  role: row.role === "user" ? "user" : row.role === "api" ? "api" : "admin",
12724
12743
  permissions: parseStoredPermissions(row.permissions),
12744
+ roles: parseStoredPermissions(row.roles) ?? [],
12725
12745
  platform_user_id: row.platform_user_id ?? null,
12726
12746
  email: row.email ?? null,
12727
12747
  display_name: row.display_name ?? null,
@@ -12739,7 +12759,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12739
12759
  await this.ensureMigrated();
12740
12760
  const cursor = await this.ctx.storage.sql.exec(
12741
12761
  `SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
12742
- permissions, platform_user_id, email, display_name, avatar_url, source, replica_active,
12762
+ permissions, (SELECT roles FROM platform_application_roles WHERE platform_user_id = users.platform_user_id) AS roles,
12763
+ platform_user_id, email, display_name, avatar_url, source, replica_active,
12743
12764
  created_at, updated_at
12744
12765
  FROM users WHERE username = ? AND deleted_at IS NULL AND replica_active != 0`,
12745
12766
  username
@@ -12755,7 +12776,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12755
12776
  await this.ensureMigrated();
12756
12777
  const cursor = await this.ctx.storage.sql.exec(
12757
12778
  `SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
12758
- permissions, platform_user_id, email, display_name, avatar_url, source, replica_active,
12779
+ permissions, (SELECT roles FROM platform_application_roles WHERE platform_user_id = users.platform_user_id) AS roles,
12780
+ platform_user_id, email, display_name, avatar_url, source, replica_active,
12759
12781
  created_at, updated_at
12760
12782
  FROM users WHERE id = ? AND deleted_at IS NULL AND replica_active != 0`,
12761
12783
  id
@@ -12799,6 +12821,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12799
12821
  password_hash: params.password_hash,
12800
12822
  role: params.role || "admin",
12801
12823
  permissions: params.permissions ?? null,
12824
+ roles: [],
12802
12825
  platform_user_id: params.platform_user_id ?? null,
12803
12826
  email: params.email ?? null,
12804
12827
  display_name: params.display_name ?? null,
@@ -12932,11 +12955,31 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12932
12955
  );
12933
12956
  return true;
12934
12957
  }
12935
- /**
12936
- * Upsert a platform-replicated identity and return the local user row.
12937
- */
12958
+ /** Apply an authoritative role replacement while retaining revocation fences. */
12959
+ applyApplicationRoles(platformUserId, roles, version, revoke = false) {
12960
+ const revision = version === void 0 ? 0 : version;
12961
+ if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) {
12962
+ throw new Error("Invalid application roles revision");
12963
+ }
12964
+ if (roles !== void 0 && (!Array.isArray(roles) || !roles.every((role) => typeof role === "string"))) {
12965
+ throw new Error("Invalid application roles");
12966
+ }
12967
+ this.ctx.storage.sql.exec(
12968
+ `INSERT INTO platform_application_roles (platform_user_id, roles, roles_version)
12969
+ VALUES (?, ?, ?)
12970
+ ON CONFLICT(platform_user_id) DO UPDATE SET roles = excluded.roles, roles_version = excluded.roles_version
12971
+ WHERE excluded.roles_version > platform_application_roles.roles_version
12972
+ OR (? = 1 AND excluded.roles_version = platform_application_roles.roles_version)`,
12973
+ platformUserId,
12974
+ JSON.stringify(roles ?? []),
12975
+ revision,
12976
+ revoke ? 1 : 0
12977
+ );
12978
+ }
12979
+ /** Upsert a platform-replicated identity and return the local user row. */
12938
12980
  async upsertPlatformReplicaUser(replica) {
12939
12981
  await this.ensureMigrated();
12982
+ this.applyApplicationRoles(replica.platform_user_id, replica.roles, replica.roles_version);
12940
12983
  const now = Math.floor(Date.now() / 1e3);
12941
12984
  const fallback = this.normalizeReplicaUsername(
12942
12985
  replica.email?.split("@")[0] ?? null,
@@ -12983,7 +13026,7 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12983
13026
  }
12984
13027
  return updated;
12985
13028
  }
12986
- return this.createUser({
13029
+ const created = await this.createUser({
12987
13030
  username: availableUsername,
12988
13031
  password_hash: `platform-replica:${crypto.randomUUID()}`,
12989
13032
  role: replica.role,
@@ -12994,12 +13037,18 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
12994
13037
  avatar_url: replica.avatar_url ?? null,
12995
13038
  source: "standard_agents"
12996
13039
  });
13040
+ const user = await this.getUserById(created.id);
13041
+ if (!user) throw new Error("Failed to load replicated user after creation");
13042
+ return user;
12997
13043
  }
12998
13044
  /**
12999
13045
  * Apply a full platform read-replica snapshot for this instance.
13000
13046
  */
13001
13047
  async applyPlatformReplicaSnapshot(snapshot) {
13002
13048
  await this.ensureMigrated();
13049
+ for (const revocation of snapshot.role_revocations ?? []) {
13050
+ this.applyApplicationRoles(revocation.platform_user_id, [], revocation.roles_version, true);
13051
+ }
13003
13052
  const activeUserIds = /* @__PURE__ */ new Set();
13004
13053
  for (const replicaUser of snapshot.users) {
13005
13054
  const user = await this.upsertPlatformReplicaUser(replicaUser);
@@ -13052,10 +13101,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
13052
13101
  const keys = snapshot.api_keys ?? [];
13053
13102
  for (const key of keys) {
13054
13103
  const user = key.user_platform_id ? await this.getUserByPlatformUserId(key.user_platform_id) : await this.getFirstReplicaAdminUser();
13055
- const keyUser = user ?? await this.getFirstReplicaAdminUser();
13056
- if (!keyUser) continue;
13104
+ if (!user) continue;
13057
13105
  activePlatformKeyIds.add(key.id);
13058
- await this.upsertPlatformReplicaApiKey(key, keyUser.id);
13106
+ await this.upsertPlatformReplicaApiKey(key, user.id);
13059
13107
  }
13060
13108
  const now = Math.floor(Date.now() / 1e3);
13061
13109
  if (activePlatformKeyIds.size > 0) {
@@ -17667,9 +17715,9 @@ var init_DurableThread = __esm({
17667
17715
  * Each migration is run in order, starting from the current version + 1.
17668
17716
  */
17669
17717
  async runMigrations(fromVersion) {
17670
- for (const migration60 of migrations) {
17671
- if (migration60.version > fromVersion) {
17672
- await migration60.up(this.ctx.storage.sql);
17718
+ for (const migration61 of migrations) {
17719
+ if (migration61.version > fromVersion) {
17720
+ await migration61.up(this.ctx.storage.sql);
17673
17721
  }
17674
17722
  }
17675
17723
  }
@@ -29547,6 +29595,7 @@ var init_ThreadStateImpl = __esm({
29547
29595
  username: user.username,
29548
29596
  role: user.role,
29549
29597
  permissions: user.permissions ?? null,
29598
+ roles: user.roles ?? [],
29550
29599
  email: user.email ?? null,
29551
29600
  display_name: user.display_name ?? null,
29552
29601
  avatar_url: user.avatar_url ?? null
@@ -30241,14 +30290,7 @@ declare module 'virtual:@standardagents/builder' {
30241
30290
  terminated?: number | null;
30242
30291
  }
30243
30292
 
30244
- export interface User {
30245
- id: string;
30246
- username: string;
30247
- password_hash: string;
30248
- role: 'admin';
30249
- created_at: number;
30250
- updated_at: number;
30251
- }
30293
+ export type User = NonNullable<Awaited<ReturnType<BaseDurableAgentBuilder['getUserById']>>>;
30252
30294
 
30253
30295
  export interface Provider {
30254
30296
  name: string;
@@ -36755,6 +36797,7 @@ var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
36755
36797
  "/api/auth/bootstrap",
36756
36798
  "/api/auth/login",
36757
36799
  "/api/auth/config",
36800
+ "/api/auth/exchange",
36758
36801
  "/api/auth/brolly-control",
36759
36802
  "/api/auth/platform-cleanup",
36760
36803
  "/api/auth/platform-replica",
@@ -38027,7 +38070,8 @@ async function loadVirtualModule(id, context) {
38027
38070
  installedProviders,
38028
38071
  rou3Code,
38029
38072
  hmr,
38030
- deviceApproval
38073
+ deviceApproval,
38074
+ keyExchange
38031
38075
  } = context;
38032
38076
  if (id === RESOLVED_VIRTUAL_TOOLS_ID) {
38033
38077
  const tools = await scanToolsDirectory(toolsDir);
@@ -38092,6 +38136,7 @@ export const config = {
38092
38136
  promptsDir: "${relativePromptsDir}",
38093
38137
  agentsDir: "${relativeAgentsDir}",
38094
38138
  deviceApproval: ${JSON.stringify(deviceApproval ?? null)},
38139
+ keyExchange: ${JSON.stringify(keyExchange ?? null)},
38095
38140
  };`;
38096
38141
  }
38097
38142
  if (id === RESOLVED_VIRTUAL_MODELS_ID) {
@@ -38500,6 +38545,35 @@ function applyPluginEnvironmentConfig(config, options = {}) {
38500
38545
  }
38501
38546
  }
38502
38547
 
38548
+ // src/utils/key-exchange.ts
38549
+ var HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
38550
+ function validHost(host) {
38551
+ return host.length <= 253 && host.split(".").length >= 2 && host.split(".").every((label) => HOST_LABEL.test(label));
38552
+ }
38553
+ function normalizeTrustedOrigin(value) {
38554
+ if (typeof value !== "string") {
38555
+ throw new Error("keyExchange.trustedOrigins entries must be strings");
38556
+ }
38557
+ const entry = value.trim().toLowerCase();
38558
+ const host = entry.startsWith("https://") ? entry.slice("https://".length) : "";
38559
+ const wildcard = host.startsWith("*.");
38560
+ if (!validHost(wildcard ? host.slice(2) : host)) {
38561
+ throw new Error(`keyExchange.trustedOrigins entry ${JSON.stringify(value)} must be https://host or https://*.suffix`);
38562
+ }
38563
+ return entry;
38564
+ }
38565
+ function normalizeKeyExchange(value) {
38566
+ if (value === void 0) return void 0;
38567
+ if (!value || typeof value !== "object" || !Array.isArray(value.trustedOrigins)) {
38568
+ throw new Error("keyExchange must be an object with a trustedOrigins array");
38569
+ }
38570
+ const trustedOrigins = [...new Set(value.trustedOrigins.map(normalizeTrustedOrigin))];
38571
+ if (trustedOrigins.length === 0) {
38572
+ throw new Error("keyExchange.trustedOrigins must name at least one origin");
38573
+ }
38574
+ return { trustedOrigins };
38575
+ }
38576
+
38503
38577
  // src/plugin/device-approval.ts
38504
38578
  function requiredText(value, field, maxLength) {
38505
38579
  if (typeof value !== "string" || !value.trim() || value.trim().length > maxLength) {
@@ -38544,6 +38618,7 @@ function normalizeDeviceApprovalBrand(value) {
38544
38618
  // src/plugin.ts
38545
38619
  function agentbuilder(options = {}) {
38546
38620
  const deviceApproval = normalizeDeviceApprovalBrand(options.deviceApproval);
38621
+ const keyExchange = normalizeKeyExchange(options.keyExchange);
38547
38622
  const trustedDevHosts = normalizeTrustedDevHosts(options.trustedDevHosts);
38548
38623
  const toolsDir = options.toolsDir ? path9__default.resolve(process.cwd(), options.toolsDir) : path9__default.resolve(process.cwd(), "agents/tools");
38549
38624
  const hooksDir = options.hooksDir ? path9__default.resolve(process.cwd(), options.hooksDir) : path9__default.resolve(process.cwd(), "agents/hooks");
@@ -38640,7 +38715,8 @@ export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog
38640
38715
  installedProviders,
38641
38716
  rou3Code,
38642
38717
  hmr,
38643
- deviceApproval
38718
+ deviceApproval,
38719
+ keyExchange
38644
38720
  };
38645
38721
  const devMiddlewareContext = {
38646
38722
  toolsDir,
@@ -38877,12 +38953,13 @@ function runtimePreflightResponse(request, env) {
38877
38953
  // src/middleware/auth.ts
38878
38954
  function endpointCallerFromAuth(auth) {
38879
38955
  if (!auth) return null;
38880
- const { id, username, role, permissions, email, display_name, avatar_url } = auth.user;
38956
+ const { id, username, role, permissions, roles, email, display_name, avatar_url } = auth.user;
38881
38957
  return {
38882
38958
  id,
38883
38959
  username,
38884
38960
  role,
38885
38961
  permissions: permissions ?? null,
38962
+ roles: roles ?? [],
38886
38963
  email: email ?? null,
38887
38964
  display_name: display_name ?? null,
38888
38965
  avatar_url: avatar_url ?? null
@@ -38928,6 +39005,8 @@ function parseWebSocketTicketAuth(authJson) {
38928
39005
  if (typeof user.id !== "string" || typeof user.username !== "string" || typeof user.role !== "string" || record.authType !== "super_admin" && record.authType !== "session" && record.authType !== "api_key") {
38929
39006
  return null;
38930
39007
  }
39008
+ const roles = user.roles;
39009
+ if (roles !== void 0 && (!Array.isArray(roles) || !roles.every((role) => typeof role === "string"))) return null;
38931
39010
  const permissions = user.permissions;
38932
39011
  if (permissions !== void 0 && permissions !== null && (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string"))) {
38933
39012
  return null;
@@ -38939,6 +39018,7 @@ function parseWebSocketTicketAuth(authJson) {
38939
39018
  username: user.username,
38940
39019
  role: user.role,
38941
39020
  permissions,
39021
+ roles: Array.isArray(roles) ? roles : [],
38942
39022
  platform_user_id: typeof user.platform_user_id === "string" ? user.platform_user_id : null,
38943
39023
  email: typeof user.email === "string" ? user.email : null,
38944
39024
  display_name: typeof user.display_name === "string" ? user.display_name : null,
@@ -38962,7 +39042,19 @@ async function consumeWebSocketTicket(request, env) {
38962
39042
  ticket_hash: await hashToken(ticket),
38963
39043
  pathname: url.pathname
38964
39044
  });
38965
- return typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
39045
+ const context = typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
39046
+ if (!context || context.authType === "super_admin") return context;
39047
+ const user = await getAgentBuilder(env).getUserById(context.user.id);
39048
+ if (!user) return null;
39049
+ return {
39050
+ ...context,
39051
+ user: {
39052
+ ...context.user,
39053
+ role: user.role,
39054
+ permissions: parsePermissions(user.permissions),
39055
+ roles: user.roles ?? []
39056
+ }
39057
+ };
38966
39058
  }
38967
39059
  async function authenticate(request, env) {
38968
39060
  const ticketAuth = await consumeWebSocketTicket(request, env);
@@ -38985,7 +39077,8 @@ async function authenticate(request, env) {
38985
39077
  user: {
38986
39078
  id: "super_admin",
38987
39079
  username: "admin",
38988
- role: "admin"
39080
+ role: "admin",
39081
+ roles: []
38989
39082
  },
38990
39083
  authType: "super_admin"
38991
39084
  };
@@ -39006,6 +39099,7 @@ async function authenticate(request, env) {
39006
39099
  username: user.username,
39007
39100
  role: user.role,
39008
39101
  permissions: parsePermissions(user.permissions),
39102
+ roles: user.roles ?? [],
39009
39103
  platform_user_id: user.platform_user_id ?? null,
39010
39104
  email: user.email ?? null,
39011
39105
  display_name: user.display_name ?? null,
@@ -39029,6 +39123,7 @@ async function authenticate(request, env) {
39029
39123
  username: user.username,
39030
39124
  role: user.role,
39031
39125
  permissions: parsePermissions(user.permissions),
39126
+ roles: user.roles ?? [],
39032
39127
  platform_user_id: user.platform_user_id ?? null,
39033
39128
  email: user.email ?? null,
39034
39129
  display_name: user.display_name ?? null,
@@ -39432,6 +39527,7 @@ var UserStateImpl = class {
39432
39527
  username: user.username,
39433
39528
  role: user.role,
39434
39529
  permissions: user.permissions ?? null,
39530
+ roles: user.roles ?? [],
39435
39531
  email: user.email ?? null,
39436
39532
  display_name: user.display_name ?? null,
39437
39533
  avatar_url: user.avatar_url ?? null