@adelie/server 0.5.5 → 0.5.7

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 (27) hide show
  1. package/dist/api/types.d.ts +92 -4
  2. package/dist/api/types.js +1 -1
  3. package/dist/{chunk-FC7VCPLY.js → chunk-6IXN3YH4.js} +1 -1
  4. package/dist/chunk-6IXN3YH4.js.map +1 -0
  5. package/dist/index.js +795 -462
  6. package/dist/index.js.map +1 -1
  7. package/dist/plugin/index.d.ts +5 -2
  8. package/package.json +4 -4
  9. package/web-dist/assets/{addon-clipboard-RGzRb4Q6.js → addon-clipboard-CjgYTjXu.js} +1 -1
  10. package/web-dist/assets/{addon-fit-C71RgV2N.js → addon-fit--6LsJmsb.js} +1 -1
  11. package/web-dist/assets/{addon-web-links-DTNHdhLW.js → addon-web-links-iGShsrJF.js} +1 -1
  12. package/web-dist/assets/{highlighter-CCiLfNSa.js → highlighter-BcPmfYRY.js} +2 -2
  13. package/web-dist/assets/{highlighter-core-DiQFTSlw.js → highlighter-core-CcWvtuYt.js} +1 -1
  14. package/web-dist/assets/ibm-plex-sans-condensed-latin-600-normal-CRd5VyFf.woff2 +0 -0
  15. package/web-dist/assets/ibm-plex-sans-condensed-latin-ext-600-normal-CBdNZ9Ci.woff2 +0 -0
  16. package/web-dist/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  17. package/web-dist/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  18. package/web-dist/assets/index-DiDPf1zX.css +1 -0
  19. package/web-dist/assets/{index-BiNjhVwW.js → index-fKcIuWTN.js} +122 -122
  20. package/web-dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  21. package/web-dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  22. package/web-dist/assets/mona-sans-latin-ext-wght-normal-BqSk0Z3x.woff2 +0 -0
  23. package/web-dist/assets/mona-sans-latin-wght-normal-Pz49MTQZ.woff2 +0 -0
  24. package/web-dist/assets/{xterm-ByTKHwic.js → xterm-Bp08fDWA.js} +1 -1
  25. package/web-dist/index.html +2 -2
  26. package/dist/chunk-FC7VCPLY.js.map +0 -1
  27. package/web-dist/assets/index-zj3zgmUk.css +0 -1
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-GO6G445C.js";
9
9
  import {
10
10
  TICKET_SLUG_PATTERN
11
- } from "./chunk-FC7VCPLY.js";
11
+ } from "./chunk-6IXN3YH4.js";
12
12
  import {
13
13
  MachinesRepo
14
14
  } from "./chunk-HMTT2PX4.js";
@@ -77,6 +77,7 @@ var MIN_ATTACHMENT_MB = 1;
77
77
  var MAX_ATTACHMENT_MB = 200;
78
78
  var MAX_ATTACHMENT_COUNT = 20;
79
79
  var INLINE_IMAGE_MAX_MB = 20;
80
+ var INLINE_VIDEO_MAX_MB = 32;
80
81
  function base64Length(bytes) {
81
82
  return Math.ceil(bytes / 3) * 4;
82
83
  }
@@ -89,6 +90,7 @@ function toAttachmentLimits(mb) {
89
90
  };
90
91
  }
91
92
  var INLINE_IMAGE_MAX_BYTES = INLINE_IMAGE_MAX_MB * MB;
93
+ var INLINE_VIDEO_MAX_BYTES = INLINE_VIDEO_MAX_MB * MB;
92
94
  function bodyLimitBytes(mb) {
93
95
  return base64Length(mb.attachmentTotalMb * MB) + BODY_HEADROOM_BYTES;
94
96
  }
@@ -2035,7 +2037,7 @@ async function probe(instance, path210) {
2035
2037
  }
2036
2038
  }
2037
2039
  var json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2038
