offsite 0.0.17 → 0.0.18

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 (114) hide show
  1. package/apps/server/dist/main.mjs +240 -135
  2. package/apps/server/dist/main.mjs.map +1 -1
  3. package/apps/web/dist/assets/HugeiconsIcon-CXlzk0hX.js +1 -0
  4. package/apps/web/dist/assets/angular-html-CPrDeWzk.js +1 -0
  5. package/apps/web/dist/assets/{angular-ts-DGEt84_g.js → angular-ts-DCisMPVX.js} +1 -1
  6. package/apps/web/dist/assets/{apl-bz_iDiNf.js → apl-BRH60u60.js} +1 -1
  7. package/apps/web/dist/assets/{astro-jXSlLJ7l.js → astro-Dq6p_VTJ.js} +1 -1
  8. package/apps/web/dist/assets/{blade-2sxYYh5A.js → blade-CC3PQRrX.js} +1 -1
  9. package/apps/web/dist/assets/c-iCihek07.js +1 -0
  10. package/apps/web/dist/assets/{chapel-DUoqr6M4.js → chapel-DmtgEXO_.js} +1 -1
  11. package/apps/web/dist/assets/{cobol-Cn0D9nVy.js → cobol-CGlx5WnO.js} +1 -1
  12. package/apps/web/dist/assets/{coffee-C1tCcS8h.js → coffee-CiiSLhB6.js} +1 -1
  13. package/apps/web/dist/assets/cpp-71xkOQct.js +1 -0
  14. package/apps/web/dist/assets/{crystal-DF-BMake.js → crystal-Bn4W9FYe.js} +1 -1
  15. package/apps/web/dist/assets/css-AeYbVOD-.js +1 -0
  16. package/apps/web/dist/assets/{edge-D8r2HbS9.js → edge-D8qeodQq.js} +1 -1
  17. package/apps/web/dist/assets/{elixir-K6buEKBc.js → elixir-BkREp_f2.js} +1 -1
  18. package/apps/web/dist/assets/{elm-B78wD7Un.js → elm-o4NJxfws.js} +1 -1
  19. package/apps/web/dist/assets/{erb-DuhNLWPu.js → erb-CpQ5AKmM.js} +1 -1
  20. package/apps/web/dist/assets/{git-rebase-C6n0imm6.js → git-rebase-BDPpS8w6.js} +1 -1
  21. package/apps/web/dist/assets/{glimmer-js-B65Nng8o.js → glimmer-js-BTqwx0oO.js} +1 -1
  22. package/apps/web/dist/assets/{glimmer-ts-Cme47tem.js → glimmer-ts-N9jKyowB.js} +1 -1
  23. package/apps/web/dist/assets/glsl-D5MA2a_X.js +1 -0
  24. package/apps/web/dist/assets/graphql-Ctjuxyvl.js +1 -0
  25. package/apps/web/dist/assets/{hack-W5aeWZGp.js → hack-ZTcFnVsP.js} +1 -1
  26. package/apps/web/dist/assets/haml-ChtpF0E7.js +1 -0
  27. package/apps/web/dist/assets/{handlebars-CYz9msjm.js → handlebars-CpsBgm7t.js} +1 -1
  28. package/apps/web/dist/assets/{html-derivative-fPnJ7c-5.js → html-derivative-DNPJNw-0.js} +1 -1
  29. package/apps/web/dist/assets/html-hnucWepv.js +1 -0
  30. package/apps/web/dist/assets/{http-DIR9LW29.js → http-kmH8AyfE.js} +1 -1
  31. package/apps/web/dist/assets/{hurl-73h4sQYH.js → hurl-DCZHXKU4.js} +1 -1
  32. package/apps/web/dist/assets/image-viewer-CBM7yEMl.js +1 -0
  33. package/apps/web/dist/assets/index-DkRVBJlp.css +2 -0
  34. package/apps/web/dist/assets/index-h6Rg_ww0.js +1206 -0
  35. package/apps/web/dist/assets/java-DdHNQ9hk.js +1 -0
  36. package/apps/web/dist/assets/javascript-obGGvMPs.js +1 -0
  37. package/apps/web/dist/assets/{jinja-wBZAPGc9.js → jinja-81yqctvv.js} +1 -1
  38. package/apps/web/dist/assets/{jison-bZuttet2.js → jison-CBjhYtSs.js} +1 -1
  39. package/apps/web/dist/assets/json-DAgxKhMr.js +1 -0
  40. package/apps/web/dist/assets/jsx-gTgmArGx.js +1 -0
  41. package/apps/web/dist/assets/jsx-runtime-BNakU3Ej.js +1 -0
  42. package/apps/web/dist/assets/{julia-Bj20nWQB.js → julia-KRkZs2hS.js} +1 -1
  43. package/apps/web/dist/assets/{just-pKKGVoyp.js → just-BuSkJCkO.js} +1 -1
  44. package/apps/web/dist/assets/{latex-DN5Fi2zg.js → latex-CvI9akcJ.js} +1 -1
  45. package/apps/web/dist/assets/{liquid-DtPrOPBs.js → liquid-VY2h0b2O.js} +1 -1
  46. package/apps/web/dist/assets/lua-FONpmTUP.js +1 -0
  47. package/apps/web/dist/assets/{marko-BAsjT454.js → marko-BRIeiu02.js} +1 -1
  48. package/apps/web/dist/assets/{mdc-DwMa0Ya_.js → mdc-CX_1c8hS.js} +1 -1
  49. package/apps/web/dist/assets/{nginx-BlYeGp57.js → nginx-BTMw9COX.js} +1 -1
  50. package/apps/web/dist/assets/{nim-DASYTjR5.js → nim-BrYFxDF2.js} +1 -1
  51. package/apps/web/dist/assets/{org-DSeAzk6D.js → org-Cd0u8vWD.js} +1 -1
  52. package/apps/web/dist/assets/{perl-BGPYGeVd.js → perl-1pN6i0RM.js} +1 -1
  53. package/apps/web/dist/assets/{php-kgT6t38x.js → php-C7NaTRTO.js} +1 -1
  54. package/apps/web/dist/assets/{pug-COTHnC13.js → pug-Co4hciMS.js} +1 -1
  55. package/apps/web/dist/assets/{qml-DhqsM4c7.js → qml-_a-QhfPD.js} +1 -1
  56. package/apps/web/dist/assets/r-BHcO8Vjq.js +1 -0
  57. package/apps/web/dist/assets/{razor-BGH58TzE.js → razor-DVe451rE.js} +1 -1
  58. package/apps/web/dist/assets/regexp-DR20uMnK.js +1 -0
  59. package/apps/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
  60. package/apps/web/dist/assets/{rst-UweowKJr.js → rst-hVyWO_Fc.js} +1 -1
  61. package/apps/web/dist/assets/{ruby-DtfMo-6m.js → ruby-Zd-8tM08.js} +1 -1
  62. package/apps/web/dist/assets/{sas-DkQGY_o2.js → sas-D3h2pRQf.js} +1 -1
  63. package/apps/web/dist/assets/scss-DDJaLTnA.js +1 -0
  64. package/apps/web/dist/assets/shellscript-Da78Ox-3.js +1 -0
  65. package/apps/web/dist/assets/{shellsession-CT0a4IuI.js → shellsession-CVsQGYfZ.js} +1 -1
  66. package/apps/web/dist/assets/{soy-B3ufTOzS.js → soy-CXX9LSpm.js} +1 -1
  67. package/apps/web/dist/assets/sql-ByhjMBKn.js +1 -0
  68. package/apps/web/dist/assets/{stata-BYhfUPEu.js → stata-CjrVU8-t.js} +1 -1
  69. package/apps/web/dist/assets/{surrealql-UIZa84-V.js → surrealql-BZoq2lXR.js} +1 -1
  70. package/apps/web/dist/assets/{svelte-SkphxuD6.js → svelte-CZ5lJGR6.js} +1 -1
  71. package/apps/web/dist/assets/{templ-C8T3Zgrx.js → templ-EWf5LxrK.js} +1 -1
  72. package/apps/web/dist/assets/tex-BK-H2Wn4.js +1 -0
  73. package/apps/web/dist/assets/{ts-tags-Bk8_Hqg_.js → ts-tags-C4CDubZg.js} +1 -1
  74. package/apps/web/dist/assets/tsx-CUOciP0b.js +1 -0
  75. package/apps/web/dist/assets/{twig-pKLKVHRj.js → twig-D6Rh4Rda.js} +1 -1
  76. package/apps/web/dist/assets/typescript-BydbNFcO.js +1 -0
  77. package/apps/web/dist/assets/{typst-DCcUGqaw.js → typst-Bq_qwoSs.js} +1 -1
  78. package/apps/web/dist/assets/video-player-D73_kRr7.js +1416 -0
  79. package/apps/web/dist/assets/video-viewer-CUqarcH1.js +1 -0
  80. package/apps/web/dist/assets/{vue-B_0IsDtY.js → vue-D5NIjYCb.js} +1 -1
  81. package/apps/web/dist/assets/{vue-html-B42dVyQs.js → vue-html-DkKkQnZL.js} +1 -1
  82. package/apps/web/dist/assets/{vue-vine-CUms6qs_.js → vue-vine-C-CFC0K-.js} +1 -1
  83. package/apps/web/dist/assets/xml-BlnrEAEy.js +1 -0
  84. package/apps/web/dist/assets/{xsl-73EQy__K.js → xsl-BOPS8gLt.js} +1 -1
  85. package/apps/web/dist/assets/yaml-Du_6cdrH.js +1 -0
  86. package/apps/web/dist/assets/zoomable-image-CVx2-A2g.js +41 -0
  87. package/apps/web/dist/index.html +5 -2
  88. package/apps/web/dist/sw.js +1 -1
  89. package/package.json +1 -1
  90. package/apps/web/dist/assets/angular-html-DS1oV12F.js +0 -1
  91. package/apps/web/dist/assets/c-DapwzVu-.js +0 -1
  92. package/apps/web/dist/assets/cpp-DxWN2Okg.js +0 -1
  93. package/apps/web/dist/assets/css-DO_8FP-m.js +0 -1
  94. package/apps/web/dist/assets/glsl-DCAs0c4j.js +0 -1
  95. package/apps/web/dist/assets/graphql-CjoRmsAT.js +0 -1
  96. package/apps/web/dist/assets/haml-C_s1GJd7.js +0 -1
  97. package/apps/web/dist/assets/html-rJzVMoNs.js +0 -1
  98. package/apps/web/dist/assets/index-CbDKWecy.js +0 -1206
  99. package/apps/web/dist/assets/index-aoUjY0qi.css +0 -2
  100. package/apps/web/dist/assets/java-CC9QxJEZ.js +0 -1
  101. package/apps/web/dist/assets/javascript-BhEdsWyv.js +0 -1
  102. package/apps/web/dist/assets/json-BwYVcFZr.js +0 -1
  103. package/apps/web/dist/assets/jsx-DZ0EGwxr.js +0 -1
  104. package/apps/web/dist/assets/lua-C1ueigSQ.js +0 -1
  105. package/apps/web/dist/assets/r-D-KnVREb.js +0 -1
  106. package/apps/web/dist/assets/regexp-BHxEHonG.js +0 -1
  107. package/apps/web/dist/assets/scss-QYV6e_ks.js +0 -1
  108. package/apps/web/dist/assets/shellscript-CBTXJvxj.js +0 -1
  109. package/apps/web/dist/assets/sql-BhGvXJN_.js +0 -1
  110. package/apps/web/dist/assets/tex-Be-e7W9g.js +0 -1
  111. package/apps/web/dist/assets/tsx-B_hC-_lk.js +0 -1
  112. package/apps/web/dist/assets/typescript-D8MumUKS.js +0 -1
  113. package/apps/web/dist/assets/xml-Dh5mAVtG.js +0 -1
  114. package/apps/web/dist/assets/yaml-C5lWNsLB.js +0 -1
