@axiom-lattice/gateway 4.0.2 → 4.1.0

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.
package/dist/index.mjs CHANGED
@@ -69,8 +69,8 @@ function resolveLocalA2AWorkingDirectory(options) {
69
69
  }
70
70
  function validateWorkingDirectory(cwd) {
71
71
  try {
72
- const stat = statSync(cwd);
73
- if (!stat.isDirectory()) {
72
+ const stat2 = statSync(cwd);
73
+ if (!stat2.isDirectory()) {
74
74
  return `Working directory is not a directory: ${cwd}`;
75
75
  }
76
76
  return null;
@@ -846,8 +846,8 @@ async function pipeAsyncIterableToSse(raw, stream, options = {}) {
846
846
  let closed = raw.destroyed || raw.writableEnded;
847
847
  let returned = false;
848
848
  let resolveClosed;
849
- const closedPromise = new Promise((resolve2) => {
850
- resolveClosed = resolve2;
849
+ const closedPromise = new Promise((resolve3) => {
850
+ resolveClosed = resolve3;
851
851
  });
852
852
  const returnConsumer = () => {
853
853
  if (returned) return;
@@ -897,14 +897,14 @@ async function pipeAsyncIterableToSse(raw, stream, options = {}) {
897
897
  async function writeSse(raw, value, closed) {
898
898
  if (raw.destroyed || raw.writableEnded) return false;
899
899
  if (raw.write(value)) return true;
900
- return new Promise((resolve2) => {
900
+ return new Promise((resolve3) => {
901
901
  let settled = false;
902
902
  const onDrain = () => finish(!raw.destroyed && !raw.writableEnded);
903
903
  const finish = (writable) => {
904
904
  if (settled) return;
905
905
  settled = true;
906
906
  raw.removeListener("drain", onDrain);
907
- resolve2(writable);
907
+ resolve3(writable);
908
908
  };
909
909
  raw.once("drain", onDrain);
910
910
  void closed.then(() => finish(false));
@@ -3712,7 +3712,7 @@ var ExportImportService = class {
3712
3712
  const files = /* @__PURE__ */ new Map();
3713
3713
  let bundle;
3714
3714
  const gunzip = zlib.createGunzip();
3715
- await new Promise((resolve2, reject) => {
3715
+ await new Promise((resolve3, reject) => {
3716
3716
  let settled = false;
3717
3717
  let entryCount = 0;
3718
3718
  let declaredTotalBytes = 0;
@@ -3832,7 +3832,7 @@ var ExportImportService = class {
3832
3832
  parser.on("end", () => {
3833
3833
  if (settled) return;
3834
3834
  settled = true;
3835
- resolve2();
3835
+ resolve3();
3836
3836
  });
3837
3837
  gunzip.pipe(parser);
3838
3838
  gunzip.end(buffer);
@@ -4517,6 +4517,20 @@ var dataQuerySchema = {
4517
4517
  };
4518
4518
 
4519
4519
  // src/schemas/index.ts
4520
+ var downloadFolderSchema = {
4521
+ description: "Download a workspace project folder as a ZIP archive",
4522
+ tags: ["Workspace"],
4523
+ querystring: {
4524
+ type: "object",
4525
+ properties: {
4526
+ path: { type: "string", minLength: 1 },
4527
+ assistantId: { type: "string", minLength: 1 },
4528
+ tenantId: { type: "string", minLength: 1 }
4529
+ },
4530
+ required: ["path"],
4531
+ additionalProperties: false
4532
+ }
4533
+ };
4520
4534
  var getAllMemoryItemsSchema = {
4521
4535
  description: "Get all memory items for an assistant thread",
4522
4536
  tags: ["Memory"],
@@ -5114,11 +5128,89 @@ function registerSandboxProxyRoutes(app2) {
5114
5128
  // src/controllers/workspace.ts
5115
5129
  import * as fs2 from "fs/promises";
5116
5130
  import * as path2 from "path";
5131
+ import { constants as fsConstants } from "fs";
5117
5132
  import * as os2 from "os";
5118
5133
  import { getStoreLattice as getStoreLattice6 } from "@axiom-lattice/core";
5119
5134
  import { SandboxFilesystem, FilesystemBackend } from "@axiom-lattice/core";
5120
5135
  import { getSandBoxManager as getSandBoxManager3 } from "@axiom-lattice/core";
5121
5136
  import { v4 as uuidv42 } from "uuid";
5137
+ var MAX_ZIP_ENTRIES = 1e4;
5138
+ var MAX_ZIP_FILE_SIZE = 100 * 1024 * 1024;
5139
+ var MAX_ZIP_TOTAL_SIZE = 512 * 1024 * 1024;
5140
+ var FolderDownloadError = class extends Error {
5141
+ constructor(statusCode, code, message) {
5142
+ super(message);
5143
+ this.statusCode = statusCode;
5144
+ this.code = code;
5145
+ this.name = "FolderDownloadError";
5146
+ }
5147
+ };
5148
+ function zipCrc32(data) {
5149
+ let crc = 4294967295;
5150
+ for (const byte of data) {
5151
+ crc ^= byte;
5152
+ for (let bit = 0; bit < 8; bit += 1) {
5153
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
5154
+ }
5155
+ }
5156
+ return (crc ^ 4294967295) >>> 0;
5157
+ }
5158
+ function createZip(entries) {
5159
+ const localParts = [];
5160
+ const centralParts = [];
5161
+ let offset = 0;
5162
+ for (const entry of entries) {
5163
+ const name = Buffer.from(entry.name, "utf8");
5164
+ const data = entry.data;
5165
+ const crc = zipCrc32(data);
5166
+ const local = Buffer.alloc(30 + name.length + data.length);
5167
+ local.writeUInt32LE(67324752, 0);
5168
+ local.writeUInt16LE(20, 4);
5169
+ local.writeUInt16LE(0, 6);
5170
+ local.writeUInt16LE(2048, 8);
5171
+ local.writeUInt16LE(0, 10);
5172
+ local.writeUInt16LE(0, 12);
5173
+ local.writeUInt32LE(crc, 14);
5174
+ local.writeUInt32LE(data.length, 18);
5175
+ local.writeUInt32LE(data.length, 22);
5176
+ local.writeUInt16LE(name.length, 26);
5177
+ local.writeUInt16LE(0, 28);
5178
+ name.copy(local, 30);
5179
+ data.copy(local, 30 + name.length);
5180
+ localParts.push(local);
5181
+ const central = Buffer.alloc(46 + name.length);
5182
+ central.writeUInt32LE(33639248, 0);
5183
+ central.writeUInt16LE(20, 4);
5184
+ central.writeUInt16LE(20, 6);
5185
+ central.writeUInt16LE(0, 8);
5186
+ central.writeUInt16LE(2048, 10);
5187
+ central.writeUInt16LE(0, 12);
5188
+ central.writeUInt16LE(0, 14);
5189
+ central.writeUInt32LE(crc, 16);
5190
+ central.writeUInt32LE(data.length, 20);
5191
+ central.writeUInt32LE(data.length, 24);
5192
+ central.writeUInt16LE(name.length, 28);
5193
+ central.writeUInt16LE(0, 30);
5194
+ central.writeUInt16LE(0, 32);
5195
+ central.writeUInt16LE(0, 34);
5196
+ central.writeUInt16LE(entry.directory ? 16 : 0, 36);
5197
+ central.writeUInt32LE(entry.directory ? 16 : 0, 38);
5198
+ central.writeUInt32LE(offset, 42);
5199
+ name.copy(central, 46);
5200
+ centralParts.push(central);
5201
+ offset += local.length;
5202
+ }
5203
+ const centralDirectory = Buffer.concat(centralParts);
5204
+ const end = Buffer.alloc(22);
5205
+ end.writeUInt32LE(101010256, 0);
5206
+ end.writeUInt16LE(0, 4);
5207
+ end.writeUInt16LE(0, 6);
5208
+ end.writeUInt16LE(entries.length, 8);
5209
+ end.writeUInt16LE(entries.length, 10);
5210
+ end.writeUInt32LE(centralDirectory.length, 12);
5211
+ end.writeUInt32LE(offset, 16);
5212
+ return Buffer.concat([...localParts, centralDirectory, end]);
5213
+ }
5122
5214
  var WorkspaceController = class {
5123
5215
  constructor() {
5124
5216
  this.workspaceStore = getStoreLattice6("default", "workspace").store;
@@ -5384,6 +5476,259 @@ var WorkspaceController = class {
5384
5476
  return reply.status(502).send({ success: false, error: `Download proxy error: ${message}` });
5385
5477
  }
5386
5478
  }
5479
+ async downloadFolder(request, reply) {
5480
+ const tenantId = this.getTenantId(request);
5481
+ const { workspaceId, projectId } = request.params;
5482
+ const folderPath = request.query.path;
5483
+ const assistantId = request.query.assistantId;
5484
+ if (!folderPath || folderPath.includes("\\") || folderPath.split("/").includes("..")) {
5485
+ return reply.status(400).send({
5486
+ success: false,
5487
+ error: "Invalid folder path",
5488
+ code: "INVALID_PATH"
5489
+ });
5490
+ }
5491
+ try {
5492
+ const storedWorkspace = await this.workspaceStore.getWorkspaceById(tenantId, workspaceId);
5493
+ if (!storedWorkspace) {
5494
+ throw new FolderDownloadError(404, "WORKSPACE_NOT_FOUND", "Workspace not found");
5495
+ }
5496
+ const project = await this.projectStore.getProjectById(tenantId, projectId);
5497
+ if (!project || project.workspaceId !== workspaceId) {
5498
+ throw new FolderDownloadError(404, "PROJECT_NOT_FOUND", "Project not found");
5499
+ }
5500
+ const { backend, workspace } = await this.getBackend(
5501
+ tenantId,
5502
+ workspaceId,
5503
+ projectId,
5504
+ assistantId,
5505
+ folderPath
5506
+ );
5507
+ if (!backend.lsInfo || workspace.storageType === "sandbox" && !backend.readBinary) {
5508
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder downloads");
5509
+ }
5510
+ let listEntries2;
5511
+ if (workspace.storageType !== "sandbox") {
5512
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
5513
+ const target = path2.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
5514
+ const contained = await resolvePathWithinRoot(root, target).catch((error) => {
5515
+ if (error instanceof Error && error.code === "ENOENT") {
5516
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5517
+ }
5518
+ throw error;
5519
+ });
5520
+ let stat2;
5521
+ try {
5522
+ stat2 = await fs2.stat(contained.targetPath);
5523
+ } catch (error) {
5524
+ if (error instanceof Error && error.code === "ENOENT") {
5525
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5526
+ }
5527
+ throw error;
5528
+ }
5529
+ if (!stat2.isDirectory()) {
5530
+ throw new FolderDownloadError(400, "NOT_A_FOLDER", "Path is not a folder");
5531
+ }
5532
+ } else if (folderPath) {
5533
+ const sandboxManager = getSandBoxManager3();
5534
+ const volumeConfig = {
5535
+ assistant_id: assistantId || "",
5536
+ thread_id: "",
5537
+ tenantId,
5538
+ workspaceId,
5539
+ projectId
5540
+ };
5541
+ const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, folderPath);
5542
+ if (!volume) {
5543
+ const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
5544
+ try {
5545
+ await sandbox.file.listPath(folderPath, { recursive: false });
5546
+ } catch (error) {
5547
+ const code = error instanceof Error ? error.code : void 0;
5548
+ const message = error instanceof Error ? error.message : String(error);
5549
+ if (code === "ENOENT" || /not found|no such file|enoent/i.test(message)) {
5550
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5551
+ }
5552
+ throw error;
5553
+ }
5554
+ listEntries2 = async (directoryPath) => {
5555
+ const result = await sandbox.file.listPath(directoryPath, { recursive: false });
5556
+ return result.files;
5557
+ };
5558
+ } else {
5559
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
5560
+ }
5561
+ }
5562
+ if (!listEntries2) {
5563
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
5564
+ }
5565
+ const folderName = path2.basename(folderPath.replace(/\/$/, "")) || "folder";
5566
+ const prefix = `${folderName}/`;
5567
+ const entries = [{ name: prefix, data: Buffer.alloc(0), directory: true }];
5568
+ let totalSize = 0;
5569
+ const filesystemRoot = workspace.storageType === "sandbox" ? void 0 : filesystemRootDirectory(tenantId, workspaceId, projectId);
5570
+ const addFile = (archivePath, data) => {
5571
+ if (data.length > MAX_ZIP_FILE_SIZE || totalSize + data.length > MAX_ZIP_TOTAL_SIZE) {
5572
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5573
+ }
5574
+ totalSize += data.length;
5575
+ entries.push({ name: archivePath, data, directory: false });
5576
+ };
5577
+ const collectFilesystem = async (directoryPath, archivePath) => {
5578
+ let children;
5579
+ try {
5580
+ children = await fs2.readdir(directoryPath, { withFileTypes: true });
5581
+ } catch (error) {
5582
+ if (error instanceof Error && error.code === "ENOENT") {
5583
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5584
+ }
5585
+ throw error;
5586
+ }
5587
+ for (const child of children) {
5588
+ if (entries.length >= MAX_ZIP_ENTRIES) {
5589
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
5590
+ }
5591
+ const childPath = path2.join(directoryPath, child.name);
5592
+ const archiveChildPath = `${archivePath}${child.name}`;
5593
+ let resolvedChild;
5594
+ try {
5595
+ resolvedChild = await resolvePathWithinRoot(filesystemRoot, childPath);
5596
+ } catch (error) {
5597
+ if (error instanceof Error && error.code === "ENOENT") {
5598
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5599
+ }
5600
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
5601
+ }
5602
+ let childStat;
5603
+ try {
5604
+ childStat = await fs2.lstat(resolvedChild.targetPath);
5605
+ } catch (error) {
5606
+ if (error instanceof Error && error.code === "ENOENT") {
5607
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5608
+ }
5609
+ throw error;
5610
+ }
5611
+ if (childStat.isSymbolicLink() || child.isSymbolicLink()) {
5612
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
5613
+ }
5614
+ if (child.isDirectory()) {
5615
+ entries.push({ name: `${archiveChildPath}/`, data: Buffer.alloc(0), directory: true });
5616
+ await collectFilesystem(resolvedChild.targetPath, `${archiveChildPath}/`);
5617
+ } else if (child.isFile()) {
5618
+ if (childStat.size > MAX_ZIP_FILE_SIZE || totalSize + childStat.size > MAX_ZIP_TOTAL_SIZE) {
5619
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5620
+ }
5621
+ const flags = fsConstants.O_NOFOLLOW === void 0 ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
5622
+ let handle3;
5623
+ try {
5624
+ handle3 = await fs2.open(resolvedChild.targetPath, flags);
5625
+ } catch (error) {
5626
+ if (error instanceof Error && error.code === "ENOENT") {
5627
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5628
+ }
5629
+ throw error;
5630
+ }
5631
+ try {
5632
+ const openedStat = await handle3.stat();
5633
+ if (!openedStat.isFile() || openedStat.size > MAX_ZIP_FILE_SIZE || totalSize + openedStat.size > MAX_ZIP_TOTAL_SIZE) {
5634
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5635
+ }
5636
+ const readLimit = MAX_ZIP_FILE_SIZE + 1;
5637
+ const dataBuffer = Buffer.alloc(Math.min(openedStat.size + 1, readLimit));
5638
+ let bytesRead = 0;
5639
+ while (bytesRead < dataBuffer.length) {
5640
+ const result = await handle3.read(
5641
+ dataBuffer,
5642
+ bytesRead,
5643
+ dataBuffer.length - bytesRead,
5644
+ bytesRead
5645
+ );
5646
+ if (result.bytesRead === 0) break;
5647
+ bytesRead += result.bytesRead;
5648
+ }
5649
+ const data = dataBuffer.subarray(0, bytesRead);
5650
+ const finalStat = await handle3.stat();
5651
+ if (finalStat.size > MAX_ZIP_FILE_SIZE || totalSize + finalStat.size > MAX_ZIP_TOTAL_SIZE || finalStat.size > data.length) {
5652
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5653
+ }
5654
+ addFile(archiveChildPath, data);
5655
+ } finally {
5656
+ await handle3.close();
5657
+ }
5658
+ }
5659
+ }
5660
+ };
5661
+ const collect = async (currentPath, archivePath) => {
5662
+ let children;
5663
+ try {
5664
+ children = await listEntries2(currentPath);
5665
+ } catch (error) {
5666
+ const message = error instanceof Error ? error.message : String(error);
5667
+ if (/not found|enoent|no such file/i.test(message)) {
5668
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5669
+ }
5670
+ throw error;
5671
+ }
5672
+ for (const child of children) {
5673
+ if (entries.length >= MAX_ZIP_ENTRIES) {
5674
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
5675
+ }
5676
+ const childPath = child.path;
5677
+ if (childPath.includes("\\") || childPath.split("/").includes("..") || !childPath.startsWith(folderPath.endsWith("/") ? folderPath : `${folderPath}/`)) {
5678
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains an unsafe child path");
5679
+ }
5680
+ if (workspace.storageType !== "sandbox") {
5681
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
5682
+ const virtualChildPath = childPath.startsWith("/") ? childPath : `/${childPath}`;
5683
+ const target = path2.resolve(root, `.${virtualChildPath}`);
5684
+ try {
5685
+ await resolvePathWithinRoot(root, target);
5686
+ const childStat = await fs2.lstat(target);
5687
+ if (childStat.isSymbolicLink()) {
5688
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
5689
+ }
5690
+ } catch (error) {
5691
+ if (error instanceof FolderDownloadError) throw error;
5692
+ if (error instanceof Error && error.code === "ENOENT") {
5693
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5694
+ }
5695
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
5696
+ }
5697
+ }
5698
+ const childName = path2.basename(childPath.replace(/\/$/, ""));
5699
+ const childArchivePath = `${archivePath}${childName}`;
5700
+ if (child.is_dir) {
5701
+ entries.push({ name: `${childArchivePath}/`, data: Buffer.alloc(0), directory: true });
5702
+ await collect(childPath, `${childArchivePath}/`);
5703
+ } else {
5704
+ const size = child.size ?? 0;
5705
+ if (size > MAX_ZIP_FILE_SIZE || totalSize + size > MAX_ZIP_TOTAL_SIZE) {
5706
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5707
+ }
5708
+ const data = await backend.readBinary(childPath);
5709
+ addFile(childArchivePath, data);
5710
+ }
5711
+ }
5712
+ };
5713
+ if (workspace.storageType === "sandbox") {
5714
+ await collect(folderPath, prefix);
5715
+ } else {
5716
+ const root = filesystemRoot;
5717
+ const target = path2.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
5718
+ await collectFilesystem(target, prefix);
5719
+ }
5720
+ const zip = createZip(entries);
5721
+ const downloadName = `${folderName}.zip`;
5722
+ return reply.status(200).type("application/zip").header("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(downloadName)}`).send(zip);
5723
+ } catch (error) {
5724
+ const folderError = error instanceof FolderDownloadError ? error : error instanceof PathOutsideRootError || error instanceof Error && ["ELOOP", "ENOTDIR", "EACCES"].includes(error.code || "") ? new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project") : new FolderDownloadError(502, "DOWNLOAD_FAILED", error instanceof Error ? error.message : String(error));
5725
+ return reply.status(folderError.statusCode).send({
5726
+ success: false,
5727
+ error: folderError.message,
5728
+ code: folderError.code
5729
+ });
5730
+ }
5731
+ }
5387
5732
  /**
5388
5733
  * View a file inline in the browser (not download).
5389
5734
  * Returns file content with inline Content-Disposition for preview.
@@ -5676,6 +6021,11 @@ function registerWorkspaceRoutes(app2) {
5676
6021
  "/api/workspaces/:workspaceId/projects/:projectId/downloadfile",
5677
6022
  controller.downloadFile.bind(controller)
5678
6023
  );
6024
+ app2.get(
6025
+ "/api/workspaces/:workspaceId/projects/:projectId/downloadfolder",
6026
+ { schema: downloadFolderSchema },
6027
+ controller.downloadFolder.bind(controller)
6028
+ );
5679
6029
  app2.get(
5680
6030
  "/api/workspaces/:workspaceId/projects/:projectId/viewfile",
5681
6031
  controller.viewFile.bind(controller)
@@ -5903,7 +6253,7 @@ async function testDatabaseConnection(request, reply) {
5903
6253
  await db.connect();
5904
6254
  await db.listTables();
5905
6255
  const latency = Date.now() - startTime;
5906
- await new Promise((resolve2) => setTimeout(resolve2, 100));
6256
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
5907
6257
  await db.disconnect();
5908
6258
  await sqlDatabaseManager.removeDatabase(tenantId, testKey);
5909
6259
  return {
@@ -6725,6 +7075,7 @@ import {
6725
7075
  LatticeEvalProject
6726
7076
  } from "@axiom-lattice/core";
6727
7077
  import { v4 as uuidv43 } from "uuid";
7078
+ var TERMINAL_PERSIST_ATTEMPTS = 2;
6728
7079
  function mapLogs(logs) {
6729
7080
  return logs.map((l) => ({
6730
7081
  timestamp: l.ts,
@@ -6736,18 +7087,42 @@ function mapLogs(logs) {
6736
7087
  var EvalRunner = class {
6737
7088
  constructor() {
6738
7089
  this.runs = /* @__PURE__ */ new Map();
7090
+ this.projectLeases = /* @__PURE__ */ new Map();
6739
7091
  this.eventEmitter = new EventEmitter();
6740
7092
  this.processStartedAt = /* @__PURE__ */ new Date();
6741
7093
  }
6742
7094
  getEventEmitter() {
6743
7095
  return this.eventEmitter;
6744
7096
  }
7097
+ reserveProjectDeletion(tenantId, projectId) {
7098
+ const leaseKey = `${tenantId}:${projectId}`;
7099
+ if (this.projectLeases.has(leaseKey) || [...this.runs.values()].some(
7100
+ (ctx) => ctx.tenantId === tenantId && ctx.projectId === projectId && !ctx.terminal.workerSettled
7101
+ )) return null;
7102
+ const leaseToken = Symbol(`delete:${leaseKey}`);
7103
+ this.projectLeases.set(leaseKey, leaseToken);
7104
+ return () => this.releaseProjectLease(leaseKey, leaseToken);
7105
+ }
6745
7106
  async startRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId) {
7107
+ const leaseKey = `${tenantId}:${projectId}`;
7108
+ if (this.projectLeases.has(leaseKey)) {
7109
+ throw new Error("A run is already in progress for this project");
7110
+ }
7111
+ const leaseToken = Symbol(leaseKey);
7112
+ this.projectLeases.set(leaseKey, leaseToken);
7113
+ try {
7114
+ return await this.startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken);
7115
+ } catch (error) {
7116
+ this.releaseProjectLease(leaseKey, leaseToken);
7117
+ throw error;
7118
+ }
7119
+ }
7120
+ async startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken) {
6746
7121
  const store = this.getEvalStore();
6747
7122
  const project = await store.getProjectById(tenantId, projectId);
6748
7123
  if (!project) throw new Error("Project not found");
6749
7124
  for (const ctx of this.runs.values()) {
6750
- if (ctx.projectId === projectId) {
7125
+ if (ctx.projectId === projectId && !ctx.terminal.persisted) {
6751
7126
  throw new Error("A run is already in progress for this project");
6752
7127
  }
6753
7128
  }
@@ -6851,14 +7226,35 @@ var EvalRunner = class {
6851
7226
  };
6852
7227
  const abortController = new AbortController();
6853
7228
  const stats = { passed: 0, failed: 0, interrupted: 0, totalScore: 0 };
7229
+ const terminal = { claiming: false, persisted: false, workerSettled: false };
7230
+ const terminalUnavailable = () => terminal.claiming || terminal.persisted;
7231
+ const persistTerminal = async (update, event) => {
7232
+ if (terminalUnavailable()) return false;
7233
+ terminal.claiming = true;
7234
+ try {
7235
+ let lastError;
7236
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
7237
+ try {
7238
+ const persistedRun = await store.updateRunStatus(tenantId, runId, update);
7239
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7240
+ lastError = void 0;
7241
+ break;
7242
+ } catch (error) {
7243
+ lastError = error;
7244
+ }
7245
+ }
7246
+ if (lastError) throw lastError;
7247
+ terminal.persisted = true;
7248
+ this.eventEmitter.emit(`run:${runId}`, event);
7249
+ return true;
7250
+ } finally {
7251
+ terminal.claiming = false;
7252
+ }
7253
+ };
6854
7254
  const onCaseComplete = async (result, suiteName) => {
7255
+ if (terminalUnavailable()) return;
6855
7256
  const passed = result.result?.pass ?? false;
6856
7257
  const score = result.result?.final_score ?? 0;
6857
- if (passed) stats.passed++;
6858
- else stats.failed++;
6859
- stats.totalScore += score;
6860
- if (result.interrupted) stats.interrupted++;
6861
- const completedCount = stats.passed + stats.failed;
6862
7258
  await store.createRunResult(tenantId, runId, uuidv43(), {
6863
7259
  suiteName,
6864
7260
  caseId: result.caseId,
@@ -6876,13 +7272,22 @@ var EvalRunner = class {
6876
7272
  error: result.error,
6877
7273
  interrupted: result.interrupted
6878
7274
  });
7275
+ if (terminalUnavailable()) return;
7276
+ if (passed) stats.passed++;
7277
+ else stats.failed++;
7278
+ stats.totalScore += score;
7279
+ if (result.interrupted) stats.interrupted++;
7280
+ const completedCount = stats.passed + stats.failed;
7281
+ if (terminalUnavailable()) return;
6879
7282
  const avgScore = completedCount > 0 ? stats.totalScore / completedCount : 0;
6880
- await store.updateRunStatus(tenantId, runId, {
7283
+ const persistedRun = await store.updateRunStatus(tenantId, runId, {
6881
7284
  passedCases: stats.passed,
6882
7285
  failedCases: stats.failed,
6883
7286
  interruptedCases: stats.interrupted,
6884
7287
  avgScore
6885
7288
  });
7289
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7290
+ if (terminalUnavailable()) return;
6886
7291
  this.eventEmitter.emit(`run:${runId}`, {
6887
7292
  type: "progress",
6888
7293
  runId,
@@ -6894,65 +7299,86 @@ var EvalRunner = class {
6894
7299
  const evalProject = new LatticeEvalProject(projectConfig, onCaseComplete);
6895
7300
  const calibration = await evalProject.calibrateJudge();
6896
7301
  if (!calibration.ok && !calibration.bypassed) {
6897
- await store.updateRunStatus(tenantId, runId, {
6898
- status: "failed",
6899
- error: `Judge calibration failed: ${calibration.reason}`,
6900
- completedAt: /* @__PURE__ */ new Date()
6901
- });
6902
7302
  throw new Error(`Judge calibration failed: ${calibration.reason}`);
6903
7303
  }
6904
7304
  if (calibration.bypassed) {
6905
7305
  console.warn(`[eval-runner] Judge calibration bypassed \u2014 judge may be unreliable: ${calibration.reason}`);
6906
7306
  }
6907
7307
  const { report } = await evalProject.runAllSuitesBatch(concurrency, abortController.signal);
6908
- if (!abortController.signal.aborted) {
6909
- const completedCount = stats.passed + stats.failed;
6910
- await store.updateRunStatus(tenantId, runId, {
6911
- status: "completed",
6912
- completedAt: /* @__PURE__ */ new Date(),
6913
- passedCases: stats.passed,
6914
- failedCases: stats.failed,
6915
- interruptedCases: stats.interrupted,
6916
- avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0
6917
- });
6918
- this.eventEmitter.emit(`run:${runId}`, {
6919
- type: "completed",
6920
- runId,
6921
- data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 }
6922
- });
6923
- }
7308
+ const completedCount = stats.passed + stats.failed;
7309
+ await persistTerminal({
7310
+ status: "completed",
7311
+ completedAt: /* @__PURE__ */ new Date(),
7312
+ passedCases: stats.passed,
7313
+ failedCases: stats.failed,
7314
+ interruptedCases: stats.interrupted,
7315
+ avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0
7316
+ }, {
7317
+ type: "completed",
7318
+ runId,
7319
+ data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 }
7320
+ });
6924
7321
  return { report };
6925
7322
  } catch (err) {
6926
7323
  const errorMsg = err.message;
6927
- if (!abortController.signal.aborted) {
6928
- await store.updateRunStatus(tenantId, runId, {
7324
+ try {
7325
+ await persistTerminal({
6929
7326
  status: "failed",
6930
7327
  error: errorMsg,
6931
7328
  completedAt: /* @__PURE__ */ new Date()
6932
- });
6933
- this.eventEmitter.emit(`run:${runId}`, {
7329
+ }, {
6934
7330
  type: "error",
6935
7331
  runId,
6936
7332
  data: { message: errorMsg }
6937
7333
  });
7334
+ } catch {
6938
7335
  }
6939
7336
  throw err;
6940
7337
  } finally {
6941
- this.runs.delete(runId);
7338
+ terminal.workerSettled = true;
7339
+ if (terminal.persisted) {
7340
+ this.runs.delete(runId);
7341
+ this.releaseProjectLease(leaseKey, leaseToken);
7342
+ }
6942
7343
  }
6943
7344
  })();
6944
- this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, taskId });
7345
+ this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, terminal, leaseKey, leaseToken, taskId });
6945
7346
  runPromise.catch(() => {
6946
7347
  });
6947
7348
  return runId;
6948
7349
  }
6949
- async abortRun(runId) {
7350
+ async abortRun(runId, tenantId) {
6950
7351
  const ctx = this.runs.get(runId);
6951
- if (!ctx) return false;
7352
+ if (!ctx || tenantId !== void 0 && ctx.tenantId !== tenantId) return false;
7353
+ if (ctx.terminal.claiming || ctx.terminal.persisted) return false;
6952
7354
  ctx.abortController.abort();
6953
7355
  const store = this.getEvalStore();
6954
- await store.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
6955
- return true;
7356
+ ctx.terminal.claiming = true;
7357
+ try {
7358
+ let lastError;
7359
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
7360
+ try {
7361
+ const persistedRun = await store.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
7362
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7363
+ lastError = void 0;
7364
+ break;
7365
+ } catch (error) {
7366
+ lastError = error;
7367
+ }
7368
+ }
7369
+ if (lastError) throw lastError;
7370
+ ctx.terminal.persisted = true;
7371
+ this.eventEmitter.emit(`run:${runId}`, {
7372
+ type: "error",
7373
+ runId,
7374
+ data: { message: "Run aborted" }
7375
+ });
7376
+ this.releaseProjectLease(ctx.leaseKey, ctx.leaseToken);
7377
+ if (ctx.terminal.workerSettled) this.runs.delete(runId);
7378
+ return true;
7379
+ } finally {
7380
+ ctx.terminal.claiming = false;
7381
+ }
6956
7382
  }
6957
7383
  async waitForRun(runId) {
6958
7384
  const ctx = this.runs.get(runId);
@@ -6960,12 +7386,25 @@ var EvalRunner = class {
6960
7386
  const { report } = await ctx.promise;
6961
7387
  return report;
6962
7388
  }
6963
- isRunning(runId) {
6964
- return this.runs.has(runId);
7389
+ /**
7390
+ * Whether the run can still be controlled or observed as a non-terminal run.
7391
+ * Acknowledged aborts return false even while their worker is unwinding.
7392
+ */
7393
+ isRunning(runId, tenantId) {
7394
+ const ctx = this.runs.get(runId);
7395
+ return ctx !== void 0 && !ctx.terminal.persisted && (tenantId === void 0 || ctx.tenantId === tenantId);
7396
+ }
7397
+ /** Whether the run's worker promise has not yet reached its finally block. */
7398
+ hasActiveExecution(runId, tenantId) {
7399
+ const ctx = this.runs.get(runId);
7400
+ return ctx !== void 0 && !ctx.terminal.workerSettled && (tenantId === void 0 || ctx.tenantId === tenantId);
6965
7401
  }
6966
7402
  getEvalStore() {
6967
7403
  return getStoreLattice9("default", "eval").store;
6968
7404
  }
7405
+ releaseProjectLease(leaseKey, leaseToken) {
7406
+ if (this.projectLeases.get(leaseKey) === leaseToken) this.projectLeases.delete(leaseKey);
7407
+ }
6969
7408
  };
6970
7409
  var evalRunner = new EvalRunner();
6971
7410
 
@@ -6980,6 +7419,18 @@ function getTenantId11(request) {
6980
7419
  function getEvalStore() {
6981
7420
  return getStoreLattice10("default", "eval").store;
6982
7421
  }
7422
+ async function getOwnedSuite(store, tenantId, projectId, suiteId) {
7423
+ const project = await store.getProjectById(tenantId, projectId);
7424
+ if (!project) return null;
7425
+ const suite = await store.getSuiteById(tenantId, suiteId);
7426
+ return suite?.projectId === projectId ? suite : null;
7427
+ }
7428
+ async function getOwnedCase(store, tenantId, projectId, suiteId, caseId) {
7429
+ const suite = await getOwnedSuite(store, tenantId, projectId, suiteId);
7430
+ if (!suite) return null;
7431
+ const evalCase = await store.getCaseById(tenantId, caseId);
7432
+ return evalCase?.suiteId === suiteId ? evalCase : null;
7433
+ }
6983
7434
  async function createProject(request, reply) {
6984
7435
  try {
6985
7436
  const tenantId = getTenantId11(request);
@@ -7135,6 +7586,7 @@ async function updateProject(request, reply) {
7135
7586
  }
7136
7587
  }
7137
7588
  async function deleteProject(request, reply) {
7589
+ let releaseDeletion;
7138
7590
  try {
7139
7591
  const tenantId = getTenantId11(request);
7140
7592
  const store = getEvalStore();
@@ -7143,6 +7595,23 @@ async function deleteProject(request, reply) {
7143
7595
  if (!existing) {
7144
7596
  return reply.status(404).send({ success: false, message: "Project not found" });
7145
7597
  }
7598
+ releaseDeletion = evalRunner.reserveProjectDeletion(tenantId, pid) ?? void 0;
7599
+ if (!releaseDeletion) {
7600
+ return reply.status(409).send({
7601
+ success: false,
7602
+ message: "Cannot delete an evaluation project with an active run"
7603
+ });
7604
+ }
7605
+ const runs = await store.getRunsByTenant(tenantId, { projectId: pid });
7606
+ const hasActiveRun = runs.some(
7607
+ (run) => run.status === "running" || evalRunner.hasActiveExecution(run.id, tenantId)
7608
+ );
7609
+ if (hasActiveRun) {
7610
+ return reply.status(409).send({
7611
+ success: false,
7612
+ message: "Cannot delete an evaluation project with an active run"
7613
+ });
7614
+ }
7146
7615
  const deleted = await store.deleteProject(tenantId, pid);
7147
7616
  if (!deleted) {
7148
7617
  return reply.status(500).send({ success: false, message: "Failed to delete project" });
@@ -7151,6 +7620,8 @@ async function deleteProject(request, reply) {
7151
7620
  } catch (err) {
7152
7621
  const message = err instanceof Error ? err.message : "Failed to delete project";
7153
7622
  return reply.status(500).send({ success: false, message });
7623
+ } finally {
7624
+ releaseDeletion?.();
7154
7625
  }
7155
7626
  }
7156
7627
  async function createSuite(request, reply) {
@@ -7179,8 +7650,8 @@ async function updateSuite(request, reply) {
7179
7650
  try {
7180
7651
  const tenantId = getTenantId11(request);
7181
7652
  const store = getEvalStore();
7182
- const { sid } = request.params;
7183
- const existing = await store.getSuiteById(tenantId, sid);
7653
+ const { pid, sid } = request.params;
7654
+ const existing = await getOwnedSuite(store, tenantId, pid, sid);
7184
7655
  if (!existing) {
7185
7656
  return reply.status(404).send({ success: false, message: "Suite not found" });
7186
7657
  }
@@ -7199,8 +7670,8 @@ async function deleteSuite(request, reply) {
7199
7670
  try {
7200
7671
  const tenantId = getTenantId11(request);
7201
7672
  const store = getEvalStore();
7202
- const { sid } = request.params;
7203
- const existing = await store.getSuiteById(tenantId, sid);
7673
+ const { pid, sid } = request.params;
7674
+ const existing = await getOwnedSuite(store, tenantId, pid, sid);
7204
7675
  if (!existing) {
7205
7676
  return reply.status(404).send({ success: false, message: "Suite not found" });
7206
7677
  }
@@ -7218,8 +7689,8 @@ async function createCase(request, reply) {
7218
7689
  try {
7219
7690
  const tenantId = getTenantId11(request);
7220
7691
  const store = getEvalStore();
7221
- const { sid } = request.params;
7222
- const suite = await store.getSuiteById(tenantId, sid);
7692
+ const { pid, sid } = request.params;
7693
+ const suite = await getOwnedSuite(store, tenantId, pid, sid);
7223
7694
  if (!suite) {
7224
7695
  return reply.status(404).send({ success: false, message: "Suite not found" });
7225
7696
  }
@@ -7248,7 +7719,11 @@ async function listCasesForSuite(request, reply) {
7248
7719
  try {
7249
7720
  const tenantId = getTenantId11(request);
7250
7721
  const store = getEvalStore();
7251
- const { sid } = request.params;
7722
+ const { pid, sid } = request.params;
7723
+ const suite = await getOwnedSuite(store, tenantId, pid, sid);
7724
+ if (!suite) {
7725
+ return reply.status(404).send({ success: false, message: "Suite not found" });
7726
+ }
7252
7727
  const cases = await store.getCasesBySuite(tenantId, sid);
7253
7728
  return {
7254
7729
  success: true,
@@ -7264,8 +7739,8 @@ async function updateCase(request, reply) {
7264
7739
  try {
7265
7740
  const tenantId = getTenantId11(request);
7266
7741
  const store = getEvalStore();
7267
- const { cid } = request.params;
7268
- const existing = await store.getCaseById(tenantId, cid);
7742
+ const { pid, sid, cid } = request.params;
7743
+ const existing = await getOwnedCase(store, tenantId, pid, sid, cid);
7269
7744
  if (!existing) {
7270
7745
  return reply.status(404).send({ success: false, message: "Case not found" });
7271
7746
  }
@@ -7284,8 +7759,8 @@ async function deleteCase(request, reply) {
7284
7759
  try {
7285
7760
  const tenantId = getTenantId11(request);
7286
7761
  const store = getEvalStore();
7287
- const { cid } = request.params;
7288
- const existing = await store.getCaseById(tenantId, cid);
7762
+ const { pid, sid, cid } = request.params;
7763
+ const existing = await getOwnedCase(store, tenantId, pid, sid, cid);
7289
7764
  if (!existing) {
7290
7765
  return reply.status(404).send({ success: false, message: "Case not found" });
7291
7766
  }
@@ -7354,6 +7829,11 @@ function registerEvalRoutes(app2) {
7354
7829
  }
7355
7830
  });
7356
7831
  app2.get("/api/eval/runs/:rid/stream", async (request, reply) => {
7832
+ const tenantId = getTenantId11(request);
7833
+ const { rid } = request.params;
7834
+ const store = getEvalStore();
7835
+ const ownedRun = await store.getRunById(tenantId, rid);
7836
+ if (!ownedRun) return reply.status(404).send({ success: false, message: "Run not found" });
7357
7837
  reply.hijack();
7358
7838
  reply.raw.writeHead(200, {
7359
7839
  "Content-Type": "text/event-stream",
@@ -7361,41 +7841,75 @@ function registerEvalRoutes(app2) {
7361
7841
  Connection: "keep-alive",
7362
7842
  "Access-Control-Allow-Origin": "*"
7363
7843
  });
7364
- const { rid } = request.params;
7365
7844
  const emitter = evalRunner.getEventEmitter();
7366
7845
  const eventKey = `run:${rid}`;
7367
- if (!evalRunner.isRunning(rid)) {
7368
- const tenantId = getTenantId11(request);
7369
- const run = await getEvalStore().getRunById(tenantId, rid);
7370
- if (run) {
7371
- const eventType = run.status === "completed" ? "completed" : "error";
7372
- reply.raw.write(`event: ${eventType}
7373
- data: ${JSON.stringify({ passed: run.passedCases, failed: run.failedCases, avgScore: run.avgScore })}
7846
+ let settled = false;
7847
+ const cleanup = () => {
7848
+ emitter.off(eventKey, handler);
7849
+ request.raw.off("close", disconnect);
7850
+ };
7851
+ const disconnect = () => {
7852
+ if (settled) return;
7853
+ settled = true;
7854
+ cleanup();
7855
+ };
7856
+ const finish = (type, data) => {
7857
+ if (settled) return;
7858
+ settled = true;
7859
+ cleanup();
7860
+ reply.raw.write(`event: ${type}
7861
+ data: ${JSON.stringify(data)}
7374
7862
 
7375
7863
  `);
7376
- }
7377
7864
  reply.raw.end();
7378
- return;
7379
- }
7865
+ };
7380
7866
  const handler = (event) => {
7381
- reply.raw.write(`event: ${event.type}
7867
+ if (event.type === "completed" || event.type === "error") {
7868
+ finish(event.type, event.data);
7869
+ return;
7870
+ }
7871
+ if (!settled) reply.raw.write(`event: ${event.type}
7382
7872
  data: ${JSON.stringify(event.data)}
7383
7873
 
7384
7874
  `);
7385
- if (event.type === "completed" || event.type === "error") {
7386
- emitter.off(eventKey, handler);
7387
- reply.raw.end();
7388
- }
7389
7875
  };
7390
7876
  emitter.on(eventKey, handler);
7391
- request.raw.on("close", () => {
7392
- emitter.off(eventKey, handler);
7393
- });
7877
+ request.raw.on("close", disconnect);
7878
+ let reconciledRun;
7879
+ try {
7880
+ reconciledRun = await store.getRunById(tenantId, rid);
7881
+ } catch (error) {
7882
+ finish("error", { message: error.message });
7883
+ return;
7884
+ }
7885
+ if (settled) return;
7886
+ if (!reconciledRun) {
7887
+ cleanup();
7888
+ reply.raw.end();
7889
+ return;
7890
+ }
7891
+ if (reconciledRun.status === "completed") {
7892
+ finish("completed", {
7893
+ passed: reconciledRun.passedCases,
7894
+ failed: reconciledRun.failedCases,
7895
+ avgScore: reconciledRun.avgScore
7896
+ });
7897
+ } else if (reconciledRun.status === "failed") {
7898
+ finish("error", { message: reconciledRun.error ?? "Evaluation failed" });
7899
+ } else if (reconciledRun.status === "aborted") {
7900
+ finish("error", { message: "Run aborted" });
7901
+ } else if (!evalRunner.isRunning(rid, tenantId)) {
7902
+ cleanup();
7903
+ reply.raw.end();
7904
+ }
7394
7905
  });
7395
7906
  app2.post("/api/eval/runs/:rid/abort", async (request, reply) => {
7396
7907
  try {
7908
+ const tenantId = getTenantId11(request);
7397
7909
  const { rid } = request.params;
7398
- const aborted = await evalRunner.abortRun(rid);
7910
+ const run = await getEvalStore().getRunById(tenantId, rid);
7911
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
7912
+ const aborted = await evalRunner.abortRun(rid, tenantId);
7399
7913
  if (!aborted) return reply.status(404).send({ success: false, message: "Run not found or not running" });
7400
7914
  reply.send({ success: true, message: "Run aborted" });
7401
7915
  } catch (err) {
@@ -7406,7 +7920,13 @@ data: ${JSON.stringify(event.data)}
7406
7920
  try {
7407
7921
  const tenantId = getTenantId11(request);
7408
7922
  const { rid } = request.params;
7409
- const deleted = await getEvalStore().deleteRun(tenantId, rid);
7923
+ const store = getEvalStore();
7924
+ const run = await store.getRunById(tenantId, rid);
7925
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
7926
+ if (run.status === "running" || evalRunner.hasActiveExecution(rid, tenantId)) {
7927
+ return reply.status(409).send({ success: false, message: "Cannot delete an active evaluation run" });
7928
+ }
7929
+ const deleted = await store.deleteRun(tenantId, rid);
7410
7930
  if (!deleted) return reply.status(404).send({ success: false, message: "Run not found" });
7411
7931
  reply.send({ success: true, message: "Run deleted" });
7412
7932
  } catch (err) {
@@ -8002,7 +8522,7 @@ function registerConsoleAuthHook(app2, options) {
8002
8522
  if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
8003
8523
  if (request.url.startsWith("/s/")) return;
8004
8524
  const path4 = request.url.split("?")[0];
8005
- if (path4.includes("/viewfile") || path4.includes("/downloadfile") || path4.includes("/uploadfile") || path4.startsWith("/f/") || path4 === "/f") {
8525
+ if (request.method === "GET" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/viewfile$/.test(path4) || request.method === "GET" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/downloadfile$/.test(path4) || request.method === "POST" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/uploadfile$/.test(path4) || path4.startsWith("/f/") || path4 === "/f") {
8006
8526
  return;
8007
8527
  }
8008
8528
  await reply.status(401).send({
@@ -9002,12 +9522,12 @@ async function handleBridgeProxy(request, reply) {
9002
9522
  };
9003
9523
  const timeout = 3e5;
9004
9524
  try {
9005
- const response = await new Promise((resolve2, reject) => {
9525
+ const response = await new Promise((resolve3, reject) => {
9006
9526
  const timer = setTimeout(() => {
9007
9527
  pending.delete(requestId);
9008
9528
  reject(new Error(`Bridge request timeout after ${timeout}ms`));
9009
9529
  }, timeout);
9010
- pending.set(requestId, { resolve: resolve2, reject, timer, agentId });
9530
+ pending.set(requestId, { resolve: resolve3, reject, timer, agentId });
9011
9531
  try {
9012
9532
  if (conn.ws.readyState !== conn.ws.OPEN) {
9013
9533
  pending.delete(requestId);
@@ -12794,7 +13314,7 @@ ${summary}`
12794
13314
  console.log(
12795
13315
  `\u5C06\u5728 ${delayMs}ms \u540E\u91CD\u8BD5\u4EFB\u52A1 (${nextRetryCount}/${maxRetries})`
12796
13316
  );
12797
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
13317
+ await new Promise((resolve3) => setTimeout(resolve3, delayMs));
12798
13318
  return handleAgentTask(taskRequest, nextRetryCount);
12799
13319
  }
12800
13320
  if (callback_event) {