@standardagents/builder 0.37.0 → 0.37.2

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.
Files changed (40) hide show
  1. package/dist/built-in-routes.js +1168 -905
  2. package/dist/built-in-routes.js.map +1 -1
  3. package/dist/client/{ApiKeysView-CTfDKe47.js → ApiKeysView-BhuT3pIF.js} +1 -1
  4. package/dist/client/{CenteredContentView-BrVntknT.js → CenteredContentView-CfgX3Ao3.js} +1 -1
  5. package/dist/client/{CompositionView-BT-1sFij.js → CompositionView-B9FfWMHd.js} +1 -1
  6. package/dist/client/{ConfirmDialog.vue_vue_type_script_setup_true_lang-yyjbXnqu.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-BqwwsXWJ.js} +1 -1
  7. package/dist/client/{CopyButton.vue_vue_type_script_setup_true_lang-BWgZbcol.js → CopyButton.vue_vue_type_script_setup_true_lang-Cqm3Vt3E.js} +1 -1
  8. package/dist/client/{DataTable.vue_vue_type_script_setup_true_lang-DP_WTiEu.js → DataTable.vue_vue_type_script_setup_true_lang-CA5qgsty.js} +1 -1
  9. package/dist/client/{JsonViewer-DY4s92Td.js → JsonViewer-ByQKCShF.js} +1 -1
  10. package/dist/client/{LoginView-DHYLpkb0.js → LoginView-J4qtHbQK.js} +1 -1
  11. package/dist/client/{MarketplaceView-Bcil0ES3.js → MarketplaceView-CshdWDqp.js} +1 -1
  12. package/dist/client/{Modal.vue_vue_type_script_setup_true_lang-BJgJZ56X.js → Modal.vue_vue_type_script_setup_true_lang-DKdB9A8Z.js} +1 -1
  13. package/dist/client/{ModelModal.vue_vue_type_script_setup_true_lang-BXTP0K4Z.js → ModelModal.vue_vue_type_script_setup_true_lang-BAxaBZsx.js} +1 -1
  14. package/dist/client/{ModelsView-_RY4gQ79.js → ModelsView-B_n306yk.js} +1 -1
  15. package/dist/client/{PromptEditView-D8kyGi30.js → PromptEditView-C85qRgsE.js} +1 -1
  16. package/dist/client/{PromptModal-DSPIuleV.js → PromptModal-PNpoJfqD.js} +1 -1
  17. package/dist/client/{PromptsView-DPvBDPJf.js → PromptsView-BKnfThIF.js} +1 -1
  18. package/dist/client/{ProvidersView-PaPnME7I.js → ProvidersView-Df41VhHD.js} +1 -1
  19. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-CtIBWHUR.js +22 -0
  20. package/dist/client/{ThreadsView-BW4SYvFN.js → ThreadsView-sjo5Rrcm.js} +1 -1
  21. package/dist/client/{ToolsView-xsIVTHh0.js → ToolsView-C0MH9x3N.js} +1 -1
  22. package/dist/client/{UsersView-CSXdBGA-.js → UsersView--TQiJYP7.js} +1 -1
  23. package/dist/client/{VariablesView-Bl5S2ZDn.js → VariablesView-BO3UljZu.js} +1 -1
  24. package/dist/client/assets/{ThreadInspectorPane-CnbmdCdH.css → ThreadInspectorPane-D9ZMzSTn.css} +1 -1
  25. package/dist/client/{index-Df4DTwgs.js → index-BgZnQr8X.js} +2 -2
  26. package/dist/client/index.html +1 -1
  27. package/dist/{index-yV9JL4r3.d.ts → index-DceFbLG9.d.ts} +45 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +958 -815
  30. package/dist/index.js.map +1 -1
  31. package/dist/packing.js +213 -161
  32. package/dist/packing.js.map +1 -1
  33. package/dist/plugin.js +535 -505
  34. package/dist/plugin.js.map +1 -1
  35. package/dist/runtime.d.ts +110 -38
  36. package/dist/runtime.js +174 -21
  37. package/dist/runtime.js.map +1 -1
  38. package/dist/test.d.ts +1 -1
  39. package/package.json +5 -5
  40. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-BAUlrnb6.js +0 -23
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-yV9JL4r3.js';
2
- export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-yV9JL4r3.js';
1
+ import { i as AuthContext, e as ThreadEnv, v as Agent, x as ThreadMetadata, y as ToolResult, M as Message, n as ClientToolRequest, o as ClientToolResponse, p as ClientStreamRequest, q as ClientStreamResponse, G as FileRecord, F as FlowState, K as FileStats, J as GrepResult, H as AttachmentRef, Q as MessageContent, E as Env, w as ThreadInstance } from './index-DceFbLG9.js';
2
+ export { A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, B as Controller, a as ControllerContext, z as FlowResult, O as ImageContentPart, I as ImageMetadata, L as LLMResponse, P as MultimodalContent, R as RequestContext, S as StorageBackend, D as TelemetryEvent, N as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as authenticate, j as callClient, k as callClientStream, c as createThreadEndpointHandler, m as decodeStreamChunk, d as defineController, l as encodeStreamChunk, h as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-DceFbLG9.js';
3
3
  import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, CredentialGrant, SkillMetadata, SkillFileInput, ModelDefinition as ModelDefinition$1, ToolArgs, PromptTextPart, PromptEnvPart, VariableDefinition, SubpromptConfig as SubpromptConfig$1, PromptToolConfig as PromptToolConfig$1, SubagentToolConfig as SubagentToolConfig$1, ReasoningConfig, SideConfig as SideConfig$1, LLMProviderInterface, ContentPart, TextPart, ImagePart, FilePart, ThreadState, NamespaceContext, DefinitionLoader } from '@standardagents/spec';
