@standardagents/builder 0.17.2 → 0.18.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.
package/dist/runtime.js CHANGED
@@ -12539,11 +12539,24 @@ async function hashToken(token) {
12539
12539
  const hashArray = new Uint8Array(hashBuffer);
12540
12540
  return Array.from(hashArray, (byte) => byte.toString(16).padStart(2, "0")).join("");
12541
12541
  }
12542
+ var SESSION_COOKIE_NAME = "agtuser_session";
12543
+ function readSessionCookie(request) {
12544
+ const header = request.headers.get("Cookie");
12545
+ if (!header) return null;
12546
+ for (const part of header.split(";")) {
12547
+ const eq = part.indexOf("=");
12548
+ if (eq === -1) continue;
12549
+ if (part.slice(0, eq).trim() === SESSION_COOKIE_NAME) {
12550
+ return decodeURIComponent(part.slice(eq + 1).trim()) || null;
12551
+ }
12552
+ }
12553
+ return null;
12554
+ }
12542
12555
  function isValidUserToken(token) {
12543
12556
  return token.startsWith("agtuser_") && token.length > 10;
12544
12557
  }
12545
12558
  function isValidApiKey(key) {
12546
- return key.startsWith("agtbldr_") && key.length > 10;
12559
+ return key.startsWith("agtbldr_") && key.length > 10 || key.startsWith("sak_live_") && key.length > 10;
12547
12560
  }