@@ -823,11 +823,9 @@ const configSchema = z.strictObject({
823
823
  opencode: z.record(z.string(), modelSetting).default({})
824
824
  }).prefault({}),
825
825
  delivery: z.enum(["queue", "steer"]).default("steer"),
826
- autoResolve: z.strictObject({
827
- questions: z.number().positive().nullable().default(3),
828
- permissions: z.number().positive().nullable().default(1),
829
- permissionDecision: z.enum(["allow", "deny"]).default("allow")
830
- }).prefault({}),
826
+ autoSkipQuestionTimeout: z.number().positive().nullable().default(180),
827
+ autoPermissionMode: z.enum(["allow", "deny"]).default("allow"),
828
+ autoPermissionTimeout: z.number().positive().nullable().default(15),
831
829
  terminal: z.strictObject({
832
830
  shell: z.strictObject({
833
831
  macos: z.string().nullable().default(null),
@@ -914,20 +912,27 @@ function broadcastActivity() {
914
912
  });
915
913
  }
916
914
  function scheduleAutoResolve(sessionId, request) {
917
- if (request.kind === "plan") return;
918
- const settings = readConfig().autoResolve;
919
- const minutes = request.kind === "question" ? settings.questions : settings.permissions;
920
- if (minutes === null) return;
915
+ if (request.kind === "plan") return request;
916
+ const config = readConfig();
917
+ const seconds = request.kind === "question" ? config.autoSkipQuestionTimeout : config.autoPermissionTimeout;
918
+ if (seconds === null) return request;
919
+ const decision = request.kind === "question" ? "allow" : config.autoPermissionMode;
921
920
  setTimeout(async () => {
922
921
  const run = runs.get(sessionId);
923
922
  if (run === void 0) return;
924
923
  try {
925
- if (request.kind === "question") await run.respond(request.id, "allow", {});
926
- else await run.respond(request.id, readConfig().autoResolve.permissionDecision, null);
924
+ await run.respond(request.id, decision, request.kind === "question" ? {} : null);
927
925
  } catch (error) {
928
926
  console.warn(`could not auto-resolve ${request.id}: ${error}`);
929
927
  }
930
- }, minutes * 6e4);
928
+ }, seconds * 1e3);
929
+ return {
930
+ ...request,
931
+ autoResolve: {
932
+ at: Date.now() + seconds * 1e3,
933
+ decision
934
+ }
935
+ };
931
936
  }