4
4
  export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ModelCapabilities as ProviderModelCapabilities, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel } from '@standardagents/spec';
5
5
  import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
@@ -94,46 +94,39 @@ declare global {
94
94
  }
95
95
 
96
96
  /**
97
- * Authentication middleware for protecting API routes
97
+ * Permissions utility for role-based access control
98
98
  *
99
- * Supports multiple authentication types:
100
- * - Super admin token (signed with ENCRYPTION_KEY)
101
- * - User session token (validated against DurableAgentBuilder)
102
- * - API key (validated against DurableAgentBuilder)
103
- */
104
- interface AuthUser {
105
- id: string;
106
- username: string;
107
- /** 'admin' | 'user' | 'api' (API-only: normal API access, no UI, no logs). */
108
- role: string;
109
- /**
110
- * Explicit permission grants (e.g. 'logs'). `null`/absent = legacy row with
111
- * no explicit set — see {@link hasPermission} for the fallback rules.
112
- */
113
- permissions?: string[] | null;
114
- platform_user_id?: string | null;
115
- email?: string | null;
116
- display_name?: string | null;
117
- avatar_url?: string | null;
118
- }
119
- interface AuthContext {
120
- user: AuthUser;
121
- authType: 'super_admin' | 'session' | 'api_key';
122
- }
123
- /**
124
- * Authenticate a request using various token types
99
+ * Current behavior:
100
+ * - Admin role can do everything (early return true)
101
+ * - Other roles only get baseline access (own threads)
102
+ *
103
+ * The `can()` function checks arbitrary permission strings.
104
+ * Currently, non-admin users have no explicit permissions,
105
+ * meaning they rely on ownership checks for access.
125
106
  */
126
- declare function authenticate(request: Request, env: any): Promise<AuthContext | null>;
107
+
127
108
  /**
128
- * Middleware to require authentication
129
- * Returns 401 if not authenticated
109
+ * Check if a user can access a specific thread
110
+ *
111
+ * @param auth - The authenticated user context
112
+ * @param thread - The thread to check access for (must have user_id)
113
+ * @returns true if the user can access the thread
130
114
  */
