@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.
@@ -9948,6 +9948,24 @@ var init_platform_handoff_jtis = __esm({
9948
9948
  }
9949
9949
  });
9950
9950
 
9951
+ // src/durable-objects/agentbuilder-migrations/0022_application_roles.ts
9952
+ var migration60;
9953
+ var init_application_roles = __esm({
9954
+ "src/durable-objects/agentbuilder-migrations/0022_application_roles.ts"() {
9955
+ migration60 = {
9956
+ version: 22,
9957
+ async up(sql) {
9958
+ sql.exec(`CREATE TABLE platform_application_roles (
9959
+ platform_user_id TEXT PRIMARY KEY,
9960
+ roles TEXT NOT NULL DEFAULT '[]',
9961
+ roles_version INTEGER NOT NULL DEFAULT 0
9962
+ )`);
9963
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '22')`);
9964
+ }
9965
+ };
9966
+ }
9967
+ });
9968
+
9951
9969
  // src/durable-objects/agentbuilder-migrations/index.ts
9952
9970
  var migrations2;
9953
9971
  var init_agentbuilder_migrations = __esm({
@@ -9973,9 +9991,10 @@ var init_agentbuilder_migrations = __esm({
9973
9991
  init_scope_skills();
9974
9992
  init_websocket_tickets();
9975
9993
  init_platform_handoff_jtis();
9976
- migrations2 = [migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57, migration58, migration59];
9994
+ init_application_roles();
9995
+ migrations2 = [migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57, migration58, migration59, migration60];
9977
9996
  migrations2.reduce(
9978
- (max, migration60) => Math.max(max, migration60.version),
9997
+ (max, migration61) => Math.max(max, migration61.version),
9979
9998
  0
9980
9999
  );
9981
10000
  }
@@ -13781,9 +13800,9 @@ var init_DurableThread = __esm({
13781
13800
  * Each migration is run in order, starting from the current version + 1.
13782
13801
  */
13783
13802
  async runMigrations(fromVersion) {
13784
- for (const migration60 of migrations) {
13785
- if (migration60.version > fromVersion) {
13786
- await migration60.up(this.ctx.storage.sql);
13803
+ for (const migration61 of migrations) {
13804
+ if (migration61.version > fromVersion) {
13805
+ await migration61.up(this.ctx.storage.sql);
13787
13806
  }
13788
13807
  }
13789
13808
  }
@@ -25378,6 +25397,7 @@ var init_ThreadStateImpl = __esm({
25378
25397
  username: user.username,
25379
25398
  role: user.role,
25380
25399
  permissions: user.permissions ?? null,
25400
+ roles: user.roles ?? [],
25381
25401
  email: user.email ?? null,
25382
25402
  display_name: user.display_name ?? null,
25383
25403
  avatar_url: user.avatar_url ?? null
@@ -78945,6 +78965,8 @@ function parseWebSocketTicketAuth(authJson) {
78945
78965
  if (typeof user.id !== "string" || typeof user.username !== "string" || typeof user.role !== "string" || record24.authType !== "super_admin" && record24.authType !== "session" && record24.authType !== "api_key") {
78946
78966
  return null;
78947
78967
  }
78968
+ const roles = user.roles;
78969
+ if (roles !== void 0 && (!Array.isArray(roles) || !roles.every((role) => typeof role === "string"))) return null;
78948
78970
  const permissions = user.permissions;
78949
78971
  if (permissions !== void 0 && permissions !== null && (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string"))) {
78950
78972
  return null;
@@ -78956,6 +78978,7 @@ function parseWebSocketTicketAuth(authJson) {
78956
78978
  username: user.username,
78957
78979
  role: user.role,
78958
78980
  permissions,
78981
+ roles: Array.isArray(roles) ? roles : [],
78959
78982
  platform_user_id: typeof user.platform_user_id === "string" ? user.platform_user_id : null,
78960
78983
  email: typeof user.email === "string" ? user.email : null,
78961
78984
  display_name: typeof user.display_name === "string" ? user.display_name : null,
@@ -78979,7 +79002,19 @@ async function consumeWebSocketTicket(request, env2) {
78979
79002
  ticket_hash: await hashToken(ticket),
78980
79003
  pathname: url.pathname
78981
79004
  });
78982
- return typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
79005
+ const context = typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
79006
+ if (!context || context.authType === "super_admin") return context;
79007
+ const user = await getAgentBuilder(env2).getUserById(context.user.id);
79008
+ if (!user) return null;
79009
+ return {
79010
+ ...context,
79011
+ user: {
79012
+ ...context.user,
79013
+ role: user.role,
79014
+ permissions: parsePermissions(user.permissions),
79015
+ roles: user.roles ?? []
79016
+ }
79017
+ };
78983
79018
  }
78984
79019
  function isWebSocketTicketPath(pathname) {
78985
79020
  if (!pathname.startsWith("/api/") || pathname.includes("?") || pathname.includes("#")) return false;
@@ -79010,7 +79045,8 @@ async function authenticate(request, env2) {
79010
79045
  user: {
79011
79046
  id: "super_admin",
79012
79047
  username: "admin",
79013
- role: "admin"
79048
+ role: "admin",
79049
+ roles: []
79014
79050
  },
79015
79051
  authType: "super_admin"
79016
79052
  };
@@ -79031,6 +79067,7 @@ async function authenticate(request, env2) {
79031
79067
  username: user.username,
79032
79068
  role: user.role,
79033
79069
  permissions: parsePermissions(user.permissions),
79070
+ roles: user.roles ?? [],
79034
79071
  platform_user_id: user.platform_user_id ?? null,
79035
79072
  email: user.email ?? null,
79036
79073
  display_name: user.display_name ?? null,
@@ -79054,6 +79091,7 @@ async function authenticate(request, env2) {
79054
79091
  username: user.username,
79055
79092
  role: user.role,
79056
79093
  permissions: parsePermissions(user.permissions),
79094
+ roles: user.roles ?? [],
79057
79095
  platform_user_id: user.platform_user_id ?? null,
79058
79096
  email: user.email ?? null,
79059
79097
  display_name: user.display_name ?? null,
@@ -79332,6 +79370,8 @@ function normalizeLocalBootstrap(payload, endpoint) {
79332
79370
  id: platformUserId,
79333
79371
  username,
79334
79372
  role,
79373
+ roles: Array.isArray(payload.user?.roles) && payload.user.roles.every((role2) => typeof role2 === "string") ? payload.user.roles : [],
79374
+ roles_version: typeof payload.user?.roles_version === "number" ? payload.user.roles_version : 0,
79335
79375
  email: typeof payload.user?.email === "string" ? payload.user.email : null,
79336
79376
  display_name: typeof payload.user?.display_name === "string" ? payload.user.display_name : null,
79337
79377
  avatar_url: typeof payload.user?.avatar_url === "string" ? payload.user.avatar_url : null,
@@ -79389,7 +79429,9 @@ async function upsertReplicaUserForPlatformProfile(env2, profile) {
79389
79429
  email: profile.email ?? null,
79390
79430
  avatar_url: profile.avatar_url ?? null,
79391
79431
  role: profile.role ?? "admin",
79392
- permissions: profile.permissions ?? null
79432
+ permissions: profile.permissions ?? null,
79433
+ roles: profile.roles ?? [],
79434
+ roles_version: profile.roles_version
79393
79435
  });
79394
79436
  }
79395
79437
  async function mintLocalSessionForPlatformUser(env2, platformUser) {
@@ -79403,7 +79445,7 @@ async function mintLocalSessionForPlatformUser(env2, platformUser) {
79403
79445
  const tokenHash = await hashToken(token);
79404
79446
  const expiresAt = Math.floor(Date.now() / 1e3) + SESSION_TTL_SECONDS;
79405
79447
  await agentBuilder.createSession({ user_id: user.id, token_hash: tokenHash, expires_at: expiresAt });
79406
- return { token, user: { id: user.id, username: user.username, role: user.role } };
79448
+ return { token, user: { id: user.id, username: user.username, role: user.role, roles: user.roles ?? [] } };
79407
79449
  }
79408
79450
  async function consumePlatformHandoffJti(env2, jti, expiresAt) {
79409
79451
  return agentBuilderAuthStub(env2).consumePlatformHandoffJti({
@@ -79431,7 +79473,9 @@ async function createPlatformLocalSession(request, env2) {
79431
79473
  display_name: identity.user.display_name ?? null,
79432
79474
  email: identity.user.email ?? null,
79433
79475
  avatar_url: identity.user.avatar_url ?? null,
79434
- role: identity.user.role
79476
+ role: identity.user.role,
79477
+ roles: identity.user.roles ?? [],
79478
+ roles_version: identity.user.roles_version
79435
79479
  });
79436
79480
  const token = generateUserToken();
79437
79481
  const tokenHash = await hashToken(token);
@@ -79446,7 +79490,8 @@ async function createPlatformLocalSession(request, env2) {
79446
79490
  user: {
79447
79491
  id: user.id,
79448
79492
  username: user.username,
79449
- role: user.role
79493
+ role: user.role,
79494
+ roles: user.roles ?? []
79450
79495
  },
79451
79496
  authType: "platform_token",
79452
79497
  platform: identity
@@ -79698,7 +79743,7 @@ var config_get_default = defineController2(async ({ config, env: env2, req }) =>
79698
79743
  });
79699
79744
 
79700
79745
  // src/version.ts
79701
- var BUILDER_VERSION = "0.41.15".length > 0 ? "0.41.15" : "dev";
79746
+ var BUILDER_VERSION = "0.41.17".length > 0 ? "0.41.17" : "dev";
79702
79747
 
79703
79748
  // src/api/diagnostics.get.ts
79704
79749
  init_LLMRequest();
@@ -82109,6 +82154,101 @@ function authRateLimitedResponse(result) {
82109
82154
  );
82110
82155
  }
82111
82156
 
82157
+ // src/utils/key-exchange.ts
82158
+ var HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
82159
+ function validHost(host) {
82160
+ return host.length <= 253 && host.split(".").length >= 2 && host.split(".").every((label) => HOST_LABEL.test(label));
82161
+ }
82162
+ function canonicalSourceOrigin(value) {
82163
+ if (typeof value !== "string" || value.length > 300) return null;
82164
+ let parsed;
82165
+ try {
82166
+ parsed = new URL(value);
82167
+ } catch {
82168
+ return null;
82169
+ }
82170
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.port || parsed.search || parsed.hash || parsed.pathname !== "/" && parsed.pathname !== "" || !validHost(parsed.hostname) || value.replace(/\/$/, "") !== parsed.origin) {
82171
+ return null;
82172
+ }
82173
+ return parsed.origin;
82174
+ }
82175
+ function isTrustedOrigin(origin, trustedOrigins) {
82176
+ const host = origin.slice("https://".length);
82177
+ return trustedOrigins.some((entry) => {
82178
+ const trusted = entry.slice("https://".length);
82179
+ return trusted.startsWith("*.") ? host.endsWith(trusted.slice(1)) && host.length > trusted.length - 1 : host === trusted;
82180
+ });
82181
+ }
82182
+
82183
+ // src/api/auth/exchange.post.ts
82184
+ var ADDRESS_LIMIT = { limit: 30, window_seconds: 60 * 60 };
82185
+ var SOURCE_TIMEOUT_MS = 5e3;
82186
+ function refused(status, error, message) {
82187
+ return Response.json({ error, message }, { status, headers: { "Cache-Control": "no-store" } });
82188
+ }
82189
+ var exchange_post_default = defineController2(async ({ req, env: env2, config }) => {
82190
+ const trustedOrigins = config?.keyExchange?.trustedOrigins;
82191
+ if (!trustedOrigins?.length) {
82192
+ return refused(404, "key_exchange_unsupported", "This instance does not exchange keys.");
82193
+ }
82194
+ const body = await req.json().catch(() => null);
82195
+ const sourceOrigin = canonicalSourceOrigin(body?.source_origin);
82196
+ const sourceKey = typeof body?.source_key === "string" ? body.source_key.trim() : "";
82197
+ if (!sourceOrigin || !sourceKey || sourceKey.length > 512) {
82198
+ return refused(400, "invalid_request", "source_origin and source_key are required.");
82199
+ }
82200
+ if (sourceOrigin === new URL(req.url).origin || !isTrustedOrigin(sourceOrigin, trustedOrigins)) {
82201
+ return refused(403, "untrusted_origin", "This instance does not trust that origin.");
82202
+ }
82203
+ const agentBuilder = env2.AGENT_BUILDER.get(env2.AGENT_BUILDER.idFromName("singleton"));
82204
+ const addressLimit = await agentBuilder.consumeAuthRateLimit({
82205
+ key: await authRateLimitKey("key-exchange-address", authClientAddress(req)),
82206
+ ...ADDRESS_LIMIT
82207
+ });
82208
+ if (!addressLimit.allowed) return authRateLimitedResponse(addressLimit);
82209
+ let identity;
82210
+ try {
82211
+ const response = await fetch(`${sourceOrigin}/api/auth/me`, {
82212
+ headers: { Authorization: `Bearer ${sourceKey}`, Accept: "application/json" },
82213
+ redirect: "manual",
82214
+ signal: AbortSignal.timeout(SOURCE_TIMEOUT_MS)
82215
+ });
82216
+ if (response.status === 401 || response.status === 403) {
82217
+ return refused(401, "source_key_rejected", "The source deployment rejected the key.");
82218
+ }
82219
+ if (!response.ok) {
82220
+ return refused(502, "source_unavailable", `The source deployment answered ${response.status}.`);
82221
+ }
82222
+ identity = await response.json();
82223
+ } catch {
82224
+ return refused(502, "source_unavailable", "The source deployment could not be reached.");
82225
+ }
82226
+ if (identity?.authType !== "api_key") {
82227
+ return refused(401, "source_key_rejected", "Only API keys can be exchanged.");
82228
+ }
82229
+ const platformUserId = identity.user?.platform_user_id;
82230
+ if (typeof platformUserId !== "string" || !platformUserId) {
82231
+ return refused(403, "unknown_user", "The key does not belong to a platform user.");
82232
+ }
82233
+ const user = await agentBuilder.getUserByPlatformUserId(platformUserId);
82234
+ if (!user) {
82235
+ return refused(403, "unknown_user", "That user has not signed in to this instance.");
82236
+ }
82237
+ const token = generateApiKey();
82238
+ const sourceHost = new URL(sourceOrigin).hostname;
82239
+ await agentBuilder.createApiKey({
82240
+ name: `Key exchange from ${sourceHost}`.slice(0, 100),
82241
+ key_hash: await hashToken(token),
82242
+ key_prefix: getTokenPrefix(token),
82243
+ last_five: getLastChars(token, 5),
82244
+ user_id: user.id
82245
+ });
82246
+ return Response.json(
82247
+ { token, endpoint: new URL(req.url).origin, user: { id: user.id, username: user.username } },
82248
+ { status: 201, headers: { "Cache-Control": "no-store" } }
82249
+ );
82250
+ });
82251
+
82112
82252
  // src/api/auth/login.post.ts
82113
82253
  var LOGIN_ADDRESS_LIMIT = { limit: 10, window_seconds: 5 * 60 };
82114
82254
  var LOGIN_IDENTITY_LIMIT = { limit: 25, window_seconds: 15 * 60 };
@@ -82371,6 +82511,7 @@ var platform_replica_post_default = defineController2(async ({ req, env: env2 })
82371
82511
  const agentBuilder = env2.AGENT_BUILDER.get(env2.AGENT_BUILDER.idFromName("singleton"));
82372
82512
  const result = await agentBuilder.applyPlatformReplicaSnapshot({
82373
82513
  users: payload.users,
82514
+ role_revocations: payload.role_revocations,
82374
82515
  api_keys: payload.api_keys ?? []
82375
82516
  });
82376
82517
  return Response.json({ ok: true, ...result });
@@ -86225,7 +86366,9 @@ var callback_get_default = defineController2(async ({ req, env: env2 }) => {
86225
86366
  email: payload.email ?? null,
86226
86367
  avatar_url: payload.avatar_url ?? null,
86227
86368
  role: payload.role,
86228
- permissions: payload.permissions ?? null
86369
+ permissions: payload.permissions ?? null,
86370
+ roles: payload.roles ?? [],
86371
+ roles_version: payload.roles_version
86229
86372
  });
86230
86373
  const returnTo = failReturnTo;
86231
86374
  const headers = new Headers({
@@ -86679,7 +86822,9 @@ async function resolveDeviceApprovalIdentity(req, env2) {
86679
86822
  email: payload.email ?? null,
86680
86823
  avatar_url: payload.avatar_url ?? null,
86681
86824
  role: payload.role,
86682
- permissions: payload.permissions ?? null
86825
+ permissions: payload.permissions ?? null,
86826
+ roles: payload.roles ?? [],
86827
+ roles_version: payload.roles_version
86683
86828
  };
86684
86829
  return {
86685
86830
  confirmationSubject: `platform:${payload.platform_user_id}`,
@@ -88079,6 +88224,7 @@ var routeHandlers = {
88079
88224
  "POST:/auth/brolly-control": brolly_control_post_default,
88080
88225
  "GET:/auth/config": config_get_default2,
88081
88226
  "DELETE:/auth/current-key": current_key_delete_default,
88227
+ "POST:/auth/exchange": exchange_post_default,
88082
88228
  "POST:/auth/login": login_post_default,
88083
88229
  "POST:/auth/logout": logout_post_default,
88084
88230
  "GET:/auth/me": me_get_default,