12548
12561
  async function verifySignedToken(signedToken, encryptionKey) {
12549
12562
  try {
@@ -12612,6 +12625,10 @@ function extractBearerToken(request) {
12612
12625
  if (authHeader && authHeader.startsWith("Bearer ")) {
12613
12626
  return authHeader.substring(7);
12614
12627
  }
12628
+ const cookieToken = readSessionCookie(request);
12629
+ if (cookieToken) {
12630
+ return cookieToken;
12631
+ }
12615
12632
  const isWebSocket = request.headers.get("upgrade")?.toLowerCase() === "websocket";
12616
12633
  if (isWebSocket) {
12617
12634
  try {
@@ -12663,7 +12680,11 @@ async function authenticate(request, env) {
12663
12680
  user: {
12664
12681
  id: user.id,
12665
12682
  username: user.username,
12666
- role: user.role
12683
+ role: user.role,
12684
+ platform_user_id: user.platform_user_id ?? null,
12685
+ email: user.email ?? null,
12686
+ display_name: user.display_name ?? null,
12687
+ avatar_url: user.avatar_url ?? null
12667
12688
  },
12668
12689
  authType: "session"
12669
12690
  };
@@ -12681,7 +12702,11 @@ async function authenticate(request, env) {
12681
12702
  user: {
12682
12703
  id: user.id,
12683
12704
  username: user.username,
12684
- role: user.role
12705
+ role: user.role,
12706
+ platform_user_id: user.platform_user_id ?? null,
12707
+ email: user.email ?? null,
12708
+ display_name: user.display_name ?? null,
12709
+ avatar_url: user.avatar_url ?? null
12685
12710
  },
12686
12711
  authType: "api_key"
12687
12712
  };
@@ -15471,9 +15496,9 @@ var DurableThread = class extends DurableObject {
15471
15496
  * Each migration is run in order, starting from the current version + 1.
15472
15497
  */
15473
15498
  async runMigrations(fromVersion) {
15474
- for (const migration37 of migrations) {
15475
- if (migration37.version > fromVersion) {
15476
- await migration37.up(this.ctx.storage.sql);
15499
+ for (const migration38 of migrations) {
15500
+ if (migration38.version > fromVersion) {
15501
+ await migration38.up(this.ctx.storage.sql);
15477
15502
  }
15478
15503
  }
15479
15504
  }
@@ -18908,9 +18933,38 @@ var migration36 = {
18908
18933
  }
18909
18934
  };
18910
18935
 
18936
+ // src/durable-objects/agentbuilder-migrations/0007_platform_identity_replica.ts
18937
+ var migration37 = {
18938
+ version: 7,
18939
+ async up(sql) {
18940
+ sql.exec(`ALTER TABLE users ADD COLUMN platform_user_id TEXT`);
18941
+ sql.exec(`ALTER TABLE users ADD COLUMN email TEXT`);
18942
+ sql.exec(`ALTER TABLE users ADD COLUMN display_name TEXT`);
18943
+ sql.exec(`ALTER TABLE users ADD COLUMN avatar_url TEXT`);
18944
+ sql.exec(`ALTER TABLE users ADD COLUMN instance_role TEXT NOT NULL DEFAULT 'admin' CHECK (instance_role IN ('admin', 'user'))`);
18945
+ sql.exec(`ALTER TABLE users ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
18946
+ sql.exec(`ALTER TABLE users ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
18947
+ sql.exec(`ALTER TABLE users ADD COLUMN replica_updated_at INTEGER`);
18948
+ sql.exec(`ALTER TABLE users ADD COLUMN deleted_at INTEGER`);
18949
+ sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_platform_user_id ON users(platform_user_id)`);
18950
+ sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_replica_active ON users(replica_active)`);
18951
+ sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_instance_role ON users(instance_role)`);
18952
+ sql.exec(`ALTER TABLE api_keys ADD COLUMN platform_key_id TEXT`);
18953
+ sql.exec(`ALTER TABLE api_keys ADD COLUMN scope TEXT`);
18954
+ sql.exec(`ALTER TABLE api_keys ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
18955
+ sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
18956
+ sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_updated_at INTEGER`);
18957
+ sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_platform_key_id ON api_keys(platform_key_id)`);
18958
+ sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_replica_active ON api_keys(replica_active)`);
18959
+ sql.exec(`
18960
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '7')
18961
+ `);
18962
+ }
18963
+ };
18964
+
18911
18965
  // src/durable-objects/agentbuilder-migrations/index.ts
18912
- var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36];
18913
- var LATEST_SCHEMA_VERSION2 = 6;
18966
+ var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36, migration37];
18967
+ var LATEST_SCHEMA_VERSION2 = 7;
18914
18968
 
18915
18969
  // src/utils/crypto.ts
18916
18970
  var CryptoUtil = class {
@@ -19534,9 +19588,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
19534
19588
  }
19535
19589
  }
19536
19590
  async runMigrations(fromVersion) {
19537
- for (const migration37 of migrations2) {
19538
- if (migration37.version > fromVersion) {
19539
- await migration37.up(this.ctx.storage.sql);
19591
+ for (const migration38 of migrations2) {
19592
+ if (migration38.version > fromVersion) {
19593
+ await migration38.up(this.ctx.storage.sql);
19540
19594
  }
19541
19595
  }
19542
19596
  }
@@ -20569,27 +20623,54 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20569
20623
  // ============================================================
20570
20624
  // User Authentication Methods
20571
20625
  // ============================================================
20626
+ normalizeReplicaUsername(input, fallback) {
20627
+ const normalized = (input || fallback).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
20628
+ return normalized || fallback;
20629
+ }
20630
+ async availableReplicaUsername(candidate, platformUserId) {
20631
+ const existing = await this.ctx.storage.sql.exec(
20632
+ `SELECT id, platform_user_id FROM users WHERE username = ? LIMIT 1`,
20633
+ candidate
20634
+ );
20635
+ const row = existing.toArray()[0];
20636
+ if (!row || row.platform_user_id === platformUserId) {
20637
+ return candidate;
20638
+ }
20639
+ const suffix = platformUserId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "user";
20640
+ const trimmed = candidate.slice(0, Math.max(1, 50 - suffix.length - 1)).replace(/-+$/g, "");
20641
+ return `${trimmed || "sa"}-${suffix}`;
20642
+ }
20643
+ userFromRow(row) {
20644
+ return {
20645
+ id: row.id,
20646
+ username: row.username,
20647
+ password_hash: row.password_hash,
20648
+ role: row.role === "user" ? "user" : "admin",
20649
+ platform_user_id: row.platform_user_id ?? null,
20650
+ email: row.email ?? null,
20651
+ display_name: row.display_name ?? null,
20652
+ avatar_url: row.avatar_url ?? null,
20653
+ source: row.source ?? "local",
20654
+ replica_active: row.replica_active ?? 1,
20655
+ created_at: row.created_at,
20656
+ updated_at: row.updated_at
20657
+ };
20658
+ }
20572
20659
  /**
20573
20660
  * Get a user by username.
20574
20661
  */
20575
20662
  async getUserByUsername(username) {
20576
20663
  await this.ensureMigrated();
20577
20664
  const cursor = await this.ctx.storage.sql.exec(
20578
- `SELECT id, username, password_hash, role, created_at, updated_at
20579
- FROM users WHERE username = ?`,
20665
+ `SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
20666
+ platform_user_id, email, display_name, avatar_url, source, replica_active,
20667
+ created_at, updated_at
20668
+ FROM users WHERE username = ? AND deleted_at IS NULL AND replica_active != 0`,
20580
20669
  username
20581
20670
  );
20582
20671
  const rows = cursor.toArray();
20583
20672
  if (rows.length === 0) return null;
20584
- const row = rows[0];
20585
- return {
20586
- id: row.id,
20587
- username: row.username,
20588
- password_hash: row.password_hash,
20589
- role: row.role,
20590
- created_at: row.created_at,
20591
- updated_at: row.updated_at
20592
- };
20673
+ return this.userFromRow(rows[0]);
20593
20674
  }
20594
20675
  /**
20595
20676
  * Get a user by ID.
@@ -20597,21 +20678,15 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20597
20678
  async getUserById(id) {
20598
20679
  await this.ensureMigrated();
20599
20680
  const cursor = await this.ctx.storage.sql.exec(
20600
- `SELECT id, username, password_hash, role, created_at, updated_at
20601
- FROM users WHERE id = ?`,
20681
+ `SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
20682
+ platform_user_id, email, display_name, avatar_url, source, replica_active,
20683
+ created_at, updated_at
20684
+ FROM users WHERE id = ? AND deleted_at IS NULL AND replica_active != 0`,
20602
20685
  id
20603
20686
  );
20604
20687
  const rows = cursor.toArray();
20605
20688
  if (rows.length === 0) return null;
20606
- const row = rows[0];
20607
- return {
20608
- id: row.id,
20609
- username: row.username,
20610
- password_hash: row.password_hash,
20611
- role: row.role,
20612
- created_at: row.created_at,
20613
- updated_at: row.updated_at
20614
- };
20689
+ return this.userFromRow(rows[0]);
20615
20690
  }
20616
20691
  /**
20617
20692
  * Create a new user.
@@ -20621,12 +20696,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20621
20696
  const id = crypto.randomUUID();
20622
20697
  const now = Math.floor(Date.now() / 1e3);
20623
20698
  await this.ctx.storage.sql.exec(
20624
- `INSERT INTO users (id, username, password_hash, role, created_at, updated_at)
20625
- VALUES (?, ?, ?, ?, ?, ?)`,
20699
+ `INSERT INTO users (
20700
+ id, username, password_hash, role, instance_role, platform_user_id,
20701
+ email, display_name, avatar_url, source, replica_active,
20702
+ replica_updated_at, created_at, updated_at
20703
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
20626
20704
  id,
20627
20705
  params.username,
20628
20706
  params.password_hash,
20707
+ "admin",
20629
20708
  params.role || "admin",
20709
+ params.platform_user_id ?? null,
20710
+ params.email ?? null,
20711
+ params.display_name ?? null,
20712
+ params.avatar_url ?? null,
20713
+ params.source ?? "local",
20714
+ 1,
20715
+ params.source === "standard_agents" ? now : null,
20630
20716
  now,
20631
20717
  now
20632
20718
  );
@@ -20635,6 +20721,12 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20635
20721
  username: params.username,
20636
20722
  password_hash: params.password_hash,
20637
20723
  role: params.role || "admin",
20724
+ platform_user_id: params.platform_user_id ?? null,
20725
+ email: params.email ?? null,
20726
+ display_name: params.display_name ?? null,
20727
+ avatar_url: params.avatar_url ?? null,
20728
+ source: params.source ?? "local",
20729
+ replica_active: 1,
20638
20730
  created_at: now,
20639
20731
  updated_at: now
20640
20732
  };
@@ -20654,11 +20746,24 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20654
20746
  */
20655
20747
  async listUsers() {
20656
20748
  await this.ensureMigrated();
20657
- const cursor = await this.ctx.storage.sql.exec(`SELECT id, username, role, created_at, updated_at FROM users ORDER BY created_at DESC`);
20749
+ const cursor = await this.ctx.storage.sql.exec(
20750
+ `SELECT id, username, COALESCE(instance_role, role) AS role,
20751
+ platform_user_id, email, display_name, avatar_url, source, replica_active,
20752
+ created_at, updated_at
20753
+ FROM users
20754
+ WHERE deleted_at IS NULL
20755
+ ORDER BY created_at DESC`
20756
+ );
20658
20757
  return cursor.toArray().map((row) => ({
20659
20758
  id: row.id,
20660
20759
  username: row.username,
20661
- role: row.role,
20760
+ role: row.role === "user" ? "user" : "admin",
20761
+ platform_user_id: row.platform_user_id ?? null,
20762
+ email: row.email ?? null,
20763
+ display_name: row.display_name ?? null,
20764
+ avatar_url: row.avatar_url ?? null,
20765
+ source: row.source ?? "local",
20766
+ replica_active: row.replica_active ?? 1,
20662
20767
  created_at: row.created_at,
20663
20768
  updated_at: row.updated_at
20664
20769
  }));
@@ -20682,14 +20787,42 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20682
20787
  values.push(params.password_hash);
20683
20788
  }
20684
20789
  if (params.role !== void 0) {
20685
- updates.push("role = ?");
20790
+ updates.push("instance_role = ?");
20686
20791
  values.push(params.role);
20687
20792
  }
20793
+ if (params.platform_user_id !== void 0) {
20794
+ updates.push("platform_user_id = ?");
20795
+ values.push(params.platform_user_id);
20796
+ }
20797
+ if (params.email !== void 0) {
20798
+ updates.push("email = ?");
20799
+ values.push(params.email);
20800
+ }
20801
+ if (params.display_name !== void 0) {
20802
+ updates.push("display_name = ?");
20803
+ values.push(params.display_name);
20804
+ }
20805
+ if (params.avatar_url !== void 0) {
20806
+ updates.push("avatar_url = ?");
20807
+ values.push(params.avatar_url);
20808
+ }
20809
+ if (params.source !== void 0) {
20810
+ updates.push("source = ?");
20811
+ values.push(params.source);
20812
+ }
20813
+ if (params.replica_active !== void 0) {
20814
+ updates.push("replica_active = ?");
20815
+ values.push(params.replica_active);
20816
+ }
20688
20817
  if (updates.length === 0) {
20689
20818
  return existing;
20690
20819
  }
20691
20820
  updates.push("updated_at = ?");
20692
20821
  values.push(now);
20822
+ if (params.source === "standard_agents" || params.replica_active !== void 0) {
20823
+ updates.push("replica_updated_at = ?");
20824
+ values.push(now);
20825
+ }
20693
20826
  values.push(id);
20694
20827
  await this.ctx.storage.sql.exec(
20695
20828
  `UPDATE users SET ${updates.join(", ")} WHERE id = ?`,
@@ -20710,9 +20843,223 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20710
20843
  `DELETE FROM api_keys WHERE user_id = ?`,
20711
20844
  id
20712
20845
  );
20713
- await this.ctx.storage.sql.exec(`DELETE FROM users WHERE id = ?`, id);
20846
+ await this.ctx.storage.sql.exec(
20847
+ `UPDATE users SET deleted_at = ?, replica_active = 0 WHERE id = ?`,
20848
+ Math.floor(Date.now() / 1e3),
20849
+ id
20850
+ );
20714
20851
  return true;
20715
20852
  }
20853
+ /**
20854
+ * Upsert a platform-replicated identity and return the local user row.
20855
+ */
20856
+ async upsertPlatformReplicaUser(replica) {
20857
+ await this.ensureMigrated();
20858
+ const now = Math.floor(Date.now() / 1e3);
20859
+ const fallback = this.normalizeReplicaUsername(
20860
+ replica.email?.split("@")[0] ?? null,
20861
+ `sa-${replica.platform_user_id.slice(0, 12)}`
20862
+ );
20863
+ const username = this.normalizeReplicaUsername(
20864
+ replica.username ?? replica.display_name ?? replica.email ?? null,
20865
+ fallback
20866
+ );
20867
+ const existingCursor = await this.ctx.storage.sql.exec(
20868
+ `SELECT id FROM users WHERE platform_user_id = ? LIMIT 1`,
20869
+ replica.platform_user_id
20870
+ );
20871
+ const existing = existingCursor.toArray()[0];
20872
+ const availableUsername = await this.availableReplicaUsername(username, replica.platform_user_id);
20873
+ if (existing) {
20874
+ await this.ctx.storage.sql.exec(
20875
+ `UPDATE users SET
20876
+ username = ?,
20877
+ instance_role = ?,
20878
+ email = ?,
20879
+ display_name = ?,
20880
+ avatar_url = ?,
20881
+ source = 'standard_agents',
20882
+ replica_active = 1,
20883
+ replica_updated_at = ?,
20884
+ deleted_at = NULL,
20885
+ updated_at = ?
20886
+ WHERE id = ?`,
20887
+ availableUsername,
20888
+ replica.role,
20889
+ replica.email ?? null,
20890
+ replica.display_name ?? null,
20891
+ replica.avatar_url ?? null,
20892
+ now,
20893
+ now,
20894
+ existing.id
20895
+ );
20896
+ const updated = await this.getUserById(existing.id);
20897
+ if (!updated) {
20898
+ throw new Error("Failed to load replicated user after update");
20899
+ }
20900
+ return updated;
20901
+ }
20902
+ return this.createUser({
20903
+ username: availableUsername,
20904
+ password_hash: `platform-replica:${crypto.randomUUID()}`,
20905
+ role: replica.role,
20906
+ platform_user_id: replica.platform_user_id,
20907
+ email: replica.email ?? null,
20908
+ display_name: replica.display_name ?? null,
20909
+ avatar_url: replica.avatar_url ?? null,
20910
+ source: "standard_agents"
20911
+ });
20912
+ }
20913
+ /**
20914
+ * Apply a full platform read-replica snapshot for this instance.
20915
+ */
20916
+ async applyPlatformReplicaSnapshot(snapshot) {
20917
+ await this.ensureMigrated();
20918
+ const activeUserIds = /* @__PURE__ */ new Set();
20919
+ for (const replicaUser of snapshot.users) {
20920
+ const user = await this.upsertPlatformReplicaUser(replicaUser);
20921
+ activeUserIds.add(user.id);
20922
+ }
20923
+ const activeList = Array.from(activeUserIds);
20924
+ if (activeList.length > 0) {
20925
+ const placeholders = activeList.map(() => "?").join(", ");
20926
+ const inactive = await this.ctx.storage.sql.exec(
20927
+ `SELECT id FROM users
20928
+ WHERE source = 'standard_agents'
20929
+ AND replica_active != 0
20930
+ AND id NOT IN (${placeholders})`,
20931
+ ...activeList
20932
+ );
20933
+ for (const row of inactive.toArray()) {
20934
+ await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
20935
+ await this.ctx.storage.sql.exec(
20936
+ `UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ? WHERE id = ?`,
20937
+ Math.floor(Date.now() / 1e3),
20938
+ Math.floor(Date.now() / 1e3),
20939
+ Math.floor(Date.now() / 1e3),
20940
+ row.id
20941
+ );
20942
+ }
20943
+ } else {
20944
+ const inactive = await this.ctx.storage.sql.exec(
20945
+ `SELECT id FROM users WHERE source = 'standard_agents' AND replica_active != 0`
20946
+ );
20947
+ for (const row of inactive.toArray()) {
20948
+ await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
20949
+ }
20950
+ const now2 = Math.floor(Date.now() / 1e3);
20951
+ await this.ctx.storage.sql.exec(
20952
+ `UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ?
20953
+ WHERE source = 'standard_agents'`,
20954
+ now2,
20955
+ now2,
20956
+ now2
20957
+ );
20958
+ }
20959
+ const activePlatformKeyIds = /* @__PURE__ */ new Set();
20960
+ const keys = snapshot.api_keys ?? [];
20961
+ for (const key of keys) {
20962
+ const user = key.user_platform_id ? await this.getUserByPlatformUserId(key.user_platform_id) : await this.getFirstReplicaAdminUser();
20963
+ const keyUser = user ?? await this.getFirstReplicaAdminUser();
20964
+ if (!keyUser) continue;
20965
+ activePlatformKeyIds.add(key.id);
20966
+ await this.upsertPlatformReplicaApiKey(key, keyUser.id);
20967
+ }
20968
+ const now = Math.floor(Date.now() / 1e3);
20969
+ if (activePlatformKeyIds.size > 0) {
20970
+ const ids = Array.from(activePlatformKeyIds);
20971
+ const placeholders = ids.map(() => "?").join(", ");
20972
+ await this.ctx.storage.sql.exec(
20973
+ `UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
20974
+ WHERE source = 'standard_agents' AND platform_key_id NOT IN (${placeholders})`,
20975
+ now,
20976
+ ...ids
20977
+ );
20978
+ } else {
20979
+ await this.ctx.storage.sql.exec(
20980
+ `UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
20981
+ WHERE source = 'standard_agents'`,
20982
+ now
20983
+ );
20984
+ }
20985
+ return { users: activeUserIds.size, api_keys: activePlatformKeyIds.size };
20986
+ }
20987
+ async getUserByPlatformUserId(platformUserId) {
20988
+ await this.ensureMigrated();
20989
+ const cursor = await this.ctx.storage.sql.exec(
20990
+ `SELECT id FROM users
20991
+ WHERE platform_user_id = ? AND deleted_at IS NULL AND replica_active != 0
20992
+ LIMIT 1`,
20993
+ platformUserId
20994
+ );
20995
+ const row = cursor.toArray()[0];
20996
+ return row ? this.getUserById(row.id) : null;
20997
+ }
20998
+ async getFirstReplicaAdminUser() {
20999
+ await this.ensureMigrated();
21000
+ const cursor = await this.ctx.storage.sql.exec(
21001
+ `SELECT id FROM users
21002
+ WHERE source = 'standard_agents'
21003
+ AND instance_role = 'admin'
21004
+ AND deleted_at IS NULL
21005
+ AND replica_active != 0
21006
+ ORDER BY created_at ASC
21007
+ LIMIT 1`
21008
+ );
21009
+ const row = cursor.toArray()[0];
21010
+ return row ? this.getUserById(row.id) : null;
21011
+ }
21012
+ async upsertPlatformReplicaApiKey(key, userId) {
21013
+ await this.ensureMigrated();
21014
+ const now = Math.floor(Date.now() / 1e3);
21015
+ const existing = await this.ctx.storage.sql.exec(
21016
+ `SELECT id FROM api_keys WHERE platform_key_id = ? LIMIT 1`,
21017
+ key.id
21018
+ );
21019
+ const row = existing.toArray()[0];
21020
+ const name = key.name || `Standard Agents ${key.key_prefix}`;
21021
+ const lastFive = key.last_five || key.key_prefix.slice(-5);
21022
+ if (row) {
21023
+ await this.ctx.storage.sql.exec(
21024
+ `UPDATE api_keys SET
21025
+ name = ?,
21026
+ key_hash = ?,
21027
+ key_prefix = ?,
21028
+ last_five = ?,
21029
+ user_id = ?,
21030
+ scope = ?,
21031
+ source = 'standard_agents',
21032
+ replica_active = 1,
21033
+ replica_updated_at = ?
21034
+ WHERE id = ?`,
21035
+ name,
21036
+ key.key_hash,
21037
+ key.key_prefix,
21038
+ lastFive,
21039
+ userId,
21040
+ key.scope ?? null,
21041
+ now,
21042
+ row.id
21043
+ );
21044
+ return;
21045
+ }
21046
+ await this.ctx.storage.sql.exec(
21047
+ `INSERT INTO api_keys (
21048
+ id, name, key_hash, key_prefix, last_five, user_id, created_at,
21049
+ platform_key_id, scope, source, replica_active, replica_updated_at
21050
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'standard_agents', 1, ?)`,
21051
+ `platform:${key.id}`,
21052
+ name,
21053
+ key.key_hash,
21054
+ key.key_prefix,
21055
+ lastFive,
21056
+ userId,
21057
+ key.created_at ?? now,
21058
+ key.id,
21059
+ key.scope ?? null,
21060
+ now
21061
+ );
21062
+ }
20716
21063
  // ============================================================
20717
21064
  // Session Methods
20718
21065
  // ============================================================
@@ -20741,8 +21088,13 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20741
21088
  await this.ensureMigrated();
20742
21089
  const now = Math.floor(Date.now() / 1e3);
20743
21090
  const cursor = await this.ctx.storage.sql.exec(
20744
- `SELECT user_id, expires_at FROM sessions
20745
- WHERE token_hash = ? AND expires_at > ?`,
21091
+ `SELECT sessions.user_id, sessions.expires_at
21092
+ FROM sessions
21093
+ JOIN users ON users.id = sessions.user_id
21094
+ WHERE sessions.token_hash = ?
21095
+ AND sessions.expires_at > ?
21096
+ AND users.deleted_at IS NULL
21097
+ AND users.replica_active != 0`,
20746
21098
  tokenHash,
20747
21099
  now
20748
21100
  );
@@ -20799,14 +21151,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20799
21151
  */
20800
21152
  async validateApiKey(keyHash) {
20801
21153
  await this.ensureMigrated();
20802
- const cursor = await this.ctx.storage.sql.exec(`SELECT id, user_id FROM api_keys WHERE key_hash = ?`, keyHash);
21154
+ const cursor = await this.ctx.storage.sql.exec(
21155
+ `SELECT api_keys.id, api_keys.user_id
21156
+ FROM api_keys
21157
+ JOIN users ON users.id = api_keys.user_id
21158
+ WHERE api_keys.key_hash = ?
21159
+ AND api_keys.replica_active != 0
21160
+ AND users.deleted_at IS NULL
21161
+ AND users.replica_active != 0`,
21162
+ keyHash
21163
+ );
20803
21164
  const rows = cursor.toArray();
20804
21165
  if (rows.length === 0) {
20805
21166
  return null;
20806
21167
  }
20807
21168
  const now = Math.floor(Date.now() / 1e3);
20808
21169
  await this.ctx.storage.sql.exec(
20809
- `UPDATE api_keys SET last_used_at = ? WHERE key_hash = ?`,
21170
+ `UPDATE api_keys SET last_used_at = ? WHERE key_hash = ? AND replica_active != 0`,
20810
21171
  now,
20811
21172
  keyHash
20812
21173
  );
@@ -20818,8 +21179,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20818
21179
  async listApiKeys(userId) {
20819
21180
  await this.ensureMigrated();
20820
21181
  const cursor = await this.ctx.storage.sql.exec(
20821
- `SELECT id, name, key_prefix, last_five, created_at, last_used_at
20822
- FROM api_keys WHERE user_id = ? ORDER BY created_at DESC`,
21182
+ `SELECT id, name, key_prefix, last_five, source, created_at, last_used_at
21183
+ FROM api_keys
21184
+ WHERE user_id = ? AND replica_active != 0
21185
+ ORDER BY created_at DESC`,
20823
21186
  userId
20824
21187
  );
20825
21188
  return cursor.toArray();