131
- declare function requireAuth(request: Request, env: any): Promise<AuthContext | Response>;
115
+ declare function canAccessThread(auth: AuthContext, thread: {
116
+ user_id: string | null;
117
+ }): boolean;
118
+
119
+ /** Central API exposure policy shared by the generated Worker router and tests. */
120
+ declare function isHostedInstance(env: Record<string, unknown> | undefined): boolean;
132
121
  /**
133
- * Middleware to require admin role
134
- * Returns 401 if not authenticated, 403 if not admin
122
+ * Local-only conveniences are enabled by the request destination, not merely
123
+ * by the absence of a deployment flag. This makes an incorrectly configured
124
+ * internet-facing Worker fail closed while preserving localhost and OrbStack.
135
125
  */
136
- declare function requireAdmin(request: Request, env: any): Promise<AuthContext | Response>;
126
+ declare function isTrustedLocalDevelopmentRequest(request: Request, env: Record<string, unknown> | undefined): boolean;
127
+ declare function isPublicApiRoute(routePath: string, request: Request, env: Record<string, unknown> | undefined): boolean;
128
+ /** Instance-global authoring and package-management surfaces are admin-only. */
129
+ declare function isAdminOnlyApiRoute(routePath: string, method?: string): boolean;
137
130
 
138
131
  type AlarmQueueStatus = "pending" | "processing" | "completed" | "failed" | "agent_canceled" | "user_canceled";
139
132
  type EffectCancelSource = "agent" | "user";
@@ -1938,6 +1931,12 @@ interface ThreadRegistryEntry {
1938
1931
  */
1939
1932
  last_message_at?: number | null;
1940
1933
  }
