@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/plugin.js CHANGED
@@ -7205,8 +7205,10 @@ import { isThreadEndpoint } from "@standardagents/spec";
7205
7205
  const PUBLIC_ROUTES = [
7206
7206
  '/api/auth/bootstrap',
7207
7207
  '/api/auth/login',
7208
- '/api/auth/bootstrap',
7209
7208
  '/api/auth/config',
7209
+ '/api/auth/platform-replica',
7210
+ '/api/auth/sa/start', // Login with Standard Agents (OAuth) \u2014 unauthenticated entry
7211
+ '/api/auth/sa/callback', // OAuth callback (sets the session cookie)
7210
7212
  '/api/config',
7211
7213
  '/api/auth/oauth/github',
7212
7214
  '/api/auth/oauth/google',
@@ -7219,15 +7221,31 @@ const PUBLIC_ROUTES = [
7219
7221
  '/api/hooks' // Hook metadata is safe to expose publicly
7220
7222
  ];
7221
7223
 
7224
+ // True when the platform deployed this instance (injects STANDARD_AGENTS_HOSTED).
7225
+ // Hosted instances are internet-reachable and multi-tenant, so the thread data
7226
+ // API and event/stream WebSockets must NOT be anonymously public the way they
7227
+ // are in single-user local dev \u2014 they require a session (admin) or API key (SDK).
7228
+ function isHostedInstance(env) {
7229
+ const value = env && env.STANDARD_AGENTS_HOSTED;
7230
+ if (typeof value === 'string') {
7231
+ const trimmed = value.trim().toLowerCase();
7232
+ return trimmed !== '' && trimmed !== '0' && trimmed !== 'false';
7233
+ }
7234
+ return Boolean(value);
7235
+ }
7236
+
7222
7237
  // Check if a route is public (no auth required)
7223
- function isPublicRoute(routePath) {
7238
+ function isPublicRoute(routePath, hosted) {
7224
7239
  // Exact match for auth routes
7225
7240
  if (PUBLIC_ROUTES.includes(routePath)) {
7226
7241
  return true;
7227
7242
  }
7228
7243
 
7229
- // Thread routes are always public
7230
- if (routePath.startsWith('/api/threads/') || routePath === '/api/threads') {
7244
+ // Thread routes (REST + message/log stream WebSockets) are public in local
7245
+ // single-user dev, but on a hosted deployment they require auth \u2014 requireAuth
7246
+ // accepts the admin's session (cookie or token) or the SDK's API key, so this
7247
+ // only blocks anonymous access to another tenant's threads/messages/files.
7248
+ if (!hosted && (routePath.startsWith('/api/threads/') || routePath === '/api/threads')) {
7231
7249
  return true;
7232
7250
  }
7233
7251
 
@@ -7236,16 +7254,25 @@ function isPublicRoute(routePath) {
7236
7254
  return true;
7237
7255
  }
7238
7256
 
7239
- // Platform proxy routes handle their own auth.
7257
+ // Platform proxy routes handle their own auth in local dev only.
7258
+ if (hosted && (routePath.startsWith('/api/platform/') || routePath === '/api/platform')) {
7259
+ return false;
7260
+ }
7240
7261
  if (routePath.startsWith('/api/platform/') || routePath === '/api/platform') {
7241
7262
  return true;
7242
7263
  }
7243
7264
 
7244
- // Platform session proxy and auth bridge handle auth via platform cookies.
7265
+ // Platform session proxy and auth bridge are local-dev helpers only.
7266
+ if (hosted && (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session')) {
7267
+ return false;
7268
+ }
7245
7269
  if (routePath.startsWith('/api/platform-session/') || routePath === '/api/platform-session') {
7246
7270
  return true;
7247
7271
  }
7248
7272
 
7273
+ if (hosted && (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth')) {
7274
+ return false;
7275
+ }
7249
7276
  if (routePath.startsWith('/api/platform-auth/') || routePath === '/api/platform-auth') {
7250
7277
  return true;
7251
7278
  }
@@ -7253,6 +7280,36 @@ function isPublicRoute(routePath) {
7253
7280
  return false;
7254
7281
  }
7255
7282
 
7283
+ function platformEndpoint(env) {
7284
+ const configured =
7285
+ env && (env.PLATFORM_ENDPOINT || env.STANDARD_AGENTS_PLATFORM_URL || env.PLATFORM_URL || env.STANDARD_AGENTS_PUBLIC_URL);
7286
+ if (typeof configured === 'string' && configured.trim()) {
7287
+ return configured.trim().replace(/\\/+$/, '');
7288
+ }
7289
+ return 'https://platform.standardagents.ai';
7290
+ }
7291
+
7292
+ function hostedInstanceRedirectId(request, env) {
7293
+ const configured = env && (env.STANDARD_AGENTS_PROJECT_ID || env.STANDARD_AGENTS_INSTANCE_ID || env.STANDARD_AGENTS_INSTANCE_SUBDOMAIN);
7294
+ if (typeof configured === 'string' && configured.trim()) {
7295
+ return configured.trim();
7296
+ }
7297
+ return new URL(request.url).hostname;
7298
+ }
7299
+
7300
+ function platformLoginUrl(request, env) {
7301
+ const requestUrl = new URL(request.url);
7302
+ const url = new URL('/login', platformEndpoint(env));
7303
+ url.searchParams.set('redirect', hostedInstanceRedirectId(request, env));
7304
+ url.searchParams.set('return_to', requestUrl.pathname + requestUrl.search || '/');
7305
+ return url.toString();
7306
+ }
7307
+
7308
+ function isHtmlNavigationRequest(request) {
7309
+ if (request.method !== 'GET' && request.method !== 'HEAD') return false;
7310
+ return (request.headers.get('Accept') || '').includes('text/html');
7311
+ }
7312
+
7256
7313
  // CORS headers for API responses
7257
7314
  const CORS_HEADERS = {
7258
7315
  "Access-Control-Allow-Origin": "*",
@@ -7329,7 +7386,7 @@ ${packedThreadRouteCode}
7329
7386
 
7330
7387
  if (routeMatch) {
7331
7388
  // Check if authentication is required for this route
7332
- const publicRoute = isPublicRoute(routePath);
7389
+ const publicRoute = isPublicRoute(routePath, isHostedInstance(env));
7333
7390
  const isApiRoute = routePath.startsWith('/api/');
7334
7391
 
7335
7392
  let authContext = null;
@@ -7344,6 +7401,21 @@ ${packedThreadRouteCode}
7344
7401
  }
7345
7402
 
7346
7403
  authContext = authResult;
7404
+
7405
+ if (routePath.startsWith('/api/threads/')) {
7406
+ const threadId = routeMatch.params?.id || routeMatch.params?.threadId;
7407
+ if (threadId) {
7408
+ const agentBuilderId = env.AGENT_BUILDER.idFromName('singleton');
7409
+ const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
7410
+ const thread = await agentBuilder.getThread(threadId);
7411
+ if (!thread) {
7412
+ return addCorsHeaders(Response.json({ error: \`Thread not found: \${threadId}\` }, { status: 404 }));
7413
+ }
7414
+ if (authContext.user.role !== 'admin' && (thread.user_id === null || thread.user_id !== authContext.user.id)) {
7415
+ return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
7416
+ }
7417
+ }
7418
+ }
7347
7419
  }
7348
7420
 
7349
7421
  let controller = await routeMatch.data();
@@ -7379,6 +7451,19 @@ ${packedThreadRouteCode}
7379
7451
  });
7380
7452
  }
7381
7453
 
7454
+ // Hosted browser navigations do not render a local login page. Redirect
7455
+ // anonymous users directly to the platform, where the instance membership is
7456
+ // resolved and returned as a signed handoff token.
7457
+ if (isHostedInstance(env) && isHtmlNavigationRequest(request)) {
7458
+ const authResult = await requireAuth(request, env);
7459
+ if (authResult instanceof Response) {
7460
+ return new Response(null, {
7461
+ status: 302,
7462
+ headers: { Location: platformLoginUrl(request, env) },
7463
+ });
7464
+ }
7465
+ }
7466
+
7382
7467
  // Serve UI for all other routes (SPA fallback)
7383
7468
  return serveUI(routePath, env);
7384
7469
  }