932
937
  //#endregion
933
938
  //#region src/lib/codex/requests.ts
@@ -1257,11 +1262,12 @@ var CodexRun = class CodexRun {
1257
1262
  });
1258
1263
  return;
1259
1264
  }
1260
- const request = toRequest$1(String(id), method, params, this.items);
1261
- if (request === null) {
1265
+ const created = toRequest$1(String(id), method, params, this.items);
1266
+ if (created === null) {
1262
1267
  this.server.replyError(id, `offsite does not handle ${method}`);
1263
1268
  return;
1264
1269
  }
1270
+ const request = scheduleAutoResolve(this.sessionId, created);
1265
1271
  this.pending.set(request.id, {
1266
1272
  request,
1267
1273
  id,
@@ -1274,7 +1280,6 @@ var CodexRun = class CodexRun {
1274
1280
  request
1275
1281
  });
1276
1282
  broadcastActivity();
1277
- scheduleAutoResolve(this.sessionId, request);
1278
1283
  }
1279
1284
  async startTurn(prompt) {
1280
1285
  clearTimeout(this.idleTimer);
@@ -1746,7 +1751,7 @@ function toolItems(base, tool) {
1746
1751
  case "skill": return [{
1747
1752
  ...base,
1748
1753
  kind: "skill",
1749
- skill: text$2(input.name),
1754
+ skill: text$2(input.id),
1750
1755
  output,
1751
1756
  status
1752
1757
  }];
@@ -2299,14 +2304,17 @@ var OpenCodeRun = class OpenCodeRun {
2299
2304
  });
2300
2305
  }
2301
2306
  addPending(pending) {
2302
- this.pending.set(pending.request.id, pending);
2307
+ const request = scheduleAutoResolve(this.sessionId, pending.request);
2308
+ this.pending.set(request.id, {
2309
+ ...pending,
2310
+ request
2311
+ });
2303
2312
  publish$2(this.sessionId, {
2304
2313
  type: "request",
2305
2314
  sessionId: this.sessionId,
2306
- request: pending.request
2315
+ request
2307
2316
  });
2308
2317
  broadcastActivity();
2309
- scheduleAutoResolve(this.sessionId, pending.request);
2310
2318
  }
2311
2319
  finishTurn() {
2312
2320
  this.partial = "";
@@ -4319,13 +4327,13 @@ var Run = class {
4319
4327
  }, IDLE_LIMIT);
4320
4328
  }