1934
+ /**
1935
+ * Safe registry projection carried by system events. Thread env and arbitrary
1936
+ * properties are intentionally excluded: the stream is an invalidation/list
1937
+ * surface, not a second transport for thread configuration or secrets.
1938
+ */
1939
+ type ThreadEventPayload = Pick<ThreadRegistryEntry, 'id' | 'agent_name' | 'origin_agent_name' | 'user_id' | 'tags' | 'parent' | 'terminated' | 'created_at' | 'last_activity_at' | 'executing' | 'last_message_preview' | 'last_message_at'>;
1941
1940
  interface PendingSubagentAttachmentRef {
1942
1941
  path: string;
1943
1942
  name?: string;
@@ -2045,6 +2044,77 @@ interface Provider$1 {
2045
2044
  sdk: string;
2046
2045
  api_key: string;
2047
2046
  }
2047
+ /**
2048
+ * Lightweight agent metadata included with real-time thread events.
2049
+ */
2050
+ interface ThreadEventAgent {
2051
+ name: string;
2052
+ title: string;
2053
+ type: string;
2054
+ description?: string;
2055
+ icon?: string;
2056
+ }
2057
+ /**
2058
+ * DurableAgentBuilder - The root Durable Object for AgentBuilder.
2059
+ *
2060
+ * This is a singleton DO that stores:
2061
+ * - Thread registry (maps thread IDs to agent names)
2062
+ * - User authentication data
2063
+ * - Provider credentials (API keys for LLM providers)
2064
+ * - Edit lock state for coordinated edits
2065
+ *
2066
+ * ## Usage
2067
+ *
2068
+ * Users must extend this class to provide virtual module access:
2069
+ *
2070
+ * ```typescript
2071
+ * import { DurableAgentBuilder } from '@standardagents/builder/runtime';
2072
+ * import { models } from 'virtual:@standardagents-models';
2073
+ * import { prompts } from 'virtual:@standardagents-prompts';
2074
+ * import { agents } from 'virtual:@standardagents-agents';
2075
+ *
2076
+ * export class AgentBuilder extends DurableAgentBuilder {
2077
+ * models() { return models; }
2078
+ * prompts() { return prompts; }
2079
+ * agents() { return agents; }
2080
+ * }
2081
+ * ```
2082
+ */
2083
+ /**
2084
+ * Event types that can be emitted via WebSocket
2085
+ */
2086
+ type AgentBuilderEvent = {
2087
+ type: 'thread_created';
2088
+ thread: ThreadEventPayload & {
2089
+ agent?: ThreadEventAgent;
2090
+ };
2091
+ } | {
2092
+ type: 'thread_updated';
2093
+ thread: ThreadEventPayload & {
2094
+ agent?: ThreadEventAgent;
2095
+ };
2096
+ } | {
2097
+ type: 'thread_deleted';
2098
+ threadId: string;
2099
+ }
2100
+ /**
2101
+ * Lightweight activity signal. Kept separate from `thread_updated` because it
2102
+ * fires on every execution start/stop: re-broadcasting the whole registry
2103
+ * entry would decrypt thread env on each transition for data that never
2104
+ * changed.
2105
+ */
2106
+ | {
2107
+ type: 'thread_activity';
2108
+ threadId: string;
2109
+ /** Present only when this event actually wrote the field: a message-only
2110
+ * recency stamp omits last_activity_at, and `executing` rides only on a
2111
+ * real transition — clients must leave their current value alone for
2112
+ * absent fields. */
2113
+ last_activity_at?: number;
2114
+ executing?: boolean;
2115
+ last_message_preview?: string | null;
2116
+ last_message_at?: number | null;
2117
+ };
2048
2118
  /** Sort orders supported by `listThreads`. */
2049
2119
  type ThreadListSort = 'activity' | 'created' | 'oldest';
2050
2120
  declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv> extends DurableObject<Env> {
@@ -2568,9 +2638,11 @@ declare class DurableAgentBuilder<Env extends AgentBuilderEnv = AgentBuilderEnv>
2568
2638
  */
2569
2639
  broadcastThreadUpdated(threadId: string): Promise<void>;
2570
2640
  /**
2571
- * Broadcast an event to all connected WebSocket clients
2641
+ * Broadcast an event only to its owner or an explicitly all-scoped admin.
2572
2642
  */
2573
2643
  private broadcastEvent;
2644
+ /** Resolve a thread owner without decrypting its env or loading the row. */
2645
+ private getThreadOwnerId;
2574
2646
  /**
2575
2647
  * WebSocket Hibernation API handler for incoming messages
2576
2648
  * Called when a message is received on a hibernated WebSocket
@@ -4191,4 +4263,4 @@ declare function createNamespaceContext(agentDef: {
4191
4263
  */
4192
4264
  declare function isQualifiedName(name: string): boolean;
4193
4265
 
4194
- export { Agent, type AgentBuilderEnv, type AgentDefinition, AttachmentRef, type AuthContext, type AuthUser, type BroadcastOptions, ClientStreamRequest, ClientStreamResponse, ClientToolRequest, ClientToolResponse, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, Env, FileRecord, FileStats, FlowState, FlowStateSdk, type FlowStateWithSdk, GrepResult, type HubClientInfo, type ImageContextConfig, type InjectMessageOptions$1 as InjectMessageOptions, type Provider as LLMProviderInterface, Message, MessageContent, type ModelDefinition, NamespaceResolutionError, NamespacedRegistry, type PromptContent, type PromptDefinition, type PromptIncludePart, type PromptPart, type PromptToolConfig, type Provider$1 as Provider, type ProviderContentPart, type ProviderFilePart, type ProviderImagePart, ProviderRegistry, type ProviderTextPart, type SessionToolBinding, type SessionToolConfig, type SideConfig, type SubagentToolConfig, type SubpromptConfig, ThreadEnv, ThreadInstance, ThreadMetadata, type ThreadRegistryEntry, ThreadWatchdog, type ThreadWatchdogState, type ToolConfig, ToolResult, type UpdateThreadParams, type User, type WatchdogCheckin, type WatchdogProbeResult, authenticate, buildImageDescription, cat, createNamespaceContext, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage };
4266
+ export { Agent, type AgentBuilderEnv, type AgentBuilderEvent, type AgentDefinition, AttachmentRef, AuthContext, type BroadcastOptions, ClientStreamRequest, ClientStreamResponse, ClientToolRequest, ClientToolResponse, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, Env, FileRecord, FileStats, FlowState, FlowStateSdk, type FlowStateWithSdk, GrepResult, type HubClientInfo, type ImageContextConfig, type InjectMessageOptions$1 as InjectMessageOptions, type Provider as LLMProviderInterface, Message, MessageContent, type ModelDefinition, NamespaceResolutionError, NamespacedRegistry, type PromptContent, type PromptDefinition, type PromptIncludePart, type PromptPart, type PromptToolConfig, type Provider$1 as Provider, type ProviderContentPart, type ProviderFilePart, type ProviderImagePart, ProviderRegistry, type ProviderTextPart, type SessionToolBinding, type SessionToolConfig, type SideConfig, type SubagentToolConfig, type SubpromptConfig, ThreadEnv, type ThreadEventPayload, ThreadInstance, ThreadMetadata, type ThreadRegistryEntry, ThreadWatchdog, type ThreadWatchdogState, type ToolConfig, ToolResult, type UpdateThreadParams, type User, type WatchdogCheckin, type WatchdogProbeResult, buildImageDescription, canAccessThread, cat, createNamespaceContext, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage };
package/dist/runtime.js CHANGED
@@ -13710,7 +13710,7 @@ var init_ThreadStateImpl = __esm({
13710
13710
  const toolCallId = this._flowState?.currentToolCall?.id;
13711
13711
  const canPark = !!(toolCallId && this._executionState && typeof instance.dispatchClientTool === "function");
13712
13712
  const side = this._flowState?.currentSide === "b" ? "b" : "a";
13713
- const mustParkForApproval = request.requestPermission != null && await this.forwardedCallNeedsApproval(request);
13713
+ const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
13714
13714
  if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
13715
13715
  await instance.dispatchClientTool(request, toolCallId, side);
13716
13716
  const getDelivered = instance.getDeliveredClientToolResult;
@@ -14854,6 +14854,84 @@ async function requireAdmin(request, env) {
14854
14854
  return result;
14855
14855
  }
14856
14856
 
14857
+ // src/utils/permissions.ts
14858
+ function canAccessThread(auth, thread) {
14859
+ if (auth.user.role === "admin") {
14860
+ return true;
14861
+ }
14862
+ if (thread.user_id === null) {
14863
+ return false;
14864
+ }
14865
+ return thread.user_id === auth.user.id;
14866
+ }
14867
+
14868
+ // src/routing/access-policy.ts
14869
+ var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
14870
+ "/api/auth/bootstrap",
14871
+ "/api/auth/login",
14872
+ "/api/auth/config",
14873
+ "/api/auth/brolly-control",
14874
+ "/api/auth/platform-cleanup",
14875
+ "/api/auth/platform-replica",
14876
+ "/api/auth/sa/start",
14877
+ "/api/auth/sa/callback",
14878
+ "/api/config",
14879
+ "/api/auth/oauth/github",
14880
+ "/api/auth/oauth/google",
14881
+ "/api/auth/oauth/github/callback",
14882
+ "/api/auth/oauth/google/callback",
14883
+ "/api/hooks"
14884
+ ]);
14885
+ function isProviderIconRoute(routePath) {
14886
+ return /^\/api\/providers\/[^/]+\/icon$/.test(routePath);
14887
+ }
14888
+ function isHostedInstance(env) {
14889
+ const value = env?.STANDARD_AGENTS_HOSTED;
14890
+ if (typeof value === "string") {
14891
+ const normalized = value.trim().toLowerCase();
14892
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
14893
+ }
14894
+ return Boolean(value);
14895
+ }
14896
+ function isTrustedLocalDevelopmentRequest(request, env) {
14897
+ if (isHostedInstance(env)) return false;
14898
+ let hostname;
14899
+ try {
14900
+ hostname = new URL(request.url).hostname.toLowerCase();
14901
+ } catch {
14902
+ return false;
14903
+ }
14904
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost") || hostname.endsWith(".orb.local");
14905
+ }
14906
+ function isPublicApiRoute(routePath, request, env) {
14907
+ if (PUBLIC_ROUTES.has(routePath)) return true;
14908
+ if (routePath === "/api/auth/device/start" || routePath.startsWith("/api/auth/device/")) {
14909
+ return true;
14910
+ }
14911
+ if (isProviderIconRoute(routePath)) {
14912
+ return true;
14913
+ }
14914
+ if (!isTrustedLocalDevelopmentRequest(request, env)) return false;
14915
+ return routePath === "/api/threads" || routePath.startsWith("/api/threads/") || routePath === "/api/platform" || routePath.startsWith("/api/platform/") || routePath === "/api/platform-session" || routePath.startsWith("/api/platform-session/") || routePath === "/api/platform-auth" || routePath.startsWith("/api/platform-auth/");
14916
+ }
14917
+ function isAdminOnlyApiRoute(routePath, method = "GET") {
14918
+ if (routePath === "/api/skills" || routePath.startsWith("/api/skills/")) return true;
14919
+ if (routePath === "/api/pack" || routePath.startsWith("/api/pack/")) return true;
14920
+ if (routePath === "/api/unpack" || routePath.startsWith("/api/unpack/")) return true;
14921
+ if (routePath === "/api/prompts" || routePath.startsWith("/api/prompts/")) return true;
14922
+ if (routePath === "/api/tools" || routePath.startsWith("/api/tools/")) return true;
14923
+ if (routePath === "/api/models" || routePath.startsWith("/api/models/")) return true;
14924
+ if (routePath === "/api/providers") return true;
14925
+ if (routePath.startsWith("/api/providers/")) {
14926
+ return !isProviderIconRoute(routePath);
14927
+ }
14928
+ if (routePath.startsWith("/api/agents/")) {
14929
+ return !/^\/api\/agents\/[^/]+\/tenvs$/.test(routePath);
14930
+ }
14931
+ if (routePath === "/api/agents" && method.toUpperCase() !== "GET") return true;
14932
+ return false;
14933
+ }
14934
+
14857
14935
  // src/runtime.ts
14858
14936
  init_client_forwarding();
14859
14937
 
@@ -16534,6 +16612,18 @@ function messageTimestampSeconds(message) {
16534
16612
  return Math.floor(micros / (1e3 * TIMESTAMP_MULTIPLIER));
16535
16613
  }
16536
16614
 
16615
+ // src/events/access.ts
16616
+ var EVENT_STREAM_USER_ID_HEADER = "X-Standard-Agents-Event-User-Id";
16617
+ var EVENT_STREAM_SCOPE_HEADER = "X-Standard-Agents-Event-Scope";
16618
+ function isEventSocketAttachment(value) {
16619
+ if (!value || typeof value !== "object") return false;
16620
+ const attachment = value;
16621
+ return attachment.channel === "events" && (attachment.scope === "user" || attachment.scope === "all") && typeof attachment.userId === "string" && attachment.userId.length > 0;
16622
+ }
16623
+ function eventSocketCanReceive(attachment, ownerUserId) {
16624
+ return attachment.scope === "all" || ownerUserId !== null && attachment.userId === ownerUserId;
16625
+ }
16626
+
16537
16627
  // src/durable-objects/agentbuilder-migrations/0001_initial.ts
16538
16628
  var migration36 = {
16539
16629
  version: 1,
@@ -17562,8 +17652,13 @@ var DurableAgentBuilder = class extends DurableObject {
17562
17652
  const sockets = ctx.getWebSockets();
17563
17653
  for (const ws of sockets) {
17564
17654
  const attachment = ws.deserializeAttachment();
17565
- if (attachment?.channel === "events") {
17655
+ if (isEventSocketAttachment(attachment)) {
17566
17656
  this.eventSockets.add(ws);
17657
+ } else if (attachment?.channel === "events") {
17658
+ try {
17659
+ ws.close(1012, "Reconnect to establish an authenticated event scope");
17660
+ } catch {
17661
+ }
17567
17662
  }
17568
17663
  }
17569
17664
  }
@@ -18192,7 +18287,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18192
18287
  executing: false
18193
18288
  };
18194
18289
  const eventThread = await this.toThreadEventPayload(thread);
18195
- this.broadcastEvent({ type: "thread_created", thread: eventThread });
18290
+ this.broadcastEvent(
18291
+ { type: "thread_created", thread: eventThread },
18292
+ thread.user_id
18293
+ );
18196
18294
  await this.runAfterThreadCreatedHook(thread);
18197
18295
  return thread;
18198
18296
  }
@@ -18495,14 +18593,20 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18495
18593
  stampedMessageAt = stampAt;
18496
18594
  }
18497
18595
  }
