@standardagents/builder 0.38.0 → 0.38.1

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 (54) hide show
  1. package/dist/built-in-routes.js +1165 -366
  2. package/dist/built-in-routes.js.map +1 -1
  3. package/dist/client/{ApiKeysView-L85eJb6G.js → ApiKeysView-AH5i-H6j.js} +1 -1
  4. package/dist/client/CenteredContentView-DaBp0K-A.js +1 -0
  5. package/dist/client/{CompositionView-BGp0EJCk.js → CompositionView-yY1Q4M23.js} +1 -1
  6. package/dist/client/{ConfirmDialog.vue_vue_type_script_setup_true_lang-DgwqZrLV.js → ConfirmDialog.vue_vue_type_script_setup_true_lang-Bwo5CJzF.js} +1 -1
  7. package/dist/client/{CopyButton.vue_vue_type_script_setup_true_lang-B8cGgU2J.js → CopyButton.vue_vue_type_script_setup_true_lang-D0WYwWyA.js} +1 -1
  8. package/dist/client/{DataTable.vue_vue_type_script_setup_true_lang-CRBXym9D.js → DataTable.vue_vue_type_script_setup_true_lang-1wwPn2aY.js} +1 -1
  9. package/dist/client/{JsonViewer-DkHnIfq3.js → JsonViewer-Bi8Zdmir.js} +1 -1
  10. package/dist/client/LoginView-BIKahEU6.js +1 -0
  11. package/dist/client/{MarketplaceView-DEnujUBs.js → MarketplaceView-DPDPVH68.js} +1 -1
  12. package/dist/client/{Modal.vue_vue_type_script_setup_true_lang-keKEwm3K.js → Modal.vue_vue_type_script_setup_true_lang-D1_rfOsy.js} +1 -1
  13. package/dist/client/{ModelModal.vue_vue_type_script_setup_true_lang-t1n0bbJe.js → ModelModal.vue_vue_type_script_setup_true_lang-DSLeOdxr.js} +1 -1
  14. package/dist/client/{ModelsView-BkCLr8sp.js → ModelsView-Q2WXu1Gw.js} +1 -1
  15. package/dist/client/{PromptEditView-BjCfsJQE.js → PromptEditView-BzA-3n1w.js} +1 -1
  16. package/dist/client/{PromptModal-nxu_LIv6.js → PromptModal-CJWh_cru.js} +1 -1
  17. package/dist/client/{PromptsView-1yXGCmEZ.js → PromptsView-BezNl-Et.js} +1 -1
  18. package/dist/client/{ProvidersView-DxF6gafk.js → ProvidersView-BuzSKxuT.js} +1 -1
  19. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-DOxNp_3C.js +22 -0
  20. package/dist/client/{ThreadsView-CBup_w8u.js → ThreadsView-DPCpZA4J.js} +1 -1
  21. package/dist/client/{ToolsView-B30TfXU9.js → ToolsView-DMjFMAhH.js} +1 -1
  22. package/dist/client/{UiCodeBlock.vue_vue_type_script_setup_true_lang-BdyzSxZZ.js → UiCodeBlock.vue_vue_type_script_setup_true_lang-7FgZmkfs.js} +1 -1
  23. package/dist/client/{UsersView-kE7Xr4f3.js → UsersView-B7jXLNQs.js} +1 -1
  24. package/dist/client/{VariablesView-yYuynG0M.js → VariablesView-CF7x0G8g.js} +1 -1
  25. package/dist/client/assets/{ThreadInspectorPane-BRGO_P0_.css → ThreadInspectorPane-CZMdV0Tb.css} +1 -1
  26. package/dist/client/assets/{index-B-69GGhJ.css → index-DAABRRAA.css} +1 -1
  27. package/dist/client/{check-YKi1o6p6.js → check-CD2lvcVi.js} +1 -1
  28. package/dist/client/{chevron-right-T0zzq_GX.js → chevron-right-bWZrmJ5a.js} +1 -1
  29. package/dist/client/{copy-C_gUGAj1.js → copy-B7nifl6a.js} +1 -1
  30. package/dist/client/{formkit-CE26dtFp.js → formkit-DJxUzqRQ.js} +1 -1
  31. package/dist/client/index-eGECJFYe.js +6 -0
  32. package/dist/client/index.html +6 -6
  33. package/dist/client/{package-B70IFSDB.js → package-Dyv7fTIQ.js} +1 -1
  34. package/dist/client/{plus-BN9IHYdt.js → plus-XkSslfF4.js} +1 -1
  35. package/dist/client/{syntax-D-ZcovVx.js → syntax-D68HCEYm.js} +1 -1
  36. package/dist/client/{timeAgo-G1ovbWPH.js → timeAgo-DRBdu9ed.js} +1 -1
  37. package/dist/client/{useActionFeedback-7pfPjKMi.js → useActionFeedback-FN4t6ezB.js} +1 -1
  38. package/dist/client/{vendor-TPZjEwCU.js → vendor-c_uwtQ-S.js} +13 -13
  39. package/dist/client/{vue-CZ-ORfS3.js → vue-BuvSxRmI.js} +1 -1
  40. package/dist/{index-BBu-Osn5.d.ts → index-CF-lgN47.d.ts} +74 -11
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +1060 -303
  43. package/dist/index.js.map +1 -1
  44. package/dist/plugin.js +102 -68
  45. package/dist/plugin.js.map +1 -1
  46. package/dist/runtime.d.ts +68 -11
  47. package/dist/runtime.js +950 -225
  48. package/dist/runtime.js.map +1 -1
  49. package/dist/test.d.ts +1 -1
  50. package/package.json +7 -6
  51. package/dist/client/CenteredContentView-D9jkWoLc.js +0 -1
  52. package/dist/client/LoginView-BXD9Odc-.js +0 -1
  53. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-CjwWJ3uw.js +0 -22
  54. package/dist/client/index-AUGwDdSH.js +0 -6
package/dist/index.js CHANGED
@@ -37,6 +37,14 @@ var __copyProps = (to, from, except, desc) => {
37
37
  };
38
38
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
39
39
 
40
+ // src/skills/scope.ts
41
+ var SHARED_SKILL_OWNER;
42
+ var init_scope = __esm({
43
+ "src/skills/scope.ts"() {
44
+ SHARED_SKILL_OWNER = "__instance_shared__";
45
+ }
46
+ });
47
+
40
48
  // src/agents/client-forwarding.ts
41
49
  function asForwarding(state) {
42
50
  return state;
@@ -3159,6 +3167,104 @@ var init_sessionTools = __esm({
3159
3167
  }
3160
3168
  });
3161
3169
 
3170
+ // src/utils/remoteAttachment.ts
3171
+ function parseAllowedHosts(value) {
3172
+ if (typeof value !== "string") {
3173
+ return /* @__PURE__ */ new Set();
3174
+ }
3175
+ return new Set(
3176
+ value.split(",").map((host) => host.trim().toLowerCase().replace(/\.$/, "")).filter(Boolean)
3177
+ );
3178
+ }
3179
+ function isForbiddenHostname(hostname) {
3180
+ const normalized = hostname.toLowerCase().replace(/\.$/, "");
3181
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(normalized)) {
3182
+ return true;
3183
+ }
3184
+ if (normalized.startsWith("[") && normalized.endsWith("]")) {
3185
+ return true;
3186
+ }
3187
+ if (!normalized.includes(".")) {
3188
+ return true;
3189
+ }
3190
+ return FORBIDDEN_HOST_SUFFIXES.some(
3191
+ (suffix) => normalized === suffix.slice(1) || normalized.endsWith(suffix)
3192
+ );
3193
+ }
3194
+ function validateRemoteAttachmentUrl(value, allowedHostsValue) {
3195
+ let url;
3196
+ try {
3197
+ url = new URL(value);
3198
+ } catch {
3199
+ return null;
3200
+ }
3201
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" && url.port !== "443") {
3202
+ return null;
3203
+ }
3204
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
3205
+ if (isForbiddenHostname(hostname)) {
3206
+ return null;
3207
+ }
3208
+ const allowedHosts = parseAllowedHosts(allowedHostsValue);
3209
+ if (!allowedHosts.has(hostname)) {
3210
+ return null;
3211
+ }
3212
+ return url;
3213
+ }
3214
+ async function fetchRemoteAttachment(location, allowedHostsValue, fetchImpl = fetch) {
3215
+ let url = validateRemoteAttachmentUrl(location, allowedHostsValue);
3216
+ if (!url) {
3217
+ return null;
3218
+ }
3219
+ for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) {
3220
+ let response;
3221
+ try {
3222
+ response = await fetchImpl(url.toString(), {
3223
+ redirect: "manual"
3224
+ });
3225
+ } catch {
3226
+ return null;
3227
+ }
3228
+ if (!REDIRECT_STATUSES.has(response.status)) {
3229
+ return response.ok ? response : null;
3230
+ }
3231
+ await response.body?.cancel().catch(() => void 0);
3232
+ if (redirectCount === MAX_REDIRECTS) {
3233
+ return null;
3234
+ }
3235
+ const redirectLocation = response.headers.get("Location");
3236
+ if (!redirectLocation) {
3237
+ return null;
3238
+ }
3239
+ let resolvedLocation;
3240
+ try {
3241
+ resolvedLocation = new URL(redirectLocation, url).toString();
3242
+ } catch {
3243
+ return null;
3244
+ }
3245
+ url = validateRemoteAttachmentUrl(resolvedLocation, allowedHostsValue);
3246
+ if (!url) {
3247
+ return null;
3248
+ }
3249
+ }
3250
+ return null;
3251
+ }
3252
+ var REDIRECT_STATUSES, MAX_REDIRECTS, FORBIDDEN_HOST_SUFFIXES;
3253
+ var init_remoteAttachment = __esm({
3254
+ "src/utils/remoteAttachment.ts"() {
3255
+ REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
3256
+ MAX_REDIRECTS = 3;
3257
+ FORBIDDEN_HOST_SUFFIXES = [
3258
+ ".internal",
3259
+ ".lan",
3260
+ ".local",
3261
+ ".localhost",
3262
+ ".home",
3263
+ ".test"
3264
+ ];
3265
+ }
3266
+ });
3267
+
3162
3268
  // src/durable-objects/files.ts
3163
3269
  var files_exports = {};
