@standardagents/builder 0.38.0 → 0.38.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/built-in-routes.js +1243 -418
  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 +1064 -304
  43. package/dist/index.js.map +1 -1
  44. package/dist/plugin.js +106 -69
  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/runtime.js CHANGED
@@ -28,6 +28,14 @@ var __copyProps = (to, from, except, desc) => {
28
28
  };
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
 
31
+ // src/skills/scope.ts
32
+ var SHARED_SKILL_OWNER;
33
+ var init_scope = __esm({
34
+ "src/skills/scope.ts"() {
35
+ SHARED_SKILL_OWNER = "__instance_shared__";
36
+ }
37
+ });
38
+
31
39
  // src/agents/client-forwarding.ts
32
40
  function asForwarding(state) {
33
41
  return state;
@@ -3150,6 +3158,104 @@ var init_sessionTools = __esm({
3150
3158
  }
3151
3159
  });
3152
3160
 
3161
+ // src/utils/remoteAttachment.ts
3162
+ function parseAllowedHosts(value) {
3163
+ if (typeof value !== "string") {
3164
+ return /* @__PURE__ */ new Set();
3165
+ }
3166
+ return new Set(
3167
+ value.split(",").map((host) => host.trim().toLowerCase().replace(/\.$/, "")).filter(Boolean)
3168
+ );
3169
+ }
3170
+ function isForbiddenHostname(hostname) {
3171
+ const normalized = hostname.toLowerCase().replace(/\.$/, "");
3172
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(normalized)) {
3173
+ return true;
3174
+ }
3175
+ if (normalized.startsWith("[") && normalized.endsWith("]")) {
3176
+ return true;
3177
+ }
3178
+ if (!normalized.includes(".")) {
3179
+ return true;
3180
+ }
3181
+ return FORBIDDEN_HOST_SUFFIXES.some(
3182
+ (suffix) => normalized === suffix.slice(1) || normalized.endsWith(suffix)
3183
+ );
3184
+ }
3185
+ function validateRemoteAttachmentUrl(value, allowedHostsValue) {
3186
+ let url;
3187
+ try {
3188
+ url = new URL(value);
3189
+ } catch {
3190
+ return null;
3191
+ }
3192
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "" && url.port !== "443") {
3193
+ return null;
3194
+ }
3195
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
3196
+ if (isForbiddenHostname(hostname)) {
3197
+ return null;
3198
+ }
3199
+ const allowedHosts = parseAllowedHosts(allowedHostsValue);
3200
+ if (!allowedHosts.has(hostname)) {
3201
+ return null;
3202
+ }
3203
+ return url;
3204
+ }
3205
+ async function fetchRemoteAttachment(location, allowedHostsValue, fetchImpl = fetch) {
3206
+ let url = validateRemoteAttachmentUrl(location, allowedHostsValue);
3207
+ if (!url) {
3208
+ return null;
3209
+ }
3210
+ for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount++) {
3211
+ let response;
3212
+ try {
3213
+ response = await fetchImpl(url.toString(), {
3214
+ redirect: "manual"
3215
+ });
3216
+ } catch {
3217
+ return null;
3218
+ }
3219
+ if (!REDIRECT_STATUSES.has(response.status)) {
3220
+ return response.ok ? response : null;
3221
+ }
3222
+ await response.body?.cancel().catch(() => void 0);
3223
+ if (redirectCount === MAX_REDIRECTS) {
3224
+ return null;
3225
+ }
3226
+ const redirectLocation = response.headers.get("Location");
3227
+ if (!redirectLocation) {
3228
+ return null;
3229
+ }
3230
+ let resolvedLocation;
3231
+ try {
3232
+ resolvedLocation = new URL(redirectLocation, url).toString();
3233
+ } catch {
3234
+ return null;
3235
+ }
3236
+ url = validateRemoteAttachmentUrl(resolvedLocation, allowedHostsValue);
3237
+ if (!url) {
3238
+ return null;
3239
+ }
3240
+ }
3241
+ return null;
3242
+ }
3243
+ var REDIRECT_STATUSES, MAX_REDIRECTS, FORBIDDEN_HOST_SUFFIXES;
3244
+ var init_remoteAttachment = __esm({
3245
+ "src/utils/remoteAttachment.ts"() {
3246
+ REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
3247
+ MAX_REDIRECTS = 3;
3248
+ FORBIDDEN_HOST_SUFFIXES = [
3249
+ ".internal",
3250
+ ".lan",
3251
+ ".local",
3252
+ ".localhost",
3253
+ ".home",
3254
+ ".test"
3255
+ ];
3256
+ }
3257
+ });
3258
+
3153
3259
  // src/durable-objects/files.ts
3154
3260
  var files_exports = {};