18498
- this.broadcastEvent({
18499
- type: "thread_activity",
18500
- threadId,
18501
- ...activityAt !== void 0 ? { last_activity_at: activityAt } : {},
18502
- ...executing !== void 0 ? { executing } : {},
18503
- ...preview != null ? { last_message_preview: preview } : {},
18504
- ...stampedMessageAt !== void 0 ? { last_message_at: stampedMessageAt } : {}
18505
- });
18596
+ const ownerUserId = await this.getThreadOwnerId(threadId);
18597
+ if (ownerUserId !== void 0) {
18598
+ this.broadcastEvent(
18599
+ {
18600
+ type: "thread_activity",
18601
+ threadId,
18602
+ ...activityAt !== void 0 ? { last_activity_at: activityAt } : {},
18603
+ ...executing !== void 0 ? { executing } : {},
18604
+ ...preview != null ? { last_message_preview: preview } : {},
18605
+ ...stampedMessageAt !== void 0 ? { last_message_at: stampedMessageAt } : {}
18606
+ },
18607
+ ownerUserId
18608
+ );
18609
+ }
18506
18610
  }
18507
18611
  /**
18508
18612
  * Fill in previews for threads that predate preview tracking. Callers read
@@ -18529,8 +18633,14 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18529
18633
  */