4321
4329
  ask(options) {
4322
- const request = toRequest({
4330
+ const request = scheduleAutoResolve(this.sessionId, toRequest({
4323
4331
  id: randomUUID(),
4324
4332
  toolName: options.toolName,
4325
4333
  input: options.input,
4326
4334
  title: options.title,
4327
4335
  canAlwaysAllow: options.suggestions.length > 0
4328
- });
4336
+ }));
4329
4337
  return new Promise((resolve) => {
4330
4338
  this.pending.set(request.id, {
4331
4339
  request,
@@ -4339,7 +4347,6 @@ var Run = class {
4339
4347
  request
4340
4348
  });
4341
4349
  broadcastActivity();
4342
- scheduleAutoResolve(this.sessionId, request);
4343
4350
  options.signal.addEventListener("abort", () => this.settle(request.id, {
4344
4351
  behavior: "deny",
4345
4352
  message: "Cancelled"
@@ -4391,6 +4398,153 @@ var Run = class {
4391
4398
  }
4392
4399
  };
4393
4400
  //#endregion
4401
+ //#region ../core/src/media.ts
4402
+ const mediaTypes = /* @__PURE__ */ new Map([
4403
+ [".png", "image/png"],
4404
+ [".jpg", "image/jpeg"],
4405
+ [".jpeg", "image/jpeg"],
4406
+ [".gif", "image/gif"],
4407
+ [".webp", "image/webp"],
4408
+ [".svg", "image/svg+xml"],
4409
+ [".mp4", "video/mp4"],
4410
+ [".m4v", "video/mp4"],
4411
+ [".webm", "video/webm"],
4412
+ [".mov", "video/quicktime"],
4413
+ [".mp3", "audio/mpeg"],
4414
+ [".wav", "audio/wav"],
4415
+ [".ogg", "audio/ogg"],
4416
+ [".opus", "audio/ogg"],
4417
+ [".m4a", "audio/mp4"],
4418
+ [".aac", "audio/aac"],
4419
+ [".flac", "audio/flac"]
4420
+ ]);
4421
+ //#endregion
4422
+ //#region src/services/files.ts
4423
+ const FILE_LIMIT = 2e4;
4424
+ const OUTPUT_LIMIT$1 = 262144;
4425
+ const READ_LIMIT = 1048576;
4426
+ const MEDIA_LINK_LIFETIME = 36e5;
4427
+ const mediaLinks = /* @__PURE__ */ new Map();
4428
+ const skipped = /* @__PURE__ */ new Set([
4429
+ "node_modules",
4430
+ ".git",
4431
+ ".DS_Store",
4432
+ "dist",
4433
+ "target",
4434
+ ".next",
4435
+ "__pycache__",
4436
+ ".venv"
4437
+ ]);
4438
+ async function listFiles(cwd) {
4439
+ const files = [];
4440
+ const pending = [""];
4441
+ while (pending.length > 0 && files.length < FILE_LIMIT) {
4442
+ const dir = pending.pop() ?? "";
4443
+ let entries;
4444
+ try {
4445
+ entries = await readdir(path.join(cwd, dir), { withFileTypes: true });
4446
+ } catch {
4447
+ continue;
4448
+ }
4449
+ for (const entry of entries) {
4450
+ if (skipped.has(entry.name)) continue;
4451
+ const relative = dir === "" ? entry.name : `${dir}/${entry.name}`;
4452
+ if (entry.isDirectory()) pending.push(relative);
4453
+ if (entry.isFile()) files.push(relative);
4454
+ if (entry.isSymbolicLink()) {
4455
+ if ((await stat(path.join(cwd, relative)).catch(() => null))?.isFile() === true) files.push(relative);
4456
+ }
4457
+ }
4458
+ }
4459
+ return files.slice(0, FILE_LIMIT).sort();
4460
+ }
4461
+ function readProjectFile(cwd, file) {
4462
+ const full = resolveProjectFile(cwd, file);
4463
+ const stats = statSync(full);
4464
+ if (!stats.isFile()) throw new Error(`${file} is not a file`);
4465
+ if (stats.size > READ_LIMIT) return {
4466
+ kind: "large",
4467
+ size: stats.size,
4468
+ limit: READ_LIMIT
4469
+ };
4470
+ const data = readFileSync(full);
4471
+ const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
4472
+ if (mediaType?.startsWith("image/") === true) return {
4473
+ kind: "image",
4474
+ size: stats.size,
4475
+ mediaType,
4476
+ data: data.toString("base64")
4477
+ };
4478
+ if (data.includes(0)) return {
4479
+ kind: "binary",
4480
+ size: stats.size
4481
+ };
4482
+ return {
4483
+ kind: "text",
4484
+ size: stats.size,
4485
+ content: data.toString("utf8")
4486
+ };
4487
+ }
4488
+ function linkProjectMedia(cwd, file) {
4489
+ const full = resolveProjectFile(cwd, file);
4490
+ const mediaType = mediaTypes.get(path.extname(full).toLowerCase());
4491
+ if (mediaType === void 0) throw new Error(`${file} is not an image, video or audio file`);
4492
+ for (const [token, link] of mediaLinks) if (link.expiresAt <= Date.now()) mediaLinks.delete(token);
4493
+ const token = randomBytes(32).toString("base64url");
4494
+ mediaLinks.set(token, {
4495
+ file: full,
4496
+ mediaType,
4497
+ expiresAt: Date.now() + MEDIA_LINK_LIFETIME
4498
+ });
4499
+ return token;
4500
+ }
4501
+ function findMediaLink(token) {
4502
+ if (token === null) return null;
4503
+ const link = mediaLinks.get(token);
4504
+ if (link === void 0 || link.expiresAt <= Date.now()) return null;
4505
+ return link;
4506
+ }
4507
+ function saveUpload(name, data) {
4508
+ const dir = path.join(home, "uploads", randomUUID());
4509
+ mkdirSync(dir, { recursive: true });
4510
+ const file = path.join(dir, path.basename(name));
4511
+ writeFileSync(file, data);
4512
+ return file;
4513
+ }
4514
+ async function taskOutput(sessionId, file) {
4515
+ const tasks = path.dirname(file);
4516
+ if (!/^[\w-]+$/.test(sessionId) || !path.isAbsolute(file) || path.normalize(file) !== file || !/^\w+\.output$/.test(path.basename(file)) || path.basename(tasks) !== "tasks" || path.basename(path.dirname(tasks)) !== sessionId) throw new Error("Not a background task output file of this thread");
4517
+ let stats;
4518
+ try {
4519
+ stats = await lstat(file);
4520
+ } catch (error) {
4521
+ if (hasCode(error, "ENOENT")) return null;
4522
+ throw error;
4523
+ }
4524
+ if (!stats.isFile()) throw new Error("Background task output is not a regular file");
4525
+ const start = Math.max(0, stats.size - OUTPUT_LIMIT$1);
4526
+ const handle = await open(file, "r");
4527
+ try {
4528
+ const buffer = Buffer.alloc(stats.size - start);
4529
+ const read = await handle.read(buffer, 0, buffer.length, start);
4530
+ return {
4531
+ text: buffer.subarray(0, read.bytesRead).toString("utf8"),
4532
+ truncated: start > 0
4533
+ };
4534
+ } finally {
4535
+ await handle.close();
4536
+ }
4537
+ }
4538
+ function resolveProjectFile(cwd, file) {
4539
+ const root = realpathSync(cwd);
4540
+ const resolved = path.resolve(root, file);
4541
+ if (!existsSync(resolved)) throw new Error(`${file} does not exist`);
4542
+ const full = realpathSync(resolved);
4543
+ const relative = path.relative(root, full);
4544
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
4545
+ return full;
4546
+ }
4547
+ //#endregion
4394
4548
  //#region src/services/git.ts
4395
4549
  const DIFF_LIMIT = 8e4;
4396
4550
  const UNTRACKED_LIMIT = 100;
@@ -4649,6 +4803,7 @@ async function isRepo(cwd) {
4649
4803
  }
4650
4804
  async function repoDiff(repo, prefix) {
4651
4805
  const tracked = await git(repo, [
4806
+ "--no-optional-locks",
4652
4807
  "diff",
4653
4808
  await diffBase(repo),
4654
4809
  "--no-color",
@@ -4708,119 +4863,6 @@ function newFilePatch(cwd, file, prefix) {
4708
4863
  return `${header}@@ -0,0 +1,${lines.length} @@\n${lines.map((line) => `+${line}\n`).join("")}`;
4709
4864
  }
4710
4865
  //#endregion
4711
- //#region src/services/files.ts
4712
- const FILE_LIMIT = 2e4;
4713
- const OUTPUT_LIMIT$1 = 262144;
4714
- const READ_LIMIT = 1048576;
4715
- const images = /* @__PURE__ */ new Map([
4716
- [".png", "image/png"],
4717
- [".jpg", "image/jpeg"],
4718
- [".jpeg", "image/jpeg"],
4719
- [".gif", "image/gif"],
4720
- [".webp", "image/webp"],
4721
- [".svg", "image/svg+xml"]
4722
- ]);
4723
- const skipped = /* @__PURE__ */ new Set([
4724
- "node_modules",
4725
- ".git",
4726
- "dist",
4727
- "target",
4728
- ".next"
4729
- ]);
4730
- async function listFiles(cwd) {
4731
- try {
4732
- const { stdout } = await git(cwd, [
4733
- "ls-files",
4734
- "--cached",
4735
- "--others",
4736
- "--exclude-standard"
4737
- ]);
4738
- return stdout.split("\n").filter(Boolean).slice(0, FILE_LIMIT);
4739
- } catch {
4740
- return walk(cwd);
4741
- }
4742
- }
4743
- function readProjectFile(cwd, file) {
4744
- const root = realpathSync(cwd);
4745
- const full = realpathSync(path.resolve(root, file));
4746
- const relative = path.relative(root, full);
4747
- if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${file} is outside the project`);
4748
- const stats = statSync(full);
4749
- if (!stats.isFile()) throw new Error(`${file} is not a file`);
4750
- if (stats.size > READ_LIMIT) return {
4751
- kind: "large",
4752
- size: stats.size,
4753
- limit: READ_LIMIT
4754
- };
4755
- const data = readFileSync(full);
4756
- const mediaType = images.get(path.extname(full).toLowerCase());
4757
- if (mediaType !== void 0) return {
4758
- kind: "image",
4759
- size: stats.size,
4760
- mediaType,
4761
- data: data.toString("base64")
4762
- };
4763
- if (data.includes(0)) return {
4764
- kind: "binary",
4765
- size: stats.size
4766
- };
4767
- return {
4768
- kind: "text",
4769
- size: stats.size,
4770
- content: data.toString("utf8")
4771
- };
4772
- }
4773
- function saveUpload(name, data) {
4774
- const dir = path.join(home, "uploads", randomUUID());
4775
- mkdirSync(dir, { recursive: true });
4776
- const file = path.join(dir, path.basename(name));
4777
- writeFileSync(file, data);
4778
- return file;
4779
- }
4780
- async function taskOutput(sessionId, file) {
4781
- const tasks = path.dirname(file);
4782
- if (!/^[\w-]+$/.test(sessionId) || !path.isAbsolute(file) || path.normalize(file) !== file || !/^\w+\.output$/.test(path.basename(file)) || path.basename(tasks) !== "tasks" || path.basename(path.dirname(tasks)) !== sessionId) throw new Error("Not a background task output file of this thread");
4783
- let stats;
4784
- try {
4785
- stats = await lstat(file);
4786
- } catch (error) {
4787
- if (hasCode(error, "ENOENT")) return null;
4788
- throw error;
4789
- }
4790
- if (!stats.isFile()) throw new Error("Background task output is not a regular file");
4791
- const start = Math.max(0, stats.size - OUTPUT_LIMIT$1);
4792
- const handle = await open(file, "r");
4793
- try {
4794
- const buffer = Buffer.alloc(stats.size - start);
4795
- const read = await handle.read(buffer, 0, buffer.length, start);
4796
- return {
4797
- text: buffer.subarray(0, read.bytesRead).toString("utf8"),
4798
- truncated: start > 0
4799
- };
4800
- } finally {
4801
- await handle.close();
4802
- }
4803
- }
4804
- function walk(root) {
4805
- const files = [];
4806
- const pending = [""];
4807
- while (pending.length > 0 && files.length < FILE_LIMIT) {
4808
- const dir = pending.pop() ?? "";
4809
- let entries;
4810
- try {
4811
- entries = readdirSync(path.join(root, dir), { withFileTypes: true });
4812
- } catch {
4813
- continue;
4814
- }
4815
- for (const entry of entries) {
4816
- const relative = dir === "" ? entry.name : `${dir}/${entry.name}`;
4817
- if (entry.isDirectory() && !skipped.has(entry.name)) pending.push(relative);
4818
- if (entry.isFile()) files.push(relative);
4819
- }
4820
- }
4821
- return files;
4822
- }
4823
- //#endregion
4824
4866
  //#region src/services/runs.ts
4825
4867
  const locks = /* @__PURE__ */ new Map();
4826
4868
  function stopAll() {
@@ -6034,6 +6076,7 @@ const handlers = {
6034
6076
  capabilities: (params) => capabilities(params.provider, params.accountId, params.projectId === null ? os.homedir() : projectPath(params.projectId)),
6035
6077
  "files.list": (params) => listFiles(projectPath(params.projectId)),
6036
6078
  "files.read": (params) => readProjectFile(projectPath(params.projectId), params.path),
6079
+ "files.media": (params) => linkProjectMedia(projectPath(params.projectId), params.path),
6037
6080
  "files.upload": (params) => saveUpload(params.name, Buffer.from(params.data, "base64")),
6038
6081
  "tasks.output": (params) => taskOutput(params.sessionId, params.path),
6039
6082
  "git.status": (params) => gitRepos(projectPath(params.projectId)),
@@ -6212,6 +6255,10 @@ const code = args.auth && args.pair && !hasDevices() ? addCode(null) : null;
6212
6255
  const tickets = /* @__PURE__ */ new Map();
6213
6256
  const server = createServer((request, response) => {
6214
6257
  const url = new URL(request.url ?? "/", "http://localhost");
6258
+ if (url.pathname === "/api/media") {
6259
+ serveMedia(url.searchParams.get("token"), request, response);
6260
+ return;
6261
+ }
6215
6262
  if (url.pathname.startsWith("/api/")) {
6216
6263
  answerApi(url.pathname, request, response);
6217
6264
  return;
@@ -6308,6 +6355,64 @@ function answerApi(pathname, request, response) {
6308
6355
  response.statusCode = 404;
6309
6356
  response.end("not found");
6310
6357
  }
6358
+ function serveMedia(token, request, response) {
6359
+ if (request.method !== "GET") {
6360
+ response.statusCode = 405;
6361
+ response.end("use GET");
6362
+ return;
6363
+ }
6364
+ const link = findMediaLink(token);
6365
+ if (link === null) {
6366
+ response.statusCode = 404;
6367
+ response.end("This media link expired");
6368
+ return;
6369
+ }
6370
+ const stats = statSync(link.file, { throwIfNoEntry: false });
6371
+ if (stats === void 0 || !stats.isFile()) {
6372
+ response.statusCode = 404;
6373
+ response.end("not found");
6374
+ return;
6375
+ }
6376
+ response.setHeader("content-type", link.mediaType);
6377
+ response.setHeader("accept-ranges", "bytes");
6378
+ response.setHeader("cache-control", "private, max-age=3600");
6379
+ response.setHeader("content-security-policy", "sandbox");
6380
+ response.setHeader("x-content-type-options", "nosniff");
6381
+ if (request.headers.range === void 0) {
6382
+ response.setHeader("content-length", stats.size);
6383
+ createReadStream(link.file).on("error", () => response.destroy()).pipe(response);
6384
+ return;
6385
+ }
6386
+ const range = byteRange(request.headers.range, stats.size);
6387
+ if (range === null) {
6388
+ response.statusCode = 416;
6389
+ response.setHeader("content-range", `bytes */${stats.size}`);
6390
+ response.end();
6391
+ return;
6392
+ }
6393
+ response.statusCode = 206;
6394
+ response.setHeader("content-range", `bytes ${range.start}-${range.end}/${stats.size}`);
6395
+ response.setHeader("content-length", range.end - range.start + 1);
6396
+ createReadStream(link.file, range).on("error", () => response.destroy()).pipe(response);
6397
+ }
6398
+ function byteRange(header, size) {
6399
+ const match = /^bytes=(?:(\d+)-(\d*)|-(\d+))$/.exec(header);
6400
+ if (match === null) return null;
6401
+ if (match[3] !== void 0) {
6402
+ if (size === 0 || Number(match[3]) === 0) return null;
6403
+ return {
6404
+ start: Math.max(size - Number(match[3]), 0),
6405
+ end: size - 1
6406
+ };
6407
+ }
6408
+ const start = Number(match[1]);
6409
+ const end = match[2] === "" ? size - 1 : Math.min(Number(match[2]), size - 1);
6410
+ if (start > end) return null;
6411
+ return {
6412
+ start,
6413
+ end
6414
+ };
6415
+ }
6311
6416
  function answerJson(response, body) {
6312
6417
  response.setHeader("content-type", "application/json");
6313
6418
  response.end(JSON.stringify(body));