@axiom-lattice/gateway 4.0.2 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));
@@ -1116,7 +1116,7 @@ var recoverRun = async (request, reply) => {
1116
1116
  const { assistantId, threadId } = request.params;
1117
1117
  const tenant_id = request.headers["x-tenant-id"];
1118
1118
  const workspace_id = request.headers["x-workspace-id"] || "default";
1119
- const project_id = request.headers["x-project-id"] || "default";
1119
+ const project_id = request.headers["x-project-id"];
1120
1120
  const agent = agentInstanceManager2.getAgent({
1121
1121
  assistant_id: assistantId,
1122
1122
  thread_id: threadId,
@@ -1124,8 +1124,7 @@ var recoverRun = async (request, reply) => {
1124
1124
  workspace_id,
1125
1125
  project_id
1126
1126
  });
1127
- await agent.resumeTask();
1128
- const status = await agent.getRunStatus();
1127
+ const { status } = await agent.resumeTask();
1129
1128
  reply.status(200).send({
1130
1129
  success: true,
1131
1130
  threadId,
@@ -2015,7 +2014,8 @@ async function getHealth(request, reply) {
2015
2014
  }
2016
2015
 
2017
2016
  // src/services/skill_service.ts
2018
- import { SandboxSkillStore } from "@axiom-lattice/core";
2017
+ import { SandboxSkillStore, PluginRegistry } from "@axiom-lattice/core";
2018
+ var PLUGIN_SKILL_SOURCE = "builtin-plugin";
2019
2019
  function getTenantId4(request) {
2020
2020
  return request.user?.tenantId || request.headers["x-tenant-id"] || "default";
2021
2021
  }
@@ -2031,28 +2031,63 @@ function buildContext(request) {
2031
2031
  projectId: getProjectId(request)
2032
2032
  };
2033
2033
  }
2034
+ function pluginSkillToSkill(item, tenantId, content) {
2035
+ return {
2036
+ id: item.id,
2037
+ tenantId,
2038
+ name: item.name,
2039
+ description: item.description,
2040
+ version: item.version,
2041
+ pluginType: item.pluginType,
2042
+ resourcePaths: item.resourcePaths,
2043
+ source: PLUGIN_SKILL_SOURCE,
2044
+ readOnly: true,
2045
+ content,
2046
+ createdAt: /* @__PURE__ */ new Date(0),
2047
+ updatedAt: /* @__PURE__ */ new Date(0)
2048
+ };
2049
+ }
2034
2050
  var SkillService = class {
2035
2051
  constructor() {
2036
2052
  this.store = new SandboxSkillStore();
2037
2053
  }
2038
2054
  async getAllSkills(request) {
2039
2055
  const tenantId = getTenantId4(request);
2040
- return this.store.getAllSkills(tenantId, buildContext(request));
2056
+ const storeSkills = await this.store.getAllSkills(tenantId, buildContext(request));
2057
+ const merged = /* @__PURE__ */ new Map();
2058
+ for (const skill of storeSkills) {
2059
+ merged.set(skill.id, skill);
2060
+ }
2061
+ for (const item of PluginRegistry.listSkills()) {
2062
+ merged.set(item.id, pluginSkillToSkill(item, tenantId));
2063
+ }
2064
+ return Array.from(merged.values());
2041
2065
  }
2042
2066
  async getSkillById(request, id) {
2043
2067
  const tenantId = getTenantId4(request);
2068
+ const plugin = PluginRegistry.getSkill(id);
2069
+ if (plugin) {
2070
+ const { definition } = plugin;
2071
+ const item = PluginRegistry.listSkills().find((entry) => entry.id === id);
2072
+ if (item) {
2073
+ return pluginSkillToSkill(item, tenantId, definition.content);
2074
+ }
2075
+ }
2044
2076
  return this.store.getSkillById(tenantId, id, buildContext(request));
2045
2077
  }
2046
2078
  async createSkill(request, id, data) {
2047
2079
  const tenantId = getTenantId4(request);
2080
+ this.assertNotPluginSkill(id, "created");
2048
2081
  return this.store.createSkill(tenantId, id, data, buildContext(request));
2049
2082
  }
2050
2083
  async updateSkill(request, id, updates) {
2051
2084
  const tenantId = getTenantId4(request);
2085
+ this.assertNotPluginSkill(id, "modified");
2052
2086
  return this.store.updateSkill(tenantId, id, updates, buildContext(request));
2053
2087
  }
2054
2088
  async deleteSkill(request, id) {
2055
2089
  const tenantId = getTenantId4(request);
2090
+ this.assertNotPluginSkill(id, "deleted");
2056
2091
  return this.store.deleteSkill(tenantId, id, buildContext(request));
2057
2092
  }
2058
2093
  async searchByMetadata(request, key, value) {
@@ -2067,6 +2102,16 @@ var SkillService = class {
2067
2102
  const tenantId = getTenantId4(request);
2068
2103
  return this.store.filterByLicense(tenantId, license, buildContext(request));
2069
2104
  }
2105
+ /**
2106
+ * Guard against mutating a registered plugin skill.
2107
+ * Message style mirrors SandboxSkillStore built-in errors so the controller's
2108
+ * existing "built-in skill" -> 403 mapping applies.
2109
+ */
2110
+ assertNotPluginSkill(id, action) {
2111
+ if (PluginRegistry.getSkill(id)) {
2112
+ throw new Error(`Skill "${id}" is a built-in skill and cannot be ${action}`);
2113
+ }
2114
+ }
2070
2115
  };
2071
2116
 
2072
2117
  // src/controllers/skills.ts
@@ -2081,6 +2126,11 @@ function serializeSkill(skill) {
2081
2126
  metadata: skill.metadata || {},
2082
2127
  content: skill.content,
2083
2128
  subSkills: skill.subSkills,
2129
+ source: skill.source,
2130
+ pluginType: skill.pluginType,
2131
+ version: skill.version,
2132
+ resources: skill.resourcePaths,
2133
+ readOnly: skill.readOnly,
2084
2134
  createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
2085
2135
  updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
2086
2136
  };
@@ -3712,7 +3762,7 @@ var ExportImportService = class {
3712
3762
  const files = /* @__PURE__ */ new Map();
3713
3763
  let bundle;
3714
3764
  const gunzip = zlib.createGunzip();
3715
- await new Promise((resolve2, reject) => {
3765
+ await new Promise((resolve3, reject) => {
3716
3766
  let settled = false;
3717
3767
  let entryCount = 0;
3718
3768
  let declaredTotalBytes = 0;
@@ -3832,7 +3882,7 @@ var ExportImportService = class {
3832
3882
  parser.on("end", () => {
3833
3883
  if (settled) return;
3834
3884
  settled = true;
3835
- resolve2();
3885
+ resolve3();
3836
3886
  });
3837
3887
  gunzip.pipe(parser);
3838
3888
  gunzip.end(buffer);
@@ -4439,10 +4489,10 @@ async function importApply(request, reply) {
4439
4489
  }
4440
4490
 
4441
4491
  // src/controllers/middleware-types.ts
4442
- import { PluginRegistry } from "@axiom-lattice/core";
4492
+ import { PluginRegistry as PluginRegistry2 } from "@axiom-lattice/core";
4443
4493
  function middlewareTypesRoutes(fastify2) {
4444
4494
  fastify2.get("/api/middleware-types", async (_request, reply) => {
4445
- const metas = PluginRegistry.listMeta();
4495
+ const metas = PluginRegistry2.listMeta();
4446
4496
  return reply.send(metas);
4447
4497
  });
4448
4498
  }
@@ -4517,6 +4567,20 @@ var dataQuerySchema = {
4517
4567
  };
4518
4568
 
4519
4569
  // src/schemas/index.ts
4570
+ var downloadFolderSchema = {
4571
+ description: "Download a workspace project folder as a ZIP archive",
4572
+ tags: ["Workspace"],
4573
+ querystring: {
4574
+ type: "object",
4575
+ properties: {
4576
+ path: { type: "string", minLength: 1 },
4577
+ assistantId: { type: "string", minLength: 1 },
4578
+ tenantId: { type: "string", minLength: 1 }
4579
+ },
4580
+ required: ["path"],
4581
+ additionalProperties: false
4582
+ }
4583
+ };
4520
4584
  var getAllMemoryItemsSchema = {
4521
4585
  description: "Get all memory items for an assistant thread",
4522
4586
  tags: ["Memory"],
@@ -5114,11 +5178,93 @@ function registerSandboxProxyRoutes(app2) {
5114
5178
  // src/controllers/workspace.ts
5115
5179
  import * as fs2 from "fs/promises";
5116
5180
  import * as path2 from "path";
5181
+ import { constants as fsConstants } from "fs";
5117
5182
  import * as os2 from "os";
5118
5183
  import { getStoreLattice as getStoreLattice6 } from "@axiom-lattice/core";
5119
5184
  import { SandboxFilesystem, FilesystemBackend } from "@axiom-lattice/core";
5120
5185
  import { getSandBoxManager as getSandBoxManager3 } from "@axiom-lattice/core";
5121
5186
  import { v4 as uuidv42 } from "uuid";
5187
+ var MAX_ZIP_ENTRIES = 1e4;
5188
+ var MAX_ZIP_FILE_SIZE = 100 * 1024 * 1024;
5189
+ var MAX_ZIP_TOTAL_SIZE = 512 * 1024 * 1024;
5190
+ var FolderDownloadError = class extends Error {
5191
+ constructor(statusCode, code, message) {
5192
+ super(message);
5193
+ this.statusCode = statusCode;
5194
+ this.code = code;
5195
+ this.name = "FolderDownloadError";
5196
+ }
5197
+ };
5198
+ function zipCrc32(data) {
5199
+ let crc = 4294967295;
5200
+ for (const byte of data) {
5201
+ crc ^= byte;
5202
+ for (let bit = 0; bit < 8; bit += 1) {
5203
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
5204
+ }
5205
+ }
5206
+ return (crc ^ 4294967295) >>> 0;
5207
+ }
5208
+ function createZip(entries) {
5209
+ const localParts = [];
5210
+ const centralParts = [];
5211
+ let offset = 0;
5212
+ for (const entry of entries) {
5213
+ const name = Buffer.from(entry.name, "utf8");
5214
+ const data = entry.data;
5215
+ const crc = zipCrc32(data);
5216
+ const local = Buffer.alloc(30 + name.length + data.length);
5217
+ local.writeUInt32LE(67324752, 0);
5218
+ local.writeUInt16LE(20, 4);
5219
+ local.writeUInt16LE(0, 6);
5220
+ local.writeUInt16LE(2048, 8);
5221
+ local.writeUInt16LE(0, 10);
5222
+ local.writeUInt16LE(0, 12);
5223
+ local.writeUInt32LE(crc, 14);
5224
+ local.writeUInt32LE(data.length, 18);
5225
+ local.writeUInt32LE(data.length, 22);
5226
+ local.writeUInt16LE(name.length, 26);
5227
+ local.writeUInt16LE(0, 28);
5228
+ name.copy(local, 30);
5229
+ data.copy(local, 30 + name.length);
5230
+ localParts.push(local);
5231
+ const central = Buffer.alloc(46 + name.length);
5232
+ central.writeUInt32LE(33639248, 0);
5233
+ central.writeUInt16LE(20, 4);
5234
+ central.writeUInt16LE(20, 6);
5235
+ central.writeUInt16LE(0, 8);
5236
+ central.writeUInt16LE(2048, 10);
5237
+ central.writeUInt16LE(0, 12);
5238
+ central.writeUInt16LE(0, 14);
5239
+ central.writeUInt32LE(crc, 16);
5240
+ central.writeUInt32LE(data.length, 20);
5241
+ central.writeUInt32LE(data.length, 24);
5242
+ central.writeUInt16LE(name.length, 28);
5243
+ central.writeUInt16LE(0, 30);
5244
+ central.writeUInt16LE(0, 32);
5245
+ central.writeUInt16LE(0, 34);
5246
+ central.writeUInt16LE(entry.directory ? 16 : 0, 36);
5247
+ central.writeUInt32LE(entry.directory ? 16 : 0, 38);
5248
+ central.writeUInt32LE(offset, 42);
5249
+ name.copy(central, 46);
5250
+ centralParts.push(central);
5251
+ offset += local.length;
5252
+ }
5253
+ const centralDirectory = Buffer.concat(centralParts);
5254
+ const end = Buffer.alloc(22);
5255
+ end.writeUInt32LE(101010256, 0);
5256
+ end.writeUInt16LE(0, 4);
5257
+ end.writeUInt16LE(0, 6);
5258
+ end.writeUInt16LE(entries.length, 8);
5259
+ end.writeUInt16LE(entries.length, 10);
5260
+ end.writeUInt32LE(centralDirectory.length, 12);
5261
+ end.writeUInt32LE(offset, 16);
5262
+ return Buffer.concat([...localParts, centralDirectory, end]);
5263
+ }
5264
+ var CAPABILITY_BUNDLE_CONFIG_MESSAGE = "Use /api/projects/:projectId/capability-bundles to update capability bundle IDs";
5265
+ function writesCapabilityBundleIds(config) {
5266
+ return config !== void 0 && Object.prototype.hasOwnProperty.call(config, "capabilityBundleIds");
5267
+ }
5122
5268
  var WorkspaceController = class {
5123
5269
  constructor() {
5124
5270
  this.workspaceStore = getStoreLattice6("default", "workspace").store;
@@ -5226,6 +5372,13 @@ var WorkspaceController = class {
5226
5372
  const tenantId = this.getTenantId(request);
5227
5373
  const { workspaceId } = request.params;
5228
5374
  const data = request.body;
5375
+ if (writesCapabilityBundleIds(data.config)) {
5376
+ return reply.status(400).send({
5377
+ success: false,
5378
+ code: "INVALID_BUNDLE_CONFIG",
5379
+ message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
5380
+ });
5381
+ }
5229
5382
  const id = uuidv42();
5230
5383
  const project = await this.projectStore.createProject(
5231
5384
  tenantId,
@@ -5248,6 +5401,13 @@ var WorkspaceController = class {
5248
5401
  const tenantId = this.getTenantId(request);
5249
5402
  const { projectId } = request.params;
5250
5403
  const updates = request.body;
5404
+ if (writesCapabilityBundleIds(updates.config)) {
5405
+ return reply.status(400).send({
5406
+ success: false,
5407
+ code: "INVALID_BUNDLE_CONFIG",
5408
+ message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
5409
+ });
5410
+ }
5251
5411
  const project = await this.projectStore.updateProject(
5252
5412
  tenantId,
5253
5413
  projectId,
@@ -5384,6 +5544,293 @@ var WorkspaceController = class {
5384
5544
  return reply.status(502).send({ success: false, error: `Download proxy error: ${message}` });
5385
5545
  }
5386
5546
  }
5547
+ async downloadFolder(request, reply) {
5548
+ const tenantId = this.getTenantId(request);
5549
+ const { workspaceId, projectId } = request.params;
5550
+ const folderPath = request.query.path;
5551
+ const assistantId = request.query.assistantId;
5552
+ if (!folderPath || folderPath.includes("\\") || folderPath.split("/").includes("..")) {
5553
+ return reply.status(400).send({
5554
+ success: false,
5555
+ error: "Invalid folder path",
5556
+ code: "INVALID_PATH"
5557
+ });
5558
+ }
5559
+ try {
5560
+ const storedWorkspace = await this.workspaceStore.getWorkspaceById(tenantId, workspaceId);
5561
+ if (!storedWorkspace) {
5562
+ throw new FolderDownloadError(404, "WORKSPACE_NOT_FOUND", "Workspace not found");
5563
+ }
5564
+ const project = await this.projectStore.getProjectById(tenantId, projectId);
5565
+ if (!project || project.workspaceId !== workspaceId) {
5566
+ throw new FolderDownloadError(404, "PROJECT_NOT_FOUND", "Project not found");
5567
+ }
5568
+ if (storedWorkspace.storageType === "sandbox") {
5569
+ const sandboxManager = getSandBoxManager3();
5570
+ const volumeConfig = {
5571
+ assistant_id: assistantId || "",
5572
+ thread_id: "",
5573
+ tenantId,
5574
+ workspaceId,
5575
+ projectId
5576
+ };
5577
+ const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
5578
+ if (!sandbox.packDirectory || !sandbox.file.deleteFile) {
5579
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder archive cleanup");
5580
+ }
5581
+ let packed;
5582
+ try {
5583
+ packed = await sandbox.packDirectory(folderPath);
5584
+ if (packed.size === void 0) {
5585
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not report packed archive size");
5586
+ }
5587
+ if (!Number.isFinite(packed.size) || packed.size < 0 || packed.size > MAX_ZIP_TOTAL_SIZE) {
5588
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Packed folder exceeds download size limits");
5589
+ }
5590
+ const buffer = await sandbox.file.downloadFile({ file: packed.path });
5591
+ return reply.status(200).type("application/zip").header("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(packed.filename)}`).send(buffer);
5592
+ } finally {
5593
+ if (packed) {
5594
+ try {
5595
+ await sandbox.file.deleteFile(packed.path);
5596
+ } catch (cleanupError) {
5597
+ console.warn("Failed to clean up packed sandbox folder archive", cleanupError);
5598
+ }
5599
+ }
5600
+ }
5601
+ }
5602
+ const { backend, workspace } = await this.getBackend(
5603
+ tenantId,
5604
+ workspaceId,
5605
+ projectId,
5606
+ assistantId,
5607
+ folderPath
5608
+ );
5609
+ if (!backend.lsInfo) {
5610
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder downloads");
5611
+ }
5612
+ let listEntries2;
5613
+ if (workspace.storageType !== "sandbox") {
5614
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
5615
+ const target = path2.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
5616
+ const contained = await resolvePathWithinRoot(root, target).catch((error) => {
5617
+ if (error instanceof Error && error.code === "ENOENT") {
5618
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5619
+ }
5620
+ throw error;
5621
+ });
5622
+ let stat2;
5623
+ try {
5624
+ stat2 = await fs2.stat(contained.targetPath);
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
+ if (!stat2.isDirectory()) {
5632
+ throw new FolderDownloadError(400, "NOT_A_FOLDER", "Path is not a folder");
5633
+ }
5634
+ } else if (folderPath) {
5635
+ const sandboxManager = getSandBoxManager3();
5636
+ const volumeConfig = {
5637
+ assistant_id: assistantId || "",
5638
+ thread_id: "",
5639
+ tenantId,
5640
+ workspaceId,
5641
+ projectId
5642
+ };
5643
+ const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, folderPath);
5644
+ if (!volume) {
5645
+ const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
5646
+ try {
5647
+ await sandbox.file.listPath(folderPath, { recursive: false });
5648
+ } catch (error) {
5649
+ const code = error instanceof Error ? error.code : void 0;
5650
+ const message = error instanceof Error ? error.message : String(error);
5651
+ if (code === "ENOENT" || /not found|no such file|enoent/i.test(message)) {
5652
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5653
+ }
5654
+ throw error;
5655
+ }
5656
+ listEntries2 = async (directoryPath) => {
5657
+ const result = await sandbox.file.listPath(directoryPath, { recursive: false });
5658
+ return result.files;
5659
+ };
5660
+ } else {
5661
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
5662
+ }
5663
+ }
5664
+ if (!listEntries2) {
5665
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
5666
+ }
5667
+ const folderName = path2.basename(folderPath.replace(/\/$/, "")) || "folder";
5668
+ const prefix = `${folderName}/`;
5669
+ const entries = [{ name: prefix, data: Buffer.alloc(0), directory: true }];
5670
+ let totalSize = 0;
5671
+ const filesystemRoot = workspace.storageType === "sandbox" ? void 0 : filesystemRootDirectory(tenantId, workspaceId, projectId);
5672
+ const addFile = (archivePath, data) => {
5673
+ if (data.length > MAX_ZIP_FILE_SIZE || totalSize + data.length > MAX_ZIP_TOTAL_SIZE) {
5674
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5675
+ }
5676
+ totalSize += data.length;
5677
+ entries.push({ name: archivePath, data, directory: false });
5678
+ };
5679
+ const collectFilesystem = async (directoryPath, archivePath) => {
5680
+ let children;
5681
+ try {
5682
+ children = await fs2.readdir(directoryPath, { withFileTypes: true });
5683
+ } catch (error) {
5684
+ if (error instanceof Error && error.code === "ENOENT") {
5685
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5686
+ }
5687
+ throw error;
5688
+ }
5689
+ for (const child of children) {
5690
+ if (entries.length >= MAX_ZIP_ENTRIES) {
5691
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
5692
+ }
5693
+ const childPath = path2.join(directoryPath, child.name);
5694
+ const archiveChildPath = `${archivePath}${child.name}`;
5695
+ let resolvedChild;
5696
+ try {
5697
+ resolvedChild = await resolvePathWithinRoot(filesystemRoot, childPath);
5698
+ } catch (error) {
5699
+ if (error instanceof Error && error.code === "ENOENT") {
5700
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5701
+ }
5702
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
5703
+ }
5704
+ let childStat;
5705
+ try {
5706
+ childStat = await fs2.lstat(resolvedChild.targetPath);
5707
+ } catch (error) {
5708
+ if (error instanceof Error && error.code === "ENOENT") {
5709
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5710
+ }
5711
+ throw error;
5712
+ }
5713
+ if (childStat.isSymbolicLink() || child.isSymbolicLink()) {
5714
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
5715
+ }
5716
+ if (child.isDirectory()) {
5717
+ entries.push({ name: `${archiveChildPath}/`, data: Buffer.alloc(0), directory: true });
5718
+ await collectFilesystem(resolvedChild.targetPath, `${archiveChildPath}/`);
5719
+ } else if (child.isFile()) {
5720
+ if (childStat.size > MAX_ZIP_FILE_SIZE || totalSize + childStat.size > MAX_ZIP_TOTAL_SIZE) {
5721
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5722
+ }
5723
+ const flags = fsConstants.O_NOFOLLOW === void 0 ? fsConstants.O_RDONLY : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
5724
+ let handle3;
5725
+ try {
5726
+ handle3 = await fs2.open(resolvedChild.targetPath, flags);
5727
+ } catch (error) {
5728
+ if (error instanceof Error && error.code === "ENOENT") {
5729
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5730
+ }
5731
+ throw error;
5732
+ }
5733
+ try {
5734
+ const openedStat = await handle3.stat();
5735
+ if (!openedStat.isFile() || openedStat.size > MAX_ZIP_FILE_SIZE || totalSize + openedStat.size > MAX_ZIP_TOTAL_SIZE) {
5736
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5737
+ }
5738
+ const readLimit = MAX_ZIP_FILE_SIZE + 1;
5739
+ const dataBuffer = Buffer.alloc(Math.min(openedStat.size + 1, readLimit));
5740
+ let bytesRead = 0;
5741
+ while (bytesRead < dataBuffer.length) {
5742
+ const result = await handle3.read(
5743
+ dataBuffer,
5744
+ bytesRead,
5745
+ dataBuffer.length - bytesRead,
5746
+ bytesRead
5747
+ );
5748
+ if (result.bytesRead === 0) break;
5749
+ bytesRead += result.bytesRead;
5750
+ }
5751
+ const data = dataBuffer.subarray(0, bytesRead);
5752
+ const finalStat = await handle3.stat();
5753
+ if (finalStat.size > MAX_ZIP_FILE_SIZE || totalSize + finalStat.size > MAX_ZIP_TOTAL_SIZE || finalStat.size > data.length) {
5754
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5755
+ }
5756
+ addFile(archiveChildPath, data);
5757
+ } finally {
5758
+ await handle3.close();
5759
+ }
5760
+ }
5761
+ }
5762
+ };
5763
+ const collect = async (currentPath, archivePath) => {
5764
+ let children;
5765
+ try {
5766
+ children = await listEntries2(currentPath);
5767
+ } catch (error) {
5768
+ const message = error instanceof Error ? error.message : String(error);
5769
+ if (/not found|enoent|no such file/i.test(message)) {
5770
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5771
+ }
5772
+ throw error;
5773
+ }
5774
+ for (const child of children) {
5775
+ if (entries.length >= MAX_ZIP_ENTRIES) {
5776
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
5777
+ }
5778
+ const childPath = child.path;
5779
+ if (childPath.includes("\\") || childPath.split("/").includes("..") || !childPath.startsWith(folderPath.endsWith("/") ? folderPath : `${folderPath}/`)) {
5780
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains an unsafe child path");
5781
+ }
5782
+ if (workspace.storageType !== "sandbox") {
5783
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
5784
+ const virtualChildPath = childPath.startsWith("/") ? childPath : `/${childPath}`;
5785
+ const target = path2.resolve(root, `.${virtualChildPath}`);
5786
+ try {
5787
+ await resolvePathWithinRoot(root, target);
5788
+ const childStat = await fs2.lstat(target);
5789
+ if (childStat.isSymbolicLink()) {
5790
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
5791
+ }
5792
+ } catch (error) {
5793
+ if (error instanceof FolderDownloadError) throw error;
5794
+ if (error instanceof Error && error.code === "ENOENT") {
5795
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
5796
+ }
5797
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
5798
+ }
5799
+ }
5800
+ const childName = path2.basename(childPath.replace(/\/$/, ""));
5801
+ const childArchivePath = `${archivePath}${childName}`;
5802
+ if (child.is_dir) {
5803
+ entries.push({ name: `${childArchivePath}/`, data: Buffer.alloc(0), directory: true });
5804
+ await collect(childPath, `${childArchivePath}/`);
5805
+ } else {
5806
+ const size = child.size ?? 0;
5807
+ if (size > MAX_ZIP_FILE_SIZE || totalSize + size > MAX_ZIP_TOTAL_SIZE) {
5808
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
5809
+ }
5810
+ const data = await backend.readBinary(childPath);
5811
+ addFile(childArchivePath, data);
5812
+ }
5813
+ }
5814
+ };
5815
+ if (workspace.storageType === "sandbox") {
5816
+ await collect(folderPath, prefix);
5817
+ } else {
5818
+ const root = filesystemRoot;
5819
+ const target = path2.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
5820
+ await collectFilesystem(target, prefix);
5821
+ }
5822
+ const zip = createZip(entries);
5823
+ const downloadName = `${folderName}.zip`;
5824
+ return reply.status(200).type("application/zip").header("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(downloadName)}`).send(zip);
5825
+ } catch (error) {
5826
+ 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));
5827
+ return reply.status(folderError.statusCode).send({
5828
+ success: false,
5829
+ error: folderError.message,
5830
+ code: folderError.code
5831
+ });
5832
+ }
5833
+ }
5387
5834
  /**
5388
5835
  * View a file inline in the browser (not download).
5389
5836
  * Returns file content with inline Content-Disposition for preview.
@@ -5676,6 +6123,11 @@ function registerWorkspaceRoutes(app2) {
5676
6123
  "/api/workspaces/:workspaceId/projects/:projectId/downloadfile",
5677
6124
  controller.downloadFile.bind(controller)
5678
6125
  );
6126
+ app2.get(
6127
+ "/api/workspaces/:workspaceId/projects/:projectId/downloadfolder",
6128
+ { schema: downloadFolderSchema },
6129
+ controller.downloadFolder.bind(controller)
6130
+ );
5679
6131
  app2.get(
5680
6132
  "/api/workspaces/:workspaceId/projects/:projectId/viewfile",
5681
6133
  controller.viewFile.bind(controller)
@@ -5903,7 +6355,7 @@ async function testDatabaseConnection(request, reply) {
5903
6355
  await db.connect();
5904
6356
  await db.listTables();
5905
6357
  const latency = Date.now() - startTime;
5906
- await new Promise((resolve2) => setTimeout(resolve2, 100));
6358
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
5907
6359
  await db.disconnect();
5908
6360
  await sqlDatabaseManager.removeDatabase(tenantId, testKey);
5909
6361
  return {
@@ -6556,7 +7008,7 @@ function registerMetricsServerConfigRoutes(app2) {
6556
7008
  }
6557
7009
 
6558
7010
  // src/controllers/connections.ts
6559
- import { ConnectionRegistry, PluginRegistry as PluginRegistry2 } from "@axiom-lattice/core";
7011
+ import { ConnectionRegistry, PluginRegistry as PluginRegistry3 } from "@axiom-lattice/core";
6560
7012
  import { z } from "zod";
6561
7013
  var createBody = z.object({
6562
7014
  type: z.string().min(1),
@@ -6659,7 +7111,7 @@ function connectionRoutes(fastify2) {
6659
7111
  fastify2.post("/api/connections/test", async (request, reply) => {
6660
7112
  const { type } = request.query;
6661
7113
  if (!type) return reply.status(400).send({ error: "query 'type' required" });
6662
- const plugin = PluginRegistry2.get(type);
7114
+ const plugin = PluginRegistry3.get(type);
6663
7115
  if (!plugin?.connection?.test) {
6664
7116
  return reply.status(400).send({ error: "plugin does not support connection testing" });
6665
7117
  }
@@ -6687,7 +7139,7 @@ function connectionRoutes(fastify2) {
6687
7139
  fastify2.post("/api/connections/discover", async (request, reply) => {
6688
7140
  const { type } = request.query;
6689
7141
  if (!type) return reply.status(400).send({ error: "query 'type' required" });
6690
- const plugin = PluginRegistry2.get(type);
7142
+ const plugin = PluginRegistry3.get(type);
6691
7143
  if (!plugin?.connection?.discover) {
6692
7144
  return reply.status(400).send({ error: "plugin does not support resource discovery" });
6693
7145
  }
@@ -6725,6 +7177,7 @@ import {
6725
7177
  LatticeEvalProject
6726
7178
  } from "@axiom-lattice/core";
6727
7179
  import { v4 as uuidv43 } from "uuid";
7180
+ var TERMINAL_PERSIST_ATTEMPTS = 2;
6728
7181
  function mapLogs(logs) {
6729
7182
  return logs.map((l) => ({
6730
7183
  timestamp: l.ts,
@@ -6736,18 +7189,42 @@ function mapLogs(logs) {
6736
7189
  var EvalRunner = class {
6737
7190
  constructor() {
6738
7191
  this.runs = /* @__PURE__ */ new Map();
7192
+ this.projectLeases = /* @__PURE__ */ new Map();
6739
7193
  this.eventEmitter = new EventEmitter();
6740
7194
  this.processStartedAt = /* @__PURE__ */ new Date();
6741
7195
  }
6742
7196
  getEventEmitter() {
6743
7197
  return this.eventEmitter;
6744
7198
  }
7199
+ reserveProjectDeletion(tenantId, projectId) {
7200
+ const leaseKey = `${tenantId}:${projectId}`;
7201
+ if (this.projectLeases.has(leaseKey) || [...this.runs.values()].some(
7202
+ (ctx) => ctx.tenantId === tenantId && ctx.projectId === projectId && !ctx.terminal.workerSettled
7203
+ )) return null;
7204
+ const leaseToken = Symbol(`delete:${leaseKey}`);
7205
+ this.projectLeases.set(leaseKey, leaseToken);
7206
+ return () => this.releaseProjectLease(leaseKey, leaseToken);
7207
+ }
6745
7208
  async startRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId) {
7209
+ const leaseKey = `${tenantId}:${projectId}`;
7210
+ if (this.projectLeases.has(leaseKey)) {
7211
+ throw new Error("A run is already in progress for this project");
7212
+ }
7213
+ const leaseToken = Symbol(leaseKey);
7214
+ this.projectLeases.set(leaseKey, leaseToken);
7215
+ try {
7216
+ return await this.startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken);
7217
+ } catch (error) {
7218
+ this.releaseProjectLease(leaseKey, leaseToken);
7219
+ throw error;
7220
+ }
7221
+ }
7222
+ async startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken) {
6746
7223
  const store = this.getEvalStore();
6747
7224
  const project = await store.getProjectById(tenantId, projectId);
6748
7225
  if (!project) throw new Error("Project not found");
6749
7226
  for (const ctx of this.runs.values()) {
6750
- if (ctx.projectId === projectId) {
7227
+ if (ctx.projectId === projectId && !ctx.terminal.persisted) {
6751
7228
  throw new Error("A run is already in progress for this project");
6752
7229
  }
6753
7230
  }
@@ -6851,14 +7328,35 @@ var EvalRunner = class {
6851
7328
  };
6852
7329
  const abortController = new AbortController();
6853
7330
  const stats = { passed: 0, failed: 0, interrupted: 0, totalScore: 0 };
7331
+ const terminal = { claiming: false, persisted: false, workerSettled: false };
7332
+ const terminalUnavailable = () => terminal.claiming || terminal.persisted;
7333
+ const persistTerminal = async (update, event) => {
7334
+ if (terminalUnavailable()) return false;
7335
+ terminal.claiming = true;
7336
+ try {
7337
+ let lastError;
7338
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
7339
+ try {
7340
+ const persistedRun = await store.updateRunStatus(tenantId, runId, update);
7341
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7342
+ lastError = void 0;
7343
+ break;
7344
+ } catch (error) {
7345
+ lastError = error;
7346
+ }
7347
+ }
7348
+ if (lastError) throw lastError;
7349
+ terminal.persisted = true;
7350
+ this.eventEmitter.emit(`run:${runId}`, event);
7351
+ return true;
7352
+ } finally {
7353
+ terminal.claiming = false;
7354
+ }
7355
+ };
6854
7356
  const onCaseComplete = async (result, suiteName) => {
7357
+ if (terminalUnavailable()) return;
6855
7358
  const passed = result.result?.pass ?? false;
6856
7359
  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
7360
  await store.createRunResult(tenantId, runId, uuidv43(), {
6863
7361
  suiteName,
6864
7362
  caseId: result.caseId,
@@ -6876,13 +7374,22 @@ var EvalRunner = class {
6876
7374
  error: result.error,
6877
7375
  interrupted: result.interrupted
6878
7376
  });
7377
+ if (terminalUnavailable()) return;
7378
+ if (passed) stats.passed++;
7379
+ else stats.failed++;
7380
+ stats.totalScore += score;
7381
+ if (result.interrupted) stats.interrupted++;
7382
+ const completedCount = stats.passed + stats.failed;
7383
+ if (terminalUnavailable()) return;
6879
7384
  const avgScore = completedCount > 0 ? stats.totalScore / completedCount : 0;
6880
- await store.updateRunStatus(tenantId, runId, {
7385
+ const persistedRun = await store.updateRunStatus(tenantId, runId, {
6881
7386
  passedCases: stats.passed,
6882
7387
  failedCases: stats.failed,
6883
7388
  interruptedCases: stats.interrupted,
6884
7389
  avgScore
6885
7390
  });
7391
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7392
+ if (terminalUnavailable()) return;
6886
7393
  this.eventEmitter.emit(`run:${runId}`, {
6887
7394
  type: "progress",
6888
7395
  runId,
@@ -6894,65 +7401,86 @@ var EvalRunner = class {
6894
7401
  const evalProject = new LatticeEvalProject(projectConfig, onCaseComplete);
6895
7402
  const calibration = await evalProject.calibrateJudge();
6896
7403
  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
7404
  throw new Error(`Judge calibration failed: ${calibration.reason}`);
6903
7405
  }
6904
7406
  if (calibration.bypassed) {
6905
7407
  console.warn(`[eval-runner] Judge calibration bypassed \u2014 judge may be unreliable: ${calibration.reason}`);
6906
7408
  }
6907
7409
  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
- }
7410
+ const completedCount = stats.passed + stats.failed;
7411
+ await persistTerminal({
7412
+ status: "completed",
7413
+ completedAt: /* @__PURE__ */ new Date(),
7414
+ passedCases: stats.passed,
7415
+ failedCases: stats.failed,
7416
+ interruptedCases: stats.interrupted,
7417
+ avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0
7418
+ }, {
7419
+ type: "completed",
7420
+ runId,
7421
+ data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 }
7422
+ });
6924
7423
  return { report };
6925
7424
  } catch (err) {
6926
7425
  const errorMsg = err.message;
6927
- if (!abortController.signal.aborted) {
6928
- await store.updateRunStatus(tenantId, runId, {
7426
+ try {
7427
+ await persistTerminal({
6929
7428
  status: "failed",
6930
7429
  error: errorMsg,
6931
7430
  completedAt: /* @__PURE__ */ new Date()
6932
- });
6933
- this.eventEmitter.emit(`run:${runId}`, {
7431
+ }, {
6934
7432
  type: "error",
6935
7433
  runId,
6936
7434
  data: { message: errorMsg }
6937
7435
  });
7436
+ } catch {
6938
7437
  }
6939
7438
  throw err;
6940
7439
  } finally {
6941
- this.runs.delete(runId);
7440
+ terminal.workerSettled = true;
7441
+ if (terminal.persisted) {
7442
+ this.runs.delete(runId);
7443
+ this.releaseProjectLease(leaseKey, leaseToken);
7444
+ }
6942
7445
  }
6943
7446
  })();
6944
- this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, taskId });
7447
+ this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, terminal, leaseKey, leaseToken, taskId });
6945
7448
  runPromise.catch(() => {
6946
7449
  });
6947
7450
  return runId;
6948
7451
  }
6949
- async abortRun(runId) {
7452
+ async abortRun(runId, tenantId) {
6950
7453
  const ctx = this.runs.get(runId);
6951
- if (!ctx) return false;
7454
+ if (!ctx || tenantId !== void 0 && ctx.tenantId !== tenantId) return false;
7455
+ if (ctx.terminal.claiming || ctx.terminal.persisted) return false;
6952
7456
  ctx.abortController.abort();
6953
7457
  const store = this.getEvalStore();
6954
- await store.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
6955
- return true;
7458
+ ctx.terminal.claiming = true;
7459
+ try {
7460
+ let lastError;
7461
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
7462
+ try {
7463
+ const persistedRun = await store.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
7464
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
7465
+ lastError = void 0;
7466
+ break;
7467
+ } catch (error) {
7468
+ lastError = error;
7469
+ }
7470
+ }
7471
+ if (lastError) throw lastError;
7472
+ ctx.terminal.persisted = true;
7473
+ this.eventEmitter.emit(`run:${runId}`, {
7474
+ type: "error",
7475
+ runId,
7476
+ data: { message: "Run aborted" }
7477
+ });
7478
+ this.releaseProjectLease(ctx.leaseKey, ctx.leaseToken);
7479
+ if (ctx.terminal.workerSettled) this.runs.delete(runId);
7480
+ return true;
7481
+ } finally {
7482
+ ctx.terminal.claiming = false;
7483
+ }
6956
7484
  }
6957
7485
  async waitForRun(runId) {
6958
7486
  const ctx = this.runs.get(runId);
@@ -6960,12 +7488,25 @@ var EvalRunner = class {
6960
7488
  const { report } = await ctx.promise;
6961
7489
  return report;
6962
7490
  }
6963
- isRunning(runId) {
6964
- return this.runs.has(runId);
7491
+ /**
7492
+ * Whether the run can still be controlled or observed as a non-terminal run.
7493
+ * Acknowledged aborts return false even while their worker is unwinding.
7494
+ */
7495
+ isRunning(runId, tenantId) {
7496
+ const ctx = this.runs.get(runId);
7497
+ return ctx !== void 0 && !ctx.terminal.persisted && (tenantId === void 0 || ctx.tenantId === tenantId);
7498
+ }
7499
+ /** Whether the run's worker promise has not yet reached its finally block. */
7500
+ hasActiveExecution(runId, tenantId) {
7501
+ const ctx = this.runs.get(runId);
7502
+ return ctx !== void 0 && !ctx.terminal.workerSettled && (tenantId === void 0 || ctx.tenantId === tenantId);
6965
7503
  }
6966
7504
  getEvalStore() {
6967
7505
  return getStoreLattice9("default", "eval").store;
6968
7506
  }
7507
+ releaseProjectLease(leaseKey, leaseToken) {
7508
+ if (this.projectLeases.get(leaseKey) === leaseToken) this.projectLeases.delete(leaseKey);
7509
+ }
6969
7510
  };
6970
7511
  var evalRunner = new EvalRunner();
6971
7512
 
@@ -6980,6 +7521,18 @@ function getTenantId11(request) {
6980
7521
  function getEvalStore() {
6981
7522
  return getStoreLattice10("default", "eval").store;
6982
7523
  }
7524
+ async function getOwnedSuite(store, tenantId, projectId, suiteId) {
7525
+ const project = await store.getProjectById(tenantId, projectId);
7526
+ if (!project) return null;
7527
+ const suite = await store.getSuiteById(tenantId, suiteId);
7528
+ return suite?.projectId === projectId ? suite : null;
7529
+ }
7530
+ async function getOwnedCase(store, tenantId, projectId, suiteId, caseId) {
7531
+ const suite = await getOwnedSuite(store, tenantId, projectId, suiteId);
7532
+ if (!suite) return null;
7533
+ const evalCase = await store.getCaseById(tenantId, caseId);
7534
+ return evalCase?.suiteId === suiteId ? evalCase : null;
7535
+ }
6983
7536
  async function createProject(request, reply) {
6984
7537
  try {
6985
7538
  const tenantId = getTenantId11(request);
@@ -7135,6 +7688,7 @@ async function updateProject(request, reply) {
7135
7688
  }
7136
7689
  }
7137
7690
  async function deleteProject(request, reply) {
7691
+ let releaseDeletion;
7138
7692
  try {
7139
7693
  const tenantId = getTenantId11(request);
7140
7694
  const store = getEvalStore();
@@ -7143,6 +7697,23 @@ async function deleteProject(request, reply) {
7143
7697
  if (!existing) {
7144
7698
  return reply.status(404).send({ success: false, message: "Project not found" });
7145
7699
  }
7700
+ releaseDeletion = evalRunner.reserveProjectDeletion(tenantId, pid) ?? void 0;
7701
+ if (!releaseDeletion) {
7702
+ return reply.status(409).send({
7703
+ success: false,
7704
+ message: "Cannot delete an evaluation project with an active run"
7705
+ });
7706
+ }
7707
+ const runs = await store.getRunsByTenant(tenantId, { projectId: pid });
7708
+ const hasActiveRun = runs.some(
7709
+ (run) => run.status === "running" || evalRunner.hasActiveExecution(run.id, tenantId)
7710
+ );
7711
+ if (hasActiveRun) {
7712
+ return reply.status(409).send({
7713
+ success: false,
7714
+ message: "Cannot delete an evaluation project with an active run"
7715
+ });
7716
+ }
7146
7717
  const deleted = await store.deleteProject(tenantId, pid);
7147
7718
  if (!deleted) {
7148
7719
  return reply.status(500).send({ success: false, message: "Failed to delete project" });
@@ -7151,6 +7722,8 @@ async function deleteProject(request, reply) {
7151
7722
  } catch (err) {
7152
7723
  const message = err instanceof Error ? err.message : "Failed to delete project";
7153
7724
  return reply.status(500).send({ success: false, message });
7725
+ } finally {
7726
+ releaseDeletion?.();
7154
7727
  }
7155
7728
  }
7156
7729
  async function createSuite(request, reply) {
@@ -7179,8 +7752,8 @@ async function updateSuite(request, reply) {
7179
7752
  try {
7180
7753
  const tenantId = getTenantId11(request);
7181
7754
  const store = getEvalStore();
7182
- const { sid } = request.params;
7183
- const existing = await store.getSuiteById(tenantId, sid);
7755
+ const { pid, sid } = request.params;
7756
+ const existing = await getOwnedSuite(store, tenantId, pid, sid);
7184
7757
  if (!existing) {
7185
7758
  return reply.status(404).send({ success: false, message: "Suite not found" });
7186
7759
  }
@@ -7199,8 +7772,8 @@ async function deleteSuite(request, reply) {
7199
7772
  try {
7200
7773
  const tenantId = getTenantId11(request);
7201
7774
  const store = getEvalStore();
7202
- const { sid } = request.params;
7203
- const existing = await store.getSuiteById(tenantId, sid);
7775
+ const { pid, sid } = request.params;
7776
+ const existing = await getOwnedSuite(store, tenantId, pid, sid);
7204
7777
  if (!existing) {
7205
7778
  return reply.status(404).send({ success: false, message: "Suite not found" });
7206
7779
  }
@@ -7218,8 +7791,8 @@ async function createCase(request, reply) {
7218
7791
  try {
7219
7792
  const tenantId = getTenantId11(request);
7220
7793
  const store = getEvalStore();
7221
- const { sid } = request.params;
7222
- const suite = await store.getSuiteById(tenantId, sid);
7794
+ const { pid, sid } = request.params;
7795
+ const suite = await getOwnedSuite(store, tenantId, pid, sid);
7223
7796
  if (!suite) {
7224
7797
  return reply.status(404).send({ success: false, message: "Suite not found" });
7225
7798
  }
@@ -7248,7 +7821,11 @@ async function listCasesForSuite(request, reply) {
7248
7821
  try {
7249
7822
  const tenantId = getTenantId11(request);
7250
7823
  const store = getEvalStore();
7251
- const { sid } = request.params;
7824
+ const { pid, sid } = request.params;
7825
+ const suite = await getOwnedSuite(store, tenantId, pid, sid);
7826
+ if (!suite) {
7827
+ return reply.status(404).send({ success: false, message: "Suite not found" });
7828
+ }
7252
7829
  const cases = await store.getCasesBySuite(tenantId, sid);
7253
7830
  return {
7254
7831
  success: true,
@@ -7264,8 +7841,8 @@ async function updateCase(request, reply) {
7264
7841
  try {
7265
7842
  const tenantId = getTenantId11(request);
7266
7843
  const store = getEvalStore();
7267
- const { cid } = request.params;
7268
- const existing = await store.getCaseById(tenantId, cid);
7844
+ const { pid, sid, cid } = request.params;
7845
+ const existing = await getOwnedCase(store, tenantId, pid, sid, cid);
7269
7846
  if (!existing) {
7270
7847
  return reply.status(404).send({ success: false, message: "Case not found" });
7271
7848
  }
@@ -7284,8 +7861,8 @@ async function deleteCase(request, reply) {
7284
7861
  try {
7285
7862
  const tenantId = getTenantId11(request);
7286
7863
  const store = getEvalStore();
7287
- const { cid } = request.params;
7288
- const existing = await store.getCaseById(tenantId, cid);
7864
+ const { pid, sid, cid } = request.params;
7865
+ const existing = await getOwnedCase(store, tenantId, pid, sid, cid);
7289
7866
  if (!existing) {
7290
7867
  return reply.status(404).send({ success: false, message: "Case not found" });
7291
7868
  }
@@ -7354,6 +7931,11 @@ function registerEvalRoutes(app2) {
7354
7931
  }
7355
7932
  });
7356
7933
  app2.get("/api/eval/runs/:rid/stream", async (request, reply) => {
7934
+ const tenantId = getTenantId11(request);
7935
+ const { rid } = request.params;
7936
+ const store = getEvalStore();
7937
+ const ownedRun = await store.getRunById(tenantId, rid);
7938
+ if (!ownedRun) return reply.status(404).send({ success: false, message: "Run not found" });
7357
7939
  reply.hijack();
7358
7940
  reply.raw.writeHead(200, {
7359
7941
  "Content-Type": "text/event-stream",
@@ -7361,41 +7943,75 @@ function registerEvalRoutes(app2) {
7361
7943
  Connection: "keep-alive",
7362
7944
  "Access-Control-Allow-Origin": "*"
7363
7945
  });
7364
- const { rid } = request.params;
7365
7946
  const emitter = evalRunner.getEventEmitter();
7366
7947
  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 })}
7948
+ let settled = false;
7949
+ const cleanup = () => {
7950
+ emitter.off(eventKey, handler);
7951
+ request.raw.off("close", disconnect);
7952
+ };
7953
+ const disconnect = () => {
7954
+ if (settled) return;
7955
+ settled = true;
7956
+ cleanup();
7957
+ };
7958
+ const finish = (type, data) => {
7959
+ if (settled) return;
7960
+ settled = true;
7961
+ cleanup();
7962
+ reply.raw.write(`event: ${type}
7963
+ data: ${JSON.stringify(data)}
7374
7964
 
7375
7965
  `);
7376
- }
7377
7966
  reply.raw.end();
7378
- return;
7379
- }
7967
+ };
7380
7968
  const handler = (event) => {
7381
- reply.raw.write(`event: ${event.type}
7969
+ if (event.type === "completed" || event.type === "error") {
7970
+ finish(event.type, event.data);
7971
+ return;
7972
+ }
7973
+ if (!settled) reply.raw.write(`event: ${event.type}
7382
7974
  data: ${JSON.stringify(event.data)}
7383
7975
 
7384
7976
  `);
7385
- if (event.type === "completed" || event.type === "error") {
7386
- emitter.off(eventKey, handler);
7387
- reply.raw.end();
7388
- }
7389
7977
  };
7390
7978
  emitter.on(eventKey, handler);
7391
- request.raw.on("close", () => {
7392
- emitter.off(eventKey, handler);
7393
- });
7979
+ request.raw.on("close", disconnect);
7980
+ let reconciledRun;
7981
+ try {
7982
+ reconciledRun = await store.getRunById(tenantId, rid);
7983
+ } catch (error) {
7984
+ finish("error", { message: error.message });
7985
+ return;
7986
+ }
7987
+ if (settled) return;
7988
+ if (!reconciledRun) {
7989
+ cleanup();
7990
+ reply.raw.end();
7991
+ return;
7992
+ }
7993
+ if (reconciledRun.status === "completed") {
7994
+ finish("completed", {
7995
+ passed: reconciledRun.passedCases,
7996
+ failed: reconciledRun.failedCases,
7997
+ avgScore: reconciledRun.avgScore
7998
+ });
7999
+ } else if (reconciledRun.status === "failed") {
8000
+ finish("error", { message: reconciledRun.error ?? "Evaluation failed" });
8001
+ } else if (reconciledRun.status === "aborted") {
8002
+ finish("error", { message: "Run aborted" });
8003
+ } else if (!evalRunner.isRunning(rid, tenantId)) {
8004
+ cleanup();
8005
+ reply.raw.end();
8006
+ }
7394
8007
  });
7395
8008
  app2.post("/api/eval/runs/:rid/abort", async (request, reply) => {
7396
8009
  try {
8010
+ const tenantId = getTenantId11(request);
7397
8011
  const { rid } = request.params;
7398
- const aborted = await evalRunner.abortRun(rid);
8012
+ const run = await getEvalStore().getRunById(tenantId, rid);
8013
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
8014
+ const aborted = await evalRunner.abortRun(rid, tenantId);
7399
8015
  if (!aborted) return reply.status(404).send({ success: false, message: "Run not found or not running" });
7400
8016
  reply.send({ success: true, message: "Run aborted" });
7401
8017
  } catch (err) {
@@ -7406,7 +8022,13 @@ data: ${JSON.stringify(event.data)}
7406
8022
  try {
7407
8023
  const tenantId = getTenantId11(request);
7408
8024
  const { rid } = request.params;
7409
- const deleted = await getEvalStore().deleteRun(tenantId, rid);
8025
+ const store = getEvalStore();
8026
+ const run = await store.getRunById(tenantId, rid);
8027
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
8028
+ if (run.status === "running" || evalRunner.hasActiveExecution(rid, tenantId)) {
8029
+ return reply.status(409).send({ success: false, message: "Cannot delete an active evaluation run" });
8030
+ }
8031
+ const deleted = await store.deleteRun(tenantId, rid);
7410
8032
  if (!deleted) return reply.status(404).send({ success: false, message: "Run not found" });
7411
8033
  reply.send({ success: true, message: "Run deleted" });
7412
8034
  } catch (err) {
@@ -7523,31 +8145,31 @@ var TenantsController = class {
7523
8145
  async createTenant(request, reply) {
7524
8146
  const data = request.body;
7525
8147
  const id = uuidv46();
7526
- const tenant = await this.tenantStore.createTenant(id, data);
7527
- return reply.status(201).send({ success: true, data: tenant });
8148
+ const tenant2 = await this.tenantStore.createTenant(id, data);
8149
+ return reply.status(201).send({ success: true, data: tenant2 });
7528
8150
  }
7529
8151
  async getTenant(request, reply) {
7530
8152
  const { tenantId } = request.params;
7531
- const tenant = await this.tenantStore.getTenantById(tenantId);
7532
- if (!tenant) {
8153
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
8154
+ if (!tenant2) {
7533
8155
  return reply.status(404).send({
7534
8156
  success: false,
7535
8157
  error: "Tenant not found"
7536
8158
  });
7537
8159
  }
7538
- return { success: true, data: tenant };
8160
+ return { success: true, data: tenant2 };
7539
8161
  }
7540
8162
  async updateTenant(request, reply) {
7541
8163
  const { tenantId } = request.params;
7542
8164
  const updates = request.body;
7543
- const tenant = await this.tenantStore.updateTenant(tenantId, updates);
7544
- if (!tenant) {
8165
+ const tenant2 = await this.tenantStore.updateTenant(tenantId, updates);
8166
+ if (!tenant2) {
7545
8167
  return reply.status(404).send({
7546
8168
  success: false,
7547
8169
  error: "Tenant not found"
7548
8170
  });
7549
8171
  }
7550
- return { success: true, data: tenant };
8172
+ return { success: true, data: tenant2 };
7551
8173
  }
7552
8174
  async deleteTenant(request, reply) {
7553
8175
  const { tenantId } = request.params;
@@ -7700,10 +8322,10 @@ var AuthController = class {
7700
8322
  const links = await this.userTenantLinkStore.getTenantsByUser(userId);
7701
8323
  const tenantsWithDetails = await Promise.all(
7702
8324
  links.map(async (link) => {
7703
- const tenant = await this.tenantStore.getTenantById(link.tenantId);
8325
+ const tenant2 = await this.tenantStore.getTenantById(link.tenantId);
7704
8326
  return {
7705
8327
  ...link,
7706
- tenant: tenant || null
8328
+ tenant: tenant2 || null
7707
8329
  };
7708
8330
  })
7709
8331
  );
@@ -7736,8 +8358,8 @@ var AuthController = class {
7736
8358
  error: "You do not have access to this tenant"
7737
8359
  });
7738
8360
  }
7739
- const tenant = await this.tenantStore.getTenantById(tenantId);
7740
- if (!tenant) {
8361
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
8362
+ if (!tenant2) {
7741
8363
  return reply.status(404).send({
7742
8364
  success: false,
7743
8365
  error: "Tenant not found"
@@ -7761,7 +8383,7 @@ var AuthController = class {
7761
8383
  return {
7762
8384
  success: true,
7763
8385
  data: {
7764
- tenant,
8386
+ tenant: tenant2,
7765
8387
  token,
7766
8388
  personalAssistant
7767
8389
  }
@@ -7831,8 +8453,8 @@ var AuthController = class {
7831
8453
  error: "User not found"
7832
8454
  });
7833
8455
  }
7834
- const tenant = await this.tenantStore.getTenantById(tenantId);
7835
- if (!tenant) {
8456
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
8457
+ if (!tenant2) {
7836
8458
  return reply.status(404).send({
7837
8459
  success: false,
7838
8460
  error: "Tenant not found"
@@ -8002,7 +8624,7 @@ function registerConsoleAuthHook(app2, options) {
8002
8624
  if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
8003
8625
  if (request.url.startsWith("/s/")) return;
8004
8626
  const path4 = request.url.split("?")[0];
8005
- if (path4.includes("/viewfile") || path4.includes("/downloadfile") || path4.includes("/uploadfile") || path4.startsWith("/f/") || path4 === "/f") {
8627
+ 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
8628
  return;
8007
8629
  }
8008
8630
  await reply.status(401).send({
@@ -9002,12 +9624,12 @@ async function handleBridgeProxy(request, reply) {
9002
9624
  };
9003
9625
  const timeout = 3e5;
9004
9626
  try {
9005
- const response = await new Promise((resolve2, reject) => {
9627
+ const response = await new Promise((resolve3, reject) => {
9006
9628
  const timer = setTimeout(() => {
9007
9629
  pending.delete(requestId);
9008
9630
  reject(new Error(`Bridge request timeout after ${timeout}ms`));
9009
9631
  }, timeout);
9010
- pending.set(requestId, { resolve: resolve2, reject, timer, agentId });
9632
+ pending.set(requestId, { resolve: resolve3, reject, timer, agentId });
9011
9633
  try {
9012
9634
  if (conn.ws.readyState !== conn.ws.OPEN) {
9013
9635
  pending.delete(requestId);
@@ -10639,11 +11261,365 @@ function isAttachmentPayload(value) {
10639
11261
  return typeof record.webAppId === "string" && typeof record.userDigest === "string" && /^[a-f0-9]{64}$/.test(record.userDigest) && typeof record.threadId === "string" && typeof record.projectId === "string" && typeof record.uri === "string" && record.uri.startsWith("/") && typeof record.name === "string" && record.name.length > 0 && typeof record.mimeType === "string" && record.mimeType.length > 0 && typeof record.size === "number" && Number.isSafeInteger(record.size) && record.size >= 0 && typeof record.contentDigest === "string" && /^[a-f0-9]{64}$/.test(record.contentDigest) && typeof record.exp === "number" && Number.isSafeInteger(record.exp);
10640
11262
  }
10641
11263
 
11264
+ // src/controllers/capability-bundles.ts
11265
+ import { z as z6 } from "zod";
11266
+
11267
+ // src/services/capability/CapabilityBundleService.ts
11268
+ import { z as z5 } from "zod";
11269
+ import { CapabilityBundleMergeError, CapabilityBundleValidationError, mergeCapabilityBundles, validateCapabilityBundleInput } from "@axiom-lattice/core";
11270
+ var CapabilityBundleServiceError = class extends Error {
11271
+ constructor(code, message, statusCode) {
11272
+ super(message);
11273
+ this.code = code;
11274
+ this.statusCode = statusCode;
11275
+ this.name = "CapabilityBundleServiceError";
11276
+ }
11277
+ };
11278
+ var uuid = z5.string().uuid();
11279
+ function ownRecord(value) {
11280
+ if (value === void 0 || value === null) return {};
11281
+ if (typeof value !== "object" || Array.isArray(value)) throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Project config must be an object", 409);
11282
+ const result = {};
11283
+ for (const [key, entry] of Object.entries(value)) {
11284
+ if (key !== "__proto__" && key !== "constructor" && key !== "prototype") result[key] = entry;
11285
+ }
11286
+ return result;
11287
+ }
11288
+ function isDuplicate(error) {
11289
+ return error instanceof Error && /already exists|unique|duplicate/i.test(error.message);
11290
+ }
11291
+ function rejectStableKey(input) {
11292
+ if (input !== null && typeof input === "object" && !Array.isArray(input) && Object.prototype.hasOwnProperty.call(input, "key")) {
11293
+ throw new CapabilityBundleServiceError("INVALID_INPUT", "Capability bundle key is generated and cannot be supplied or changed", 400);
11294
+ }
11295
+ }
11296
+ function slugify(name) {
11297
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11298
+ return slug || "runtime-profile";
11299
+ }
11300
+ function projectBundleIds(config) {
11301
+ const record = ownRecord(config);
11302
+ if (!Object.prototype.hasOwnProperty.call(record, "capabilityBundleIds")) return [];
11303
+ const value = record.capabilityBundleIds;
11304
+ if (!Array.isArray(value) || value.some((id) => typeof id !== "string" || !uuid.safeParse(id).success) || new Set(value).size !== value.length) {
11305
+ throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Project capabilityBundleIds must be a unique array of UUIDs", 409);
11306
+ }
11307
+ return [...value];
11308
+ }
11309
+ function issue(error) {
11310
+ if (error instanceof CapabilityBundleMergeError || error instanceof CapabilityBundleValidationError) return { code: error instanceof CapabilityBundleValidationError && error.code === "CAPABILITY_BUNDLE_INVALID_CONFIG" ? "INVALID_BUNDLE_CONFIG" : error.code, message: error.message, bundleId: error instanceof CapabilityBundleMergeError ? error.bundleId : void 0, capabilityType: error.capabilityType, fieldPath: error.fieldPath };
11311
+ return { code: "CAPABILITY_BUNDLE_CONFIGURATION_ERROR", message: error instanceof Error ? error.message : "Invalid capability bundle configuration" };
11312
+ }
11313
+ var CapabilityBundleService = class {
11314
+ constructor(deps) {
11315
+ this.deps = deps;
11316
+ }
11317
+ async list(tenantId) {
11318
+ const values = await this.deps.bundleStore.listByTenant(tenantId);
11319
+ const bundles = [];
11320
+ const errors = [];
11321
+ for (const value of values) {
11322
+ try {
11323
+ validateCapabilityBundleInput(value);
11324
+ bundles.push(value);
11325
+ } catch {
11326
+ errors.push({ bundleId: value.id, code: "INVALID_BUNDLE_CONFIG", message: "Capability bundle record is invalid" });
11327
+ }
11328
+ }
11329
+ return { bundles, errors };
11330
+ }
11331
+ async get(tenantId, id) {
11332
+ const value = await this.deps.bundleStore.getById(tenantId, id);
11333
+ if (!value) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
11334
+ try {
11335
+ validateCapabilityBundleInput(value);
11336
+ } catch {
11337
+ throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Capability bundle record is invalid", 422);
11338
+ }
11339
+ return value;
11340
+ }
11341
+ async create(tenantId, input) {
11342
+ rejectStableKey(input);
11343
+ validateCapabilityBundleInput(input);
11344
+ const value = input;
11345
+ const name = value.name.trim();
11346
+ const base = slugify(name);
11347
+ for (let attempt = 0; attempt < 5; attempt += 1) {
11348
+ const existing = await this.deps.bundleStore.listByTenant(tenantId) ?? [];
11349
+ const keys = new Set(existing.map((bundle) => bundle.key));
11350
+ let key = base;
11351
+ let suffix = 2;
11352
+ while (keys.has(key)) key = `${base}-${suffix++}`;
11353
+ const normalized = { ...value, key, name };
11354
+ validateCapabilityBundleInput(normalized);
11355
+ try {
11356
+ const result = await this.deps.bundleStore.create(tenantId, normalized);
11357
+ validateCapabilityBundleInput(result);
11358
+ return result;
11359
+ } catch (error) {
11360
+ if (!isDuplicate(error)) throw error;
11361
+ if (attempt === 4) throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
11362
+ }
11363
+ }
11364
+ throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
11365
+ }
11366
+ async update(tenantId, id, input) {
11367
+ rejectStableKey(input);
11368
+ validateCapabilityBundleInput(input, { partial: true });
11369
+ if (!input || typeof input !== "object" || Array.isArray(input) || typeof input.expectedUpdatedAt !== "string" || !input.expectedUpdatedAt.trim()) throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "expectedUpdatedAt is required for capability bundle updates", 400);
11370
+ const value = input;
11371
+ const normalized = { ...value, ...value.name === void 0 ? {} : { name: value.name.trim() }, ...Object.prototype.hasOwnProperty.call(value, "description") ? { description: value.description?.trim() || void 0 } : {} };
11372
+ validateCapabilityBundleInput(normalized, { partial: true });
11373
+ try {
11374
+ const result = await this.deps.bundleStore.update(tenantId, id, normalized);
11375
+ if (!result) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
11376
+ if ("status" in result && result.status === "conflict") throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Capability bundle changed after it was loaded; reload before saving", 409);
11377
+ validateCapabilityBundleInput(result);
11378
+ return result;
11379
+ } catch (error) {
11380
+ if (error instanceof CapabilityBundleServiceError) throw error;
11381
+ if (isDuplicate(error)) throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
11382
+ throw error;
11383
+ }
11384
+ }
11385
+ async remove(tenantId, id) {
11386
+ const result = await this.deps.bundleStore.deleteIfUnreferenced(tenantId, id);
11387
+ if (result === "not_found") throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
11388
+ if (result === "in_use") throw new CapabilityBundleServiceError("BUNDLE_IN_USE", "Capability bundle is in use", 409);
11389
+ }
11390
+ async getProjectBundles(tenantId, projectId) {
11391
+ const project = await this.deps.projectStore.getProjectById(tenantId, projectId);
11392
+ if (!project) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
11393
+ const ids = projectBundleIds(project.config);
11394
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
11395
+ return this.compose(project, ids, stored);
11396
+ }
11397
+ async previewProject(tenantId, projectId) {
11398
+ const project = await this.deps.projectStore.getProjectById(tenantId, projectId);
11399
+ if (!project) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
11400
+ let ids;
11401
+ try {
11402
+ ids = projectBundleIds(project.config);
11403
+ } catch (error) {
11404
+ return { bundleIds: [], capabilities: [], overrides: [], sources: [], warnings: [], errors: [{ code: "INVALID_BUNDLE_CONFIG", message: error instanceof Error ? error.message : "Invalid project capability configuration" }], revision: "" };
11405
+ }
11406
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
11407
+ const byId = new Map(stored.map((value) => [value.id, value]));
11408
+ const missing = ids.filter((id) => !byId.has(id));
11409
+ if (missing.length > 0) return { bundleIds: ids, capabilities: [], overrides: [], sources: [], warnings: [], errors: missing.map((id) => ({ code: "BUNDLE_NOT_FOUND", message: `Persisted capability bundle '${id}' is missing`, bundleId: id })), revision: "" };
11410
+ try {
11411
+ const ordered = ids.map((id) => byId.get(id));
11412
+ ordered.forEach((bundle) => validateCapabilityBundleInput(bundle));
11413
+ return mergeCapabilityBundles(ordered);
11414
+ } catch (error) {
11415
+ return { bundleIds: ids, capabilities: [], overrides: [], sources: [], warnings: [], errors: [issue(error)], revision: "" };
11416
+ }
11417
+ }
11418
+ async project(tenantId, projectId, ids, persist, expectedRevisions) {
11419
+ const current = await this.deps.projectStore.getProjectById(tenantId, projectId);
11420
+ if (!current) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
11421
+ if (new Set(ids).size !== ids.length) throw new CapabilityBundleServiceError("DUPLICATE_PROJECT_BUNDLE", "Duplicate capability bundle ID", 400);
11422
+ if (persist) this.assertExpectedRevisions(ids, expectedRevisions);
11423
+ const ordered = await this.loadOrderedBundles(tenantId, ids);
11424
+ let resultProject = current;
11425
+ if (persist) {
11426
+ projectBundleIds(current.config);
11427
+ const update = await this.deps.projectStore.updateCapabilityBundleIds(tenantId, projectId, ids, expectedRevisions);
11428
+ if (update.status === "project_not_found") throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
11429
+ if (update.status === "bundle_not_found") throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
11430
+ if (update.status === "bundle_conflict") throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Capability bundle changed after preview; preview the current bundle contents before saving", 409);
11431
+ resultProject = update.project;
11432
+ }
11433
+ return this.compose(resultProject, ids, ordered);
11434
+ }
11435
+ assertExpectedRevisions(ids, revisions) {
11436
+ if (!revisions || Object.keys(revisions).length !== ids.length || ids.some((id) => typeof revisions[id] !== "string" || !revisions[id].trim()) || Object.keys(revisions).some((id) => !ids.includes(id))) {
11437
+ throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Expected revisions must exactly match the selected capability bundles", 409);
11438
+ }
11439
+ }
11440
+ async loadOrderedBundles(tenantId, ids) {
11441
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
11442
+ const byId = new Map(stored.map((value) => [value.id, value]));
11443
+ return ids.map((id) => {
11444
+ const value = byId.get(id);
11445
+ if (!value) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
11446
+ validateCapabilityBundleInput(value);
11447
+ return value;
11448
+ });
11449
+ }
11450
+ compose(project, ids, bundles) {
11451
+ const valid = [];
11452
+ const errors = [];
11453
+ const byId = new Map(bundles.map((bundle) => [bundle.id, bundle]));
11454
+ for (const id of ids) {
11455
+ const bundle = byId.get(id);
11456
+ if (!bundle) {
11457
+ errors.push({ code: "BUNDLE_NOT_FOUND", message: `Persisted capability bundle '${id}' is missing`, bundleId: id });
11458
+ continue;
11459
+ }
11460
+ try {
11461
+ validateCapabilityBundleInput(bundle);
11462
+ valid.push(bundle);
11463
+ } catch (error) {
11464
+ errors.push({ ...issue(error), bundleId: id });
11465
+ }
11466
+ }
11467
+ let preview;
11468
+ try {
11469
+ preview = mergeCapabilityBundles(valid);
11470
+ } catch (error) {
11471
+ preview = { bundleIds: ids, capabilities: [], overrides: [], sources: [], warnings: [], errors: [issue(error)], revision: "" };
11472
+ }
11473
+ preview = { ...preview, bundleIds: ids, errors: [...errors, ...preview.errors] };
11474
+ return { project, persistedBundleIds: ids, bundles: valid, preview };
11475
+ }
11476
+ };
11477
+
11478
+ // src/controllers/capability-bundles.ts
11479
+ var logger3 = new Logger({ serviceName: "lattice/gateway/capability-bundles" });
11480
+ var capability = z6.unknown();
11481
+ var createSchema = z6.object({ name: z6.string(), description: z6.string().optional(), capabilities: z6.array(capability) }).strict();
11482
+ var updateSchema = createSchema.partial().extend({ expectedUpdatedAt: z6.string().optional() });
11483
+ var bundleIdSchema = z6.string().uuid();
11484
+ var bundleParamsSchema = z6.object({ bundleId: bundleIdSchema });
11485
+ var projectSchema = z6.object({ bundleIds: z6.array(bundleIdSchema), expectedRevisions: z6.record(z6.string()).optional() }).strict();
11486
+ function tenant(request, authRequired2) {
11487
+ const authenticated = request.user?.tenantId;
11488
+ const header = request.headers["x-tenant-id"];
11489
+ const requested = Array.isArray(header) ? header[0] : header;
11490
+ if (authenticated) {
11491
+ if (requested && requested !== authenticated) throw new CapabilityBundleServiceError("INVALID_INPUT", "Forbidden", 403);
11492
+ return authenticated;
11493
+ }
11494
+ if (authRequired2) throw new CapabilityBundleServiceError("INVALID_INPUT", "Unauthorized", 401);
11495
+ return requested || "default";
11496
+ }
11497
+ function sendError(reply, error) {
11498
+ if (error instanceof CapabilityBundleServiceError) return reply.status(error.statusCode).send({ success: false, code: error.code, message: error.message });
11499
+ if (error instanceof Error && error.name === "CapabilityBundleValidationError") {
11500
+ const validationError = error;
11501
+ const code = validationError.code === "CAPABILITY_BUNDLE_INELIGIBLE_CAPABILITY" ? "BUNDLE_PLUGIN_NOT_ELIGIBLE" : validationError.code === "CAPABILITY_BUNDLE_CREDENTIALS_FORBIDDEN" || validationError.code === "CAPABILITY_BUNDLE_INVALID_CONFIG" ? "INVALID_BUNDLE_CONFIG" : validationError.code;
11502
+ return reply.status(400).send({ success: false, code, message: error.message, capabilityType: validationError.capabilityType, fieldPath: validationError.fieldPath });
11503
+ }
11504
+ logger3.error("Capability bundle controller error", { error: error instanceof Error ? error : new Error(String(error)) });
11505
+ return reply.status(500).send({ success: false, code: "INTERNAL_ERROR", message: "Internal server error" });
11506
+ }
11507
+ var CapabilityBundleController = class {
11508
+ constructor(deps, options = {}) {
11509
+ this.service = new CapabilityBundleService(deps);
11510
+ this.authRequired = options.authRequired ?? true;
11511
+ }
11512
+ async listBundles(request, reply) {
11513
+ try {
11514
+ const result = await this.service.list(tenant(request, this.authRequired));
11515
+ return reply.send({ success: true, data: result.bundles, errors: result.errors });
11516
+ } catch (error) {
11517
+ return sendError(reply, error);
11518
+ }
11519
+ }
11520
+ async getBundle(request, reply) {
11521
+ try {
11522
+ const tenantId = tenant(request, this.authRequired);
11523
+ const parsed = bundleParamsSchema.safeParse(request.params);
11524
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle ID" });
11525
+ return reply.send({ success: true, data: await this.service.get(tenantId, parsed.data.bundleId) });
11526
+ } catch (error) {
11527
+ return sendError(reply, error);
11528
+ }
11529
+ }
11530
+ async createBundle(request, reply) {
11531
+ try {
11532
+ const tenantId = tenant(request, this.authRequired);
11533
+ const parsed = createSchema.safeParse(request.body);
11534
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle body" });
11535
+ return reply.status(201).send({ success: true, data: await this.service.create(tenantId, parsed.data) });
11536
+ } catch (error) {
11537
+ return sendError(reply, error);
11538
+ }
11539
+ }
11540
+ async updateBundle(request, reply) {
11541
+ try {
11542
+ const tenantId = tenant(request, this.authRequired);
11543
+ const params = bundleParamsSchema.safeParse(request.params);
11544
+ const parsed = updateSchema.safeParse(request.body);
11545
+ if (!params.success || !parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle request" });
11546
+ return reply.send({ success: true, data: await this.service.update(tenantId, params.data.bundleId, parsed.data) });
11547
+ } catch (error) {
11548
+ return sendError(reply, error);
11549
+ }
11550
+ }
11551
+ async deleteBundle(request, reply) {
11552
+ try {
11553
+ const tenantId = tenant(request, this.authRequired);
11554
+ const parsed = bundleParamsSchema.safeParse(request.params);
11555
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle ID" });
11556
+ await this.service.remove(tenantId, parsed.data.bundleId);
11557
+ return reply.send({ success: true, data: null });
11558
+ } catch (error) {
11559
+ return sendError(reply, error);
11560
+ }
11561
+ }
11562
+ async updateProjectBundles(request, reply) {
11563
+ try {
11564
+ const tenantId = tenant(request, this.authRequired);
11565
+ const parsed = projectSchema.safeParse(request.body);
11566
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid project capability bundle body" });
11567
+ const result = await this.service.project(tenantId, request.params.projectId, parsed.data.bundleIds, true, parsed.data.expectedRevisions);
11568
+ return reply.send({ success: true, data: { project: result.project, persistedBundleIds: result.persistedBundleIds, bundles: result.bundles, preview: result.preview } });
11569
+ } catch (error) {
11570
+ return sendError(reply, error);
11571
+ }
11572
+ }
11573
+ async getProjectBundles(request, reply) {
11574
+ try {
11575
+ const result = await this.service.getProjectBundles(tenant(request, this.authRequired), request.params.projectId);
11576
+ if (result.preview.errors.some((issue2) => issue2.code === "CAPABILITY_BUNDLE_CREDENTIALS_FORBIDDEN")) return reply.status(400).send({ success: false, code: "INVALID_BUNDLE_CONFIG", message: "Capability bundle record is invalid" });
11577
+ return reply.send({ success: true, data: { project: result.project, persistedBundleIds: result.persistedBundleIds, bundles: result.bundles, preview: result.preview } });
11578
+ } catch (error) {
11579
+ return sendError(reply, error);
11580
+ }
11581
+ }
11582
+ async getProjectPreview(request, reply) {
11583
+ try {
11584
+ const preview = await this.service.previewProject(tenant(request, this.authRequired), request.params.projectId);
11585
+ return reply.send({ success: true, data: preview });
11586
+ } catch (error) {
11587
+ return sendError(reply, error);
11588
+ }
11589
+ }
11590
+ async postProjectPreview(request, reply) {
11591
+ try {
11592
+ const tenantId = tenant(request, this.authRequired);
11593
+ const parsed = projectSchema.safeParse(request.body);
11594
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid project capability bundle body" });
11595
+ const result = await this.service.project(tenantId, request.params.projectId, parsed.data.bundleIds, false);
11596
+ return reply.send({ success: true, data: result.preview });
11597
+ } catch (error) {
11598
+ return sendError(reply, error);
11599
+ }
11600
+ }
11601
+ };
11602
+ function registerCapabilityBundleRoutes(app2, controller) {
11603
+ app2.get("/api/capability-bundles", controller.listBundles.bind(controller));
11604
+ app2.post("/api/capability-bundles", controller.createBundle.bind(controller));
11605
+ app2.get("/api/capability-bundles/:bundleId", controller.getBundle.bind(controller));
11606
+ app2.put("/api/capability-bundles/:bundleId", controller.updateBundle.bind(controller));
11607
+ app2.delete("/api/capability-bundles/:bundleId", controller.deleteBundle.bind(controller));
11608
+ app2.get("/api/projects/:projectId/capability-bundles", controller.getProjectBundles.bind(controller));
11609
+ app2.put("/api/projects/:projectId/capability-bundles", controller.updateProjectBundles.bind(controller));
11610
+ app2.get("/api/projects/:projectId/capability-preview", controller.getProjectPreview.bind(controller));
11611
+ app2.post("/api/projects/:projectId/capability-preview", controller.postProjectPreview.bind(controller));
11612
+ }
11613
+
10642
11614
  // src/routes/index.ts
10643
11615
  var registerLatticeRoutes = (app2, channelDeps) => {
10644
11616
  const webAppStore = getStoreLattice16("default", "agentWebApp").store;
10645
11617
  const assistantStore = getStoreLattice16("default", "assistant").store;
10646
11618
  const projectStore = getStoreLattice16("default", "project").store;
11619
+ registerCapabilityBundleRoutes(app2, new CapabilityBundleController({
11620
+ bundleStore: getStoreLattice16("default", "capabilityBundle").store,
11621
+ projectStore
11622
+ }, { authRequired: process.env.AUTH_REQUIRED === "true" }));
10647
11623
  registerAgentWebAppRoutes(app2, {
10648
11624
  service: new AgentWebAppService({
10649
11625
  webAppStore,
@@ -12620,13 +13596,13 @@ function createRateLimitMiddleware(maxRequests = 10, windowMs = 60 * 1e3, maxEnt
12620
13596
  }
12621
13597
 
12622
13598
  // src/router/middlewares/auditLogger.ts
12623
- var logger3 = new Logger({ serviceName: "lattice/gateway/audit" });
13599
+ var logger4 = new Logger({ serviceName: "lattice/gateway/audit" });
12624
13600
  function createAuditLoggerMiddleware() {
12625
13601
  return async (ctx, next) => {
12626
13602
  const start2 = Date.now();
12627
13603
  try {
12628
13604
  await next();
12629
- logger3.info("message routed", {
13605
+ logger4.info("message routed", {
12630
13606
  event: "message:routed",
12631
13607
  channel: ctx.inboundMessage.channel,
12632
13608
  senderId: ctx.inboundMessage.sender.id,
@@ -12636,7 +13612,7 @@ function createAuditLoggerMiddleware() {
12636
13612
  status: "success"
12637
13613
  });
12638
13614
  } catch (error) {
12639
- logger3.error(
13615
+ logger4.error(
12640
13616
  error instanceof Error ? error.message : String(error),
12641
13617
  {
12642
13618
  event: "message:error",
@@ -12794,7 +13770,7 @@ ${summary}`
12794
13770
  console.log(
12795
13771
  `\u5C06\u5728 ${delayMs}ms \u540E\u91CD\u8BD5\u4EFB\u52A1 (${nextRetryCount}/${maxRetries})`
12796
13772
  );
12797
- await new Promise((resolve2) => setTimeout(resolve2, delayMs));
13773
+ await new Promise((resolve3) => setTimeout(resolve3, delayMs));
12798
13774
  return handleAgentTask(taskRequest, nextRetryCount);
12799
13775
  }
12800
13776
  if (callback_event) {
@@ -12994,7 +13970,7 @@ var DEFAULT_LOGGER_CONFIG = {
12994
13970
  loggerName: "lattice/gateway"
12995
13971
  };
12996
13972
  var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
12997
- var logger4 = loggerLattice.client;
13973
+ var logger5 = loggerLattice.client;
12998
13974
  function initializeLogger(config) {
12999
13975
  if (loggerLatticeManager.hasLattice("default")) {
13000
13976
  loggerLatticeManager.removeLattice("default");
@@ -13027,7 +14003,9 @@ var getHeaderValue = (header) => {
13027
14003
  var authRequired = process.env.AUTH_REQUIRED === "true";
13028
14004
  registerConsoleAuthHook(app, { authRequired });
13029
14005
  app.addHook("onRequest", (request, reply, done) => {
13030
- if (!request.headers["x-project-id"]) {
14006
+ const requestPath = request.url.split("?", 1)[0];
14007
+ const isAgentRunRequest = requestPath === "/api/runs" || requestPath === "/api/resume_stream" || requestPath.includes("/threads/") && requestPath.endsWith("/recover");
14008
+ if (!request.headers["x-project-id"] && !isAgentRunRequest) {
13031
14009
  request.headers["x-project-id"] = "default";
13032
14010
  }
13033
14011
  done();
@@ -13077,7 +14055,7 @@ app.setErrorHandler((error, request, reply) => {
13077
14055
  "x-request-id": getHeaderValue(request.headers["x-request-id"]),
13078
14056
  "x-user-id": getHeaderValue(request.headers["x-user-id"])
13079
14057
  };
13080
- logger4.error(
14058
+ logger5.error(
13081
14059
  `\u8BF7\u6C42\u9519\u8BEF: ${request.method} ${request.url} error:${error.message}`,
13082
14060
  {
13083
14061
  ...context,
@@ -13112,34 +14090,34 @@ async function restoreMcpConnections() {
13112
14090
  const storeLattice = getStoreLattice27("default", "mcp");
13113
14091
  const store = storeLattice.store;
13114
14092
  if (!store) {
13115
- logger4.info("MCP store not configured, skipping connection restoration");
14093
+ logger5.info("MCP store not configured, skipping connection restoration");
13116
14094
  return;
13117
14095
  }
13118
14096
  const configs = await store.getAllConfigs("default");
13119
14097
  const connectedConfigs = configs.filter((c2) => c2.status === "connected");
13120
14098
  if (connectedConfigs.length === 0) return;
13121
- logger4.info(`Restoring ${connectedConfigs.length} MCP server connections...`);
14099
+ logger5.info(`Restoring ${connectedConfigs.length} MCP server connections...`);
13122
14100
  const { convertToConnection } = await import("./mcp-configs-SHRMQHIL.mjs");
13123
14101
  for (const config of connectedConfigs) {
13124
14102
  try {
13125
14103
  const connection = convertToConnection(config.config);
13126
14104
  mcpManager.addServer(config.key, connection);
13127
14105
  } catch (err) {
13128
- logger4.warn(`Failed to prepare MCP server "${config.key}" for restoration`, { error: err });
14106
+ logger5.warn(`Failed to prepare MCP server "${config.key}" for restoration`, { error: err });
13129
14107
  }
13130
14108
  }
13131
14109
  await mcpManager.connect();
13132
14110
  for (const config of connectedConfigs) {
13133
14111
  try {
13134
14112
  await mcpManager.registerToolsToToolLattice(config.key, config.selectedTools);
13135
- logger4.info(`MCP server "${config.key}" restored with ${config.selectedTools.length} tools`);
14113
+ logger5.info(`MCP server "${config.key}" restored with ${config.selectedTools.length} tools`);
13136
14114
  } catch (err) {
13137
- logger4.warn(`Failed to register tools for MCP server "${config.key}"`, { error: err });
14115
+ logger5.warn(`Failed to register tools for MCP server "${config.key}"`, { error: err });
13138
14116
  }
13139
14117
  }
13140
- logger4.info(`MCP connection restoration complete: ${connectedConfigs.length} servers`);
14118
+ logger5.info(`MCP connection restoration complete: ${connectedConfigs.length} servers`);
13141
14119
  } catch (err) {
13142
- logger4.error("MCP connection restoration failed", { error: err });
14120
+ logger5.error("MCP connection restoration failed", { error: err });
13143
14121
  }
13144
14122
  }
13145
14123
  var start = async (config) => {
@@ -13152,7 +14130,7 @@ var start = async (config) => {
13152
14130
  file: config.loggerConfig.file || DEFAULT_LOGGER_CONFIG.file
13153
14131
  };
13154
14132
  loggerLattice = initializeLogger(loggerConfig);
13155
- logger4 = loggerLattice.client;
14133
+ logger5 = loggerLattice.client;
13156
14134
  }
13157
14135
  app.decorate("loggerLattice", loggerLattice);
13158
14136
  let channelDeps;
@@ -13177,7 +14155,7 @@ var start = async (config) => {
13177
14155
  });
13178
14156
  channelDeps = { router, installationStore };
13179
14157
  } catch (err) {
13180
- logger4.warn("Channel infrastructure unavailable", {
14158
+ logger5.warn("Channel infrastructure unavailable", {
13181
14159
  error: err instanceof Error ? err.message : String(err)
13182
14160
  });
13183
14161
  }
@@ -13185,7 +14163,7 @@ var start = async (config) => {
13185
14163
  try {
13186
14164
  const menuStore = getStoreLattice27("default", "menu").store;
13187
14165
  setMenuRegistry(menuStore);
13188
- logger4.info("Menu registry initialized");
14166
+ logger5.info("Menu registry initialized");
13189
14167
  } catch {
13190
14168
  }
13191
14169
  registerAllBuiltinEntities();
@@ -13218,15 +14196,15 @@ var start = async (config) => {
13218
14196
  getProjectStore: () => projectStore
13219
14197
  });
13220
14198
  a2a.registerA2ARoutes(app);
13221
- logger4.info("A2A standard routes registered");
14199
+ logger5.info("A2A standard routes registered");
13222
14200
  } catch (err) {
13223
- logger4.warn("A2A standard routes unavailable", {
14201
+ logger5.warn("A2A standard routes unavailable", {
13224
14202
  error: err instanceof Error ? err.message : String(err)
13225
14203
  });
13226
14204
  }
13227
14205
  if (!sandboxLatticeManager2.hasLattice("default")) {
13228
14206
  sandboxLatticeManager2.registerLattice("default", getConfiguredSandboxProvider());
13229
- logger4.info("Registered sandbox manager from env configuration");
14207
+ logger5.info("Registered sandbox manager from env configuration");
13230
14208
  }
13231
14209
  try {
13232
14210
  const { ResourceController } = await import("./resources-VA7LSDKN.mjs");
@@ -13236,12 +14214,12 @@ var start = async (config) => {
13236
14214
  store: sharedResourceStore,
13237
14215
  sandboxManager: sandboxLatticeManager2,
13238
14216
  cache,
13239
- logger: logger4
14217
+ logger: logger5
13240
14218
  });
13241
14219
  registerResourceRoutes(app, resourceController);
13242
- logger4.info("Resource share routes registered");
14220
+ logger5.info("Resource share routes registered");
13243
14221
  } catch (err) {
13244
- logger4.warn("Resource share infrastructure unavailable", {
14222
+ logger5.warn("Resource share infrastructure unavailable", {
13245
14223
  error: err instanceof Error ? err.message : String(err)
13246
14224
  });
13247
14225
  }
@@ -13253,14 +14231,14 @@ var start = async (config) => {
13253
14231
  { deps: { router: channelDeps.router } }
13254
14232
  );
13255
14233
  } catch (err) {
13256
- logger4.error("Failed to start channel connections", {
14234
+ logger5.error("Failed to start channel connections", {
13257
14235
  error: err instanceof Error ? err.message : String(err)
13258
14236
  });
13259
14237
  }
13260
14238
  }
13261
14239
  const target_port = config?.port || Number(process.env.PORT) || 4001;
13262
14240
  await app.listen({ port: target_port, host: "0.0.0.0" });
13263
- logger4.info(`Lattice Gateway is running on port: ${target_port}`);
14241
+ logger5.info(`Lattice Gateway is running on port: ${target_port}`);
13264
14242
  const queueServiceConfig = config?.queueServiceConfig;
13265
14243
  if (queueServiceConfig) {
13266
14244
  setQueueServiceType(queueServiceConfig.type);
@@ -13271,16 +14249,16 @@ var start = async (config) => {
13271
14249
  }
13272
14250
  if (process.env.AXIOM_RESTORE_PENDING_ON_STARTUP === "true") {
13273
14251
  agentInstanceManager9.restore().then((stats) => {
13274
- logger4.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
14252
+ logger5.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
13275
14253
  }).catch((error) => {
13276
- logger4.error("Agent recovery failed", { error });
14254
+ logger5.error("Agent recovery failed", { error });
13277
14255
  });
13278
14256
  }
13279
14257
  restoreMcpConnections().catch((error) => {
13280
- logger4.error("MCP connection restoration failed", { error });
14258
+ logger5.error("MCP connection restoration failed", { error });
13281
14259
  });
13282
14260
  } catch (err) {
13283
- logger4.error("Server start failed", { error: err });
14261
+ logger5.error("Server start failed", { error: err });
13284
14262
  process.exit(1);
13285
14263
  }
13286
14264
  };