18530
18634
  async deleteThread(id) {
18531
18635
  await this.ensureMigrated();
18636
+ const ownerUserId = await this.getThreadOwnerId(id);
18532
18637
  await this.ctx.storage.sql.exec(`DELETE FROM threads WHERE id = ?`, id);
18533
- this.broadcastEvent({ type: "thread_deleted", threadId: id });
18638
+ if (ownerUserId !== void 0) {
18639
+ this.broadcastEvent(
18640
+ { type: "thread_deleted", threadId: id },
18641
+ ownerUserId
18642
+ );
18643
+ }
18534
18644
  return true;
18535
18645
  }
18536
18646
  /**
@@ -18592,7 +18702,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18592
18702
  const updated = await this.getThread(id);
18593
18703
  if (updated) {
18594
18704
  const eventThread = await this.toThreadEventPayload(updated);
18595
- this.broadcastEvent({ type: "thread_updated", thread: eventThread });
18705
+ this.broadcastEvent(
18706
+ { type: "thread_updated", thread: eventThread },
18707
+ updated.user_id
18708
+ );
18596
18709
  }
18597
18710
  } catch {
18598
18711
  }
@@ -18672,7 +18785,10 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18672
18785
  const updated = await this.getThread(id);
18673
18786
  if (updated) {
18674
18787
  const eventThread = await this.toThreadEventPayload(updated);
18675
- this.broadcastEvent({ type: "thread_updated", thread: eventThread });
18788
+ this.broadcastEvent(
18789
+ { type: "thread_updated", thread: eventThread },
18790
+ updated.user_id
18791
+ );
18676
18792
  }
18677
18793
  return updated;
18678
18794
  }
@@ -19377,7 +19493,18 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
19377
19493
  }
19378
19494
  async toThreadEventPayload(thread) {
19379
19495
  const eventThread = {
19380
- ...thread
19496
+ id: thread.id,
19497
+ agent_name: thread.agent_name,
19498
+ origin_agent_name: thread.origin_agent_name,
19499
+ user_id: thread.user_id,
19500
+ tags: thread.tags,
19501
+ parent: thread.parent,
19502
+ terminated: thread.terminated,
19503
+ created_at: thread.created_at,
19504
+ last_activity_at: thread.last_activity_at,
19505
+ executing: thread.executing,
19506
+ last_message_preview: thread.last_message_preview,
19507
+ last_message_at: thread.last_message_at
19381
19508
  };
19382
19509
  try {
19383
19510
  const agentDef = await this.loadAgent(thread.agent_name);
@@ -20244,10 +20371,21 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20244
20371
  * Uses Hibernation API to reduce costs during inactivity
20245
20372
  */