- var bad = (message2) => json(400, { error: { code: "bad_request", message: message2 } });
2040
+ var bad = (message3) => json(400, { error: { code: "bad_request", message: message3 } });
2039
2041
  function parseUpgradeTarget(contentType, body, readBlob = () => null) {
2040
2042
  const type2 = (contentType ?? "").split(";")[0].trim().toLowerCase();
2041
2043
  if (type2 !== "application/gzip" && type2 !== "application/octet-stream") {
@@ -2888,8 +2890,8 @@ import { Hono } from "hono";
2888
2890
  import fs6 from "fs/promises";
2889
2891
  import path9 from "path";
2890
2892
  import { isValidId } from "@adelie/core";
2891
- function badRequest(message2) {
2892
- return new HttpError(400, "bad_request", message2);
2893
+ function badRequest(message3) {
2894
+ return new HttpError(400, "bad_request", message3);
2893
2895
  }
2894
2896
  function pathParam(c, name) {
2895
2897
  const v = c.req.param(name);
@@ -3680,7 +3682,7 @@ import { DEFAULT_PROJECT_ID as DEFAULT_PROJECT_ID3, DEFAULT_PROJECT_NAME as DEFA
3680
3682
 
3681
3683
  // src/ifaces.json
3682
3684
  var ifaces_default = {
3683
- hash: "09ffec7eb0cdedd0d4c854572c6f05390cb320e6a103978ae78277abfcbfdb4d",
3685
+ hash: "0b0f68e407b030585d1a39c05a849df1a4d025bd5b85d34c5246b92a42670a96",
3684
3686
  ifaces: {
3685
3687
  "@adelie/server#Access": {
3686
3688
  name: "Access",
@@ -18771,7 +18773,7 @@ var ifaces_default = {
18771
18773
  }
18772
18774
  }
18773
18775
  },
18774
- readTraceImage: {
18776
+ readTraceMedia: {
18775
18777
  params: [
18776
18778
  {
18777
18779
  data: "string"
@@ -18790,6 +18792,9 @@ var ifaces_default = {
18790
18792
  },
18791
18793
  {
18792
18794
  data: "undefined|number"
18795
+ },
18796
+ {
18797
+ data: "'image'|'video'"
18793
18798
  }
18794
18799
  ],
18795
18800
  returns: {
@@ -19110,7 +19115,7 @@ var ifaces_default = {
19110
19115
  }
19111
19116
  }
19112
19117
  },
19113
- readTraceImage: {
19118
+ readTraceMedia: {
19114
19119
  params: [
19115
19120
  {
19116
19121
  data: "string"
@@ -19129,6 +19134,9 @@ var ifaces_default = {
19129
19134
  },
19130
19135
  {
19131
19136
  data: "undefined|number"
19137
+ },
19138
+ {
19139
+ data: "'image'|'video'"
19132
19140
  }
19133
19141
  ],
19134
19142
  returns: {
@@ -21878,6 +21886,7 @@ var ifaces_default = {
21878
21886
  "client_type?": "string",
21879
21887
  "display_name?": "string",
21880
21888
  "vision?": "boolean",
21889
+ "video?": "boolean",
21881
21890
  "max_tokens?": "number",
21882
21891
  "fast_mode?": "boolean",
21883
21892
  "pricing?": {
@@ -21964,22 +21973,28 @@ var ifaces_default = {
21964
21973
  [
21965
21974
  [
21966
21975
  [
21967
- {
21968
- $ref: "@adelie/core#ToolCallPayload"
21969
- },
21976
+ [
21977
+ {
21978
+ $ref: "@adelie/core#ToolCallPayload"
21979
+ },
21980
+ "|",
21981
+ {
21982
+ $ref: "@adelie/core#SessionMetaPayload"
21983
+ }
21984
+ ],
21970
21985
  "|",
21971
21986
  {
21972
- $ref: "@adelie/core#SessionMetaPayload"
21987
+ $ref: "@adelie/core#TextPayload"
21973
21988
  }
21974
21989
  ],
21975
21990
  "|",
21976
21991
  {
21977
- $ref: "@adelie/core#TextPayload"
21992
+ $ref: "@adelie/core#ImageUrlPayload"
21978
21993
  }
21979
21994
  ],
21980
21995
  "|",
21981
21996
  {
21982
- $ref: "@adelie/core#ImageUrlPayload"
21997
+ $ref: "@adelie/core#VideoUrlPayload"
21983
21998
  }
21984
21999
  ],
21985
22000
  "|",
@@ -22334,6 +22349,14 @@ var ifaces_default = {
22334
22349
  }
22335
22350
  },
22336
22351
  "@adelie/core#ToolPermission": "'r'|'rw'",
22352
+ "@adelie/core#VideoUrlPayload": {
22353
+ type: "'video_url'",
22354
+ role: "'user'",
22355
+ video_url: "string",
22356
+ "stop_reason?": {
22357
+ $ref: "@adelie/core#StopReason"
22358
+ }
22359
+ },
22337
22360
  "@adelie/server#AccessibleProjectRow": {
22338
22361
  role: {
22339
22362
  $ref: "@adelie/server#ProjectRole"
@@ -23130,6 +23153,7 @@ var ifaces_default = {
23130
23153
  "contextWindow?": "number",
23131
23154
  "clientType?": "string",
23132
23155
  "vision?": "boolean",
23156
+ "video?": "boolean",
23133
23157
  "maxTokens?": "number",
23134
23158
  "fastMode?": "boolean",
23135
23159
  "pricing?": {
@@ -23953,12 +23977,14 @@ var ifaces_default = {
23953
23977
  id: "string",
23954
23978
  text: "string",
23955
23979
  images: "number",
23980
+ videos: "number",
23956
23981
  files: "number"
23957
23982
  },
23958
23983
  "@adelie/server#PendingSteeringInfo": {
23959
23984
  id: "string",
23960
23985
  text: "string",
23961
23986
  images: "number",
23987
+ videos: "number",
23962
23988
  files: "number"
23963
23989
  },
23964
23990
  "@adelie/server#PlatformAuthFlowErrorCode": "'upstream_failed'|'unreachable'|'apply_failed'|'invalid_key'|'expired'|'locked'|'already_delivered'",
@@ -24238,6 +24264,9 @@ var ifaces_default = {
24238
24264
  images: {
24239
24265
  $ref: "typescript#Array<string>"
24240
24266
  },
24267
+ videos: {
24268
+ $ref: "typescript#Array<string>"
24269
+ },
24241
24270
  files: {
24242
24271
  $ref: "typescript#Array<RecallableFile>"
24243
24272
  }
@@ -31714,10 +31743,10 @@ var StreamErrorWatcher = class {
31714
31743
  this.pending.delete(key2);
31715
31744
  const spec = retried && entry.status === "retryable" ? LLM_RETRIED : LLM_FAILURES[entry.status];
31716
31745
  const trimmed = reason?.trim();
31717
- const message2 = trimmed && !isUserAbortReason(trimmed) ? trimmed : this.composeMessage(entry, retried, spec);
31746
+ const message3 = trimmed && !isUserAbortReason(trimmed) ? trimmed : this.composeMessage(entry, retried, spec);
31718
31747
  this.errors.record({
31719
31748
  source: "llm",
31720
- err: message2,
31749
+ err: message3,
31721
31750
  ctx: this.ctxFor(key2),
31722
31751
  code: spec.code,
31723
31752
  kind: spec.kind
@@ -32006,7 +32035,7 @@ var SandboxService = class {
32006
32035
  );
32007
32036
  }
32008
32037
  failedSuffix() {
32009
- const parts = [...this.loadErrors].map(([name, message2]) => `${name} (${message2})`);
32038
+ const parts = [...this.loadErrors].map(([name, message3]) => `${name} (${message3})`);
32010
32039
  if (this.declinedNames.length > 0) {
32011
32040
  parts.push(`${this.declinedNames.join(", ")} (not for this host)`);
32012
32041
  }
@@ -32322,8 +32351,8 @@ var UNSUPPORTED_PATTERNS = [
32322
32351
  "cannot process image",
32323
32352
  "image not supported"
32324
32353
  ];
32325
- function isImageRejection(message2) {
32326
- const m = message2.toLowerCase();
32354
+ function isImageRejection(message3) {
32355
+ const m = message3.toLowerCase();
32327
32356
  return UNSUPPORTED_PATTERNS.some((p) => m.includes(p));
32328
32357
  }
32329
32358
  function classifyVisionProbe(outcome, sawContent) {
@@ -32333,8 +32362,8 @@ function classifyVisionProbe(outcome, sawContent) {
32333
32362
  return isImageRejection(String(detail)) ? "unsupported" : "failed";
32334
32363
  }
32335
32364
  function classifyVisionProbeError(err) {
32336
- const message2 = err instanceof Error ? err.message : String(err);
32337
- return isImageRejection(message2) ? "unsupported" : "failed";
32365
+ const message3 = err instanceof Error ? err.message : String(err);
32366
+ return isImageRejection(message3) ? "unsupported" : "failed";
32338
32367
  }
32339
32368
 
32340
32369
  // src/services/project-config-service.ts
@@ -33121,6 +33150,7 @@ var ProjectConfigService = class {
33121
33150
  const fallback = modelEnvFallback({ provider, modelId, clientType, baseUrl: credBaseUrl });
33122
33151
  const envKey = fallback?.envKey;
33123
33152
  const vision = typeof m.vision === "boolean" ? m.vision : cat?.supportsVision;
33153
+ const video = m.video === true || cat?.supportsVideo === true ? true : void 0;
33124
33154
  const maxTokens = optNum(m.max_tokens);
33125
33155
  const fastMode = m.fast_mode === true ? true : void 0;
33126
33156
  const displayName = m.display_name === "" ? "" : optStr(m.display_name) ?? cat?.displayName;
@@ -33142,6 +33172,7 @@ var ProjectConfigService = class {
33142
33172
  ...optNum(m.context_window) !== void 0 ? { contextWindow: optNum(m.context_window) } : {},
33143
33173
  ...clientType ? { clientType } : {},
33144
33174
  ...vision !== void 0 ? { vision } : {},
33175
+ ...video !== void 0 ? { video } : {},
33145
33176
  ...maxTokens !== void 0 ? { maxTokens } : {},
33146
33177
  ...fastMode !== void 0 ? { fastMode } : {},
33147
33178
  ...envKey ? { envKey } : {},
@@ -34099,8 +34130,8 @@ function maskValues(schema2, values) {
34099
34130
  return out;
34100
34131
  }
34101
34132
  var PluginConfigError = class extends Error {
34102
- constructor(field, message2) {
34103
- super(message2);
34133
+ constructor(field, message3) {
34134
+ super(message3);
34104
34135
  this.field = field;
34105
34136
  this.name = "PluginConfigError";
34106
34137
  }
@@ -35860,13 +35891,13 @@ function compactUnavailable(why) {
35860
35891
  message: "The context was just compacted and there is no new conversation since; no need to compact again."
35861
35892
  }
35862
35893
  };
35863
- const { code, message: message2 } = reasons[why];
35864
- return new HttpError(409, code, message2);
35894
+ const { code, message: message3 } = reasons[why];
35895
+ return new HttpError(409, code, message3);
35865
35896
  }
35866
35897
  function switchRefusal(err, ref) {
35867
35898
  if (!(err instanceof ModelSwitchRefusedError)) throw err;
35868
- const message2 = err.reason === "model_unavailable" && isMissingCredential(err) ? `Model ${ref.modelId} has no API key yet. Configure it on the Models page first.` : err.message;
35869
- return new HttpError(409, err.reason, message2);
35899
+ const message3 = err.reason === "model_unavailable" && isMissingCredential(err) ? `Model ${ref.modelId} has no API key yet. Configure it on the Models page first.` : err.message;
35900
+ return new HttpError(409, err.reason, message3);
35870
35901
  }
35871
35902
  async function* resumeFrom(head, rest) {
35872
35903
  yield head;
@@ -35962,19 +35993,24 @@ function followUpInfo(f) {
35962
35993
  id: f.id,
35963
35994
  text: f.recall.text,
35964
35995
  images: f.recall.images.length,
35996
+ videos: f.recall.videos.length,
35965
35997
  files: f.recall.files.length
35966
35998
  };
35967
35999
  }
35968
36000
  function recallStoreOf(input) {
35969
36001
  const text2 = input.filter(isPlainText("user")).map((m) => m.payload.text).join("\n");
35970
36002
  const images = [];
36003
+ const videos = [];
35971
36004
  for (const msg of input) {
35972
36005
  const payload = msg.payload;
35973
- if (msg.type === "model_msg" && payload.type === "image_url" && typeof payload.image_url === "string") {
36006
+ if (msg.type !== "model_msg") continue;
36007
+ if (payload.type === "image_url" && typeof payload.image_url === "string") {
35974
36008
  images.push(payload.image_url);
36009
+ } else if (payload.type === "video_url" && typeof payload.video_url === "string") {
36010
+ videos.push(payload.video_url);
35975
36011
  }
35976
36012
  }
35977
- return { text: text2, images, files: [] };
36013
+ return { text: text2, images, videos, files: [] };
35978
36014
  }
35979
36015
  function isDeliveredSteering(msg) {
35980
36016
  const p = msg.payload;
@@ -36732,6 +36768,7 @@ var SessionManager = class {
36732
36768
  id: randomUUID5(),
36733
36769
  text: recall.text,
36734
36770
  images: recall.images.length,
36771
+ videos: recall.videos.length,
36735
36772
  files: recall.files.length
36736
36773
  },
36737
36774
  input,
@@ -37689,8 +37726,8 @@ __runInitializers(_init44, 1, DefaultTitleGenerators);
37689
37726
 
37690
37727
  // src/runtime/messaging/connector.ts
37691
37728
  var MessagingChannelError = class extends Error {
37692
- constructor(message2, recovers) {
37693
- super(message2);
37729
+ constructor(message3, recovers) {
37730
+ super(message3);
37694
37731
  this.recovers = recovers;
37695
37732
  this.name = "MessagingChannelError";
37696
37733
  }
@@ -37742,8 +37779,8 @@ var MessagingMediaTooLargeError = class extends Error {
37742
37779
  }
37743
37780
  };
37744
37781
  var MessagingPermissionError = class extends Error {
37745
- constructor(scopes, grantUrl, message2) {
37746
- super(message2);
37782
+ constructor(scopes, grantUrl, message3) {
37783
+ super(message3);
37747
37784
  this.scopes = scopes;
37748
37785
  this.grantUrl = grantUrl;
37749
37786
  this.name = "MessagingPermissionError";
@@ -37752,14 +37789,14 @@ var MessagingPermissionError = class extends Error {
37752
37789
  grantUrl;
37753
37790
  };
37754
37791
  var MessagingUnsupportedError = class extends Error {
37755
- constructor(message2) {
37756
- super(message2);
37792
+ constructor(message3) {
37793
+ super(message3);
37757
37794
  this.name = "MessagingUnsupportedError";
37758
37795
  }
37759
37796
  };
37760
37797
  var MessagingOutboundCapError = class extends Error {
37761
- constructor(message2) {
37762
- super(message2);
37798
+ constructor(message3) {
37799
+ super(message3);
37763
37800
  this.name = "MessagingOutboundCapError";
37764
37801
  }
37765
37802
  };
@@ -37800,8 +37837,8 @@ function closeCodeIsRoutine(code) {
37800
37837
  return code === 4009 || code >= 4900 && code <= 4913 || code === 1001 || code === 1006 || code >= 1011 && code <= 1014;
37801
37838
  }
37802
37839
  var MessagingConnectionClosedError = class extends MessagingChannelError {
37803
- constructor(message2, closeCode, recovers) {
37804
- super(message2, recovers);
37840
+ constructor(message3, closeCode, recovers) {
37841
+ super(message3, recovers);
37805
37842
  this.closeCode = closeCode;
37806
37843
  this.name = "MessagingConnectionClosedError";
37807
37844
  }
@@ -37828,21 +37865,21 @@ var QQ_BODY_INDEPENDENT_SEND_CODES = /* @__PURE__ */ new Set([
37828
37865
  40034105,
37829
37866
  40054013
37830
37867
  ]);
37831
- function qqSendErrorText(errCode, message2) {
37868
+ function qqSendErrorText(errCode, message3) {
37832
37869
  if (errCode === 40034128) {
37833
- return `${message2} \u2014 QQ allows only a few replies to one message, within minutes of it`;
37870
+ return `${message3} \u2014 QQ allows only a few replies to one message, within minutes of it`;
37834
37871
  }
37835
37872
  if (errCode === 40034005 || errCode === 304103 || errCode === 40034024) {
37836
- return `${message2} \u2014 the QQ message being replied to has expired`;
37873
+ return `${message3} \u2014 the QQ message being replied to has expired`;
37837
37874
  }
37838
37875
  if (errCode === 40034105 || errCode === 40054013) {
37839
- return `${message2} \u2014 this QQ user has turned off messages from bots`;
37876
+ return `${message3} \u2014 this QQ user has turned off messages from bots`;
37840
37877
  }
37841
- return message2;
37878
+ return message3;
37842
37879
  }
37843
37880
  var QQApiError = class extends MessagingChannelError {
37844
- constructor(message2, code, recovers = false) {
37845
- super(message2, recovers);
37881
+ constructor(message3, code, recovers = false) {
37882
+ super(message3, recovers);
37846
37883
  this.code = code;
37847
37884
  this.name = "QQApiError";
37848
37885
  }
@@ -39087,8 +39124,8 @@ function scopeDenialDetail(msg) {
39087
39124
  return { scopes, grantUrl: url !== null && url.length <= GRANT_URL_MAX ? url : null };
39088
39125
  }
39089
39126
  var FeishuApiError = class extends Error {
39090
- constructor(message2, code) {
39091
- super(message2);
39127
+ constructor(message3, code) {
39128
+ super(message3);
39092
39129
  this.code = code;
39093
39130
  this.name = "FeishuApiError";
39094
39131
  }
@@ -39666,8 +39703,8 @@ var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
39666
39703
  var CALL_TIMEOUT_MS3 = 15e3;
39667
39704
  var TRANSFER_TIMEOUT_MS = 6e4;
39668
39705
  var TelegramApiError = class extends MessagingChannelError {
39669
- constructor(message2, errorCode, recovers = httpStatusRecovers(errorCode)) {
39670
- super(message2, recovers);
39706
+ constructor(message3, errorCode, recovers = httpStatusRecovers(errorCode)) {
39707
+ super(message3, recovers);
39671
39708
  this.errorCode = errorCode;
39672
39709
  this.name = "TelegramApiError";
39673
39710
  }
@@ -40835,8 +40872,8 @@ var AES_KEY_BYTES = 16;
40835
40872
  var WECHAT_VIDEO_FILE_NAME = "video.mp4";
40836
40873
  var WECHAT_STALE_TOKEN_CODE = -14;
40837
40874
  var WeChatApiError = class extends MessagingChannelError {
40838
- constructor(ret, message2, flags = {}) {
40839
- super(message2, flags.recovers ?? false);
40875
+ constructor(ret, message3, flags = {}) {
40876
+ super(message3, flags.recovers ?? false);
40840
40877
  this.ret = ret;
40841
40878
  this.name = "WeChatApiError";
40842
40879
  this.authenticated = flags.authenticated ?? false;
@@ -42530,9 +42567,9 @@ function messagingFilesMissingLog(names) {
42530
42567
  function filesSkippedError(channel, fileNames) {
42531
42568
  const n = fileNames.length;
42532
42569
  const files = n === 1 ? "1 more file the reply mentioned was" : `${n} more files the reply mentioned were`;
42533
- const message2 = (list) => `${files} not sent to the ${channel} chat: at most ${MESSAGING_OUTBOUND_FILE_MAX_COUNT} ride along with one reply (${list})`;
42570
+ const message3 = (list) => `${files} not sent to the ${channel} chat: at most ${MESSAGING_OUTBOUND_FILE_MAX_COUNT} ride along with one reply (${list})`;
42534
42571
  return new MessagingOutboundCapError(
42535
- message2(quotedNames(fileNames, MESSAGE_MAX - message2("").length))
42572
+ message3(quotedNames(fileNames, MESSAGE_MAX - message3("").length))
42536
42573
  );
42537
42574
  }
42538
42575
  var MESSAGING_INBOUND_IMAGE_BUDGET_BYTES = 2 * INLINE_IMAGE_MAX_BYTES;
@@ -44323,9 +44360,9 @@ var HarnessHistoryStore = class {
44323
44360
  await this.push(id);
44324
44361
  return true;
44325
44362
  } catch (err) {
44326
- const message2 = err instanceof Error ? err.message : String(err);
44327
- this.lastRollback = { id, error: message2, at: this.clock.now().toISOString() };
44328
- this.log.line(`[harness-history] rollback to ${id} failed: ${message2}`);
44363
+ const message3 = err instanceof Error ? err.message : String(err);
44364
+ this.lastRollback = { id, error: message3, at: this.clock.now().toISOString() };
44365
+ this.log.line(`[harness-history] rollback to ${id} failed: ${message3}`);
44329
44366
  throw err;
44330
44367
  }
44331
44368
  }
@@ -49159,8 +49196,8 @@ async function scanMessages(state, messages, onBoundary, expandChild, fromOrdina
49159
49196
  if (msg.origin !== void 0 && msg.origin.length > 0) continue;
49160
49197
  const ms = tsMs(msg.timestamp);
49161
49198
  const p = msg.payload;
49162
- const isUserImage = msg.type === "model_msg" && p.type === "image_url";
49163
- if (!isUserImage) state.steeringOpen = false;
49199
+ const isUserMedia = msg.type === "model_msg" && (p.type === "image_url" || p.type === "video_url");
49200
+ if (!isUserMedia) state.steeringOpen = false;
49164
49201
  if (msg.type === "model_msg") {
49165
49202
  if (state.compactionActive) {
49166
49203
  touchTask(state, ms);
@@ -49192,7 +49229,7 @@ async function scanMessages(state, messages, onBoundary, expandChild, fromOrdina
49192
49229
  );
49193
49230
  continue;
49194
49231
  }
49195
- if (p.type === "image_url") {
49232
+ if (p.type === "image_url" || p.type === "video_url") {
49196
49233
  if (state.steeringOpen) {
49197
49234
  touchTask(state, ms);
49198
49235
  continue;
@@ -49448,6 +49485,7 @@ function buildContextBreakdown(messages) {
49448
49485
  switch (p.type) {
49449
49486
  case "text":
49450
49487
  case "image_url":
49488
+ case "video_url":
49451
49489
  case "inline_data":
49452
49490
  if (p.role === "assistant") out.assistantMessages += size;
49453
49491
  else out.userMessages += size;
@@ -49655,43 +49693,63 @@ var TraceLineIndex = class {
49655
49693
  }
49656
49694
  };
49657
49695
 
49658
- // src/services/trace-images.ts
49696
+ // src/services/trace-media.ts
49659
49697
  var REFERENCED_IMAGE_MIMES = /* @__PURE__ */ new Set([
49660
49698
  "image/png",
49661
49699
  "image/jpeg",
49662
49700
  "image/gif",
49663
49701
  "image/webp"
49664
49702
  ]);
49665
- function referableImage(url) {
49703
+ var REFERENCED_VIDEO_MIMES = /* @__PURE__ */ new Set([
49704
+ "video/mp4",
49705
+ "video/quicktime",
49706
+ "video/webm",
49707
+ "video/x-matroska",
49708
+ "video/x-msvideo"
49709
+ ]);
49710
+ function referableDataUrl(url, mimes) {
49666
49711
  if (!url.startsWith("data:")) return null;
49667
49712
  const comma = url.indexOf(",");
49668
49713
  if (comma < 0 || comma === url.length - 1) return null;
49669
49714
  const header = url.slice("data:".length, comma).toLowerCase();
49670
49715
  if (!header.endsWith(";base64")) return null;
49671
49716
  const mime = header.slice(0, header.indexOf(";")).trim();
49672
- return REFERENCED_IMAGE_MIMES.has(mime) ? { mime, dataStart: comma + 1 } : null;
49717
+ return mimes.has(mime) ? { mime, dataStart: comma + 1 } : null;
49673
49718
  }
49674
49719
  function traceImageUrl(sessionId, at, slot) {
49675
49720
  const query = `file=${at.fileIndex}&ordinal=${at.ordinal}${slot !== void 0 ? `&i=${slot}` : ""}`;
49676
49721
  return `/api/sessions/${encodeURIComponent(sessionId)}/trace-image?${query}`;
49677
49722
  }
49678
- function withImagesByReference(sessionId, msg) {
49723
+ function traceVideoUrl(sessionId, at) {
49724
+ const query = `file=${at.fileIndex}&ordinal=${at.ordinal}`;
49725
+ return `/api/sessions/${encodeURIComponent(sessionId)}/trace-video?${query}`;
49726
+ }
49727
+ function withMediaByReference(sessionId, msg) {
49679
49728
  const at = msg.tracePosition;
49680
49729
  if (at === void 0 || msg.type !== "model_msg") return msg;
49681
49730
  const kind = msg.payload.type;
49682
49731
  if (kind === "image_url") {
49683
49732
  const payload = msg.payload;
49684
- if (typeof payload.image_url !== "string" || referableImage(payload.image_url) === null) {
49733
+ if (typeof payload.image_url !== "string" || referableDataUrl(payload.image_url, REFERENCED_IMAGE_MIMES) === null) {
49685
49734
  return msg;
49686
49735
  }
49687
49736
  return { ...msg, payload: { ...payload, image_url: traceImageUrl(sessionId, at) } };
49688
49737
  }
49738
+ if (kind === "video_url") {
49739
+ const payload = msg.payload;
49740
+ if (typeof payload.video_url !== "string" || referableDataUrl(payload.video_url, REFERENCED_VIDEO_MIMES) === null) {
49741
+ return msg;
49742
+ }
49743
+ return { ...msg, payload: { ...payload, video_url: traceVideoUrl(sessionId, at) } };
49744
+ }
49689
49745
  if (kind === "tool_call_output") {
49690
49746
  const payload = msg.payload;
49691
49747
  if (!Array.isArray(payload.images)) return msg;
49692
49748
  let replaced = false;
49693
49749
  const images = payload.images.map((image, k) => {
49694
- if (typeof image !== "string" || referableImage(image) === null) return image;
49750
+ if (typeof image !== "string" || referableDataUrl(image, REFERENCED_IMAGE_MIMES) === null) {
49751
+ return image;
49752
+ }
49695
49753
  replaced = true;
49696
49754
  return traceImageUrl(sessionId, at, k);
49697
49755
  });
@@ -49699,19 +49757,21 @@ function withImagesByReference(sessionId, msg) {
49699
49757
  }
49700
49758
  return msg;
49701
49759
  }
49702
- function traceRecordImage(record, slot) {
49760
+ function traceRecordMedia(record, slot, kind) {
49703
49761
  if (record.type !== "model_msg") return null;
49704
49762
  const p = record.payload;
49763
+ const mimes = kind === "video" ? REFERENCED_VIDEO_MIMES : REFERENCED_IMAGE_MIMES;
49705
49764
  let url;
49706
49765
  if (slot === void 0) {
49707
- url = p.type === "image_url" ? p.image_url : void 0;
49766
+ const wanted = kind === "video" ? "video_url" : "image_url";
49767
+ url = p.type === wanted ? kind === "video" ? p.video_url : p.image_url : void 0;
49708
49768
  } else if (p.type === "tool_call_output" && Array.isArray(p.images)) {
49709
49769
  url = p.images[slot];
49710
49770
  }
49711
49771
  if (typeof url !== "string") return null;
49712
- const image = referableImage(url);
49713
- if (image === null) return null;
49714
- return { mime: image.mime, bytes: Buffer.from(url.slice(image.dataStart), "base64") };
49772
+ const media = referableDataUrl(url, mimes);
49773
+ if (media === null) return null;
49774
+ return { mime: media.mime, bytes: Buffer.from(url.slice(media.dataStart), "base64") };
49715
49775
  }
49716
49776
 
49717
49777
  // src/services/trace-service.ts
@@ -49719,7 +49779,7 @@ import { Component as Component36, Use as Use38 } from "@adelie/core/kernel";
49719
49779
  var IMPORT_SESSION_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
49720
49780
  var MAX_TRACE_BUNDLE_SHARDS = 200;
49721
49781
  function parseTraceShard(content, what) {
49722
- const invalid3 = (message2) => new HttpError(400, "invalid_trace", message2);
49782
+ const invalid3 = (message3) => new HttpError(400, "invalid_trace", message3);
49723
49783
  let records;
49724
49784
  try {
49725
49785
  records = parseTraceLines2(content, { onMalformed: "throw" });
@@ -49749,7 +49809,7 @@ function parseTraceBundle(content) {
49749
49809
  if (typeof parsed !== "object" || parsed === null) return null;
49750
49810
  const envelope = parsed;
49751
49811
  if (envelope.format !== "adelie-trace-bundle") return null;
49752
- const invalid3 = (message2) => new HttpError(400, "invalid_trace", message2);
49812
+ const invalid3 = (message3) => new HttpError(400, "invalid_trace", message3);
49753
49813
  if (envelope.version !== 1) {
49754
49814
  throw invalid3(`Unsupported Trace bundle version ${String(envelope.version)}.`);
49755
49815
  }
@@ -50164,7 +50224,7 @@ var TraceService = class {
50164
50224
  let unitBytes = 0;
50165
50225
  for (let i = starts[k].at; i < unitEnd; i++) {
50166
50226
  unitBytes += Buffer.byteLength(
50167
- JSON.stringify(withImagesByReference(sessionId, spanRaw[i])),
50227
+ JSON.stringify(withMediaByReference(sessionId, spanRaw[i])),
50168
50228
  "utf8"
50169
50229
  );
50170
50230
  }
@@ -50211,7 +50271,7 @@ var TraceService = class {
50211
50271
  * resumes from the selected shard exactly as it would after a restart.
50212
50272
  */
50213
50273
  async forkSessionTrace(projectId, agentId, sourceSessionId, position) {
50214
- const invalid3 = (message2) => new HttpError(400, "invalid_fork_position", message2);
50274
+ const invalid3 = (message3) => new HttpError(400, "invalid_fork_position", message3);
50215
50275
  const files = await this.locateAll(projectId, agentId, sourceSessionId);
50216
50276
  const targetFilePos = files.findIndex((file) => file.index === position.fileIndex);
50217
50277
  if (targetFilePos < 0) throw invalid3("The selected reply no longer exists in this Session.");
@@ -50255,7 +50315,7 @@ var TraceService = class {
50255
50315
  }
50256
50316
  if (!completedRequest) throw invalid3("The selected assistant reply has not completed yet.");
50257
50317
  const resumed = resumeTrace(targetShard.slice(0, cutOrdinal));
50258
- if (resumed.contextClosed || resumed.carryOver.length > 0 || !resumed.history.some((message2) => message2 === target)) {
50318
+ if (resumed.contextClosed || resumed.carryOver.length > 0 || !resumed.history.some((message3) => message3 === target)) {
50259
50319
  throw invalid3("The selected reply is not a complete resumable Task boundary.");
50260
50320
  }
50261
50321
  const firstMeta = shards.flat().find(isSessionMeta6);
@@ -50384,20 +50444,20 @@ var TraceService = class {
50384
50444
  return { events, offset, limit, total };
50385
50445
  }
50386
50446
  /**
50387
- * One image of one Trace record, decoded: the `image_url` of the record at
50388
- * (`fileIndex`, `ordinal`), or its tool output's `images[slot]`. This is what a windowed
50389
- * history page's image URL names (trace-images.ts). 404 `trace_image_not_found` when the
50390
- * file, the record or the slot holds no image the route serves.
50447
+ * One image or video of one Trace record, decoded: the `image_url` / `video_url` of the
50448
+ * record at (`fileIndex`, `ordinal`), or its tool output's `images[slot]`. This is what a
50449
+ * windowed history page's media URL names (trace-media.ts). 404, with a code per kind, when
50450
+ * the file, the record or the slot holds no media the route serves.
50391
50451
  */
50392
- async readTraceImage(projectId, agentId, sessionId, fileIndex, ordinal, slot) {
50393
- const notFound2 = () => new HttpError(404, "trace_image_not_found", "This Trace record holds no such image.");
50452
+ async readTraceMedia(projectId, agentId, sessionId, fileIndex, ordinal, slot, kind) {
50453
+ const notFound2 = () => new HttpError(404, `trace_${kind}_not_found`, `This Trace record holds no such ${kind}.`);
50394
50454
  const files = await this.locateAll(projectId, agentId, sessionId);
50395
50455
  const file = files.find((f) => f.index === fileIndex);
50396
50456
  if (file === void 0) throw notFound2();
50397
50457
  const { events } = await this.lineIndex.readRange(file.path, ordinal, 1);
50398
- const image = events[0] !== void 0 ? traceRecordImage(events[0], slot) : null;
50399
- if (image === null) throw notFound2();
50400
- return image;
50458
+ const media = events[0] !== void 0 ? traceRecordMedia(events[0], slot, kind) : null;
50459
+ if (media === null) throw notFound2();
50460
+ return media;
50401
50461
  }
50402
50462
  /**
50403
50463
  * What the analysis prices a file's Requests on: the paired reference named by the file's own
@@ -54596,12 +54656,14 @@ import path27 from "path";
54596
54656
  import { Hono as Hono22 } from "hono";
54597
54657
  import {
54598
54658
  THINKING_LEVEL_NAMES as THINKING_LEVEL_NAMES2,
54659
+ catalogSupportsVideo,
54599
54660
  imageUrlMessage as imageUrlMessage3,
54600
54661
  isValidId as isValidId4,
54601
54662
  scratchpadDir as scratchpadDir3,
54602
54663
  sessionScratchpadDir,
54603
54664
  stripLeadingMarkerBlocks,
54604
- userText as userText5
54665
+ userText as userText5,
54666
+ videoUrlMessage
54605
54667
  } from "@adelie/core";
54606
54668
 
54607
54669
  // src/services/workspace-guard.ts
@@ -55049,13 +55111,13 @@ var ModelBalances = class {
55049
55111
  }
55050
55112
  return { ok: true, provider, ...figures, fetchedAt: new Date(this.now()).toISOString() };
55051
55113
  }
55052
- failure(provider, error, message2, status) {
55114
+ failure(provider, error, message3, status) {
55053
55115
  return {
55054
55116
  ok: false,
55055
55117
  provider,
55056
55118
  error,
55057
55119
  ...status !== void 0 ? { status } : {},
55058
- message: message2,
55120
+ message: message3,
55059
55121
  fetchedAt: new Date(this.now()).toISOString()
55060
55122
  };
55061
55123
  }
@@ -55413,8 +55475,8 @@ function modelOAuthCallbackRoutes(deps) {
55413
55475
  try {
55414
55476
  deps.modelOAuth.deposit({ flowId: flowId3, projectId, code });
55415
55477
  } catch (err) {
55416
- const message2 = err instanceof HttpError ? err.message : "Something went wrong. Return to Adelie and start a new authorization.";
55417
- return c.html(resultPage("Authorization failed", message2), 400);
55478
+ const message3 = err instanceof HttpError ? err.message : "Something went wrong. Return to Adelie and start a new authorization.";
55479
+ return c.html(resultPage("Authorization failed", message3), 400);
55418
55480
  }
55419
55481
  return c.html(
55420
55482
  resultPage(
@@ -56089,12 +56151,12 @@ function pageLimit(raw, name) {
56089
56151
  }
56090
56152
  function appendPendingInputs(messages, pending) {
56091
56153
  if (pending.length === 0) return messages;
56092
- const envelopeKey = (message2) => {
56093
- const { tracePosition: _tracePosition, ...envelope } = message2;
56154
+ const envelopeKey = (message3) => {
56155
+ const { tracePosition: _tracePosition, ...envelope } = message3;
56094
56156
  return JSON.stringify(envelope);
56095
56157
  };
56096
56158
  const tail = new Set(messages.slice(-50).map(envelopeKey));
56097
- const missing = pending.filter((message2) => !tail.has(envelopeKey(message2)));
56159
+ const missing = pending.filter((message3) => !tail.has(envelopeKey(message3)));
56098
56160
  return missing.length > 0 ? [...messages, ...missing] : messages;
56099
56161
  }
56100
56162
  function messagesPageQuery(c) {
@@ -56156,6 +56218,49 @@ function requireImageUrl(url, field) {
56156
56218
  `${field} must be an http(s) URL or a base64 data: URL (data:<mime>;base64,<bytes>).`
56157
56219
  );
56158
56220
  }
56221
+ var VIDEO_DATA_URL = /^data:video\/[^;,]+;base64,[A-Za-z0-9+/=\s]+$/;
56222
+ function requireVideoUrl(url, field) {
56223
+ if (typeof url === "string") {
56224
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
56225
+ if (VIDEO_DATA_URL.test(url)) {
56226
+ const bytes = dataUrlByteLength(url);
56227
+ if (bytes > INLINE_VIDEO_MAX_BYTES) {
56228
+ throw new HttpError(
56229
+ 413,
56230
+ "video_too_large",
56231
+ `${field} exceeds the ${INLINE_VIDEO_MAX_MB}MB inline video limit.`
56232
+ );
56233
+ }
56234
+ return url;
56235
+ }
56236
+ }
56237
+ throw badRequest(
56238
+ `${field} must be an http(s) URL or a base64 video data: URL (data:video/<type>;base64,<bytes>).`
56239
+ );
56240
+ }
56241
+ function decodeVideoDataUrl(url, field) {
56242
+ const match = /^data:([^;,]+);base64,([A-Za-z0-9+/=\s]+)$/.exec(url);
56243
+ if (!match) {
56244
+ throw badRequest(`${field} must be a base64 data: URL for the video to be written to disk.`);
56245
+ }
56246
+ return { bytes: Buffer.from(match[2], "base64"), mime: match[1] };
56247
+ }
56248
+ var VIDEO_MIME_EXTENSIONS = {
56249
+ "video/mp4": ".mp4",
56250
+ "video/quicktime": ".mov",
56251
+ "video/webm": ".webm",
56252
+ "video/x-matroska": ".mkv",
56253
+ "video/x-msvideo": ".avi"
56254
+ };
56255
+ function videoFileName(given, mime) {
56256
+ if (typeof given === "string" && given.length > 0) return given;
56257
+ const ext = VIDEO_MIME_EXTENSIONS[mime.toLowerCase()] ?? "";
56258
+ return `video${ext || ".mp4"}`;
56259
+ }
56260
+ function videoAttachment(url, given, field) {
56261
+ const { bytes, mime } = decodeVideoDataUrl(url, field);
56262
+ return { fileName: videoFileName(given, mime), bytes, mime };
56263
+ }
56159
56264
  function dataUrlByteLength(dataUrl) {
56160
56265
  const payload = dataUrl.slice(dataUrl.indexOf(",") + 1).replace(/[=\s]+/g, "");
56161
56266
  return Math.floor(payload.length * 3 / 4);
@@ -56169,7 +56274,19 @@ function resolveScratchpadFile(dir, fileName) {
56169
56274
  const resolved = path27.resolve(dir, fileName);
56170
56275
  return path27.dirname(resolved) === path27.resolve(dir) ? resolved : null;
56171
56276
  }
56172
- function parseTaskInput(body, limits) {
56277
+ async function sessionTakesVideo(deps, row) {
56278
+ if (catalogSupportsVideo(row.provider, row.modelId)) return true;
56279
+ try {
56280
+ const project = await deps.projectConfigService.loadConfig(row.projectId);
56281
+ const entry = (project.models ?? []).find(
56282
+ (m) => m.provider === row.provider && m.model_id === row.modelId
56283
+ );
56284
+ return entry?.video === true;
56285
+ } catch {
56286
+ return false;
56287
+ }
56288
+ }
56289
+ function parseTaskInput(body, limits, videoInput) {
56173
56290
  const input = body.input;
56174
56291
  if (!Array.isArray(input) || input.length === 0) {
56175
56292
  throw badRequest("input must be an array with at least one item.");
@@ -56178,6 +56295,7 @@ function parseTaskInput(body, limits) {
56178
56295
  const attachments = [];
56179
56296
  const texts = [];
56180
56297
  const images = [];
56298
+ const videos = [];
56181
56299
  input.forEach((item, i) => {
56182
56300
  if (item === null || typeof item !== "object" || Array.isArray(item)) {
56183
56301
  throw badRequest(`input[${i}] must be an object.`);
@@ -56197,19 +56315,31 @@ function parseTaskInput(body, limits) {
56197
56315
  images.push(url);
56198
56316
  return;
56199
56317
  }
56318
+ if (part.type === "video_url") {
56319
+ const url = requireVideoUrl(part.videoUrl, `input[${i}].videoUrl`);
56320
+ if (videoInput) {
56321
+ messages.push(videoUrlMessage(url));
56322
+ videos.push(url);
56323
+ } else {
56324
+ attachments.push(videoAttachment(url, part.fileName, `input[${i}].videoUrl`));
56325
+ assertAttachmentBudget(attachments, limits);
56326
+ }
56327
+ return;
56328
+ }
56200
56329
  if (part.type === "file") {
56201
56330
  attachments.push(parseAttachmentPart(part, i, limits));
56202
56331
  assertAttachmentBudget(attachments, limits);
56203
56332
  return;
56204
56333
  }
56205
- throw badRequest(`input[${i}].type must be one of text / image_url / file.`);
56334
+ throw badRequest(`input[${i}].type must be one of text / image_url / video_url / file.`);
56206
56335
  });
56207
- return { messages, attachments, texts, images };
56336
+ return { messages, attachments, texts, images, videos };
56208
56337
  }
56209
- function recallStore(text2, images, attachments, written) {
56338
+ function recallStore(text2, images, videos, attachments, written) {
56210
56339
  return {
56211
56340
  text: text2,
56212
56341
  images,
56342
+ videos,
56213
56343
  files: written.map((p, i) => ({
56214
56344
  fileName: attachments[i].fileName,
56215
56345
  path: p,
@@ -56220,7 +56350,7 @@ function recallStore(text2, images, attachments, written) {
56220
56350
  async function recalledResponse(recall) {
56221
56351
  const files = await readRecalledFiles(recall.files);
56222
56352
  await removeAttachments(recall.files.map((f) => f.path));
56223
- return { text: recall.text, images: recall.images, files };
56353
+ return { text: recall.text, images: recall.images, videos: recall.videos, files };
56224
56354
  }
56225
56355
  function parseSteerImages(body) {
56226
56356
  const images = body.images;
@@ -56228,6 +56358,17 @@ function parseSteerImages(body) {
56228
56358
  if (!Array.isArray(images)) throw badRequest("images must be an array.");
56229
56359
  return images.map((url, i) => requireImageUrl(url, `images[${i}]`));
56230
56360
  }
56361
+ function parseSteerVideos(body, videoInput) {
56362
+ const videos = body.videos;
56363
+ if (videos === void 0) return [];
56364
+ if (!Array.isArray(videos)) throw badRequest("videos must be an array.");
56365
+ if (!videoInput) {
56366
+ throw badRequest(
56367
+ "This Session's model takes no video input; send the video as a file attachment instead."
56368
+ );
56369
+ }
56370
+ return videos.map((url, i) => requireVideoUrl(url, `videos[${i}]`));
56371
+ }
56231
56372
  function parseSteerFiles(body, limits) {
56232
56373
  const files = body.files;
56233
56374
  if (files === void 0) return [];
@@ -56654,13 +56795,14 @@ function sessionsRoutes(deps) {
56654
56795
  if (fileIndex === void 0 || ordinal === void 0) {
56655
56796
  throw badRequest("file and ordinal are required.");
56656
56797
  }
56657
- const image = await deps.traceService.readTraceImage(
56798
+ const image = await deps.traceService.readTraceMedia(
56658
56799
  row.projectId,
56659
56800
  row.agentId,
56660
56801
  row.sessionId,
56661
56802
  fileIndex,
56662
56803
  ordinal,
56663
- intQuery("i", 0)
56804
+ intQuery("i", 0),
56805
+ "image"
56664
56806
  );
56665
56807
  return c.body(new Uint8Array(image.bytes), 200, {
56666
56808
  "content-type": image.mime,
@@ -56668,6 +56810,28 @@ function sessionsRoutes(deps) {
56668
56810
  "cache-control": "private, max-age=31536000, immutable"
56669
56811
  });
56670
56812
  });
56813
+ app.get("/:sessionId/trace-video", async (c) => {
56814
+ const row = resolveSession(c);
56815
+ const fileIndex = parseNonNegativeInt(c.req.query("file") ?? "");
56816
+ const ordinal = parseNonNegativeInt(c.req.query("ordinal") ?? "");
56817
+ if (fileIndex === null || fileIndex < 1 || ordinal === null) {
56818
+ throw badRequest("file (at least 1) and ordinal are required.");
56819
+ }
56820
+ const video = await deps.traceService.readTraceMedia(
56821
+ row.projectId,
56822
+ row.agentId,
56823
+ row.sessionId,
56824
+ fileIndex,
56825
+ ordinal,
56826
+ void 0,
56827
+ "video"
56828
+ );
56829
+ return c.body(new Uint8Array(video.bytes), 200, {
56830
+ "content-type": video.mime,
56831
+ "x-content-type-options": "nosniff",
56832
+ "cache-control": "private, max-age=31536000, immutable"
56833
+ });
56834
+ });
56671
56835
  app.get("/:sessionId/messages", async (c) => {
56672
56836
  const row = resolveSession(c);
56673
56837
  const page = messagesPageQuery(c);
@@ -56707,7 +56871,7 @@ function sessionsRoutes(deps) {
56707
56871
  ...result.agentState !== void 0 ? { agentState: result.agentState } : {}
56708
56872
  };
56709
56873
  const messages2 = appendPendingInputs(result.messages, pendingInputs).map(
56710
- (m) => withImagesByReference(row.sessionId, m)
56874
+ (m) => withMediaByReference(row.sessionId, m)
56711
56875
  );
56712
56876
  return c.json({
56713
56877
  messages: messages2,
@@ -56763,14 +56927,15 @@ function sessionsRoutes(deps) {
56763
56927
  const body = await readJson(c);
56764
56928
  const goal = parseGoalField(body);
56765
56929
  const limits = toAttachmentLimits(deps.serverSettingsRepo.getAttachmentLimitsMb());
56930
+ const videoInput = await sessionTakesVideo(deps, row);
56766
56931
  if (goal) {
56767
- const { messages, attachments } = parseTaskInput(body, limits);
56932
+ const { messages, attachments } = parseTaskInput(body, limits, videoInput);
56768
56933
  const text2 = messages.filter((m) => m.payload.type === "text").map((m) => m.payload.text).join("\n").trim();
56769
56934
  if (!text2) {
56770
56935
  throw badRequest("goal mode requires a non-empty text objective.");
56771
56936
  }
56772
56937
  if (attachments.length > 0) {
56773
- throw badRequest("goal mode accepts text and images only (no file attachments).");
56938
+ throw badRequest("goal mode accepts text, images and videos only (no file attachments).");
56774
56939
  }
56775
56940
  const objective = stripLeadingMarkerBlocks(text2).trim() || text2;
56776
56941
  const { sessionId } = await deps.manager.startGoal(row.sessionId, {
@@ -56780,7 +56945,7 @@ function sessionsRoutes(deps) {
56780
56945
  });
56781
56946
  return c.json({ sessionId }, 202);
56782
56947
  }
56783
- const parsed = parseTaskInput(body, limits);
56948
+ const parsed = parseTaskInput(body, limits, videoInput);
56784
56949
  const queueIfBusy = body.queueIfBusy === true;
56785
56950
  deps.manager.assertCanAcceptTask(row.sessionId, { queueIfBusy });
56786
56951
  const { input, written } = await attachFilesToInput(
@@ -56794,7 +56959,13 @@ function sessionsRoutes(deps) {
56794
56959
  queueIfBusy,
56795
56960
  // Original content, kept while the input waits in the follow-up queue so a recall
56796
56961
  // (DELETE /follow-ups/:id) can hand it back; unused when the task starts directly.
56797
- recall: recallStore(parsed.texts.join("\n"), parsed.images, parsed.attachments, written)
56962
+ recall: recallStore(
56963
+ parsed.texts.join("\n"),
56964
+ parsed.images,
56965
+ parsed.videos,
56966
+ parsed.attachments,
56967
+ written
56968
+ )
56798
56969
  });
56799
56970
  return c.json({ sessionId, queued }, 202);
56800
56971
  } catch (err) {
@@ -56807,21 +56978,26 @@ function sessionsRoutes(deps) {
56807
56978
  const body = await readJson(c);
56808
56979
  const text2 = typeof body.text === "string" ? body.text.trim() : "";
56809
56980
  const images = parseSteerImages(body);
56981
+ const videos = parseSteerVideos(body, await sessionTakesVideo(deps, row));
56810
56982
  const files = parseSteerFiles(
56811
56983
  body,
56812
56984
  toAttachmentLimits(deps.serverSettingsRepo.getAttachmentLimitsMb())
56813
56985
  );
56814
- if (!text2 && images.length === 0 && files.length === 0) {
56815
- throw badRequest("text, images or files must carry the steering message.");
56986
+ if (!text2 && images.length === 0 && videos.length === 0 && files.length === 0) {
56987
+ throw badRequest("text, images, videos or files must carry the steering message.");
56816
56988
  }
56817
56989
  const { input, written } = await attachFilesToInput(
56818
- [...text2 ? [userText5(text2)] : [], ...images.map((url) => imageUrlMessage3(url))],
56990
+ [
56991
+ ...text2 ? [userText5(text2)] : [],
56992
+ ...images.map((url) => imageUrlMessage3(url)),
56993
+ ...videos.map((url) => videoUrlMessage(url))
56994
+ ],
56819
56995
  files,
56820
56996
  scratchpadDir3(deps.config.root, row.projectId, row.agentId),
56821
56997
  row.sessionId
56822
56998
  );
56823
56999
  try {
56824
- deps.manager.steer(row.sessionId, input, recallStore(text2, images, files, written));
57000
+ deps.manager.steer(row.sessionId, input, recallStore(text2, images, videos, files, written));
56825
57001
  } catch (err) {
56826
57002
  await removeAttachments(written);
56827
57003
  throw err;
@@ -60717,13 +60893,13 @@ function dialThroughSocks(socksPort, host, port, timeoutMs = SOCKS_HANDSHAKE_TIM
60717
60893
  let buffer = Buffer.alloc(0);
60718
60894
  let stage = "greet";
60719
60895
  let settled = false;
60720
- const fail2 = (message2) => {
60896
+ const fail2 = (message3) => {
60721
60897
  if (settled) return;
60722
60898
  settled = true;
60723
60899
  socket.removeListener("data", onData);
60724
60900
  socket.removeListener("close", onClose);
60725
60901
  socket.destroy();
60726
- reject(new Error(message2));
60902
+ reject(new Error(message3));
60727
60903
  };
60728
60904
  const onData = (chunk) => {
60729
60905
  buffer = Buffer.concat([buffer, chunk]);
@@ -61411,8 +61587,8 @@ var APPLY_TIMEOUT_MS = 10 * 6e4;
61411
61587
  function refusalDetail(status, text2) {
61412
61588
  try {
61413
61589
  const body = JSON.parse(text2);
61414
- const message2 = body.error?.message;
61415
- if (typeof message2 === "string" && message2 !== "") return message2;
61590
+ const message3 = body.error?.message;
61591
+ if (typeof message3 === "string" && message3 !== "") return message3;
61416
61592
  } catch {
61417
61593
  }
61418
61594
  const said = text2.trim();
@@ -65665,10 +65841,10 @@ ${serializeTicket(ticket.doc).trimEnd()}`
65665
65841
 
65666
65842
  // src/runtime/organization/reconcile.ts
65667
65843
  var nowOf = (deps) => deps.now?.() ?? Date.now();
65668
- function recordError(deps, org, code, message2, agentId) {
65844
+ function recordError(deps, org, code, message3, agentId) {
65669
65845
  deps.errors.record({
65670
65846
  source: "organization",
65671
- err: new Error(message2),
65847
+ err: new Error(message3),
65672
65848
  code,
65673
65849
  ctx: { projectId: org.projectId, ...agentId !== void 0 ? { agentId } : {} }
65674
65850
  });
@@ -66447,7 +66623,7 @@ var notAMember = (channelId, principal, why) => new HttpError(
66447
66623
  "not_a_member",
66448
66624
  why ?? `${principal} is not a member of the channel ${channelId}.`
66449
66625
  );
66450
- var allHandsImmutable = (message2) => new HttpError(400, "all_hands_immutable", message2);
66626
+ var allHandsImmutable = (message3) => new HttpError(400, "all_hands_immutable", message3);
66451
66627
  var requireChainLimit = (value) => {
66452
66628
  if (!Number.isInteger(value) || value < 0 || value > MENTION_CHAIN_LIMIT_MAX) {
66453
66629
  throw badRequest(
@@ -66497,14 +66673,14 @@ var OrganizationService = class {
66497
66673
  );
66498
66674
  }
66499
66675
  /** One dead end of an id proposal, in the log and in the Project's errors panel. */
66500
- recordIdSuggestFailure(projectId, message2) {
66501
- this.deps.log?.(`org: id suggestion fell back for ${projectId}: ${message2}`);
66676
+ recordIdSuggestFailure(projectId, message3) {
66677
+ this.deps.log?.(`org: id suggestion fell back for ${projectId}: ${message3}`);
66502
66678
  this.deps.errors.record({
66503
66679
  source: "organization",
66504
66680
  code: "id_suggest_failed",
66505
66681
  kind: "expected",
66506
66682
  ctx: { projectId },
66507
- err: new Error(message2)
66683
+ err: new Error(message3)
66508
66684
  });
66509
66685
  }
66510
66686
  async requireOrg(projectId, orgId) {
@@ -69100,7 +69276,7 @@ function organizationRoutes(deps) {
69100
69276
  member(c, projectId);
69101
69277
  const body = await readJson(c).catch(() => ({}));
69102
69278
  const agentId = optionalString(body, "agentId", { minLen: 2, maxLen: 64 });
69103
- const message2 = optionalString(body, "message", { maxLen: 1e5 });
69279
+ const message3 = optionalString(body, "message", { maxLen: 1e5 });
69104
69280
  const workspace = optionalString(body, "workspace", { minLen: 1, maxLen: 4096 });
69105
69281
  const res = await deps.orgService.startTicket(
69106
69282
  projectId,
@@ -69108,7 +69284,7 @@ function organizationRoutes(deps) {
69108
69284
  ticketId,
69109
69285
  {
69110
69286
  ...agentId !== void 0 ? { agentId } : {},
69111
- ...message2 !== void 0 ? { message: message2 } : {},
69287
+ ...message3 !== void 0 ? { message: message3 } : {},
69112
69288
  ...workspace !== void 0 ? { workspace } : {}
69113
69289
  },
69114
69290
  // Who asks decides whether it may: an employee starts sessions only on its own tickets.
@@ -70292,8 +70468,8 @@ var DRAFT_SHORTCUT_MAX_COUNT = 3;
70292
70468
  var DRAFT_SHORTCUT_TITLE_MAX = 40;
70293
70469
  var DRAFT_SHORTCUT_PROMPT_MAX = 4e3;
70294
70470
  var DRAFT_SHORTCUT_ID_MAX = 64;
70295
- function invalid(message2) {
70296
- return new HttpError(400, "invalid_draft_shortcuts", message2);
70471
+ function invalid(message3) {
70472
+ return new HttpError(400, "invalid_draft_shortcuts", message3);
70297
70473
  }
70298
70474
  function shortcutField(entry, key2, index, maxLen) {
70299
70475
  const value = entry[key2];
@@ -70337,8 +70513,8 @@ var KEYBINDINGS_BYTES_MAX = 8 * 1024;
70337
70513
  var SECTIONS = ["mac", "windows", "linux"];
70338
70514
  var ID_RE = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
70339
70515
  var CHORD_RE = /^(Mod\+)?(Ctrl\+)?(Alt\+)?(Shift\+)?[A-Za-z][A-Za-z0-9]*$/;
70340
- function invalid2(message2) {
70341
- return new HttpError(400, "invalid_keybindings", message2);
70516
+ function invalid2(message3) {
70517
+ return new HttpError(400, "invalid_keybindings", message3);
70342
70518
  }
70343
70519
  function validateKeybindings(value) {
70344
70520
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -70437,6 +70613,7 @@ function meRoutes(deps) {
70437
70613
  ...deps.serverSettingsRepo.getAttachmentLimitsMb(),
70438
70614
  attachmentMaxCount: MAX_ATTACHMENT_COUNT,
70439
70615
  imageMaxMb: INLINE_IMAGE_MAX_MB,
70616
+ videoMaxMb: INLINE_VIDEO_MAX_MB,
70440
70617
  attachmentLimitMinMb: MIN_ATTACHMENT_MB,
70441
70618
  attachmentLimitMaxMb: MAX_ATTACHMENT_MB
70442
70619
  },
@@ -70738,13 +70915,399 @@ InstallRoutes = __decorateElement(_init131, 0, "InstallRoutes", _InstallRoutes_d
70738
70915
  __runInitializers(_init131, 1, InstallRoutes);
70739
70916
 
70740
70917
  // src/http/routes/themes.ts
70741
- import { readdir, readFile } from "fs/promises";
70918
+ import { mkdir, readdir, readFile, rm as rm2, writeFile } from "fs/promises";
70742
70919
  import { join as join2 } from "path";
70743
70920
  import { Hono as Hono41 } from "hono";
70744
- import { externalThemesCss, readExternalTheme } from "@adelie/core";
70921
+ import { externalThemesCss, MAX_THEME_FILE_BYTES as MAX_THEME_FILE_BYTES2, readExternalTheme } from "@adelie/core";
70745
70922
  import { Bind as Bind25, Component as Component65, Use as Use67 } from "@adelie/core/kernel";
70923
+
70924
+ // src/services/theme-download.ts
70925
+ import { MAX_THEME_FILE_BYTES } from "@adelie/core";
70926
+
70927
+ // src/services/plugin-download.ts
70928
+ import { createHash as createHash9, timingSafeEqual as timingSafeEqual3 } from "crypto";
70929
+ import { isIP } from "net";
70930
+ var MAX_PLUGIN_ARCHIVE_BYTES = 32 * 1024 * 1024;
70931
+ var DOWNLOAD_TIMEOUT_MS = 6e4;
70932
+ function normalizePluginUrl(raw) {
70933
+ const trimmed = raw.trim();
70934
+ if (trimmed === "") throw badRequest("url must not be empty.");
70935
+ let url;
70936
+ try {
70937
+ url = new URL(trimmed);
70938
+ } catch {
70939
+ throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
70940
+ }
70941
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
70942
+ throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
70943
+ }
70944
+ assertHostAllowed(url);
70945
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
70946
+ if (host !== "github.com") {
70947
+ const file = url.pathname.split("/").filter(Boolean).pop() ?? "";
70948
+ const suggested = archiveStem(file);
70949
+ return { fetchUrl: url.toString(), subdir: "", ...suggested ? { name: suggested } : {} };
70950
+ }
70951
+ const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
70952
+ const [owner3, repo, kind, ...rest] = segments;
70953
+ if (!owner3 || !repo) {
70954
+ throw new HttpError(
70955
+ 400,
70956
+ "unsupported_url",
70957
+ "Not a GitHub repository URL: expected /<owner>/<repo>."
70958
+ );
70959
+ }
70960
+ const repoUrl = `https://github.com/${owner3}/${repo}`;
70961
+ if (kind === void 0) {
70962
+ return { fetchUrl: `${repoUrl}/archive/HEAD.zip`, subdir: "", name: repo };
70963
+ }
70964
+ if (kind !== "tree") {
70965
+ return { fetchUrl: url.toString(), subdir: "", name: repo };
70966
+ }
70967
+ const [ref, ...sub] = rest;
70968
+ if (!ref) throw new HttpError(400, "unsupported_url", "A GitHub tree URL needs a branch or tag.");
70969
+ const subdir = sub.join("/");
70970
+ return {
70971
+ fetchUrl: `${repoUrl}/archive/${encodeURIComponent(ref)}.zip`,
70972
+ subdir,
70973
+ // The plugin directory is the last segment of the subdirectory — that is its name.
70974
+ name: sub.length > 0 ? sub[sub.length - 1] : repo
70975
+ };
70976
+ }
70977
+ var NPM_REGISTRY = "https://registry.npmjs.org";
70978
+ var NPM_NAME = /^(?:@[A-Za-z0-9._~!*'()-]+\/)?[A-Za-z0-9._~!*'()-]+$/;
70979
+ var NPM_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
70980
+ function parseNpmPluginSpec(raw) {
70981
+ const trimmed = raw.trim();
70982
+ if (trimmed === "") return null;
70983
+ if (trimmed.toLowerCase().startsWith("npm:")) {
70984
+ return parseNpmName(trimmed.slice("npm:".length).trim());
70985
+ }
70986
+ if (/^https?:\/\//i.test(trimmed)) {
70987
+ let url;
70988
+ try {
70989
+ url = new URL(trimmed);
70990
+ } catch {
70991
+ return null;
70992
+ }
70993
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
70994
+ if (host === "npmjs.com") {
70995
+ const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
70996
+ if (segments[0] !== "package") return null;
70997
+ const scoped = segments[1] === void 0 ? void 0 : segments[1].startsWith("@");
70998
+ const name = scoped ? `${segments[1]}/${segments[2] ?? ""}` : segments[1];
70999
+ if (name === void 0 || name.endsWith("/")) return null;
71000
+ const marker = scoped ? 3 : 2;
71001
+ const version = segments[marker] === "v" ? segments[marker + 1] : void 0;
71002
+ return parseNpmName(version === void 0 ? name : `${name}@${version}`);
71003
+ }
71004
+ if (host === "registry.npmjs.org") {
71005
+ const rest = decodeURIComponent(url.pathname.replace(/^\//, ""));
71006
+ const segments = rest.split("/");
71007
+ if (segments[0] === void 0 || segments[0] === "") return null;
71008
+ const scoped = segments[0].startsWith("@");
71009
+ const name = scoped ? `${segments[0]}/${segments[1] ?? ""}` : segments[0];
71010
+ if (name.endsWith("/") || segments.length > (scoped ? 2 : 1)) return null;
71011
+ return parseNpmName(name);
71012
+ }
71013
+ return null;
71014
+ }
71015
+ return parseNpmName(trimmed);
71016
+ }
71017
+ function parseNpmName(spec) {
71018
+ if (spec === "") return null;
71019
+ const at = spec.lastIndexOf("@");
71020
+ const name = at > 0 ? spec.slice(0, at) : spec;
71021
+ const version = at > 0 ? spec.slice(at + 1) : void 0;
71022
+ if (name.length > 214 || !NPM_NAME.test(name)) return null;
71023
+ if (version !== void 0 && !NPM_VERSION.test(version)) return null;
71024
+ return version === void 0 ? { name } : { name, version };
71025
+ }
71026
+ async function resolvePluginSource(raw, options = {}) {
71027
+ const spec = parseNpmPluginSpec(raw);
71028
+ if (spec === null) return normalizePluginUrl(raw);
71029
+ const found = await fetchPackument(spec, options);
71030
+ const entry = pickVersion(spec, found);
71031
+ const tarball = entry.tarball;
71032
+ let url;
71033
+ try {
71034
+ url = new URL(tarball);
71035
+ } catch {
71036
+ throw new HttpError(400, "download_failed", "The registry named an unreadable tarball URL.");
71037
+ }
71038
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
71039
+ throw new HttpError(400, "download_failed", "The registry named an unsupported tarball URL.");
71040
+ }
71041
+ assertHostAllowed(url);
71042
+ return {
71043
+ fetchUrl: url.toString(),
71044
+ subdir: "",
71045
+ // The package's own name, minus the scope: the scope names a publisher, and the plugin is
71046
+ // installed under the name its own repository gives it (`@scope/use-firecrawl` → `use-firecrawl`).
71047
+ name: spec.name.slice(spec.name.startsWith("@") ? spec.name.indexOf("/") + 1 : 0),
71048
+ ...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
71049
+ };
71050
+ }
71051
+ async function fetchPackument(spec, options) {
71052
+ const doFetch = options.fetchImpl ?? fetch;
71053
+ const encoded = spec.name.startsWith("@") ? `@${encodeURIComponent(spec.name.slice(1))}` : encodeURIComponent(spec.name);
71054
+ const url = `${NPM_REGISTRY}/${encoded}`;
71055
+ let res;
71056
+ try {
71057
+ res = await doFetch(url, {
71058
+ method: "GET",
71059
+ redirect: "follow",
71060
+ // The abbreviated document: dist-tags and each version's dist, which is all this reads, and a
71061
+ // fraction of the full packument for a package with a long history.
71062
+ headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
71063
+ signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
71064
+ });
71065
+ } catch (err) {
71066
+ throw new HttpError(400, "npm_registry_failed", `Could not reach npm: ${message(err)}`);
71067
+ }
71068
+ if (res.url) assertHostAllowed(new URL(res.url));
71069
+ if (res.status === 404) {
71070
+ throw new HttpError(404, "npm_package_not_found", `npm has no package ${spec.name}.`);
71071
+ }
71072
+ if (!res.ok) {
71073
+ await res.body?.cancel().catch(() => void 0);
71074
+ throw new HttpError(
71075
+ 400,
71076
+ "npm_registry_failed",
71077
+ `npm answered HTTP ${res.status} for ${spec.name}.`
71078
+ );
71079
+ }
71080
+ try {
71081
+ const data = await res.json();
71082
+ if (typeof data !== "object" || data === null) throw new Error("not an object");
71083
+ return data;
71084
+ } catch {
71085
+ throw new HttpError(
71086
+ 400,
71087
+ "npm_registry_failed",
71088
+ `npm returned no usable data for ${spec.name}.`
71089
+ );
71090
+ }
71091
+ }
71092
+ function pickVersion(spec, packument) {
71093
+ const versions = packument["versions"];
71094
+ const table = typeof versions === "object" && versions !== null ? versions : {};
71095
+ const distTags = packument["dist-tags"];
71096
+ const tags = typeof distTags === "object" && distTags !== null ? distTags : {};
71097
+ const version = spec.version ?? (typeof tags["latest"] === "string" ? tags["latest"] : void 0);
71098
+ if (version === void 0) {
71099
+ throw new HttpError(404, "npm_version_not_found", `${spec.name} has no published version.`);
71100
+ }
71101
+ const entry = table[version];
71102
+ if (typeof entry !== "object" || entry === null) {
71103
+ throw new HttpError(
71104
+ 404,
71105
+ "npm_version_not_found",
71106
+ `npm has no version ${version} of ${spec.name}; use an exact version or a dist-tag.`
71107
+ );
71108
+ }
71109
+ const dist = entry["dist"];
71110
+ const tarball = typeof dist === "object" && dist !== null ? dist["tarball"] : void 0;
71111
+ if (typeof tarball !== "string" || tarball === "") {
71112
+ throw new HttpError(404, "npm_version_not_found", `${spec.name}@${version} has no tarball.`);
71113
+ }
71114
+ const integrity = dist["integrity"];
71115
+ const entryOut = { tarball };
71116
+ if (typeof integrity === "string" && integrity !== "") entryOut.integrity = integrity;
71117
+ return entryOut;
71118
+ }
71119
+ function archiveStem(file) {
71120
+ const lower = file.toLowerCase();
71121
+ for (const suffix of [".tar.gz", ".tgz", ".zip"]) {
71122
+ if (lower.endsWith(suffix)) return file.slice(0, -suffix.length);
71123
+ }
71124
+ return void 0;
71125
+ }
71126
+ function isBlockedHost(hostname) {
71127
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
71128
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
71129
+ if (host.endsWith(".local") || host.endsWith(".internal")) return true;
71130
+ const version = isIP(host);
71131
+ if (version === 4) {
71132
+ const octets = host.split(".").map(Number);
71133
+ const [a, b] = octets;
71134
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224;
71135
+ }
71136
+ if (version === 6) {
71137
+ return /^(0*:)*0*1$/.test(host) || host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb") || host.startsWith("fc") || host.startsWith("fd");
71138
+ }
71139
+ return false;
71140
+ }
71141
+ function assertHostAllowed(url) {
71142
+ if (isBlockedHost(url.hostname)) {
71143
+ throw new HttpError(
71144
+ 400,
71145
+ "blocked_url",
71146
+ `Refusing to fetch from a local or private host: ${url.hostname}`
71147
+ );
71148
+ }
71149
+ }
71150
+ async function fetchPluginArchive(source, options = {}) {
71151
+ const doFetch = options.fetchImpl ?? fetch;
71152
+ let res;
71153
+ try {
71154
+ res = await doFetch(source.fetchUrl, {
71155
+ method: "GET",
71156
+ redirect: "follow",
71157
+ headers: { accept: "application/zip, application/octet-stream, */*" },
71158
+ signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
71159
+ });
71160
+ } catch (err) {
71161
+ throw new HttpError(400, "download_failed", `Could not download: ${message(err)}`);
71162
+ }
71163
+ if (res.url) {
71164
+ try {
71165
+ assertHostAllowed(new URL(res.url));
71166
+ } catch (err) {
71167
+ if (err instanceof HttpError) throw err;
71168
+ throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
71169
+ }
71170
+ }
71171
+ if (!res.ok) {
71172
+ await res.body?.cancel().catch(() => void 0);
71173
+ throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
71174
+ }
71175
+ const declared = Number(res.headers.get("content-length") ?? "");
71176
+ if (Number.isFinite(declared) && declared > MAX_PLUGIN_ARCHIVE_BYTES) {
71177
+ await res.body?.cancel().catch(() => void 0);
71178
+ throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
71179
+ }
71180
+ const archive = await readBounded(res);
71181
+ if (source.integrity !== void 0) checkIntegrity(source.integrity, archive);
71182
+ return archive;
71183
+ }
71184
+ var INTEGRITY_PATTERN = /^(sha512|sha384|sha256|sha1)-([A-Za-z0-9+/]+={0,2})$/;
71185
+ function checkIntegrity(integrity, archive) {
71186
+ const parts = INTEGRITY_PATTERN.exec(integrity);
71187
+ if (parts === null) return;
71188
+ const expected = Buffer.from(parts[2], "base64");
71189
+ const actual = createHash9(parts[1]).update(archive).digest();
71190
+ if (actual.length !== expected.length || !timingSafeEqual3(actual, expected)) {
71191
+ throw new HttpError(
71192
+ 400,
71193
+ "integrity_failed",
71194
+ "The downloaded archive does not match the checksum the registry published for it."
71195
+ );
71196
+ }
71197
+ }
71198
+ async function readBounded(res) {
71199
+ const reader = res.body?.getReader();
71200
+ if (!reader) return Buffer.from(await res.arrayBuffer());
71201
+ const chunks = [];
71202
+ let total = 0;
71203
+ for (; ; ) {
71204
+ const { done, value } = await reader.read();
71205
+ if (done) break;
71206
+ total += value.byteLength;
71207
+ if (total > MAX_PLUGIN_ARCHIVE_BYTES) {
71208
+ await reader.cancel().catch(() => void 0);
71209
+ throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
71210
+ }
71211
+ chunks.push(value);
71212
+ }
71213
+ return Buffer.concat(chunks);
71214
+ }
71215
+ function pluginArchiveTooLarge(limitBytes) {
71216
+ return new HttpError(
71217
+ 413,
71218
+ "plugin_too_large",
71219
+ `The archive exceeds the ${Math.floor(limitBytes / (1024 * 1024))}MB limit.`
71220
+ );
71221
+ }
71222
+ function message(err) {
71223
+ return err instanceof Error ? err.message : String(err);
71224
+ }
71225
+
71226
+ // src/services/theme-download.ts
71227
+ var DOWNLOAD_TIMEOUT_MS2 = 3e4;
71228
+ function assertHostAllowed2(url) {
71229
+ if (isBlockedHost(url.hostname)) {
71230
+ throw new HttpError(
71231
+ 400,
71232
+ "blocked_url",
71233
+ `Refusing to fetch from a local or private host: ${url.hostname}`
71234
+ );
71235
+ }
71236
+ }
71237
+ function themeTooLarge() {
71238
+ return new HttpError(
71239
+ 413,
71240
+ "theme_too_large",
71241
+ `The theme file exceeds the ${Math.floor(MAX_THEME_FILE_BYTES / 1024)}KB limit.`
71242
+ );
71243
+ }
71244
+ async function fetchThemeFile(rawUrl, options = {}) {
71245
+ let url;
71246
+ try {
71247
+ url = new URL(rawUrl.trim());
71248
+ } catch {
71249
+ throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
71250
+ }
71251
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
71252
+ throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
71253
+ }
71254
+ assertHostAllowed2(url);
71255
+ const doFetch = options.fetchImpl ?? fetch;
71256
+ let res;
71257
+ try {
71258
+ res = await doFetch(url.toString(), {
71259
+ method: "GET",
71260
+ redirect: "follow",
71261
+ // A theme is JSON, but a host may serve it as text/plain (a raw file link, a gist) — the
71262
+ // bytes decide, so the accept header asks without insisting.
71263
+ headers: { accept: "application/json, text/plain, */*" },
71264
+ signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS2)
71265
+ });
71266
+ } catch (err) {
71267
+ throw new HttpError(400, "download_failed", `Could not download: ${message2(err)}`);
71268
+ }
71269
+ if (res.url) {
71270
+ try {
71271
+ assertHostAllowed2(new URL(res.url));
71272
+ } catch (err) {
71273
+ if (err instanceof HttpError) throw err;
71274
+ throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
71275
+ }
71276
+ }
71277
+ if (!res.ok) {
71278
+ await res.body?.cancel().catch(() => void 0);
71279
+ throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
71280
+ }
71281
+ const declared = Number(res.headers.get("content-length") ?? "");
71282
+ if (Number.isFinite(declared) && declared > MAX_THEME_FILE_BYTES) {
71283
+ await res.body?.cancel().catch(() => void 0);
71284
+ throw themeTooLarge();
71285
+ }
71286
+ return readBounded2(res);
71287
+ }
71288
+ async function readBounded2(res) {
71289
+ const reader = res.body?.getReader();
71290
+ if (!reader) return Buffer.from(await res.arrayBuffer());
71291
+ const chunks = [];
71292
+ let total = 0;
71293
+ for (; ; ) {
71294
+ const { done, value } = await reader.read();
71295
+ if (done) break;
71296
+ total += value.byteLength;
71297
+ if (total > MAX_THEME_FILE_BYTES) {
71298
+ await reader.cancel().catch(() => void 0);
71299
+ throw themeTooLarge();
71300
+ }
71301
+ chunks.push(value);
71302
+ }
71303
+ return Buffer.concat(chunks);
71304
+ }
71305
+ function message2(err) {
71306
+ return err instanceof Error ? err.message : String(err);
71307
+ }
71308
+
71309
+ // src/http/routes/themes.ts
70746
71310
  var THEMES_DIR = "themes";
70747
- var MAX_FILE_BYTES2 = 256 * 1024;
70748
71311
  async function readImportedThemes(root) {
70749
71312
  const dir = join2(root, THEMES_DIR);
70750
71313
  let names;
@@ -70763,8 +71326,8 @@ async function readImportedThemes(root) {
70763
71326
  rejected.push({ file, errors: [`\u8BFB\u4E0D\u4E86\u8FD9\u4E2A\u6587\u4EF6\uFF1A${error.message}`] });
70764
71327
  continue;
70765
71328
  }
70766
- if (text2.length > MAX_FILE_BYTES2) {
70767
- rejected.push({ file, errors: [`\u6587\u4EF6\u8D85\u8FC7 ${MAX_FILE_BYTES2} \u5B57\u8282`] });
71329
+ if (text2.length > MAX_THEME_FILE_BYTES2) {
71330
+ rejected.push({ file, errors: [`\u6587\u4EF6\u8D85\u8FC7 ${MAX_THEME_FILE_BYTES2} \u5B57\u8282`] });
70768
71331
  continue;
70769
71332
  }
70770
71333
  let parsed;
@@ -70787,18 +71350,6 @@ async function readImportedThemes(root) {
70787
71350
  }
70788
71351
  return { dir, themes, rejected };
70789
71352
  }
70790
- function themesRoutes(deps) {
70791
- const routes = new Hono41();
70792
- routes.get("/", async (c) => {
70793
- const { dir, themes, rejected } = await readImportedThemes(deps.root);
70794
- return c.json({
70795
- dir,
70796
- themes: themes.map(({ file, theme }) => ({ file, theme })),
70797
- rejected: rejected.map(({ file, errors }) => ({ file, errors: [...errors] }))
70798
- });
70799
- });
70800
- return routes;
70801
- }
70802
71353
  function themeStylesheetRoutes(deps) {
70803
71354
  const routes = new Hono41();
70804
71355
  routes.get("/", async (c) => {
@@ -70812,6 +71363,87 @@ function themeStylesheetRoutes(deps) {
70812
71363
  });
70813
71364
  return routes;
70814
71365
  }
71366
+ function requireAdmin2(c) {
71367
+ if (!c.var.user.isAdmin) {
71368
+ throw new HttpError(403, "admin_required", "Only an admin can import themes.");
71369
+ }
71370
+ }
71371
+ function readThemeText(bytes) {
71372
+ try {
71373
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
71374
+ } catch {
71375
+ throw new HttpError(400, "theme_invalid", "The file is not UTF-8 text.");
71376
+ }
71377
+ }
71378
+ function readThemeTextAsTheme(text2) {
71379
+ let parsed;
71380
+ try {
71381
+ parsed = JSON.parse(text2);
71382
+ } catch (error) {
71383
+ throw new HttpError(400, "theme_invalid", `\u4E0D\u662F\u5408\u6CD5\u7684 JSON\uFF1A${error.message}`);
71384
+ }
71385
+ const read = readExternalTheme(parsed);
71386
+ if (!read.ok) throw new HttpError(400, "theme_invalid", read.errors.join("\uFF1B"));
71387
+ return read.theme;
71388
+ }
71389
+ async function installTheme(root, text2, overwrite) {
71390
+ const theme = readThemeTextAsTheme(text2);
71391
+ const file = `${theme.id}.json`;
71392
+ const claimed = (await readImportedThemes(root)).themes.find(
71393
+ (entry) => entry.theme.id === theme.id
71394
+ )?.file;
71395
+ if (claimed !== void 0 && !overwrite) {
71396
+ throw new HttpError(
71397
+ 409,
71398
+ "theme_exists",
71399
+ `A theme named ${theme.id} is already imported as ${claimed}.`
71400
+ );
71401
+ }
71402
+ const dir = join2(root, THEMES_DIR);
71403
+ await mkdir(dir, { recursive: true });
71404
+ if (claimed !== void 0 && claimed !== file) await rm2(join2(dir, claimed));
71405
+ await writeFile(join2(dir, file), text2, "utf8");
71406
+ return { file, theme };
71407
+ }
71408
+ function themesRoutes(deps) {
71409
+ const routes = new Hono41();
71410
+ routes.get("/", async (c) => {
71411
+ const { dir, themes, rejected } = await readImportedThemes(deps.root);
71412
+ return c.json({
71413
+ dir,
71414
+ themes: themes.map(({ file, theme }) => ({ file, theme })),
71415
+ rejected: rejected.map(({ file, errors }) => ({ file, errors: [...errors] }))
71416
+ });
71417
+ });
71418
+ routes.post("/upload", async (c) => {
71419
+ requireAdmin2(c);
71420
+ const body = await readJson(c);
71421
+ const dataBase64 = requireString(body, "dataBase64", { minLen: 1, label: "dataBase64" });
71422
+ const bytes = Buffer.from(dataBase64, "base64");
71423
+ if (bytes.byteLength === 0) throw badRequest("The file is empty.");
71424
+ if (bytes.byteLength > MAX_THEME_FILE_BYTES2) throw themeTooLarge();
71425
+ const { file, theme } = await installTheme(
71426
+ deps.root,
71427
+ readThemeText(bytes),
71428
+ optionalBoolean(body, "overwrite") ?? false
71429
+ );
71430
+ return c.json({ file, theme }, 201);
71431
+ });
71432
+ routes.post("/download", async (c) => {
71433
+ requireAdmin2(c);
71434
+ const body = await readJson(c);
71435
+ const url = requireString(body, "url", { minLen: 1, maxLen: 2048 });
71436
+ const bytes = await fetchThemeFile(url);
71437
+ if (bytes.byteLength === 0) throw badRequest("The download is empty.");
71438
+ const { file, theme } = await installTheme(
71439
+ deps.root,
71440
+ readThemeText(bytes),
71441
+ optionalBoolean(body, "overwrite") ?? false
71442
+ );
71443
+ return c.json({ file, theme }, 201);
71444
+ });
71445
+ return routes;
71446
+ }
70815
71447
  var _routes_dec17, _config_dec15, _ThemesRoutes_decorators, _init132;
70816
71448
  _ThemesRoutes_decorators = [Component65({
70817
71449
  contributes: {
@@ -72056,305 +72688,6 @@ function builtinPluginRegistry(deps = {}) {
72056
72688
  };
72057
72689
  }
72058
72690
 
72059
- // src/services/plugin-download.ts
72060
- import { createHash as createHash9, timingSafeEqual as timingSafeEqual3 } from "crypto";
72061
- import { isIP } from "net";
72062
- var MAX_PLUGIN_ARCHIVE_BYTES = 32 * 1024 * 1024;
72063
- var DOWNLOAD_TIMEOUT_MS = 6e4;
72064
- function normalizePluginUrl(raw) {
72065
- const trimmed = raw.trim();
72066
- if (trimmed === "") throw badRequest("url must not be empty.");
72067
- let url;
72068
- try {
72069
- url = new URL(trimmed);
72070
- } catch {
72071
- throw new HttpError(400, "unsupported_url", "url must be an absolute http(s) URL.");
72072
- }
72073
- if (url.protocol !== "http:" && url.protocol !== "https:") {
72074
- throw new HttpError(400, "unsupported_url", "url must be an http(s) URL.");
72075
- }
72076
- assertHostAllowed(url);
72077
- const host = url.hostname.toLowerCase().replace(/^www\./, "");
72078
- if (host !== "github.com") {
72079
- const file = url.pathname.split("/").filter(Boolean).pop() ?? "";
72080
- const suggested = archiveStem(file);
72081
- return { fetchUrl: url.toString(), subdir: "", ...suggested ? { name: suggested } : {} };
72082
- }
72083
- const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
72084
- const [owner3, repo, kind, ...rest] = segments;
72085
- if (!owner3 || !repo) {
72086
- throw new HttpError(
72087
- 400,
72088
- "unsupported_url",
72089
- "Not a GitHub repository URL: expected /<owner>/<repo>."
72090
- );
72091
- }
72092
- const repoUrl = `https://github.com/${owner3}/${repo}`;
72093
- if (kind === void 0) {
72094
- return { fetchUrl: `${repoUrl}/archive/HEAD.zip`, subdir: "", name: repo };
72095
- }
72096
- if (kind !== "tree") {
72097
- return { fetchUrl: url.toString(), subdir: "", name: repo };
72098
- }
72099
- const [ref, ...sub] = rest;
72100
- if (!ref) throw new HttpError(400, "unsupported_url", "A GitHub tree URL needs a branch or tag.");
72101
- const subdir = sub.join("/");
72102
- return {
72103
- fetchUrl: `${repoUrl}/archive/${encodeURIComponent(ref)}.zip`,
72104
- subdir,
72105
- // The plugin directory is the last segment of the subdirectory — that is its name.
72106
- name: sub.length > 0 ? sub[sub.length - 1] : repo
72107
- };
72108
- }
72109
- var NPM_REGISTRY = "https://registry.npmjs.org";
72110
- var NPM_NAME = /^(?:@[A-Za-z0-9._~!*'()-]+\/)?[A-Za-z0-9._~!*'()-]+$/;
72111
- var NPM_VERSION = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
72112
- function parseNpmPluginSpec(raw) {
72113
- const trimmed = raw.trim();
72114
- if (trimmed === "") return null;
72115
- if (trimmed.toLowerCase().startsWith("npm:")) {
72116
- return parseNpmName(trimmed.slice("npm:".length).trim());
72117
- }
72118
- if (/^https?:\/\//i.test(trimmed)) {
72119
- let url;
72120
- try {
72121
- url = new URL(trimmed);
72122
- } catch {
72123
- return null;
72124
- }
72125
- const host = url.hostname.toLowerCase().replace(/^www\./, "");
72126
- if (host === "npmjs.com") {
72127
- const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
72128
- if (segments[0] !== "package") return null;
72129
- const scoped = segments[1] === void 0 ? void 0 : segments[1].startsWith("@");
72130
- const name = scoped ? `${segments[1]}/${segments[2] ?? ""}` : segments[1];
72131
- if (name === void 0 || name.endsWith("/")) return null;
72132
- const marker = scoped ? 3 : 2;
72133
- const version = segments[marker] === "v" ? segments[marker + 1] : void 0;
72134
- return parseNpmName(version === void 0 ? name : `${name}@${version}`);
72135
- }
72136
- if (host === "registry.npmjs.org") {
72137
- const rest = decodeURIComponent(url.pathname.replace(/^\//, ""));
72138
- const segments = rest.split("/");
72139
- if (segments[0] === void 0 || segments[0] === "") return null;
72140
- const scoped = segments[0].startsWith("@");
72141
- const name = scoped ? `${segments[0]}/${segments[1] ?? ""}` : segments[0];
72142
- if (name.endsWith("/") || segments.length > (scoped ? 2 : 1)) return null;
72143
- return parseNpmName(name);
72144
- }
72145
- return null;
72146
- }
72147
- return parseNpmName(trimmed);
72148
- }
72149
- function parseNpmName(spec) {
72150
- if (spec === "") return null;
72151
- const at = spec.lastIndexOf("@");
72152
- const name = at > 0 ? spec.slice(0, at) : spec;
72153
- const version = at > 0 ? spec.slice(at + 1) : void 0;
72154
- if (name.length > 214 || !NPM_NAME.test(name)) return null;
72155
- if (version !== void 0 && !NPM_VERSION.test(version)) return null;
72156
- return version === void 0 ? { name } : { name, version };
72157
- }
72158
- async function resolvePluginSource(raw, options = {}) {
72159
- const spec = parseNpmPluginSpec(raw);
72160
- if (spec === null) return normalizePluginUrl(raw);
72161
- const found = await fetchPackument(spec, options);
72162
- const entry = pickVersion(spec, found);
72163
- const tarball = entry.tarball;
72164
- let url;
72165
- try {
72166
- url = new URL(tarball);
72167
- } catch {
72168
- throw new HttpError(400, "download_failed", "The registry named an unreadable tarball URL.");
72169
- }
72170
- if (url.protocol !== "http:" && url.protocol !== "https:") {
72171
- throw new HttpError(400, "download_failed", "The registry named an unsupported tarball URL.");
72172
- }
72173
- assertHostAllowed(url);
72174
- return {
72175
- fetchUrl: url.toString(),
72176
- subdir: "",
72177
- // The package's own name, minus the scope: the scope names a publisher, and the plugin is
72178
- // installed under the name its own repository gives it (`@scope/use-firecrawl` → `use-firecrawl`).
72179
- name: spec.name.slice(spec.name.startsWith("@") ? spec.name.indexOf("/") + 1 : 0),
72180
- ...entry.integrity !== void 0 ? { integrity: entry.integrity } : {}
72181
- };
72182
- }
72183
- async function fetchPackument(spec, options) {
72184
- const doFetch = options.fetchImpl ?? fetch;
72185
- const encoded = spec.name.startsWith("@") ? `@${encodeURIComponent(spec.name.slice(1))}` : encodeURIComponent(spec.name);
72186
- const url = `${NPM_REGISTRY}/${encoded}`;
72187
- let res;
72188
- try {
72189
- res = await doFetch(url, {
72190
- method: "GET",
72191
- redirect: "follow",
72192
- // The abbreviated document: dist-tags and each version's dist, which is all this reads, and a
72193
- // fraction of the full packument for a package with a long history.
72194
- headers: { accept: "application/vnd.npm.install-v1+json, application/json" },
72195
- signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
72196
- });
72197
- } catch (err) {
72198
- throw new HttpError(400, "npm_registry_failed", `Could not reach npm: ${message(err)}`);
72199
- }
72200
- if (res.url) assertHostAllowed(new URL(res.url));
72201
- if (res.status === 404) {
72202
- throw new HttpError(404, "npm_package_not_found", `npm has no package ${spec.name}.`);
72203
- }
72204
- if (!res.ok) {
72205
- await res.body?.cancel().catch(() => void 0);
72206
- throw new HttpError(
72207
- 400,
72208
- "npm_registry_failed",
72209
- `npm answered HTTP ${res.status} for ${spec.name}.`
72210
- );
72211
- }
72212
- try {
72213
- const data = await res.json();
72214
- if (typeof data !== "object" || data === null) throw new Error("not an object");
72215
- return data;
72216
- } catch {
72217
- throw new HttpError(
72218
- 400,
72219
- "npm_registry_failed",
72220
- `npm returned no usable data for ${spec.name}.`
72221
- );
72222
- }
72223
- }
72224
- function pickVersion(spec, packument) {
72225
- const versions = packument["versions"];
72226
- const table = typeof versions === "object" && versions !== null ? versions : {};
72227
- const distTags = packument["dist-tags"];
72228
- const tags = typeof distTags === "object" && distTags !== null ? distTags : {};
72229
- const version = spec.version ?? (typeof tags["latest"] === "string" ? tags["latest"] : void 0);
72230
- if (version === void 0) {
72231
- throw new HttpError(404, "npm_version_not_found", `${spec.name} has no published version.`);
72232
- }
72233
- const entry = table[version];
72234
- if (typeof entry !== "object" || entry === null) {
72235
- throw new HttpError(
72236
- 404,
72237
- "npm_version_not_found",
72238
- `npm has no version ${version} of ${spec.name}; use an exact version or a dist-tag.`
72239
- );
72240
- }
72241
- const dist = entry["dist"];
72242
- const tarball = typeof dist === "object" && dist !== null ? dist["tarball"] : void 0;
72243
- if (typeof tarball !== "string" || tarball === "") {
72244
- throw new HttpError(404, "npm_version_not_found", `${spec.name}@${version} has no tarball.`);
72245
- }
72246
- const integrity = dist["integrity"];
72247
- const entryOut = { tarball };
72248
- if (typeof integrity === "string" && integrity !== "") entryOut.integrity = integrity;
72249
- return entryOut;
72250
- }
72251
- function archiveStem(file) {
72252
- const lower = file.toLowerCase();
72253
- for (const suffix of [".tar.gz", ".tgz", ".zip"]) {
72254
- if (lower.endsWith(suffix)) return file.slice(0, -suffix.length);
72255
- }
72256
- return void 0;
72257
- }
72258
- function isBlockedHost(hostname) {
72259
- const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
72260
- if (host === "localhost" || host.endsWith(".localhost")) return true;
72261
- if (host.endsWith(".local") || host.endsWith(".internal")) return true;
72262
- const version = isIP(host);
72263
- if (version === 4) {
72264
- const octets = host.split(".").map(Number);
72265
- const [a, b] = octets;
72266
- return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a >= 224;
72267
- }
72268
- if (version === 6) {
72269
- return /^(0*:)*0*1$/.test(host) || host.startsWith("fe8") || host.startsWith("fe9") || host.startsWith("fea") || host.startsWith("feb") || host.startsWith("fc") || host.startsWith("fd");
72270
- }
72271
- return false;
72272
- }
72273
- function assertHostAllowed(url) {
72274
- if (isBlockedHost(url.hostname)) {
72275
- throw new HttpError(
72276
- 400,
72277
- "blocked_url",
72278
- `Refusing to fetch from a local or private host: ${url.hostname}`
72279
- );
72280
- }
72281
- }
72282
- async function fetchPluginArchive(source, options = {}) {
72283
- const doFetch = options.fetchImpl ?? fetch;
72284
- let res;
72285
- try {
72286
- res = await doFetch(source.fetchUrl, {
72287
- method: "GET",
72288
- redirect: "follow",
72289
- headers: { accept: "application/zip, application/octet-stream, */*" },
72290
- signal: AbortSignal.timeout(options.timeoutMs ?? DOWNLOAD_TIMEOUT_MS)
72291
- });
72292
- } catch (err) {
72293
- throw new HttpError(400, "download_failed", `Could not download: ${message(err)}`);
72294
- }
72295
- if (res.url) {
72296
- try {
72297
- assertHostAllowed(new URL(res.url));
72298
- } catch (err) {
72299
- if (err instanceof HttpError) throw err;
72300
- throw new HttpError(400, "download_failed", "Could not read the URL that answered.");
72301
- }
72302
- }
72303
- if (!res.ok) {
72304
- await res.body?.cancel().catch(() => void 0);
72305
- throw new HttpError(400, "download_failed", `Download failed with HTTP ${res.status}.`);
72306
- }
72307
- const declared = Number(res.headers.get("content-length") ?? "");
72308
- if (Number.isFinite(declared) && declared > MAX_PLUGIN_ARCHIVE_BYTES) {
72309
- await res.body?.cancel().catch(() => void 0);
72310
- throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
72311
- }
72312
- const archive = await readBounded(res);
72313
- if (source.integrity !== void 0) checkIntegrity(source.integrity, archive);
72314
- return archive;
72315
- }
72316
- var INTEGRITY_PATTERN = /^(sha512|sha384|sha256|sha1)-([A-Za-z0-9+/]+={0,2})$/;
72317
- function checkIntegrity(integrity, archive) {
72318
- const parts = INTEGRITY_PATTERN.exec(integrity);
72319
- if (parts === null) return;
72320
- const expected = Buffer.from(parts[2], "base64");
72321
- const actual = createHash9(parts[1]).update(archive).digest();
72322
- if (actual.length !== expected.length || !timingSafeEqual3(actual, expected)) {
72323
- throw new HttpError(
72324
- 400,
72325
- "integrity_failed",
72326
- "The downloaded archive does not match the checksum the registry published for it."
72327
- );
72328
- }
72329
- }
72330
- async function readBounded(res) {
72331
- const reader = res.body?.getReader();
72332
- if (!reader) return Buffer.from(await res.arrayBuffer());
72333
- const chunks = [];
72334
- let total = 0;
72335
- for (; ; ) {
72336
- const { done, value } = await reader.read();
72337
- if (done) break;
72338
- total += value.byteLength;
72339
- if (total > MAX_PLUGIN_ARCHIVE_BYTES) {
72340
- await reader.cancel().catch(() => void 0);
72341
- throw pluginArchiveTooLarge(MAX_PLUGIN_ARCHIVE_BYTES);
72342
- }
72343
- chunks.push(value);
72344
- }
72345
- return Buffer.concat(chunks);
72346
- }
72347
- function pluginArchiveTooLarge(limitBytes) {
72348
- return new HttpError(
72349
- 413,
72350
- "plugin_too_large",
72351
- `The archive exceeds the ${Math.floor(limitBytes / (1024 * 1024))}MB limit.`
72352
- );
72353
- }
72354
- function message(err) {
72355
- return err instanceof Error ? err.message : String(err);
72356
- }
72357
-
72358
72691
  // src/services/tar-archive.ts
72359
72692
  import { gunzipSync } from "zlib";
72360
72693
  var BLOCK = 512;
@@ -72521,7 +72854,7 @@ function libraryResponse(root) {
72521
72854
  }))
72522
72855
  };
72523
72856
  }
72524
- function requireAdmin2(c) {
72857
+ function requireAdmin3(c) {
72525
72858
  if (!c.var.user.isAdmin) {
72526
72859
  throw new HttpError(403, "admin_required", "Only an admin can manage the plugin directory.");
72527
72860
  }
@@ -72646,7 +72979,7 @@ function pluginLibraryRoutes(deps) {
72646
72979
  })
72647
72980
  );
72648
72981
  app.post("/upload", async (c) => {
72649
- requireAdmin2(c);
72982
+ requireAdmin3(c);
72650
72983
  const body = await readJson(c);
72651
72984
  const dataBase64 = requireString(body, "dataBase64", { minLen: 1, label: "dataBase64" });
72652
72985
  const archive = Buffer.from(dataBase64, "base64");
@@ -72664,7 +72997,7 @@ function pluginLibraryRoutes(deps) {
72664
72997
  );
72665
72998
  });
72666
72999
  app.post("/download", async (c) => {
72667
- requireAdmin2(c);
73000
+ requireAdmin3(c);
72668
73001
  const body = await readJson(c);
72669
73002
  const url = requireString(body, "url", { minLen: 1, maxLen: 2048 });
72670
73003
  const requestedName = optionalString(body, "name", { maxLen: 128 });
@@ -72714,7 +73047,7 @@ function pluginLibraryRoutes(deps) {
72714
73047
  });
72715
73048
  });
72716
73049
  app.delete("/:plugin", async (c) => {
72717
- requireAdmin2(c);
73050
+ requireAdmin3(c);
72718
73051
  const pluginName = c.req.param("plugin");
72719
73052
  if (!PLUGIN_NAME_PATTERN4.test(pluginName)) {
72720
73053
  throw new HttpError(404, "unknown_plugin", `No such plugin: ${pluginName}`);
@@ -72942,7 +73275,7 @@ function installedPluginRoutes(deps) {
72942
73275
  };
72943
73276
  };
72944
73277
  app.get("/", async (c) => c.json(await view(scope(c))));
72945
- const requireAdmin3 = (c) => {
73278
+ const requireAdmin4 = (c) => {
72946
73279
  if (!c.var.user.isAdmin) {
72947
73280
  throw new HttpError(403, "admin_required", "Only an admin can perform this operation.");
72948
73281
  }
@@ -72993,7 +73326,7 @@ function installedPluginRoutes(deps) {
72993
73326
  });
72994
73327
  };
72995
73328
  app.post("/", async (c) => {
72996
- requireAdmin3(c);
73329
+ requireAdmin4(c);
72997
73330
  const projectId = scope(c);
72998
73331
  const body = await readJson(c);
72999
73332
  const specifier = specifierOf(body.specifier);
@@ -73016,7 +73349,7 @@ function installedPluginRoutes(deps) {
73016
73349
  return c.json(await view(projectId));
73017
73350
  });
73018
73351
  app.delete("/", async (c) => {
73019
- requireAdmin3(c);
73352
+ requireAdmin4(c);
73020
73353
  const projectId = scope(c);
73021
73354
  const specifier = specifierOf(c.req.query("specifier"));
73022
73355
  const machineId = machineOf(c.req.query("machineId"));
@@ -73041,7 +73374,7 @@ function installedPluginRoutes(deps) {
73041
73374
  return c.json(await view(projectId));
73042
73375
  });
73043
73376
  app.put("/", async (c) => {
73044
- requireAdmin3(c);
73377
+ requireAdmin4(c);
73045
73378
  const projectId = scope(c);
73046
73379
  const body = await readJson(c);
73047
73380
  const list = body.plugins;
@@ -74227,7 +74560,7 @@ var WorkflowService = class {
74227
74560
  state = next ?? null;
74228
74561
  await writeState(folder.dir, state);
74229
74562
  },
74230
- log: (message2) => service.log.line(`[workflow ${folder.id}] ${message2}`)
74563
+ log: (message3) => service.log.line(`[workflow ${folder.id}] ${message3}`)
74231
74564
  };
74232
74565
  }
74233
74566
  /** A workflow reaches the Sessions of its own Project and no others. */
@@ -75163,8 +75496,8 @@ import os10 from "os";
75163
75496
  // src/builtin-browser/shell-link.ts
75164
75497
  import { randomUUID as randomUUID9 } from "crypto";
75165
75498
  var ShellLinkError = class extends Error {
75166
- constructor(kind, message2) {
75167
- super(message2);
75499
+ constructor(kind, message3) {
75500
+ super(message3);
75168
75501
  this.kind = kind;
75169
75502
  this.name = "ShellLinkError";
75170
75503
  }
@@ -75436,8 +75769,8 @@ var LOAD_POLL_MS = 250;
75436
75769
  var LOAD_SETTLE_MS = 300;
75437
75770
  var CONTEXT_LOST = /Execution context was destroyed|Inspected target navigated or closed|Cannot find context/i;
75438
75771
  var PageScriptError = class extends Error {
75439
- constructor(message2) {
75440
- super(message2);
75772
+ constructor(message3) {
75773
+ super(message3);
75441
75774
  this.name = "PageScriptError";
75442
75775
  }
75443
75776
  };
@@ -75545,9 +75878,9 @@ ${code}
75545
75878
  }
75546
75879
  const res = raw ?? {};
75547
75880
  if (res.exceptionDetails !== void 0) {
75548
- const message2 = exceptionMessage(res.exceptionDetails);
75549
- if (CONTEXT_LOST.test(message2)) return { reloaded: true };
75550
- throw new PageScriptError(message2);
75881
+ const message3 = exceptionMessage(res.exceptionDetails);
75882
+ if (CONTEXT_LOST.test(message3)) return { reloaded: true };
75883
+ throw new PageScriptError(message3);
75551
75884
  }
75552
75885
  const result = res.result;
75553
75886
  if (result === void 0) return { value: void 0 };
@@ -76934,8 +77267,8 @@ var BrowserActions = class {
76934
77267
  if (method !== DIALOG_EVENT) return;
76935
77268
  const type2 = DIALOG_TYPES.find((t) => t === params.type) ?? "alert";
76936
77269
  const accepted = type2 === "alert" || accept;
76937
- const message2 = typeof params.message === "string" ? params.message : "";
76938
- into.push({ type: type2, message: message2, accepted });
77270
+ const message3 = typeof params.message === "string" ? params.message : "";
77271
+ into.push({ type: type2, message: message3, accepted });
76939
77272
  const answer = { accept: accepted };
76940
77273
  if (accepted && type2 === "prompt" && typeof params.defaultPrompt === "string") {
76941
77274
  answer.promptText = params.defaultPrompt;
@@ -79564,7 +79897,7 @@ import { Hono as Hono52 } from "hono";
79564
79897
  import { isValidId as isValidId9 } from "@adelie/core";
79565
79898
  var agentNotFound = () => new HttpError(404, "agent_not_found", "No Agent with this id has its API enabled.");
79566
79899
  var sessionNotFound = () => new HttpError(404, "session_not_found", "No API Session with this id.");
79567
- var unauthorized = (message2) => new HttpError(401, "unauthorized", message2);
79900
+ var unauthorized = (message3) => new HttpError(401, "unauthorized", message3);
79568
79901
  function requireAgentApiOn(settings) {
79569
79902
  if (!settings.getAgentApiEnabled()) {
79570
79903
  throw new HttpError(403, "agent_api_disabled", "The Agent API is turned off on this server.");
@@ -80243,8 +80576,8 @@ async function runStream(c, deps) {
80243
80576
  }
80244
80577
  } catch (err) {
80245
80578
  if (stream.aborted) return;
80246
- const message2 = err instanceof Error ? err.message : String(err);
80247
- deps.log.line(`[amsp] run stream of ${actual} failed: ${message2}`);
80579
+ const message3 = err instanceof Error ? err.message : String(err);
80580
+ deps.log.line(`[amsp] run stream of ${actual} failed: ${message3}`);
80248
80581
  deps.errors.record({
80249
80582
  source: "session",
80250
80583
  err,
@@ -80255,7 +80588,7 @@ async function runStream(c, deps) {
80255
80588
  if (!ended) {
80256
80589
  deps.manager.abortTask(actual);
80257
80590
  try {
80258
- await end(translator.fail({ code: "internal", message: message2 }));
80591
+ await end(translator.fail({ code: "internal", message: message3 }));
80259
80592
  } catch {
80260
80593
  }
80261
80594
  }