@standardagents/builder 0.17.3 → 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/built-in-routes.js +442 -358
- package/dist/built-in-routes.js.map +1 -1
- package/dist/client/LoginView.js +1 -1
- package/dist/client/index.js +3 -3
- package/dist/index.js +461 -47
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +70 -2
- package/dist/plugin.js.map +1 -1
- package/dist/runtime.d.ts +72 -4
- package/dist/runtime.js +391 -45
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -19697,6 +19697,7 @@ const PUBLIC_ROUTES = [
|
|
|
19697
19697
|
'/api/auth/bootstrap',
|
|
19698
19698
|
'/api/auth/login',
|
|
19699
19699
|
'/api/auth/config',
|
|
19700
|
+
'/api/auth/platform-replica',
|
|
19700
19701
|
'/api/auth/sa/start', // Login with Standard Agents (OAuth) \u2014 unauthenticated entry
|
|
19701
19702
|
'/api/auth/sa/callback', // OAuth callback (sets the session cookie)
|
|
19702
19703
|
'/api/config',
|
|
@@ -19744,16 +19745,25 @@ function isPublicRoute(routePath, hosted) {
|
|
|
19744
19745
|
return true;
|
|
19745
19746
|
}
|
|
19746
19747
|
|
|
19747
|
-
// Platform proxy routes handle their own auth.
|
|
19748
|
+
// Platform proxy routes handle their own auth in local dev only.
|
|
19749
|
+
if (hosted && (routePath.startsWith('/api/platform/') || routePath === '/api/platform')) {
|
|
19750
|
+
return false;
|
|
19751
|
+
}
|
|
19748
19752
|
if (routePath.startsWith('/api/platform/') || routePath === '/api/platform') {
|
|
19749
19753
|
return true;
|
|
19750
19754
|
}
|
|
19751
19755
|
|
|
19752
|
-
// Platform session proxy and auth bridge
|
|
19756
|
+
// Platform session proxy and auth bridge are local-dev helpers only.
|
|
19757
|
+
if (hosted && (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session')) {
|
|
19758
|
+
return false;
|
|
19759
|
+
}
|
|
19753
19760
|
if (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session') {
|
|
19754
19761
|
return true;
|
|
19755
19762
|
}
|
|
19756
19763
|
|
|
19764
|
+
if (hosted && (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth')) {
|
|
19765
|
+
return false;
|
|
19766
|
+
}
|
|
19757
19767
|
if (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth') {
|
|
19758
19768
|
return true;
|
|
19759
19769
|
}
|
|
@@ -19761,6 +19771,36 @@ function isPublicRoute(routePath, hosted) {
|
|
|
19761
19771
|
return false;
|
|
19762
19772
|
}
|
|
19763
19773
|
|
|
19774
|
+
function platformEndpoint(env) {
|
|
19775
|
+
const configured =
|
|
19776
|
+
env && (env.PLATFORM_ENDPOINT || env.STANDARD_AGENTS_PLATFORM_URL || env.PLATFORM_URL || env.STANDARD_AGENTS_PUBLIC_URL);
|
|
19777
|
+
if (typeof configured === 'string' && configured.trim()) {
|
|
19778
|
+
return configured.trim().replace(/\\/+$/, '');
|
|
19779
|
+
}
|
|
19780
|
+
return 'https://platform.standardagents.ai';
|
|
19781
|
+
}
|
|
19782
|
+
|
|
19783
|
+
function hostedInstanceRedirectId(request, env) {
|
|
19784
|
+
const configured = env && (env.STANDARD_AGENTS_PROJECT_ID || env.STANDARD_AGENTS_INSTANCE_ID || env.STANDARD_AGENTS_INSTANCE_SUBDOMAIN);
|
|
19785
|
+
if (typeof configured === 'string' && configured.trim()) {
|
|
19786
|
+
return configured.trim();
|
|
19787
|
+
}
|
|
19788
|
+
return new URL(request.url).hostname;
|
|
19789
|
+
}
|
|
19790
|
+
|
|
19791
|
+
function platformLoginUrl(request, env) {
|
|
19792
|
+
const requestUrl = new URL(request.url);
|
|
19793
|
+
const url = new URL('/login', platformEndpoint(env));
|
|
19794
|
+
url.searchParams.set('redirect', hostedInstanceRedirectId(request, env));
|
|
19795
|
+
url.searchParams.set('return_to', requestUrl.pathname + requestUrl.search || '/');
|
|
19796
|
+
return url.toString();
|
|
19797
|
+
}
|
|
19798
|
+
|
|
19799
|
+
function isHtmlNavigationRequest(request) {
|
|
19800
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') return false;
|
|
19801
|
+
return (request.headers.get('Accept') || '').includes('text/html');
|
|
19802
|
+
}
|
|
19803
|
+
|
|
19764
19804
|
// CORS headers for API responses
|
|
19765
19805
|
const CORS_HEADERS = {
|
|
19766
19806
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -19852,6 +19892,21 @@ ${packedThreadRouteCode}
|
|
|
19852
19892
|
}
|
|
19853
19893
|
|
|
19854
19894
|
authContext = authResult;
|
|
19895
|
+
|
|
19896
|
+
if (routePath.startsWith('/api/threads/')) {
|
|
19897
|
+
const threadId = routeMatch.params?.id || routeMatch.params?.threadId;
|
|
19898
|
+
if (threadId) {
|
|
19899
|
+
const agentBuilderId = env.AGENT_BUILDER.idFromName('singleton');
|
|
19900
|
+
const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
|
|
19901
|
+
const thread = await agentBuilder.getThread(threadId);
|
|
19902
|
+
if (!thread) {
|
|
19903
|
+
return addCorsHeaders(Response.json({ error: \`Thread not found: \${threadId}\` }, { status: 404 }));
|
|
19904
|
+
}
|
|
19905
|
+
if (authContext.user.role !== 'admin' && (thread.user_id === null || thread.user_id !== authContext.user.id)) {
|
|
19906
|
+
return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
}
|
|
19855
19910
|
}
|
|
19856
19911
|
|
|
19857
19912
|
let controller = await routeMatch.data();
|
|
@@ -19887,6 +19942,19 @@ ${packedThreadRouteCode}
|
|
|
19887
19942
|
});
|
|
19888
19943
|
}
|
|
19889
19944
|
|
|
19945
|
+
// Hosted browser navigations do not render a local login page. Redirect
|
|
19946
|
+
// anonymous users directly to the platform, where the instance membership is
|
|
19947
|
+
// resolved and returned as a signed handoff token.
|
|
19948
|
+
if (isHostedInstance(env) && isHtmlNavigationRequest(request)) {
|
|
19949
|
+
const authResult = await requireAuth(request, env);
|
|
19950
|
+
if (authResult instanceof Response) {
|
|
19951
|
+
return new Response(null, {
|
|
19952
|
+
status: 302,
|
|
19953
|
+
headers: { Location: platformLoginUrl(request, env) },
|
|
19954
|
+
});
|
|
19955
|
+
}
|
|
19956
|
+
}
|
|
19957
|
+
|
|
19890
19958
|
// Serve UI for all other routes (SPA fallback)
|
|
19891
19959
|
return serveUI(routePath, env);
|
|
19892
19960
|
}
|
|
@@ -20607,7 +20675,7 @@ function isValidUserToken(token) {
|
|
|
20607
20675
|
return token.startsWith("agtuser_") && token.length > 10;
|
|
20608
20676
|
}
|
|
20609
20677
|
function isValidApiKey(key) {
|
|
20610
|
-
return key.startsWith("agtbldr_") && key.length > 10;
|
|
20678
|
+
return key.startsWith("agtbldr_") && key.length > 10 || key.startsWith("sak_live_") && key.length > 10;
|
|
20611
20679
|
}
|
|
20612
20680
|
async function verifySignedToken(signedToken, encryptionKey) {
|
|
20613
20681
|
try {
|
|
@@ -20731,7 +20799,11 @@ async function authenticate(request, env) {
|
|
|
20731
20799
|
user: {
|
|
20732
20800
|
id: user.id,
|
|
20733
20801
|
username: user.username,
|
|
20734
|
-
role: user.role
|
|
20802
|
+
role: user.role,
|
|
20803
|
+
platform_user_id: user.platform_user_id ?? null,
|
|
20804
|
+
email: user.email ?? null,
|
|
20805
|
+
display_name: user.display_name ?? null,
|
|
20806
|
+
avatar_url: user.avatar_url ?? null
|
|
20735
20807
|
},
|
|
20736
20808
|
authType: "session"
|
|
20737
20809
|
};
|
|
@@ -20749,7 +20821,11 @@ async function authenticate(request, env) {
|
|
|
20749
20821
|
user: {
|
|
20750
20822
|
id: user.id,
|
|
20751
20823
|
username: user.username,
|
|
20752
|
-
role: user.role
|
|
20824
|
+
role: user.role,
|
|
20825
|
+
platform_user_id: user.platform_user_id ?? null,
|
|
20826
|
+
email: user.email ?? null,
|
|
20827
|
+
display_name: user.display_name ?? null,
|
|
20828
|
+
avatar_url: user.avatar_url ?? null
|
|
20753
20829
|
},
|
|
20754
20830
|
authType: "api_key"
|
|
20755
20831
|
};
|
|
@@ -23539,9 +23615,9 @@ var DurableThread = class extends DurableObject {
|
|
|
23539
23615
|
* Each migration is run in order, starting from the current version + 1.
|
|
23540
23616
|
*/
|
|
23541
23617
|
async runMigrations(fromVersion) {
|
|
23542
|
-
for (const
|
|
23543
|
-
if (
|
|
23544
|
-
await
|
|
23618
|
+
for (const migration38 of migrations) {
|
|
23619
|
+
if (migration38.version > fromVersion) {
|
|
23620
|
+
await migration38.up(this.ctx.storage.sql);
|
|
23545
23621
|
}
|
|
23546
23622
|
}
|
|
23547
23623
|
}
|
|
@@ -26976,9 +27052,38 @@ var migration36 = {
|
|
|
26976
27052
|
}
|
|
26977
27053
|
};
|
|
26978
27054
|
|
|
27055
|
+
// src/durable-objects/agentbuilder-migrations/0007_platform_identity_replica.ts
|
|
27056
|
+
var migration37 = {
|
|
27057
|
+
version: 7,
|
|
27058
|
+
async up(sql) {
|
|
27059
|
+
sql.exec(`ALTER TABLE users ADD COLUMN platform_user_id TEXT`);
|
|
27060
|
+
sql.exec(`ALTER TABLE users ADD COLUMN email TEXT`);
|
|
27061
|
+
sql.exec(`ALTER TABLE users ADD COLUMN display_name TEXT`);
|
|
27062
|
+
sql.exec(`ALTER TABLE users ADD COLUMN avatar_url TEXT`);
|
|
27063
|
+
sql.exec(`ALTER TABLE users ADD COLUMN instance_role TEXT NOT NULL DEFAULT 'admin' CHECK (instance_role IN ('admin', 'user'))`);
|
|
27064
|
+
sql.exec(`ALTER TABLE users ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
|
|
27065
|
+
sql.exec(`ALTER TABLE users ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
|
|
27066
|
+
sql.exec(`ALTER TABLE users ADD COLUMN replica_updated_at INTEGER`);
|
|
27067
|
+
sql.exec(`ALTER TABLE users ADD COLUMN deleted_at INTEGER`);
|
|
27068
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_platform_user_id ON users(platform_user_id)`);
|
|
27069
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_replica_active ON users(replica_active)`);
|
|
27070
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_users_instance_role ON users(instance_role)`);
|
|
27071
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN platform_key_id TEXT`);
|
|
27072
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN scope TEXT`);
|
|
27073
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN source TEXT NOT NULL DEFAULT 'local' CHECK (source IN ('local', 'standard_agents'))`);
|
|
27074
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_active INTEGER NOT NULL DEFAULT 1`);
|
|
27075
|
+
sql.exec(`ALTER TABLE api_keys ADD COLUMN replica_updated_at INTEGER`);
|
|
27076
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_platform_key_id ON api_keys(platform_key_id)`);
|
|
27077
|
+
sql.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_replica_active ON api_keys(replica_active)`);
|
|
27078
|
+
sql.exec(`
|
|
27079
|
+
INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '7')
|
|
27080
|
+
`);
|
|
27081
|
+
}
|
|
27082
|
+
};
|
|
27083
|
+
|
|
26979
27084
|
// src/durable-objects/agentbuilder-migrations/index.ts
|
|
26980
|
-
var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36];
|
|
26981
|
-
var LATEST_SCHEMA_VERSION2 =
|
|
27085
|
+
var migrations2 = [migration31, migration32, migration33, migration34, migration35, migration36, migration37];
|
|
27086
|
+
var LATEST_SCHEMA_VERSION2 = 7;
|
|
26982
27087
|
|
|
26983
27088
|
// src/utils/crypto.ts
|
|
26984
27089
|
var CryptoUtil = class {
|
|
@@ -27602,9 +27707,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
27602
27707
|
}
|
|
27603
27708
|
}
|
|
27604
27709
|
async runMigrations(fromVersion) {
|
|
27605
|
-
for (const
|
|
27606
|
-
if (
|
|
27607
|
-
await
|
|
27710
|
+
for (const migration38 of migrations2) {
|
|
27711
|
+
if (migration38.version > fromVersion) {
|
|
27712
|
+
await migration38.up(this.ctx.storage.sql);
|
|
27608
27713
|
}
|
|
27609
27714
|
}
|
|
27610
27715
|
}
|
|
@@ -28637,27 +28742,54 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28637
28742
|
// ============================================================
|
|
28638
28743
|
// User Authentication Methods
|
|
28639
28744
|
// ============================================================
|
|
28745
|
+
normalizeReplicaUsername(input, fallback) {
|
|
28746
|
+
const normalized = (input || fallback).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
28747
|
+
return normalized || fallback;
|
|
28748
|
+
}
|
|
28749
|
+
async availableReplicaUsername(candidate, platformUserId) {
|
|
28750
|
+
const existing = await this.ctx.storage.sql.exec(
|
|
28751
|
+
`SELECT id, platform_user_id FROM users WHERE username = ? LIMIT 1`,
|
|
28752
|
+
candidate
|
|
28753
|
+
);
|
|
28754
|
+
const row = existing.toArray()[0];
|
|
28755
|
+
if (!row || row.platform_user_id === platformUserId) {
|
|
28756
|
+
return candidate;
|
|
28757
|
+
}
|
|
28758
|
+
const suffix = platformUserId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "user";
|
|
28759
|
+
const trimmed = candidate.slice(0, Math.max(1, 50 - suffix.length - 1)).replace(/-+$/g, "");
|
|
28760
|
+
return `${trimmed || "sa"}-${suffix}`;
|
|
28761
|
+
}
|
|
28762
|
+
userFromRow(row) {
|
|
28763
|
+
return {
|
|
28764
|
+
id: row.id,
|
|
28765
|
+
username: row.username,
|
|
28766
|
+
password_hash: row.password_hash,
|
|
28767
|
+
role: row.role === "user" ? "user" : "admin",
|
|
28768
|
+
platform_user_id: row.platform_user_id ?? null,
|
|
28769
|
+
email: row.email ?? null,
|
|
28770
|
+
display_name: row.display_name ?? null,
|
|
28771
|
+
avatar_url: row.avatar_url ?? null,
|
|
28772
|
+
source: row.source ?? "local",
|
|
28773
|
+
replica_active: row.replica_active ?? 1,
|
|
28774
|
+
created_at: row.created_at,
|
|
28775
|
+
updated_at: row.updated_at
|
|
28776
|
+
};
|
|
28777
|
+
}
|
|
28640
28778
|
/**
|
|
28641
28779
|
* Get a user by username.
|
|
28642
28780
|
*/
|
|
28643
28781
|
async getUserByUsername(username) {
|
|
28644
28782
|
await this.ensureMigrated();
|
|
28645
28783
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28646
|
-
`SELECT id, username, password_hash,
|
|
28647
|
-
|
|
28784
|
+
`SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
|
|
28785
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28786
|
+
created_at, updated_at
|
|
28787
|
+
FROM users WHERE username = ? AND deleted_at IS NULL AND replica_active != 0`,
|
|
28648
28788
|
username
|
|
28649
28789
|
);
|
|
28650
28790
|
const rows = cursor.toArray();
|
|
28651
28791
|
if (rows.length === 0) return null;
|
|
28652
|
-
|
|
28653
|
-
return {
|
|
28654
|
-
id: row.id,
|
|
28655
|
-
username: row.username,
|
|
28656
|
-
password_hash: row.password_hash,
|
|
28657
|
-
role: row.role,
|
|
28658
|
-
created_at: row.created_at,
|
|
28659
|
-
updated_at: row.updated_at
|
|
28660
|
-
};
|
|
28792
|
+
return this.userFromRow(rows[0]);
|
|
28661
28793
|
}
|
|
28662
28794
|
/**
|
|
28663
28795
|
* Get a user by ID.
|
|
@@ -28665,21 +28797,15 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28665
28797
|
async getUserById(id) {
|
|
28666
28798
|
await this.ensureMigrated();
|
|
28667
28799
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28668
|
-
`SELECT id, username, password_hash,
|
|
28669
|
-
|
|
28800
|
+
`SELECT id, username, password_hash, COALESCE(instance_role, role) AS role,
|
|
28801
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28802
|
+
created_at, updated_at
|
|
28803
|
+
FROM users WHERE id = ? AND deleted_at IS NULL AND replica_active != 0`,
|
|
28670
28804
|
id
|
|
28671
28805
|
);
|
|
28672
28806
|
const rows = cursor.toArray();
|
|
28673
28807
|
if (rows.length === 0) return null;
|
|
28674
|
-
|
|
28675
|
-
return {
|
|
28676
|
-
id: row.id,
|
|
28677
|
-
username: row.username,
|
|
28678
|
-
password_hash: row.password_hash,
|
|
28679
|
-
role: row.role,
|
|
28680
|
-
created_at: row.created_at,
|
|
28681
|
-
updated_at: row.updated_at
|
|
28682
|
-
};
|
|
28808
|
+
return this.userFromRow(rows[0]);
|
|
28683
28809
|
}
|
|
28684
28810
|
/**
|
|
28685
28811
|
* Create a new user.
|
|
@@ -28689,12 +28815,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28689
28815
|
const id = crypto.randomUUID();
|
|
28690
28816
|
const now = Math.floor(Date.now() / 1e3);
|
|
28691
28817
|
await this.ctx.storage.sql.exec(
|
|
28692
|
-
`INSERT INTO users (
|
|
28693
|
-
|
|
28818
|
+
`INSERT INTO users (
|
|
28819
|
+
id, username, password_hash, role, instance_role, platform_user_id,
|
|
28820
|
+
email, display_name, avatar_url, source, replica_active,
|
|
28821
|
+
replica_updated_at, created_at, updated_at
|
|
28822
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
28694
28823
|
id,
|
|
28695
28824
|
params.username,
|
|
28696
28825
|
params.password_hash,
|
|
28826
|
+
"admin",
|
|
28697
28827
|
params.role || "admin",
|
|
28828
|
+
params.platform_user_id ?? null,
|
|
28829
|
+
params.email ?? null,
|
|
28830
|
+
params.display_name ?? null,
|
|
28831
|
+
params.avatar_url ?? null,
|
|
28832
|
+
params.source ?? "local",
|
|
28833
|
+
1,
|
|
28834
|
+
params.source === "standard_agents" ? now : null,
|
|
28698
28835
|
now,
|
|
28699
28836
|
now
|
|
28700
28837
|
);
|
|
@@ -28703,6 +28840,12 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28703
28840
|
username: params.username,
|
|
28704
28841
|
password_hash: params.password_hash,
|
|
28705
28842
|
role: params.role || "admin",
|
|
28843
|
+
platform_user_id: params.platform_user_id ?? null,
|
|
28844
|
+
email: params.email ?? null,
|
|
28845
|
+
display_name: params.display_name ?? null,
|
|
28846
|
+
avatar_url: params.avatar_url ?? null,
|
|
28847
|
+
source: params.source ?? "local",
|
|
28848
|
+
replica_active: 1,
|
|
28706
28849
|
created_at: now,
|
|
28707
28850
|
updated_at: now
|
|
28708
28851
|
};
|
|
@@ -28722,11 +28865,24 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28722
28865
|
*/
|
|
28723
28866
|
async listUsers() {
|
|
28724
28867
|
await this.ensureMigrated();
|
|
28725
|
-
const cursor = await this.ctx.storage.sql.exec(
|
|
28868
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
28869
|
+
`SELECT id, username, COALESCE(instance_role, role) AS role,
|
|
28870
|
+
platform_user_id, email, display_name, avatar_url, source, replica_active,
|
|
28871
|
+
created_at, updated_at
|
|
28872
|
+
FROM users
|
|
28873
|
+
WHERE deleted_at IS NULL
|
|
28874
|
+
ORDER BY created_at DESC`
|
|
28875
|
+
);
|
|
28726
28876
|
return cursor.toArray().map((row) => ({
|
|
28727
28877
|
id: row.id,
|
|
28728
28878
|
username: row.username,
|
|
28729
|
-
role: row.role,
|
|
28879
|
+
role: row.role === "user" ? "user" : "admin",
|
|
28880
|
+
platform_user_id: row.platform_user_id ?? null,
|
|
28881
|
+
email: row.email ?? null,
|
|
28882
|
+
display_name: row.display_name ?? null,
|
|
28883
|
+
avatar_url: row.avatar_url ?? null,
|
|
28884
|
+
source: row.source ?? "local",
|
|
28885
|
+
replica_active: row.replica_active ?? 1,
|
|
28730
28886
|
created_at: row.created_at,
|
|
28731
28887
|
updated_at: row.updated_at
|
|
28732
28888
|
}));
|
|
@@ -28750,14 +28906,42 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28750
28906
|
values.push(params.password_hash);
|
|
28751
28907
|
}
|
|
28752
28908
|
if (params.role !== void 0) {
|
|
28753
|
-
updates.push("
|
|
28909
|
+
updates.push("instance_role = ?");
|
|
28754
28910
|
values.push(params.role);
|
|
28755
28911
|
}
|
|
28912
|
+
if (params.platform_user_id !== void 0) {
|
|
28913
|
+
updates.push("platform_user_id = ?");
|
|
28914
|
+
values.push(params.platform_user_id);
|
|
28915
|
+
}
|
|
28916
|
+
if (params.email !== void 0) {
|
|
28917
|
+
updates.push("email = ?");
|
|
28918
|
+
values.push(params.email);
|
|
28919
|
+
}
|
|
28920
|
+
if (params.display_name !== void 0) {
|
|
28921
|
+
updates.push("display_name = ?");
|
|
28922
|
+
values.push(params.display_name);
|
|
28923
|
+
}
|
|
28924
|
+
if (params.avatar_url !== void 0) {
|
|
28925
|
+
updates.push("avatar_url = ?");
|
|
28926
|
+
values.push(params.avatar_url);
|
|
28927
|
+
}
|
|
28928
|
+
if (params.source !== void 0) {
|
|
28929
|
+
updates.push("source = ?");
|
|
28930
|
+
values.push(params.source);
|
|
28931
|
+
}
|
|
28932
|
+
if (params.replica_active !== void 0) {
|
|
28933
|
+
updates.push("replica_active = ?");
|
|
28934
|
+
values.push(params.replica_active);
|
|
28935
|
+
}
|
|
28756
28936
|
if (updates.length === 0) {
|
|
28757
28937
|
return existing;
|
|
28758
28938
|
}
|
|
28759
28939
|
updates.push("updated_at = ?");
|
|
28760
28940
|
values.push(now);
|
|
28941
|
+
if (params.source === "standard_agents" || params.replica_active !== void 0) {
|
|
28942
|
+
updates.push("replica_updated_at = ?");
|
|
28943
|
+
values.push(now);
|
|
28944
|
+
}
|
|
28761
28945
|
values.push(id);
|
|
28762
28946
|
await this.ctx.storage.sql.exec(
|
|
28763
28947
|
`UPDATE users SET ${updates.join(", ")} WHERE id = ?`,
|
|
@@ -28778,9 +28962,223 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28778
28962
|
`DELETE FROM api_keys WHERE user_id = ?`,
|
|
28779
28963
|
id
|
|
28780
28964
|
);
|
|
28781
|
-
await this.ctx.storage.sql.exec(
|
|
28965
|
+
await this.ctx.storage.sql.exec(
|
|
28966
|
+
`UPDATE users SET deleted_at = ?, replica_active = 0 WHERE id = ?`,
|
|
28967
|
+
Math.floor(Date.now() / 1e3),
|
|
28968
|
+
id
|
|
28969
|
+
);
|
|
28782
28970
|
return true;
|
|
28783
28971
|
}
|
|
28972
|
+
/**
|
|
28973
|
+
* Upsert a platform-replicated identity and return the local user row.
|
|
28974
|
+
*/
|
|
28975
|
+
async upsertPlatformReplicaUser(replica) {
|
|
28976
|
+
await this.ensureMigrated();
|
|
28977
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
28978
|
+
const fallback = this.normalizeReplicaUsername(
|
|
28979
|
+
replica.email?.split("@")[0] ?? null,
|
|
28980
|
+
`sa-${replica.platform_user_id.slice(0, 12)}`
|
|
28981
|
+
);
|
|
28982
|
+
const username = this.normalizeReplicaUsername(
|
|
28983
|
+
replica.username ?? replica.display_name ?? replica.email ?? null,
|
|
28984
|
+
fallback
|
|
28985
|
+
);
|
|
28986
|
+
const existingCursor = await this.ctx.storage.sql.exec(
|
|
28987
|
+
`SELECT id FROM users WHERE platform_user_id = ? LIMIT 1`,
|
|
28988
|
+
replica.platform_user_id
|
|
28989
|
+
);
|
|
28990
|
+
const existing = existingCursor.toArray()[0];
|
|
28991
|
+
const availableUsername = await this.availableReplicaUsername(username, replica.platform_user_id);
|
|
28992
|
+
if (existing) {
|
|
28993
|
+
await this.ctx.storage.sql.exec(
|
|
28994
|
+
`UPDATE users SET
|
|
28995
|
+
username = ?,
|
|
28996
|
+
instance_role = ?,
|
|
28997
|
+
email = ?,
|
|
28998
|
+
display_name = ?,
|
|
28999
|
+
avatar_url = ?,
|
|
29000
|
+
source = 'standard_agents',
|
|
29001
|
+
replica_active = 1,
|
|
29002
|
+
replica_updated_at = ?,
|
|
29003
|
+
deleted_at = NULL,
|
|
29004
|
+
updated_at = ?
|
|
29005
|
+
WHERE id = ?`,
|
|
29006
|
+
availableUsername,
|
|
29007
|
+
replica.role,
|
|
29008
|
+
replica.email ?? null,
|
|
29009
|
+
replica.display_name ?? null,
|
|
29010
|
+
replica.avatar_url ?? null,
|
|
29011
|
+
now,
|
|
29012
|
+
now,
|
|
29013
|
+
existing.id
|
|
29014
|
+
);
|
|
29015
|
+
const updated = await this.getUserById(existing.id);
|
|
29016
|
+
if (!updated) {
|
|
29017
|
+
throw new Error("Failed to load replicated user after update");
|
|
29018
|
+
}
|
|
29019
|
+
return updated;
|
|
29020
|
+
}
|
|
29021
|
+
return this.createUser({
|
|
29022
|
+
username: availableUsername,
|
|
29023
|
+
password_hash: `platform-replica:${crypto.randomUUID()}`,
|
|
29024
|
+
role: replica.role,
|
|
29025
|
+
platform_user_id: replica.platform_user_id,
|
|
29026
|
+
email: replica.email ?? null,
|
|
29027
|
+
display_name: replica.display_name ?? null,
|
|
29028
|
+
avatar_url: replica.avatar_url ?? null,
|
|
29029
|
+
source: "standard_agents"
|
|
29030
|
+
});
|
|
29031
|
+
}
|
|
29032
|
+
/**
|
|
29033
|
+
* Apply a full platform read-replica snapshot for this instance.
|
|
29034
|
+
*/
|
|
29035
|
+
async applyPlatformReplicaSnapshot(snapshot) {
|
|
29036
|
+
await this.ensureMigrated();
|
|
29037
|
+
const activeUserIds = /* @__PURE__ */ new Set();
|
|
29038
|
+
for (const replicaUser of snapshot.users) {
|
|
29039
|
+
const user = await this.upsertPlatformReplicaUser(replicaUser);
|
|
29040
|
+
activeUserIds.add(user.id);
|
|
29041
|
+
}
|
|
29042
|
+
const activeList = Array.from(activeUserIds);
|
|
29043
|
+
if (activeList.length > 0) {
|
|
29044
|
+
const placeholders = activeList.map(() => "?").join(", ");
|
|
29045
|
+
const inactive = await this.ctx.storage.sql.exec(
|
|
29046
|
+
`SELECT id FROM users
|
|
29047
|
+
WHERE source = 'standard_agents'
|
|
29048
|
+
AND replica_active != 0
|
|
29049
|
+
AND id NOT IN (${placeholders})`,
|
|
29050
|
+
...activeList
|
|
29051
|
+
);
|
|
29052
|
+
for (const row of inactive.toArray()) {
|
|
29053
|
+
await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
|
|
29054
|
+
await this.ctx.storage.sql.exec(
|
|
29055
|
+
`UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ? WHERE id = ?`,
|
|
29056
|
+
Math.floor(Date.now() / 1e3),
|
|
29057
|
+
Math.floor(Date.now() / 1e3),
|
|
29058
|
+
Math.floor(Date.now() / 1e3),
|
|
29059
|
+
row.id
|
|
29060
|
+
);
|
|
29061
|
+
}
|
|
29062
|
+
} else {
|
|
29063
|
+
const inactive = await this.ctx.storage.sql.exec(
|
|
29064
|
+
`SELECT id FROM users WHERE source = 'standard_agents' AND replica_active != 0`
|
|
29065
|
+
);
|
|
29066
|
+
for (const row of inactive.toArray()) {
|
|
29067
|
+
await this.ctx.storage.sql.exec(`DELETE FROM sessions WHERE user_id = ?`, row.id);
|
|
29068
|
+
}
|
|
29069
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
29070
|
+
await this.ctx.storage.sql.exec(
|
|
29071
|
+
`UPDATE users SET replica_active = 0, deleted_at = ?, replica_updated_at = ?, updated_at = ?
|
|
29072
|
+
WHERE source = 'standard_agents'`,
|
|
29073
|
+
now2,
|
|
29074
|
+
now2,
|
|
29075
|
+
now2
|
|
29076
|
+
);
|
|
29077
|
+
}
|
|
29078
|
+
const activePlatformKeyIds = /* @__PURE__ */ new Set();
|
|
29079
|
+
const keys = snapshot.api_keys ?? [];
|
|
29080
|
+
for (const key of keys) {
|
|
29081
|
+
const user = key.user_platform_id ? await this.getUserByPlatformUserId(key.user_platform_id) : await this.getFirstReplicaAdminUser();
|
|
29082
|
+
const keyUser = user ?? await this.getFirstReplicaAdminUser();
|
|
29083
|
+
if (!keyUser) continue;
|
|
29084
|
+
activePlatformKeyIds.add(key.id);
|
|
29085
|
+
await this.upsertPlatformReplicaApiKey(key, keyUser.id);
|
|
29086
|
+
}
|
|
29087
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
29088
|
+
if (activePlatformKeyIds.size > 0) {
|
|
29089
|
+
const ids = Array.from(activePlatformKeyIds);
|
|
29090
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
29091
|
+
await this.ctx.storage.sql.exec(
|
|
29092
|
+
`UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
|
|
29093
|
+
WHERE source = 'standard_agents' AND platform_key_id NOT IN (${placeholders})`,
|
|
29094
|
+
now,
|
|
29095
|
+
...ids
|
|
29096
|
+
);
|
|
29097
|
+
} else {
|
|
29098
|
+
await this.ctx.storage.sql.exec(
|
|
29099
|
+
`UPDATE api_keys SET replica_active = 0, replica_updated_at = ?
|
|
29100
|
+
WHERE source = 'standard_agents'`,
|
|
29101
|
+
now
|
|
29102
|
+
);
|
|
29103
|
+
}
|
|
29104
|
+
return { users: activeUserIds.size, api_keys: activePlatformKeyIds.size };
|
|
29105
|
+
}
|
|
29106
|
+
async getUserByPlatformUserId(platformUserId) {
|
|
29107
|
+
await this.ensureMigrated();
|
|
29108
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29109
|
+
`SELECT id FROM users
|
|
29110
|
+
WHERE platform_user_id = ? AND deleted_at IS NULL AND replica_active != 0
|
|
29111
|
+
LIMIT 1`,
|
|
29112
|
+
platformUserId
|
|
29113
|
+
);
|
|
29114
|
+
const row = cursor.toArray()[0];
|
|
29115
|
+
return row ? this.getUserById(row.id) : null;
|
|
29116
|
+
}
|
|
29117
|
+
async getFirstReplicaAdminUser() {
|
|
29118
|
+
await this.ensureMigrated();
|
|
29119
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29120
|
+
`SELECT id FROM users
|
|
29121
|
+
WHERE source = 'standard_agents'
|
|
29122
|
+
AND instance_role = 'admin'
|
|
29123
|
+
AND deleted_at IS NULL
|
|
29124
|
+
AND replica_active != 0
|
|
29125
|
+
ORDER BY created_at ASC
|
|
29126
|
+
LIMIT 1`
|
|
29127
|
+
);
|
|
29128
|
+
const row = cursor.toArray()[0];
|
|
29129
|
+
return row ? this.getUserById(row.id) : null;
|
|
29130
|
+
}
|
|
29131
|
+
async upsertPlatformReplicaApiKey(key, userId) {
|
|
29132
|
+
await this.ensureMigrated();
|
|
29133
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
29134
|
+
const existing = await this.ctx.storage.sql.exec(
|
|
29135
|
+
`SELECT id FROM api_keys WHERE platform_key_id = ? LIMIT 1`,
|
|
29136
|
+
key.id
|
|
29137
|
+
);
|
|
29138
|
+
const row = existing.toArray()[0];
|
|
29139
|
+
const name = key.name || `Standard Agents ${key.key_prefix}`;
|
|
29140
|
+
const lastFive = key.last_five || key.key_prefix.slice(-5);
|
|
29141
|
+
if (row) {
|
|
29142
|
+
await this.ctx.storage.sql.exec(
|
|
29143
|
+
`UPDATE api_keys SET
|
|
29144
|
+
name = ?,
|
|
29145
|
+
key_hash = ?,
|
|
29146
|
+
key_prefix = ?,
|
|
29147
|
+
last_five = ?,
|
|
29148
|
+
user_id = ?,
|
|
29149
|
+
scope = ?,
|
|
29150
|
+
source = 'standard_agents',
|
|
29151
|
+
replica_active = 1,
|
|
29152
|
+
replica_updated_at = ?
|
|
29153
|
+
WHERE id = ?`,
|
|
29154
|
+
name,
|
|
29155
|
+
key.key_hash,
|
|
29156
|
+
key.key_prefix,
|
|
29157
|
+
lastFive,
|
|
29158
|
+
userId,
|
|
29159
|
+
key.scope ?? null,
|
|
29160
|
+
now,
|
|
29161
|
+
row.id
|
|
29162
|
+
);
|
|
29163
|
+
return;
|
|
29164
|
+
}
|
|
29165
|
+
await this.ctx.storage.sql.exec(
|
|
29166
|
+
`INSERT INTO api_keys (
|
|
29167
|
+
id, name, key_hash, key_prefix, last_five, user_id, created_at,
|
|
29168
|
+
platform_key_id, scope, source, replica_active, replica_updated_at
|
|
29169
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'standard_agents', 1, ?)`,
|
|
29170
|
+
`platform:${key.id}`,
|
|
29171
|
+
name,
|
|
29172
|
+
key.key_hash,
|
|
29173
|
+
key.key_prefix,
|
|
29174
|
+
lastFive,
|
|
29175
|
+
userId,
|
|
29176
|
+
key.created_at ?? now,
|
|
29177
|
+
key.id,
|
|
29178
|
+
key.scope ?? null,
|
|
29179
|
+
now
|
|
29180
|
+
);
|
|
29181
|
+
}
|
|
28784
29182
|
// ============================================================
|
|
28785
29183
|
// Session Methods
|
|
28786
29184
|
// ============================================================
|
|
@@ -28809,8 +29207,13 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28809
29207
|
await this.ensureMigrated();
|
|
28810
29208
|
const now = Math.floor(Date.now() / 1e3);
|
|
28811
29209
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28812
|
-
`SELECT user_id, expires_at
|
|
28813
|
-
|
|
29210
|
+
`SELECT sessions.user_id, sessions.expires_at
|
|
29211
|
+
FROM sessions
|
|
29212
|
+
JOIN users ON users.id = sessions.user_id
|
|
29213
|
+
WHERE sessions.token_hash = ?
|
|
29214
|
+
AND sessions.expires_at > ?
|
|
29215
|
+
AND users.deleted_at IS NULL
|
|
29216
|
+
AND users.replica_active != 0`,
|
|
28814
29217
|
tokenHash,
|
|
28815
29218
|
now
|
|
28816
29219
|
);
|
|
@@ -28867,14 +29270,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28867
29270
|
*/
|
|
28868
29271
|
async validateApiKey(keyHash) {
|
|
28869
29272
|
await this.ensureMigrated();
|
|
28870
|
-
const cursor = await this.ctx.storage.sql.exec(
|
|
29273
|
+
const cursor = await this.ctx.storage.sql.exec(
|
|
29274
|
+
`SELECT api_keys.id, api_keys.user_id
|
|
29275
|
+
FROM api_keys
|
|
29276
|
+
JOIN users ON users.id = api_keys.user_id
|
|
29277
|
+
WHERE api_keys.key_hash = ?
|
|
29278
|
+
AND api_keys.replica_active != 0
|
|
29279
|
+
AND users.deleted_at IS NULL
|
|
29280
|
+
AND users.replica_active != 0`,
|
|
29281
|
+
keyHash
|
|
29282
|
+
);
|
|
28871
29283
|
const rows = cursor.toArray();
|
|
28872
29284
|
if (rows.length === 0) {
|
|
28873
29285
|
return null;
|
|
28874
29286
|
}
|
|
28875
29287
|
const now = Math.floor(Date.now() / 1e3);
|
|
28876
29288
|
await this.ctx.storage.sql.exec(
|
|
28877
|
-
`UPDATE api_keys SET last_used_at = ? WHERE key_hash =
|
|
29289
|
+
`UPDATE api_keys SET last_used_at = ? WHERE key_hash = ? AND replica_active != 0`,
|
|
28878
29290
|
now,
|
|
28879
29291
|
keyHash
|
|
28880
29292
|
);
|
|
@@ -28886,8 +29298,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
|
|
|
28886
29298
|
async listApiKeys(userId) {
|
|
28887
29299
|
await this.ensureMigrated();
|
|
28888
29300
|
const cursor = await this.ctx.storage.sql.exec(
|
|
28889
|
-
`SELECT id, name, key_prefix, last_five, created_at, last_used_at
|
|
28890
|
-
FROM api_keys
|
|
29301
|
+
`SELECT id, name, key_prefix, last_five, source, created_at, last_used_at
|
|
29302
|
+
FROM api_keys
|
|
29303
|
+
WHERE user_id = ? AND replica_active != 0
|
|
29304
|
+
ORDER BY created_at DESC`,
|
|
28891
29305
|
userId
|
|
28892
29306
|
);
|
|
28893
29307
|
return cursor.toArray();
|