3164
3270
  __export(files_exports, {
@@ -3896,6 +4002,7 @@ var init_ToolExecutor = __esm({
3896
4002
  init_types();
3897
4003
  init_ThreadStateImpl();
3898
4004
  init_sessionTools();
4005
+ init_remoteAttachment();
3899
4006
  SUBAGENT_CREATE_TOOL = "subagent_create";
3900
4007
  SUBAGENT_MESSAGE_TOOL = "subagent_message";
3901
4008
  SUBAGENTS_LIST_TOOL = "subagents";
@@ -4944,7 +5051,8 @@ Message one with subagent_message(referenceId, message).`
4944
5051
  stub,
4945
5052
  state.thread.instance,
4946
5053
  childResult.attachments,
4947
- `from-subagent-${childThreadId}`
5054
+ `from-subagent-${childThreadId}`,
5055
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
4948
5056
  );
4949
5057
  if (resumable) {
4950
5058
  await this.settleResumableChildAfterRun(
@@ -4977,7 +5085,8 @@ Message one with subagent_message(referenceId, message).`
4977
5085
  stub,
4978
5086
  state.thread.instance,
4979
5087
  childResult.attachments,
4980
- `from-subagent-${childThreadId}`
5088
+ `from-subagent-${childThreadId}`,
5089
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
4981
5090
  );
4982
5091
  const messageContent = this.formatSubagentTerminalMessage(
4983
5092
  childThreadId,
@@ -5391,7 +5500,8 @@ Message one with subagent_message(referenceId, message).`
5391
5500
  state.thread.instance,
5392
5501
  childThread,
5393
5502
  parentAttachments,
5394
- `from-parent-${state.threadId}`
5503
+ `from-parent-${state.threadId}`,
5504
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
5395
5505
  );
5396
5506
  const targetRole = receivesMessages === "side_a" ? "user" : "assistant";
5397
5507
  const baseTimestamp = Date.now() * 1e3;
@@ -5651,7 +5761,7 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5651
5761
  const shortId = crypto.randomUUID().slice(0, 8);
5652
5762
  return `/attachments/${normalizedStem}-${shortId}.${finalExt}`;
5653
5763
  }
5654
- static async copyAttachmentsBetweenThreads(sourceThread, targetThread, attachments, _pathPrefix) {
5764
+ static async copyAttachmentsBetweenThreads(sourceThread, targetThread, attachments, _pathPrefix, allowedAttachmentFetchHosts) {
5655
5765
  if (!attachments || attachments.length === 0) {
5656
5766
  return [];
5657
5767
  }
@@ -5679,7 +5789,8 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5679
5789
  const copiedData = await this.readAttachmentDataFromSource(
5680
5790
  sourceThread,
5681
5791
  attachment.path,
5682
- sourceFile
5792
+ sourceFile,
5793
+ allowedAttachmentFetchHosts
5683
5794
  );
5684
5795
  if (!copiedData) {
5685
5796
  continue;
@@ -5736,7 +5847,7 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5736
5847
  }
5737
5848
  return copied;
5738
5849
  }
5739
- static async readAttachmentDataFromSource(sourceThread, path21, sourceFile) {
5850
+ static async readAttachmentDataFromSource(sourceThread, path21, sourceFile, allowedAttachmentFetchHosts) {
5740
5851
  const sourceData = await sourceThread.readFile(path21);
5741
5852
  if (sourceData.success && sourceData.data) {
5742
5853
  return sourceData.data;
@@ -5745,14 +5856,17 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5745
5856
  return null;
5746
5857
  }
5747
5858
  const location = sourceFile.location;
5748
- if (!location || !/^https?:\/\//i.test(location)) {
5859
+ if (!location) {
5860
+ return null;
5861
+ }
5862
+ const response = await fetchRemoteAttachment(
5863
+ location,
5864
+ allowedAttachmentFetchHosts
5865
+ );
5866
+ if (!response) {
5749
5867
  return null;
5750
5868
  }
5751
5869
  try {
5752
- const response = await fetch(location);
5753
- if (!response.ok) {
5754
- return null;
5755
- }
5756
5870
  const buffer = await response.arrayBuffer();
5757
5871
  return this.arrayBufferToBase64(buffer);
5758
5872
  } catch {
@@ -6006,7 +6120,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
6006
6120
  state.thread.instance,
6007
6121
  childStub,
6008
6122
  sourceAttachments,
6009
- `from-parent-${state.threadId}`
6123
+ `from-parent-${state.threadId}`,
6124
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
6010
6125
  );
6011
6126
  await childStub.queueMessage(referenceId, {
6012
6127
  role: receivesMessages === "side_a" ? "user" : "assistant",
@@ -12988,6 +13103,32 @@ var ThreadStateImpl_exports = {};
12988
13103
  __export(ThreadStateImpl_exports, {
12989
13104
  ThreadStateImpl: () => ThreadStateImpl
12990
13105
  });
13106
+ function isRuntimeDurableNamespace(value) {
13107
+ return value !== null && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function" && "get" in value && typeof value.get === "function";
13108
+ }
13109
+ function isThreadStateRuntimeEnvironment(value) {
13110
+ return value !== null && typeof value === "object" && "AGENT_BUILDER" in value && isRuntimeDurableNamespace(value.AGENT_BUILDER) && (!("AGENT_BUILDER_USER_HUB" in value) || value.AGENT_BUILDER_USER_HUB === void 0 || isRuntimeDurableNamespace(value.AGENT_BUILDER_USER_HUB));
13111
+ }
13112
+ function confinedUserEndpointUrl(userId, endpoint) {
13113
+ const base = new URL(
13114
+ `https://user-hub/users/${encodeURIComponent(userId)}/endpoints/`
13115
+ );
13116
+ const relative4 = endpoint.replace(/^\/+/, "");
13117
+ let decoded;
13118
+ try {
13119
+ decoded = decodeURIComponent(relative4);
13120
+ } catch {
13121
+ throw new Error("Invalid user endpoint path: malformed percent encoding.");
13122
+ }
13123
+ if (decoded.includes("\\") || decoded.includes("?") || decoded.includes("#") || /[\u0000-\u001f\u007f]/.test(decoded) || decoded.split("/").some((segment) => segment === "." || segment === "..")) {
13124
+ throw new Error("Invalid user endpoint path: traversal and URL syntax are not allowed.");
13125
+ }
13126
+ const resolved = new URL(relative4, base);
13127
+ if (resolved.origin !== base.origin || !resolved.pathname.startsWith(base.pathname)) {
13128
+ throw new Error("Invalid user endpoint path: endpoint escaped the user namespace.");
13129
+ }
13130
+ return resolved.toString();
13131
+ }
12991
13132
  function hasLocalCodeExecutionRuntime(threadInstance) {
12992
13133
  const candidate = threadInstance;
12993
13134
  return Boolean(candidate.env && candidate.ctx);
@@ -13168,6 +13309,7 @@ function runCodeViaThreadRpc(threadInstance, threadId, source, options) {
13168
13309
  var ThreadStateImpl, ExecutionStateImpl;
13169
13310
  var init_ThreadStateImpl = __esm({
13170
13311
  "src/agents/ThreadStateImpl.ts"() {
13312
+ init_scope();
13171
13313
  init_client_forwarding();
13172
13314
  init_types();
13173
13315
  init_utilities();
@@ -13401,6 +13543,14 @@ var init_ThreadStateImpl = __esm({
13401
13543
  }
13402
13544
  return value;
13403
13545
  }
13546
+ async instanceEnv(propertyName) {
13547
+ const agentBuilder = this._getAgentBuilderStub();
13548
+ if (typeof agentBuilder.getInstanceEnv !== "function") {
13549
+ throw new Error("instanceEnv is not supported by this runtime");
13550
+ }
13551
+ const values = await agentBuilder.getInstanceEnv();
13552
+ return values[propertyName];
13553
+ }
13404
13554
  async envType(propertyName) {
13405
13555
  const agentBuilder = this._getAgentBuilderStub();
13406
13556
  if (typeof agentBuilder.resolveThreadEnvType === "function") {
@@ -14431,12 +14581,11 @@ var init_ThreadStateImpl = __esm({
14431
14581
  * dispatcher recorded in the runtime context instead.
14432
14582
  */
14433
14583
  _getRuntimeEnv() {
14434
- const contextEnv = this._runtimeContext.env;
14435
- if (contextEnv?.AGENT_BUILDER) {
14436
- return contextEnv;
14584
+ if (isThreadStateRuntimeEnvironment(this._runtimeContext.env)) {
14585
+ return this._runtimeContext.env;
14437
14586
  }
14438
14587
  const instanceEnv = this._threadInstance.env;
14439
- if (!instanceEnv?.AGENT_BUILDER) {
14588
+ if (!isThreadStateRuntimeEnvironment(instanceEnv)) {
14440
14589
  throw new Error(
14441
14590
  "No runtime environment available on this ThreadState. Stub-backed states must be constructed with runtimeContext.env."
14442
14591
  );
@@ -14453,9 +14602,7 @@ var init_ThreadStateImpl = __esm({
14453
14602
  return namespace.get(namespace.idFromName(userId));
14454
14603
  }
14455
14604
  // ─────────────────────────────────────────────────────────────────────────
14456
- // Skills — instance-global Agent Skills library, backed by the
14457
- // DurableAgentBuilder singleton (skills are never stored per-thread or on
14458
- // a client machine).
14605
+ // Skills — owner-scoped library plus explicitly shared administrator skills.
14459
14606
  // ─────────────────────────────────────────────────────────────────────────
14460
14607
  /**
14461
14608
  * The profile of the user this thread belongs to (identity fields only —
@@ -14478,9 +14625,8 @@ var init_ThreadStateImpl = __esm({
14478
14625
  "Reentrant user-scope call: this ThreadState was raised from user-scope code; state.user().call() is unavailable here."
14479
14626
  );
14480
14627
  }
14481
- const path21 = endpoint.replace(/^\/+/, "");
14482
14628
  return hub().fetch(
14483
- new Request(`https://user-hub/users/${encodeURIComponent(userId)}/endpoints/${path21}`, {
14629
+ new Request(confinedUserEndpointUrl(userId, endpoint), {
14484
14630
  method: "POST",
14485
14631
  headers: {
14486
14632
  "Content-Type": "application/json",
@@ -14540,18 +14686,42 @@ var init_ThreadStateImpl = __esm({
14540
14686
  }
14541
14687
  get skills() {
14542
14688
  const stub = this._getAgentBuilderStub();
14689
+ const ownerId = this._metadata.user_id ?? SHARED_SKILL_OWNER;
14690
+ const scope = {
14691
+ ownerId,
14692
+ includeShared: ownerId !== SHARED_SKILL_OWNER
14693
+ };
14694
+ const requireMutationAdmin = async () => {
14695
+ const userId = this._metadata.user_id;
14696
+ if (!userId) {
14697
+ throw new Error("Skill mutations require an authenticated instance admin.");
14698
+ }
14699
+ const user = await stub.getUserById(userId);
14700
+ if (user?.role !== "admin") {
14701
+ throw new Error("Skill mutations require an instance admin.");
14702
+ }
14703
+ };
14543
14704
  return {
14544
- list: (options) => stub.listSkills(options),
14705
+ list: (options) => stub.listSkills(scope, options),
14545
14706
  get: async (name) => {
14546
- const metadata = await stub.getSkill(name);
14707
+ const metadata = await stub.getSkill(scope, name);
14547
14708
  if (!metadata) return null;
14548
- const skillMd = await stub.getSkillFile(name, "SKILL.md") ?? "";
14709
+ const skillMd = await stub.getSkillFile(scope, name, "SKILL.md") ?? "";
14549
14710
  return { metadata, skillMd };
14550
14711
  },
14551
- readFile: (name, path21) => stub.getSkillFile(name, path21),
14552
- install: (files) => stub.upsertSkill(files),
14553
- setEnabled: (name, enabled) => stub.setSkillEnabled(name, enabled),
14554
- remove: (name) => stub.deleteSkill(name)
14712
+ readFile: (name, path21) => stub.getSkillFile(scope, name, path21),
14713
+ install: async (files) => {
14714
+ await requireMutationAdmin();
14715
+ return stub.upsertSkill(scope, files);
14716
+ },
14717
+ setEnabled: async (name, enabled) => {
14718
+ await requireMutationAdmin();
14719
+ return stub.setSkillEnabled(scope, name, enabled);
14720
+ },
14721
+ remove: async (name) => {
14722
+ await requireMutationAdmin();
14723
+ return stub.deleteSkill(scope, name);
14724
+ }
14555
14725
  };
14556
14726
  }
14557
14727
  // ─────────────────────────────────────────────────────────────────────────
@@ -15389,6 +15559,27 @@ declare module 'virtual:@standardagents/builder' {
15389
15559
  validateSession(tokenHash: string): Promise<{ user_id: string; expires_at: number } | null>;
15390
15560
  cleanupSessions(): Promise<number>;
15391
15561
  deleteSession(tokenHash: string): Promise<void>;
15562
+ createSuperAdminSession(params: {
15563
+ token_hash: string;
15564
+ expires_at: number;
15565
+ }): Promise<void>;
15566
+ validateSuperAdminSession(tokenHash: string): Promise<boolean>;
15567
+ deleteSuperAdminSession(tokenHash: string): Promise<void>;
15568
+ createWebSocketTicket(params: {
15569
+ ticket_hash: string;
15570
+ pathname: string;
15571
+ auth_json: string;
15572
+ expires_at: number;
15573
+ }): Promise<void>;
15574
+ consumeWebSocketTicket(params: {
15575
+ ticket_hash: string;
15576
+ pathname: string;
15577
+ }): Promise<string | null>;
15578
+ consumeAuthRateLimit(params: {
15579
+ key: string;
15580
+ limit: number;
15581
+ window_seconds: number;
15582
+ }): Promise<{ allowed: boolean; retry_after: number; remaining: number }>;
15392
15583
 
15393
15584
  // API key methods
15394
15585
  createApiKey(params: {
@@ -15990,6 +16181,92 @@ function resolveLocalBootstrapCookie(projectRoot, routePath, cookieHeader) {
15990
16181
  return findLocalBootstrapSession(projectRoot)?.session.session_cookie ?? null;
15991
16182
  }
15992
16183
 
16184
+ // src/utils/auth.ts
16185
+ var SUPER_ADMIN_TOKEN_TTL_SECONDS = 12 * 60 * 60;
16186
+ async function hashToken(token) {
16187
+ const encoder = new TextEncoder();
16188
+ const data = encoder.encode(token);
16189
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
16190
+ const hashArray = new Uint8Array(hashBuffer);
16191
+ return Array.from(hashArray, (byte) => byte.toString(16).padStart(2, "0")).join("");
16192
+ }
16193
+ var SESSION_COOKIE_NAME = "agtuser_session";
16194
+ function readSessionCookie(request) {
16195
+ const header = request.headers.get("Cookie");
16196
+ if (!header) return null;
16197
+ for (const part of header.split(";")) {
16198
+ const eq = part.indexOf("=");
16199
+ if (eq === -1) continue;
16200
+ if (part.slice(0, eq).trim() === SESSION_COOKIE_NAME) {
16201
+ return decodeURIComponent(part.slice(eq + 1).trim()) || null;
16202
+ }
16203
+ }
16204
+ return null;
16205
+ }
16206
+ function isValidUserToken(token) {
16207
+ return token.startsWith("agtuser_") && token.length > 10;
16208
+ }
16209
+ function isValidApiKey(key) {
16210
+ return key.startsWith("agtbldr_") && key.length > 10 || key.startsWith("sak_live_") && key.length > 10;
16211
+ }
16212
+ async function verifySignedToken(signedToken, encryptionKey) {
16213
+ try {
16214
+ const parts = signedToken.split(".");
16215
+ if (parts.length !== 2) {
16216
+ return null;
16217
+ }
16218
+ const [payloadB64, signatureB64] = parts;
16219
+ const encoder = new TextEncoder();
16220
+ const keyData = encoder.encode(encryptionKey);
16221
+ const cryptoKey = await crypto.subtle.importKey(
16222
+ "raw",
16223
+ keyData,
16224
+ { name: "HMAC", hash: "SHA-256" },
16225
+ false,
16226
+ ["verify"]
16227
+ );
16228
+ const payloadData = base64ToBuffer(payloadB64);
16229
+ const signature = base64ToBuffer(signatureB64);
16230
+ const isValid = await crypto.subtle.verify("HMAC", cryptoKey, signature, payloadData);
16231
+ if (!isValid) {
16232
+ return null;
16233
+ }
16234
+ const decoder = new TextDecoder();
16235
+ return decoder.decode(payloadData);
16236
+ } catch (error) {
16237
+ console.error("Error verifying signed token:", error);
16238
+ return null;
16239
+ }
16240
+ }
16241
+ async function verifySuperAdminToken(token, encryptionKey) {
16242
+ const payload = await verifySignedToken(token, encryptionKey);
16243
+ if (!payload) {
16244
+ return false;
16245
+ }
16246
+ const parts = payload.split(":");
16247
+ if (parts.length !== 3 || parts[0] !== "super_admin") {
16248
+ return false;
16249
+ }
16250
+ const timestamp = parseInt(parts[1], 10);
16251
+ if (!Number.isFinite(timestamp)) {
16252
+ return false;
16253
+ }
16254
+ const age = Date.now() - timestamp;
16255
+ const maxAge = SUPER_ADMIN_TOKEN_TTL_SECONDS * 1e3;
16256
+ if (age < 0 || age > maxAge) {
16257
+ return false;
16258
+ }
16259
+ return true;
16260
+ }
16261
+ function base64ToBuffer(base64) {
16262
+ const binary = atob(base64);
16263
+ const bytes = new Uint8Array(binary.length);
16264
+ for (let i = 0; i < binary.length; i++) {
16265
+ bytes[i] = binary.charCodeAt(i);
16266
+ }
16267
+ return bytes;
16268
+ }
16269
+
15993
16270
  // src/sdk/generators/generateModelFile.ts
15994
16271
  function generateModelFile(data, options) {
15995
16272
  const { providerName, providerPackage, defaultImport } = options;
@@ -16055,7 +16332,7 @@ function generateModelFile(data, options) {
16055
16332
  return lines.join("\n");
16056
16333
  }
16057
16334
  function escapeString(str) {
16058
- return str.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
16335
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
16059
16336
  }
16060
16337
 
16061
16338
  // src/sdk/generators/jsonSchemaToZod.ts
@@ -16586,7 +16863,7 @@ function formatSideConfig(config) {
16586
16863
  return parts.join("\n");
16587
16864
  }
16588
16865
  function escapeString3(str) {
16589
- return str.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
16866
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
16590
16867
  }
16591
16868
  function formatSessionBinding(binding) {
16592
16869
  if (typeof binding === "string") {
@@ -21408,14 +21685,13 @@ var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
21408
21685
  "/api/auth/oauth/github",
21409
21686
  "/api/auth/oauth/google",
21410
21687
  "/api/auth/oauth/github/callback",
21411
- "/api/auth/oauth/google/callback",
21412
- "/api/hooks"
21688
+ "/api/auth/oauth/google/callback"
21413
21689
  ]);
21414
21690
  function isProviderIconRoute(routePath) {
21415
21691
  return /^\/api\/providers\/[^/]+\/icon$/.test(routePath);
21416
21692
  }
21417
21693
  function isHostedInstance(env) {
21418
- const value = env?.STANDARD_AGENTS_HOSTED;
21694
+ const value = env && typeof env === "object" ? Reflect.get(env, "STANDARD_AGENTS_HOSTED") : void 0;
21419
21695
  if (typeof value === "string") {
21420
21696
  const normalized = value.trim().toLowerCase();
21421
21697
  return normalized !== "" && normalized !== "0" && normalized !== "false";
@@ -21440,10 +21716,54 @@ function isPublicApiRoute(routePath, request, env) {
21440
21716
  if (isProviderIconRoute(routePath)) {
21441
21717
  return true;
21442
21718
  }
21443
- if (!isTrustedLocalDevelopmentRequest(request, env)) return false;
21444
- 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/");
21719
+ return routePath === "/api/platform-auth/bootstrap" && isTrustedLocalDevelopmentRequest(request, env);
21720
+ }
21721
+ function requestObservedOrigins(request) {
21722
+ const origins = /* @__PURE__ */ new Set();
21723
+ let url = null;
21724
+ try {
21725
+ url = new URL(request.url);
21726
+ origins.add(url.origin);
21727
+ } catch {
21728
+ url = null;
21729
+ }
21730
+ const forwardedHost = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
21731
+ const host = forwardedHost || request.headers.get("host")?.trim();
21732
+ if (!host) return origins;
21733
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase();
21734
+ const schemes = /* @__PURE__ */ new Set();
21735
+ if (forwardedProto === "http" || forwardedProto === "https") schemes.add(forwardedProto);
21736
+ if (url) schemes.add(url.protocol.replace(":", ""));
21737
+ if (schemes.size === 0) {
21738
+ schemes.add(host.endsWith(".orb.local") ? "https" : "http");
21739
+ }
21740
+ for (const scheme of schemes) {
21741
+ try {
21742
+ origins.add(new URL(`${scheme}://${host}`).origin);
21743
+ } catch {
21744
+ }
21745
+ }
21746
+ return origins;
21747
+ }
21748
+ function isSameOriginBrowserRequest(request) {
21749
+ const fetchSite = request.headers.get("Sec-Fetch-Site")?.trim().toLowerCase();
21750
+ if (fetchSite === "same-origin" || fetchSite === "none") {
21751
+ return true;
21752
+ }
21753
+ if (fetchSite) {
21754
+ return false;
21755
+ }
21756
+ const origin = request.headers.get("Origin")?.trim();
21757
+ if (!origin) return true;
21758
+ if (origin === "null") return false;
21759
+ try {
21760
+ return requestObservedOrigins(request).has(new URL(origin).origin);
21761
+ } catch {
21762
+ return false;
21763
+ }
21445
21764
  }
21446
21765
  function isAdminOnlyApiRoute(routePath, method = "GET") {
21766
+ if (routePath === "/api/hooks") return true;
21447
21767
  if (routePath === "/api/skills" || routePath.startsWith("/api/skills/")) return true;
21448
21768
  if (routePath === "/api/pack" || routePath.startsWith("/api/pack/")) return true;
21449
21769
  if (routePath === "/api/unpack" || routePath.startsWith("/api/unpack/")) return true;
@@ -21462,6 +21782,9 @@ function isAdminOnlyApiRoute(routePath, method = "GET") {
21462
21782
  }
21463
21783
 
21464
21784
  // src/plugin/dev-middleware.ts
21785
+ function isEncryptedSocket(socket) {
21786
+ return "encrypted" in socket && socket.encrypted === true;
21787
+ }
21465
21788
  function readRawRequestBody(req) {
21466
21789
  return new Promise((resolve3, reject) => {
21467
21790
  const chunks = [];
@@ -21486,6 +21809,36 @@ function resolveContainedFile(baseDir, requestPath) {
21486
21809
  return null;
21487
21810
  }
21488
21811
  }
21812
+ function firstHeader(value) {
21813
+ return Array.isArray(value) ? value[0] : value;
21814
+ }
21815
+ function isSameOriginDevRequest(req) {
21816
+ const fetchSite = firstHeader(req.headers["sec-fetch-site"])?.trim().toLowerCase();
21817
+ if (fetchSite && fetchSite !== "same-origin" && fetchSite !== "none") {
21818
+ return false;
21819
+ }
21820
+ const origin = firstHeader(req.headers.origin)?.trim();
21821
+ if (!origin) return true;
21822
+ if (origin === "null") return false;
21823
+ try {
21824
+ const forwardedProtocol = firstHeader(req.headers["x-forwarded-proto"])?.split(",")[0]?.trim().toLowerCase();
21825
+ const protocol = forwardedProtocol === "https" || forwardedProtocol === "http" ? forwardedProtocol : isEncryptedSocket(req.socket) ? "https" : "http";
21826
+ const requestOrigin = new URL(`${protocol}://${String(req.headers.host || "").trim()}`).origin;
21827
+ return new URL(origin).origin === requestOrigin;
21828
+ } catch {
21829
+ return false;
21830
+ }
21831
+ }
21832
+ function hasDevAuthoringCredential(req) {
21833
+ const cookie = firstHeader(req.headers.cookie) || "";
21834
+ const hasSession = cookie.split(";").some((part) => {
21835
+ const separator = part.indexOf("=");
21836
+ return separator !== -1 && part.slice(0, separator).trim() === SESSION_COOKIE_NAME && part.slice(separator + 1).trim() !== "";
21837
+ });
21838
+ if (hasSession) return true;
21839
+ const authorization = firstHeader(req.headers.authorization)?.trim() || "";
21840
+ return /^Bearer\s+\S+$/.test(authorization);
21841
+ }
21489
21842
  function injectUiConfigIntoHtml(htmlContent) {
21490
21843
  const configScript = `<script>window.__AGENTBUILDER_CONFIG__ = { devMode: true };</script>`;
21491
21844
  return htmlContent.replace(/\/agents\//g, "/").replace("</head>", `${configScript}</head>`);
@@ -21540,11 +21893,21 @@ function createDevMiddleware(server, context) {
21540
21893
  );
21541
21894
  } catch {
21542
21895
  }
21543
- if (isAdminOnlyApiRoute(routePath, method) && !trustedLocalHost) {
21544
- res.statusCode = 403;
21545
- res.setHeader("Content-Type", "application/json");
21546
- res.end(JSON.stringify({ error: "Authoring APIs are available only from local development hosts" }));
21547
- return;
21896
+ if (isAdminOnlyApiRoute(routePath, method)) {
21897
+ if (!trustedLocalHost || !isSameOriginDevRequest(req)) {
21898
+ res.statusCode = 403;
21899
+ res.setHeader("Content-Type", "application/json");
21900
+ res.setHeader("Cache-Control", "no-store");
21901
+ res.end(JSON.stringify({ error: "Authoring APIs require a same-origin local request" }));
21902
+ return;
21903
+ }
21904
+ if (!hasDevAuthoringCredential(req)) {
21905
+ res.statusCode = 401;
21906
+ res.setHeader("Content-Type", "application/json");
21907
+ res.setHeader("Cache-Control", "no-store");
21908
+ res.end(JSON.stringify({ error: "Authoring APIs require an authenticated local session" }));
21909
+ return;
21910
+ }
21548
21911
  }
21549
21912
  if (await handleConfigApiRequest({
21550
21913
  req,
@@ -22199,8 +22562,13 @@ import {
22199
22562
  canAccessThread,
22200
22563
  endpointCallerFromAuth,
22201
22564
  isHostedInstance,
22565
+ isTrustedLocalDevelopmentRequest,
22566
+ isSameOriginBrowserRequest,
22202
22567
  isPublicApiRoute,
22203
22568
  isAdminOnlyApiRoute,
22569
+ applyRuntimeCors,
22570
+ runtimeCorsHeaders,
22571
+ runtimePreflightResponse,
22204
22572
  } from "@standardagents/builder/runtime";
22205
22573
  import { isThreadEndpoint, getEndpointAccess } from "@standardagents/spec";
22206
22574
 
@@ -22257,71 +22625,34 @@ function isHtmlNavigationRequest(request) {
22257
22625
  return (accept === '' || accept === '*/*') && isUiNavigationPath(pathname);
22258
22626
  }
22259
22627
 
22260
- // CORS headers for API responses
22261
- const CORS_HEADERS = {
22262
- "Access-Control-Allow-Origin": "*",
22263
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
22264
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
22265
- "Access-Control-Max-Age": "86400",
22266
- };
22267
-
22268
22628
  // Helper to create headers with CORS
22269
- function corsHeaders(contentType) {
22629
+ function corsHeaders(contentType, request, env) {
22270
22630
  return {
22271
22631
  "Content-Type": contentType,
22272
- ...CORS_HEADERS,
22632
+ ...runtimeCorsHeaders(request, env),
22273
22633
  };
22274
22634
  }
22275
22635
 
22276
- // Helper to add CORS headers to any Response without touching the body
22277
- function addCorsHeaders(response) {
22278
- // Skip WebSocket upgrade responses - they can't be wrapped
22279
- if (response.status === 101) {
22280
- return response;
22281
- }
22282
-
22283
- // Skip if already has CORS headers
22284
- if (response.headers.has("Access-Control-Allow-Origin")) {
22285
- return response;
22286
- }
22287
-
22288
- // Prefer mutating the original Response headers. Rebuilding a Response from
22289
- // response.headers can drop special headers such as Set-Cookie in Worker
22290
- // runtimes, even when the header is not visible through Headers.has().
22291
- try {
22292
- for (const [key, value] of Object.entries(CORS_HEADERS)) {
22293
- response.headers.set(key, value);
22294
- }
22295
- return response;
22296
- } catch {
22297
- // Some Responses, especially fetched upstream responses, have immutable
22298
- // headers. Fall back to wrapping those responses.
22299
- }
22300
-
22301
- // Create new headers with CORS added
22302
- const newHeaders = new Headers(response.headers);
22303
- for (const [key, value] of Object.entries(CORS_HEADERS)) {
22304
- newHeaders.set(key, value);
22305
- }
22306
-
22307
- // Return new Response with same body stream (not cloned, just transferred)
22308
- return new Response(response.body, {
22309
- status: response.status,
22310
- statusText: response.statusText,
22311
- headers: newHeaders,
22312
- });
22313
- }
22314
-
22315
22636
  export async function router(request, env) {
22316
22637
  const url = new URL(request.url);
22317
22638
  const pathname = url.pathname;
22318
22639
 
22319
22640
  // Handle CORS preflight requests
22320
22641
  if (request.method === "OPTIONS") {
22321
- return new Response(null, {
22322
- status: 204,
22323
- headers: CORS_HEADERS,
22324
- });
22642
+ return runtimePreflightResponse(request, env);
22643
+ }
22644
+
22645
+ // WebSockets do not use CORS. Reject foreign browser provenance before
22646
+ // route dispatch so a malicious page cannot open a local stream/bridge even
22647
+ // if a browser were to attach ambient cookies to the handshake.
22648
+ if (
22649
+ isTrustedLocalDevelopmentRequest(request, env) &&
22650
+ !isSameOriginBrowserRequest(request)
22651
+ ) {
22652
+ return Response.json(
22653
+ { error: "Cross-origin access is disabled for local development" },
22654
+ { status: 403, headers: { "Cache-Control": "no-store" } }
22655
+ );
22325
22656
  }
22326
22657
 
22327
22658
  const routePath = pathname || "/";
@@ -22369,13 +22700,13 @@ ${packedThreadRouteCode}
22369
22700
 
22370
22701
  // If requireAuth returns a Response, it's an error (401)
22371
22702
  if (authResult instanceof Response) {
22372
- return addCorsHeaders(authResult);
22703
+ return applyRuntimeCors(authResult, request, env);
22373
22704
  }
22374
22705
 
22375
22706
  authContext = authResult;
22376
22707
 
22377
22708
  if (isAdminOnlyApiRoute(routePath, request.method) && authContext.user.role !== 'admin') {
22378
- return addCorsHeaders(Response.json({ error: 'Forbidden: Admin access required' }, { status: 403 }));
22709
+ return applyRuntimeCors(Response.json({ error: 'Forbidden: Admin access required' }, { status: 403 }), request, env);
22379
22710
  }
22380
22711
 
22381
22712
  if (routePath.startsWith('/api/threads/')) {
@@ -22389,10 +22720,10 @@ ${packedThreadRouteCode}
22389
22720
  const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
22390
22721
  const thread = await agentBuilder.getThread(threadId);
22391
22722
  if (!thread) {
22392
- return addCorsHeaders(Response.json({ error: \`Thread not found: \${threadId}\` }, { status: 404 }));
22723
+ return applyRuntimeCors(Response.json({ error: \`Thread not found: \${threadId}\` }, { status: 404 }), request, env);
22393
22724
  }
22394
22725
  if (!canAccessThread(authContext, thread)) {
22395
- return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
22726
+ return applyRuntimeCors(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }), request, env);
22396
22727
  }
22397
22728
  threadAccess = { threadId, thread };
22398
22729
  }
@@ -22435,16 +22766,16 @@ ${packedThreadRouteCode}
22435
22766
  const result = await controller(context);
22436
22767
 
22437
22768
  if (result instanceof Response) {
22438
- return addCorsHeaders(result);
22769
+ return applyRuntimeCors(result, request, env);
22439
22770
  }
22440
22771
  if (typeof result === "string") {
22441
22772
  return new Response(result, {
22442
- headers: corsHeaders("text/plain"),
22773
+ headers: corsHeaders("text/plain", request, env),
22443
22774
  });
22444
22775
  }
22445
22776
  // JSON responses get CORS headers
22446
22777
  return new Response(JSON.stringify(result), {
22447
- headers: corsHeaders("application/json"),
22778
+ headers: corsHeaders("application/json", request, env),
22448
22779
  });
22449
22780
  }
22450
22781
 
@@ -23211,95 +23542,68 @@ var builder = agentbuilder;
23211
23542
  // src/router/index.ts
23212
23543
  init_ThreadStateImpl();
23213
23544
 
23214
- // src/utils/auth.ts
23215
- async function hashToken(token) {
23216
- const encoder = new TextEncoder();
23217
- const data = encoder.encode(token);
23218
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
23219
- const hashArray = new Uint8Array(hashBuffer);
23220
- return Array.from(hashArray, (byte) => byte.toString(16).padStart(2, "0")).join("");
23221
- }
23222
- var SESSION_COOKIE_NAME = "agtuser_session";
23223
- function readSessionCookie(request) {
23224
- const header = request.headers.get("Cookie");
23225
- if (!header) return null;
23226
- for (const part of header.split(";")) {
23227
- const eq = part.indexOf("=");
23228
- if (eq === -1) continue;
23229
- if (part.slice(0, eq).trim() === SESSION_COOKIE_NAME) {
23230
- return decodeURIComponent(part.slice(eq + 1).trim()) || null;
23545
+ // src/routing/cors.ts
23546
+ var RUNTIME_CORS_HEADERS = {
23547
+ "Access-Control-Allow-Origin": "*",
23548
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
23549
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
23550
+ "Access-Control-Max-Age": "86400"
23551
+ };
23552
+ var CORS_RESPONSE_HEADERS = [
23553
+ "Access-Control-Allow-Origin",
23554
+ "Access-Control-Allow-Methods",
23555
+ "Access-Control-Allow-Headers",
23556
+ "Access-Control-Max-Age",
23557
+ "Access-Control-Allow-Credentials",
23558
+ "Access-Control-Expose-Headers"
23559
+ ];
23560
+ function mutateCorsHeaders(headers, exposeCors) {
23561
+ if (exposeCors) {
23562
+ for (const [key, value] of Object.entries(RUNTIME_CORS_HEADERS)) {
23563
+ if (!headers.has(key)) headers.set(key, value);
23231
23564
  }
23565
+ return;
23566
+ }
23567
+ for (const key of CORS_RESPONSE_HEADERS) {
23568
+ headers.delete(key);
23232
23569
  }
23233
- return null;
23234
23570
  }
23235
- function isValidUserToken(token) {
23236
- return token.startsWith("agtuser_") && token.length > 10;
23571
+ function exposesRuntimeCors(request, env) {
23572
+ return !isTrustedLocalDevelopmentRequest(request, env);
23237
23573
  }
23238
- function isValidApiKey(key) {
23239
- return key.startsWith("agtbldr_") && key.length > 10 || key.startsWith("sak_live_") && key.length > 10;
23574
+ function runtimeCorsHeaders(request, env) {
23575
+ return exposesRuntimeCors(request, env) ? { ...RUNTIME_CORS_HEADERS } : {};
23240
23576
  }
23241
- async function verifySignedToken(signedToken, encryptionKey) {
23577
+ function applyRuntimeCors(response, request, env) {
23578
+ if (response.status === 101) return response;
23579
+ const exposeCors = exposesRuntimeCors(request, env);
23242
23580
  try {
23243
- const parts = signedToken.split(".");
23244
- if (parts.length !== 2) {
23245
- return null;
23246
- }
23247
- const [payloadB64, signatureB64] = parts;
23248
- const encoder = new TextEncoder();
23249
- const keyData = encoder.encode(encryptionKey);
23250
- const cryptoKey = await crypto.subtle.importKey(
23251
- "raw",
23252
- keyData,
23253
- { name: "HMAC", hash: "SHA-256" },
23254
- false,
23255
- ["verify"]
23256
- );
23257
- const payloadData = base64ToBuffer(payloadB64);
23258
- const signature = base64ToBuffer(signatureB64);
23259
- const isValid = await crypto.subtle.verify("HMAC", cryptoKey, signature, payloadData);
23260
- if (!isValid) {
23261
- return null;
23262
- }
23263
- const decoder = new TextDecoder();
23264
- return decoder.decode(payloadData);
23265
- } catch (error) {
23266
- console.error("Error verifying signed token:", error);
23267
- return null;
23268
- }
23269
- }
23270
- async function verifySuperAdminToken(token, encryptionKey) {
23271
- const payload = await verifySignedToken(token, encryptionKey);
23272
- if (!payload) {
23273
- return false;
23274
- }
23275
- const parts = payload.split(":");
23276
- if (parts.length !== 3 || parts[0] !== "super_admin") {
23277
- return false;
23278
- }
23279
- const timestamp = parseInt(parts[1], 10);
23280
- const age = Date.now() - timestamp;
23281
- const maxAge = 30 * 24 * 60 * 60 * 1e3;
23282
- if (age > maxAge) {
23283
- return false;
23581
+ mutateCorsHeaders(response.headers, exposeCors);
23582
+ return response;
23583
+ } catch {
23284
23584
  }
23285
- return true;
23585
+ const headers = new Headers(response.headers);
23586
+ mutateCorsHeaders(headers, exposeCors);
23587
+ return new Response(response.body, {
23588
+ status: response.status,
23589
+ statusText: response.statusText,
23590
+ headers
23591
+ });
23286
23592
  }
23287
- function base64ToBuffer(base64) {
23288
- const binary = atob(base64);
23289
- const bytes = new Uint8Array(binary.length);
23290
- for (let i = 0; i < binary.length; i++) {
23291
- bytes[i] = binary.charCodeAt(i);
23593
+ function runtimePreflightResponse(request, env) {
23594
+ if (!exposesRuntimeCors(request, env)) {
23595
+ return Response.json(
23596
+ { error: "Cross-origin access is disabled for local development" },
23597
+ { status: 403, headers: { "Cache-Control": "no-store" } }
23598
+ );
23292
23599
  }
23293
- return bytes;
23600
+ return new Response(null, {
23601
+ status: 204,
23602
+ headers: RUNTIME_CORS_HEADERS
23603
+ });
23294
23604
  }
23295
23605
 
23296
23606
  // src/middleware/auth.ts
23297
- var CORS_HEADERS = {
23298
- "Access-Control-Allow-Origin": "*",
23299
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
23300
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
23301
- "Access-Control-Max-Age": "86400"
23302
- };
23303
23607
  function endpointCallerFromAuth(auth) {
23304
23608
  if (!auth) return null;
23305
23609
  const { id, username, role, permissions, email, display_name, avatar_url } = auth.user;
@@ -23333,43 +23637,88 @@ function extractBearerToken(request) {
23333
23637
  if (cookieToken) {
23334
23638
  return cookieToken;
23335
23639
  }
23336
- const isWebSocket = request.headers.get("upgrade")?.toLowerCase() === "websocket";
23337
- if (isWebSocket) {
23338
- try {
23339
- const url = new URL(request.url);
23340
- const token = url.searchParams.get("token");
23341
- if (token) {
23342
- return token;
23343
- }
23344
- } catch (err) {
23345
- }
23346
- }
23347
23640
  return null;
23348
23641
  }
23349
23642
  function getAgentBuilder(env) {
23350
23643
  const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
23351
23644
  return env.AGENT_BUILDER.get(agentBuilderId);
23352
23645
  }
23646
+ function parseWebSocketTicketAuth(authJson) {
23647
+ let parsed;
23648
+ try {
23649
+ parsed = JSON.parse(authJson);
23650
+ } catch {
23651
+ return null;
23652
+ }
23653
+ if (!parsed || typeof parsed !== "object") return null;
23654
+ const record = parsed;
23655
+ if (!record.user || typeof record.user !== "object") return null;
23656
+ const user = record.user;
23657
+ if (typeof user.id !== "string" || typeof user.username !== "string" || typeof user.role !== "string" || record.authType !== "super_admin" && record.authType !== "session" && record.authType !== "api_key") {
23658
+ return null;
23659
+ }
23660
+ const permissions = user.permissions;
23661
+ if (permissions !== void 0 && permissions !== null && (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string"))) {
23662
+ return null;
23663
+ }
23664
+ if (record.credentialId !== void 0 && typeof record.credentialId !== "string") return null;
23665
+ return {
23666
+ user: {
23667
+ id: user.id,
23668
+ username: user.username,
23669
+ role: user.role,
23670
+ permissions,
23671
+ platform_user_id: typeof user.platform_user_id === "string" ? user.platform_user_id : null,
23672
+ email: typeof user.email === "string" ? user.email : null,
23673
+ display_name: typeof user.display_name === "string" ? user.display_name : null,
23674
+ avatar_url: typeof user.avatar_url === "string" ? user.avatar_url : null
23675
+ },
23676
+ authType: record.authType,
23677
+ credentialId: record.credentialId
23678
+ };
23679
+ }
23680
+ async function consumeWebSocketTicket(request, env) {
23681
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") return null;
23682
+ let url;
23683
+ try {
23684
+ url = new URL(request.url);
23685
+ } catch {
23686
+ return null;
23687
+ }
23688
+ const ticket = url.searchParams.get("ticket");
23689
+ if (!ticket) return null;
23690
+ const authJson = await getAgentBuilder(env).consumeWebSocketTicket({
23691
+ ticket_hash: await hashToken(ticket),
23692
+ pathname: url.pathname
23693
+ });
23694
+ return typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
23695
+ }
23353
23696
  async function authenticate(request, env) {
23697
+ const ticketAuth = await consumeWebSocketTicket(request, env);
23698
+ if (ticketAuth) return ticketAuth;
23354
23699
  const token = extractBearerToken(request);
23355
23700
  if (!token) {
23356
23701
  return null;
23357
23702
  }
23358
- if (env.SUPER_ADMIN_PASSWORD && env.ENCRYPTION_KEY) {
23703
+ if (typeof env.SUPER_ADMIN_PASSWORD === "string" && typeof env.ENCRYPTION_KEY === "string") {
23359
23704
  if (token.includes(".")) {
23360
23705
  const isValidSuperAdmin = await verifySuperAdminToken(
23361
23706
  token,
23362
23707
  env.ENCRYPTION_KEY
23363
23708
  );
23364
23709
  if (isValidSuperAdmin) {
23365
- return {
23366
- user: {
23367
- id: "super_admin",
23368
- username: "admin",
23369
- role: "admin"
23370
- },
23371
- authType: "super_admin"
23372
- };
23710
+ const tokenHash = await hashToken(token);
23711
+ const agentBuilder = getAgentBuilder(env);
23712
+ if (await agentBuilder.validateSuperAdminSession(tokenHash)) {
23713
+ return {
23714
+ user: {
23715
+ id: "super_admin",
23716
+ username: "admin",
23717
+ role: "admin"
23718
+ },
23719
+ authType: "super_admin"
23720
+ };
23721
+ }
23373
23722
  }
23374
23723
  }
23375
23724
  }
@@ -23414,7 +23763,8 @@ async function authenticate(request, env) {
23414
23763
  display_name: user.display_name ?? null,
23415
23764
  avatar_url: user.avatar_url ?? null
23416
23765
  },
23417
- authType: "api_key"
23766
+ authType: "api_key",
23767
+ credentialId: apiKey.id
23418
23768
  };
23419
23769
  }
23420
23770
  }
@@ -23426,7 +23776,10 @@ async function requireAuth(request, env) {
23426
23776
  if (!authContext) {
23427
23777
  return new Response(JSON.stringify({ error: "Unauthorized" }), {
23428
23778
  status: 401,
23429
- headers: { "Content-Type": "application/json", ...CORS_HEADERS }
23779
+ headers: {
23780
+ "Content-Type": "application/json",
23781
+ ...runtimeCorsHeaders(request, env)
23782
+ }
23430
23783
  });
23431
23784
  }
23432
23785
  return authContext;
@@ -23441,7 +23794,10 @@ async function requireAdmin(request, env) {
23441
23794
  JSON.stringify({ error: "Forbidden: Admin access required" }),
23442
23795
  {
23443
23796
  status: 403,
23444
- headers: { "Content-Type": "application/json", ...CORS_HEADERS }
23797
+ headers: {
23798
+ "Content-Type": "application/json",
23799
+ ...runtimeCorsHeaders(request, env)
23800
+ }
23445
23801
  }
23446
23802
  );
23447
23803
  }
@@ -24426,8 +24782,37 @@ var migration35 = {
24426
24782
  }
24427
24783
  };
24428
24784
 
24785
+ // src/durable-objects/migrations/036_add_tool_delivery_tokens.ts
24786
+ var migration36 = {
24787
+ version: 36,
24788
+ name: "add_tool_delivery_tokens",
24789
+ async up(sql) {
24790
+ sql.exec(`
24791
+ CREATE TABLE IF NOT EXISTS pending_client_calls (
24792
+ tool_call_id TEXT PRIMARY KEY,
24793
+ tool TEXT NOT NULL,
24794
+ args TEXT,
24795
+ request_permission TEXT,
24796
+ risk INTEGER,
24797
+ side TEXT NOT NULL DEFAULT 'a',
24798
+ status TEXT NOT NULL DEFAULT 'pending',
24799
+ created_at INTEGER NOT NULL,
24800
+ dispatched_client_id TEXT,
24801
+ dispatched_generation INTEGER NOT NULL DEFAULT 0
24802
+ )
24803
+ `);
24804
+ const columns = sql.exec(
24805
+ `PRAGMA table_info(pending_client_calls)`
24806
+ ).toArray();
24807
+ if (!columns.some((column) => column.name === "delivery_token_hash")) {
24808
+ sql.exec(`ALTER TABLE pending_client_calls ADD COLUMN delivery_token_hash TEXT`);
24809
+ }
24810
+ sql.exec(`UPDATE _metadata SET value = '36' WHERE key = 'schema_version'`);
24811
+ }
24812
+ };
24813
+
24429
24814
  // src/durable-objects/migrations/index.ts
24430
- var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17, migration18, migration19, migration20, migration21, migration22, migration23, migration24, migration25, migration26, migration27, migration28, migration29, migration30, migration31, migration32, migration33, migration34, migration35];
24815
+ var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17, migration18, migration19, migration20, migration21, migration22, migration23, migration24, migration25, migration26, migration27, migration28, migration29, migration30, migration31, migration32, migration33, migration34, migration35, migration36];
24431
24816
  var LATEST_SCHEMA_VERSION = migrations.length;
24432
24817
 
24433
24818
  // src/durable-objects/subagent-reconcile.ts
@@ -25263,7 +25648,7 @@ function eventSocketCanReceive(attachment, ownerUserId) {
25263
25648
  }
25264
25649
 
25265
25650
  // src/durable-objects/agentbuilder-migrations/0001_initial.ts
25266
- var migration36 = {
25651
+ var migration37 = {
25267
25652
  version: 1,
25268
25653
  async up(sql) {
25269
25654
  sql.exec(`
@@ -25362,7 +25747,7 @@ var migration36 = {
25362
25747
  };
25363
25748
 
25364
25749
  // src/durable-objects/agentbuilder-migrations/0002_add_tenvs_properties.ts
25365
- var migration37 = {
25750
+ var migration38 = {
25366
25751
  version: 2,
25367
25752
  async up(sql) {
25368
25753
  sql.exec(`ALTER TABLE threads ADD COLUMN tenvs TEXT`);
@@ -25374,7 +25759,7 @@ var migration37 = {
25374
25759
  };
25375
25760
 
25376
25761
  // src/durable-objects/agentbuilder-migrations/0003_add_parent_and_terminated.ts
25377
- var migration38 = {
25762
+ var migration39 = {
25378
25763
  version: 3,
25379
25764
  async up(sql) {
25380
25765
  sql.exec(`ALTER TABLE threads ADD COLUMN parent TEXT REFERENCES threads(id)`);
@@ -25387,7 +25772,7 @@ var migration38 = {
25387
25772
  };
25388
25773
 
25389
25774
  // src/durable-objects/agentbuilder-migrations/0004_add_env_tables.ts
25390
- var migration39 = {
25775
+ var migration40 = {
25391
25776
  version: 4,
25392
25777
  async up(sql) {
25393
25778
  sql.exec(`ALTER TABLE threads ADD COLUMN env TEXT`);
@@ -25411,7 +25796,7 @@ var migration39 = {
25411
25796
  };
25412
25797
 
25413
25798
  // src/durable-objects/agentbuilder-migrations/0005_add_pending_subagent_env_requests.ts
25414
- var migration40 = {
25799
+ var migration41 = {
25415
25800
  version: 5,
25416
25801
  async up(sql) {
25417
25802
  sql.exec(`
@@ -25437,7 +25822,7 @@ var migration40 = {
25437
25822
  };
25438
25823
 
25439
25824
  // src/durable-objects/agentbuilder-migrations/0006_add_env_value_types.ts
25440
- var migration41 = {
25825
+ var migration42 = {
25441
25826
  version: 6,
25442
25827
  async up(sql) {
25443
25828
  sql.exec(
@@ -25452,7 +25837,7 @@ var migration41 = {
25452
25837
  };
25453
25838
 
25454
25839
  // src/durable-objects/agentbuilder-migrations/0007_platform_identity_replica.ts
25455
- var migration42 = {
25840
+ var migration43 = {
25456
25841
  version: 7,
25457
25842
  async up(sql) {
25458
25843
  sql.exec(`ALTER TABLE users ADD COLUMN platform_user_id TEXT`);
@@ -25481,7 +25866,7 @@ var migration42 = {
25481
25866
  };
25482
25867
 
25483
25868
  // src/durable-objects/agentbuilder-migrations/0008_add_skills.ts
25484
- var migration43 = {
25869
+ var migration44 = {
25485
25870
  version: 8,
25486
25871
  async up(sql) {
25487
25872
  sql.exec(`
@@ -25512,7 +25897,7 @@ var migration43 = {
25512
25897
  };
25513
25898
 
25514
25899
  // src/durable-objects/agentbuilder-migrations/0009_add_user_permissions.ts
25515
- var migration44 = {
25900
+ var migration45 = {
25516
25901
  version: 9,
25517
25902
  async up(sql) {
25518
25903
  sql.exec(`ALTER TABLE users ADD COLUMN permissions TEXT`);
@@ -25523,7 +25908,7 @@ var migration44 = {
25523
25908
  };
25524
25909
 
25525
25910
  // src/durable-objects/agentbuilder-migrations/0010_add_device_logins.ts
25526
- var migration45 = {
25911
+ var migration46 = {
25527
25912
  version: 10,
25528
25913
  async up(sql) {
25529
25914
  sql.exec(`
@@ -25543,7 +25928,7 @@ var migration45 = {
25543
25928
  };
25544
25929
 
25545
25930
  // src/durable-objects/agentbuilder-migrations/0011_add_user_values.ts
25546
- var migration46 = {
25931
+ var migration47 = {
25547
25932
  version: 11,
25548
25933
  async up(sql) {
25549
25934
  sql.exec(`
@@ -25566,7 +25951,7 @@ var migration46 = {
25566
25951
  };
25567
25952
 
25568
25953
  // src/durable-objects/agentbuilder-migrations/0012_allow_api_instance_role.ts
25569
- var migration47 = {
25954
+ var migration48 = {
25570
25955
  version: 12,
25571
25956
  async up(sql) {
25572
25957
  sql.exec(`CREATE TABLE oauth_accounts_0012_backup AS SELECT * FROM oauth_accounts`);
@@ -25676,7 +26061,7 @@ var migration47 = {
25676
26061
  };
25677
26062
 
25678
26063
  // src/durable-objects/agentbuilder-migrations/0013_add_thread_activity.ts
25679
- var migration48 = {
26064
+ var migration49 = {
25680
26065
  version: 13,
25681
26066
  async up(sql) {
25682
26067
  sql.exec(`ALTER TABLE threads ADD COLUMN last_activity_at INTEGER`);
@@ -25692,7 +26077,7 @@ var migration48 = {
25692
26077
  };
25693
26078
 
25694
26079
  // src/durable-objects/agentbuilder-migrations/0014_thread_activity_scale.ts
25695
- var migration49 = {
26080
+ var migration50 = {
25696
26081
  version: 14,
25697
26082
  async up(sql) {
25698
26083
  sql.exec(`ALTER TABLE threads ADD COLUMN last_message_preview TEXT`);
@@ -25710,7 +26095,7 @@ var migration49 = {
25710
26095
  };
25711
26096
 
25712
26097
  // src/durable-objects/agentbuilder-migrations/0015_clear_envelope_previews.ts
25713
- var migration50 = {
26098
+ var migration51 = {
25714
26099
  version: 15,
25715
26100
  async up(sql) {
25716
26101
  sql.exec(
@@ -25724,7 +26109,7 @@ var migration50 = {
25724
26109
  };
25725
26110
 
25726
26111
  // src/durable-objects/agentbuilder-migrations/0016_add_origin_agent.ts
25727
- var migration51 = {
26112
+ var migration52 = {
25728
26113
  version: 16,
25729
26114
  async up(sql) {
25730
26115
  sql.exec(`ALTER TABLE threads ADD COLUMN origin_agent_name TEXT`);
@@ -25739,7 +26124,7 @@ var migration51 = {
25739
26124
  };
25740
26125
 
25741
26126
  // src/durable-objects/agentbuilder-migrations/0017_add_thread_message_recency.ts
25742
- var migration52 = {
26127
+ var migration53 = {
25743
26128
  version: 17,
25744
26129
  async up(sql) {
25745
26130
  sql.exec(`ALTER TABLE threads ADD COLUMN last_message_at INTEGER`);
@@ -25750,10 +26135,132 @@ var migration52 = {
25750
26135
  }
25751
26136
  };
25752
26137
 
26138
+ // src/durable-objects/agentbuilder-migrations/0018_auth_security.ts
26139
+ var migration54 = {
26140
+ version: 18,
26141
+ async up(sql) {
26142
+ sql.exec(`
26143
+ CREATE TABLE IF NOT EXISTS super_admin_sessions (
26144
+ token_hash TEXT PRIMARY KEY,
26145
+ created_at INTEGER NOT NULL,
26146
+ expires_at INTEGER NOT NULL
26147
+ )
26148
+ `);
26149
+ sql.exec(`
26150
+ CREATE INDEX IF NOT EXISTS idx_super_admin_sessions_expires
26151
+ ON super_admin_sessions(expires_at)
26152
+ `);
26153
+ sql.exec(`
26154
+ CREATE TABLE IF NOT EXISTS auth_rate_limits (
26155
+ bucket_key TEXT PRIMARY KEY,
26156
+ attempts INTEGER NOT NULL,
26157
+ reset_at INTEGER NOT NULL
26158
+ )
26159
+ `);
26160
+ sql.exec(`
26161
+ CREATE INDEX IF NOT EXISTS idx_auth_rate_limits_reset
26162
+ ON auth_rate_limits(reset_at)
26163
+ `);
26164
+ sql.exec(`
26165
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '18')
26166
+ `);
26167
+ }
26168
+ };
26169
+
26170
+ // src/durable-objects/agentbuilder-migrations/0019_scope_skills.ts
26171
+ var migration55 = {
26172
+ version: 19,
26173
+ async up(sql) {
26174
+ sql.exec(`
26175
+ CREATE TABLE skills_0019 (
26176
+ owner_id TEXT NOT NULL,
26177
+ name TEXT NOT NULL,
26178
+ description TEXT NOT NULL,
26179
+ version TEXT,
26180
+ enabled INTEGER NOT NULL DEFAULT 1,
26181
+ shared INTEGER NOT NULL DEFAULT 0,
26182
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
26183
+ updated_at INTEGER DEFAULT (strftime('%s', 'now')),
26184
+ PRIMARY KEY (owner_id, name)
26185
+ )
26186
+ `);
26187
+ sql.exec(`
26188
+ INSERT INTO skills_0019 (
26189
+ owner_id, name, description, version, enabled, shared, created_at, updated_at
26190
+ )
26191
+ SELECT '__instance_shared__', name, description, version, enabled, 1, created_at, updated_at
26192
+ FROM skills
26193
+ `);
26194
+ sql.exec(`
26195
+ CREATE TABLE skill_files_0019 (
26196
+ owner_id TEXT NOT NULL,
26197
+ skill_name TEXT NOT NULL,
26198
+ path TEXT NOT NULL,
26199
+ content TEXT NOT NULL,
26200
+ size INTEGER NOT NULL,
26201
+ PRIMARY KEY (owner_id, skill_name, path),
26202
+ FOREIGN KEY (owner_id, skill_name) REFERENCES skills_0019(owner_id, name) ON DELETE CASCADE
26203
+ )
26204
+ `);
26205
+ sql.exec(`
26206
+ INSERT INTO skill_files_0019 (owner_id, skill_name, path, content, size)
26207
+ SELECT '__instance_shared__', skill_name, path, content, size FROM skill_files
26208
+ `);
26209
+ sql.exec(`DROP TABLE skill_files`);
26210
+ sql.exec(`DROP TABLE skills`);
26211
+ sql.exec(`ALTER TABLE skills_0019 RENAME TO skills`);
26212
+ sql.exec(`ALTER TABLE skill_files_0019 RENAME TO skill_files`);
26213
+ sql.exec(`CREATE INDEX idx_skills_owner_enabled ON skills(owner_id, enabled)`);
26214
+ sql.exec(`CREATE INDEX idx_skills_shared_enabled ON skills(shared, enabled)`);
26215
+ sql.exec(`CREATE INDEX idx_skill_files_skill ON skill_files(owner_id, skill_name)`);
26216
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '19')`);
26217
+ }
26218
+ };
26219
+
26220
+ // src/durable-objects/agentbuilder-migrations/0020_websocket_tickets.ts
26221
+ var migration56 = {
26222
+ version: 20,
26223
+ async up(sql) {
26224
+ sql.exec(`
26225
+ CREATE TABLE websocket_tickets (
26226
+ ticket_hash TEXT PRIMARY KEY,
26227
+ pathname TEXT NOT NULL,
26228
+ auth_json TEXT NOT NULL,
26229
+ created_at INTEGER NOT NULL,
26230
+ expires_at INTEGER NOT NULL
26231
+ )
26232
+ `);
26233
+ sql.exec(`
26234
+ CREATE INDEX idx_websocket_tickets_expires
26235
+ ON websocket_tickets(expires_at)
26236
+ `);
26237
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '20')`);
26238
+ }
26239
+ };
26240
+
26241
+ // src/durable-objects/agentbuilder-migrations/0021_platform_handoff_jtis.ts
26242
+ var migration57 = {
26243
+ version: 21,
26244
+ async up(sql) {
26245
+ sql.exec(`
26246
+ CREATE TABLE platform_handoff_jtis (
26247
+ jti TEXT PRIMARY KEY,
26248
+ consumed_at INTEGER NOT NULL,
26249
+ expires_at INTEGER NOT NULL
26250
+ )
26251
+ `);
26252
+ sql.exec(`
26253
+ CREATE INDEX idx_platform_handoff_jtis_expires
26254
+ ON platform_handoff_jtis(expires_at)
26255
+ `);
26256
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '21')`);
26257
+ }
26258
+ };
26259
+
25753
26260
  // src/durable-objects/agentbuilder-migrations/index.ts
25754
- var migrations2 = [migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52];
26261
+ var migrations2 = [migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57];
25755
26262
  var LATEST_SCHEMA_VERSION2 = migrations2.reduce(
25756
- (max, migration53) => Math.max(max, migration53.version),
26263
+ (max, migration58) => Math.max(max, migration58.version),
25757
26264
  0
25758
26265
  );
25759
26266
 
@@ -26258,6 +26765,7 @@ function validateSkillFiles(files) {
26258
26765
  }
26259
26766
 
26260
26767
  // src/durable-objects/DurableAgentBuilder.ts
26768
+ init_scope();
26261
26769
  var ENV_TYPES_PROPERTY = "__agentbuilder_env_types";
26262
26770
  function parseStoredPermissions(value) {
26263
26771
  if (typeof value !== "string" || !value.trim()) return null;
@@ -26850,9 +27358,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
26850
27358
  }
26851
27359
  }
26852
27360
  async runMigrations(fromVersion) {
26853
- for (const migration53 of migrations2) {
26854
- if (migration53.version > fromVersion) {
26855
- await migration53.up(this.ctx.storage.sql);
27361
+ for (const migration58 of migrations2) {
27362
+ if (migration58.version > fromVersion) {
27363
+ await migration58.up(this.ctx.storage.sql);
26856
27364
  }
26857
27365
  }
26858
27366
  }
@@ -28801,6 +29309,150 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
28801
29309
  tokenHash
28802
29310
  );
28803
29311
  }
29312
+ /** Register a short-lived super-admin token so it can be revoked alone. */
29313
+ async createSuperAdminSession(params) {
29314
+ await this.ensureMigrated();
29315
+ const now = Math.floor(Date.now() / 1e3);
29316
+ await this.ctx.storage.sql.exec(
29317
+ `DELETE FROM super_admin_sessions WHERE expires_at <= ?`,
29318
+ now
29319
+ );
29320
+ await this.ctx.storage.sql.exec(
29321
+ `INSERT INTO super_admin_sessions (token_hash, created_at, expires_at)
29322
+ VALUES (?, ?, ?)`,
29323
+ params.token_hash,
29324
+ now,
29325
+ params.expires_at
29326
+ );
29327
+ }
29328
+ /** A valid signature is insufficient: the session must still be registered. */
29329
+ async validateSuperAdminSession(tokenHash) {
29330
+ await this.ensureMigrated();
29331
+ const now = Math.floor(Date.now() / 1e3);
29332
+ const cursor = await this.ctx.storage.sql.exec(
29333
+ `SELECT 1 AS found FROM super_admin_sessions
29334
+ WHERE token_hash = ? AND expires_at > ?`,
29335
+ tokenHash,
29336
+ now
29337
+ );
29338
+ return cursor.toArray().length > 0;
29339
+ }
29340
+ /** Revoke only the presented super-admin session; no key rotation required. */
29341
+ async deleteSuperAdminSession(tokenHash) {
29342
+ await this.ensureMigrated();
29343
+ await this.ctx.storage.sql.exec(
29344
+ `DELETE FROM super_admin_sessions WHERE token_hash = ?`,
29345
+ tokenHash
29346
+ );
29347
+ }
29348
+ /** Register a path-bound bearer ticket for one WebSocket handshake. */
29349
+ async createWebSocketTicket(params) {
29350
+ await this.ensureMigrated();
29351
+ const now = Math.floor(Date.now() / 1e3);
29352
+ this.ctx.storage.sql.exec(
29353
+ `DELETE FROM websocket_tickets WHERE expires_at <= ?`,
29354
+ now
29355
+ );
29356
+ this.ctx.storage.sql.exec(
29357
+ `INSERT INTO websocket_tickets (ticket_hash, pathname, auth_json, created_at, expires_at)
29358
+ VALUES (?, ?, ?, ?, ?)`,
29359
+ params.ticket_hash,
29360
+ params.pathname,
29361
+ params.auth_json,
29362
+ now,
29363
+ params.expires_at
29364
+ );
29365
+ }
29366
+ /**
29367
+ * Atomically consume a WebSocket ticket. Deleting before validating its path
29368
+ * means even a mistaken or malicious first presentation burns the bearer.
29369
+ */
29370
+ async consumeWebSocketTicket(params) {
29371
+ await this.ensureMigrated();
29372
+ const cursor = this.ctx.storage.sql.exec(
29373
+ `DELETE FROM websocket_tickets
29374
+ WHERE ticket_hash = ?
29375
+ RETURNING pathname, auth_json, expires_at`,
29376
+ params.ticket_hash
29377
+ );
29378
+ const ticket = cursor.toArray()[0];
29379
+ const now = Math.floor(Date.now() / 1e3);
29380
+ if (!ticket || ticket.expires_at <= now || ticket.pathname !== params.pathname) {
29381
+ return null;
29382
+ }
29383
+ return ticket.auth_json;
29384
+ }
29385
+ /** Atomically accept an unexpired platform handoff identifier once. */
29386
+ async consumePlatformHandoffJti(params) {
29387
+ await this.ensureMigrated();
29388
+ const jti = params.jti.trim();
29389
+ const expiresAt = Math.floor(params.expires_at);
29390
+ const now = Math.floor(Date.now() / 1e3);
29391
+ if (!jti || jti.length > 200 || !Number.isSafeInteger(expiresAt) || expiresAt <= now) {
29392
+ return false;
29393
+ }
29394
+ this.ctx.storage.sql.exec(
29395
+ `DELETE FROM platform_handoff_jtis WHERE expires_at <= ?`,
29396
+ now
29397
+ );
29398
+ const cursor = this.ctx.storage.sql.exec(
29399
+ `INSERT OR IGNORE INTO platform_handoff_jtis (jti, consumed_at, expires_at)
29400
+ VALUES (?, ?, ?)`,
29401
+ jti,
29402
+ now,
29403
+ expiresAt
29404
+ );
29405
+ return cursor.rowsWritten > 0;
29406
+ }
29407
+ /**
29408
+ * Atomically consume one attempt from a fixed-window authentication bucket.
29409
+ * The singleton Durable Object serializes calls, preventing parallel bypass.
29410
+ */
29411
+ async consumeAuthRateLimit(params) {
29412
+ await this.ensureMigrated();
29413
+ const key = params.key.trim();
29414
+ const limit = Math.floor(params.limit);
29415
+ const windowSeconds = Math.floor(params.window_seconds);
29416
+ if (!key || key.length > 160 || limit < 1 || limit > 1e4 || windowSeconds < 1 || windowSeconds > 86400) {
29417
+ throw new Error("Invalid authentication rate-limit bucket");
29418
+ }
29419
+ const now = Math.floor(Date.now() / 1e3);
29420
+ await this.ctx.storage.sql.exec(
29421
+ `DELETE FROM auth_rate_limits WHERE reset_at <= ?`,
29422
+ now
29423
+ );
29424
+ const cursor = await this.ctx.storage.sql.exec(
29425
+ `SELECT attempts, reset_at FROM auth_rate_limits WHERE bucket_key = ?`,
29426
+ key
29427
+ );
29428
+ const current = cursor.toArray()[0];
29429
+ if (!current || current.reset_at <= now) {
29430
+ const resetAt = now + windowSeconds;
29431
+ await this.ctx.storage.sql.exec(
29432
+ `INSERT INTO auth_rate_limits (bucket_key, attempts, reset_at)
29433
+ VALUES (?, 1, ?)
29434
+ ON CONFLICT(bucket_key) DO UPDATE SET attempts = 1, reset_at = excluded.reset_at`,
29435
+ key,
29436
+ resetAt
29437
+ );
29438
+ return { allowed: true, retry_after: windowSeconds, remaining: limit - 1 };
29439
+ }
29440
+ const retryAfter = Math.max(1, current.reset_at - now);
29441
+ if (current.attempts >= limit) {
29442
+ return { allowed: false, retry_after: retryAfter, remaining: 0 };
29443
+ }
29444
+ const attempts = current.attempts + 1;
29445
+ await this.ctx.storage.sql.exec(
29446
+ `UPDATE auth_rate_limits SET attempts = ? WHERE bucket_key = ?`,
29447
+ attempts,
29448
+ key
29449
+ );
29450
+ return {
29451
+ allowed: true,
29452
+ retry_after: retryAfter,
29453
+ remaining: Math.max(0, limit - attempts)
29454
+ };
29455
+ }
28804
29456
  // ============================================================
28805
29457
  // API Key Methods
28806
29458
  // ============================================================
@@ -29118,40 +29770,61 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
29118
29770
  }
29119
29771
  }
29120
29772
  // ─────────────────────────────────────────────────────────────────────────
29121
- // Skills — the instance-global Agent Skills library (agentskills.io format).
29122
- // Installed once, visible to every thread; file contents live here so
29123
- // nothing about a skill is stored on a client machine.
29773
+ // Skills — owner-scoped Agent Skills plus explicitly shared admin skills.
29124
29774
  // ─────────────────────────────────────────────────────────────────────────
29125
29775
  /** Installed skills, metadata only (no file contents). */
29126
- async listSkills(options = {}) {
29776
+ async listSkills(scope, options = {}) {
29127
29777
  await this.ensureMigrated();
29128
- const where = options.includeDisabled ? "" : "WHERE enabled = 1";
29129
29778
  const cursor = await this.ctx.storage.sql.exec(
29130
29779
  `SELECT s.name, s.description, s.version, s.enabled, s.updated_at,
29131
- (SELECT json_group_array(f.path) FROM skill_files f WHERE f.skill_name = s.name) AS files
29132
- FROM skills s ${where} ORDER BY s.name`
29780
+ s.owner_id,
29781
+ (SELECT json_group_array(f.path) FROM skill_files f
29782
+ WHERE f.owner_id = s.owner_id AND f.skill_name = s.name) AS files
29783
+ FROM skills s
29784
+ WHERE (s.owner_id = ? OR (? = 1 AND s.shared = 1))
29785
+ AND (? = 1 OR s.enabled = 1)
29786
+ ORDER BY s.name, CASE WHEN s.owner_id = ? THEN 0 ELSE 1 END`,
29787
+ scope.ownerId,
29788
+ scope.includeShared ? 1 : 0,
29789
+ options.includeDisabled ? 1 : 0,
29790
+ scope.ownerId
29133
29791
  );
29134
- return cursor.toArray().map((row) => ({
29135
- name: row.name,
29136
- description: row.description,
29137
- version: row.version ?? void 0,
29138
- enabled: row.enabled === 1,
29139
- files: row.files ? JSON.parse(row.files).sort() : [],
29140
- updatedAt: row.updated_at
29141
- }));
29792
+ const skills = /* @__PURE__ */ new Map();
29793
+ for (const row of cursor.toArray()) {
29794
+ if (skills.has(row.name)) continue;
29795
+ const parsedFiles = row.files ? JSON.parse(row.files) : [];
29796
+ const files = Array.isArray(parsedFiles) ? parsedFiles.filter((path21) => typeof path21 === "string").sort() : [];
29797
+ skills.set(row.name, {
29798
+ name: row.name,
29799
+ description: row.description,
29800
+ version: row.version ?? void 0,
29801
+ enabled: row.enabled === 1,
29802
+ files,
29803
+ updatedAt: row.updated_at
29804
+ });
29805
+ }
29806
+ return [...skills.values()];
29142
29807
  }
29143
29808
  /** One skill's metadata (null when not installed). */
29144
- async getSkill(name) {
29145
- const all = await this.listSkills({ includeDisabled: true });
29809
+ async getSkill(scope, name) {
29810
+ const all = await this.listSkills(scope, { includeDisabled: true });
29146
29811
  return all.find((s) => s.name === name) ?? null;
29147
29812
  }
29148
29813
  /** One bundled file's text (null when the skill or file is missing). */
29149
- async getSkillFile(name, path21) {
29814
+ async getSkillFile(scope, name, path21) {
29150
29815
  await this.ensureMigrated();
29151
29816
  const cursor = await this.ctx.storage.sql.exec(
29152
- `SELECT content FROM skill_files WHERE skill_name = ? AND path = ?`,
29817
+ `SELECT f.content FROM skill_files f
29818
+ JOIN skills s ON s.owner_id = f.owner_id AND s.name = f.skill_name
29819
+ WHERE f.skill_name = ? AND f.path = ?
29820
+ AND (f.owner_id = ? OR (? = 1 AND s.shared = 1))
29821
+ ORDER BY CASE WHEN f.owner_id = ? THEN 0 ELSE 1 END
29822
+ LIMIT 1`,
29153
29823
  name,
29154
- path21.replace(/^\.\//, "")
29824
+ path21.replace(/^\.\//, ""),
29825
+ scope.ownerId,
29826
+ scope.includeShared ? 1 : 0,
29827
+ scope.ownerId
29155
29828
  );
29156
29829
  const rows = cursor.toArray();
29157
29830
  return rows.length ? rows[0].content : null;
@@ -29162,44 +29835,56 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
29162
29835
  * that frontmatter. Throws SkillValidationError with an actionable message
29163
29836
  * on any standard violation.
29164
29837
  */
29165
- async upsertSkill(files) {
29838
+ async upsertSkill(scope, files) {
29166
29839
  await this.ensureMigrated();
29167
29840
  const frontmatter = validateSkillFiles(files);
29168
29841
  const name = frontmatter.name;
29169
- const existing = await this.getSkill(name);
29170
- await this.ctx.storage.sql.exec(`DELETE FROM skill_files WHERE skill_name = ?`, name);
29842
+ const ownScope = { ownerId: scope.ownerId };
29843
+ const existing = await this.getSkill(ownScope, name);
29171
29844
  await this.ctx.storage.sql.exec(
29172
- `INSERT INTO skills (name, description, version, enabled, updated_at)
29173
- VALUES (?, ?, ?, ?, strftime('%s', 'now'))
29174
- ON CONFLICT(name) DO UPDATE SET
29845
+ `DELETE FROM skill_files WHERE owner_id = ? AND skill_name = ?`,
29846
+ scope.ownerId,
29847
+ name
29848
+ );
29849
+ await this.ctx.storage.sql.exec(
29850
+ `INSERT INTO skills (owner_id, name, description, version, enabled, shared, updated_at)
29851
+ VALUES (?, ?, ?, ?, ?, ?, strftime('%s', 'now'))
29852
+ ON CONFLICT(owner_id, name) DO UPDATE SET
29175
29853
  description = excluded.description,
29176
29854
  version = excluded.version,
29177
29855
  updated_at = excluded.updated_at`,
29856
+ scope.ownerId,
29178
29857
  name,
29179
29858
  frontmatter.description,
29180
29859
  frontmatter.version ?? null,
29181
- existing ? existing.enabled ? 1 : 0 : 1
29860
+ existing ? existing.enabled ? 1 : 0 : 1,
29861
+ scope.ownerId === SHARED_SKILL_OWNER ? 1 : 0
29182
29862
  );
29183
29863
  for (const f of files) {
29184
29864
  const p = f.path.replace(/^\.\//, "");
29185
29865
  await this.ctx.storage.sql.exec(
29186
- `INSERT INTO skill_files (skill_name, path, content, size) VALUES (?, ?, ?, ?)`,
29866
+ `INSERT INTO skill_files (owner_id, skill_name, path, content, size) VALUES (?, ?, ?, ?, ?)`,
29867
+ scope.ownerId,
29187
29868
  name,
29188
29869
  p,
29189
29870
  f.content,
29190
29871
  f.content.length
29191
29872
  );
29192
29873
  }
29193
- return await this.getSkill(name);
29874
+ const installed = await this.getSkill(ownScope, name);
29875
+ if (!installed) throw new Error(`Installed skill was not found: ${name}`);
29876
+ return installed;
29194
29877
  }
29195
29878
  /** Enable/disable an installed skill. Returns false when not installed. */
29196
- async setSkillEnabled(name, enabled) {
29879
+ async setSkillEnabled(scope, name, enabled) {
29197
29880
  await this.ensureMigrated();
29198
- const existing = await this.getSkill(name);
29881
+ const existing = await this.getSkill({ ownerId: scope.ownerId }, name);
29199
29882
  if (!existing) return false;
29200
29883
  await this.ctx.storage.sql.exec(
29201
- `UPDATE skills SET enabled = ?, updated_at = strftime('%s', 'now') WHERE name = ?`,
29884
+ `UPDATE skills SET enabled = ?, updated_at = strftime('%s', 'now')
29885
+ WHERE owner_id = ? AND name = ?`,
29202
29886
  enabled ? 1 : 0,
29887
+ scope.ownerId,
29203
29888
  name
29204
29889
  );
29205
29890
  return true;
@@ -29274,12 +29959,20 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
29274
29959
  return true;
29275
29960
  }
29276
29961
  /** Uninstall a skill and its files. Returns false when not installed. */
29277
- async deleteSkill(name) {
29962
+ async deleteSkill(scope, name) {
29278
29963
  await this.ensureMigrated();
29279
- const existing = await this.getSkill(name);
29964
+ const existing = await this.getSkill({ ownerId: scope.ownerId }, name);
29280
29965
  if (!existing) return false;
29281
- await this.ctx.storage.sql.exec(`DELETE FROM skill_files WHERE skill_name = ?`, name);
29282
- await this.ctx.storage.sql.exec(`DELETE FROM skills WHERE name = ?`, name);
29966
+ await this.ctx.storage.sql.exec(
29967
+ `DELETE FROM skill_files WHERE owner_id = ? AND skill_name = ?`,
29968
+ scope.ownerId,
29969
+ name
29970
+ );
29971
+ await this.ctx.storage.sql.exec(
29972
+ `DELETE FROM skills WHERE owner_id = ? AND name = ?`,
29973
+ scope.ownerId,
29974
+ name
29975
+ );
29283
29976
  return true;
29284
29977
  }
29285
29978
  };
@@ -29371,8 +30064,38 @@ function selectBridgeSocket(candidates, ownerClientId) {
29371
30064
 
29372
30065
  // src/durable-objects/DurableThread.ts
29373
30066
  init_code_execution();
30067
+ init_remoteAttachment();
29374
30068
  var DEFAULT_CLIENT_TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
29375
30069
  var BRIDGE_LIVENESS_TIMEOUT_MS = 20 * 1e3;
30070
+ var INTERNAL_TOOL_DELIVERY = Symbol("internal-tool-delivery");
30071
+ function generateToolDeliveryToken() {
30072
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
30073
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
30074
+ }
30075
+ async function hashToolDeliveryToken(token) {
30076
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
30077
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
30078
+ }
30079
+ async function createToolDeliveryCapability() {
30080
+ const token = generateToolDeliveryToken();
30081
+ return { token, tokenHash: await hashToolDeliveryToken(token) };
30082
+ }
30083
+ async function matchesToolDeliveryToken(expectedHash, token) {
30084
+ if (!expectedHash || !token || token.length > 256) return false;
30085
+ const actualHash = await hashToolDeliveryToken(token);
30086
+ if (expectedHash.length !== actualHash.length) return false;
30087
+ let difference = 0;
30088
+ for (let index = 0; index < actualHash.length; index++) {
30089
+ difference |= expectedHash.charCodeAt(index) ^ actualHash.charCodeAt(index);
30090
+ }
30091
+ return difference === 0;
30092
+ }
30093
+ function matchesToolDeliveryOwner(pending, proof) {
30094
+ return !pending.dispatched_client_id || pending.dispatched_client_id === proof.clientId && Number(pending.dispatched_generation ?? 0) === (proof.generation ?? -1);
30095
+ }
30096
+ async function authorizesToolDelivery(pending, proof) {
30097
+ return matchesToolDeliveryOwner(pending, proof) && await matchesToolDeliveryToken(pending.delivery_token_hash, proof.token);
30098
+ }
29376
30099
  var RUNTIME_STORAGE_PREFIX = "__agentbuilder_runtime_storage__:";
29377
30100
  function normalizeEndpointThreadPath(path21) {
29378
30101
  if (!path21 || /^[A-Za-z][A-Za-z\d+\-.]*:/.test(path21)) {
@@ -29523,6 +30246,30 @@ function createEndpointThreadStateBridge(state) {
29523
30246
  getFileThumbnail: (path21) => state.getFileThumbnail(normalizeEndpointThreadPath(path21))
29524
30247
  };
29525
30248
  }
30249
+ function routeBridgeClientFrame(socket, message, handlers) {
30250
+ const attachment = socket.deserializeAttachment();
30251
+ const attachedAsBridge = attachment !== null && typeof attachment === "object" && "type" in attachment && attachment.type === "bridge";
30252
+ if (!attachedAsBridge || !handlers.isTracked(socket)) return false;
30253
+ handlers.markSeen(socket);
30254
+ if (typeof message !== "string") return false;
30255
+ let parsed;
30256
+ try {
30257
+ const value = JSON.parse(message);
30258
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
30259
+ parsed = value;
30260
+ } catch {
30261
+ return false;
30262
+ }
30263
+ if (parsed.type === "tool_response") {
30264
+ handlers.resolveToolResponse(socket, parsed);
30265
+ return true;
30266
+ }
30267
+ if (typeof parsed.type === "string" && parsed.type.startsWith("stream_") && parsed.type !== "stream_ping" && parsed.type !== "stream_pong") {
30268
+ handlers.handleStreamFrame(socket, parsed);
30269
+ return true;
30270
+ }
30271
+ return false;
30272
+ }
29526
30273
  var DurableThread = class _DurableThread extends DurableObject {
29527
30274
  migratedToVersion = null;
29528
30275
  logSockets = /* @__PURE__ */ new Set();
@@ -30917,9 +31664,9 @@ var DurableThread = class _DurableThread extends DurableObject {
30917
31664
  * Each migration is run in order, starting from the current version + 1.
30918
31665
  */
30919
31666
  async runMigrations(fromVersion) {
30920
- for (const migration53 of migrations) {
30921
- if (migration53.version > fromVersion) {
30922
- await migration53.up(this.ctx.storage.sql);
31667
+ for (const migration58 of migrations) {
31668
+ if (migration58.version > fromVersion) {
31669
+ await migration58.up(this.ctx.storage.sql);
30923
31670
  }
30924
31671
  }
30925
31672
  }
@@ -31603,14 +32350,17 @@ var DurableThread = class _DurableThread extends DurableObject {
31603
32350
  return null;
31604
32351
  }
31605
32352
  const location = sourceFile.location;
31606
- if (!location || !/^https?:\/\//i.test(location)) {
32353
+ if (!location) {
32354
+ return null;
32355
+ }
32356
+ const response = await fetchRemoteAttachment(
32357
+ location,
32358
+ this.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
32359
+ );
32360
+ if (!response) {
31607
32361
  return null;
31608
32362
  }
31609
32363
  try {
31610
- const response = await fetch(location);
31611
- if (!response.ok) {
31612
- return null;
31613
- }
31614
32364
  const buffer = await response.arrayBuffer();
31615
32365
  return this.arrayBufferToBase64(buffer);
31616
32366
  } catch {
@@ -33307,7 +34057,9 @@ ${resultContent}${attachmentSummary}`;
33307
34057
  void 0,
33308
34058
  "The client executing this operation was replaced before it finished. Retry if the work is still needed.",
33309
34059
  previous.client_id,
33310
- previous.generation
34060
+ previous.generation,
34061
+ void 0,
34062
+ INTERNAL_TOOL_DELIVERY
33311
34063
  );
33312
34064
  if (delivered) sides.add(delivered.side);
33313
34065
  }
@@ -33337,18 +34089,21 @@ ${resultContent}${attachmentSummary}`;
33337
34089
  data: { noClient: true }
33338
34090
  };
33339
34091
  }
34092
+ let deliveryToken;
33340
34093
  if (durability) {
33341
34094
  try {
33342
34095
  await this.ensureMigrated();
33343
34096
  await this.ensurePendingClientTable();
34097
+ const capability = await createToolDeliveryCapability();
34098
+ deliveryToken = capability.token;
33344
34099
  const ownerId = owner?.client_id ?? null;
33345
34100
  const socketClientId = this.bridgeClientInfo.get(socket)?.clientId ?? null;
33346
34101
  const fenced = owner && socketClientId === owner.client_id;
33347
34102
  await this.ctx.storage.sql.exec(
33348
34103
  `INSERT OR REPLACE INTO pending_client_calls
33349
34104
  (tool_call_id, tool, args, request_permission, risk, side, status, created_at,
33350
- dispatched_client_id, dispatched_generation)
33351
- VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
34105
+ dispatched_client_id, dispatched_generation, delivery_token_hash)
34106
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?)`,
33352
34107
  durability.toolCallId,
33353
34108
  request.tool,
33354
34109
  JSON.stringify(request.args ?? {}),
@@ -33357,7 +34112,8 @@ ${resultContent}${attachmentSummary}`;
33357
34112
  durability.side,
33358
34113
  Date.now(),
33359
34114
  fenced ? ownerId : null,
33360
- fenced ? owner.generation : 0
34115
+ fenced ? owner.generation : 0,
34116
+ capability.tokenHash
33361
34117
  );
33362
34118
  } catch (error) {
33363
34119
  console.error("[DurableThread] Failed to persist fast-path call (continuing):", error);
@@ -33393,7 +34149,8 @@ ${resultContent}${attachmentSummary}`;
33393
34149
  tool: request.tool,
33394
34150
  args: request.args ?? {},
33395
34151
  requestPermission: request.requestPermission ?? null,
33396
- risk: typeof request.risk === "number" ? request.risk : null
34152
+ risk: typeof request.risk === "number" ? request.risk : null,
34153
+ deliveryToken: deliveryToken ?? null
33397
34154
  });
33398
34155
  try {
33399
34156
  socket.send(frame);
@@ -33516,7 +34273,8 @@ ${resultContent}${attachmentSummary}`;
33516
34273
  status TEXT NOT NULL DEFAULT 'pending',
33517
34274
  created_at INTEGER NOT NULL,
33518
34275
  dispatched_client_id TEXT,
33519
- dispatched_generation INTEGER NOT NULL DEFAULT 0
34276
+ dispatched_generation INTEGER NOT NULL DEFAULT 0,
34277
+ delivery_token_hash TEXT
33520
34278
  )
33521
34279
  `);
33522
34280
  }
@@ -33557,13 +34315,15 @@ ${resultContent}${attachmentSummary}`;
33557
34315
  const socketClientId = this.bridgeClientInfo.get(socket)?.clientId ?? null;
33558
34316
  const fenced = owner && socketClientId === owner.client_id;
33559
34317
  const generation = fenced ? owner.generation : 0;
34318
+ const capability = await createToolDeliveryCapability();
33560
34319
  try {
33561
34320
  await this.ctx.storage.sql.exec(
33562
34321
  `UPDATE pending_client_calls
33563
- SET dispatched_client_id = ?, dispatched_generation = ?
34322
+ SET dispatched_client_id = ?, dispatched_generation = ?, delivery_token_hash = ?
33564
34323
  WHERE tool_call_id = ? AND status = 'pending'`,
33565
34324
  fenced ? owner.client_id : null,
33566
34325
  generation,
34326
+ capability.tokenHash,
33567
34327
  toolCallId
33568
34328
  );
33569
34329
  socket.send(
@@ -33575,7 +34335,8 @@ ${resultContent}${attachmentSummary}`;
33575
34335
  args: request.args ?? {},
33576
34336
  requestPermission: request.requestPermission ?? null,
33577
34337
  risk: typeof request.risk === "number" ? request.risk : null,
33578
- generation
34338
+ generation,
34339
+ deliveryToken: capability.token
33579
34340
  })
33580
34341
  );
33581
34342
  this.bridgeLastSeen.set(socket, Date.now());
@@ -33698,12 +34459,14 @@ ${resultContent}${attachmentSummary}`;
33698
34459
  const pending = await this.getPendingClientCalls();
33699
34460
  for (const p of pending) {
33700
34461
  if (socket.readyState !== WebSocket.OPEN) break;
34462
+ const capability = await createToolDeliveryCapability();
33701
34463
  await this.ctx.storage.sql.exec(
33702
34464
  `UPDATE pending_client_calls
33703
- SET dispatched_client_id = ?, dispatched_generation = ?
34465
+ SET dispatched_client_id = ?, dispatched_generation = ?, delivery_token_hash = ?
33704
34466
  WHERE tool_call_id = ? AND status = 'pending'`,
33705
34467
  fenced ? owner.client_id : null,
33706
34468
  generation,
34469
+ capability.tokenHash,
33707
34470
  p.toolCallId
33708
34471
  );
33709
34472
  socket.send(
@@ -33715,7 +34478,8 @@ ${resultContent}${attachmentSummary}`;
33715
34478
  args: p.args,
33716
34479
  requestPermission: p.requestPermission,
33717
34480
  risk: p.risk,
33718
- generation
34481
+ generation,
34482
+ deliveryToken: capability.token
33719
34483
  })
33720
34484
  );
33721
34485
  }
@@ -33728,20 +34492,25 @@ ${resultContent}${attachmentSummary}`;
33728
34492
  * call resolved. Returns the side to continue, or null if there is no pending
33729
34493
  * call (unknown id or already resolved — the delivery is idempotent).
33730
34494
  */
33731
- async deliverClientToolResult(toolCallId, ok, result, error, deliveredByClientId, deliveredGeneration) {
34495
+ async deliverClientToolResult(toolCallId, ok, result, error, deliveredByClientId, deliveredGeneration, deliveryToken, internalAuthorization) {
33732
34496
  await this.ensureMigrated();
33733
34497
  await this.ensurePendingClientTable();
33734
34498
  const pendingCursor = await this.ctx.storage.sql.exec(
33735
- `SELECT side, status, dispatched_client_id, dispatched_generation
34499
+ `SELECT side, status, dispatched_client_id, dispatched_generation, delivery_token_hash
33736
34500
  FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
33737
34501
  toolCallId
33738
34502
  );
33739
34503
  const pending = pendingCursor.toArray()[0];
33740
34504
  if (!pending || pending.status !== "pending") return null;
33741
- if (pending.dispatched_client_id) {
33742
- if (pending.dispatched_client_id !== deliveredByClientId || Number(pending.dispatched_generation ?? 0) !== (deliveredGeneration ?? -1)) {
33743
- return null;
33744
- }
34505
+ const proof = {
34506
+ token: deliveryToken,
34507
+ clientId: deliveredByClientId,
34508
+ generation: deliveredGeneration
34509
+ };
34510
+ if (internalAuthorization !== INTERNAL_TOOL_DELIVERY) {
34511
+ if (!await authorizesToolDelivery(pending, proof)) return null;
34512
+ } else if (!matchesToolDeliveryOwner(pending, proof)) {
34513
+ return null;
33745
34514
  }
33746
34515
  const side = pending.side === "b" ? "b" : "a";
33747
34516
  const content = ok ? result ?? "" : `Error: ${error ?? "tool failed"}`;
@@ -34458,29 +35227,17 @@ ${resultContent}${attachmentSummary}`;
34458
35227
  */
34459
35228
  async webSocketMessage(ws, message) {
34460
35229
  try {
34461
- if (this.bridgeSockets.has(ws)) {
34462
- this.bridgeLastSeen.set(ws, Date.now());
34463
- }
35230
+ const handledBridgeFrame = routeBridgeClientFrame(ws, message, {
35231
+ isTracked: (socket) => this.bridgeSockets.has(socket),
35232
+ markSeen: (socket) => this.bridgeLastSeen.set(socket, Date.now()),
35233
+ resolveToolResponse: (socket, payload) => this.resolveClientCall(socket, payload),
35234
+ handleStreamFrame: (socket, payload) => this.handleClientStreamFrame(socket, payload)
35235
+ });
35236
+ if (handledBridgeFrame) return;
34464
35237
  if (typeof message === "string" && message === "ping") {
34465
35238
  ws.send("pong");
34466
35239
  return;
34467
35240
  }
34468
- if (typeof message === "string") {
34469
- let parsed = null;
34470
- try {
34471
- parsed = JSON.parse(message);
34472
- } catch {
34473
- parsed = null;
34474
- }
34475
- if (parsed && parsed.type === "tool_response") {
34476
- this.resolveClientCall(ws, parsed);
34477
- return;
34478
- }
34479
- if (parsed && typeof parsed.type === "string" && parsed.type.startsWith("stream_") && parsed.type !== "stream_ping" && parsed.type !== "stream_pong") {
34480
- this.handleClientStreamFrame(ws, parsed);
34481
- return;
34482
- }
34483
- }
34484
35241
  } catch (error) {
34485
35242
  console.error("[DurableThread] Error handling WebSocket message:", error);
34486
35243
  }
@@ -37487,6 +38244,6 @@ init_context();
37487
38244
  init_ProviderRegistry();
37488
38245
  init_types2();
37489
38246
 
37490
- export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, PackageDiscoveryService, ProviderRegistry, ThreadWatchdog, agentbuilder, assistantMessageText, authenticate, buildImageDescription, builder, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, discoverPackages, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, generateAgentFile, generateImageDescription, generateModelFile, generatePromptFile, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, hasJsonArrayEntries, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isTrustedLocalDevelopmentRequest, isVisibleTerminalAssistantMessage, linkFile, messageTimestampSeconds, 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 };
38247
+ export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, PackageDiscoveryService, ProviderRegistry, ThreadWatchdog, agentbuilder, applyRuntimeCors, assistantMessageText, authenticate, buildImageDescription, builder, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, discoverPackages, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, generateAgentFile, generateImageDescription, generateModelFile, generatePromptFile, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, hasJsonArrayEntries, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isSameOriginBrowserRequest, isTrustedLocalDevelopmentRequest, isVisibleTerminalAssistantMessage, linkFile, messageTimestampSeconds, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, runtimeCorsHeaders, runtimePreflightResponse, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
37491
38248
  //# sourceMappingURL=index.js.map
37492
38249
  //# sourceMappingURL=index.js.map