20246
20373
  async handleEventsWebSocketUpgrade(request) {
20374
+ const attachment = {
20375
+ channel: "events",
20376
+ scope: request.headers.get(EVENT_STREAM_SCOPE_HEADER) === "all" ? "all" : "user",
20377
+ userId: request.headers.get(EVENT_STREAM_USER_ID_HEADER) ?? ""
20378
+ };
20379
+ if (!isEventSocketAttachment(attachment)) {
20380
+ return Response.json(
20381
+ { error: "Missing authenticated event subscription" },
20382
+ { status: 401 }
20383
+ );
20384
+ }
20247
20385
  const pair = new WebSocketPair();
20248
20386
  const [client, server] = Object.values(pair);
20249
20387
  this.ctx.acceptWebSocket(server);
20250
- server.serializeAttachment({ channel: "events" });
20388
+ server.serializeAttachment(attachment);
20251
20389
  this.eventSockets.add(server);
20252
20390
  return new Response(null, {
20253
20391
  status: 101,
@@ -20268,17 +20406,23 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20268
20406
  const thread = await this.getThread(threadId);
20269
20407
  if (!thread) return;
20270
20408
  const eventThread = await this.toThreadEventPayload(thread);
20271
- this.broadcastEvent({ type: "thread_updated", thread: eventThread });
20409
+ this.broadcastEvent(
20410
+ { type: "thread_updated", thread: eventThread },
20411
+ thread.user_id
20412
+ );
20272
20413
  }
20273
20414
  /**
20274
- * Broadcast an event to all connected WebSocket clients
20415
+ * Broadcast an event only to its owner or an explicitly all-scoped admin.
20275
20416
  */
20276
- broadcastEvent(event) {
20417
+ broadcastEvent(event, ownerUserId) {
20277
20418
  const payload = JSON.stringify(event);
20278
20419
  for (const ws of Array.from(this.eventSockets)) {
20279
20420
  try {
20280
20421
  if (ws.readyState === WebSocket.OPEN) {
20281
- ws.send(payload);
20422
+ const attachment = ws.deserializeAttachment();
20423
+ if (isEventSocketAttachment(attachment) && eventSocketCanReceive(attachment, ownerUserId)) {
20424
+ ws.send(payload);
20425
+ }
20282
20426
  } else {
20283
20427
  this.eventSockets.delete(ws);
20284
20428
  }
@@ -20287,6 +20431,15 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20287
20431
  }
20288
20432
  }
20289
20433
  }
20434
+ /** Resolve a thread owner without decrypting its env or loading the row. */
20435
+ async getThreadOwnerId(threadId) {
20436
+ const cursor = await this.ctx.storage.sql.exec(
20437
+ `SELECT user_id FROM threads WHERE id = ? LIMIT 1`,
20438
+ threadId
20439
+ );
20440
+ const rows = cursor.toArray();
20441
+ return rows[0]?.user_id;
20442
+ }
20290
20443
  /**
20291
20444
  * WebSocket Hibernation API handler for incoming messages
20292
20445
  * Called when a message is received on a hibernated WebSocket
@@ -28541,6 +28694,6 @@ init_context();
28541
28694
  init_ProviderRegistry();
28542
28695
  init_types2();
28543
28696
 
28544
- export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, authenticate, buildImageDescription, callClient, callClientStream, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
28697
+ export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, authenticate, buildImageDescription, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
28545
28698
  //# sourceMappingURL=runtime.js.map
28546
28699
  //# sourceMappingURL=runtime.js.map