3155
3261
  __export(files_exports, {
@@ -3887,6 +3993,7 @@ var init_ToolExecutor = __esm({
3887
3993
  init_types();
3888
3994
  init_ThreadStateImpl();
3889
3995
  init_sessionTools();
3996
+ init_remoteAttachment();
3890
3997
  SUBAGENT_CREATE_TOOL = "subagent_create";
3891
3998
  SUBAGENT_MESSAGE_TOOL = "subagent_message";
3892
3999
  SUBAGENTS_LIST_TOOL = "subagents";
@@ -4935,7 +5042,8 @@ Message one with subagent_message(referenceId, message).`
4935
5042
  stub,
4936
5043
  state.thread.instance,
4937
5044
  childResult.attachments,
4938
- `from-subagent-${childThreadId}`
5045
+ `from-subagent-${childThreadId}`,
5046
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
4939
5047
  );
4940
5048
  if (resumable) {
4941
5049
  await this.settleResumableChildAfterRun(
@@ -4968,7 +5076,8 @@ Message one with subagent_message(referenceId, message).`
4968
5076
  stub,
4969
5077
  state.thread.instance,
4970
5078
  childResult.attachments,
4971
- `from-subagent-${childThreadId}`
5079
+ `from-subagent-${childThreadId}`,
5080
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
4972
5081
  );
4973
5082
  const messageContent = this.formatSubagentTerminalMessage(
4974
5083
  childThreadId,
@@ -5382,7 +5491,8 @@ Message one with subagent_message(referenceId, message).`
5382
5491
  state.thread.instance,
5383
5492
  childThread,
5384
5493
  parentAttachments,
5385
- `from-parent-${state.threadId}`
5494
+ `from-parent-${state.threadId}`,
5495
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
5386
5496
  );
5387
5497
  const targetRole = receivesMessages === "side_a" ? "user" : "assistant";
5388
5498
  const baseTimestamp = Date.now() * 1e3;
@@ -5642,7 +5752,7 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5642
5752
  const shortId = crypto.randomUUID().slice(0, 8);
5643
5753
  return `/attachments/${normalizedStem}-${shortId}.${finalExt}`;
5644
5754
  }
5645
- static async copyAttachmentsBetweenThreads(sourceThread, targetThread, attachments, _pathPrefix) {
5755
+ static async copyAttachmentsBetweenThreads(sourceThread, targetThread, attachments, _pathPrefix, allowedAttachmentFetchHosts) {
5646
5756
  if (!attachments || attachments.length === 0) {
5647
5757
  return [];
5648
5758
  }
@@ -5670,7 +5780,8 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5670
5780
  const copiedData = await this.readAttachmentDataFromSource(
5671
5781
  sourceThread,
5672
5782
  attachment.path,
5673
- sourceFile
5783
+ sourceFile,
5784
+ allowedAttachmentFetchHosts
5674
5785
  );
5675
5786
  if (!copiedData) {
5676
5787
  continue;
@@ -5727,7 +5838,7 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5727
5838
  }
5728
5839
  return copied;
5729
5840
  }
5730
- static async readAttachmentDataFromSource(sourceThread, path, sourceFile) {
5841
+ static async readAttachmentDataFromSource(sourceThread, path, sourceFile, allowedAttachmentFetchHosts) {
5731
5842
  const sourceData = await sourceThread.readFile(path);
5732
5843
  if (sourceData.success && sourceData.data) {
5733
5844
  return sourceData.data;
@@ -5736,14 +5847,17 @@ Reference: ${referenceId}` : `Reference: ${referenceId}`;
5736
5847
  return null;
5737
5848
  }
5738
5849
  const location = sourceFile.location;
5739
- if (!location || !/^https?:\/\//i.test(location)) {
5850
+ if (!location) {
5851
+ return null;
5852
+ }
5853
+ const response = await fetchRemoteAttachment(
5854
+ location,
5855
+ allowedAttachmentFetchHosts
5856
+ );
5857
+ if (!response) {
5740
5858
  return null;
5741
5859
  }
5742
5860
  try {
5743
- const response = await fetch(location);
5744
- if (!response.ok) {
5745
- return null;
5746
- }
5747
5861
  const buffer = await response.arrayBuffer();
5748
5862
  return this.arrayBufferToBase64(buffer);
5749
5863
  } catch {
@@ -5997,7 +6111,8 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
5997
6111
  state.thread.instance,
5998
6112
  childStub,
5999
6113
  sourceAttachments,
6000
- `from-parent-${state.threadId}`
6114
+ `from-parent-${state.threadId}`,
6115
+ state.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
6001
6116
  );
6002
6117
  await childStub.queueMessage(referenceId, {
6003
6118
  role: receivesMessages === "side_a" ? "user" : "assistant",
@@ -12979,6 +13094,32 @@ var ThreadStateImpl_exports = {};
12979
13094
  __export(ThreadStateImpl_exports, {
12980
13095
  ThreadStateImpl: () => ThreadStateImpl
12981
13096
  });
13097
+ function isRuntimeDurableNamespace(value) {
13098
+ return value !== null && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function" && "get" in value && typeof value.get === "function";
13099
+ }
13100
+ function isThreadStateRuntimeEnvironment(value) {
13101
+ 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));
13102
+ }
13103
+ function confinedUserEndpointUrl(userId, endpoint) {
13104
+ const base = new URL(
13105
+ `https://user-hub/users/${encodeURIComponent(userId)}/endpoints/`
13106
+ );
13107
+ const relative = endpoint.replace(/^\/+/, "");
13108
+ let decoded;
13109
+ try {
13110
+ decoded = decodeURIComponent(relative);
13111
+ } catch {
13112
+ throw new Error("Invalid user endpoint path: malformed percent encoding.");
13113
+ }
13114
+ if (decoded.includes("\\") || decoded.includes("?") || decoded.includes("#") || /[\u0000-\u001f\u007f]/.test(decoded) || decoded.split("/").some((segment) => segment === "." || segment === "..")) {
13115
+ throw new Error("Invalid user endpoint path: traversal and URL syntax are not allowed.");
13116
+ }
13117
+ const resolved = new URL(relative, base);
13118
+ if (resolved.origin !== base.origin || !resolved.pathname.startsWith(base.pathname)) {
13119
+ throw new Error("Invalid user endpoint path: endpoint escaped the user namespace.");
13120
+ }
13121
+ return resolved.toString();
13122
+ }
12982
13123
  function hasLocalCodeExecutionRuntime(threadInstance) {
12983
13124
  const candidate = threadInstance;
12984
13125
  return Boolean(candidate.env && candidate.ctx);
@@ -13159,6 +13300,7 @@ function runCodeViaThreadRpc(threadInstance, threadId, source, options) {
13159
13300
  var ThreadStateImpl, ExecutionStateImpl;
13160
13301
  var init_ThreadStateImpl = __esm({
13161
13302
  "src/agents/ThreadStateImpl.ts"() {
13303
+ init_scope();
13162
13304
  init_client_forwarding();
13163
13305
  init_types();
13164
13306
  init_utilities();
@@ -13392,6 +13534,14 @@ var init_ThreadStateImpl = __esm({
13392
13534
  }
13393
13535
  return value;
13394
13536
  }
13537
+ async instanceEnv(propertyName) {
13538
+ const agentBuilder = this._getAgentBuilderStub();
13539
+ if (typeof agentBuilder.getInstanceEnv !== "function") {
13540
+ throw new Error("instanceEnv is not supported by this runtime");
13541
+ }
13542
+ const values = await agentBuilder.getInstanceEnv();
13543
+ return values[propertyName];
13544
+ }
13395
13545
  async envType(propertyName) {
13396
13546
  const agentBuilder = this._getAgentBuilderStub();
13397
13547
  if (typeof agentBuilder.resolveThreadEnvType === "function") {
@@ -14422,12 +14572,11 @@ var init_ThreadStateImpl = __esm({
14422
14572
  * dispatcher recorded in the runtime context instead.
14423
14573
  */
14424
14574
  _getRuntimeEnv() {
14425
- const contextEnv = this._runtimeContext.env;
14426
- if (contextEnv?.AGENT_BUILDER) {
14427
- return contextEnv;
14575
+ if (isThreadStateRuntimeEnvironment(this._runtimeContext.env)) {
14576
+ return this._runtimeContext.env;
14428
14577
  }
14429
14578
  const instanceEnv = this._threadInstance.env;
14430
- if (!instanceEnv?.AGENT_BUILDER) {
14579
+ if (!isThreadStateRuntimeEnvironment(instanceEnv)) {
14431
14580
  throw new Error(
14432
14581
  "No runtime environment available on this ThreadState. Stub-backed states must be constructed with runtimeContext.env."
14433
14582
  );
@@ -14444,9 +14593,7 @@ var init_ThreadStateImpl = __esm({
14444
14593
  return namespace.get(namespace.idFromName(userId));
14445
14594
  }
14446
14595
  // ─────────────────────────────────────────────────────────────────────────
14447
- // Skills — instance-global Agent Skills library, backed by the
14448
- // DurableAgentBuilder singleton (skills are never stored per-thread or on
14449
- // a client machine).
14596
+ // Skills — owner-scoped library plus explicitly shared administrator skills.
14450
14597
  // ─────────────────────────────────────────────────────────────────────────
14451
14598
  /**
14452
14599
  * The profile of the user this thread belongs to (identity fields only —
@@ -14469,9 +14616,8 @@ var init_ThreadStateImpl = __esm({
14469
14616
  "Reentrant user-scope call: this ThreadState was raised from user-scope code; state.user().call() is unavailable here."
14470
14617
  );
14471
14618
  }
14472
- const path = endpoint.replace(/^\/+/, "");
14473
14619
  return hub().fetch(
14474
- new Request(`https://user-hub/users/${encodeURIComponent(userId)}/endpoints/${path}`, {
14620
+ new Request(confinedUserEndpointUrl(userId, endpoint), {
14475
14621
  method: "POST",
14476
14622
  headers: {
14477
14623
  "Content-Type": "application/json",
@@ -14531,18 +14677,42 @@ var init_ThreadStateImpl = __esm({
14531
14677
  }
14532
14678
  get skills() {
14533
14679
  const stub = this._getAgentBuilderStub();
14680
+ const ownerId = this._metadata.user_id ?? SHARED_SKILL_OWNER;
14681
+ const scope = {
14682
+ ownerId,
14683
+ includeShared: ownerId !== SHARED_SKILL_OWNER
14684
+ };
14685
+ const requireMutationAdmin = async () => {
14686
+ const userId = this._metadata.user_id;
14687
+ if (!userId) {
14688
+ throw new Error("Skill mutations require an authenticated instance admin.");
14689
+ }
14690
+ const user = await stub.getUserById(userId);
14691
+ if (user?.role !== "admin") {
14692
+ throw new Error("Skill mutations require an instance admin.");
14693
+ }
14694
+ };
14534
14695
  return {
14535
- list: (options) => stub.listSkills(options),
14696
+ list: (options) => stub.listSkills(scope, options),
14536
14697
  get: async (name) => {
14537
- const metadata = await stub.getSkill(name);
14698
+ const metadata = await stub.getSkill(scope, name);
14538
14699
  if (!metadata) return null;
14539
- const skillMd = await stub.getSkillFile(name, "SKILL.md") ?? "";
14700
+ const skillMd = await stub.getSkillFile(scope, name, "SKILL.md") ?? "";
14540
14701
  return { metadata, skillMd };
14541
14702
  },
14542
- readFile: (name, path) => stub.getSkillFile(name, path),
14543
- install: (files) => stub.upsertSkill(files),
14544
- setEnabled: (name, enabled) => stub.setSkillEnabled(name, enabled),
14545
- remove: (name) => stub.deleteSkill(name)
14703
+ readFile: (name, path) => stub.getSkillFile(scope, name, path),
14704
+ install: async (files) => {
14705
+ await requireMutationAdmin();
14706
+ return stub.upsertSkill(scope, files);
14707
+ },
14708
+ setEnabled: async (name, enabled) => {
14709
+ await requireMutationAdmin();
14710
+ return stub.setSkillEnabled(scope, name, enabled);
14711
+ },
14712
+ remove: async (name) => {
14713
+ await requireMutationAdmin();
14714
+ return stub.deleteSkill(scope, name);
14715
+ }
14546
14716
  };
14547
14717
  }
14548
14718
  // ─────────────────────────────────────────────────────────────────────────
@@ -14705,6 +14875,7 @@ var init_image_processing = __esm({
14705
14875
  init_ThreadStateImpl();
14706
14876
 
14707
14877
  // src/utils/auth.ts
14878
+ var SUPER_ADMIN_TOKEN_TTL_SECONDS = 12 * 60 * 60;
14708
14879
  async function hashToken(token) {
14709
14880
  const encoder = new TextEncoder();
14710
14881
  const data = encoder.encode(token);
@@ -14770,9 +14941,12 @@ async function verifySuperAdminToken(token, encryptionKey) {
14770
14941
  return false;
14771
14942
  }
14772
14943
  const timestamp = parseInt(parts[1], 10);
14944
+ if (!Number.isFinite(timestamp)) {
14945
+ return false;
14946
+ }
14773
14947
  const age = Date.now() - timestamp;
14774
- const maxAge = 30 * 24 * 60 * 60 * 1e3;
14775
- if (age > maxAge) {
14948
+ const maxAge = SUPER_ADMIN_TOKEN_TTL_SECONDS * 1e3;
14949
+ if (age < 0 || age > maxAge) {
14776
14950
  return false;
14777
14951
  }
14778
14952
  return true;
@@ -14786,13 +14960,178 @@ function base64ToBuffer(base64) {
14786
14960
  return bytes;
14787
14961
  }
14788
14962
 
14789
- // src/middleware/auth.ts
14790
- var CORS_HEADERS = {
14963
+ // src/routing/access-policy.ts
14964
+ var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
14965
+ "/api/auth/bootstrap",
14966
+ "/api/auth/login",
14967
+ "/api/auth/config",
14968
+ "/api/auth/brolly-control",
14969
+ "/api/auth/platform-cleanup",
14970
+ "/api/auth/platform-replica",
14971
+ "/api/auth/sa/start",
14972
+ "/api/auth/sa/callback",
14973
+ "/api/config",
14974
+ "/api/auth/oauth/github",
14975
+ "/api/auth/oauth/google",
14976
+ "/api/auth/oauth/github/callback",
14977
+ "/api/auth/oauth/google/callback"
14978
+ ]);
14979
+ function isProviderIconRoute(routePath) {
14980
+ return /^\/api\/providers\/[^/]+\/icon$/.test(routePath);
14981
+ }
14982
+ function isHostedInstance(env) {
14983
+ const value = env && typeof env === "object" ? Reflect.get(env, "STANDARD_AGENTS_HOSTED") : void 0;
14984
+ if (typeof value === "string") {
14985
+ const normalized = value.trim().toLowerCase();
14986
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
14987
+ }
14988
+ return Boolean(value);
14989
+ }
14990
+ function isTrustedLocalDevelopmentRequest(request, env) {
14991
+ if (isHostedInstance(env)) return false;
14992
+ let hostname;
14993
+ try {
14994
+ hostname = new URL(request.url).hostname.toLowerCase();
14995
+ } catch {
14996
+ return false;
14997
+ }
14998
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost") || hostname.endsWith(".orb.local");
14999
+ }
15000
+ function isPublicApiRoute(routePath, request, env) {
15001
+ if (PUBLIC_ROUTES.has(routePath)) return true;
15002
+ if (routePath === "/api/auth/device/start" || routePath.startsWith("/api/auth/device/")) {
15003
+ return true;
15004
+ }
15005
+ if (isProviderIconRoute(routePath)) {
15006
+ return true;
15007
+ }
15008
+ return routePath === "/api/platform-auth/bootstrap" && isTrustedLocalDevelopmentRequest(request, env);
15009
+ }
15010
+ function requestObservedOrigins(request) {
15011
+ const origins = /* @__PURE__ */ new Set();
15012
+ let url = null;
15013
+ try {
15014
+ url = new URL(request.url);
15015
+ origins.add(url.origin);
15016
+ } catch {
15017
+ url = null;
15018
+ }
15019
+ const forwardedHost = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
15020
+ const host = forwardedHost || request.headers.get("host")?.trim();
15021
+ if (!host) return origins;
15022
+ const forwardedProto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase();
15023
+ const schemes = /* @__PURE__ */ new Set();
15024
+ if (forwardedProto === "http" || forwardedProto === "https") schemes.add(forwardedProto);
15025
+ if (url) schemes.add(url.protocol.replace(":", ""));
15026
+ if (schemes.size === 0) {
15027
+ schemes.add(host.endsWith(".orb.local") ? "https" : "http");
15028
+ }
15029
+ for (const scheme of schemes) {
15030
+ try {
15031
+ origins.add(new URL(`${scheme}://${host}`).origin);
15032
+ } catch {
15033
+ }
15034
+ }
15035
+ return origins;
15036
+ }
15037
+ function isSameOriginBrowserRequest(request) {
15038
+ const fetchSite = request.headers.get("Sec-Fetch-Site")?.trim().toLowerCase();
15039
+ if (fetchSite === "same-origin" || fetchSite === "none") {
15040
+ return true;
15041
+ }
15042
+ if (fetchSite) {
15043
+ return false;
15044
+ }
15045
+ const origin = request.headers.get("Origin")?.trim();
15046
+ if (!origin) return true;
15047
+ if (origin === "null") return false;
15048
+ try {
15049
+ return requestObservedOrigins(request).has(new URL(origin).origin);
15050
+ } catch {
15051
+ return false;
15052
+ }
15053
+ }
15054
+ function isAdminOnlyApiRoute(routePath, method = "GET") {
15055
+ if (routePath === "/api/hooks") return true;
15056
+ if (routePath === "/api/skills" || routePath.startsWith("/api/skills/")) return true;
15057
+ if (routePath === "/api/pack" || routePath.startsWith("/api/pack/")) return true;
15058
+ if (routePath === "/api/unpack" || routePath.startsWith("/api/unpack/")) return true;
15059
+ if (routePath === "/api/prompts" || routePath.startsWith("/api/prompts/")) return true;
15060
+ if (routePath === "/api/tools" || routePath.startsWith("/api/tools/")) return true;
15061
+ if (routePath === "/api/models" || routePath.startsWith("/api/models/")) return true;
15062
+ if (routePath === "/api/providers") return true;
15063
+ if (routePath.startsWith("/api/providers/")) {
15064
+ return !isProviderIconRoute(routePath);
15065
+ }
15066
+ if (routePath.startsWith("/api/agents/")) {
15067
+ return !/^\/api\/agents\/[^/]+\/tenvs$/.test(routePath);
15068
+ }
15069
+ if (routePath === "/api/agents" && method.toUpperCase() !== "GET") return true;
15070
+ return false;
15071
+ }
15072
+
15073
+ // src/routing/cors.ts
15074
+ var RUNTIME_CORS_HEADERS = {
14791
15075
  "Access-Control-Allow-Origin": "*",
14792
15076
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
14793
15077
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
14794
15078
  "Access-Control-Max-Age": "86400"
14795
15079
  };
15080
+ var CORS_RESPONSE_HEADERS = [
15081
+ "Access-Control-Allow-Origin",
15082
+ "Access-Control-Allow-Methods",
15083
+ "Access-Control-Allow-Headers",
15084
+ "Access-Control-Max-Age",
15085
+ "Access-Control-Allow-Credentials",
15086
+ "Access-Control-Expose-Headers"
15087
+ ];
15088
+ function mutateCorsHeaders(headers, exposeCors) {
15089
+ if (exposeCors) {
15090
+ for (const [key, value] of Object.entries(RUNTIME_CORS_HEADERS)) {
15091
+ if (!headers.has(key)) headers.set(key, value);
15092
+ }
15093
+ return;
15094
+ }
15095
+ for (const key of CORS_RESPONSE_HEADERS) {
15096
+ headers.delete(key);
15097
+ }
15098
+ }
15099
+ function exposesRuntimeCors(request, env) {
15100
+ return !isTrustedLocalDevelopmentRequest(request, env);
15101
+ }
15102
+ function runtimeCorsHeaders(request, env) {
15103
+ return exposesRuntimeCors(request, env) ? { ...RUNTIME_CORS_HEADERS } : {};
15104
+ }
15105
+ function applyRuntimeCors(response, request, env) {
15106
+ if (response.status === 101) return response;
15107
+ const exposeCors = exposesRuntimeCors(request, env);
15108
+ try {
15109
+ mutateCorsHeaders(response.headers, exposeCors);
15110
+ return response;
15111
+ } catch {
15112
+ }
15113
+ const headers = new Headers(response.headers);
15114
+ mutateCorsHeaders(headers, exposeCors);
15115
+ return new Response(response.body, {
15116
+ status: response.status,
15117
+ statusText: response.statusText,
15118
+ headers
15119
+ });
15120
+ }
15121
+ function runtimePreflightResponse(request, env) {
15122
+ if (!exposesRuntimeCors(request, env)) {
15123
+ return Response.json(
15124
+ { error: "Cross-origin access is disabled for local development" },
15125
+ { status: 403, headers: { "Cache-Control": "no-store" } }
15126
+ );
15127
+ }
15128
+ return new Response(null, {
15129
+ status: 204,
15130
+ headers: RUNTIME_CORS_HEADERS
15131
+ });
15132
+ }
15133
+
15134
+ // src/middleware/auth.ts
14796
15135
  function endpointCallerFromAuth(auth) {
14797
15136
  if (!auth) return null;
14798
15137
  const { id, username, role, permissions, email, display_name, avatar_url } = auth.user;
@@ -14826,43 +15165,88 @@ function extractBearerToken(request) {
14826
15165
  if (cookieToken) {
14827
15166
  return cookieToken;
14828
15167
  }
14829
- const isWebSocket = request.headers.get("upgrade")?.toLowerCase() === "websocket";
14830
- if (isWebSocket) {
14831
- try {
14832
- const url = new URL(request.url);
14833
- const token = url.searchParams.get("token");
14834
- if (token) {
14835
- return token;
14836
- }
14837
- } catch (err) {
14838
- }
14839
- }
14840
15168
  return null;
14841
15169
  }
14842
15170
  function getAgentBuilder(env) {
14843
15171
  const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
14844
15172
  return env.AGENT_BUILDER.get(agentBuilderId);
14845
15173
  }
15174
+ function parseWebSocketTicketAuth(authJson) {
15175
+ let parsed;
15176
+ try {
15177
+ parsed = JSON.parse(authJson);
15178
+ } catch {
15179
+ return null;
15180
+ }
15181
+ if (!parsed || typeof parsed !== "object") return null;
15182
+ const record = parsed;
15183
+ if (!record.user || typeof record.user !== "object") return null;
15184
+ const user = record.user;
15185
+ if (typeof user.id !== "string" || typeof user.username !== "string" || typeof user.role !== "string" || record.authType !== "super_admin" && record.authType !== "session" && record.authType !== "api_key") {
15186
+ return null;
15187
+ }
15188
+ const permissions = user.permissions;
15189
+ if (permissions !== void 0 && permissions !== null && (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string"))) {
15190
+ return null;
15191
+ }
15192
+ if (record.credentialId !== void 0 && typeof record.credentialId !== "string") return null;
15193
+ return {
15194
+ user: {
15195
+ id: user.id,
15196
+ username: user.username,
15197
+ role: user.role,
15198
+ permissions,
15199
+ platform_user_id: typeof user.platform_user_id === "string" ? user.platform_user_id : null,
15200
+ email: typeof user.email === "string" ? user.email : null,
15201
+ display_name: typeof user.display_name === "string" ? user.display_name : null,
15202
+ avatar_url: typeof user.avatar_url === "string" ? user.avatar_url : null
15203
+ },
15204
+ authType: record.authType,
15205
+ credentialId: record.credentialId
15206
+ };
15207
+ }
15208
+ async function consumeWebSocketTicket(request, env) {
15209
+ if (request.headers.get("upgrade")?.toLowerCase() !== "websocket") return null;
15210
+ let url;
15211
+ try {
15212
+ url = new URL(request.url);
15213
+ } catch {
15214
+ return null;
15215
+ }
15216
+ const ticket = url.searchParams.get("ticket");
15217
+ if (!ticket) return null;
15218
+ const authJson = await getAgentBuilder(env).consumeWebSocketTicket({
15219
+ ticket_hash: await hashToken(ticket),
15220
+ pathname: url.pathname
15221
+ });
15222
+ return typeof authJson === "string" ? parseWebSocketTicketAuth(authJson) : null;
15223
+ }
14846
15224
  async function authenticate(request, env) {
15225
+ const ticketAuth = await consumeWebSocketTicket(request, env);
15226
+ if (ticketAuth) return ticketAuth;
14847
15227
  const token = extractBearerToken(request);
14848
15228
  if (!token) {
14849
15229
  return null;
14850
15230
  }
14851
- if (env.SUPER_ADMIN_PASSWORD && env.ENCRYPTION_KEY) {
15231
+ if (typeof env.SUPER_ADMIN_PASSWORD === "string" && typeof env.ENCRYPTION_KEY === "string") {
14852
15232
  if (token.includes(".")) {
14853
15233
  const isValidSuperAdmin = await verifySuperAdminToken(
14854
15234
  token,
14855
15235
  env.ENCRYPTION_KEY
14856
15236
  );
14857
15237
  if (isValidSuperAdmin) {
14858
- return {
14859
- user: {
14860
- id: "super_admin",
14861
- username: "admin",
14862
- role: "admin"
14863
- },
14864
- authType: "super_admin"
14865
- };
15238
+ const tokenHash = await hashToken(token);
15239
+ const agentBuilder = getAgentBuilder(env);
15240
+ if (await agentBuilder.validateSuperAdminSession(tokenHash)) {
15241
+ return {
15242
+ user: {
15243
+ id: "super_admin",
15244
+ username: "admin",
15245
+ role: "admin"
15246
+ },
15247
+ authType: "super_admin"
15248
+ };
15249
+ }
14866
15250
  }
14867
15251
  }
14868
15252
  }
@@ -14907,7 +15291,8 @@ async function authenticate(request, env) {
14907
15291
  display_name: user.display_name ?? null,
14908
15292
  avatar_url: user.avatar_url ?? null
14909
15293
  },
14910
- authType: "api_key"
15294
+ authType: "api_key",
15295
+ credentialId: apiKey.id
14911
15296
  };
14912
15297
  }
14913
15298
  }
@@ -14919,7 +15304,10 @@ async function requireAuth(request, env) {
14919
15304
  if (!authContext) {
14920
15305
  return new Response(JSON.stringify({ error: "Unauthorized" }), {
14921
15306
  status: 401,
14922
- headers: { "Content-Type": "application/json", ...CORS_HEADERS }
15307
+ headers: {
15308
+ "Content-Type": "application/json",
15309
+ ...runtimeCorsHeaders(request, env)
15310
+ }
14923
15311
  });
14924
15312
  }
14925
15313
  return authContext;
@@ -14934,7 +15322,10 @@ async function requireAdmin(request, env) {
14934
15322
  JSON.stringify({ error: "Forbidden: Admin access required" }),
14935
15323
  {
14936
15324
  status: 403,
14937
- headers: { "Content-Type": "application/json", ...CORS_HEADERS }
15325
+ headers: {
15326
+ "Content-Type": "application/json",
15327
+ ...runtimeCorsHeaders(request, env)
15328
+ }
14938
15329
  }
14939
15330
  );
14940
15331
  }
@@ -15007,73 +15398,6 @@ function canAccessThread(auth, thread) {
15007
15398
  return thread.user_id === auth.user.id;
15008
15399
  }
15009
15400
 
15010
- // src/routing/access-policy.ts
15011
- var PUBLIC_ROUTES = /* @__PURE__ */ new Set([
15012
- "/api/auth/bootstrap",
15013
- "/api/auth/login",
15014
- "/api/auth/config",
15015
- "/api/auth/brolly-control",
15016
- "/api/auth/platform-cleanup",
15017
- "/api/auth/platform-replica",
15018
- "/api/auth/sa/start",
15019
- "/api/auth/sa/callback",
15020
- "/api/config",
15021
- "/api/auth/oauth/github",
15022
- "/api/auth/oauth/google",
15023
- "/api/auth/oauth/github/callback",
15024
- "/api/auth/oauth/google/callback",
15025
- "/api/hooks"
15026
- ]);
15027
- function isProviderIconRoute(routePath) {
15028
- return /^\/api\/providers\/[^/]+\/icon$/.test(routePath);
15029
- }
15030
- function isHostedInstance(env) {
15031
- const value = env?.STANDARD_AGENTS_HOSTED;
15032
- if (typeof value === "string") {
15033
- const normalized = value.trim().toLowerCase();
15034
- return normalized !== "" && normalized !== "0" && normalized !== "false";
15035
- }
15036
- return Boolean(value);
15037
- }
15038
- function isTrustedLocalDevelopmentRequest(request, env) {
15039
- if (isHostedInstance(env)) return false;
15040
- let hostname;
15041
- try {
15042
- hostname = new URL(request.url).hostname.toLowerCase();
15043
- } catch {
15044
- return false;
15045
- }
15046
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost") || hostname.endsWith(".orb.local");
15047
- }
15048
- function isPublicApiRoute(routePath, request, env) {
15049
- if (PUBLIC_ROUTES.has(routePath)) return true;
15050
- if (routePath === "/api/auth/device/start" || routePath.startsWith("/api/auth/device/")) {
15051
- return true;
15052
- }
15053
- if (isProviderIconRoute(routePath)) {
15054
- return true;
15055
- }
15056
- if (!isTrustedLocalDevelopmentRequest(request, env)) return false;
15057
- 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/");
15058
- }
15059
- function isAdminOnlyApiRoute(routePath, method = "GET") {
15060
- if (routePath === "/api/skills" || routePath.startsWith("/api/skills/")) return true;
15061
- if (routePath === "/api/pack" || routePath.startsWith("/api/pack/")) return true;
15062
- if (routePath === "/api/unpack" || routePath.startsWith("/api/unpack/")) return true;
15063
- if (routePath === "/api/prompts" || routePath.startsWith("/api/prompts/")) return true;
15064
- if (routePath === "/api/tools" || routePath.startsWith("/api/tools/")) return true;
15065
- if (routePath === "/api/models" || routePath.startsWith("/api/models/")) return true;
15066
- if (routePath === "/api/providers") return true;
15067
- if (routePath.startsWith("/api/providers/")) {
15068
- return !isProviderIconRoute(routePath);
15069
- }
15070
- if (routePath.startsWith("/api/agents/")) {
15071
- return !/^\/api\/agents\/[^/]+\/tenvs$/.test(routePath);
15072
- }
15073
- if (routePath === "/api/agents" && method.toUpperCase() !== "GET") return true;
15074
- return false;
15075
- }
15076
-
15077
15401
  // src/runtime.ts
15078
15402
  init_client_forwarding();
15079
15403
 
@@ -15986,8 +16310,37 @@ var migration35 = {
15986
16310
  }
15987
16311
  };
15988
16312
 
16313
+ // src/durable-objects/migrations/036_add_tool_delivery_tokens.ts
16314
+ var migration36 = {
16315
+ version: 36,
16316
+ name: "add_tool_delivery_tokens",
16317
+ async up(sql) {
16318
+ sql.exec(`
16319
+ CREATE TABLE IF NOT EXISTS pending_client_calls (
16320
+ tool_call_id TEXT PRIMARY KEY,
16321
+ tool TEXT NOT NULL,
16322
+ args TEXT,
16323
+ request_permission TEXT,
16324
+ risk INTEGER,
16325
+ side TEXT NOT NULL DEFAULT 'a',
16326
+ status TEXT NOT NULL DEFAULT 'pending',
16327
+ created_at INTEGER NOT NULL,
16328
+ dispatched_client_id TEXT,
16329
+ dispatched_generation INTEGER NOT NULL DEFAULT 0
16330
+ )
16331
+ `);
16332
+ const columns = sql.exec(
16333
+ `PRAGMA table_info(pending_client_calls)`
16334
+ ).toArray();
16335
+ if (!columns.some((column) => column.name === "delivery_token_hash")) {
16336
+ sql.exec(`ALTER TABLE pending_client_calls ADD COLUMN delivery_token_hash TEXT`);
16337
+ }
16338
+ sql.exec(`UPDATE _metadata SET value = '36' WHERE key = 'schema_version'`);
16339
+ }
16340
+ };
16341
+
15989
16342
  // src/durable-objects/migrations/index.ts
15990
- 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];
16343
+ 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];
15991
16344
  var LATEST_SCHEMA_VERSION = migrations.length;
15992
16345
 
15993
16346
  // src/durable-objects/subagent-reconcile.ts
@@ -16823,7 +17176,7 @@ function eventSocketCanReceive(attachment, ownerUserId) {
16823
17176
  }
16824
17177
 
16825
17178
  // src/durable-objects/agentbuilder-migrations/0001_initial.ts
16826
- var migration36 = {
17179
+ var migration37 = {
16827
17180
  version: 1,
16828
17181
  async up(sql) {
16829
17182
  sql.exec(`
@@ -16922,7 +17275,7 @@ var migration36 = {
16922
17275
  };
16923
17276
 
16924
17277
  // src/durable-objects/agentbuilder-migrations/0002_add_tenvs_properties.ts
16925
- var migration37 = {
17278
+ var migration38 = {
16926
17279
  version: 2,
16927
17280
  async up(sql) {
16928
17281
  sql.exec(`ALTER TABLE threads ADD COLUMN tenvs TEXT`);
@@ -16934,7 +17287,7 @@ var migration37 = {
16934
17287
  };
16935
17288
 
16936
17289
  // src/durable-objects/agentbuilder-migrations/0003_add_parent_and_terminated.ts
16937
- var migration38 = {
17290
+ var migration39 = {
16938
17291
  version: 3,
16939
17292
  async up(sql) {
16940
17293
  sql.exec(`ALTER TABLE threads ADD COLUMN parent TEXT REFERENCES threads(id)`);
@@ -16947,7 +17300,7 @@ var migration38 = {
16947
17300
  };
16948
17301
 
16949
17302
  // src/durable-objects/agentbuilder-migrations/0004_add_env_tables.ts
16950
- var migration39 = {
17303
+ var migration40 = {
16951
17304
  version: 4,
16952
17305
  async up(sql) {
16953
17306
  sql.exec(`ALTER TABLE threads ADD COLUMN env TEXT`);
@@ -16971,7 +17324,7 @@ var migration39 = {
16971
17324
  };
16972
17325
 
16973
17326
  // src/durable-objects/agentbuilder-migrations/0005_add_pending_subagent_env_requests.ts
16974
- var migration40 = {
17327
+ var migration41 = {
16975
17328
  version: 5,
16976
17329
  async up(sql) {
16977
17330
  sql.exec(`
@@ -16997,7 +17350,7 @@ var migration40 = {
16997
17350
  };
16998
17351
 
16999
17352
  // src/durable-objects/agentbuilder-migrations/0006_add_env_value_types.ts
17000
- var migration41 = {
17353
+ var migration42 = {
17001
17354
  version: 6,
17002
17355
  async up(sql) {
17003
17356
  sql.exec(
@@ -17012,7 +17365,7 @@ var migration41 = {
17012
17365
  };
17013
17366
 
17014
17367
  // src/durable-objects/agentbuilder-migrations/0007_platform_identity_replica.ts
17015
- var migration42 = {
17368
+ var migration43 = {
17016
17369
  version: 7,
17017
17370
  async up(sql) {
17018
17371
  sql.exec(`ALTER TABLE users ADD COLUMN platform_user_id TEXT`);
@@ -17041,7 +17394,7 @@ var migration42 = {
17041
17394
  };
17042
17395
 
17043
17396
  // src/durable-objects/agentbuilder-migrations/0008_add_skills.ts
17044
- var migration43 = {
17397
+ var migration44 = {
17045
17398
  version: 8,
17046
17399
  async up(sql) {
17047
17400
  sql.exec(`
@@ -17072,7 +17425,7 @@ var migration43 = {
17072
17425
  };
17073
17426
 
17074
17427
  // src/durable-objects/agentbuilder-migrations/0009_add_user_permissions.ts
17075
- var migration44 = {
17428
+ var migration45 = {
17076
17429
  version: 9,
17077
17430
  async up(sql) {
17078
17431
  sql.exec(`ALTER TABLE users ADD COLUMN permissions TEXT`);
@@ -17083,7 +17436,7 @@ var migration44 = {
17083
17436
  };
17084
17437
 
17085
17438
  // src/durable-objects/agentbuilder-migrations/0010_add_device_logins.ts
17086
- var migration45 = {
17439
+ var migration46 = {
17087
17440
  version: 10,
17088
17441
  async up(sql) {
17089
17442
  sql.exec(`
@@ -17103,7 +17456,7 @@ var migration45 = {
17103
17456
  };
17104
17457
 
17105
17458
  // src/durable-objects/agentbuilder-migrations/0011_add_user_values.ts
17106
- var migration46 = {
17459
+ var migration47 = {
17107
17460
  version: 11,
17108
17461
  async up(sql) {
17109
17462
  sql.exec(`
@@ -17126,7 +17479,7 @@ var migration46 = {
17126
17479
  };
17127
17480
 
17128
17481
  // src/durable-objects/agentbuilder-migrations/0012_allow_api_instance_role.ts
17129
- var migration47 = {
17482
+ var migration48 = {
17130
17483
  version: 12,
17131
17484
  async up(sql) {
17132
17485
  sql.exec(`CREATE TABLE oauth_accounts_0012_backup AS SELECT * FROM oauth_accounts`);
@@ -17236,7 +17589,7 @@ var migration47 = {
17236
17589
  };
17237
17590
 
17238
17591
  // src/durable-objects/agentbuilder-migrations/0013_add_thread_activity.ts
17239
- var migration48 = {
17592
+ var migration49 = {
17240
17593
  version: 13,
17241
17594
  async up(sql) {
17242
17595
  sql.exec(`ALTER TABLE threads ADD COLUMN last_activity_at INTEGER`);
@@ -17252,7 +17605,7 @@ var migration48 = {
17252
17605
  };
17253
17606
 
17254
17607
  // src/durable-objects/agentbuilder-migrations/0014_thread_activity_scale.ts
17255
- var migration49 = {
17608
+ var migration50 = {
17256
17609
  version: 14,
17257
17610
  async up(sql) {
17258
17611
  sql.exec(`ALTER TABLE threads ADD COLUMN last_message_preview TEXT`);
@@ -17270,7 +17623,7 @@ var migration49 = {
17270
17623
  };
17271
17624
 
17272
17625
  // src/durable-objects/agentbuilder-migrations/0015_clear_envelope_previews.ts
17273
- var migration50 = {
17626
+ var migration51 = {
17274
17627
  version: 15,
17275
17628
  async up(sql) {
17276
17629
  sql.exec(
@@ -17284,7 +17637,7 @@ var migration50 = {
17284
17637
  };
17285
17638
 
17286
17639
  // src/durable-objects/agentbuilder-migrations/0016_add_origin_agent.ts
17287
- var migration51 = {
17640
+ var migration52 = {
17288
17641
  version: 16,
17289
17642
  async up(sql) {
17290
17643
  sql.exec(`ALTER TABLE threads ADD COLUMN origin_agent_name TEXT`);
@@ -17299,7 +17652,7 @@ var migration51 = {
17299
17652
  };
17300
17653
 
17301
17654
  // src/durable-objects/agentbuilder-migrations/0017_add_thread_message_recency.ts
17302
- var migration52 = {
17655
+ var migration53 = {
17303
17656
  version: 17,
17304
17657
  async up(sql) {
17305
17658
  sql.exec(`ALTER TABLE threads ADD COLUMN last_message_at INTEGER`);
@@ -17310,10 +17663,132 @@ var migration52 = {
17310
17663
  }
17311
17664
  };
17312
17665
 
17666
+ // src/durable-objects/agentbuilder-migrations/0018_auth_security.ts
17667
+ var migration54 = {
17668
+ version: 18,
17669
+ async up(sql) {
17670
+ sql.exec(`
17671
+ CREATE TABLE IF NOT EXISTS super_admin_sessions (
17672
+ token_hash TEXT PRIMARY KEY,
17673
+ created_at INTEGER NOT NULL,
17674
+ expires_at INTEGER NOT NULL
17675
+ )
17676
+ `);
17677
+ sql.exec(`
17678
+ CREATE INDEX IF NOT EXISTS idx_super_admin_sessions_expires
17679
+ ON super_admin_sessions(expires_at)
17680
+ `);
17681
+ sql.exec(`
17682
+ CREATE TABLE IF NOT EXISTS auth_rate_limits (
17683
+ bucket_key TEXT PRIMARY KEY,
17684
+ attempts INTEGER NOT NULL,
17685
+ reset_at INTEGER NOT NULL
17686
+ )
17687
+ `);
17688
+ sql.exec(`
17689
+ CREATE INDEX IF NOT EXISTS idx_auth_rate_limits_reset
17690
+ ON auth_rate_limits(reset_at)
17691
+ `);
17692
+ sql.exec(`
17693
+ INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '18')
17694
+ `);
17695
+ }
17696
+ };
17697
+
17698
+ // src/durable-objects/agentbuilder-migrations/0019_scope_skills.ts
17699
+ var migration55 = {
17700
+ version: 19,
17701
+ async up(sql) {
17702
+ sql.exec(`
17703
+ CREATE TABLE skills_0019 (
17704
+ owner_id TEXT NOT NULL,
17705
+ name TEXT NOT NULL,
17706
+ description TEXT NOT NULL,
17707
+ version TEXT,
17708
+ enabled INTEGER NOT NULL DEFAULT 1,
17709
+ shared INTEGER NOT NULL DEFAULT 0,
17710
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
17711
+ updated_at INTEGER DEFAULT (strftime('%s', 'now')),
17712
+ PRIMARY KEY (owner_id, name)
17713
+ )
17714
+ `);
17715
+ sql.exec(`
17716
+ INSERT INTO skills_0019 (
17717
+ owner_id, name, description, version, enabled, shared, created_at, updated_at
17718
+ )
17719
+ SELECT '__instance_shared__', name, description, version, enabled, 1, created_at, updated_at
17720
+ FROM skills
17721
+ `);
17722
+ sql.exec(`
17723
+ CREATE TABLE skill_files_0019 (
17724
+ owner_id TEXT NOT NULL,
17725
+ skill_name TEXT NOT NULL,
17726
+ path TEXT NOT NULL,
17727
+ content TEXT NOT NULL,
17728
+ size INTEGER NOT NULL,
17729
+ PRIMARY KEY (owner_id, skill_name, path),
17730
+ FOREIGN KEY (owner_id, skill_name) REFERENCES skills_0019(owner_id, name) ON DELETE CASCADE
17731
+ )
17732
+ `);
17733
+ sql.exec(`
17734
+ INSERT INTO skill_files_0019 (owner_id, skill_name, path, content, size)
17735
+ SELECT '__instance_shared__', skill_name, path, content, size FROM skill_files
17736
+ `);
17737
+ sql.exec(`DROP TABLE skill_files`);
17738
+ sql.exec(`DROP TABLE skills`);
17739
+ sql.exec(`ALTER TABLE skills_0019 RENAME TO skills`);
17740
+ sql.exec(`ALTER TABLE skill_files_0019 RENAME TO skill_files`);
17741
+ sql.exec(`CREATE INDEX idx_skills_owner_enabled ON skills(owner_id, enabled)`);
17742
+ sql.exec(`CREATE INDEX idx_skills_shared_enabled ON skills(shared, enabled)`);
17743
+ sql.exec(`CREATE INDEX idx_skill_files_skill ON skill_files(owner_id, skill_name)`);
17744
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '19')`);
17745
+ }
17746
+ };
17747
+
17748
+ // src/durable-objects/agentbuilder-migrations/0020_websocket_tickets.ts
17749
+ var migration56 = {
17750
+ version: 20,
17751
+ async up(sql) {
17752
+ sql.exec(`
17753
+ CREATE TABLE websocket_tickets (
17754
+ ticket_hash TEXT PRIMARY KEY,
17755
+ pathname TEXT NOT NULL,
17756
+ auth_json TEXT NOT NULL,
17757
+ created_at INTEGER NOT NULL,
17758
+ expires_at INTEGER NOT NULL
17759
+ )
17760
+ `);
17761
+ sql.exec(`
17762
+ CREATE INDEX idx_websocket_tickets_expires
17763
+ ON websocket_tickets(expires_at)
17764
+ `);
17765
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '20')`);
17766
+ }
17767
+ };
17768
+
17769
+ // src/durable-objects/agentbuilder-migrations/0021_platform_handoff_jtis.ts
17770
+ var migration57 = {
17771
+ version: 21,
17772
+ async up(sql) {
17773
+ sql.exec(`
17774
+ CREATE TABLE platform_handoff_jtis (
17775
+ jti TEXT PRIMARY KEY,
17776
+ consumed_at INTEGER NOT NULL,
17777
+ expires_at INTEGER NOT NULL
17778
+ )
17779
+ `);
17780
+ sql.exec(`
17781
+ CREATE INDEX idx_platform_handoff_jtis_expires
17782
+ ON platform_handoff_jtis(expires_at)
17783
+ `);
17784
+ sql.exec(`INSERT OR REPLACE INTO _metadata (key, value) VALUES ('schema_version', '21')`);
17785
+ }
17786
+ };
17787
+
17313
17788
  // src/durable-objects/agentbuilder-migrations/index.ts
17314
- var migrations2 = [migration36, migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52];
17789
+ var migrations2 = [migration37, migration38, migration39, migration40, migration41, migration42, migration43, migration44, migration45, migration46, migration47, migration48, migration49, migration50, migration51, migration52, migration53, migration54, migration55, migration56, migration57];
17315
17790
  var LATEST_SCHEMA_VERSION2 = migrations2.reduce(
17316
- (max, migration53) => Math.max(max, migration53.version),
17791
+ (max, migration58) => Math.max(max, migration58.version),
17317
17792
  0
17318
17793
  );
17319
17794
 
@@ -17818,6 +18293,7 @@ function validateSkillFiles(files) {
17818
18293
  }
17819
18294
 
17820
18295
  // src/durable-objects/DurableAgentBuilder.ts
18296
+ init_scope();
17821
18297
  var ENV_TYPES_PROPERTY = "__agentbuilder_env_types";
17822
18298
  function parseStoredPermissions(value) {
17823
18299
  if (typeof value !== "string" || !value.trim()) return null;
@@ -18410,9 +18886,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
18410
18886
  }
18411
18887
  }
18412
18888
  async runMigrations(fromVersion) {
18413
- for (const migration53 of migrations2) {
18414
- if (migration53.version > fromVersion) {
18415
- await migration53.up(this.ctx.storage.sql);
18889
+ for (const migration58 of migrations2) {
18890
+ if (migration58.version > fromVersion) {
18891
+ await migration58.up(this.ctx.storage.sql);
18416
18892
  }
18417
18893
  }
18418
18894
  }
@@ -20361,6 +20837,150 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20361
20837
  tokenHash
20362
20838
  );
20363
20839
  }
20840
+ /** Register a short-lived super-admin token so it can be revoked alone. */
20841
+ async createSuperAdminSession(params) {
20842
+ await this.ensureMigrated();
20843
+ const now = Math.floor(Date.now() / 1e3);
20844
+ await this.ctx.storage.sql.exec(
20845
+ `DELETE FROM super_admin_sessions WHERE expires_at <= ?`,
20846
+ now
20847
+ );
20848
+ await this.ctx.storage.sql.exec(
20849
+ `INSERT INTO super_admin_sessions (token_hash, created_at, expires_at)
20850
+ VALUES (?, ?, ?)`,
20851
+ params.token_hash,
20852
+ now,
20853
+ params.expires_at
20854
+ );
20855
+ }
20856
+ /** A valid signature is insufficient: the session must still be registered. */
20857
+ async validateSuperAdminSession(tokenHash) {
20858
+ await this.ensureMigrated();
20859
+ const now = Math.floor(Date.now() / 1e3);
20860
+ const cursor = await this.ctx.storage.sql.exec(
20861
+ `SELECT 1 AS found FROM super_admin_sessions
20862
+ WHERE token_hash = ? AND expires_at > ?`,
20863
+ tokenHash,
20864
+ now
20865
+ );
20866
+ return cursor.toArray().length > 0;
20867
+ }
20868
+ /** Revoke only the presented super-admin session; no key rotation required. */
20869
+ async deleteSuperAdminSession(tokenHash) {
20870
+ await this.ensureMigrated();
20871
+ await this.ctx.storage.sql.exec(
20872
+ `DELETE FROM super_admin_sessions WHERE token_hash = ?`,
20873
+ tokenHash
20874
+ );
20875
+ }
20876
+ /** Register a path-bound bearer ticket for one WebSocket handshake. */
20877
+ async createWebSocketTicket(params) {
20878
+ await this.ensureMigrated();
20879
+ const now = Math.floor(Date.now() / 1e3);
20880
+ this.ctx.storage.sql.exec(
20881
+ `DELETE FROM websocket_tickets WHERE expires_at <= ?`,
20882
+ now
20883
+ );
20884
+ this.ctx.storage.sql.exec(
20885
+ `INSERT INTO websocket_tickets (ticket_hash, pathname, auth_json, created_at, expires_at)
20886
+ VALUES (?, ?, ?, ?, ?)`,
20887
+ params.ticket_hash,
20888
+ params.pathname,
20889
+ params.auth_json,
20890
+ now,
20891
+ params.expires_at
20892
+ );
20893
+ }
20894
+ /**
20895
+ * Atomically consume a WebSocket ticket. Deleting before validating its path
20896
+ * means even a mistaken or malicious first presentation burns the bearer.
20897
+ */
20898
+ async consumeWebSocketTicket(params) {
20899
+ await this.ensureMigrated();
20900
+ const cursor = this.ctx.storage.sql.exec(
20901
+ `DELETE FROM websocket_tickets
20902
+ WHERE ticket_hash = ?
20903
+ RETURNING pathname, auth_json, expires_at`,
20904
+ params.ticket_hash
20905
+ );
20906
+ const ticket = cursor.toArray()[0];
20907
+ const now = Math.floor(Date.now() / 1e3);
20908
+ if (!ticket || ticket.expires_at <= now || ticket.pathname !== params.pathname) {
20909
+ return null;
20910
+ }
20911
+ return ticket.auth_json;
20912
+ }
20913
+ /** Atomically accept an unexpired platform handoff identifier once. */
20914
+ async consumePlatformHandoffJti(params) {
20915
+ await this.ensureMigrated();
20916
+ const jti = params.jti.trim();
20917
+ const expiresAt = Math.floor(params.expires_at);
20918
+ const now = Math.floor(Date.now() / 1e3);
20919
+ if (!jti || jti.length > 200 || !Number.isSafeInteger(expiresAt) || expiresAt <= now) {
20920
+ return false;
20921
+ }
20922
+ this.ctx.storage.sql.exec(
20923
+ `DELETE FROM platform_handoff_jtis WHERE expires_at <= ?`,
20924
+ now
20925
+ );
20926
+ const cursor = this.ctx.storage.sql.exec(
20927
+ `INSERT OR IGNORE INTO platform_handoff_jtis (jti, consumed_at, expires_at)
20928
+ VALUES (?, ?, ?)`,
20929
+ jti,
20930
+ now,
20931
+ expiresAt
20932
+ );
20933
+ return cursor.rowsWritten > 0;
20934
+ }
20935
+ /**
20936
+ * Atomically consume one attempt from a fixed-window authentication bucket.
20937
+ * The singleton Durable Object serializes calls, preventing parallel bypass.
20938
+ */
20939
+ async consumeAuthRateLimit(params) {
20940
+ await this.ensureMigrated();
20941
+ const key = params.key.trim();
20942
+ const limit = Math.floor(params.limit);
20943
+ const windowSeconds = Math.floor(params.window_seconds);
20944
+ if (!key || key.length > 160 || limit < 1 || limit > 1e4 || windowSeconds < 1 || windowSeconds > 86400) {
20945
+ throw new Error("Invalid authentication rate-limit bucket");
20946
+ }
20947
+ const now = Math.floor(Date.now() / 1e3);
20948
+ await this.ctx.storage.sql.exec(
20949
+ `DELETE FROM auth_rate_limits WHERE reset_at <= ?`,
20950
+ now
20951
+ );
20952
+ const cursor = await this.ctx.storage.sql.exec(
20953
+ `SELECT attempts, reset_at FROM auth_rate_limits WHERE bucket_key = ?`,
20954
+ key
20955
+ );
20956
+ const current = cursor.toArray()[0];
20957
+ if (!current || current.reset_at <= now) {
20958
+ const resetAt = now + windowSeconds;
20959
+ await this.ctx.storage.sql.exec(
20960
+ `INSERT INTO auth_rate_limits (bucket_key, attempts, reset_at)
20961
+ VALUES (?, 1, ?)
20962
+ ON CONFLICT(bucket_key) DO UPDATE SET attempts = 1, reset_at = excluded.reset_at`,
20963
+ key,
20964
+ resetAt
20965
+ );
20966
+ return { allowed: true, retry_after: windowSeconds, remaining: limit - 1 };
20967
+ }
20968
+ const retryAfter = Math.max(1, current.reset_at - now);
20969
+ if (current.attempts >= limit) {
20970
+ return { allowed: false, retry_after: retryAfter, remaining: 0 };
20971
+ }
20972
+ const attempts = current.attempts + 1;
20973
+ await this.ctx.storage.sql.exec(
20974
+ `UPDATE auth_rate_limits SET attempts = ? WHERE bucket_key = ?`,
20975
+ attempts,
20976
+ key
20977
+ );
20978
+ return {
20979
+ allowed: true,
20980
+ retry_after: retryAfter,
20981
+ remaining: Math.max(0, limit - attempts)
20982
+ };
20983
+ }
20364
20984
  // ============================================================
20365
20985
  // API Key Methods
20366
20986
  // ============================================================
@@ -20678,40 +21298,61 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20678
21298
  }
20679
21299
  }
20680
21300
  // ─────────────────────────────────────────────────────────────────────────
20681
- // Skills — the instance-global Agent Skills library (agentskills.io format).
20682
- // Installed once, visible to every thread; file contents live here so
20683
- // nothing about a skill is stored on a client machine.
21301
+ // Skills — owner-scoped Agent Skills plus explicitly shared admin skills.
20684
21302
  // ─────────────────────────────────────────────────────────────────────────
20685
21303
  /** Installed skills, metadata only (no file contents). */
20686
- async listSkills(options = {}) {
21304
+ async listSkills(scope, options = {}) {
20687
21305
  await this.ensureMigrated();
20688
- const where = options.includeDisabled ? "" : "WHERE enabled = 1";
20689
21306
  const cursor = await this.ctx.storage.sql.exec(
20690
21307
  `SELECT s.name, s.description, s.version, s.enabled, s.updated_at,
20691
- (SELECT json_group_array(f.path) FROM skill_files f WHERE f.skill_name = s.name) AS files
20692
- FROM skills s ${where} ORDER BY s.name`
21308
+ s.owner_id,
21309
+ (SELECT json_group_array(f.path) FROM skill_files f
21310
+ WHERE f.owner_id = s.owner_id AND f.skill_name = s.name) AS files
21311
+ FROM skills s
21312
+ WHERE (s.owner_id = ? OR (? = 1 AND s.shared = 1))
21313
+ AND (? = 1 OR s.enabled = 1)
21314
+ ORDER BY s.name, CASE WHEN s.owner_id = ? THEN 0 ELSE 1 END`,
21315
+ scope.ownerId,
21316
+ scope.includeShared ? 1 : 0,
21317
+ options.includeDisabled ? 1 : 0,
21318
+ scope.ownerId
20693
21319
  );
20694
- return cursor.toArray().map((row) => ({
20695
- name: row.name,
20696
- description: row.description,
20697
- version: row.version ?? void 0,
20698
- enabled: row.enabled === 1,
20699
- files: row.files ? JSON.parse(row.files).sort() : [],
20700
- updatedAt: row.updated_at
20701
- }));
21320
+ const skills = /* @__PURE__ */ new Map();
21321
+ for (const row of cursor.toArray()) {
21322
+ if (skills.has(row.name)) continue;
21323
+ const parsedFiles = row.files ? JSON.parse(row.files) : [];
21324
+ const files = Array.isArray(parsedFiles) ? parsedFiles.filter((path) => typeof path === "string").sort() : [];
21325
+ skills.set(row.name, {
21326
+ name: row.name,
21327
+ description: row.description,
21328
+ version: row.version ?? void 0,
21329
+ enabled: row.enabled === 1,
21330
+ files,
21331
+ updatedAt: row.updated_at
21332
+ });
21333
+ }
21334
+ return [...skills.values()];
20702
21335
  }
20703
21336
  /** One skill's metadata (null when not installed). */
20704
- async getSkill(name) {
20705
- const all = await this.listSkills({ includeDisabled: true });
21337
+ async getSkill(scope, name) {
21338
+ const all = await this.listSkills(scope, { includeDisabled: true });
20706
21339
  return all.find((s) => s.name === name) ?? null;
20707
21340
  }
20708
21341
  /** One bundled file's text (null when the skill or file is missing). */
20709
- async getSkillFile(name, path) {
21342
+ async getSkillFile(scope, name, path) {
20710
21343
  await this.ensureMigrated();
20711
21344
  const cursor = await this.ctx.storage.sql.exec(
20712
- `SELECT content FROM skill_files WHERE skill_name = ? AND path = ?`,
21345
+ `SELECT f.content FROM skill_files f
21346
+ JOIN skills s ON s.owner_id = f.owner_id AND s.name = f.skill_name
21347
+ WHERE f.skill_name = ? AND f.path = ?
21348
+ AND (f.owner_id = ? OR (? = 1 AND s.shared = 1))
21349
+ ORDER BY CASE WHEN f.owner_id = ? THEN 0 ELSE 1 END
21350
+ LIMIT 1`,
20713
21351
  name,
20714
- path.replace(/^\.\//, "")
21352
+ path.replace(/^\.\//, ""),
21353
+ scope.ownerId,
21354
+ scope.includeShared ? 1 : 0,
21355
+ scope.ownerId
20715
21356
  );
20716
21357
  const rows = cursor.toArray();
20717
21358
  return rows.length ? rows[0].content : null;
@@ -20722,44 +21363,56 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20722
21363
  * that frontmatter. Throws SkillValidationError with an actionable message
20723
21364
  * on any standard violation.
20724
21365
  */
20725
- async upsertSkill(files) {
21366
+ async upsertSkill(scope, files) {
20726
21367
  await this.ensureMigrated();
20727
21368
  const frontmatter = validateSkillFiles(files);
20728
21369
  const name = frontmatter.name;
20729
- const existing = await this.getSkill(name);
20730
- await this.ctx.storage.sql.exec(`DELETE FROM skill_files WHERE skill_name = ?`, name);
21370
+ const ownScope = { ownerId: scope.ownerId };
21371
+ const existing = await this.getSkill(ownScope, name);
20731
21372
  await this.ctx.storage.sql.exec(
20732
- `INSERT INTO skills (name, description, version, enabled, updated_at)
20733
- VALUES (?, ?, ?, ?, strftime('%s', 'now'))
20734
- ON CONFLICT(name) DO UPDATE SET
21373
+ `DELETE FROM skill_files WHERE owner_id = ? AND skill_name = ?`,
21374
+ scope.ownerId,
21375
+ name
21376
+ );
21377
+ await this.ctx.storage.sql.exec(
21378
+ `INSERT INTO skills (owner_id, name, description, version, enabled, shared, updated_at)
21379
+ VALUES (?, ?, ?, ?, ?, ?, strftime('%s', 'now'))
21380
+ ON CONFLICT(owner_id, name) DO UPDATE SET
20735
21381
  description = excluded.description,
20736
21382
  version = excluded.version,
20737
21383
  updated_at = excluded.updated_at`,
21384
+ scope.ownerId,
20738
21385
  name,
20739
21386
  frontmatter.description,
20740
21387
  frontmatter.version ?? null,
20741
- existing ? existing.enabled ? 1 : 0 : 1
21388
+ existing ? existing.enabled ? 1 : 0 : 1,
21389
+ scope.ownerId === SHARED_SKILL_OWNER ? 1 : 0
20742
21390
  );
20743
21391
  for (const f of files) {
20744
21392
  const p = f.path.replace(/^\.\//, "");
20745
21393
  await this.ctx.storage.sql.exec(
20746
- `INSERT INTO skill_files (skill_name, path, content, size) VALUES (?, ?, ?, ?)`,
21394
+ `INSERT INTO skill_files (owner_id, skill_name, path, content, size) VALUES (?, ?, ?, ?, ?)`,
21395
+ scope.ownerId,
20747
21396
  name,
20748
21397
  p,
20749
21398
  f.content,
20750
21399
  f.content.length
20751
21400
  );
20752
21401
  }
20753
- return await this.getSkill(name);
21402
+ const installed = await this.getSkill(ownScope, name);
21403
+ if (!installed) throw new Error(`Installed skill was not found: ${name}`);
21404
+ return installed;
20754
21405
  }
20755
21406
  /** Enable/disable an installed skill. Returns false when not installed. */
20756
- async setSkillEnabled(name, enabled) {
21407
+ async setSkillEnabled(scope, name, enabled) {
20757
21408
  await this.ensureMigrated();
20758
- const existing = await this.getSkill(name);
21409
+ const existing = await this.getSkill({ ownerId: scope.ownerId }, name);
20759
21410
  if (!existing) return false;
20760
21411
  await this.ctx.storage.sql.exec(
20761
- `UPDATE skills SET enabled = ?, updated_at = strftime('%s', 'now') WHERE name = ?`,
21412
+ `UPDATE skills SET enabled = ?, updated_at = strftime('%s', 'now')
21413
+ WHERE owner_id = ? AND name = ?`,
20762
21414
  enabled ? 1 : 0,
21415
+ scope.ownerId,
20763
21416
  name
20764
21417
  );
20765
21418
  return true;
@@ -20834,12 +21487,20 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
20834
21487
  return true;
20835
21488
  }
20836
21489
  /** Uninstall a skill and its files. Returns false when not installed. */
20837
- async deleteSkill(name) {
21490
+ async deleteSkill(scope, name) {
20838
21491
  await this.ensureMigrated();
20839
- const existing = await this.getSkill(name);
21492
+ const existing = await this.getSkill({ ownerId: scope.ownerId }, name);
20840
21493
  if (!existing) return false;
20841
- await this.ctx.storage.sql.exec(`DELETE FROM skill_files WHERE skill_name = ?`, name);
20842
- await this.ctx.storage.sql.exec(`DELETE FROM skills WHERE name = ?`, name);
21494
+ await this.ctx.storage.sql.exec(
21495
+ `DELETE FROM skill_files WHERE owner_id = ? AND skill_name = ?`,
21496
+ scope.ownerId,
21497
+ name
21498
+ );
21499
+ await this.ctx.storage.sql.exec(
21500
+ `DELETE FROM skills WHERE owner_id = ? AND name = ?`,
21501
+ scope.ownerId,
21502
+ name
21503
+ );
20843
21504
  return true;
20844
21505
  }
20845
21506
  };
@@ -20931,8 +21592,38 @@ function selectBridgeSocket(candidates, ownerClientId) {
20931
21592
 
20932
21593
  // src/durable-objects/DurableThread.ts
20933
21594
  init_code_execution();
21595
+ init_remoteAttachment();
20934
21596
  var DEFAULT_CLIENT_TOOL_TIMEOUT_MS = 5 * 60 * 1e3;
20935
21597
  var BRIDGE_LIVENESS_TIMEOUT_MS = 20 * 1e3;
21598
+ var INTERNAL_TOOL_DELIVERY = Symbol("internal-tool-delivery");
21599
+ function generateToolDeliveryToken() {
21600
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
21601
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
21602
+ }
21603
+ async function hashToolDeliveryToken(token) {
21604
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
21605
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
21606
+ }
21607
+ async function createToolDeliveryCapability() {
21608
+ const token = generateToolDeliveryToken();
21609
+ return { token, tokenHash: await hashToolDeliveryToken(token) };
21610
+ }
21611
+ async function matchesToolDeliveryToken(expectedHash, token) {
21612
+ if (!expectedHash || !token || token.length > 256) return false;
21613
+ const actualHash = await hashToolDeliveryToken(token);
21614
+ if (expectedHash.length !== actualHash.length) return false;
21615
+ let difference = 0;
21616
+ for (let index = 0; index < actualHash.length; index++) {
21617
+ difference |= expectedHash.charCodeAt(index) ^ actualHash.charCodeAt(index);
21618
+ }
21619
+ return difference === 0;
21620
+ }
21621
+ function matchesToolDeliveryOwner(pending, proof) {
21622
+ return !pending.dispatched_client_id || pending.dispatched_client_id === proof.clientId && Number(pending.dispatched_generation ?? 0) === (proof.generation ?? -1);
21623
+ }
21624
+ async function authorizesToolDelivery(pending, proof) {
21625
+ return matchesToolDeliveryOwner(pending, proof) && await matchesToolDeliveryToken(pending.delivery_token_hash, proof.token);
21626
+ }
20936
21627
  var RUNTIME_STORAGE_PREFIX = "__agentbuilder_runtime_storage__:";
20937
21628
  function normalizeEndpointThreadPath(path) {
20938
21629
  if (!path || /^[A-Za-z][A-Za-z\d+\-.]*:/.test(path)) {
@@ -21083,6 +21774,30 @@ function createEndpointThreadStateBridge(state) {
21083
21774
  getFileThumbnail: (path) => state.getFileThumbnail(normalizeEndpointThreadPath(path))
21084
21775
  };
21085
21776
  }
21777
+ function routeBridgeClientFrame(socket, message, handlers) {
21778
+ const attachment = socket.deserializeAttachment();
21779
+ const attachedAsBridge = attachment !== null && typeof attachment === "object" && "type" in attachment && attachment.type === "bridge";
21780
+ if (!attachedAsBridge || !handlers.isTracked(socket)) return false;
21781
+ handlers.markSeen(socket);
21782
+ if (typeof message !== "string") return false;
21783
+ let parsed;
21784
+ try {
21785
+ const value = JSON.parse(message);
21786
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
21787
+ parsed = value;
21788
+ } catch {
21789
+ return false;
21790
+ }
21791
+ if (parsed.type === "tool_response") {
21792
+ handlers.resolveToolResponse(socket, parsed);
21793
+ return true;
21794
+ }
21795
+ if (typeof parsed.type === "string" && parsed.type.startsWith("stream_") && parsed.type !== "stream_ping" && parsed.type !== "stream_pong") {
21796
+ handlers.handleStreamFrame(socket, parsed);
21797
+ return true;
21798
+ }
21799
+ return false;
21800
+ }
21086
21801
  var DurableThread = class _DurableThread extends DurableObject {
21087
21802
  migratedToVersion = null;
21088
21803
  logSockets = /* @__PURE__ */ new Set();
@@ -22477,9 +23192,9 @@ var DurableThread = class _DurableThread extends DurableObject {
22477
23192
  * Each migration is run in order, starting from the current version + 1.
22478
23193
  */
22479
23194
  async runMigrations(fromVersion) {
22480
- for (const migration53 of migrations) {
22481
- if (migration53.version > fromVersion) {
22482
- await migration53.up(this.ctx.storage.sql);
23195
+ for (const migration58 of migrations) {
23196
+ if (migration58.version > fromVersion) {
23197
+ await migration58.up(this.ctx.storage.sql);
22483
23198
  }
22484
23199
  }
22485
23200
  }
@@ -23163,14 +23878,17 @@ var DurableThread = class _DurableThread extends DurableObject {
23163
23878
  return null;
23164
23879
  }
23165
23880
  const location = sourceFile.location;
23166
- if (!location || !/^https?:\/\//i.test(location)) {
23881
+ if (!location) {
23882
+ return null;
23883
+ }
23884
+ const response = await fetchRemoteAttachment(
23885
+ location,
23886
+ this.env.AGENT_BUILDER_ATTACHMENT_FETCH_HOSTS
23887
+ );
23888
+ if (!response) {
23167
23889
  return null;
23168
23890
  }
23169
23891
  try {
23170
- const response = await fetch(location);
23171
- if (!response.ok) {
23172
- return null;
23173
- }
23174
23892
  const buffer = await response.arrayBuffer();
23175
23893
  return this.arrayBufferToBase64(buffer);
23176
23894
  } catch {
@@ -24867,7 +25585,9 @@ ${resultContent}${attachmentSummary}`;
24867
25585
  void 0,
24868
25586
  "The client executing this operation was replaced before it finished. Retry if the work is still needed.",
24869
25587
  previous.client_id,
24870
- previous.generation
25588
+ previous.generation,
25589
+ void 0,
25590
+ INTERNAL_TOOL_DELIVERY
24871
25591
  );
24872
25592
  if (delivered) sides.add(delivered.side);
24873
25593
  }
@@ -24897,18 +25617,21 @@ ${resultContent}${attachmentSummary}`;
24897
25617
  data: { noClient: true }
24898
25618
  };
24899
25619
  }
25620
+ let deliveryToken;
24900
25621
  if (durability) {
24901
25622
  try {
24902
25623
  await this.ensureMigrated();
24903
25624
  await this.ensurePendingClientTable();
25625
+ const capability = await createToolDeliveryCapability();
25626
+ deliveryToken = capability.token;
24904
25627
  const ownerId = owner?.client_id ?? null;
24905
25628
  const socketClientId = this.bridgeClientInfo.get(socket)?.clientId ?? null;
24906
25629
  const fenced = owner && socketClientId === owner.client_id;
24907
25630
  await this.ctx.storage.sql.exec(
24908
25631
  `INSERT OR REPLACE INTO pending_client_calls
24909
25632
  (tool_call_id, tool, args, request_permission, risk, side, status, created_at,
24910
- dispatched_client_id, dispatched_generation)
24911
- VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
25633
+ dispatched_client_id, dispatched_generation, delivery_token_hash)
25634
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?)`,
24912
25635
  durability.toolCallId,
24913
25636
  request.tool,
24914
25637
  JSON.stringify(request.args ?? {}),
@@ -24917,7 +25640,8 @@ ${resultContent}${attachmentSummary}`;
24917
25640
  durability.side,
24918
25641
  Date.now(),
24919
25642
  fenced ? ownerId : null,
24920
- fenced ? owner.generation : 0
25643
+ fenced ? owner.generation : 0,
25644
+ capability.tokenHash
24921
25645
  );
24922
25646
  } catch (error) {
24923
25647
  console.error("[DurableThread] Failed to persist fast-path call (continuing):", error);
@@ -24953,7 +25677,8 @@ ${resultContent}${attachmentSummary}`;
24953
25677
  tool: request.tool,
24954
25678
  args: request.args ?? {},
24955
25679
  requestPermission: request.requestPermission ?? null,
24956
- risk: typeof request.risk === "number" ? request.risk : null
25680
+ risk: typeof request.risk === "number" ? request.risk : null,
25681
+ deliveryToken: deliveryToken ?? null
24957
25682
  });
24958
25683
  try {
24959
25684
  socket.send(frame);
@@ -25076,7 +25801,8 @@ ${resultContent}${attachmentSummary}`;
25076
25801
  status TEXT NOT NULL DEFAULT 'pending',
25077
25802
  created_at INTEGER NOT NULL,
25078
25803
  dispatched_client_id TEXT,
25079
- dispatched_generation INTEGER NOT NULL DEFAULT 0
25804
+ dispatched_generation INTEGER NOT NULL DEFAULT 0,
25805
+ delivery_token_hash TEXT
25080
25806
  )
25081
25807
  `);
25082
25808
  }
@@ -25117,13 +25843,15 @@ ${resultContent}${attachmentSummary}`;
25117
25843
  const socketClientId = this.bridgeClientInfo.get(socket)?.clientId ?? null;
25118
25844
  const fenced = owner && socketClientId === owner.client_id;
25119
25845
  const generation = fenced ? owner.generation : 0;
25846
+ const capability = await createToolDeliveryCapability();
25120
25847
  try {
25121
25848
  await this.ctx.storage.sql.exec(
25122
25849
  `UPDATE pending_client_calls
25123
- SET dispatched_client_id = ?, dispatched_generation = ?
25850
+ SET dispatched_client_id = ?, dispatched_generation = ?, delivery_token_hash = ?
25124
25851
  WHERE tool_call_id = ? AND status = 'pending'`,
25125
25852
  fenced ? owner.client_id : null,
25126
25853
  generation,
25854
+ capability.tokenHash,
25127
25855
  toolCallId
25128
25856
  );
25129
25857
  socket.send(
@@ -25135,7 +25863,8 @@ ${resultContent}${attachmentSummary}`;
25135
25863
  args: request.args ?? {},
25136
25864
  requestPermission: request.requestPermission ?? null,
25137
25865
  risk: typeof request.risk === "number" ? request.risk : null,
25138
- generation
25866
+ generation,
25867
+ deliveryToken: capability.token
25139
25868
  })
25140
25869
  );
25141
25870
  this.bridgeLastSeen.set(socket, Date.now());
@@ -25258,12 +25987,14 @@ ${resultContent}${attachmentSummary}`;
25258
25987
  const pending = await this.getPendingClientCalls();
25259
25988
  for (const p of pending) {
25260
25989
  if (socket.readyState !== WebSocket.OPEN) break;
25990
+ const capability = await createToolDeliveryCapability();
25261
25991
  await this.ctx.storage.sql.exec(
25262
25992
  `UPDATE pending_client_calls
25263
- SET dispatched_client_id = ?, dispatched_generation = ?
25993
+ SET dispatched_client_id = ?, dispatched_generation = ?, delivery_token_hash = ?
25264
25994
  WHERE tool_call_id = ? AND status = 'pending'`,
25265
25995
  fenced ? owner.client_id : null,
25266
25996
  generation,
25997
+ capability.tokenHash,
25267
25998
  p.toolCallId
25268
25999
  );
25269
26000
  socket.send(
@@ -25275,7 +26006,8 @@ ${resultContent}${attachmentSummary}`;
25275
26006
  args: p.args,
25276
26007
  requestPermission: p.requestPermission,
25277
26008
  risk: p.risk,
25278
- generation
26009
+ generation,
26010
+ deliveryToken: capability.token
25279
26011
  })
25280
26012
  );
25281
26013
  }
@@ -25288,20 +26020,25 @@ ${resultContent}${attachmentSummary}`;
25288
26020
  * call resolved. Returns the side to continue, or null if there is no pending
25289
26021
  * call (unknown id or already resolved — the delivery is idempotent).
25290
26022
  */
25291
- async deliverClientToolResult(toolCallId, ok, result, error, deliveredByClientId, deliveredGeneration) {
26023
+ async deliverClientToolResult(toolCallId, ok, result, error, deliveredByClientId, deliveredGeneration, deliveryToken, internalAuthorization) {
25292
26024
  await this.ensureMigrated();
25293
26025
  await this.ensurePendingClientTable();
25294
26026
  const pendingCursor = await this.ctx.storage.sql.exec(
25295
- `SELECT side, status, dispatched_client_id, dispatched_generation
26027
+ `SELECT side, status, dispatched_client_id, dispatched_generation, delivery_token_hash
25296
26028
  FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
25297
26029
  toolCallId
25298
26030
  );
25299
26031
  const pending = pendingCursor.toArray()[0];
25300
26032
  if (!pending || pending.status !== "pending") return null;
25301
- if (pending.dispatched_client_id) {
25302
- if (pending.dispatched_client_id !== deliveredByClientId || Number(pending.dispatched_generation ?? 0) !== (deliveredGeneration ?? -1)) {
25303
- return null;
25304
- }
26033
+ const proof = {
26034
+ token: deliveryToken,
26035
+ clientId: deliveredByClientId,
26036
+ generation: deliveredGeneration
26037
+ };
26038
+ if (internalAuthorization !== INTERNAL_TOOL_DELIVERY) {
26039
+ if (!await authorizesToolDelivery(pending, proof)) return null;
26040
+ } else if (!matchesToolDeliveryOwner(pending, proof)) {
26041
+ return null;
25305
26042
  }
25306
26043
  const side = pending.side === "b" ? "b" : "a";
25307
26044
  const content = ok ? result ?? "" : `Error: ${error ?? "tool failed"}`;
@@ -26018,29 +26755,17 @@ ${resultContent}${attachmentSummary}`;
26018
26755
  */
26019
26756
  async webSocketMessage(ws, message) {
26020
26757
  try {
26021
- if (this.bridgeSockets.has(ws)) {
26022
- this.bridgeLastSeen.set(ws, Date.now());
26023
- }
26758
+ const handledBridgeFrame = routeBridgeClientFrame(ws, message, {
26759
+ isTracked: (socket) => this.bridgeSockets.has(socket),
26760
+ markSeen: (socket) => this.bridgeLastSeen.set(socket, Date.now()),
26761
+ resolveToolResponse: (socket, payload) => this.resolveClientCall(socket, payload),
26762
+ handleStreamFrame: (socket, payload) => this.handleClientStreamFrame(socket, payload)
26763
+ });
26764
+ if (handledBridgeFrame) return;
26024
26765
  if (typeof message === "string" && message === "ping") {
26025
26766
  ws.send("pong");
26026
26767
  return;
26027
26768
  }
26028
- if (typeof message === "string") {
26029
- let parsed = null;
26030
- try {
26031
- parsed = JSON.parse(message);
26032
- } catch {
26033
- parsed = null;
26034
- }
26035
- if (parsed && parsed.type === "tool_response") {
26036
- this.resolveClientCall(ws, parsed);
26037
- return;
26038
- }
26039
- if (parsed && typeof parsed.type === "string" && parsed.type.startsWith("stream_") && parsed.type !== "stream_ping" && parsed.type !== "stream_pong") {
26040
- this.handleClientStreamFrame(ws, parsed);
26041
- return;
26042
- }
26043
- }
26044
26769
  } catch (error) {
26045
26770
  console.error("[DurableThread] Error handling WebSocket message:", error);
26046
26771
  }
@@ -29047,6 +29772,6 @@ init_context();
29047
29772
  init_ProviderRegistry();
29048
29773
  init_types2();
29049
29774
 
29050
- export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, authenticate, buildImageDescription, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
29775
+ export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, applyRuntimeCors, authenticate, buildImageDescription, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isSameOriginBrowserRequest, isTrustedLocalDevelopmentRequest, linkFile, 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 };
29051
29776
  //# sourceMappingURL=runtime.js.map
29052
29777
  //# sourceMappingURL=runtime.js.map